@enyo-energy/sunspec-sdk 0.0.50 → 0.0.51

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.
@@ -36,6 +36,9 @@ class SunspecModbusClient {
36
36
  openCount = 0;
37
37
  closeCount = 0;
38
38
  currentlyOpen = 0;
39
+ // Serializes connection-state transitions so concurrent callers cannot open a second
40
+ // TCP socket while the first is still alive (some Modbus devices allow only one client).
41
+ operationChain = Promise.resolve();
39
42
  constructor(energyApp) {
40
43
  this.energyApp = energyApp;
41
44
  this.connectionHealth = new EnergyAppModbusConnectionHealth_js_1.EnergyAppModbusConnectionHealth();
@@ -49,25 +52,23 @@ class SunspecModbusClient {
49
52
  * @param secondaryHost Optional secondary host (ipAddress) for fallback during reconnection
50
53
  */
51
54
  async connect(host, port = 502, unitId = 1, secondaryHost) {
52
- if (this.connected) {
53
- await this.disconnect();
54
- }
55
- // Store connection parameters for potential reconnection
56
- this.connectionParams = { primaryHost: host, secondaryHost, port, unitId };
57
- this.modbusClient = await this.energyApp.useModbus().connect({
58
- host,
59
- port,
60
- unitId,
61
- timeout: 5000
55
+ return this.withConnectionLock(async () => {
56
+ const sameParams = this.connected
57
+ && this.connectionParams !== null
58
+ && this.connectionParams.primaryHost === host
59
+ && this.connectionParams.port === port
60
+ && this.connectionParams.unitId === unitId;
61
+ if (sameParams) {
62
+ console.debug(`connect(): already connected to ${host}:${port} unit ${unitId}, skipping`);
63
+ return;
64
+ }
65
+ if (this.connected || this.modbusClient) {
66
+ await this._closeSocket();
67
+ }
68
+ this.connectionParams = { primaryHost: host, secondaryHost, port, unitId };
69
+ await this._openSocket(host, port, unitId);
70
+ console.log(`Connected to Sunspec device at ${host}:${port} unit ${unitId} (opens=${this.openCount}, closes=${this.closeCount}, current=${this.currentlyOpen})`);
62
71
  });
63
- // Create fault-tolerant reader with connection health monitoring
64
- if (this.modbusClient) {
65
- this.faultTolerantReader = new EnergyAppModbusFaultTolerantReader_js_1.EnergyAppModbusFaultTolerantReader(this.modbusClient, this.connectionHealth);
66
- }
67
- this.connected = true;
68
- this.connectionHealth.recordSuccess();
69
- this.recordOpen();
70
- console.log(`Connected to Sunspec device at ${host}:${port} unit ${unitId} (opens=${this.openCount}, closes=${this.closeCount}, current=${this.currentlyOpen})`);
71
72
  }
72
73
  /**
73
74
  * Disconnect from Modbus device.
@@ -76,18 +77,16 @@ class SunspecModbusClient {
76
77
  * They will be overwritten by the next connect() call anyway.
77
78
  */
78
79
  async disconnect() {
79
- if (this.modbusClient && this.connected) {
80
- await this.modbusClient.disconnect();
81
- this.modbusClient = null;
82
- this.faultTolerantReader = null;
83
- this.connected = false;
84
- this.discoveredModels.clear();
85
- this.recordClose();
80
+ return this.withConnectionLock(async () => {
81
+ if (!this.modbusClient && !this.connected) {
82
+ return;
83
+ }
86
84
  const host = this.connectionParams?.primaryHost ?? 'unknown';
87
85
  const port = this.connectionParams?.port ?? 0;
88
86
  const unitId = this.connectionParams?.unitId ?? 0;
87
+ await this._closeSocket();
89
88
  console.log(`Disconnected from Sunspec device at ${host}:${port} unit ${unitId} (opens=${this.openCount}, closes=${this.closeCount}, current=${this.currentlyOpen})`);
90
- }
89
+ });
91
90
  }
92
91
  /**
93
92
  * Reconnect using stored connection parameters
@@ -95,78 +94,122 @@ class SunspecModbusClient {
95
94
  * Returns true if reconnection was successful, false otherwise
96
95
  */
97
96
  async reconnect() {
98
- if (!this.connectionParams) {
99
- console.error('Cannot reconnect: no connection parameters stored. Call connect() first.');
97
+ return this.withConnectionLock(async () => {
98
+ if (!this.connectionParams) {
99
+ console.error('Cannot reconnect: no connection parameters stored. Call connect() first.');
100
+ return false;
101
+ }
102
+ const { primaryHost, secondaryHost, port, unitId } = this.connectionParams;
103
+ console.log(`Attempting to reconnect to primary host ${primaryHost}:${port} unit ${unitId}...`);
104
+ const primarySuccess = await this.attemptConnection(primaryHost, port, unitId);
105
+ if (primarySuccess) {
106
+ console.log(`Successfully reconnected to primary host ${primaryHost}:${port} unit ${unitId}`);
107
+ return true;
108
+ }
109
+ if (secondaryHost && secondaryHost !== primaryHost) {
110
+ console.log(`Primary host failed, attempting secondary host ${secondaryHost}:${port} unit ${unitId}...`);
111
+ const secondarySuccess = await this.attemptConnection(secondaryHost, port, unitId);
112
+ if (secondarySuccess) {
113
+ console.log(`Successfully reconnected to secondary host ${secondaryHost}:${port} unit ${unitId}`);
114
+ return true;
115
+ }
116
+ }
117
+ console.error(`Reconnection failed to all available hosts`);
100
118
  return false;
119
+ });
120
+ }
121
+ /**
122
+ * Attempt to establish a connection to a specific host. Caller must hold the connection lock.
123
+ * Returns true if successful, false otherwise.
124
+ */
125
+ async attemptConnection(host, port, unitId) {
126
+ if (this.connected || this.modbusClient) {
127
+ await this._closeSocket();
101
128
  }
102
- const { primaryHost, secondaryHost, port, unitId } = this.connectionParams;
103
- // Try primary host first
104
- console.log(`Attempting to reconnect to primary host ${primaryHost}:${port} unit ${unitId}...`);
105
- const primarySuccess = await this.attemptConnection(primaryHost, port, unitId);
106
- if (primarySuccess) {
107
- console.log(`Successfully reconnected to primary host ${primaryHost}:${port} unit ${unitId}`);
129
+ try {
130
+ await this._openSocket(host, port, unitId);
131
+ console.log(`Connection attempt to ${host}:${port} unit ${unitId} succeeded (opens=${this.openCount}, closes=${this.closeCount}, current=${this.currentlyOpen})`);
108
132
  return true;
109
133
  }
110
- // If primary failed and secondary is available, try secondary
111
- if (secondaryHost && secondaryHost !== primaryHost) {
112
- console.log(`Primary host failed, attempting secondary host ${secondaryHost}:${port} unit ${unitId}...`);
113
- const secondarySuccess = await this.attemptConnection(secondaryHost, port, unitId);
114
- if (secondarySuccess) {
115
- console.log(`Successfully reconnected to secondary host ${secondaryHost}:${port} unit ${unitId}`);
116
- return true;
117
- }
134
+ catch (error) {
135
+ console.error(`Connection attempt to ${host}:${port} failed: ${error}`);
136
+ return false;
118
137
  }
119
- console.error(`Reconnection failed to all available hosts`);
120
- this.connected = false;
121
- return false;
122
138
  }
123
139
  /**
124
- * Attempt to establish a connection to a specific host
125
- * Returns true if successful, false otherwise
140
+ * Open a new Modbus socket and wire up the fault-tolerant reader. On any failure, the
141
+ * just-opened socket (if any) is closed so we never leak. Caller must hold the connection
142
+ * lock and ensure no prior socket is open (use _closeSocket first).
126
143
  */
127
- async attemptConnection(host, port, unitId) {
144
+ async _openSocket(host, port, unitId) {
145
+ let candidate = null;
128
146
  try {
129
- // Disconnect existing connection if any
130
- if (this.modbusClient) {
131
- const wasConnected = this.connected;
132
- try {
133
- await this.modbusClient.disconnect();
134
- }
135
- catch (e) {
136
- // Ignore disconnect errors during reconnection
137
- }
138
- this.modbusClient = null;
139
- this.faultTolerantReader = null;
140
- if (wasConnected) {
141
- this.connected = false;
142
- this.recordClose();
143
- }
144
- }
145
- // Attempt connection
146
- this.modbusClient = await this.energyApp.useModbus().connect({
147
+ candidate = await this.energyApp.useModbus().connect({
147
148
  host,
148
149
  port,
149
150
  unitId,
150
151
  timeout: 5000
151
152
  });
152
- // Create fault-tolerant reader with connection health monitoring
153
- if (this.modbusClient) {
154
- this.faultTolerantReader = new EnergyAppModbusFaultTolerantReader_js_1.EnergyAppModbusFaultTolerantReader(this.modbusClient, this.connectionHealth);
153
+ if (!candidate) {
154
+ throw new Error(`useModbus().connect returned null for ${host}:${port} unit ${unitId}`);
155
155
  }
156
+ this.faultTolerantReader = new EnergyAppModbusFaultTolerantReader_js_1.EnergyAppModbusFaultTolerantReader(candidate, this.connectionHealth);
157
+ this.modbusClient = candidate;
156
158
  this.connected = true;
157
159
  this.connectionHealth.recordSuccess();
158
160
  this.recordOpen();
159
- console.log(`Connection attempt to ${host}:${port} unit ${unitId} succeeded (opens=${this.openCount}, closes=${this.closeCount}, current=${this.currentlyOpen})`);
160
- return true;
161
161
  }
162
- catch (error) {
163
- console.error(`Connection attempt to ${host}:${port} failed: ${error}`);
164
- return false;
162
+ catch (err) {
163
+ if (candidate) {
164
+ try {
165
+ await candidate.disconnect();
166
+ }
167
+ catch { /* ignore */ }
168
+ }
169
+ this.modbusClient = null;
170
+ this.faultTolerantReader = null;
171
+ this.connected = false;
172
+ throw err;
165
173
  }
166
174
  }
175
+ /**
176
+ * Close the current Modbus socket if any. Idempotent. Caller must hold the connection lock.
177
+ */
178
+ async _closeSocket() {
179
+ if (!this.modbusClient && !this.connected)
180
+ return;
181
+ const client = this.modbusClient;
182
+ const wasConnected = this.connected;
183
+ this.modbusClient = null;
184
+ this.faultTolerantReader = null;
185
+ this.connected = false;
186
+ this.discoveredModels.clear();
187
+ if (client) {
188
+ try {
189
+ await client.disconnect();
190
+ }
191
+ catch { /* ignore */ }
192
+ }
193
+ if (wasConnected)
194
+ this.recordClose();
195
+ }
196
+ /**
197
+ * Run `fn` with exclusive access to connection-state transitions. Subsequent calls queue
198
+ * behind any in-flight one. A rejected `fn` does not poison the chain for later callers.
199
+ */
200
+ withConnectionLock(fn) {
201
+ const run = this.operationChain.then(fn, fn);
202
+ this.operationChain = run.catch(() => undefined);
203
+ return run;
204
+ }
167
205
  recordOpen() {
168
206
  this.openCount++;
169
207
  this.currentlyOpen++;
208
+ if (this.currentlyOpen > 1) {
209
+ console.error(`SunspecModbusClient: currentlyOpen=${this.currentlyOpen} after open ` +
210
+ `(opens=${this.openCount}, closes=${this.closeCount}). ` +
211
+ `This indicates a code path bypassing the connection lock — please investigate.`);
212
+ }
170
213
  }
171
214
  recordClose() {
172
215
  this.closeCount++;
@@ -516,7 +559,7 @@ class SunspecModbusClient {
516
559
  async readInverterData() {
517
560
  const model = this.findModel(sunspec_interfaces_js_1.SunspecModelId.Inverter3Phase);
518
561
  if (!model) {
519
- console.log('Inverter model 103 not found, trying single phase model 101');
562
+ console.debug('Inverter model 103 not found, trying single phase model 101');
520
563
  const singlePhaseModel = this.findModel(sunspec_interfaces_js_1.SunspecModelId.InverterSinglePhase);
521
564
  if (!singlePhaseModel) {
522
565
  console.error('No inverter model found');
@@ -525,10 +568,10 @@ class SunspecModbusClient {
525
568
  console.warn('IMPORTANT: Working with single-phase inverter, but 3-phase is expected!');
526
569
  return this.readSinglePhaseInverterData(singlePhaseModel);
527
570
  }
528
- console.log(`Found 3-phase inverter model 103 at address ${model.address} with length ${model.length}`);
571
+ console.debug(`Found 3-phase inverter model 103 at address ${model.address} with length ${model.length}`);
529
572
  try {
530
573
  // Read entire model block in a single Modbus call
531
- console.log(`Reading Inverter Data from Model ${model.id} at base address: ${model.address}`);
574
+ console.debug(`Reading Inverter Data from Model ${model.id} at base address: ${model.address}`);
532
575
  const buffer = await this.readModelBlock(model);
533
576
  // Extract all scale factors from buffer
534
577
  const scaleFactors = this.extractInverterScaleFactors(buffer);
@@ -612,7 +655,7 @@ class SunspecModbusClient {
612
655
  */
613
656
  async readSinglePhaseInverterData(model) {
614
657
  try {
615
- console.log(`Reading Single-Phase Inverter Data from Model 101 at base address: ${model.address}`);
658
+ console.debug(`Reading Single-Phase Inverter Data from Model 101 at base address: ${model.address}`);
616
659
  // Read entire model block in a single Modbus call
617
660
  const buffer = await this.readModelBlock(model);
618
661
  // Extract scale factors from buffer
@@ -766,7 +809,7 @@ class SunspecModbusClient {
766
809
  async readMPPTScaleFactors() {
767
810
  const model = this.findModel(sunspec_interfaces_js_1.SunspecModelId.MPPT);
768
811
  if (!model) {
769
- console.log('MPPT model 160 not found');
812
+ console.debug('MPPT model 160 not found');
770
813
  return null;
771
814
  }
772
815
  try {
@@ -800,7 +843,7 @@ class SunspecModbusClient {
800
843
  this.isUnimplementedValue(dcCurrentRaw, 'uint16') &&
801
844
  this.isUnimplementedValue(dcVoltageRaw, 'uint16') &&
802
845
  this.isUnimplementedValue(dcPowerRaw, 'uint16')) {
803
- console.log(`MPPT module ${moduleId} appears to be unconnected (all values are 0xFFFF)`);
846
+ console.debug(`MPPT module ${moduleId} appears to be unconnected (all values are 0xFFFF)`);
804
847
  return null;
805
848
  }
806
849
  const temperatureScaleFactor = -1;
@@ -838,7 +881,7 @@ class SunspecModbusClient {
838
881
  async readMPPTData(moduleId = 1) {
839
882
  const model = this.findModel(sunspec_interfaces_js_1.SunspecModelId.MPPT);
840
883
  if (!model) {
841
- console.log('MPPT model 160 not found');
884
+ console.debug('MPPT model 160 not found');
842
885
  return null;
843
886
  }
844
887
  try {
@@ -859,7 +902,7 @@ class SunspecModbusClient {
859
902
  const mpptData = [];
860
903
  const model = this.findModel(sunspec_interfaces_js_1.SunspecModelId.MPPT);
861
904
  if (!model) {
862
- console.log('MPPT model 160 not found');
905
+ console.debug('MPPT model 160 not found');
863
906
  return [];
864
907
  }
865
908
  try {
@@ -871,16 +914,16 @@ class SunspecModbusClient {
871
914
  const count = this.extractValue(buffer, 8, 'uint16');
872
915
  if (!this.isUnimplementedValue(count, 'uint16') && count > 0 && count <= 20) {
873
916
  moduleCount = count;
874
- console.log(`MPPT module count from register 8: ${moduleCount}`);
917
+ console.debug(`MPPT module count from register 8: ${moduleCount}`);
875
918
  }
876
919
  else {
877
- console.log(`Invalid or unimplemented module count (${count}), using default: ${moduleCount}`);
920
+ console.debug(`Invalid or unimplemented module count (${count}), using default: ${moduleCount}`);
878
921
  }
879
922
  // Extract each MPPT module from the same buffer
880
923
  for (let i = 1; i <= moduleCount; i++) {
881
924
  try {
882
925
  const data = this.extractMPPTModuleData(buffer, model, i, scaleFactors);
883
- console.log(`MPPT ${i} has id ${data?.id} (${data?.stringId}) with ${data?.dcPower}W`);
926
+ console.debug(`MPPT ${i} has id ${data?.id} (${data?.stringId}) with ${data?.dcPower}W`);
884
927
  if (data &&
885
928
  (data.dcCurrent !== undefined ||
886
929
  data.dcVoltage !== undefined ||
@@ -1028,10 +1071,10 @@ class SunspecModbusClient {
1028
1071
  async readBatteryBaseData() {
1029
1072
  const model = this.findModel(sunspec_interfaces_js_1.SunspecModelId.BatteryBase);
1030
1073
  if (!model) {
1031
- console.log('Battery Base model 802 not found');
1074
+ console.debug('Battery Base model 802 not found');
1032
1075
  return null;
1033
1076
  }
1034
- console.log(`Reading Battery Base Data from Model 802 at base address: ${model.address}`);
1077
+ console.debug(`Reading Battery Base Data from Model 802 at base address: ${model.address}`);
1035
1078
  try {
1036
1079
  // Read entire model block in a single Modbus call
1037
1080
  const buffer = await this.readModelBlock(model);
@@ -1174,14 +1217,14 @@ class SunspecModbusClient {
1174
1217
  model = this.findModel(sunspec_interfaces_js_1.SunspecModelId.BatteryControl);
1175
1218
  }
1176
1219
  if (!model) {
1177
- console.log('No battery model found');
1220
+ console.debug('No battery model found');
1178
1221
  return null;
1179
1222
  }
1180
- console.log(`Reading Battery Data from Model ${model.id} at base address: ${model.address}`);
1223
+ console.debug(`Reading Battery Data from Model ${model.id} at base address: ${model.address}`);
1181
1224
  try {
1182
1225
  if (model.id === 124) {
1183
1226
  // Model 124: Basic Storage Controls
1184
- console.log('Using Model 124 (Basic Storage Controls)');
1227
+ console.debug('Using Model 124 (Basic Storage Controls)');
1185
1228
  // Read entire model block in a single Modbus call
1186
1229
  const buffer = await this.readModelBlock(model);
1187
1230
  // Extract scale factors from buffer (offsets 18-25)
@@ -1271,19 +1314,19 @@ class SunspecModbusClient {
1271
1314
  // Calculate charge/discharge power if rates are available
1272
1315
  if (data.inWRte !== undefined && data.wChaMax !== undefined) {
1273
1316
  data.chargePower = (data.inWRte / 100) * data.wChaMax;
1274
- console.log(`Calculated Charge Power: (inWRte: ${data.inWRte}, wChaMax: ${data.wChaMax}) ${data.chargePower?.toFixed(2)} W`);
1317
+ console.debug(`Calculated Charge Power: (inWRte: ${data.inWRte}, wChaMax: ${data.wChaMax}) ${data.chargePower?.toFixed(2)} W`);
1275
1318
  }
1276
1319
  if (data.outWRte !== undefined && data.wChaMax !== undefined) {
1277
1320
  // Assuming WDisChaMax is similar to WChaMax for simplicity
1278
1321
  data.dischargePower = Math.abs((data.outWRte / 100) * data.wChaMax);
1279
- console.log(`Calculated Discharge Power (inWRte: ${data.outWRte}, wChaMax: ${data.wChaMax}): ${data.dischargePower?.toFixed(2)} W`);
1322
+ console.debug(`Calculated Discharge Power (inWRte: ${data.outWRte}, wChaMax: ${data.wChaMax}): ${data.dischargePower?.toFixed(2)} W`);
1280
1323
  }
1281
1324
  console.debug('[Model 124] Battery Data:', data);
1282
1325
  return data;
1283
1326
  }
1284
1327
  else if (model.id === 802) {
1285
1328
  // Model 802: Battery Base
1286
- console.log('Using Model 802 (Battery Base)');
1329
+ console.debug('Using Model 802 (Battery Base)');
1287
1330
  const baseData = await this.readBatteryBaseData();
1288
1331
  if (!baseData) {
1289
1332
  return null;
@@ -1320,7 +1363,7 @@ class SunspecModbusClient {
1320
1363
  }
1321
1364
  else {
1322
1365
  // Handle other battery models (803) if needed
1323
- console.log(`Battery Model ${model.id} reading not yet implemented`);
1366
+ console.debug(`Battery Model ${model.id} reading not yet implemented`);
1324
1367
  return {
1325
1368
  blockNumber: model.id,
1326
1369
  blockAddress: model.address,
@@ -1492,13 +1535,13 @@ class SunspecModbusClient {
1492
1535
  model = this.findModel(sunspec_interfaces_js_1.SunspecModelId.MeterSinglePhase);
1493
1536
  }
1494
1537
  if (!model) {
1495
- console.log('No meter model found');
1538
+ console.debug('No meter model found');
1496
1539
  return null;
1497
1540
  }
1498
- console.log(`Reading Meter Data from Model ${model.id} at base address: ${model.address}`);
1541
+ console.debug(`Reading Meter Data from Model ${model.id} at base address: ${model.address}`);
1499
1542
  try {
1500
1543
  // Different meter models have different register offsets
1501
- console.log(`Meter is Model ${model.id}`);
1544
+ console.debug(`Meter is Model ${model.id}`);
1502
1545
  let powerOffset;
1503
1546
  let powerSFOffset;
1504
1547
  let freqOffset;
@@ -1508,7 +1551,7 @@ class SunspecModbusClient {
1508
1551
  let energySFOffset;
1509
1552
  switch (model.id) {
1510
1553
  case 203: // 3-phase meter (Delta)
1511
- console.log('Using Model 203 (3-Phase Meter Delta) offsets');
1554
+ console.debug('Using Model 203 (3-Phase Meter Delta) offsets');
1512
1555
  powerOffset = 18; // W - Total Real Power (int16)
1513
1556
  powerSFOffset = 22; // W_SF - Power scale factor
1514
1557
  freqOffset = 16; // Hz - Frequency (uint16)
@@ -1518,7 +1561,7 @@ class SunspecModbusClient {
1518
1561
  energySFOffset = 54; // TotWh_SF - Total Energy scale factor
1519
1562
  break;
1520
1563
  case 204: // 3-phase meter (Wye)
1521
- console.log('Using Model 204 (3-Phase Meter Wye) offsets');
1564
+ console.debug('Using Model 204 (3-Phase Meter Wye) offsets');
1522
1565
  powerOffset = 18; // W - Total Real Power (int16)
1523
1566
  powerSFOffset = 22; // W_SF - Power scale factor
1524
1567
  freqOffset = 16; // Hz - Frequency (uint16)
@@ -1529,7 +1572,7 @@ class SunspecModbusClient {
1529
1572
  break;
1530
1573
  case 201: // Single-phase meter
1531
1574
  default:
1532
- console.log('Using Model 201 (Single-Phase Meter) offsets');
1575
+ console.debug('Using Model 201 (Single-Phase Meter) offsets');
1533
1576
  powerOffset = 18; // W - Total Real Power (int16)
1534
1577
  powerSFOffset = 22; // W_SF - Power scale factor
1535
1578
  freqOffset = 16; // Hz - Frequency (uint16)
@@ -1651,7 +1694,7 @@ class SunspecModbusClient {
1651
1694
  async readInverterSettings() {
1652
1695
  const model = this.findModel(sunspec_interfaces_js_1.SunspecModelId.Settings);
1653
1696
  if (!model) {
1654
- console.log('Settings model 121 not found');
1697
+ console.debug('Settings model 121 not found');
1655
1698
  return null;
1656
1699
  }
1657
1700
  try {
@@ -32,6 +32,7 @@ export declare class SunspecModbusClient {
32
32
  private openCount;
33
33
  private closeCount;
34
34
  private currentlyOpen;
35
+ private operationChain;
35
36
  constructor(energyApp: EnergyApp);
36
37
  /**
37
38
  * Connect to Modbus device
@@ -55,10 +56,25 @@ export declare class SunspecModbusClient {
55
56
  */
56
57
  reconnect(): Promise<boolean>;
57
58
  /**
58
- * Attempt to establish a connection to a specific host
59
- * Returns true if successful, false otherwise
59
+ * Attempt to establish a connection to a specific host. Caller must hold the connection lock.
60
+ * Returns true if successful, false otherwise.
60
61
  */
61
62
  private attemptConnection;
63
+ /**
64
+ * Open a new Modbus socket and wire up the fault-tolerant reader. On any failure, the
65
+ * just-opened socket (if any) is closed so we never leak. Caller must hold the connection
66
+ * lock and ensure no prior socket is open (use _closeSocket first).
67
+ */
68
+ private _openSocket;
69
+ /**
70
+ * Close the current Modbus socket if any. Idempotent. Caller must hold the connection lock.
71
+ */
72
+ private _closeSocket;
73
+ /**
74
+ * Run `fn` with exclusive access to connection-state transitions. Subsequent calls queue
75
+ * behind any in-flight one. A rejected `fn` does not poison the chain for later callers.
76
+ */
77
+ private withConnectionLock;
62
78
  private recordOpen;
63
79
  private recordClose;
64
80
  /**
@@ -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.50';
12
+ exports.SDK_VERSION = '0.0.51';
13
13
  /**
14
14
  * Gets the current SDK version.
15
15
  * @returns The semantic version string of the SDK
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * Current version of the enyo Energy App SDK.
7
7
  */
8
- export declare const SDK_VERSION = "0.0.50";
8
+ export declare const SDK_VERSION = "0.0.51";
9
9
  /**
10
10
  * Gets the current SDK version.
11
11
  * @returns The semantic version string of the SDK
@@ -32,6 +32,7 @@ export declare class SunspecModbusClient {
32
32
  private openCount;
33
33
  private closeCount;
34
34
  private currentlyOpen;
35
+ private operationChain;
35
36
  constructor(energyApp: EnergyApp);
36
37
  /**
37
38
  * Connect to Modbus device
@@ -55,10 +56,25 @@ export declare class SunspecModbusClient {
55
56
  */
56
57
  reconnect(): Promise<boolean>;
57
58
  /**
58
- * Attempt to establish a connection to a specific host
59
- * Returns true if successful, false otherwise
59
+ * Attempt to establish a connection to a specific host. Caller must hold the connection lock.
60
+ * Returns true if successful, false otherwise.
60
61
  */
61
62
  private attemptConnection;
63
+ /**
64
+ * Open a new Modbus socket and wire up the fault-tolerant reader. On any failure, the
65
+ * just-opened socket (if any) is closed so we never leak. Caller must hold the connection
66
+ * lock and ensure no prior socket is open (use _closeSocket first).
67
+ */
68
+ private _openSocket;
69
+ /**
70
+ * Close the current Modbus socket if any. Idempotent. Caller must hold the connection lock.
71
+ */
72
+ private _closeSocket;
73
+ /**
74
+ * Run `fn` with exclusive access to connection-state transitions. Subsequent calls queue
75
+ * behind any in-flight one. A rejected `fn` does not poison the chain for later callers.
76
+ */
77
+ private withConnectionLock;
62
78
  private recordOpen;
63
79
  private recordClose;
64
80
  /**
@@ -33,6 +33,9 @@ export class SunspecModbusClient {
33
33
  openCount = 0;
34
34
  closeCount = 0;
35
35
  currentlyOpen = 0;
36
+ // Serializes connection-state transitions so concurrent callers cannot open a second
37
+ // TCP socket while the first is still alive (some Modbus devices allow only one client).
38
+ operationChain = Promise.resolve();
36
39
  constructor(energyApp) {
37
40
  this.energyApp = energyApp;
38
41
  this.connectionHealth = new EnergyAppModbusConnectionHealth();
@@ -46,25 +49,23 @@ export class SunspecModbusClient {
46
49
  * @param secondaryHost Optional secondary host (ipAddress) for fallback during reconnection
47
50
  */
48
51
  async connect(host, port = 502, unitId = 1, secondaryHost) {
49
- if (this.connected) {
50
- await this.disconnect();
51
- }
52
- // Store connection parameters for potential reconnection
53
- this.connectionParams = { primaryHost: host, secondaryHost, port, unitId };
54
- this.modbusClient = await this.energyApp.useModbus().connect({
55
- host,
56
- port,
57
- unitId,
58
- timeout: 5000
52
+ return this.withConnectionLock(async () => {
53
+ const sameParams = this.connected
54
+ && this.connectionParams !== null
55
+ && this.connectionParams.primaryHost === host
56
+ && this.connectionParams.port === port
57
+ && this.connectionParams.unitId === unitId;
58
+ if (sameParams) {
59
+ console.debug(`connect(): already connected to ${host}:${port} unit ${unitId}, skipping`);
60
+ return;
61
+ }
62
+ if (this.connected || this.modbusClient) {
63
+ await this._closeSocket();
64
+ }
65
+ this.connectionParams = { primaryHost: host, secondaryHost, port, unitId };
66
+ await this._openSocket(host, port, unitId);
67
+ console.log(`Connected to Sunspec device at ${host}:${port} unit ${unitId} (opens=${this.openCount}, closes=${this.closeCount}, current=${this.currentlyOpen})`);
59
68
  });
60
- // Create fault-tolerant reader with connection health monitoring
61
- if (this.modbusClient) {
62
- this.faultTolerantReader = new EnergyAppModbusFaultTolerantReader(this.modbusClient, this.connectionHealth);
63
- }
64
- this.connected = true;
65
- this.connectionHealth.recordSuccess();
66
- this.recordOpen();
67
- console.log(`Connected to Sunspec device at ${host}:${port} unit ${unitId} (opens=${this.openCount}, closes=${this.closeCount}, current=${this.currentlyOpen})`);
68
69
  }
69
70
  /**
70
71
  * Disconnect from Modbus device.
@@ -73,18 +74,16 @@ export class SunspecModbusClient {
73
74
  * They will be overwritten by the next connect() call anyway.
74
75
  */
75
76
  async disconnect() {
76
- if (this.modbusClient && this.connected) {
77
- await this.modbusClient.disconnect();
78
- this.modbusClient = null;
79
- this.faultTolerantReader = null;
80
- this.connected = false;
81
- this.discoveredModels.clear();
82
- this.recordClose();
77
+ return this.withConnectionLock(async () => {
78
+ if (!this.modbusClient && !this.connected) {
79
+ return;
80
+ }
83
81
  const host = this.connectionParams?.primaryHost ?? 'unknown';
84
82
  const port = this.connectionParams?.port ?? 0;
85
83
  const unitId = this.connectionParams?.unitId ?? 0;
84
+ await this._closeSocket();
86
85
  console.log(`Disconnected from Sunspec device at ${host}:${port} unit ${unitId} (opens=${this.openCount}, closes=${this.closeCount}, current=${this.currentlyOpen})`);
87
- }
86
+ });
88
87
  }
89
88
  /**
90
89
  * Reconnect using stored connection parameters
@@ -92,78 +91,122 @@ export class SunspecModbusClient {
92
91
  * Returns true if reconnection was successful, false otherwise
93
92
  */
94
93
  async reconnect() {
95
- if (!this.connectionParams) {
96
- console.error('Cannot reconnect: no connection parameters stored. Call connect() first.');
94
+ return this.withConnectionLock(async () => {
95
+ if (!this.connectionParams) {
96
+ console.error('Cannot reconnect: no connection parameters stored. Call connect() first.');
97
+ return false;
98
+ }
99
+ const { primaryHost, secondaryHost, port, unitId } = this.connectionParams;
100
+ console.log(`Attempting to reconnect to primary host ${primaryHost}:${port} unit ${unitId}...`);
101
+ const primarySuccess = await this.attemptConnection(primaryHost, port, unitId);
102
+ if (primarySuccess) {
103
+ console.log(`Successfully reconnected to primary host ${primaryHost}:${port} unit ${unitId}`);
104
+ return true;
105
+ }
106
+ if (secondaryHost && secondaryHost !== primaryHost) {
107
+ console.log(`Primary host failed, attempting secondary host ${secondaryHost}:${port} unit ${unitId}...`);
108
+ const secondarySuccess = await this.attemptConnection(secondaryHost, port, unitId);
109
+ if (secondarySuccess) {
110
+ console.log(`Successfully reconnected to secondary host ${secondaryHost}:${port} unit ${unitId}`);
111
+ return true;
112
+ }
113
+ }
114
+ console.error(`Reconnection failed to all available hosts`);
97
115
  return false;
116
+ });
117
+ }
118
+ /**
119
+ * Attempt to establish a connection to a specific host. Caller must hold the connection lock.
120
+ * Returns true if successful, false otherwise.
121
+ */
122
+ async attemptConnection(host, port, unitId) {
123
+ if (this.connected || this.modbusClient) {
124
+ await this._closeSocket();
98
125
  }
99
- const { primaryHost, secondaryHost, port, unitId } = this.connectionParams;
100
- // Try primary host first
101
- console.log(`Attempting to reconnect to primary host ${primaryHost}:${port} unit ${unitId}...`);
102
- const primarySuccess = await this.attemptConnection(primaryHost, port, unitId);
103
- if (primarySuccess) {
104
- console.log(`Successfully reconnected to primary host ${primaryHost}:${port} unit ${unitId}`);
126
+ try {
127
+ await this._openSocket(host, port, unitId);
128
+ console.log(`Connection attempt to ${host}:${port} unit ${unitId} succeeded (opens=${this.openCount}, closes=${this.closeCount}, current=${this.currentlyOpen})`);
105
129
  return true;
106
130
  }
107
- // If primary failed and secondary is available, try secondary
108
- if (secondaryHost && secondaryHost !== primaryHost) {
109
- console.log(`Primary host failed, attempting secondary host ${secondaryHost}:${port} unit ${unitId}...`);
110
- const secondarySuccess = await this.attemptConnection(secondaryHost, port, unitId);
111
- if (secondarySuccess) {
112
- console.log(`Successfully reconnected to secondary host ${secondaryHost}:${port} unit ${unitId}`);
113
- return true;
114
- }
131
+ catch (error) {
132
+ console.error(`Connection attempt to ${host}:${port} failed: ${error}`);
133
+ return false;
115
134
  }
116
- console.error(`Reconnection failed to all available hosts`);
117
- this.connected = false;
118
- return false;
119
135
  }
120
136
  /**
121
- * Attempt to establish a connection to a specific host
122
- * Returns true if successful, false otherwise
137
+ * Open a new Modbus socket and wire up the fault-tolerant reader. On any failure, the
138
+ * just-opened socket (if any) is closed so we never leak. Caller must hold the connection
139
+ * lock and ensure no prior socket is open (use _closeSocket first).
123
140
  */
124
- async attemptConnection(host, port, unitId) {
141
+ async _openSocket(host, port, unitId) {
142
+ let candidate = null;
125
143
  try {
126
- // Disconnect existing connection if any
127
- if (this.modbusClient) {
128
- const wasConnected = this.connected;
129
- try {
130
- await this.modbusClient.disconnect();
131
- }
132
- catch (e) {
133
- // Ignore disconnect errors during reconnection
134
- }
135
- this.modbusClient = null;
136
- this.faultTolerantReader = null;
137
- if (wasConnected) {
138
- this.connected = false;
139
- this.recordClose();
140
- }
141
- }
142
- // Attempt connection
143
- this.modbusClient = await this.energyApp.useModbus().connect({
144
+ candidate = await this.energyApp.useModbus().connect({
144
145
  host,
145
146
  port,
146
147
  unitId,
147
148
  timeout: 5000
148
149
  });
149
- // Create fault-tolerant reader with connection health monitoring
150
- if (this.modbusClient) {
151
- this.faultTolerantReader = new EnergyAppModbusFaultTolerantReader(this.modbusClient, this.connectionHealth);
150
+ if (!candidate) {
151
+ throw new Error(`useModbus().connect returned null for ${host}:${port} unit ${unitId}`);
152
152
  }
153
+ this.faultTolerantReader = new EnergyAppModbusFaultTolerantReader(candidate, this.connectionHealth);
154
+ this.modbusClient = candidate;
153
155
  this.connected = true;
154
156
  this.connectionHealth.recordSuccess();
155
157
  this.recordOpen();
156
- console.log(`Connection attempt to ${host}:${port} unit ${unitId} succeeded (opens=${this.openCount}, closes=${this.closeCount}, current=${this.currentlyOpen})`);
157
- return true;
158
158
  }
159
- catch (error) {
160
- console.error(`Connection attempt to ${host}:${port} failed: ${error}`);
161
- return false;
159
+ catch (err) {
160
+ if (candidate) {
161
+ try {
162
+ await candidate.disconnect();
163
+ }
164
+ catch { /* ignore */ }
165
+ }
166
+ this.modbusClient = null;
167
+ this.faultTolerantReader = null;
168
+ this.connected = false;
169
+ throw err;
162
170
  }
163
171
  }
172
+ /**
173
+ * Close the current Modbus socket if any. Idempotent. Caller must hold the connection lock.
174
+ */
175
+ async _closeSocket() {
176
+ if (!this.modbusClient && !this.connected)
177
+ return;
178
+ const client = this.modbusClient;
179
+ const wasConnected = this.connected;
180
+ this.modbusClient = null;
181
+ this.faultTolerantReader = null;
182
+ this.connected = false;
183
+ this.discoveredModels.clear();
184
+ if (client) {
185
+ try {
186
+ await client.disconnect();
187
+ }
188
+ catch { /* ignore */ }
189
+ }
190
+ if (wasConnected)
191
+ this.recordClose();
192
+ }
193
+ /**
194
+ * Run `fn` with exclusive access to connection-state transitions. Subsequent calls queue
195
+ * behind any in-flight one. A rejected `fn` does not poison the chain for later callers.
196
+ */
197
+ withConnectionLock(fn) {
198
+ const run = this.operationChain.then(fn, fn);
199
+ this.operationChain = run.catch(() => undefined);
200
+ return run;
201
+ }
164
202
  recordOpen() {
165
203
  this.openCount++;
166
204
  this.currentlyOpen++;
205
+ if (this.currentlyOpen > 1) {
206
+ console.error(`SunspecModbusClient: currentlyOpen=${this.currentlyOpen} after open ` +
207
+ `(opens=${this.openCount}, closes=${this.closeCount}). ` +
208
+ `This indicates a code path bypassing the connection lock — please investigate.`);
209
+ }
167
210
  }
168
211
  recordClose() {
169
212
  this.closeCount++;
@@ -513,7 +556,7 @@ export class SunspecModbusClient {
513
556
  async readInverterData() {
514
557
  const model = this.findModel(SunspecModelId.Inverter3Phase);
515
558
  if (!model) {
516
- console.log('Inverter model 103 not found, trying single phase model 101');
559
+ console.debug('Inverter model 103 not found, trying single phase model 101');
517
560
  const singlePhaseModel = this.findModel(SunspecModelId.InverterSinglePhase);
518
561
  if (!singlePhaseModel) {
519
562
  console.error('No inverter model found');
@@ -522,10 +565,10 @@ export class SunspecModbusClient {
522
565
  console.warn('IMPORTANT: Working with single-phase inverter, but 3-phase is expected!');
523
566
  return this.readSinglePhaseInverterData(singlePhaseModel);
524
567
  }
525
- console.log(`Found 3-phase inverter model 103 at address ${model.address} with length ${model.length}`);
568
+ console.debug(`Found 3-phase inverter model 103 at address ${model.address} with length ${model.length}`);
526
569
  try {
527
570
  // Read entire model block in a single Modbus call
528
- console.log(`Reading Inverter Data from Model ${model.id} at base address: ${model.address}`);
571
+ console.debug(`Reading Inverter Data from Model ${model.id} at base address: ${model.address}`);
529
572
  const buffer = await this.readModelBlock(model);
530
573
  // Extract all scale factors from buffer
531
574
  const scaleFactors = this.extractInverterScaleFactors(buffer);
@@ -609,7 +652,7 @@ export class SunspecModbusClient {
609
652
  */
610
653
  async readSinglePhaseInverterData(model) {
611
654
  try {
612
- console.log(`Reading Single-Phase Inverter Data from Model 101 at base address: ${model.address}`);
655
+ console.debug(`Reading Single-Phase Inverter Data from Model 101 at base address: ${model.address}`);
613
656
  // Read entire model block in a single Modbus call
614
657
  const buffer = await this.readModelBlock(model);
615
658
  // Extract scale factors from buffer
@@ -763,7 +806,7 @@ export class SunspecModbusClient {
763
806
  async readMPPTScaleFactors() {
764
807
  const model = this.findModel(SunspecModelId.MPPT);
765
808
  if (!model) {
766
- console.log('MPPT model 160 not found');
809
+ console.debug('MPPT model 160 not found');
767
810
  return null;
768
811
  }
769
812
  try {
@@ -797,7 +840,7 @@ export class SunspecModbusClient {
797
840
  this.isUnimplementedValue(dcCurrentRaw, 'uint16') &&
798
841
  this.isUnimplementedValue(dcVoltageRaw, 'uint16') &&
799
842
  this.isUnimplementedValue(dcPowerRaw, 'uint16')) {
800
- console.log(`MPPT module ${moduleId} appears to be unconnected (all values are 0xFFFF)`);
843
+ console.debug(`MPPT module ${moduleId} appears to be unconnected (all values are 0xFFFF)`);
801
844
  return null;
802
845
  }
803
846
  const temperatureScaleFactor = -1;
@@ -835,7 +878,7 @@ export class SunspecModbusClient {
835
878
  async readMPPTData(moduleId = 1) {
836
879
  const model = this.findModel(SunspecModelId.MPPT);
837
880
  if (!model) {
838
- console.log('MPPT model 160 not found');
881
+ console.debug('MPPT model 160 not found');
839
882
  return null;
840
883
  }
841
884
  try {
@@ -856,7 +899,7 @@ export class SunspecModbusClient {
856
899
  const mpptData = [];
857
900
  const model = this.findModel(SunspecModelId.MPPT);
858
901
  if (!model) {
859
- console.log('MPPT model 160 not found');
902
+ console.debug('MPPT model 160 not found');
860
903
  return [];
861
904
  }
862
905
  try {
@@ -868,16 +911,16 @@ export class SunspecModbusClient {
868
911
  const count = this.extractValue(buffer, 8, 'uint16');
869
912
  if (!this.isUnimplementedValue(count, 'uint16') && count > 0 && count <= 20) {
870
913
  moduleCount = count;
871
- console.log(`MPPT module count from register 8: ${moduleCount}`);
914
+ console.debug(`MPPT module count from register 8: ${moduleCount}`);
872
915
  }
873
916
  else {
874
- console.log(`Invalid or unimplemented module count (${count}), using default: ${moduleCount}`);
917
+ console.debug(`Invalid or unimplemented module count (${count}), using default: ${moduleCount}`);
875
918
  }
876
919
  // Extract each MPPT module from the same buffer
877
920
  for (let i = 1; i <= moduleCount; i++) {
878
921
  try {
879
922
  const data = this.extractMPPTModuleData(buffer, model, i, scaleFactors);
880
- console.log(`MPPT ${i} has id ${data?.id} (${data?.stringId}) with ${data?.dcPower}W`);
923
+ console.debug(`MPPT ${i} has id ${data?.id} (${data?.stringId}) with ${data?.dcPower}W`);
881
924
  if (data &&
882
925
  (data.dcCurrent !== undefined ||
883
926
  data.dcVoltage !== undefined ||
@@ -1025,10 +1068,10 @@ export class SunspecModbusClient {
1025
1068
  async readBatteryBaseData() {
1026
1069
  const model = this.findModel(SunspecModelId.BatteryBase);
1027
1070
  if (!model) {
1028
- console.log('Battery Base model 802 not found');
1071
+ console.debug('Battery Base model 802 not found');
1029
1072
  return null;
1030
1073
  }
1031
- console.log(`Reading Battery Base Data from Model 802 at base address: ${model.address}`);
1074
+ console.debug(`Reading Battery Base Data from Model 802 at base address: ${model.address}`);
1032
1075
  try {
1033
1076
  // Read entire model block in a single Modbus call
1034
1077
  const buffer = await this.readModelBlock(model);
@@ -1171,14 +1214,14 @@ export class SunspecModbusClient {
1171
1214
  model = this.findModel(SunspecModelId.BatteryControl);
1172
1215
  }
1173
1216
  if (!model) {
1174
- console.log('No battery model found');
1217
+ console.debug('No battery model found');
1175
1218
  return null;
1176
1219
  }
1177
- console.log(`Reading Battery Data from Model ${model.id} at base address: ${model.address}`);
1220
+ console.debug(`Reading Battery Data from Model ${model.id} at base address: ${model.address}`);
1178
1221
  try {
1179
1222
  if (model.id === 124) {
1180
1223
  // Model 124: Basic Storage Controls
1181
- console.log('Using Model 124 (Basic Storage Controls)');
1224
+ console.debug('Using Model 124 (Basic Storage Controls)');
1182
1225
  // Read entire model block in a single Modbus call
1183
1226
  const buffer = await this.readModelBlock(model);
1184
1227
  // Extract scale factors from buffer (offsets 18-25)
@@ -1268,19 +1311,19 @@ export class SunspecModbusClient {
1268
1311
  // Calculate charge/discharge power if rates are available
1269
1312
  if (data.inWRte !== undefined && data.wChaMax !== undefined) {
1270
1313
  data.chargePower = (data.inWRte / 100) * data.wChaMax;
1271
- console.log(`Calculated Charge Power: (inWRte: ${data.inWRte}, wChaMax: ${data.wChaMax}) ${data.chargePower?.toFixed(2)} W`);
1314
+ console.debug(`Calculated Charge Power: (inWRte: ${data.inWRte}, wChaMax: ${data.wChaMax}) ${data.chargePower?.toFixed(2)} W`);
1272
1315
  }
1273
1316
  if (data.outWRte !== undefined && data.wChaMax !== undefined) {
1274
1317
  // Assuming WDisChaMax is similar to WChaMax for simplicity
1275
1318
  data.dischargePower = Math.abs((data.outWRte / 100) * data.wChaMax);
1276
- console.log(`Calculated Discharge Power (inWRte: ${data.outWRte}, wChaMax: ${data.wChaMax}): ${data.dischargePower?.toFixed(2)} W`);
1319
+ console.debug(`Calculated Discharge Power (inWRte: ${data.outWRte}, wChaMax: ${data.wChaMax}): ${data.dischargePower?.toFixed(2)} W`);
1277
1320
  }
1278
1321
  console.debug('[Model 124] Battery Data:', data);
1279
1322
  return data;
1280
1323
  }
1281
1324
  else if (model.id === 802) {
1282
1325
  // Model 802: Battery Base
1283
- console.log('Using Model 802 (Battery Base)');
1326
+ console.debug('Using Model 802 (Battery Base)');
1284
1327
  const baseData = await this.readBatteryBaseData();
1285
1328
  if (!baseData) {
1286
1329
  return null;
@@ -1317,7 +1360,7 @@ export class SunspecModbusClient {
1317
1360
  }
1318
1361
  else {
1319
1362
  // Handle other battery models (803) if needed
1320
- console.log(`Battery Model ${model.id} reading not yet implemented`);
1363
+ console.debug(`Battery Model ${model.id} reading not yet implemented`);
1321
1364
  return {
1322
1365
  blockNumber: model.id,
1323
1366
  blockAddress: model.address,
@@ -1489,13 +1532,13 @@ export class SunspecModbusClient {
1489
1532
  model = this.findModel(SunspecModelId.MeterSinglePhase);
1490
1533
  }
1491
1534
  if (!model) {
1492
- console.log('No meter model found');
1535
+ console.debug('No meter model found');
1493
1536
  return null;
1494
1537
  }
1495
- console.log(`Reading Meter Data from Model ${model.id} at base address: ${model.address}`);
1538
+ console.debug(`Reading Meter Data from Model ${model.id} at base address: ${model.address}`);
1496
1539
  try {
1497
1540
  // Different meter models have different register offsets
1498
- console.log(`Meter is Model ${model.id}`);
1541
+ console.debug(`Meter is Model ${model.id}`);
1499
1542
  let powerOffset;
1500
1543
  let powerSFOffset;
1501
1544
  let freqOffset;
@@ -1505,7 +1548,7 @@ export class SunspecModbusClient {
1505
1548
  let energySFOffset;
1506
1549
  switch (model.id) {
1507
1550
  case 203: // 3-phase meter (Delta)
1508
- console.log('Using Model 203 (3-Phase Meter Delta) offsets');
1551
+ console.debug('Using Model 203 (3-Phase Meter Delta) offsets');
1509
1552
  powerOffset = 18; // W - Total Real Power (int16)
1510
1553
  powerSFOffset = 22; // W_SF - Power scale factor
1511
1554
  freqOffset = 16; // Hz - Frequency (uint16)
@@ -1515,7 +1558,7 @@ export class SunspecModbusClient {
1515
1558
  energySFOffset = 54; // TotWh_SF - Total Energy scale factor
1516
1559
  break;
1517
1560
  case 204: // 3-phase meter (Wye)
1518
- console.log('Using Model 204 (3-Phase Meter Wye) offsets');
1561
+ console.debug('Using Model 204 (3-Phase Meter Wye) offsets');
1519
1562
  powerOffset = 18; // W - Total Real Power (int16)
1520
1563
  powerSFOffset = 22; // W_SF - Power scale factor
1521
1564
  freqOffset = 16; // Hz - Frequency (uint16)
@@ -1526,7 +1569,7 @@ export class SunspecModbusClient {
1526
1569
  break;
1527
1570
  case 201: // Single-phase meter
1528
1571
  default:
1529
- console.log('Using Model 201 (Single-Phase Meter) offsets');
1572
+ console.debug('Using Model 201 (Single-Phase Meter) offsets');
1530
1573
  powerOffset = 18; // W - Total Real Power (int16)
1531
1574
  powerSFOffset = 22; // W_SF - Power scale factor
1532
1575
  freqOffset = 16; // Hz - Frequency (uint16)
@@ -1648,7 +1691,7 @@ export class SunspecModbusClient {
1648
1691
  async readInverterSettings() {
1649
1692
  const model = this.findModel(SunspecModelId.Settings);
1650
1693
  if (!model) {
1651
- console.log('Settings model 121 not found');
1694
+ console.debug('Settings model 121 not found');
1652
1695
  return null;
1653
1696
  }
1654
1697
  try {
package/dist/version.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * Current version of the enyo Energy App SDK.
7
7
  */
8
- export declare const SDK_VERSION = "0.0.50";
8
+ export declare const SDK_VERSION = "0.0.51";
9
9
  /**
10
10
  * Gets the current SDK version.
11
11
  * @returns The semantic version string of the SDK
package/dist/version.js CHANGED
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * Current version of the enyo Energy App SDK.
7
7
  */
8
- export const SDK_VERSION = '0.0.50';
8
+ export const SDK_VERSION = '0.0.51';
9
9
  /**
10
10
  * Gets the current SDK version.
11
11
  * @returns The semantic version string of the SDK
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enyo-energy/sunspec-sdk",
3
- "version": "0.0.50",
3
+ "version": "0.0.51",
4
4
  "description": "enyo Energy Sunspec SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",