@enyo-energy/sunspec-sdk 0.0.52 → 0.0.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/sunspec-interfaces.cjs +3 -0
- package/dist/cjs/sunspec-interfaces.d.cts +7 -2
- package/dist/cjs/sunspec-modbus-client.cjs +124 -44
- package/dist/cjs/sunspec-modbus-client.d.cts +21 -1
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/sunspec-interfaces.d.ts +7 -2
- package/dist/sunspec-interfaces.js +3 -0
- package/dist/sunspec-modbus-client.d.ts +21 -1
- package/dist/sunspec-modbus-client.js +124 -44
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -20,6 +20,9 @@ var SunspecModelId;
|
|
|
20
20
|
SunspecModelId[SunspecModelId["Common"] = 1] = "Common";
|
|
21
21
|
SunspecModelId[SunspecModelId["Inverter3Phase"] = 103] = "Inverter3Phase";
|
|
22
22
|
SunspecModelId[SunspecModelId["InverterSinglePhase"] = 101] = "InverterSinglePhase";
|
|
23
|
+
SunspecModelId[SunspecModelId["InverterSinglePhaseFloat"] = 111] = "InverterSinglePhaseFloat";
|
|
24
|
+
SunspecModelId[SunspecModelId["InverterSplitPhaseFloat"] = 112] = "InverterSplitPhaseFloat";
|
|
25
|
+
SunspecModelId[SunspecModelId["Inverter3PhaseFloat"] = 113] = "Inverter3PhaseFloat";
|
|
23
26
|
SunspecModelId[SunspecModelId["MPPT"] = 160] = "MPPT";
|
|
24
27
|
SunspecModelId[SunspecModelId["Battery"] = 124] = "Battery";
|
|
25
28
|
SunspecModelId[SunspecModelId["BatteryBase"] = 802] = "BatteryBase";
|
|
@@ -30,6 +30,9 @@ export declare enum SunspecModelId {
|
|
|
30
30
|
Common = 1,
|
|
31
31
|
Inverter3Phase = 103,
|
|
32
32
|
InverterSinglePhase = 101,
|
|
33
|
+
InverterSinglePhaseFloat = 111,
|
|
34
|
+
InverterSplitPhaseFloat = 112,
|
|
35
|
+
Inverter3PhaseFloat = 113,
|
|
33
36
|
MPPT = 160,
|
|
34
37
|
Battery = 124,
|
|
35
38
|
BatteryBase = 802,
|
|
@@ -74,10 +77,12 @@ export interface SunspecCommonBlock extends SunspecBlock {
|
|
|
74
77
|
deviceAddress?: number;
|
|
75
78
|
}
|
|
76
79
|
/**
|
|
77
|
-
* Inverter data structure
|
|
80
|
+
* Inverter data structure. Covers SunSpec int+SF inverter models 101/103 and float variants 111/112/113.
|
|
81
|
+
* The per-field offset comments below describe the int+SF (101/103) layout; float variants 111/112/113
|
|
82
|
+
* use 32-bit IEEE 754 float values at different offsets — see the float reader implementations.
|
|
78
83
|
*/
|
|
79
84
|
export interface SunspecInverterData extends SunspecBlock {
|
|
80
|
-
blockNumber: 103 | 101;
|
|
85
|
+
blockNumber: 103 | 101 | 113 | 112 | 111;
|
|
81
86
|
acCurrent?: number;
|
|
82
87
|
phaseACurrent?: number;
|
|
83
88
|
phaseBCurrent?: number;
|
|
@@ -504,7 +504,7 @@ class SunspecModbusClient {
|
|
|
504
504
|
catch (error) {
|
|
505
505
|
console.error(`Error during model discovery at address ${currentAddress} (unit ${unitId}): ${error}`);
|
|
506
506
|
}
|
|
507
|
-
console.log(`Discovery complete for unit ${unitId}. Found ${models.size} models`);
|
|
507
|
+
console.log(`Discovery complete for unit ${unitId}. Found ${models.size} models: [${[...models.keys()].sort((a, b) => a - b).join(', ')}]`);
|
|
508
508
|
return models;
|
|
509
509
|
}
|
|
510
510
|
/**
|
|
@@ -686,28 +686,36 @@ class SunspecModbusClient {
|
|
|
686
686
|
}
|
|
687
687
|
}
|
|
688
688
|
/**
|
|
689
|
-
* Read inverter data
|
|
689
|
+
* Read inverter data. Detects which SunSpec inverter model the device exposes —
|
|
690
|
+
* int+SF (101/103) or float (111/112/113) — by checking the discovered model directory,
|
|
691
|
+
* and dispatches to the appropriate decoder.
|
|
690
692
|
*/
|
|
691
693
|
async readInverterData(unitId) {
|
|
692
|
-
const
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
694
|
+
const tryOrder = [
|
|
695
|
+
{ id: 103, reader: m => this.readThreePhaseInverterData_IntSF(unitId, m) },
|
|
696
|
+
{ id: 113, reader: m => this.readFloatInverterData(unitId, m, 113) },
|
|
697
|
+
{ id: 112, reader: m => this.readFloatInverterData(unitId, m, 112) },
|
|
698
|
+
{ id: 101, reader: m => this.readSinglePhaseInverterData(unitId, m) },
|
|
699
|
+
{ id: 111, reader: m => this.readFloatInverterData(unitId, m, 111) },
|
|
700
|
+
];
|
|
701
|
+
for (const { id, reader } of tryOrder) {
|
|
702
|
+
const model = this.findModel(unitId, id);
|
|
703
|
+
if (model) {
|
|
704
|
+
console.debug(`Using inverter Model ${id} at address ${model.address} (length ${model.length}) on unit ${unitId}`);
|
|
705
|
+
return reader(model);
|
|
699
706
|
}
|
|
700
|
-
console.warn('IMPORTANT: Working with single-phase inverter, but 3-phase is expected!');
|
|
701
|
-
return this.readSinglePhaseInverterData(unitId, singlePhaseModel);
|
|
702
707
|
}
|
|
703
|
-
console.debug(`
|
|
708
|
+
console.debug(`No inverter model (101/103/111/112/113) on unit ${unitId}`);
|
|
709
|
+
return null;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Read three-phase inverter data from Model 103 (int + scale factor encoding).
|
|
713
|
+
*/
|
|
714
|
+
async readThreePhaseInverterData_IntSF(unitId, model) {
|
|
704
715
|
try {
|
|
705
|
-
|
|
706
|
-
console.debug(`Reading Inverter Data from Model ${model.id} at base address: ${model.address} (unit ${unitId})`);
|
|
716
|
+
console.debug(`Reading Inverter Data from Model 103 at base address: ${model.address} (unit ${unitId})`);
|
|
707
717
|
const buffer = await this.readModelBlock(unitId, model);
|
|
708
|
-
// Extract all scale factors from buffer
|
|
709
718
|
const scaleFactors = this.extractInverterScaleFactors(buffer);
|
|
710
|
-
// Extract raw values from buffer
|
|
711
719
|
const acCurrentRaw = this.extractValue(buffer, 2, 'uint16');
|
|
712
720
|
const voltageANRaw = this.extractValue(buffer, 10, 'uint16');
|
|
713
721
|
const voltageBNRaw = this.extractValue(buffer, 11, 'uint16');
|
|
@@ -759,7 +767,6 @@ class SunspecModbusClient {
|
|
|
759
767
|
vendorEvents3: this.extractValue(buffer, 48, 'uint32', 2),
|
|
760
768
|
vendorEvents4: this.extractValue(buffer, 50, 'uint32', 2)
|
|
761
769
|
};
|
|
762
|
-
// Log non-scaled fields
|
|
763
770
|
this.logRegisterRead(103, 38, 'Operating State', data.operatingState, 'enum16');
|
|
764
771
|
this.logRegisterRead(103, 39, 'Vendor State', data.vendorState, 'uint16');
|
|
765
772
|
this.logRegisterRead(103, 40, 'Events', data.events, 'bitfield32');
|
|
@@ -768,7 +775,6 @@ class SunspecModbusClient {
|
|
|
768
775
|
this.logRegisterRead(103, 46, 'Vendor Events 2', data.vendorEvents2, 'bitfield32');
|
|
769
776
|
this.logRegisterRead(103, 48, 'Vendor Events 3', data.vendorEvents3, 'bitfield32');
|
|
770
777
|
this.logRegisterRead(103, 50, 'Vendor Events 4', data.vendorEvents4, 'bitfield32');
|
|
771
|
-
// Read AC Energy (32-bit accumulator) - Offset 24-25
|
|
772
778
|
const acEnergy = this.extractValue(buffer, 24, 'uint32', 2);
|
|
773
779
|
this.logRegisterRead(103, 24, 'AC Energy', acEnergy, 'acc32');
|
|
774
780
|
data.acEnergy = !this.isUnimplementedValue(acEnergy, 'acc32')
|
|
@@ -790,44 +796,44 @@ class SunspecModbusClient {
|
|
|
790
796
|
console.debug(`Reading Single-Phase Inverter Data from Model 101 at base address: ${model.address} (unit ${unitId})`);
|
|
791
797
|
// Read entire model block in a single Modbus call
|
|
792
798
|
const buffer = await this.readModelBlock(unitId, model);
|
|
793
|
-
// Extract scale factors from buffer
|
|
799
|
+
// Extract scale factors from buffer (offsets aligned with Model 103 reader, which works on real
|
|
800
|
+
// hardware; differs from the published SunSpec Model 101 spec for DC fields).
|
|
794
801
|
const scaleFactors = {
|
|
795
802
|
A_SF: this.extractValue(buffer, 6, 'int16'),
|
|
796
803
|
V_SF: this.extractValue(buffer, 13, 'int16'),
|
|
797
|
-
W_SF: this.extractValue(buffer,
|
|
798
|
-
Hz_SF: this.extractValue(buffer,
|
|
804
|
+
W_SF: this.extractValue(buffer, 15, 'int16'),
|
|
805
|
+
Hz_SF: this.extractValue(buffer, 17, 'int16'),
|
|
799
806
|
WH_SF: this.extractValue(buffer, 26, 'int16'),
|
|
800
|
-
DCA_SF: this.extractValue(buffer,
|
|
801
|
-
DCV_SF: this.extractValue(buffer,
|
|
802
|
-
DCW_SF: this.extractValue(buffer,
|
|
807
|
+
DCA_SF: this.extractValue(buffer, 28, 'int16'),
|
|
808
|
+
DCV_SF: this.extractValue(buffer, 29, 'int16'),
|
|
809
|
+
DCW_SF: this.extractValue(buffer, 31, 'int16')
|
|
803
810
|
};
|
|
804
811
|
this.logRegisterRead(101, 6, 'A_SF', scaleFactors.A_SF, 'int16');
|
|
805
812
|
this.logRegisterRead(101, 13, 'V_SF', scaleFactors.V_SF, 'int16');
|
|
806
|
-
this.logRegisterRead(101,
|
|
807
|
-
this.logRegisterRead(101,
|
|
813
|
+
this.logRegisterRead(101, 15, 'W_SF', scaleFactors.W_SF, 'int16');
|
|
814
|
+
this.logRegisterRead(101, 17, 'Hz_SF', scaleFactors.Hz_SF, 'int16');
|
|
808
815
|
this.logRegisterRead(101, 26, 'WH_SF', scaleFactors.WH_SF, 'int16');
|
|
809
|
-
this.logRegisterRead(101,
|
|
810
|
-
this.logRegisterRead(101,
|
|
811
|
-
this.logRegisterRead(101,
|
|
812
|
-
// Extract raw values from buffer
|
|
816
|
+
this.logRegisterRead(101, 28, 'DCA_SF', scaleFactors.DCA_SF, 'int16');
|
|
817
|
+
this.logRegisterRead(101, 29, 'DCV_SF', scaleFactors.DCV_SF, 'int16');
|
|
818
|
+
this.logRegisterRead(101, 31, 'DCW_SF', scaleFactors.DCW_SF, 'int16');
|
|
813
819
|
const acCurrentRaw = this.extractValue(buffer, 2, 'uint16');
|
|
814
|
-
const voltageRaw = this.extractValue(buffer,
|
|
815
|
-
const acPowerRaw = this.extractValue(buffer,
|
|
816
|
-
const freqRaw = this.extractValue(buffer,
|
|
817
|
-
const dcCurrentRaw = this.extractValue(buffer,
|
|
818
|
-
const dcVoltageRaw = this.extractValue(buffer,
|
|
819
|
-
const dcPowerRaw = this.extractValue(buffer,
|
|
820
|
-
const stateRaw = this.extractValue(buffer,
|
|
821
|
-
this.logRegisterRead(101,
|
|
820
|
+
const voltageRaw = this.extractValue(buffer, 10, 'uint16');
|
|
821
|
+
const acPowerRaw = this.extractValue(buffer, 14, 'int16');
|
|
822
|
+
const freqRaw = this.extractValue(buffer, 16, 'uint16');
|
|
823
|
+
const dcCurrentRaw = this.extractValue(buffer, 27, 'uint16');
|
|
824
|
+
const dcVoltageRaw = this.extractValue(buffer, 28, 'uint16');
|
|
825
|
+
const dcPowerRaw = this.extractValue(buffer, 30, 'int16');
|
|
826
|
+
const stateRaw = this.extractValue(buffer, 38, 'uint16');
|
|
827
|
+
this.logRegisterRead(101, 38, 'Operating State', stateRaw, 'enum16');
|
|
822
828
|
const data = {
|
|
823
829
|
blockNumber: 101,
|
|
824
|
-
voltageAN: this.applyScaleFactor(voltageRaw, scaleFactors.V_SF, 'uint16', 'Voltage AN',
|
|
830
|
+
voltageAN: this.applyScaleFactor(voltageRaw, scaleFactors.V_SF, 'uint16', 'Voltage AN', 10, 101),
|
|
825
831
|
acCurrent: this.applyScaleFactor(acCurrentRaw, scaleFactors.A_SF, 'uint16', 'AC Current', 2, 101),
|
|
826
|
-
acPower: this.applyScaleFactor(acPowerRaw, scaleFactors.W_SF, 'int16', 'AC Power',
|
|
827
|
-
frequency: this.applyScaleFactor(freqRaw, scaleFactors.Hz_SF, 'uint16', 'Frequency',
|
|
828
|
-
dcCurrent: this.applyScaleFactor(dcCurrentRaw, scaleFactors.DCA_SF, 'uint16', 'DC Current',
|
|
829
|
-
dcVoltage: this.applyScaleFactor(dcVoltageRaw, scaleFactors.DCV_SF, 'uint16', 'DC Voltage',
|
|
830
|
-
dcPower: this.applyScaleFactor(dcPowerRaw, scaleFactors.DCW_SF, 'int16', 'DC Power',
|
|
832
|
+
acPower: this.applyScaleFactor(acPowerRaw, scaleFactors.W_SF, 'int16', 'AC Power', 14, 101),
|
|
833
|
+
frequency: this.applyScaleFactor(freqRaw, scaleFactors.Hz_SF, 'uint16', 'Frequency', 16, 101),
|
|
834
|
+
dcCurrent: this.applyScaleFactor(dcCurrentRaw, scaleFactors.DCA_SF, 'uint16', 'DC Current', 27, 101),
|
|
835
|
+
dcVoltage: this.applyScaleFactor(dcVoltageRaw, scaleFactors.DCV_SF, 'uint16', 'DC Voltage', 28, 101),
|
|
836
|
+
dcPower: this.applyScaleFactor(dcPowerRaw, scaleFactors.DCW_SF, 'int16', 'DC Power', 30, 101),
|
|
831
837
|
operatingState: stateRaw
|
|
832
838
|
};
|
|
833
839
|
// Read AC Energy (32-bit accumulator) - Offset 24-25
|
|
@@ -844,6 +850,70 @@ class SunspecModbusClient {
|
|
|
844
850
|
return null;
|
|
845
851
|
}
|
|
846
852
|
}
|
|
853
|
+
/**
|
|
854
|
+
* Read inverter data from a float-variant model: 111 (single-phase), 112 (split-phase), or 113 (three-phase).
|
|
855
|
+
*
|
|
856
|
+
* All three models share the same SunSpec register layout — measurement fields are 32-bit IEEE 754 floats
|
|
857
|
+
* (no scale factors), status registers stay enum16, event registers stay bitfield32. Phase scope differs
|
|
858
|
+
* by model: 111 populates phase A only; 112 phases A+B; 113 all three. Unpopulated phase fields read NaN
|
|
859
|
+
* and are returned as undefined.
|
|
860
|
+
*/
|
|
861
|
+
async readFloatInverterData(unitId, model, blockNumber) {
|
|
862
|
+
try {
|
|
863
|
+
console.debug(`Reading Float Inverter Data from Model ${blockNumber} at base address: ${model.address} (unit ${unitId})`);
|
|
864
|
+
const buffer = await this.readModelBlock(unitId, model);
|
|
865
|
+
const data = {
|
|
866
|
+
blockNumber,
|
|
867
|
+
blockAddress: model.address,
|
|
868
|
+
blockLength: model.length,
|
|
869
|
+
acCurrent: this.extractFloat32OrUndefined(buffer, 2, blockNumber, 'AC Current'),
|
|
870
|
+
phaseACurrent: this.extractFloat32OrUndefined(buffer, 4, blockNumber, 'Phase A Current'),
|
|
871
|
+
phaseBCurrent: this.extractFloat32OrUndefined(buffer, 6, blockNumber, 'Phase B Current'),
|
|
872
|
+
phaseCCurrent: this.extractFloat32OrUndefined(buffer, 8, blockNumber, 'Phase C Current'),
|
|
873
|
+
voltageAB: this.extractFloat32OrUndefined(buffer, 10, blockNumber, 'Voltage AB'),
|
|
874
|
+
voltageBC: this.extractFloat32OrUndefined(buffer, 12, blockNumber, 'Voltage BC'),
|
|
875
|
+
voltageCA: this.extractFloat32OrUndefined(buffer, 14, blockNumber, 'Voltage CA'),
|
|
876
|
+
voltageAN: this.extractFloat32OrUndefined(buffer, 16, blockNumber, 'Voltage AN'),
|
|
877
|
+
voltageBN: this.extractFloat32OrUndefined(buffer, 18, blockNumber, 'Voltage BN'),
|
|
878
|
+
voltageCN: this.extractFloat32OrUndefined(buffer, 20, blockNumber, 'Voltage CN'),
|
|
879
|
+
acPower: this.extractFloat32OrUndefined(buffer, 22, blockNumber, 'AC Power'),
|
|
880
|
+
frequency: this.extractFloat32OrUndefined(buffer, 24, blockNumber, 'Frequency'),
|
|
881
|
+
apparentPower: this.extractFloat32OrUndefined(buffer, 26, blockNumber, 'Apparent Power'),
|
|
882
|
+
reactivePower: this.extractFloat32OrUndefined(buffer, 28, blockNumber, 'Reactive Power'),
|
|
883
|
+
powerFactor: this.extractFloat32OrUndefined(buffer, 30, blockNumber, 'Power Factor'),
|
|
884
|
+
acEnergy: this.extractFloat32OrUndefined(buffer, 32, blockNumber, 'AC Energy'),
|
|
885
|
+
dcCurrent: this.extractFloat32OrUndefined(buffer, 34, blockNumber, 'DC Current'),
|
|
886
|
+
dcVoltage: this.extractFloat32OrUndefined(buffer, 36, blockNumber, 'DC Voltage'),
|
|
887
|
+
dcPower: this.extractFloat32OrUndefined(buffer, 38, blockNumber, 'DC Power'),
|
|
888
|
+
cabinetTemperature: this.extractFloat32OrUndefined(buffer, 40, blockNumber, 'Cabinet Temperature'),
|
|
889
|
+
heatSinkTemperature: this.extractFloat32OrUndefined(buffer, 42, blockNumber, 'Heat Sink Temperature'),
|
|
890
|
+
transformerTemperature: this.extractFloat32OrUndefined(buffer, 44, blockNumber, 'Transformer Temperature'),
|
|
891
|
+
otherTemperature: this.extractFloat32OrUndefined(buffer, 46, blockNumber, 'Other Temperature'),
|
|
892
|
+
operatingState: this.extractValue(buffer, 48, 'uint16'),
|
|
893
|
+
vendorState: this.extractValue(buffer, 49, 'uint16'),
|
|
894
|
+
events: this.extractValue(buffer, 50, 'uint32', 2),
|
|
895
|
+
events2: this.extractValue(buffer, 52, 'uint32', 2),
|
|
896
|
+
vendorEvents1: this.extractValue(buffer, 54, 'uint32', 2),
|
|
897
|
+
vendorEvents2: this.extractValue(buffer, 56, 'uint32', 2),
|
|
898
|
+
vendorEvents3: this.extractValue(buffer, 58, 'uint32', 2),
|
|
899
|
+
vendorEvents4: this.extractValue(buffer, 60, 'uint32', 2),
|
|
900
|
+
};
|
|
901
|
+
this.logRegisterRead(blockNumber, 48, 'Operating State', data.operatingState, 'enum16');
|
|
902
|
+
this.logRegisterRead(blockNumber, 49, 'Vendor State', data.vendorState, 'enum16');
|
|
903
|
+
this.logRegisterRead(blockNumber, 50, 'Events', data.events, 'bitfield32');
|
|
904
|
+
this.logRegisterRead(blockNumber, 52, 'Events2', data.events2, 'bitfield32');
|
|
905
|
+
this.logRegisterRead(blockNumber, 54, 'Vendor Events 1', data.vendorEvents1, 'bitfield32');
|
|
906
|
+
this.logRegisterRead(blockNumber, 56, 'Vendor Events 2', data.vendorEvents2, 'bitfield32');
|
|
907
|
+
this.logRegisterRead(blockNumber, 58, 'Vendor Events 3', data.vendorEvents3, 'bitfield32');
|
|
908
|
+
this.logRegisterRead(blockNumber, 60, 'Vendor Events 4', data.vendorEvents4, 'bitfield32');
|
|
909
|
+
console.debug(`[Model ${blockNumber}] Float Inverter Data:`, data);
|
|
910
|
+
return data;
|
|
911
|
+
}
|
|
912
|
+
catch (error) {
|
|
913
|
+
console.error(`Error reading float inverter data (Model ${blockNumber}): ${error}`);
|
|
914
|
+
return null;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
847
917
|
/**
|
|
848
918
|
* Extract inverter scale factors from a pre-read model buffer
|
|
849
919
|
*/
|
|
@@ -911,6 +981,16 @@ class SunspecModbusClient {
|
|
|
911
981
|
console.debug(`[Model ${modelId}] offset ${offset}: ${fieldName} = "${rawValue}"${typeInfo}`);
|
|
912
982
|
}
|
|
913
983
|
}
|
|
984
|
+
/**
|
|
985
|
+
* Read a float32 (IEEE 754, big-endian, 2 registers) from a model buffer.
|
|
986
|
+
* SunSpec uses NaN as the "unimplemented" sentinel for floats; returns undefined in that case.
|
|
987
|
+
*/
|
|
988
|
+
extractFloat32OrUndefined(buffer, offset, modelId, fieldName) {
|
|
989
|
+
const value = this.extractValue(buffer, offset, 'float32', 2);
|
|
990
|
+
const display = Number.isNaN(value) ? 'NaN' : value;
|
|
991
|
+
console.debug(`[Model ${modelId}] offset ${offset}: ${fieldName} = ${display} (float32)`);
|
|
992
|
+
return Number.isNaN(value) ? undefined : value;
|
|
993
|
+
}
|
|
914
994
|
/**
|
|
915
995
|
* Read scale factors from Model 160 (MPPT)
|
|
916
996
|
*
|
|
@@ -201,13 +201,28 @@ export declare class SunspecModbusClient {
|
|
|
201
201
|
*/
|
|
202
202
|
writeRegisterValue(unitId: number, address: number, value: number | number[], dataType?: EnergyAppModbusDataType): Promise<boolean>;
|
|
203
203
|
/**
|
|
204
|
-
* Read inverter data
|
|
204
|
+
* Read inverter data. Detects which SunSpec inverter model the device exposes —
|
|
205
|
+
* int+SF (101/103) or float (111/112/113) — by checking the discovered model directory,
|
|
206
|
+
* and dispatches to the appropriate decoder.
|
|
205
207
|
*/
|
|
206
208
|
readInverterData(unitId: number): Promise<SunspecInverterData | null>;
|
|
209
|
+
/**
|
|
210
|
+
* Read three-phase inverter data from Model 103 (int + scale factor encoding).
|
|
211
|
+
*/
|
|
212
|
+
private readThreePhaseInverterData_IntSF;
|
|
207
213
|
/**
|
|
208
214
|
* Read single phase inverter data (Model 101)
|
|
209
215
|
*/
|
|
210
216
|
private readSinglePhaseInverterData;
|
|
217
|
+
/**
|
|
218
|
+
* Read inverter data from a float-variant model: 111 (single-phase), 112 (split-phase), or 113 (three-phase).
|
|
219
|
+
*
|
|
220
|
+
* All three models share the same SunSpec register layout — measurement fields are 32-bit IEEE 754 floats
|
|
221
|
+
* (no scale factors), status registers stay enum16, event registers stay bitfield32. Phase scope differs
|
|
222
|
+
* by model: 111 populates phase A only; 112 phases A+B; 113 all three. Unpopulated phase fields read NaN
|
|
223
|
+
* and are returned as undefined.
|
|
224
|
+
*/
|
|
225
|
+
private readFloatInverterData;
|
|
211
226
|
/**
|
|
212
227
|
* Extract inverter scale factors from a pre-read model buffer
|
|
213
228
|
*/
|
|
@@ -218,6 +233,11 @@ export declare class SunspecModbusClient {
|
|
|
218
233
|
*/
|
|
219
234
|
applyScaleFactor(value: number, scaleFactor: number, dataType?: 'uint16' | 'int16' | 'acc32', fieldName?: string, offset?: number, modelId?: number): number | undefined;
|
|
220
235
|
logRegisterRead(modelId: number, offset: number, fieldName: string, rawValue: number | string | undefined, dataType?: string): void;
|
|
236
|
+
/**
|
|
237
|
+
* Read a float32 (IEEE 754, big-endian, 2 registers) from a model buffer.
|
|
238
|
+
* SunSpec uses NaN as the "unimplemented" sentinel for floats; returns undefined in that case.
|
|
239
|
+
*/
|
|
240
|
+
private extractFloat32OrUndefined;
|
|
221
241
|
/**
|
|
222
242
|
* Read scale factors from Model 160 (MPPT)
|
|
223
243
|
*
|
package/dist/cjs/version.cjs
CHANGED
|
@@ -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.
|
|
12
|
+
exports.SDK_VERSION = '0.0.54';
|
|
13
13
|
/**
|
|
14
14
|
* Gets the current SDK version.
|
|
15
15
|
* @returns The semantic version string of the SDK
|
package/dist/cjs/version.d.cts
CHANGED
|
@@ -30,6 +30,9 @@ export declare enum SunspecModelId {
|
|
|
30
30
|
Common = 1,
|
|
31
31
|
Inverter3Phase = 103,
|
|
32
32
|
InverterSinglePhase = 101,
|
|
33
|
+
InverterSinglePhaseFloat = 111,
|
|
34
|
+
InverterSplitPhaseFloat = 112,
|
|
35
|
+
Inverter3PhaseFloat = 113,
|
|
33
36
|
MPPT = 160,
|
|
34
37
|
Battery = 124,
|
|
35
38
|
BatteryBase = 802,
|
|
@@ -74,10 +77,12 @@ export interface SunspecCommonBlock extends SunspecBlock {
|
|
|
74
77
|
deviceAddress?: number;
|
|
75
78
|
}
|
|
76
79
|
/**
|
|
77
|
-
* Inverter data structure
|
|
80
|
+
* Inverter data structure. Covers SunSpec int+SF inverter models 101/103 and float variants 111/112/113.
|
|
81
|
+
* The per-field offset comments below describe the int+SF (101/103) layout; float variants 111/112/113
|
|
82
|
+
* use 32-bit IEEE 754 float values at different offsets — see the float reader implementations.
|
|
78
83
|
*/
|
|
79
84
|
export interface SunspecInverterData extends SunspecBlock {
|
|
80
|
-
blockNumber: 103 | 101;
|
|
85
|
+
blockNumber: 103 | 101 | 113 | 112 | 111;
|
|
81
86
|
acCurrent?: number;
|
|
82
87
|
phaseACurrent?: number;
|
|
83
88
|
phaseBCurrent?: number;
|
|
@@ -17,6 +17,9 @@ export var SunspecModelId;
|
|
|
17
17
|
SunspecModelId[SunspecModelId["Common"] = 1] = "Common";
|
|
18
18
|
SunspecModelId[SunspecModelId["Inverter3Phase"] = 103] = "Inverter3Phase";
|
|
19
19
|
SunspecModelId[SunspecModelId["InverterSinglePhase"] = 101] = "InverterSinglePhase";
|
|
20
|
+
SunspecModelId[SunspecModelId["InverterSinglePhaseFloat"] = 111] = "InverterSinglePhaseFloat";
|
|
21
|
+
SunspecModelId[SunspecModelId["InverterSplitPhaseFloat"] = 112] = "InverterSplitPhaseFloat";
|
|
22
|
+
SunspecModelId[SunspecModelId["Inverter3PhaseFloat"] = 113] = "Inverter3PhaseFloat";
|
|
20
23
|
SunspecModelId[SunspecModelId["MPPT"] = 160] = "MPPT";
|
|
21
24
|
SunspecModelId[SunspecModelId["Battery"] = 124] = "Battery";
|
|
22
25
|
SunspecModelId[SunspecModelId["BatteryBase"] = 802] = "BatteryBase";
|
|
@@ -201,13 +201,28 @@ export declare class SunspecModbusClient {
|
|
|
201
201
|
*/
|
|
202
202
|
writeRegisterValue(unitId: number, address: number, value: number | number[], dataType?: EnergyAppModbusDataType): Promise<boolean>;
|
|
203
203
|
/**
|
|
204
|
-
* Read inverter data
|
|
204
|
+
* Read inverter data. Detects which SunSpec inverter model the device exposes —
|
|
205
|
+
* int+SF (101/103) or float (111/112/113) — by checking the discovered model directory,
|
|
206
|
+
* and dispatches to the appropriate decoder.
|
|
205
207
|
*/
|
|
206
208
|
readInverterData(unitId: number): Promise<SunspecInverterData | null>;
|
|
209
|
+
/**
|
|
210
|
+
* Read three-phase inverter data from Model 103 (int + scale factor encoding).
|
|
211
|
+
*/
|
|
212
|
+
private readThreePhaseInverterData_IntSF;
|
|
207
213
|
/**
|
|
208
214
|
* Read single phase inverter data (Model 101)
|
|
209
215
|
*/
|
|
210
216
|
private readSinglePhaseInverterData;
|
|
217
|
+
/**
|
|
218
|
+
* Read inverter data from a float-variant model: 111 (single-phase), 112 (split-phase), or 113 (three-phase).
|
|
219
|
+
*
|
|
220
|
+
* All three models share the same SunSpec register layout — measurement fields are 32-bit IEEE 754 floats
|
|
221
|
+
* (no scale factors), status registers stay enum16, event registers stay bitfield32. Phase scope differs
|
|
222
|
+
* by model: 111 populates phase A only; 112 phases A+B; 113 all three. Unpopulated phase fields read NaN
|
|
223
|
+
* and are returned as undefined.
|
|
224
|
+
*/
|
|
225
|
+
private readFloatInverterData;
|
|
211
226
|
/**
|
|
212
227
|
* Extract inverter scale factors from a pre-read model buffer
|
|
213
228
|
*/
|
|
@@ -218,6 +233,11 @@ export declare class SunspecModbusClient {
|
|
|
218
233
|
*/
|
|
219
234
|
applyScaleFactor(value: number, scaleFactor: number, dataType?: 'uint16' | 'int16' | 'acc32', fieldName?: string, offset?: number, modelId?: number): number | undefined;
|
|
220
235
|
logRegisterRead(modelId: number, offset: number, fieldName: string, rawValue: number | string | undefined, dataType?: string): void;
|
|
236
|
+
/**
|
|
237
|
+
* Read a float32 (IEEE 754, big-endian, 2 registers) from a model buffer.
|
|
238
|
+
* SunSpec uses NaN as the "unimplemented" sentinel for floats; returns undefined in that case.
|
|
239
|
+
*/
|
|
240
|
+
private extractFloat32OrUndefined;
|
|
221
241
|
/**
|
|
222
242
|
* Read scale factors from Model 160 (MPPT)
|
|
223
243
|
*
|
|
@@ -499,7 +499,7 @@ export class SunspecModbusClient {
|
|
|
499
499
|
catch (error) {
|
|
500
500
|
console.error(`Error during model discovery at address ${currentAddress} (unit ${unitId}): ${error}`);
|
|
501
501
|
}
|
|
502
|
-
console.log(`Discovery complete for unit ${unitId}. Found ${models.size} models`);
|
|
502
|
+
console.log(`Discovery complete for unit ${unitId}. Found ${models.size} models: [${[...models.keys()].sort((a, b) => a - b).join(', ')}]`);
|
|
503
503
|
return models;
|
|
504
504
|
}
|
|
505
505
|
/**
|
|
@@ -681,28 +681,36 @@ export class SunspecModbusClient {
|
|
|
681
681
|
}
|
|
682
682
|
}
|
|
683
683
|
/**
|
|
684
|
-
* Read inverter data
|
|
684
|
+
* Read inverter data. Detects which SunSpec inverter model the device exposes —
|
|
685
|
+
* int+SF (101/103) or float (111/112/113) — by checking the discovered model directory,
|
|
686
|
+
* and dispatches to the appropriate decoder.
|
|
685
687
|
*/
|
|
686
688
|
async readInverterData(unitId) {
|
|
687
|
-
const
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
689
|
+
const tryOrder = [
|
|
690
|
+
{ id: 103, reader: m => this.readThreePhaseInverterData_IntSF(unitId, m) },
|
|
691
|
+
{ id: 113, reader: m => this.readFloatInverterData(unitId, m, 113) },
|
|
692
|
+
{ id: 112, reader: m => this.readFloatInverterData(unitId, m, 112) },
|
|
693
|
+
{ id: 101, reader: m => this.readSinglePhaseInverterData(unitId, m) },
|
|
694
|
+
{ id: 111, reader: m => this.readFloatInverterData(unitId, m, 111) },
|
|
695
|
+
];
|
|
696
|
+
for (const { id, reader } of tryOrder) {
|
|
697
|
+
const model = this.findModel(unitId, id);
|
|
698
|
+
if (model) {
|
|
699
|
+
console.debug(`Using inverter Model ${id} at address ${model.address} (length ${model.length}) on unit ${unitId}`);
|
|
700
|
+
return reader(model);
|
|
694
701
|
}
|
|
695
|
-
console.warn('IMPORTANT: Working with single-phase inverter, but 3-phase is expected!');
|
|
696
|
-
return this.readSinglePhaseInverterData(unitId, singlePhaseModel);
|
|
697
702
|
}
|
|
698
|
-
console.debug(`
|
|
703
|
+
console.debug(`No inverter model (101/103/111/112/113) on unit ${unitId}`);
|
|
704
|
+
return null;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Read three-phase inverter data from Model 103 (int + scale factor encoding).
|
|
708
|
+
*/
|
|
709
|
+
async readThreePhaseInverterData_IntSF(unitId, model) {
|
|
699
710
|
try {
|
|
700
|
-
|
|
701
|
-
console.debug(`Reading Inverter Data from Model ${model.id} at base address: ${model.address} (unit ${unitId})`);
|
|
711
|
+
console.debug(`Reading Inverter Data from Model 103 at base address: ${model.address} (unit ${unitId})`);
|
|
702
712
|
const buffer = await this.readModelBlock(unitId, model);
|
|
703
|
-
// Extract all scale factors from buffer
|
|
704
713
|
const scaleFactors = this.extractInverterScaleFactors(buffer);
|
|
705
|
-
// Extract raw values from buffer
|
|
706
714
|
const acCurrentRaw = this.extractValue(buffer, 2, 'uint16');
|
|
707
715
|
const voltageANRaw = this.extractValue(buffer, 10, 'uint16');
|
|
708
716
|
const voltageBNRaw = this.extractValue(buffer, 11, 'uint16');
|
|
@@ -754,7 +762,6 @@ export class SunspecModbusClient {
|
|
|
754
762
|
vendorEvents3: this.extractValue(buffer, 48, 'uint32', 2),
|
|
755
763
|
vendorEvents4: this.extractValue(buffer, 50, 'uint32', 2)
|
|
756
764
|
};
|
|
757
|
-
// Log non-scaled fields
|
|
758
765
|
this.logRegisterRead(103, 38, 'Operating State', data.operatingState, 'enum16');
|
|
759
766
|
this.logRegisterRead(103, 39, 'Vendor State', data.vendorState, 'uint16');
|
|
760
767
|
this.logRegisterRead(103, 40, 'Events', data.events, 'bitfield32');
|
|
@@ -763,7 +770,6 @@ export class SunspecModbusClient {
|
|
|
763
770
|
this.logRegisterRead(103, 46, 'Vendor Events 2', data.vendorEvents2, 'bitfield32');
|
|
764
771
|
this.logRegisterRead(103, 48, 'Vendor Events 3', data.vendorEvents3, 'bitfield32');
|
|
765
772
|
this.logRegisterRead(103, 50, 'Vendor Events 4', data.vendorEvents4, 'bitfield32');
|
|
766
|
-
// Read AC Energy (32-bit accumulator) - Offset 24-25
|
|
767
773
|
const acEnergy = this.extractValue(buffer, 24, 'uint32', 2);
|
|
768
774
|
this.logRegisterRead(103, 24, 'AC Energy', acEnergy, 'acc32');
|
|
769
775
|
data.acEnergy = !this.isUnimplementedValue(acEnergy, 'acc32')
|
|
@@ -785,44 +791,44 @@ export class SunspecModbusClient {
|
|
|
785
791
|
console.debug(`Reading Single-Phase Inverter Data from Model 101 at base address: ${model.address} (unit ${unitId})`);
|
|
786
792
|
// Read entire model block in a single Modbus call
|
|
787
793
|
const buffer = await this.readModelBlock(unitId, model);
|
|
788
|
-
// Extract scale factors from buffer
|
|
794
|
+
// Extract scale factors from buffer (offsets aligned with Model 103 reader, which works on real
|
|
795
|
+
// hardware; differs from the published SunSpec Model 101 spec for DC fields).
|
|
789
796
|
const scaleFactors = {
|
|
790
797
|
A_SF: this.extractValue(buffer, 6, 'int16'),
|
|
791
798
|
V_SF: this.extractValue(buffer, 13, 'int16'),
|
|
792
|
-
W_SF: this.extractValue(buffer,
|
|
793
|
-
Hz_SF: this.extractValue(buffer,
|
|
799
|
+
W_SF: this.extractValue(buffer, 15, 'int16'),
|
|
800
|
+
Hz_SF: this.extractValue(buffer, 17, 'int16'),
|
|
794
801
|
WH_SF: this.extractValue(buffer, 26, 'int16'),
|
|
795
|
-
DCA_SF: this.extractValue(buffer,
|
|
796
|
-
DCV_SF: this.extractValue(buffer,
|
|
797
|
-
DCW_SF: this.extractValue(buffer,
|
|
802
|
+
DCA_SF: this.extractValue(buffer, 28, 'int16'),
|
|
803
|
+
DCV_SF: this.extractValue(buffer, 29, 'int16'),
|
|
804
|
+
DCW_SF: this.extractValue(buffer, 31, 'int16')
|
|
798
805
|
};
|
|
799
806
|
this.logRegisterRead(101, 6, 'A_SF', scaleFactors.A_SF, 'int16');
|
|
800
807
|
this.logRegisterRead(101, 13, 'V_SF', scaleFactors.V_SF, 'int16');
|
|
801
|
-
this.logRegisterRead(101,
|
|
802
|
-
this.logRegisterRead(101,
|
|
808
|
+
this.logRegisterRead(101, 15, 'W_SF', scaleFactors.W_SF, 'int16');
|
|
809
|
+
this.logRegisterRead(101, 17, 'Hz_SF', scaleFactors.Hz_SF, 'int16');
|
|
803
810
|
this.logRegisterRead(101, 26, 'WH_SF', scaleFactors.WH_SF, 'int16');
|
|
804
|
-
this.logRegisterRead(101,
|
|
805
|
-
this.logRegisterRead(101,
|
|
806
|
-
this.logRegisterRead(101,
|
|
807
|
-
// Extract raw values from buffer
|
|
811
|
+
this.logRegisterRead(101, 28, 'DCA_SF', scaleFactors.DCA_SF, 'int16');
|
|
812
|
+
this.logRegisterRead(101, 29, 'DCV_SF', scaleFactors.DCV_SF, 'int16');
|
|
813
|
+
this.logRegisterRead(101, 31, 'DCW_SF', scaleFactors.DCW_SF, 'int16');
|
|
808
814
|
const acCurrentRaw = this.extractValue(buffer, 2, 'uint16');
|
|
809
|
-
const voltageRaw = this.extractValue(buffer,
|
|
810
|
-
const acPowerRaw = this.extractValue(buffer,
|
|
811
|
-
const freqRaw = this.extractValue(buffer,
|
|
812
|
-
const dcCurrentRaw = this.extractValue(buffer,
|
|
813
|
-
const dcVoltageRaw = this.extractValue(buffer,
|
|
814
|
-
const dcPowerRaw = this.extractValue(buffer,
|
|
815
|
-
const stateRaw = this.extractValue(buffer,
|
|
816
|
-
this.logRegisterRead(101,
|
|
815
|
+
const voltageRaw = this.extractValue(buffer, 10, 'uint16');
|
|
816
|
+
const acPowerRaw = this.extractValue(buffer, 14, 'int16');
|
|
817
|
+
const freqRaw = this.extractValue(buffer, 16, 'uint16');
|
|
818
|
+
const dcCurrentRaw = this.extractValue(buffer, 27, 'uint16');
|
|
819
|
+
const dcVoltageRaw = this.extractValue(buffer, 28, 'uint16');
|
|
820
|
+
const dcPowerRaw = this.extractValue(buffer, 30, 'int16');
|
|
821
|
+
const stateRaw = this.extractValue(buffer, 38, 'uint16');
|
|
822
|
+
this.logRegisterRead(101, 38, 'Operating State', stateRaw, 'enum16');
|
|
817
823
|
const data = {
|
|
818
824
|
blockNumber: 101,
|
|
819
|
-
voltageAN: this.applyScaleFactor(voltageRaw, scaleFactors.V_SF, 'uint16', 'Voltage AN',
|
|
825
|
+
voltageAN: this.applyScaleFactor(voltageRaw, scaleFactors.V_SF, 'uint16', 'Voltage AN', 10, 101),
|
|
820
826
|
acCurrent: this.applyScaleFactor(acCurrentRaw, scaleFactors.A_SF, 'uint16', 'AC Current', 2, 101),
|
|
821
|
-
acPower: this.applyScaleFactor(acPowerRaw, scaleFactors.W_SF, 'int16', 'AC Power',
|
|
822
|
-
frequency: this.applyScaleFactor(freqRaw, scaleFactors.Hz_SF, 'uint16', 'Frequency',
|
|
823
|
-
dcCurrent: this.applyScaleFactor(dcCurrentRaw, scaleFactors.DCA_SF, 'uint16', 'DC Current',
|
|
824
|
-
dcVoltage: this.applyScaleFactor(dcVoltageRaw, scaleFactors.DCV_SF, 'uint16', 'DC Voltage',
|
|
825
|
-
dcPower: this.applyScaleFactor(dcPowerRaw, scaleFactors.DCW_SF, 'int16', 'DC Power',
|
|
827
|
+
acPower: this.applyScaleFactor(acPowerRaw, scaleFactors.W_SF, 'int16', 'AC Power', 14, 101),
|
|
828
|
+
frequency: this.applyScaleFactor(freqRaw, scaleFactors.Hz_SF, 'uint16', 'Frequency', 16, 101),
|
|
829
|
+
dcCurrent: this.applyScaleFactor(dcCurrentRaw, scaleFactors.DCA_SF, 'uint16', 'DC Current', 27, 101),
|
|
830
|
+
dcVoltage: this.applyScaleFactor(dcVoltageRaw, scaleFactors.DCV_SF, 'uint16', 'DC Voltage', 28, 101),
|
|
831
|
+
dcPower: this.applyScaleFactor(dcPowerRaw, scaleFactors.DCW_SF, 'int16', 'DC Power', 30, 101),
|
|
826
832
|
operatingState: stateRaw
|
|
827
833
|
};
|
|
828
834
|
// Read AC Energy (32-bit accumulator) - Offset 24-25
|
|
@@ -839,6 +845,70 @@ export class SunspecModbusClient {
|
|
|
839
845
|
return null;
|
|
840
846
|
}
|
|
841
847
|
}
|
|
848
|
+
/**
|
|
849
|
+
* Read inverter data from a float-variant model: 111 (single-phase), 112 (split-phase), or 113 (three-phase).
|
|
850
|
+
*
|
|
851
|
+
* All three models share the same SunSpec register layout — measurement fields are 32-bit IEEE 754 floats
|
|
852
|
+
* (no scale factors), status registers stay enum16, event registers stay bitfield32. Phase scope differs
|
|
853
|
+
* by model: 111 populates phase A only; 112 phases A+B; 113 all three. Unpopulated phase fields read NaN
|
|
854
|
+
* and are returned as undefined.
|
|
855
|
+
*/
|
|
856
|
+
async readFloatInverterData(unitId, model, blockNumber) {
|
|
857
|
+
try {
|
|
858
|
+
console.debug(`Reading Float Inverter Data from Model ${blockNumber} at base address: ${model.address} (unit ${unitId})`);
|
|
859
|
+
const buffer = await this.readModelBlock(unitId, model);
|
|
860
|
+
const data = {
|
|
861
|
+
blockNumber,
|
|
862
|
+
blockAddress: model.address,
|
|
863
|
+
blockLength: model.length,
|
|
864
|
+
acCurrent: this.extractFloat32OrUndefined(buffer, 2, blockNumber, 'AC Current'),
|
|
865
|
+
phaseACurrent: this.extractFloat32OrUndefined(buffer, 4, blockNumber, 'Phase A Current'),
|
|
866
|
+
phaseBCurrent: this.extractFloat32OrUndefined(buffer, 6, blockNumber, 'Phase B Current'),
|
|
867
|
+
phaseCCurrent: this.extractFloat32OrUndefined(buffer, 8, blockNumber, 'Phase C Current'),
|
|
868
|
+
voltageAB: this.extractFloat32OrUndefined(buffer, 10, blockNumber, 'Voltage AB'),
|
|
869
|
+
voltageBC: this.extractFloat32OrUndefined(buffer, 12, blockNumber, 'Voltage BC'),
|
|
870
|
+
voltageCA: this.extractFloat32OrUndefined(buffer, 14, blockNumber, 'Voltage CA'),
|
|
871
|
+
voltageAN: this.extractFloat32OrUndefined(buffer, 16, blockNumber, 'Voltage AN'),
|
|
872
|
+
voltageBN: this.extractFloat32OrUndefined(buffer, 18, blockNumber, 'Voltage BN'),
|
|
873
|
+
voltageCN: this.extractFloat32OrUndefined(buffer, 20, blockNumber, 'Voltage CN'),
|
|
874
|
+
acPower: this.extractFloat32OrUndefined(buffer, 22, blockNumber, 'AC Power'),
|
|
875
|
+
frequency: this.extractFloat32OrUndefined(buffer, 24, blockNumber, 'Frequency'),
|
|
876
|
+
apparentPower: this.extractFloat32OrUndefined(buffer, 26, blockNumber, 'Apparent Power'),
|
|
877
|
+
reactivePower: this.extractFloat32OrUndefined(buffer, 28, blockNumber, 'Reactive Power'),
|
|
878
|
+
powerFactor: this.extractFloat32OrUndefined(buffer, 30, blockNumber, 'Power Factor'),
|
|
879
|
+
acEnergy: this.extractFloat32OrUndefined(buffer, 32, blockNumber, 'AC Energy'),
|
|
880
|
+
dcCurrent: this.extractFloat32OrUndefined(buffer, 34, blockNumber, 'DC Current'),
|
|
881
|
+
dcVoltage: this.extractFloat32OrUndefined(buffer, 36, blockNumber, 'DC Voltage'),
|
|
882
|
+
dcPower: this.extractFloat32OrUndefined(buffer, 38, blockNumber, 'DC Power'),
|
|
883
|
+
cabinetTemperature: this.extractFloat32OrUndefined(buffer, 40, blockNumber, 'Cabinet Temperature'),
|
|
884
|
+
heatSinkTemperature: this.extractFloat32OrUndefined(buffer, 42, blockNumber, 'Heat Sink Temperature'),
|
|
885
|
+
transformerTemperature: this.extractFloat32OrUndefined(buffer, 44, blockNumber, 'Transformer Temperature'),
|
|
886
|
+
otherTemperature: this.extractFloat32OrUndefined(buffer, 46, blockNumber, 'Other Temperature'),
|
|
887
|
+
operatingState: this.extractValue(buffer, 48, 'uint16'),
|
|
888
|
+
vendorState: this.extractValue(buffer, 49, 'uint16'),
|
|
889
|
+
events: this.extractValue(buffer, 50, 'uint32', 2),
|
|
890
|
+
events2: this.extractValue(buffer, 52, 'uint32', 2),
|
|
891
|
+
vendorEvents1: this.extractValue(buffer, 54, 'uint32', 2),
|
|
892
|
+
vendorEvents2: this.extractValue(buffer, 56, 'uint32', 2),
|
|
893
|
+
vendorEvents3: this.extractValue(buffer, 58, 'uint32', 2),
|
|
894
|
+
vendorEvents4: this.extractValue(buffer, 60, 'uint32', 2),
|
|
895
|
+
};
|
|
896
|
+
this.logRegisterRead(blockNumber, 48, 'Operating State', data.operatingState, 'enum16');
|
|
897
|
+
this.logRegisterRead(blockNumber, 49, 'Vendor State', data.vendorState, 'enum16');
|
|
898
|
+
this.logRegisterRead(blockNumber, 50, 'Events', data.events, 'bitfield32');
|
|
899
|
+
this.logRegisterRead(blockNumber, 52, 'Events2', data.events2, 'bitfield32');
|
|
900
|
+
this.logRegisterRead(blockNumber, 54, 'Vendor Events 1', data.vendorEvents1, 'bitfield32');
|
|
901
|
+
this.logRegisterRead(blockNumber, 56, 'Vendor Events 2', data.vendorEvents2, 'bitfield32');
|
|
902
|
+
this.logRegisterRead(blockNumber, 58, 'Vendor Events 3', data.vendorEvents3, 'bitfield32');
|
|
903
|
+
this.logRegisterRead(blockNumber, 60, 'Vendor Events 4', data.vendorEvents4, 'bitfield32');
|
|
904
|
+
console.debug(`[Model ${blockNumber}] Float Inverter Data:`, data);
|
|
905
|
+
return data;
|
|
906
|
+
}
|
|
907
|
+
catch (error) {
|
|
908
|
+
console.error(`Error reading float inverter data (Model ${blockNumber}): ${error}`);
|
|
909
|
+
return null;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
842
912
|
/**
|
|
843
913
|
* Extract inverter scale factors from a pre-read model buffer
|
|
844
914
|
*/
|
|
@@ -906,6 +976,16 @@ export class SunspecModbusClient {
|
|
|
906
976
|
console.debug(`[Model ${modelId}] offset ${offset}: ${fieldName} = "${rawValue}"${typeInfo}`);
|
|
907
977
|
}
|
|
908
978
|
}
|
|
979
|
+
/**
|
|
980
|
+
* Read a float32 (IEEE 754, big-endian, 2 registers) from a model buffer.
|
|
981
|
+
* SunSpec uses NaN as the "unimplemented" sentinel for floats; returns undefined in that case.
|
|
982
|
+
*/
|
|
983
|
+
extractFloat32OrUndefined(buffer, offset, modelId, fieldName) {
|
|
984
|
+
const value = this.extractValue(buffer, offset, 'float32', 2);
|
|
985
|
+
const display = Number.isNaN(value) ? 'NaN' : value;
|
|
986
|
+
console.debug(`[Model ${modelId}] offset ${offset}: ${fieldName} = ${display} (float32)`);
|
|
987
|
+
return Number.isNaN(value) ? undefined : value;
|
|
988
|
+
}
|
|
909
989
|
/**
|
|
910
990
|
* Read scale factors from Model 160 (MPPT)
|
|
911
991
|
*
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED