@enyo-energy/sunspec-sdk 0.0.4 → 0.0.6
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 +12 -9
- package/dist/cjs/sunspec-devices.d.cts +3 -3
- package/dist/cjs/sunspec-modbus-client.cjs +159 -33
- package/dist/cjs/sunspec-modbus-client.d.cts +15 -1
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/sunspec-devices.d.ts +3 -3
- package/dist/sunspec-devices.js +12 -9
- package/dist/sunspec-modbus-client.d.ts +15 -1
- package/dist/sunspec-modbus-client.js +159 -33
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -113,12 +113,12 @@ class SunspecInverter extends BaseSunspecDevice {
|
|
|
113
113
|
timestampIso: timestamp.toISOString(),
|
|
114
114
|
resolution,
|
|
115
115
|
data: {
|
|
116
|
-
pvPowerW: inverterData.
|
|
117
|
-
activePowerLimitationW: inverterData.acPower || 0,
|
|
116
|
+
pvPowerW: inverterData.dcPower || 0, // Use DC power for PV power
|
|
117
|
+
activePowerLimitationW: inverterData.acPower || 0,
|
|
118
118
|
state: this.mapOperatingState(inverterData.operatingState),
|
|
119
119
|
voltageL1: inverterData.voltageAN || 0,
|
|
120
|
-
voltageL2: inverterData.voltageBN,
|
|
121
|
-
voltageL3: inverterData.voltageCN,
|
|
120
|
+
voltageL2: inverterData.voltageBN ?? undefined, // Use null if undefined (unimplemented phase)
|
|
121
|
+
voltageL3: inverterData.voltageCN ?? undefined, // Use null if undefined (unimplemented phase)
|
|
122
122
|
strings: this.mapMPPTToStrings(mpptDataList)
|
|
123
123
|
}
|
|
124
124
|
};
|
|
@@ -152,11 +152,14 @@ class SunspecInverter extends BaseSunspecDevice {
|
|
|
152
152
|
mapMPPTToStrings(mpptDataList) {
|
|
153
153
|
const result = [];
|
|
154
154
|
mpptDataList.forEach((mppt, index) => {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
155
|
+
// Only include strings with valid data
|
|
156
|
+
if (mppt.dcVoltage !== undefined || mppt.dcPower !== undefined) {
|
|
157
|
+
result.push({
|
|
158
|
+
index: index + 1,
|
|
159
|
+
voltage: mppt.dcVoltage ?? undefined, // Use null if undefined
|
|
160
|
+
powerW: mppt.dcPower ?? 0 // Default to 0 if undefined
|
|
161
|
+
});
|
|
162
|
+
}
|
|
160
163
|
});
|
|
161
164
|
return result;
|
|
162
165
|
}
|
|
@@ -2,7 +2,7 @@ import { ApplianceManager, EnergyApp } from "@enyo-energy/energy-app-sdk";
|
|
|
2
2
|
import { EnyoApplianceName } from "@enyo-energy/energy-app-sdk/dist/types/enyo-appliance.js";
|
|
3
3
|
import { EnyoNetworkDevice } from "@enyo-energy/energy-app-sdk/dist/types/enyo-network-device.js";
|
|
4
4
|
import { SunspecModbusClient } from "./sunspec-modbus-client.cjs";
|
|
5
|
-
import { EnyoDataBusMessage } from "@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js";
|
|
5
|
+
import { EnyoDataBusMessage, EnyoDataBusMessageResolution } from "@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js";
|
|
6
6
|
/**
|
|
7
7
|
* Base abstract class for all Sunspec devices
|
|
8
8
|
*/
|
|
@@ -27,7 +27,7 @@ export declare abstract class BaseSunspecDevice {
|
|
|
27
27
|
/**
|
|
28
28
|
* Update device data and return data bus messages
|
|
29
29
|
*/
|
|
30
|
-
abstract readData(clockId: string, resolution:
|
|
30
|
+
abstract readData(clockId: string, resolution: EnyoDataBusMessageResolution): Promise<EnyoDataBusMessage[]>;
|
|
31
31
|
/**
|
|
32
32
|
* Check if the device is connected
|
|
33
33
|
*/
|
|
@@ -83,5 +83,5 @@ export declare class SunspecMeter extends BaseSunspecDevice {
|
|
|
83
83
|
/**
|
|
84
84
|
* Update meter data and return data bus messages
|
|
85
85
|
*/
|
|
86
|
-
readData(clockId: string, resolution:
|
|
86
|
+
readData(clockId: string, resolution: EnyoDataBusMessageResolution): Promise<EnyoDataBusMessage[]>;
|
|
87
87
|
}
|
|
@@ -51,29 +51,63 @@ class SunspecModbusClient {
|
|
|
51
51
|
this.scaleFactors = {};
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Detect the base address and addressing mode (0-based or 1-based) for SunSpec
|
|
56
|
+
*/
|
|
57
|
+
async detectSunspecBaseAddress() {
|
|
58
|
+
if (!this.modbusClient) {
|
|
59
|
+
throw new Error('Modbus client not initialized');
|
|
60
|
+
}
|
|
61
|
+
// Try 1-based addressing first (most common)
|
|
62
|
+
try {
|
|
63
|
+
const sunspecId = await this.modbusClient.readRegisterStringValue(40001, 2);
|
|
64
|
+
if (sunspecId.includes('SunS')) {
|
|
65
|
+
console.log('Detected 1-based addressing mode (base address: 40001)');
|
|
66
|
+
this.baseAddress = 40001;
|
|
67
|
+
return {
|
|
68
|
+
baseAddress: 40001,
|
|
69
|
+
isZeroBased: false,
|
|
70
|
+
nextAddress: 40003
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
console.debug('Could not read SunS at 40001:', error);
|
|
76
|
+
}
|
|
77
|
+
// Try 0-based addressing
|
|
78
|
+
try {
|
|
79
|
+
const sunspecId = await this.modbusClient.readRegisterStringValue(40000, 2);
|
|
80
|
+
if (sunspecId.includes('SunS')) {
|
|
81
|
+
console.log('Detected 0-based addressing mode (base address: 40000)');
|
|
82
|
+
this.baseAddress = 40000;
|
|
83
|
+
return {
|
|
84
|
+
baseAddress: 40000,
|
|
85
|
+
isZeroBased: true,
|
|
86
|
+
nextAddress: 40002
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
console.debug('Could not read SunS at 40000:', error);
|
|
92
|
+
}
|
|
93
|
+
throw new Error('Device is not SunSpec compliant - "SunS" identifier not found at addresses 40000 or 40001');
|
|
94
|
+
}
|
|
54
95
|
/**
|
|
55
96
|
* Discover all available Sunspec models
|
|
56
|
-
*
|
|
97
|
+
* Automatically detects base address (40000 or 40001) and scans from there
|
|
57
98
|
*/
|
|
58
99
|
async discoverModels() {
|
|
59
100
|
if (!this.connected) {
|
|
60
101
|
throw new Error('Not connected to Modbus device');
|
|
61
102
|
}
|
|
62
103
|
this.discoveredModels.clear();
|
|
63
|
-
let currentAddress = this.baseAddress;
|
|
64
104
|
const maxAddress = 50000; // Safety limit
|
|
105
|
+
let currentAddress = 0;
|
|
65
106
|
console.log('Starting Sunspec model discovery...');
|
|
66
107
|
try {
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
const sunspecId = await this.modbusClient.readRegisterStringValue(40001, 2);
|
|
72
|
-
if (!sunspecId.includes('SunS')) {
|
|
73
|
-
console.warn('Device may not be Sunspec compliant - missing SunS identifier');
|
|
74
|
-
}
|
|
75
|
-
// Start scanning after the SunS identifier
|
|
76
|
-
currentAddress = 40003;
|
|
108
|
+
// Detect the base address and addressing mode
|
|
109
|
+
const addressInfo = await this.detectSunspecBaseAddress();
|
|
110
|
+
currentAddress = addressInfo.nextAddress;
|
|
77
111
|
while (currentAddress < maxAddress) {
|
|
78
112
|
// Read model ID and length
|
|
79
113
|
if (!this.modbusClient) {
|
|
@@ -155,6 +189,24 @@ class SunspecModbusClient {
|
|
|
155
189
|
}
|
|
156
190
|
return value;
|
|
157
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Check if a value is "unimplemented" according to Sunspec
|
|
194
|
+
* Returns true if the value represents an unimplemented/not applicable register
|
|
195
|
+
*/
|
|
196
|
+
isUnimplementedValue(value, dataType = 'uint16') {
|
|
197
|
+
switch (dataType) {
|
|
198
|
+
case 'uint16':
|
|
199
|
+
return value === 0xFFFF || value === 65535;
|
|
200
|
+
case 'int16':
|
|
201
|
+
return value === 0x7FFF || value === 32767;
|
|
202
|
+
case 'uint32':
|
|
203
|
+
return value === 0xFFFFFFFF;
|
|
204
|
+
case 'int32':
|
|
205
|
+
return value === 0x7FFFFFFF;
|
|
206
|
+
default:
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
158
210
|
/**
|
|
159
211
|
* Helper to clean string values by removing null characters
|
|
160
212
|
*/
|
|
@@ -234,6 +286,15 @@ class SunspecModbusClient {
|
|
|
234
286
|
// Read all scale factors first using fault-tolerant reader
|
|
235
287
|
const scaleFactors = await this.readInverterScaleFactors(baseAddr);
|
|
236
288
|
// Read values using fault-tolerant reader with proper data types
|
|
289
|
+
// Read raw voltage values to check for unimplemented phases
|
|
290
|
+
const voltageANRaw = await this.readRegisterValue(baseAddr + 10, 1, 'uint16');
|
|
291
|
+
const voltageBNRaw = await this.readRegisterValue(baseAddr + 11, 1, 'uint16');
|
|
292
|
+
const voltageCNRaw = await this.readRegisterValue(baseAddr + 12, 1, 'uint16');
|
|
293
|
+
console.log('Inverter Raw Voltage Values:', {
|
|
294
|
+
voltageANRaw: `0x${voltageANRaw.toString(16).toUpperCase()} (${voltageANRaw})`,
|
|
295
|
+
voltageBNRaw: `0x${voltageBNRaw.toString(16).toUpperCase()} (${voltageBNRaw})`,
|
|
296
|
+
voltageCNRaw: `0x${voltageCNRaw.toString(16).toUpperCase()} (${voltageCNRaw})`
|
|
297
|
+
});
|
|
237
298
|
const data = {
|
|
238
299
|
blockNumber: 103,
|
|
239
300
|
blockAddress: model.address,
|
|
@@ -247,9 +308,10 @@ class SunspecModbusClient {
|
|
|
247
308
|
voltageAB: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 7, 1, 'uint16'), scaleFactors.V_SF),
|
|
248
309
|
voltageBC: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 8, 1, 'uint16'), scaleFactors.V_SF),
|
|
249
310
|
voltageCA: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 9, 1, 'uint16'), scaleFactors.V_SF),
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
311
|
+
// Apply scale factor with unimplemented value checking
|
|
312
|
+
voltageAN: this.applyScaleFactor(voltageANRaw, scaleFactors.V_SF),
|
|
313
|
+
voltageBN: this.applyScaleFactor(voltageBNRaw, scaleFactors.V_SF),
|
|
314
|
+
voltageCN: this.applyScaleFactor(voltageCNRaw, scaleFactors.V_SF),
|
|
253
315
|
// Power values - Offsets 14, 18, 20, 22
|
|
254
316
|
acPower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 14, 1, 'int16'), scaleFactors.W_SF),
|
|
255
317
|
apparentPower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 18, 1, 'uint16'), scaleFactors.VA_SF),
|
|
@@ -315,7 +377,7 @@ class SunspecModbusClient {
|
|
|
315
377
|
* Read inverter scale factors
|
|
316
378
|
*/
|
|
317
379
|
async readInverterScaleFactors(baseAddr) {
|
|
318
|
-
|
|
380
|
+
const scaleFactors = {
|
|
319
381
|
A_SF: await this.readRegisterValue(baseAddr + 6, 1, 'int16'), // Offset 6
|
|
320
382
|
V_SF: await this.readRegisterValue(baseAddr + 13, 1, 'int16'), // Offset 13
|
|
321
383
|
W_SF: await this.readRegisterValue(baseAddr + 15, 1, 'int16'), // Offset 15
|
|
@@ -329,11 +391,23 @@ class SunspecModbusClient {
|
|
|
329
391
|
DCW_SF: await this.readRegisterValue(baseAddr + 31, 1, 'int16'), // Offset 31
|
|
330
392
|
Tmp_SF: await this.readRegisterValue(baseAddr + 36, 1, 'int16') // Offset 36
|
|
331
393
|
};
|
|
394
|
+
console.log('Inverter Scale Factors:', JSON.stringify(scaleFactors, null, 2));
|
|
395
|
+
return scaleFactors;
|
|
332
396
|
}
|
|
333
397
|
/**
|
|
334
398
|
* Apply scale factor to a value
|
|
399
|
+
* Returns undefined if the value is unimplemented or scale factor is out of range
|
|
335
400
|
*/
|
|
336
|
-
applyScaleFactor(value, scaleFactor) {
|
|
401
|
+
applyScaleFactor(value, scaleFactor, dataType = 'uint16') {
|
|
402
|
+
// Check for unimplemented values
|
|
403
|
+
if (this.isUnimplementedValue(value, dataType)) {
|
|
404
|
+
return undefined;
|
|
405
|
+
}
|
|
406
|
+
// Validate scale factor is within reasonable range (-10 to +10)
|
|
407
|
+
if (Math.abs(scaleFactor) > 10) {
|
|
408
|
+
console.warn(`Scale factor ${scaleFactor} is outside reasonable range, clamping to ±10`);
|
|
409
|
+
scaleFactor = Math.max(-10, Math.min(10, scaleFactor));
|
|
410
|
+
}
|
|
337
411
|
return value * Math.pow(10, scaleFactor);
|
|
338
412
|
}
|
|
339
413
|
/**
|
|
@@ -359,6 +433,28 @@ class SunspecModbusClient {
|
|
|
359
433
|
DCWH_SF: await this.readRegisterValue(moduleAddr + 17, 1, 'int16'),
|
|
360
434
|
Tmp_SF: await this.readRegisterValue(moduleAddr + 21, 1, 'int16')
|
|
361
435
|
};
|
|
436
|
+
console.log(`MPPT Module ${moduleId} Scale Factors:`, JSON.stringify(scaleFactors, null, 2));
|
|
437
|
+
// Read raw values first
|
|
438
|
+
const dcCurrentRaw = await this.readRegisterValue(moduleAddr + 9, 1, 'uint16');
|
|
439
|
+
const dcVoltageRaw = await this.readRegisterValue(moduleAddr + 11, 1, 'uint16');
|
|
440
|
+
const dcPowerRaw = await this.readRegisterValue(moduleAddr + 13, 1, 'uint16');
|
|
441
|
+
const dcEnergyRaw = await this.readRegisterValue(moduleAddr + 15, 2, 'acc32');
|
|
442
|
+
const temperatureRaw = await this.readRegisterValue(moduleAddr + 20, 1, 'int16');
|
|
443
|
+
console.log(`MPPT Module ${moduleId} Raw Values:`, {
|
|
444
|
+
dcCurrentRaw: `0x${dcCurrentRaw.toString(16).toUpperCase()} (${dcCurrentRaw})`,
|
|
445
|
+
dcVoltageRaw: `0x${dcVoltageRaw.toString(16).toUpperCase()} (${dcVoltageRaw})`,
|
|
446
|
+
dcPowerRaw: `0x${dcPowerRaw.toString(16).toUpperCase()} (${dcPowerRaw})`,
|
|
447
|
+
dcEnergyRaw: `0x${dcEnergyRaw.toString(16).toUpperCase()} (${dcEnergyRaw})`,
|
|
448
|
+
temperatureRaw: `0x${temperatureRaw.toString(16).toUpperCase()} (${temperatureRaw})`
|
|
449
|
+
});
|
|
450
|
+
// Check if this module is actually implemented/connected
|
|
451
|
+
// If all key values are unimplemented, this module doesn't exist
|
|
452
|
+
if (this.isUnimplementedValue(dcCurrentRaw, 'uint16') &&
|
|
453
|
+
this.isUnimplementedValue(dcVoltageRaw, 'uint16') &&
|
|
454
|
+
this.isUnimplementedValue(dcPowerRaw, 'uint16')) {
|
|
455
|
+
console.log(`MPPT module ${moduleId} appears to be unconnected (all values are 0xFFFF)`);
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
362
458
|
const data = {
|
|
363
459
|
blockNumber: 160,
|
|
364
460
|
blockAddress: model.address,
|
|
@@ -367,22 +463,24 @@ class SunspecModbusClient {
|
|
|
367
463
|
// String ID - Offset 1 (8 registers for string)
|
|
368
464
|
stringId: await this.readRegisterValue(moduleAddr + 1, 8, 'string'),
|
|
369
465
|
// DC Current - Offset 9
|
|
370
|
-
dcCurrent: this.applyScaleFactor(
|
|
466
|
+
dcCurrent: this.applyScaleFactor(dcCurrentRaw, scaleFactors.DCA_SF),
|
|
371
467
|
dcCurrentSF: scaleFactors.DCA_SF,
|
|
372
468
|
// DC Voltage - Offset 11
|
|
373
|
-
dcVoltage: this.applyScaleFactor(
|
|
469
|
+
dcVoltage: this.applyScaleFactor(dcVoltageRaw, scaleFactors.DCV_SF),
|
|
374
470
|
dcVoltageSF: scaleFactors.DCV_SF,
|
|
375
471
|
// DC Power - Offset 13
|
|
376
|
-
dcPower: this.applyScaleFactor(
|
|
472
|
+
dcPower: this.applyScaleFactor(dcPowerRaw, scaleFactors.DCW_SF),
|
|
377
473
|
dcPowerSF: scaleFactors.DCW_SF,
|
|
378
474
|
// DC Energy - Offset 15-16 (32-bit accumulator)
|
|
379
|
-
|
|
380
|
-
|
|
475
|
+
// Only calculate if value is not unimplemented
|
|
476
|
+
dcEnergy: !this.isUnimplementedValue(dcEnergyRaw, 'uint32')
|
|
477
|
+
? BigInt(dcEnergyRaw) * BigInt(Math.pow(10, scaleFactors.DCWH_SF))
|
|
478
|
+
: undefined,
|
|
381
479
|
dcEnergySF: scaleFactors.DCWH_SF,
|
|
382
480
|
// Timestamp - Offset 18-19 (32-bit)
|
|
383
481
|
timestamp: await this.readRegisterValue(moduleAddr + 18, 2, 'uint32'),
|
|
384
482
|
// Temperature - Offset 20
|
|
385
|
-
temperature: this.applyScaleFactor(
|
|
483
|
+
temperature: this.applyScaleFactor(temperatureRaw, scaleFactors.Tmp_SF, 'int16'),
|
|
386
484
|
temperatureSF: scaleFactors.Tmp_SF,
|
|
387
485
|
// Operating State - Offset 22
|
|
388
486
|
operatingState: await this.readRegisterValue(moduleAddr + 22, 1, 'uint16'),
|
|
@@ -405,9 +503,19 @@ class SunspecModbusClient {
|
|
|
405
503
|
const mpptData = [];
|
|
406
504
|
// Try to read up to 4 MPPT strings (typical maximum)
|
|
407
505
|
for (let i = 1; i <= 4; i++) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
506
|
+
try {
|
|
507
|
+
const data = await this.readMPPTData(i);
|
|
508
|
+
// Only include if we got valid data (not null) and it has actual values
|
|
509
|
+
if (data &&
|
|
510
|
+
(data.dcCurrent !== undefined ||
|
|
511
|
+
data.dcVoltage !== undefined ||
|
|
512
|
+
data.dcPower !== undefined)) {
|
|
513
|
+
mpptData.push(data);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
catch (error) {
|
|
517
|
+
console.debug(`Could not read MPPT module ${i}: ${error}`);
|
|
518
|
+
// Continue to try other modules
|
|
411
519
|
}
|
|
412
520
|
}
|
|
413
521
|
return mpptData;
|
|
@@ -452,15 +560,31 @@ class SunspecModbusClient {
|
|
|
452
560
|
return null;
|
|
453
561
|
}
|
|
454
562
|
const baseAddr = model.address + 2; // Skip ID and Length
|
|
563
|
+
console.log(`Reading Common Block - Model address: ${model.address}, Base address for data: ${baseAddr}`);
|
|
455
564
|
try {
|
|
456
565
|
// Read all strings using fault-tolerant reader with proper data type conversion
|
|
457
|
-
|
|
458
|
-
const
|
|
459
|
-
const
|
|
460
|
-
const
|
|
461
|
-
const
|
|
462
|
-
const
|
|
463
|
-
|
|
566
|
+
// Common block offsets are relative to the start of the data (after ID and Length)
|
|
567
|
+
const manufacturerAddr = baseAddr; // Offset 0-15 (16 registers) from data start
|
|
568
|
+
const modelAddr = baseAddr + 16; // Offset 16-31 (16 registers) from data start
|
|
569
|
+
const optionsAddr = baseAddr + 32; // Offset 32-39 (8 registers) from data start
|
|
570
|
+
const versionAddr = baseAddr + 40; // Offset 40-47 (8 registers) from data start
|
|
571
|
+
const serialAddr = baseAddr + 48; // Offset 48-63 (16 registers) from data start
|
|
572
|
+
const deviceAddrAddr = baseAddr + 64; // Offset 64 from data start
|
|
573
|
+
console.log(`Reading manufacturer from address ${manufacturerAddr} (16 registers)`);
|
|
574
|
+
const manufacturer = await this.readRegisterValue(manufacturerAddr, 16, 'string');
|
|
575
|
+
console.log(`Manufacturer raw value: "${manufacturer}"`);
|
|
576
|
+
console.log(`Reading model from address ${modelAddr} (16 registers)`);
|
|
577
|
+
const modelName = await this.readRegisterValue(modelAddr, 16, 'string');
|
|
578
|
+
console.log(`Model raw value: "${modelName}"`);
|
|
579
|
+
console.log(`Reading options from address ${optionsAddr} (8 registers)`);
|
|
580
|
+
const options = await this.readRegisterValue(optionsAddr, 8, 'string');
|
|
581
|
+
console.log(`Reading version from address ${versionAddr} (8 registers)`);
|
|
582
|
+
const version = await this.readRegisterValue(versionAddr, 8, 'string');
|
|
583
|
+
console.log(`Reading serial from address ${serialAddr} (16 registers)`);
|
|
584
|
+
const serialNumber = await this.readRegisterValue(serialAddr, 16, 'string');
|
|
585
|
+
console.log(`Reading device address from address ${deviceAddrAddr}`);
|
|
586
|
+
const deviceAddress = await this.readRegisterValue(deviceAddrAddr, 1, 'uint16');
|
|
587
|
+
const result = {
|
|
464
588
|
manufacturer: manufacturer,
|
|
465
589
|
model: modelName,
|
|
466
590
|
options: options,
|
|
@@ -468,6 +592,8 @@ class SunspecModbusClient {
|
|
|
468
592
|
serialNumber: serialNumber,
|
|
469
593
|
deviceAddress: deviceAddress
|
|
470
594
|
};
|
|
595
|
+
console.log('Common Block Data:', JSON.stringify(result, null, 2));
|
|
596
|
+
return result;
|
|
471
597
|
}
|
|
472
598
|
catch (error) {
|
|
473
599
|
console.error(`Error reading common block: ${error}`);
|
|
@@ -19,9 +19,17 @@ export declare class SunspecModbusClient {
|
|
|
19
19
|
* Disconnect from Modbus device
|
|
20
20
|
*/
|
|
21
21
|
disconnect(): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Detect the base address and addressing mode (0-based or 1-based) for SunSpec
|
|
24
|
+
*/
|
|
25
|
+
detectSunspecBaseAddress(): Promise<{
|
|
26
|
+
baseAddress: number;
|
|
27
|
+
isZeroBased: boolean;
|
|
28
|
+
nextAddress: number;
|
|
29
|
+
}>;
|
|
22
30
|
/**
|
|
23
31
|
* Discover all available Sunspec models
|
|
24
|
-
*
|
|
32
|
+
* Automatically detects base address (40000 or 40001) and scans from there
|
|
25
33
|
*/
|
|
26
34
|
discoverModels(): Promise<Map<number, SunspecModel>>;
|
|
27
35
|
/**
|
|
@@ -36,6 +44,11 @@ export declare class SunspecModbusClient {
|
|
|
36
44
|
* Convert unsigned 16-bit value to signed
|
|
37
45
|
*/
|
|
38
46
|
private convertToSigned16;
|
|
47
|
+
/**
|
|
48
|
+
* Check if a value is "unimplemented" according to Sunspec
|
|
49
|
+
* Returns true if the value represents an unimplemented/not applicable register
|
|
50
|
+
*/
|
|
51
|
+
private isUnimplementedValue;
|
|
39
52
|
/**
|
|
40
53
|
* Helper to clean string values by removing null characters
|
|
41
54
|
*/
|
|
@@ -58,6 +71,7 @@ export declare class SunspecModbusClient {
|
|
|
58
71
|
private readInverterScaleFactors;
|
|
59
72
|
/**
|
|
60
73
|
* Apply scale factor to a value
|
|
74
|
+
* Returns undefined if the value is unimplemented or scale factor is out of range
|
|
61
75
|
*/
|
|
62
76
|
private applyScaleFactor;
|
|
63
77
|
/**
|
package/dist/cjs/version.cjs
CHANGED
package/dist/cjs/version.d.cts
CHANGED
|
@@ -2,7 +2,7 @@ import { ApplianceManager, EnergyApp } from "@enyo-energy/energy-app-sdk";
|
|
|
2
2
|
import { EnyoApplianceName } from "@enyo-energy/energy-app-sdk/dist/types/enyo-appliance.js";
|
|
3
3
|
import { EnyoNetworkDevice } from "@enyo-energy/energy-app-sdk/dist/types/enyo-network-device.js";
|
|
4
4
|
import { SunspecModbusClient } from "./sunspec-modbus-client.js";
|
|
5
|
-
import { EnyoDataBusMessage } from "@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js";
|
|
5
|
+
import { EnyoDataBusMessage, EnyoDataBusMessageResolution } from "@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js";
|
|
6
6
|
/**
|
|
7
7
|
* Base abstract class for all Sunspec devices
|
|
8
8
|
*/
|
|
@@ -27,7 +27,7 @@ export declare abstract class BaseSunspecDevice {
|
|
|
27
27
|
/**
|
|
28
28
|
* Update device data and return data bus messages
|
|
29
29
|
*/
|
|
30
|
-
abstract readData(clockId: string, resolution:
|
|
30
|
+
abstract readData(clockId: string, resolution: EnyoDataBusMessageResolution): Promise<EnyoDataBusMessage[]>;
|
|
31
31
|
/**
|
|
32
32
|
* Check if the device is connected
|
|
33
33
|
*/
|
|
@@ -83,5 +83,5 @@ export declare class SunspecMeter extends BaseSunspecDevice {
|
|
|
83
83
|
/**
|
|
84
84
|
* Update meter data and return data bus messages
|
|
85
85
|
*/
|
|
86
|
-
readData(clockId: string, resolution:
|
|
86
|
+
readData(clockId: string, resolution: EnyoDataBusMessageResolution): Promise<EnyoDataBusMessage[]>;
|
|
87
87
|
}
|
package/dist/sunspec-devices.js
CHANGED
|
@@ -109,12 +109,12 @@ export class SunspecInverter extends BaseSunspecDevice {
|
|
|
109
109
|
timestampIso: timestamp.toISOString(),
|
|
110
110
|
resolution,
|
|
111
111
|
data: {
|
|
112
|
-
pvPowerW: inverterData.
|
|
113
|
-
activePowerLimitationW: inverterData.acPower || 0,
|
|
112
|
+
pvPowerW: inverterData.dcPower || 0, // Use DC power for PV power
|
|
113
|
+
activePowerLimitationW: inverterData.acPower || 0,
|
|
114
114
|
state: this.mapOperatingState(inverterData.operatingState),
|
|
115
115
|
voltageL1: inverterData.voltageAN || 0,
|
|
116
|
-
voltageL2: inverterData.voltageBN,
|
|
117
|
-
voltageL3: inverterData.voltageCN,
|
|
116
|
+
voltageL2: inverterData.voltageBN ?? undefined, // Use null if undefined (unimplemented phase)
|
|
117
|
+
voltageL3: inverterData.voltageCN ?? undefined, // Use null if undefined (unimplemented phase)
|
|
118
118
|
strings: this.mapMPPTToStrings(mpptDataList)
|
|
119
119
|
}
|
|
120
120
|
};
|
|
@@ -148,11 +148,14 @@ export class SunspecInverter extends BaseSunspecDevice {
|
|
|
148
148
|
mapMPPTToStrings(mpptDataList) {
|
|
149
149
|
const result = [];
|
|
150
150
|
mpptDataList.forEach((mppt, index) => {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
151
|
+
// Only include strings with valid data
|
|
152
|
+
if (mppt.dcVoltage !== undefined || mppt.dcPower !== undefined) {
|
|
153
|
+
result.push({
|
|
154
|
+
index: index + 1,
|
|
155
|
+
voltage: mppt.dcVoltage ?? undefined, // Use null if undefined
|
|
156
|
+
powerW: mppt.dcPower ?? 0 // Default to 0 if undefined
|
|
157
|
+
});
|
|
158
|
+
}
|
|
156
159
|
});
|
|
157
160
|
return result;
|
|
158
161
|
}
|
|
@@ -19,9 +19,17 @@ export declare class SunspecModbusClient {
|
|
|
19
19
|
* Disconnect from Modbus device
|
|
20
20
|
*/
|
|
21
21
|
disconnect(): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Detect the base address and addressing mode (0-based or 1-based) for SunSpec
|
|
24
|
+
*/
|
|
25
|
+
detectSunspecBaseAddress(): Promise<{
|
|
26
|
+
baseAddress: number;
|
|
27
|
+
isZeroBased: boolean;
|
|
28
|
+
nextAddress: number;
|
|
29
|
+
}>;
|
|
22
30
|
/**
|
|
23
31
|
* Discover all available Sunspec models
|
|
24
|
-
*
|
|
32
|
+
* Automatically detects base address (40000 or 40001) and scans from there
|
|
25
33
|
*/
|
|
26
34
|
discoverModels(): Promise<Map<number, SunspecModel>>;
|
|
27
35
|
/**
|
|
@@ -36,6 +44,11 @@ export declare class SunspecModbusClient {
|
|
|
36
44
|
* Convert unsigned 16-bit value to signed
|
|
37
45
|
*/
|
|
38
46
|
private convertToSigned16;
|
|
47
|
+
/**
|
|
48
|
+
* Check if a value is "unimplemented" according to Sunspec
|
|
49
|
+
* Returns true if the value represents an unimplemented/not applicable register
|
|
50
|
+
*/
|
|
51
|
+
private isUnimplementedValue;
|
|
39
52
|
/**
|
|
40
53
|
* Helper to clean string values by removing null characters
|
|
41
54
|
*/
|
|
@@ -58,6 +71,7 @@ export declare class SunspecModbusClient {
|
|
|
58
71
|
private readInverterScaleFactors;
|
|
59
72
|
/**
|
|
60
73
|
* Apply scale factor to a value
|
|
74
|
+
* Returns undefined if the value is unimplemented or scale factor is out of range
|
|
61
75
|
*/
|
|
62
76
|
private applyScaleFactor;
|
|
63
77
|
/**
|
|
@@ -48,29 +48,63 @@ export class SunspecModbusClient {
|
|
|
48
48
|
this.scaleFactors = {};
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Detect the base address and addressing mode (0-based or 1-based) for SunSpec
|
|
53
|
+
*/
|
|
54
|
+
async detectSunspecBaseAddress() {
|
|
55
|
+
if (!this.modbusClient) {
|
|
56
|
+
throw new Error('Modbus client not initialized');
|
|
57
|
+
}
|
|
58
|
+
// Try 1-based addressing first (most common)
|
|
59
|
+
try {
|
|
60
|
+
const sunspecId = await this.modbusClient.readRegisterStringValue(40001, 2);
|
|
61
|
+
if (sunspecId.includes('SunS')) {
|
|
62
|
+
console.log('Detected 1-based addressing mode (base address: 40001)');
|
|
63
|
+
this.baseAddress = 40001;
|
|
64
|
+
return {
|
|
65
|
+
baseAddress: 40001,
|
|
66
|
+
isZeroBased: false,
|
|
67
|
+
nextAddress: 40003
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
console.debug('Could not read SunS at 40001:', error);
|
|
73
|
+
}
|
|
74
|
+
// Try 0-based addressing
|
|
75
|
+
try {
|
|
76
|
+
const sunspecId = await this.modbusClient.readRegisterStringValue(40000, 2);
|
|
77
|
+
if (sunspecId.includes('SunS')) {
|
|
78
|
+
console.log('Detected 0-based addressing mode (base address: 40000)');
|
|
79
|
+
this.baseAddress = 40000;
|
|
80
|
+
return {
|
|
81
|
+
baseAddress: 40000,
|
|
82
|
+
isZeroBased: true,
|
|
83
|
+
nextAddress: 40002
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
console.debug('Could not read SunS at 40000:', error);
|
|
89
|
+
}
|
|
90
|
+
throw new Error('Device is not SunSpec compliant - "SunS" identifier not found at addresses 40000 or 40001');
|
|
91
|
+
}
|
|
51
92
|
/**
|
|
52
93
|
* Discover all available Sunspec models
|
|
53
|
-
*
|
|
94
|
+
* Automatically detects base address (40000 or 40001) and scans from there
|
|
54
95
|
*/
|
|
55
96
|
async discoverModels() {
|
|
56
97
|
if (!this.connected) {
|
|
57
98
|
throw new Error('Not connected to Modbus device');
|
|
58
99
|
}
|
|
59
100
|
this.discoveredModels.clear();
|
|
60
|
-
let currentAddress = this.baseAddress;
|
|
61
101
|
const maxAddress = 50000; // Safety limit
|
|
102
|
+
let currentAddress = 0;
|
|
62
103
|
console.log('Starting Sunspec model discovery...');
|
|
63
104
|
try {
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
const sunspecId = await this.modbusClient.readRegisterStringValue(40001, 2);
|
|
69
|
-
if (!sunspecId.includes('SunS')) {
|
|
70
|
-
console.warn('Device may not be Sunspec compliant - missing SunS identifier');
|
|
71
|
-
}
|
|
72
|
-
// Start scanning after the SunS identifier
|
|
73
|
-
currentAddress = 40003;
|
|
105
|
+
// Detect the base address and addressing mode
|
|
106
|
+
const addressInfo = await this.detectSunspecBaseAddress();
|
|
107
|
+
currentAddress = addressInfo.nextAddress;
|
|
74
108
|
while (currentAddress < maxAddress) {
|
|
75
109
|
// Read model ID and length
|
|
76
110
|
if (!this.modbusClient) {
|
|
@@ -152,6 +186,24 @@ export class SunspecModbusClient {
|
|
|
152
186
|
}
|
|
153
187
|
return value;
|
|
154
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Check if a value is "unimplemented" according to Sunspec
|
|
191
|
+
* Returns true if the value represents an unimplemented/not applicable register
|
|
192
|
+
*/
|
|
193
|
+
isUnimplementedValue(value, dataType = 'uint16') {
|
|
194
|
+
switch (dataType) {
|
|
195
|
+
case 'uint16':
|
|
196
|
+
return value === 0xFFFF || value === 65535;
|
|
197
|
+
case 'int16':
|
|
198
|
+
return value === 0x7FFF || value === 32767;
|
|
199
|
+
case 'uint32':
|
|
200
|
+
return value === 0xFFFFFFFF;
|
|
201
|
+
case 'int32':
|
|
202
|
+
return value === 0x7FFFFFFF;
|
|
203
|
+
default:
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
155
207
|
/**
|
|
156
208
|
* Helper to clean string values by removing null characters
|
|
157
209
|
*/
|
|
@@ -231,6 +283,15 @@ export class SunspecModbusClient {
|
|
|
231
283
|
// Read all scale factors first using fault-tolerant reader
|
|
232
284
|
const scaleFactors = await this.readInverterScaleFactors(baseAddr);
|
|
233
285
|
// Read values using fault-tolerant reader with proper data types
|
|
286
|
+
// Read raw voltage values to check for unimplemented phases
|
|
287
|
+
const voltageANRaw = await this.readRegisterValue(baseAddr + 10, 1, 'uint16');
|
|
288
|
+
const voltageBNRaw = await this.readRegisterValue(baseAddr + 11, 1, 'uint16');
|
|
289
|
+
const voltageCNRaw = await this.readRegisterValue(baseAddr + 12, 1, 'uint16');
|
|
290
|
+
console.log('Inverter Raw Voltage Values:', {
|
|
291
|
+
voltageANRaw: `0x${voltageANRaw.toString(16).toUpperCase()} (${voltageANRaw})`,
|
|
292
|
+
voltageBNRaw: `0x${voltageBNRaw.toString(16).toUpperCase()} (${voltageBNRaw})`,
|
|
293
|
+
voltageCNRaw: `0x${voltageCNRaw.toString(16).toUpperCase()} (${voltageCNRaw})`
|
|
294
|
+
});
|
|
234
295
|
const data = {
|
|
235
296
|
blockNumber: 103,
|
|
236
297
|
blockAddress: model.address,
|
|
@@ -244,9 +305,10 @@ export class SunspecModbusClient {
|
|
|
244
305
|
voltageAB: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 7, 1, 'uint16'), scaleFactors.V_SF),
|
|
245
306
|
voltageBC: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 8, 1, 'uint16'), scaleFactors.V_SF),
|
|
246
307
|
voltageCA: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 9, 1, 'uint16'), scaleFactors.V_SF),
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
308
|
+
// Apply scale factor with unimplemented value checking
|
|
309
|
+
voltageAN: this.applyScaleFactor(voltageANRaw, scaleFactors.V_SF),
|
|
310
|
+
voltageBN: this.applyScaleFactor(voltageBNRaw, scaleFactors.V_SF),
|
|
311
|
+
voltageCN: this.applyScaleFactor(voltageCNRaw, scaleFactors.V_SF),
|
|
250
312
|
// Power values - Offsets 14, 18, 20, 22
|
|
251
313
|
acPower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 14, 1, 'int16'), scaleFactors.W_SF),
|
|
252
314
|
apparentPower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 18, 1, 'uint16'), scaleFactors.VA_SF),
|
|
@@ -312,7 +374,7 @@ export class SunspecModbusClient {
|
|
|
312
374
|
* Read inverter scale factors
|
|
313
375
|
*/
|
|
314
376
|
async readInverterScaleFactors(baseAddr) {
|
|
315
|
-
|
|
377
|
+
const scaleFactors = {
|
|
316
378
|
A_SF: await this.readRegisterValue(baseAddr + 6, 1, 'int16'), // Offset 6
|
|
317
379
|
V_SF: await this.readRegisterValue(baseAddr + 13, 1, 'int16'), // Offset 13
|
|
318
380
|
W_SF: await this.readRegisterValue(baseAddr + 15, 1, 'int16'), // Offset 15
|
|
@@ -326,11 +388,23 @@ export class SunspecModbusClient {
|
|
|
326
388
|
DCW_SF: await this.readRegisterValue(baseAddr + 31, 1, 'int16'), // Offset 31
|
|
327
389
|
Tmp_SF: await this.readRegisterValue(baseAddr + 36, 1, 'int16') // Offset 36
|
|
328
390
|
};
|
|
391
|
+
console.log('Inverter Scale Factors:', JSON.stringify(scaleFactors, null, 2));
|
|
392
|
+
return scaleFactors;
|
|
329
393
|
}
|
|
330
394
|
/**
|
|
331
395
|
* Apply scale factor to a value
|
|
396
|
+
* Returns undefined if the value is unimplemented or scale factor is out of range
|
|
332
397
|
*/
|
|
333
|
-
applyScaleFactor(value, scaleFactor) {
|
|
398
|
+
applyScaleFactor(value, scaleFactor, dataType = 'uint16') {
|
|
399
|
+
// Check for unimplemented values
|
|
400
|
+
if (this.isUnimplementedValue(value, dataType)) {
|
|
401
|
+
return undefined;
|
|
402
|
+
}
|
|
403
|
+
// Validate scale factor is within reasonable range (-10 to +10)
|
|
404
|
+
if (Math.abs(scaleFactor) > 10) {
|
|
405
|
+
console.warn(`Scale factor ${scaleFactor} is outside reasonable range, clamping to ±10`);
|
|
406
|
+
scaleFactor = Math.max(-10, Math.min(10, scaleFactor));
|
|
407
|
+
}
|
|
334
408
|
return value * Math.pow(10, scaleFactor);
|
|
335
409
|
}
|
|
336
410
|
/**
|
|
@@ -356,6 +430,28 @@ export class SunspecModbusClient {
|
|
|
356
430
|
DCWH_SF: await this.readRegisterValue(moduleAddr + 17, 1, 'int16'),
|
|
357
431
|
Tmp_SF: await this.readRegisterValue(moduleAddr + 21, 1, 'int16')
|
|
358
432
|
};
|
|
433
|
+
console.log(`MPPT Module ${moduleId} Scale Factors:`, JSON.stringify(scaleFactors, null, 2));
|
|
434
|
+
// Read raw values first
|
|
435
|
+
const dcCurrentRaw = await this.readRegisterValue(moduleAddr + 9, 1, 'uint16');
|
|
436
|
+
const dcVoltageRaw = await this.readRegisterValue(moduleAddr + 11, 1, 'uint16');
|
|
437
|
+
const dcPowerRaw = await this.readRegisterValue(moduleAddr + 13, 1, 'uint16');
|
|
438
|
+
const dcEnergyRaw = await this.readRegisterValue(moduleAddr + 15, 2, 'acc32');
|
|
439
|
+
const temperatureRaw = await this.readRegisterValue(moduleAddr + 20, 1, 'int16');
|
|
440
|
+
console.log(`MPPT Module ${moduleId} Raw Values:`, {
|
|
441
|
+
dcCurrentRaw: `0x${dcCurrentRaw.toString(16).toUpperCase()} (${dcCurrentRaw})`,
|
|
442
|
+
dcVoltageRaw: `0x${dcVoltageRaw.toString(16).toUpperCase()} (${dcVoltageRaw})`,
|
|
443
|
+
dcPowerRaw: `0x${dcPowerRaw.toString(16).toUpperCase()} (${dcPowerRaw})`,
|
|
444
|
+
dcEnergyRaw: `0x${dcEnergyRaw.toString(16).toUpperCase()} (${dcEnergyRaw})`,
|
|
445
|
+
temperatureRaw: `0x${temperatureRaw.toString(16).toUpperCase()} (${temperatureRaw})`
|
|
446
|
+
});
|
|
447
|
+
// Check if this module is actually implemented/connected
|
|
448
|
+
// If all key values are unimplemented, this module doesn't exist
|
|
449
|
+
if (this.isUnimplementedValue(dcCurrentRaw, 'uint16') &&
|
|
450
|
+
this.isUnimplementedValue(dcVoltageRaw, 'uint16') &&
|
|
451
|
+
this.isUnimplementedValue(dcPowerRaw, 'uint16')) {
|
|
452
|
+
console.log(`MPPT module ${moduleId} appears to be unconnected (all values are 0xFFFF)`);
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
359
455
|
const data = {
|
|
360
456
|
blockNumber: 160,
|
|
361
457
|
blockAddress: model.address,
|
|
@@ -364,22 +460,24 @@ export class SunspecModbusClient {
|
|
|
364
460
|
// String ID - Offset 1 (8 registers for string)
|
|
365
461
|
stringId: await this.readRegisterValue(moduleAddr + 1, 8, 'string'),
|
|
366
462
|
// DC Current - Offset 9
|
|
367
|
-
dcCurrent: this.applyScaleFactor(
|
|
463
|
+
dcCurrent: this.applyScaleFactor(dcCurrentRaw, scaleFactors.DCA_SF),
|
|
368
464
|
dcCurrentSF: scaleFactors.DCA_SF,
|
|
369
465
|
// DC Voltage - Offset 11
|
|
370
|
-
dcVoltage: this.applyScaleFactor(
|
|
466
|
+
dcVoltage: this.applyScaleFactor(dcVoltageRaw, scaleFactors.DCV_SF),
|
|
371
467
|
dcVoltageSF: scaleFactors.DCV_SF,
|
|
372
468
|
// DC Power - Offset 13
|
|
373
|
-
dcPower: this.applyScaleFactor(
|
|
469
|
+
dcPower: this.applyScaleFactor(dcPowerRaw, scaleFactors.DCW_SF),
|
|
374
470
|
dcPowerSF: scaleFactors.DCW_SF,
|
|
375
471
|
// DC Energy - Offset 15-16 (32-bit accumulator)
|
|
376
|
-
|
|
377
|
-
|
|
472
|
+
// Only calculate if value is not unimplemented
|
|
473
|
+
dcEnergy: !this.isUnimplementedValue(dcEnergyRaw, 'uint32')
|
|
474
|
+
? BigInt(dcEnergyRaw) * BigInt(Math.pow(10, scaleFactors.DCWH_SF))
|
|
475
|
+
: undefined,
|
|
378
476
|
dcEnergySF: scaleFactors.DCWH_SF,
|
|
379
477
|
// Timestamp - Offset 18-19 (32-bit)
|
|
380
478
|
timestamp: await this.readRegisterValue(moduleAddr + 18, 2, 'uint32'),
|
|
381
479
|
// Temperature - Offset 20
|
|
382
|
-
temperature: this.applyScaleFactor(
|
|
480
|
+
temperature: this.applyScaleFactor(temperatureRaw, scaleFactors.Tmp_SF, 'int16'),
|
|
383
481
|
temperatureSF: scaleFactors.Tmp_SF,
|
|
384
482
|
// Operating State - Offset 22
|
|
385
483
|
operatingState: await this.readRegisterValue(moduleAddr + 22, 1, 'uint16'),
|
|
@@ -402,9 +500,19 @@ export class SunspecModbusClient {
|
|
|
402
500
|
const mpptData = [];
|
|
403
501
|
// Try to read up to 4 MPPT strings (typical maximum)
|
|
404
502
|
for (let i = 1; i <= 4; i++) {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
503
|
+
try {
|
|
504
|
+
const data = await this.readMPPTData(i);
|
|
505
|
+
// Only include if we got valid data (not null) and it has actual values
|
|
506
|
+
if (data &&
|
|
507
|
+
(data.dcCurrent !== undefined ||
|
|
508
|
+
data.dcVoltage !== undefined ||
|
|
509
|
+
data.dcPower !== undefined)) {
|
|
510
|
+
mpptData.push(data);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
catch (error) {
|
|
514
|
+
console.debug(`Could not read MPPT module ${i}: ${error}`);
|
|
515
|
+
// Continue to try other modules
|
|
408
516
|
}
|
|
409
517
|
}
|
|
410
518
|
return mpptData;
|
|
@@ -449,15 +557,31 @@ export class SunspecModbusClient {
|
|
|
449
557
|
return null;
|
|
450
558
|
}
|
|
451
559
|
const baseAddr = model.address + 2; // Skip ID and Length
|
|
560
|
+
console.log(`Reading Common Block - Model address: ${model.address}, Base address for data: ${baseAddr}`);
|
|
452
561
|
try {
|
|
453
562
|
// Read all strings using fault-tolerant reader with proper data type conversion
|
|
454
|
-
|
|
455
|
-
const
|
|
456
|
-
const
|
|
457
|
-
const
|
|
458
|
-
const
|
|
459
|
-
const
|
|
460
|
-
|
|
563
|
+
// Common block offsets are relative to the start of the data (after ID and Length)
|
|
564
|
+
const manufacturerAddr = baseAddr; // Offset 0-15 (16 registers) from data start
|
|
565
|
+
const modelAddr = baseAddr + 16; // Offset 16-31 (16 registers) from data start
|
|
566
|
+
const optionsAddr = baseAddr + 32; // Offset 32-39 (8 registers) from data start
|
|
567
|
+
const versionAddr = baseAddr + 40; // Offset 40-47 (8 registers) from data start
|
|
568
|
+
const serialAddr = baseAddr + 48; // Offset 48-63 (16 registers) from data start
|
|
569
|
+
const deviceAddrAddr = baseAddr + 64; // Offset 64 from data start
|
|
570
|
+
console.log(`Reading manufacturer from address ${manufacturerAddr} (16 registers)`);
|
|
571
|
+
const manufacturer = await this.readRegisterValue(manufacturerAddr, 16, 'string');
|
|
572
|
+
console.log(`Manufacturer raw value: "${manufacturer}"`);
|
|
573
|
+
console.log(`Reading model from address ${modelAddr} (16 registers)`);
|
|
574
|
+
const modelName = await this.readRegisterValue(modelAddr, 16, 'string');
|
|
575
|
+
console.log(`Model raw value: "${modelName}"`);
|
|
576
|
+
console.log(`Reading options from address ${optionsAddr} (8 registers)`);
|
|
577
|
+
const options = await this.readRegisterValue(optionsAddr, 8, 'string');
|
|
578
|
+
console.log(`Reading version from address ${versionAddr} (8 registers)`);
|
|
579
|
+
const version = await this.readRegisterValue(versionAddr, 8, 'string');
|
|
580
|
+
console.log(`Reading serial from address ${serialAddr} (16 registers)`);
|
|
581
|
+
const serialNumber = await this.readRegisterValue(serialAddr, 16, 'string');
|
|
582
|
+
console.log(`Reading device address from address ${deviceAddrAddr}`);
|
|
583
|
+
const deviceAddress = await this.readRegisterValue(deviceAddrAddr, 1, 'uint16');
|
|
584
|
+
const result = {
|
|
461
585
|
manufacturer: manufacturer,
|
|
462
586
|
model: modelName,
|
|
463
587
|
options: options,
|
|
@@ -465,6 +589,8 @@ export class SunspecModbusClient {
|
|
|
465
589
|
serialNumber: serialNumber,
|
|
466
590
|
deviceAddress: deviceAddress
|
|
467
591
|
};
|
|
592
|
+
console.log('Common Block Data:', JSON.stringify(result, null, 2));
|
|
593
|
+
return result;
|
|
468
594
|
}
|
|
469
595
|
catch (error) {
|
|
470
596
|
console.error(`Error reading common block: ${error}`);
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED