@enyo-energy/sunspec-sdk 0.0.5 → 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 +113 -21
- package/dist/cjs/sunspec-modbus-client.d.cts +6 -0
- 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 +6 -0
- package/dist/sunspec-modbus-client.js +113 -21
- 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
|
}
|
|
@@ -189,6 +189,24 @@ class SunspecModbusClient {
|
|
|
189
189
|
}
|
|
190
190
|
return value;
|
|
191
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
|
+
}
|
|
192
210
|
/**
|
|
193
211
|
* Helper to clean string values by removing null characters
|
|
194
212
|
*/
|
|
@@ -268,6 +286,15 @@ class SunspecModbusClient {
|
|
|
268
286
|
// Read all scale factors first using fault-tolerant reader
|
|
269
287
|
const scaleFactors = await this.readInverterScaleFactors(baseAddr);
|
|
270
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
|
+
});
|
|
271
298
|
const data = {
|
|
272
299
|
blockNumber: 103,
|
|
273
300
|
blockAddress: model.address,
|
|
@@ -281,9 +308,10 @@ class SunspecModbusClient {
|
|
|
281
308
|
voltageAB: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 7, 1, 'uint16'), scaleFactors.V_SF),
|
|
282
309
|
voltageBC: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 8, 1, 'uint16'), scaleFactors.V_SF),
|
|
283
310
|
voltageCA: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 9, 1, 'uint16'), scaleFactors.V_SF),
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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),
|
|
287
315
|
// Power values - Offsets 14, 18, 20, 22
|
|
288
316
|
acPower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 14, 1, 'int16'), scaleFactors.W_SF),
|
|
289
317
|
apparentPower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 18, 1, 'uint16'), scaleFactors.VA_SF),
|
|
@@ -349,7 +377,7 @@ class SunspecModbusClient {
|
|
|
349
377
|
* Read inverter scale factors
|
|
350
378
|
*/
|
|
351
379
|
async readInverterScaleFactors(baseAddr) {
|
|
352
|
-
|
|
380
|
+
const scaleFactors = {
|
|
353
381
|
A_SF: await this.readRegisterValue(baseAddr + 6, 1, 'int16'), // Offset 6
|
|
354
382
|
V_SF: await this.readRegisterValue(baseAddr + 13, 1, 'int16'), // Offset 13
|
|
355
383
|
W_SF: await this.readRegisterValue(baseAddr + 15, 1, 'int16'), // Offset 15
|
|
@@ -363,11 +391,23 @@ class SunspecModbusClient {
|
|
|
363
391
|
DCW_SF: await this.readRegisterValue(baseAddr + 31, 1, 'int16'), // Offset 31
|
|
364
392
|
Tmp_SF: await this.readRegisterValue(baseAddr + 36, 1, 'int16') // Offset 36
|
|
365
393
|
};
|
|
394
|
+
console.log('Inverter Scale Factors:', JSON.stringify(scaleFactors, null, 2));
|
|
395
|
+
return scaleFactors;
|
|
366
396
|
}
|
|
367
397
|
/**
|
|
368
398
|
* Apply scale factor to a value
|
|
399
|
+
* Returns undefined if the value is unimplemented or scale factor is out of range
|
|
369
400
|
*/
|
|
370
|
-
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
|
+
}
|
|
371
411
|
return value * Math.pow(10, scaleFactor);
|
|
372
412
|
}
|
|
373
413
|
/**
|
|
@@ -393,6 +433,28 @@ class SunspecModbusClient {
|
|
|
393
433
|
DCWH_SF: await this.readRegisterValue(moduleAddr + 17, 1, 'int16'),
|
|
394
434
|
Tmp_SF: await this.readRegisterValue(moduleAddr + 21, 1, 'int16')
|
|
395
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
|
+
}
|
|
396
458
|
const data = {
|
|
397
459
|
blockNumber: 160,
|
|
398
460
|
blockAddress: model.address,
|
|
@@ -401,22 +463,24 @@ class SunspecModbusClient {
|
|
|
401
463
|
// String ID - Offset 1 (8 registers for string)
|
|
402
464
|
stringId: await this.readRegisterValue(moduleAddr + 1, 8, 'string'),
|
|
403
465
|
// DC Current - Offset 9
|
|
404
|
-
dcCurrent: this.applyScaleFactor(
|
|
466
|
+
dcCurrent: this.applyScaleFactor(dcCurrentRaw, scaleFactors.DCA_SF),
|
|
405
467
|
dcCurrentSF: scaleFactors.DCA_SF,
|
|
406
468
|
// DC Voltage - Offset 11
|
|
407
|
-
dcVoltage: this.applyScaleFactor(
|
|
469
|
+
dcVoltage: this.applyScaleFactor(dcVoltageRaw, scaleFactors.DCV_SF),
|
|
408
470
|
dcVoltageSF: scaleFactors.DCV_SF,
|
|
409
471
|
// DC Power - Offset 13
|
|
410
|
-
dcPower: this.applyScaleFactor(
|
|
472
|
+
dcPower: this.applyScaleFactor(dcPowerRaw, scaleFactors.DCW_SF),
|
|
411
473
|
dcPowerSF: scaleFactors.DCW_SF,
|
|
412
474
|
// DC Energy - Offset 15-16 (32-bit accumulator)
|
|
413
|
-
|
|
414
|
-
|
|
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,
|
|
415
479
|
dcEnergySF: scaleFactors.DCWH_SF,
|
|
416
480
|
// Timestamp - Offset 18-19 (32-bit)
|
|
417
481
|
timestamp: await this.readRegisterValue(moduleAddr + 18, 2, 'uint32'),
|
|
418
482
|
// Temperature - Offset 20
|
|
419
|
-
temperature: this.applyScaleFactor(
|
|
483
|
+
temperature: this.applyScaleFactor(temperatureRaw, scaleFactors.Tmp_SF, 'int16'),
|
|
420
484
|
temperatureSF: scaleFactors.Tmp_SF,
|
|
421
485
|
// Operating State - Offset 22
|
|
422
486
|
operatingState: await this.readRegisterValue(moduleAddr + 22, 1, 'uint16'),
|
|
@@ -439,9 +503,19 @@ class SunspecModbusClient {
|
|
|
439
503
|
const mpptData = [];
|
|
440
504
|
// Try to read up to 4 MPPT strings (typical maximum)
|
|
441
505
|
for (let i = 1; i <= 4; i++) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
|
445
519
|
}
|
|
446
520
|
}
|
|
447
521
|
return mpptData;
|
|
@@ -486,15 +560,31 @@ class SunspecModbusClient {
|
|
|
486
560
|
return null;
|
|
487
561
|
}
|
|
488
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}`);
|
|
489
564
|
try {
|
|
490
565
|
// Read all strings using fault-tolerant reader with proper data type conversion
|
|
491
|
-
|
|
492
|
-
const
|
|
493
|
-
const
|
|
494
|
-
const
|
|
495
|
-
const
|
|
496
|
-
const
|
|
497
|
-
|
|
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 = {
|
|
498
588
|
manufacturer: manufacturer,
|
|
499
589
|
model: modelName,
|
|
500
590
|
options: options,
|
|
@@ -502,6 +592,8 @@ class SunspecModbusClient {
|
|
|
502
592
|
serialNumber: serialNumber,
|
|
503
593
|
deviceAddress: deviceAddress
|
|
504
594
|
};
|
|
595
|
+
console.log('Common Block Data:', JSON.stringify(result, null, 2));
|
|
596
|
+
return result;
|
|
505
597
|
}
|
|
506
598
|
catch (error) {
|
|
507
599
|
console.error(`Error reading common block: ${error}`);
|
|
@@ -44,6 +44,11 @@ export declare class SunspecModbusClient {
|
|
|
44
44
|
* Convert unsigned 16-bit value to signed
|
|
45
45
|
*/
|
|
46
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;
|
|
47
52
|
/**
|
|
48
53
|
* Helper to clean string values by removing null characters
|
|
49
54
|
*/
|
|
@@ -66,6 +71,7 @@ export declare class SunspecModbusClient {
|
|
|
66
71
|
private readInverterScaleFactors;
|
|
67
72
|
/**
|
|
68
73
|
* Apply scale factor to a value
|
|
74
|
+
* Returns undefined if the value is unimplemented or scale factor is out of range
|
|
69
75
|
*/
|
|
70
76
|
private applyScaleFactor;
|
|
71
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
|
}
|
|
@@ -44,6 +44,11 @@ export declare class SunspecModbusClient {
|
|
|
44
44
|
* Convert unsigned 16-bit value to signed
|
|
45
45
|
*/
|
|
46
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;
|
|
47
52
|
/**
|
|
48
53
|
* Helper to clean string values by removing null characters
|
|
49
54
|
*/
|
|
@@ -66,6 +71,7 @@ export declare class SunspecModbusClient {
|
|
|
66
71
|
private readInverterScaleFactors;
|
|
67
72
|
/**
|
|
68
73
|
* Apply scale factor to a value
|
|
74
|
+
* Returns undefined if the value is unimplemented or scale factor is out of range
|
|
69
75
|
*/
|
|
70
76
|
private applyScaleFactor;
|
|
71
77
|
/**
|
|
@@ -186,6 +186,24 @@ export class SunspecModbusClient {
|
|
|
186
186
|
}
|
|
187
187
|
return value;
|
|
188
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
|
+
}
|
|
189
207
|
/**
|
|
190
208
|
* Helper to clean string values by removing null characters
|
|
191
209
|
*/
|
|
@@ -265,6 +283,15 @@ export class SunspecModbusClient {
|
|
|
265
283
|
// Read all scale factors first using fault-tolerant reader
|
|
266
284
|
const scaleFactors = await this.readInverterScaleFactors(baseAddr);
|
|
267
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
|
+
});
|
|
268
295
|
const data = {
|
|
269
296
|
blockNumber: 103,
|
|
270
297
|
blockAddress: model.address,
|
|
@@ -278,9 +305,10 @@ export class SunspecModbusClient {
|
|
|
278
305
|
voltageAB: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 7, 1, 'uint16'), scaleFactors.V_SF),
|
|
279
306
|
voltageBC: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 8, 1, 'uint16'), scaleFactors.V_SF),
|
|
280
307
|
voltageCA: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 9, 1, 'uint16'), scaleFactors.V_SF),
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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),
|
|
284
312
|
// Power values - Offsets 14, 18, 20, 22
|
|
285
313
|
acPower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 14, 1, 'int16'), scaleFactors.W_SF),
|
|
286
314
|
apparentPower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 18, 1, 'uint16'), scaleFactors.VA_SF),
|
|
@@ -346,7 +374,7 @@ export class SunspecModbusClient {
|
|
|
346
374
|
* Read inverter scale factors
|
|
347
375
|
*/
|
|
348
376
|
async readInverterScaleFactors(baseAddr) {
|
|
349
|
-
|
|
377
|
+
const scaleFactors = {
|
|
350
378
|
A_SF: await this.readRegisterValue(baseAddr + 6, 1, 'int16'), // Offset 6
|
|
351
379
|
V_SF: await this.readRegisterValue(baseAddr + 13, 1, 'int16'), // Offset 13
|
|
352
380
|
W_SF: await this.readRegisterValue(baseAddr + 15, 1, 'int16'), // Offset 15
|
|
@@ -360,11 +388,23 @@ export class SunspecModbusClient {
|
|
|
360
388
|
DCW_SF: await this.readRegisterValue(baseAddr + 31, 1, 'int16'), // Offset 31
|
|
361
389
|
Tmp_SF: await this.readRegisterValue(baseAddr + 36, 1, 'int16') // Offset 36
|
|
362
390
|
};
|
|
391
|
+
console.log('Inverter Scale Factors:', JSON.stringify(scaleFactors, null, 2));
|
|
392
|
+
return scaleFactors;
|
|
363
393
|
}
|
|
364
394
|
/**
|
|
365
395
|
* Apply scale factor to a value
|
|
396
|
+
* Returns undefined if the value is unimplemented or scale factor is out of range
|
|
366
397
|
*/
|
|
367
|
-
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
|
+
}
|
|
368
408
|
return value * Math.pow(10, scaleFactor);
|
|
369
409
|
}
|
|
370
410
|
/**
|
|
@@ -390,6 +430,28 @@ export class SunspecModbusClient {
|
|
|
390
430
|
DCWH_SF: await this.readRegisterValue(moduleAddr + 17, 1, 'int16'),
|
|
391
431
|
Tmp_SF: await this.readRegisterValue(moduleAddr + 21, 1, 'int16')
|
|
392
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
|
+
}
|
|
393
455
|
const data = {
|
|
394
456
|
blockNumber: 160,
|
|
395
457
|
blockAddress: model.address,
|
|
@@ -398,22 +460,24 @@ export class SunspecModbusClient {
|
|
|
398
460
|
// String ID - Offset 1 (8 registers for string)
|
|
399
461
|
stringId: await this.readRegisterValue(moduleAddr + 1, 8, 'string'),
|
|
400
462
|
// DC Current - Offset 9
|
|
401
|
-
dcCurrent: this.applyScaleFactor(
|
|
463
|
+
dcCurrent: this.applyScaleFactor(dcCurrentRaw, scaleFactors.DCA_SF),
|
|
402
464
|
dcCurrentSF: scaleFactors.DCA_SF,
|
|
403
465
|
// DC Voltage - Offset 11
|
|
404
|
-
dcVoltage: this.applyScaleFactor(
|
|
466
|
+
dcVoltage: this.applyScaleFactor(dcVoltageRaw, scaleFactors.DCV_SF),
|
|
405
467
|
dcVoltageSF: scaleFactors.DCV_SF,
|
|
406
468
|
// DC Power - Offset 13
|
|
407
|
-
dcPower: this.applyScaleFactor(
|
|
469
|
+
dcPower: this.applyScaleFactor(dcPowerRaw, scaleFactors.DCW_SF),
|
|
408
470
|
dcPowerSF: scaleFactors.DCW_SF,
|
|
409
471
|
// DC Energy - Offset 15-16 (32-bit accumulator)
|
|
410
|
-
|
|
411
|
-
|
|
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,
|
|
412
476
|
dcEnergySF: scaleFactors.DCWH_SF,
|
|
413
477
|
// Timestamp - Offset 18-19 (32-bit)
|
|
414
478
|
timestamp: await this.readRegisterValue(moduleAddr + 18, 2, 'uint32'),
|
|
415
479
|
// Temperature - Offset 20
|
|
416
|
-
temperature: this.applyScaleFactor(
|
|
480
|
+
temperature: this.applyScaleFactor(temperatureRaw, scaleFactors.Tmp_SF, 'int16'),
|
|
417
481
|
temperatureSF: scaleFactors.Tmp_SF,
|
|
418
482
|
// Operating State - Offset 22
|
|
419
483
|
operatingState: await this.readRegisterValue(moduleAddr + 22, 1, 'uint16'),
|
|
@@ -436,9 +500,19 @@ export class SunspecModbusClient {
|
|
|
436
500
|
const mpptData = [];
|
|
437
501
|
// Try to read up to 4 MPPT strings (typical maximum)
|
|
438
502
|
for (let i = 1; i <= 4; i++) {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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
|
|
442
516
|
}
|
|
443
517
|
}
|
|
444
518
|
return mpptData;
|
|
@@ -483,15 +557,31 @@ export class SunspecModbusClient {
|
|
|
483
557
|
return null;
|
|
484
558
|
}
|
|
485
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}`);
|
|
486
561
|
try {
|
|
487
562
|
// Read all strings using fault-tolerant reader with proper data type conversion
|
|
488
|
-
|
|
489
|
-
const
|
|
490
|
-
const
|
|
491
|
-
const
|
|
492
|
-
const
|
|
493
|
-
const
|
|
494
|
-
|
|
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 = {
|
|
495
585
|
manufacturer: manufacturer,
|
|
496
586
|
model: modelName,
|
|
497
587
|
options: options,
|
|
@@ -499,6 +589,8 @@ export class SunspecModbusClient {
|
|
|
499
589
|
serialNumber: serialNumber,
|
|
500
590
|
deviceAddress: deviceAddress
|
|
501
591
|
};
|
|
592
|
+
console.log('Common Block Data:', JSON.stringify(result, null, 2));
|
|
593
|
+
return result;
|
|
502
594
|
}
|
|
503
595
|
catch (error) {
|
|
504
596
|
console.error(`Error reading common block: ${error}`);
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED