@enyo-energy/sunspec-sdk 0.0.39 → 0.0.41
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/README.md +626 -1
- package/dist/cjs/sunspec-modbus-client.cjs +418 -376
- package/dist/cjs/sunspec-modbus-client.d.cts +25 -4
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/sunspec-modbus-client.d.ts +25 -4
- package/dist/sunspec-modbus-client.js +418 -376
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
|
@@ -366,6 +366,42 @@ class SunspecModbusClient {
|
|
|
366
366
|
cleanString(value) {
|
|
367
367
|
return value.replace(/\u0000/g, '').trim();
|
|
368
368
|
}
|
|
369
|
+
/**
|
|
370
|
+
* Read an entire model's register block in a single Modbus call.
|
|
371
|
+
* Returns a Buffer containing all registers for the model.
|
|
372
|
+
*/
|
|
373
|
+
async readModelBlock(model) {
|
|
374
|
+
if (!this.faultTolerantReader) {
|
|
375
|
+
throw new Error('Fault-tolerant reader not initialized');
|
|
376
|
+
}
|
|
377
|
+
// Read model.length + 2 registers: the 2-register header (ID + length) plus all data registers.
|
|
378
|
+
// This way buffer offsets match the convention used throughout: offset 0 = model ID,
|
|
379
|
+
// offset 1 = model length, offset 2 = first data register, etc.
|
|
380
|
+
const totalRegisters = model.length + 2;
|
|
381
|
+
const result = await this.faultTolerantReader.readHoldingRegisters(model.address, totalRegisters);
|
|
382
|
+
if (!result.success || !result.value) {
|
|
383
|
+
throw new Error(`Failed to read model block ${model.id} at address ${model.address}: ${result.error?.message || 'Unknown error'}`);
|
|
384
|
+
}
|
|
385
|
+
this.connectionHealth.recordSuccess();
|
|
386
|
+
return result.value;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Extract a typed value from a model block buffer at a given register offset.
|
|
390
|
+
* @param buffer - The buffer returned by readModelBlock
|
|
391
|
+
* @param offset - Register offset within the model (0-based)
|
|
392
|
+
* @param dataType - The data type to convert to
|
|
393
|
+
* @param quantity - Number of registers to read (default: auto from dataType for strings, 1 otherwise)
|
|
394
|
+
*/
|
|
395
|
+
extractValue(buffer, offset, dataType, quantity = 1) {
|
|
396
|
+
const byteOffset = offset * 2;
|
|
397
|
+
const byteLength = quantity * 2;
|
|
398
|
+
const sliced = buffer.subarray(byteOffset, byteOffset + byteLength);
|
|
399
|
+
const value = this.modbusDataTypeConverter.convertFromBuffer(sliced, dataType, undefined, quantity);
|
|
400
|
+
if (dataType === 'string') {
|
|
401
|
+
return this.cleanString(value);
|
|
402
|
+
}
|
|
403
|
+
return value;
|
|
404
|
+
}
|
|
369
405
|
/**
|
|
370
406
|
* Helper to read register value(s) using the fault-tolerant reader with data type conversion
|
|
371
407
|
*/
|
|
@@ -454,64 +490,63 @@ class SunspecModbusClient {
|
|
|
454
490
|
return this.readSinglePhaseInverterData(singlePhaseModel);
|
|
455
491
|
}
|
|
456
492
|
console.log(`Found 3-phase inverter model 103 at address ${model.address} with length ${model.length}`);
|
|
457
|
-
const baseAddr = model.address;
|
|
458
493
|
try {
|
|
459
|
-
// Read
|
|
460
|
-
console.log(`Reading Inverter Data from Model ${model.id} at base address: ${
|
|
461
|
-
const
|
|
462
|
-
//
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
const
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
const
|
|
469
|
-
const acPowerRaw =
|
|
470
|
-
const dcCurrentRaw =
|
|
471
|
-
const dcVoltageRaw =
|
|
472
|
-
const dcPowerRaw =
|
|
494
|
+
// Read entire model block in a single Modbus call
|
|
495
|
+
console.log(`Reading Inverter Data from Model ${model.id} at base address: ${model.address}`);
|
|
496
|
+
const buffer = await this.readModelBlock(model);
|
|
497
|
+
// Extract all scale factors from buffer
|
|
498
|
+
const scaleFactors = this.extractInverterScaleFactors(buffer);
|
|
499
|
+
// Extract raw values from buffer
|
|
500
|
+
const acCurrentRaw = this.extractValue(buffer, 2, 'uint16');
|
|
501
|
+
const voltageANRaw = this.extractValue(buffer, 10, 'uint16');
|
|
502
|
+
const voltageBNRaw = this.extractValue(buffer, 11, 'uint16');
|
|
503
|
+
const voltageCNRaw = this.extractValue(buffer, 12, 'uint16');
|
|
504
|
+
const acPowerRaw = this.extractValue(buffer, 14, 'int16');
|
|
505
|
+
const dcCurrentRaw = this.extractValue(buffer, 27, 'uint16');
|
|
506
|
+
const dcVoltageRaw = this.extractValue(buffer, 28, 'uint16');
|
|
507
|
+
const dcPowerRaw = this.extractValue(buffer, 30, 'int16');
|
|
473
508
|
const data = {
|
|
474
509
|
blockNumber: 103,
|
|
475
510
|
blockAddress: model.address,
|
|
476
511
|
blockLength: model.length,
|
|
477
512
|
// AC Current values - Offsets 2-5
|
|
478
513
|
acCurrent: this.applyScaleFactor(acCurrentRaw, scaleFactors.A_SF, 'uint16', 'AC Current', 2, 103),
|
|
479
|
-
phaseACurrent: this.applyScaleFactor(
|
|
480
|
-
phaseBCurrent: this.applyScaleFactor(
|
|
481
|
-
phaseCCurrent: this.applyScaleFactor(
|
|
514
|
+
phaseACurrent: this.applyScaleFactor(this.extractValue(buffer, 3, 'uint16'), scaleFactors.A_SF, 'uint16', 'Phase A Current', 3, 103),
|
|
515
|
+
phaseBCurrent: this.applyScaleFactor(this.extractValue(buffer, 4, 'uint16'), scaleFactors.A_SF, 'uint16', 'Phase B Current', 4, 103),
|
|
516
|
+
phaseCCurrent: this.applyScaleFactor(this.extractValue(buffer, 5, 'uint16'), scaleFactors.A_SF, 'uint16', 'Phase C Current', 5, 103),
|
|
482
517
|
// Voltage values - Offsets 7-12
|
|
483
|
-
voltageAB: this.applyScaleFactor(
|
|
484
|
-
voltageBC: this.applyScaleFactor(
|
|
485
|
-
voltageCA: this.applyScaleFactor(
|
|
518
|
+
voltageAB: this.applyScaleFactor(this.extractValue(buffer, 7, 'uint16'), scaleFactors.V_SF, 'uint16', 'Voltage AB', 7, 103),
|
|
519
|
+
voltageBC: this.applyScaleFactor(this.extractValue(buffer, 8, 'uint16'), scaleFactors.V_SF, 'uint16', 'Voltage BC', 8, 103),
|
|
520
|
+
voltageCA: this.applyScaleFactor(this.extractValue(buffer, 9, 'uint16'), scaleFactors.V_SF, 'uint16', 'Voltage CA', 9, 103),
|
|
486
521
|
voltageAN: this.applyScaleFactor(voltageANRaw, scaleFactors.V_SF, 'uint16', 'Voltage AN', 10, 103),
|
|
487
522
|
voltageBN: this.applyScaleFactor(voltageBNRaw, scaleFactors.V_SF, 'uint16', 'Voltage BN', 11, 103),
|
|
488
523
|
voltageCN: this.applyScaleFactor(voltageCNRaw, scaleFactors.V_SF, 'uint16', 'Voltage CN', 12, 103),
|
|
489
524
|
// Power values - Offsets 14, 18, 20, 22
|
|
490
525
|
acPower: this.applyScaleFactor(acPowerRaw, scaleFactors.W_SF, 'int16', 'AC Power', 14, 103),
|
|
491
|
-
apparentPower: this.applyScaleFactor(
|
|
492
|
-
reactivePower: this.applyScaleFactor(
|
|
493
|
-
powerFactor: this.applyScaleFactor(
|
|
526
|
+
apparentPower: this.applyScaleFactor(this.extractValue(buffer, 18, 'uint16'), scaleFactors.VA_SF, 'uint16', 'Apparent Power', 18, 103),
|
|
527
|
+
reactivePower: this.applyScaleFactor(this.extractValue(buffer, 20, 'int16'), scaleFactors.VAr_SF, 'int16', 'Reactive Power', 20, 103),
|
|
528
|
+
powerFactor: this.applyScaleFactor(this.extractValue(buffer, 22, 'int16'), scaleFactors.PF_SF, 'int16', 'Power Factor', 22, 103),
|
|
494
529
|
// Frequency - Offset 16
|
|
495
|
-
frequency: this.applyScaleFactor(
|
|
530
|
+
frequency: this.applyScaleFactor(this.extractValue(buffer, 16, 'uint16'), scaleFactors.Hz_SF, 'uint16', 'Frequency', 16, 103),
|
|
496
531
|
// DC values - Offsets 27, 28, 30
|
|
497
532
|
dcCurrent: this.applyScaleFactor(dcCurrentRaw, scaleFactors.DCA_SF, 'uint16', 'DC Current', 27, 103),
|
|
498
533
|
dcVoltage: this.applyScaleFactor(dcVoltageRaw, scaleFactors.DCV_SF, 'uint16', 'DC Voltage', 28, 103),
|
|
499
534
|
dcPower: this.applyScaleFactor(dcPowerRaw, scaleFactors.DCW_SF, 'int16', 'DC Power', 30, 103),
|
|
500
535
|
// Temperature values - Offsets 32, 34, 35, 36
|
|
501
|
-
cabinetTemperature: this.applyScaleFactor(
|
|
502
|
-
heatSinkTemperature: this.applyScaleFactor(
|
|
503
|
-
transformerTemperature: this.applyScaleFactor(
|
|
504
|
-
otherTemperature: this.applyScaleFactor(
|
|
536
|
+
cabinetTemperature: this.applyScaleFactor(this.extractValue(buffer, 32, 'int16'), scaleFactors.Tmp_SF, 'int16', 'Cabinet Temperature', 32, 103),
|
|
537
|
+
heatSinkTemperature: this.applyScaleFactor(this.extractValue(buffer, 34, 'int16'), scaleFactors.Tmp_SF, 'int16', 'Heat Sink Temperature', 34, 103),
|
|
538
|
+
transformerTemperature: this.applyScaleFactor(this.extractValue(buffer, 35, 'int16'), scaleFactors.Tmp_SF, 'int16', 'Transformer Temperature', 35, 103),
|
|
539
|
+
otherTemperature: this.applyScaleFactor(this.extractValue(buffer, 36, 'int16'), scaleFactors.Tmp_SF, 'int16', 'Other Temperature', 36, 103),
|
|
505
540
|
// Status values - Offsets 38, 39
|
|
506
|
-
operatingState:
|
|
507
|
-
vendorState:
|
|
541
|
+
operatingState: this.extractValue(buffer, 38, 'uint16'),
|
|
542
|
+
vendorState: this.extractValue(buffer, 39, 'uint16'),
|
|
508
543
|
// Event bitfields - Offsets 40-51
|
|
509
|
-
events:
|
|
510
|
-
events2:
|
|
511
|
-
vendorEvents1:
|
|
512
|
-
vendorEvents2:
|
|
513
|
-
vendorEvents3:
|
|
514
|
-
vendorEvents4:
|
|
544
|
+
events: this.extractValue(buffer, 40, 'uint32', 2),
|
|
545
|
+
events2: this.extractValue(buffer, 42, 'uint32', 2),
|
|
546
|
+
vendorEvents1: this.extractValue(buffer, 44, 'uint32', 2),
|
|
547
|
+
vendorEvents2: this.extractValue(buffer, 46, 'uint32', 2),
|
|
548
|
+
vendorEvents3: this.extractValue(buffer, 48, 'uint32', 2),
|
|
549
|
+
vendorEvents4: this.extractValue(buffer, 50, 'uint32', 2)
|
|
515
550
|
};
|
|
516
551
|
// Log non-scaled fields
|
|
517
552
|
this.logRegisterRead(103, 38, 'Operating State', data.operatingState, 'enum16');
|
|
@@ -523,11 +558,12 @@ class SunspecModbusClient {
|
|
|
523
558
|
this.logRegisterRead(103, 48, 'Vendor Events 3', data.vendorEvents3, 'bitfield32');
|
|
524
559
|
this.logRegisterRead(103, 50, 'Vendor Events 4', data.vendorEvents4, 'bitfield32');
|
|
525
560
|
// Read AC Energy (32-bit accumulator) - Offset 24-25
|
|
526
|
-
const acEnergy =
|
|
561
|
+
const acEnergy = this.extractValue(buffer, 24, 'uint32', 2);
|
|
527
562
|
this.logRegisterRead(103, 24, 'AC Energy', acEnergy, 'acc32');
|
|
528
563
|
data.acEnergy = !this.isUnimplementedValue(acEnergy, 'acc32')
|
|
529
564
|
? acEnergy * Math.pow(10, scaleFactors.WH_SF)
|
|
530
565
|
: undefined;
|
|
566
|
+
console.debug('[Model 103] Inverter Data:', data);
|
|
531
567
|
return data;
|
|
532
568
|
}
|
|
533
569
|
catch (error) {
|
|
@@ -539,20 +575,19 @@ class SunspecModbusClient {
|
|
|
539
575
|
* Read single phase inverter data (Model 101)
|
|
540
576
|
*/
|
|
541
577
|
async readSinglePhaseInverterData(model) {
|
|
542
|
-
// Similar to 3-phase but with fewer phase-specific values
|
|
543
|
-
// Implementation would be similar but simplified
|
|
544
|
-
const baseAddr = model.address;
|
|
545
578
|
try {
|
|
546
|
-
console.log(`Reading Single-Phase Inverter Data from Model 101 at base address: ${
|
|
547
|
-
// Read
|
|
579
|
+
console.log(`Reading Single-Phase Inverter Data from Model 101 at base address: ${model.address}`);
|
|
580
|
+
// Read entire model block in a single Modbus call
|
|
581
|
+
const buffer = await this.readModelBlock(model);
|
|
582
|
+
// Extract scale factors from buffer
|
|
548
583
|
const scaleFactors = {
|
|
549
|
-
A_SF:
|
|
550
|
-
V_SF:
|
|
551
|
-
W_SF:
|
|
552
|
-
Hz_SF:
|
|
553
|
-
DCA_SF:
|
|
554
|
-
DCV_SF:
|
|
555
|
-
DCW_SF:
|
|
584
|
+
A_SF: this.extractValue(buffer, 6, 'int16'),
|
|
585
|
+
V_SF: this.extractValue(buffer, 13, 'int16'),
|
|
586
|
+
W_SF: this.extractValue(buffer, 10, 'int16'),
|
|
587
|
+
Hz_SF: this.extractValue(buffer, 12, 'int16'),
|
|
588
|
+
DCA_SF: this.extractValue(buffer, 18, 'int16'),
|
|
589
|
+
DCV_SF: this.extractValue(buffer, 19, 'int16'),
|
|
590
|
+
DCW_SF: this.extractValue(buffer, 21, 'int16')
|
|
556
591
|
};
|
|
557
592
|
this.logRegisterRead(101, 6, 'A_SF', scaleFactors.A_SF, 'int16');
|
|
558
593
|
this.logRegisterRead(101, 13, 'V_SF', scaleFactors.V_SF, 'int16');
|
|
@@ -561,17 +596,17 @@ class SunspecModbusClient {
|
|
|
561
596
|
this.logRegisterRead(101, 18, 'DCA_SF', scaleFactors.DCA_SF, 'int16');
|
|
562
597
|
this.logRegisterRead(101, 19, 'DCV_SF', scaleFactors.DCV_SF, 'int16');
|
|
563
598
|
this.logRegisterRead(101, 21, 'DCW_SF', scaleFactors.DCW_SF, 'int16');
|
|
564
|
-
//
|
|
565
|
-
const acCurrentRaw =
|
|
566
|
-
const voltageRaw =
|
|
567
|
-
const acPowerRaw =
|
|
568
|
-
const freqRaw =
|
|
569
|
-
const dcCurrentRaw =
|
|
570
|
-
const dcVoltageRaw =
|
|
571
|
-
const dcPowerRaw =
|
|
572
|
-
const stateRaw =
|
|
599
|
+
// Extract raw values from buffer
|
|
600
|
+
const acCurrentRaw = this.extractValue(buffer, 2, 'uint16');
|
|
601
|
+
const voltageRaw = this.extractValue(buffer, 7, 'uint16');
|
|
602
|
+
const acPowerRaw = this.extractValue(buffer, 9, 'int16');
|
|
603
|
+
const freqRaw = this.extractValue(buffer, 11, 'uint16');
|
|
604
|
+
const dcCurrentRaw = this.extractValue(buffer, 14, 'uint16');
|
|
605
|
+
const dcVoltageRaw = this.extractValue(buffer, 15, 'uint16');
|
|
606
|
+
const dcPowerRaw = this.extractValue(buffer, 20, 'int16');
|
|
607
|
+
const stateRaw = this.extractValue(buffer, 24, 'uint16');
|
|
573
608
|
this.logRegisterRead(101, 24, 'Operating State', stateRaw, 'enum16');
|
|
574
|
-
|
|
609
|
+
const data = {
|
|
575
610
|
blockNumber: 101,
|
|
576
611
|
voltageAN: this.applyScaleFactor(voltageRaw, scaleFactors.V_SF, 'uint16', 'Voltage AN', 7, 101),
|
|
577
612
|
acCurrent: this.applyScaleFactor(acCurrentRaw, scaleFactors.A_SF, 'uint16', 'AC Current', 2, 101),
|
|
@@ -582,6 +617,8 @@ class SunspecModbusClient {
|
|
|
582
617
|
dcPower: this.applyScaleFactor(dcPowerRaw, scaleFactors.DCW_SF, 'int16', 'DC Power', 20, 101),
|
|
583
618
|
operatingState: stateRaw
|
|
584
619
|
};
|
|
620
|
+
console.debug('[Model 101] Single Phase Inverter Data:', data);
|
|
621
|
+
return data;
|
|
585
622
|
}
|
|
586
623
|
catch (error) {
|
|
587
624
|
console.error(`Error reading single phase inverter data: ${error}`);
|
|
@@ -589,22 +626,22 @@ class SunspecModbusClient {
|
|
|
589
626
|
}
|
|
590
627
|
}
|
|
591
628
|
/**
|
|
592
|
-
*
|
|
629
|
+
* Extract inverter scale factors from a pre-read model buffer
|
|
593
630
|
*/
|
|
594
|
-
|
|
631
|
+
extractInverterScaleFactors(buffer) {
|
|
595
632
|
const scaleFactors = {
|
|
596
|
-
A_SF:
|
|
597
|
-
V_SF:
|
|
598
|
-
W_SF:
|
|
599
|
-
Hz_SF:
|
|
600
|
-
VA_SF:
|
|
601
|
-
VAr_SF:
|
|
602
|
-
PF_SF:
|
|
603
|
-
WH_SF:
|
|
604
|
-
DCA_SF:
|
|
605
|
-
DCV_SF:
|
|
606
|
-
DCW_SF:
|
|
607
|
-
Tmp_SF:
|
|
633
|
+
A_SF: this.extractValue(buffer, 6, 'int16'), // Offset 6
|
|
634
|
+
V_SF: this.extractValue(buffer, 13, 'int16'), // Offset 13
|
|
635
|
+
W_SF: this.extractValue(buffer, 15, 'int16'), // Offset 15
|
|
636
|
+
Hz_SF: this.extractValue(buffer, 17, 'int16'), // Offset 17
|
|
637
|
+
VA_SF: this.extractValue(buffer, 19, 'int16'), // Offset 19
|
|
638
|
+
VAr_SF: this.extractValue(buffer, 21, 'int16'), // Offset 21
|
|
639
|
+
PF_SF: this.extractValue(buffer, 23, 'int16'), // Offset 23
|
|
640
|
+
WH_SF: this.extractValue(buffer, 26, 'int16'), // Offset 26
|
|
641
|
+
DCA_SF: this.extractValue(buffer, 28, 'int16'), // Offset 28
|
|
642
|
+
DCV_SF: this.extractValue(buffer, 29, 'int16'), // Offset 29
|
|
643
|
+
DCW_SF: this.extractValue(buffer, 31, 'int16'), // Offset 31
|
|
644
|
+
Tmp_SF: this.extractValue(buffer, 36, 'int16') // Offset 36
|
|
608
645
|
};
|
|
609
646
|
this.logRegisterRead(103, 6, 'A_SF', scaleFactors.A_SF, 'int16');
|
|
610
647
|
this.logRegisterRead(103, 13, 'V_SF', scaleFactors.V_SF, 'int16');
|
|
@@ -666,38 +703,91 @@ class SunspecModbusClient {
|
|
|
666
703
|
*
|
|
667
704
|
* @returns Object containing all scale factors or null if model not found
|
|
668
705
|
*/
|
|
706
|
+
/**
|
|
707
|
+
* Extract MPPT scale factors from a pre-read model buffer
|
|
708
|
+
*/
|
|
709
|
+
extractMPPTScaleFactors(buffer) {
|
|
710
|
+
const scaleFactors = {
|
|
711
|
+
DCA_SF: this.extractValue(buffer, 2, 'int16'),
|
|
712
|
+
DCV_SF: this.extractValue(buffer, 3, 'int16'),
|
|
713
|
+
DCW_SF: this.extractValue(buffer, 4, 'int16'),
|
|
714
|
+
DCWH_SF: this.extractValue(buffer, 5, 'int16'),
|
|
715
|
+
};
|
|
716
|
+
this.logRegisterRead(160, 2, 'DCA_SF', scaleFactors.DCA_SF, 'int16');
|
|
717
|
+
this.logRegisterRead(160, 3, 'DCV_SF', scaleFactors.DCV_SF, 'int16');
|
|
718
|
+
this.logRegisterRead(160, 4, 'DCW_SF', scaleFactors.DCW_SF, 'int16');
|
|
719
|
+
this.logRegisterRead(160, 5, 'DCWH_SF', scaleFactors.DCWH_SF, 'int16');
|
|
720
|
+
return scaleFactors;
|
|
721
|
+
}
|
|
669
722
|
async readMPPTScaleFactors() {
|
|
670
723
|
const model = this.findModel(sunspec_interfaces_js_1.SunspecModelId.MPPT);
|
|
671
724
|
if (!model) {
|
|
672
725
|
console.log('MPPT model 160 not found');
|
|
673
726
|
return null;
|
|
674
727
|
}
|
|
675
|
-
const baseAddr = model.address;
|
|
676
728
|
try {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
// Read scale factors at their specific offsets
|
|
680
|
-
const DCA_SF = await this.readRegisterValue(moduleAddr + 2, 1, 'int16');
|
|
681
|
-
const DCV_SF = await this.readRegisterValue(moduleAddr + 3, 1, 'int16');
|
|
682
|
-
const DCW_SF = await this.readRegisterValue(moduleAddr + 4, 1, 'int16');
|
|
683
|
-
const DCWH_SF = await this.readRegisterValue(moduleAddr + 5, 1, 'int16');
|
|
684
|
-
const scaleFactors = {
|
|
685
|
-
DCA_SF, // Current Scale Factor
|
|
686
|
-
DCV_SF, // Voltage Scale Factor
|
|
687
|
-
DCW_SF, // Power Scale Factor
|
|
688
|
-
DCWH_SF, // Energy Scale Factor
|
|
689
|
-
};
|
|
690
|
-
this.logRegisterRead(160, 2, 'DCA_SF', DCA_SF, 'int16');
|
|
691
|
-
this.logRegisterRead(160, 3, 'DCV_SF', DCV_SF, 'int16');
|
|
692
|
-
this.logRegisterRead(160, 4, 'DCW_SF', DCW_SF, 'int16');
|
|
693
|
-
this.logRegisterRead(160, 5, 'DCWH_SF', DCWH_SF, 'int16');
|
|
694
|
-
return scaleFactors;
|
|
729
|
+
const buffer = await this.readModelBlock(model);
|
|
730
|
+
return this.extractMPPTScaleFactors(buffer);
|
|
695
731
|
}
|
|
696
732
|
catch (error) {
|
|
697
733
|
console.error(`Error reading MPPT scale factors: ${error}`);
|
|
698
734
|
return null;
|
|
699
735
|
}
|
|
700
736
|
}
|
|
737
|
+
/**
|
|
738
|
+
* Extract MPPT module data from a pre-read model buffer
|
|
739
|
+
*/
|
|
740
|
+
extractMPPTModuleData(buffer, model, moduleId, scaleFactors) {
|
|
741
|
+
const fixedBlockScaleFactorsOffset = 10;
|
|
742
|
+
const moduleSize = 20;
|
|
743
|
+
const offset = (moduleId - 1) * moduleSize;
|
|
744
|
+
const moduleOffset = fixedBlockScaleFactorsOffset + offset;
|
|
745
|
+
const id = this.extractValue(buffer, moduleOffset, 'uint16');
|
|
746
|
+
const idString = this.extractValue(buffer, moduleOffset + 1, 'string', 8);
|
|
747
|
+
const dcCurrentRaw = this.extractValue(buffer, moduleOffset + 9, 'uint16');
|
|
748
|
+
const dcVoltageRaw = this.extractValue(buffer, moduleOffset + 10, 'uint16');
|
|
749
|
+
const dcPowerRaw = this.extractValue(buffer, moduleOffset + 11, 'uint16');
|
|
750
|
+
const dcEnergyRaw = this.extractValue(buffer, moduleOffset + 12, 'uint32', 2);
|
|
751
|
+
const temperatureRaw = this.extractValue(buffer, moduleOffset + 16, 'int16');
|
|
752
|
+
const dcst = this.extractValue(buffer, moduleOffset + 17, 'uint16');
|
|
753
|
+
this.logRegisterRead(160, 0, `MPPT ${moduleId} ID`, id, 'uint16');
|
|
754
|
+
this.logRegisterRead(160, 1, `MPPT ${moduleId} String ID`, idString, 'string');
|
|
755
|
+
if (this.isUnimplementedValue(id, 'uint16') &&
|
|
756
|
+
this.isUnimplementedValue(dcCurrentRaw, 'uint16') &&
|
|
757
|
+
this.isUnimplementedValue(dcVoltageRaw, 'uint16') &&
|
|
758
|
+
this.isUnimplementedValue(dcPowerRaw, 'uint16')) {
|
|
759
|
+
console.log(`MPPT module ${moduleId} appears to be unconnected (all values are 0xFFFF)`);
|
|
760
|
+
return null;
|
|
761
|
+
}
|
|
762
|
+
const temperatureScaleFactor = -1;
|
|
763
|
+
this.logRegisterRead(160, 12, `MPPT ${moduleId} DC Energy`, dcEnergyRaw, 'acc32');
|
|
764
|
+
const timestampRaw = this.extractValue(buffer, moduleOffset + 14, 'uint32', 2);
|
|
765
|
+
this.logRegisterRead(160, 14, `MPPT ${moduleId} Timestamp`, timestampRaw, 'uint32');
|
|
766
|
+
this.logRegisterRead(160, 17, `MPPT ${moduleId} Operating State`, dcst, 'enum16');
|
|
767
|
+
const data = {
|
|
768
|
+
blockNumber: 160,
|
|
769
|
+
blockAddress: model.address,
|
|
770
|
+
blockLength: model.length,
|
|
771
|
+
id: id,
|
|
772
|
+
stringId: idString,
|
|
773
|
+
dcCurrent: this.applyScaleFactor(dcCurrentRaw, scaleFactors.DCA_SF, 'uint16', `MPPT ${moduleId} DC Current`, 9, 160),
|
|
774
|
+
dcCurrentSF: scaleFactors.DCA_SF,
|
|
775
|
+
dcVoltage: this.applyScaleFactor(dcVoltageRaw, scaleFactors.DCV_SF, 'uint16', `MPPT ${moduleId} DC Voltage`, 10, 160),
|
|
776
|
+
dcVoltageSF: scaleFactors.DCV_SF,
|
|
777
|
+
dcPower: this.applyScaleFactor(dcPowerRaw, scaleFactors.DCW_SF, 'uint16', `MPPT ${moduleId} DC Power`, 11, 160),
|
|
778
|
+
dcPowerSF: scaleFactors.DCW_SF,
|
|
779
|
+
dcEnergy: !this.isUnimplementedValue(dcEnergyRaw, 'acc32')
|
|
780
|
+
? dcEnergyRaw * Math.pow(10, scaleFactors.DCWH_SF)
|
|
781
|
+
: undefined,
|
|
782
|
+
dcEnergySF: scaleFactors.DCWH_SF,
|
|
783
|
+
timestamp: timestampRaw,
|
|
784
|
+
temperature: this.applyScaleFactor(temperatureRaw, temperatureScaleFactor, 'int16', `MPPT ${moduleId} Temperature`, 16, 160),
|
|
785
|
+
temperatureSF: temperatureScaleFactor,
|
|
786
|
+
operatingState: dcst,
|
|
787
|
+
};
|
|
788
|
+
console.debug(`[Model 160] MPPT Module ${moduleId} Data:`, data);
|
|
789
|
+
return data;
|
|
790
|
+
}
|
|
701
791
|
/**
|
|
702
792
|
* Read MPPT data from Model 160
|
|
703
793
|
*/
|
|
@@ -707,68 +797,11 @@ class SunspecModbusClient {
|
|
|
707
797
|
console.log('MPPT model 160 not found');
|
|
708
798
|
return null;
|
|
709
799
|
}
|
|
710
|
-
const baseAddr = model.address;
|
|
711
800
|
try {
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
const
|
|
715
|
-
|
|
716
|
-
const moduleAddr = baseAddr + fixedBlockScaleFactorsOffset + offset;
|
|
717
|
-
// Read scale factors using dedicated method
|
|
718
|
-
const scaleFactors = await this.readMPPTScaleFactors();
|
|
719
|
-
if (!scaleFactors) {
|
|
720
|
-
console.error(`Failed to read scale factors for MPPT module ${moduleId}`);
|
|
721
|
-
return null;
|
|
722
|
-
}
|
|
723
|
-
const id = await this.readRegisterValue(moduleAddr, 1, 'uint16');
|
|
724
|
-
const idString = await this.readRegisterValue(moduleAddr + 1, 8, 'string');
|
|
725
|
-
const dcCurrentRaw = await this.readRegisterValue(moduleAddr + 9, 1, 'uint16');
|
|
726
|
-
const dcVoltageRaw = await this.readRegisterValue(moduleAddr + 10, 1, 'uint16');
|
|
727
|
-
const dcPowerRaw = await this.readRegisterValue(moduleAddr + 11, 1, 'uint16');
|
|
728
|
-
const dcEnergyRaw = await this.readRegisterValue(moduleAddr + 12, 2, 'uint32');
|
|
729
|
-
const temperatureRaw = await this.readRegisterValue(moduleAddr + 16, 1, 'int16');
|
|
730
|
-
const dcst = await this.readRegisterValue(moduleAddr + 17, 1, 'uint16');
|
|
731
|
-
this.logRegisterRead(160, 0, `MPPT ${moduleId} ID`, id, 'uint16');
|
|
732
|
-
this.logRegisterRead(160, 1, `MPPT ${moduleId} String ID`, idString, 'string');
|
|
733
|
-
// Map the DC module state to human-readable name
|
|
734
|
-
// Check if this module is actually implemented/connected
|
|
735
|
-
// If all key values are unimplemented, this module doesn't exist
|
|
736
|
-
if (this.isUnimplementedValue(id, 'uint16') &&
|
|
737
|
-
this.isUnimplementedValue(dcCurrentRaw, 'uint16') &&
|
|
738
|
-
this.isUnimplementedValue(dcVoltageRaw, 'uint16') &&
|
|
739
|
-
this.isUnimplementedValue(dcPowerRaw, 'uint16')) {
|
|
740
|
-
console.log(`MPPT module ${moduleId} appears to be unconnected (all values are 0xFFFF)`);
|
|
741
|
-
return null;
|
|
742
|
-
}
|
|
743
|
-
// Note: There appears to be a temperature scale factor in the original model,
|
|
744
|
-
// but it's not in the current register map. We'll apply a default scale factor.
|
|
745
|
-
const temperatureScaleFactor = -1; // Common default for temperature readings
|
|
746
|
-
this.logRegisterRead(160, 12, `MPPT ${moduleId} DC Energy`, dcEnergyRaw, 'acc32');
|
|
747
|
-
const timestampRaw = await this.readRegisterValue(moduleAddr + 14, 2, 'uint32');
|
|
748
|
-
this.logRegisterRead(160, 14, `MPPT ${moduleId} Timestamp`, timestampRaw, 'uint32');
|
|
749
|
-
this.logRegisterRead(160, 17, `MPPT ${moduleId} Operating State`, dcst, 'enum16');
|
|
750
|
-
const data = {
|
|
751
|
-
blockNumber: 160,
|
|
752
|
-
blockAddress: model.address,
|
|
753
|
-
blockLength: model.length,
|
|
754
|
-
id: id,
|
|
755
|
-
stringId: idString,
|
|
756
|
-
dcCurrent: this.applyScaleFactor(dcCurrentRaw, scaleFactors.DCA_SF, 'uint16', `MPPT ${moduleId} DC Current`, 9, 160),
|
|
757
|
-
dcCurrentSF: scaleFactors.DCA_SF,
|
|
758
|
-
dcVoltage: this.applyScaleFactor(dcVoltageRaw, scaleFactors.DCV_SF, 'uint16', `MPPT ${moduleId} DC Voltage`, 10, 160),
|
|
759
|
-
dcVoltageSF: scaleFactors.DCV_SF,
|
|
760
|
-
dcPower: this.applyScaleFactor(dcPowerRaw, scaleFactors.DCW_SF, 'uint16', `MPPT ${moduleId} DC Power`, 11, 160),
|
|
761
|
-
dcPowerSF: scaleFactors.DCW_SF,
|
|
762
|
-
dcEnergy: !this.isUnimplementedValue(dcEnergyRaw, 'acc32')
|
|
763
|
-
? dcEnergyRaw * Math.pow(10, scaleFactors.DCWH_SF)
|
|
764
|
-
: undefined,
|
|
765
|
-
dcEnergySF: scaleFactors.DCWH_SF,
|
|
766
|
-
timestamp: timestampRaw,
|
|
767
|
-
temperature: this.applyScaleFactor(temperatureRaw, temperatureScaleFactor, 'int16', `MPPT ${moduleId} Temperature`, 16, 160),
|
|
768
|
-
temperatureSF: temperatureScaleFactor,
|
|
769
|
-
operatingState: dcst,
|
|
770
|
-
};
|
|
771
|
-
return data;
|
|
801
|
+
// Read entire model block in a single Modbus call
|
|
802
|
+
const buffer = await this.readModelBlock(model);
|
|
803
|
+
const scaleFactors = this.extractMPPTScaleFactors(buffer);
|
|
804
|
+
return this.extractMPPTModuleData(buffer, model, moduleId, scaleFactors);
|
|
772
805
|
}
|
|
773
806
|
catch (error) {
|
|
774
807
|
console.error(`Error reading MPPT data for module ${moduleId}: ${error}`);
|
|
@@ -780,24 +813,18 @@ class SunspecModbusClient {
|
|
|
780
813
|
*/
|
|
781
814
|
async readAllMPPTData() {
|
|
782
815
|
const mpptData = [];
|
|
783
|
-
// Find the MPPT model first
|
|
784
816
|
const model = this.findModel(sunspec_interfaces_js_1.SunspecModelId.MPPT);
|
|
785
817
|
if (!model) {
|
|
786
818
|
console.log('MPPT model 160 not found');
|
|
787
819
|
return [];
|
|
788
820
|
}
|
|
789
|
-
const scaleFactors = await this.readMPPTScaleFactors();
|
|
790
|
-
if (!scaleFactors) {
|
|
791
|
-
console.error(`Failed to read scale factors for MPPT`);
|
|
792
|
-
return [];
|
|
793
|
-
}
|
|
794
|
-
// Read the module count from register 8
|
|
795
|
-
let moduleCount = 4; // Default fallback value
|
|
796
821
|
try {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
const
|
|
800
|
-
//
|
|
822
|
+
// Read entire model block in a single Modbus call
|
|
823
|
+
const buffer = await this.readModelBlock(model);
|
|
824
|
+
const scaleFactors = this.extractMPPTScaleFactors(buffer);
|
|
825
|
+
// Read the module count from register 8
|
|
826
|
+
let moduleCount = 4; // Default fallback value
|
|
827
|
+
const count = this.extractValue(buffer, 8, 'uint16');
|
|
801
828
|
if (!this.isUnimplementedValue(count, 'uint16') && count > 0 && count <= 20) {
|
|
802
829
|
moduleCount = count;
|
|
803
830
|
console.log(`MPPT module count from register 8: ${moduleCount}`);
|
|
@@ -805,27 +832,25 @@ class SunspecModbusClient {
|
|
|
805
832
|
else {
|
|
806
833
|
console.log(`Invalid or unimplemented module count (${count}), using default: ${moduleCount}`);
|
|
807
834
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
mpptData.push(data);
|
|
835
|
+
// Extract each MPPT module from the same buffer
|
|
836
|
+
for (let i = 1; i <= moduleCount; i++) {
|
|
837
|
+
try {
|
|
838
|
+
const data = this.extractMPPTModuleData(buffer, model, i, scaleFactors);
|
|
839
|
+
console.log(`MPPT ${i} has id ${data?.id} (${data?.stringId}) with ${data?.dcPower}W`);
|
|
840
|
+
if (data &&
|
|
841
|
+
(data.dcCurrent !== undefined ||
|
|
842
|
+
data.dcVoltage !== undefined ||
|
|
843
|
+
data.dcPower !== undefined)) {
|
|
844
|
+
mpptData.push(data);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
catch (error) {
|
|
848
|
+
console.debug(`Could not read MPPT module ${i}: ${error}`);
|
|
823
849
|
}
|
|
824
850
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
}
|
|
851
|
+
}
|
|
852
|
+
catch (error) {
|
|
853
|
+
console.error(`Error reading all MPPT data: ${error}`);
|
|
829
854
|
}
|
|
830
855
|
return mpptData;
|
|
831
856
|
}
|
|
@@ -922,22 +947,22 @@ class SunspecModbusClient {
|
|
|
922
947
|
}
|
|
923
948
|
}
|
|
924
949
|
/**
|
|
925
|
-
*
|
|
950
|
+
* Extract Model 802 scale factors from a pre-read buffer (offsets 52-63)
|
|
926
951
|
*/
|
|
927
|
-
|
|
952
|
+
extractBatteryBaseScaleFactors(buffer) {
|
|
928
953
|
const scaleFactors = {
|
|
929
|
-
AHRtg_SF:
|
|
930
|
-
WHRtg_SF:
|
|
931
|
-
WChaDisChaMax_SF:
|
|
932
|
-
DisChaRte_SF:
|
|
933
|
-
SoC_SF:
|
|
934
|
-
DoD_SF:
|
|
935
|
-
SoH_SF:
|
|
936
|
-
V_SF:
|
|
937
|
-
CellV_SF:
|
|
938
|
-
A_SF:
|
|
939
|
-
AMax_SF:
|
|
940
|
-
W_SF:
|
|
954
|
+
AHRtg_SF: this.extractValue(buffer, 52, 'int16'),
|
|
955
|
+
WHRtg_SF: this.extractValue(buffer, 53, 'int16'),
|
|
956
|
+
WChaDisChaMax_SF: this.extractValue(buffer, 54, 'int16'),
|
|
957
|
+
DisChaRte_SF: this.extractValue(buffer, 55, 'int16'),
|
|
958
|
+
SoC_SF: this.extractValue(buffer, 56, 'int16'),
|
|
959
|
+
DoD_SF: this.extractValue(buffer, 57, 'int16'),
|
|
960
|
+
SoH_SF: this.extractValue(buffer, 58, 'int16'),
|
|
961
|
+
V_SF: this.extractValue(buffer, 59, 'int16'),
|
|
962
|
+
CellV_SF: this.extractValue(buffer, 60, 'int16'),
|
|
963
|
+
A_SF: this.extractValue(buffer, 61, 'int16'),
|
|
964
|
+
AMax_SF: this.extractValue(buffer, 62, 'int16'),
|
|
965
|
+
W_SF: this.extractValue(buffer, 63, 'int16'),
|
|
941
966
|
};
|
|
942
967
|
this.logRegisterRead(802, 52, 'AHRtg_SF', scaleFactors.AHRtg_SF, 'int16');
|
|
943
968
|
this.logRegisterRead(802, 53, 'WHRtg_SF', scaleFactors.WHRtg_SF, 'int16');
|
|
@@ -962,51 +987,52 @@ class SunspecModbusClient {
|
|
|
962
987
|
console.log('Battery Base model 802 not found');
|
|
963
988
|
return null;
|
|
964
989
|
}
|
|
965
|
-
|
|
966
|
-
console.log(`Reading Battery Base Data from Model 802 at base address: ${baseAddr}`);
|
|
990
|
+
console.log(`Reading Battery Base Data from Model 802 at base address: ${model.address}`);
|
|
967
991
|
try {
|
|
968
|
-
// Read
|
|
969
|
-
const
|
|
970
|
-
//
|
|
971
|
-
const
|
|
972
|
-
|
|
973
|
-
const
|
|
974
|
-
const
|
|
975
|
-
const
|
|
976
|
-
const
|
|
977
|
-
const
|
|
978
|
-
const
|
|
979
|
-
const
|
|
980
|
-
const
|
|
981
|
-
const
|
|
982
|
-
const
|
|
983
|
-
const
|
|
984
|
-
const
|
|
985
|
-
const
|
|
986
|
-
const
|
|
987
|
-
const
|
|
988
|
-
const
|
|
989
|
-
const
|
|
990
|
-
const
|
|
991
|
-
const
|
|
992
|
-
const
|
|
993
|
-
const
|
|
994
|
-
const
|
|
995
|
-
const
|
|
996
|
-
const
|
|
997
|
-
const
|
|
998
|
-
const
|
|
999
|
-
const
|
|
1000
|
-
const
|
|
1001
|
-
const
|
|
1002
|
-
const
|
|
1003
|
-
const
|
|
1004
|
-
const
|
|
1005
|
-
const
|
|
1006
|
-
const
|
|
1007
|
-
const
|
|
1008
|
-
const
|
|
1009
|
-
const
|
|
992
|
+
// Read entire model block in a single Modbus call
|
|
993
|
+
const buffer = await this.readModelBlock(model);
|
|
994
|
+
// Extract scale factors from buffer (offsets 52-63)
|
|
995
|
+
const sf = this.extractBatteryBaseScaleFactors(buffer);
|
|
996
|
+
// Extract raw values from buffer
|
|
997
|
+
const ahRtgRaw = this.extractValue(buffer, 2, 'uint16');
|
|
998
|
+
const whRtgRaw = this.extractValue(buffer, 3, 'uint16');
|
|
999
|
+
const wChaRteMaxRaw = this.extractValue(buffer, 4, 'uint16');
|
|
1000
|
+
const wDisChaRteMaxRaw = this.extractValue(buffer, 5, 'uint16');
|
|
1001
|
+
const disChaRteRaw = this.extractValue(buffer, 6, 'uint16');
|
|
1002
|
+
const soCMaxRaw = this.extractValue(buffer, 7, 'uint16');
|
|
1003
|
+
const soCMinRaw = this.extractValue(buffer, 8, 'uint16');
|
|
1004
|
+
const soCRsvMaxRaw = this.extractValue(buffer, 9, 'uint16');
|
|
1005
|
+
const soCRsvMinRaw = this.extractValue(buffer, 10, 'uint16');
|
|
1006
|
+
const soCRaw = this.extractValue(buffer, 11, 'uint16');
|
|
1007
|
+
const doDRaw = this.extractValue(buffer, 12, 'uint16');
|
|
1008
|
+
const soHRaw = this.extractValue(buffer, 13, 'uint16');
|
|
1009
|
+
const nCycRaw = this.extractValue(buffer, 14, 'uint32', 2);
|
|
1010
|
+
const chaStRaw = this.extractValue(buffer, 16, 'uint16');
|
|
1011
|
+
const locRemCtlRaw = this.extractValue(buffer, 17, 'uint16');
|
|
1012
|
+
const typRaw = this.extractValue(buffer, 21, 'uint16');
|
|
1013
|
+
const stateRaw = this.extractValue(buffer, 22, 'uint16');
|
|
1014
|
+
const evt1Raw = this.extractValue(buffer, 26, 'uint32', 2);
|
|
1015
|
+
const evt2Raw = this.extractValue(buffer, 28, 'uint32', 2);
|
|
1016
|
+
const evtVnd1Raw = this.extractValue(buffer, 30, 'uint32', 2);
|
|
1017
|
+
const evtVnd2Raw = this.extractValue(buffer, 32, 'uint32', 2);
|
|
1018
|
+
const vRaw = this.extractValue(buffer, 34, 'uint16');
|
|
1019
|
+
const vMaxRaw = this.extractValue(buffer, 35, 'uint16');
|
|
1020
|
+
const vMinRaw = this.extractValue(buffer, 36, 'uint16');
|
|
1021
|
+
const cellVMaxRaw = this.extractValue(buffer, 37, 'uint16');
|
|
1022
|
+
const cellVMaxStrRaw = this.extractValue(buffer, 38, 'uint16');
|
|
1023
|
+
const cellVMaxModRaw = this.extractValue(buffer, 39, 'uint16');
|
|
1024
|
+
const cellVMinRaw = this.extractValue(buffer, 40, 'uint16');
|
|
1025
|
+
const cellVMinStrRaw = this.extractValue(buffer, 41, 'uint16');
|
|
1026
|
+
const cellVMinModRaw = this.extractValue(buffer, 42, 'uint16');
|
|
1027
|
+
const cellVAvgRaw = this.extractValue(buffer, 43, 'uint16');
|
|
1028
|
+
const aRaw = this.extractValue(buffer, 44, 'int16');
|
|
1029
|
+
const aChaMaxRaw = this.extractValue(buffer, 45, 'uint16');
|
|
1030
|
+
const aDisChaMaxRaw = this.extractValue(buffer, 46, 'uint16');
|
|
1031
|
+
const wRaw = this.extractValue(buffer, 47, 'int16');
|
|
1032
|
+
const reqInvStateRaw = this.extractValue(buffer, 48, 'uint16');
|
|
1033
|
+
const reqWRaw = this.extractValue(buffer, 49, 'int16');
|
|
1034
|
+
const setOpRaw = this.extractValue(buffer, 50, 'uint16');
|
|
1035
|
+
const setInvStateRaw = this.extractValue(buffer, 51, 'uint16');
|
|
1010
1036
|
// Map enum fields
|
|
1011
1037
|
const chaStName = this.mapBatteryChargeState(chaStRaw);
|
|
1012
1038
|
const typName = this.mapBatteryType(typRaw);
|
|
@@ -1082,6 +1108,7 @@ class SunspecModbusClient {
|
|
|
1082
1108
|
setOp: !this.isUnimplementedValue(setOpRaw, 'enum16') ? setOpRaw : undefined,
|
|
1083
1109
|
setInvState: !this.isUnimplementedValue(setInvStateRaw, 'enum16') ? setInvStateRaw : undefined,
|
|
1084
1110
|
};
|
|
1111
|
+
console.debug('[Model 802] Battery Base Data:', data);
|
|
1085
1112
|
return data;
|
|
1086
1113
|
}
|
|
1087
1114
|
catch (error) {
|
|
@@ -1106,22 +1133,23 @@ class SunspecModbusClient {
|
|
|
1106
1133
|
console.log('No battery model found');
|
|
1107
1134
|
return null;
|
|
1108
1135
|
}
|
|
1109
|
-
|
|
1110
|
-
console.log(`Reading Battery Data from Model ${model.id} at base address: ${baseAddr}`);
|
|
1136
|
+
console.log(`Reading Battery Data from Model ${model.id} at base address: ${model.address}`);
|
|
1111
1137
|
try {
|
|
1112
1138
|
if (model.id === 124) {
|
|
1113
1139
|
// Model 124: Basic Storage Controls
|
|
1114
1140
|
console.log('Using Model 124 (Basic Storage Controls)');
|
|
1115
|
-
// Read
|
|
1141
|
+
// Read entire model block in a single Modbus call
|
|
1142
|
+
const buffer = await this.readModelBlock(model);
|
|
1143
|
+
// Extract scale factors from buffer (offsets 18-25)
|
|
1116
1144
|
const scaleFactors = {
|
|
1117
|
-
WChaMax_SF:
|
|
1118
|
-
WChaDisChaGra_SF:
|
|
1119
|
-
VAChaMax_SF:
|
|
1120
|
-
MinRsvPct_SF:
|
|
1121
|
-
ChaState_SF:
|
|
1122
|
-
StorAval_SF:
|
|
1123
|
-
InBatV_SF:
|
|
1124
|
-
InOutWRte_SF:
|
|
1145
|
+
WChaMax_SF: this.extractValue(buffer, 18, 'int16'),
|
|
1146
|
+
WChaDisChaGra_SF: this.extractValue(buffer, 19, 'int16'),
|
|
1147
|
+
VAChaMax_SF: this.extractValue(buffer, 20, 'int16'),
|
|
1148
|
+
MinRsvPct_SF: this.extractValue(buffer, 21, 'int16'),
|
|
1149
|
+
ChaState_SF: this.extractValue(buffer, 22, 'int16'),
|
|
1150
|
+
StorAval_SF: this.extractValue(buffer, 23, 'int16'),
|
|
1151
|
+
InBatV_SF: this.extractValue(buffer, 24, 'int16'),
|
|
1152
|
+
InOutWRte_SF: this.extractValue(buffer, 25, 'int16')
|
|
1125
1153
|
};
|
|
1126
1154
|
this.logRegisterRead(124, 18, 'WChaMax_SF', scaleFactors.WChaMax_SF, 'int16');
|
|
1127
1155
|
this.logRegisterRead(124, 19, 'WChaDisChaGra_SF', scaleFactors.WChaDisChaGra_SF, 'int16');
|
|
@@ -1131,23 +1159,23 @@ class SunspecModbusClient {
|
|
|
1131
1159
|
this.logRegisterRead(124, 23, 'StorAval_SF', scaleFactors.StorAval_SF, 'int16');
|
|
1132
1160
|
this.logRegisterRead(124, 24, 'InBatV_SF', scaleFactors.InBatV_SF, 'int16');
|
|
1133
1161
|
this.logRegisterRead(124, 25, 'InOutWRte_SF', scaleFactors.InOutWRte_SF, 'int16');
|
|
1134
|
-
//
|
|
1135
|
-
const wChaMaxRaw =
|
|
1136
|
-
const wChaGraRaw =
|
|
1137
|
-
const wDisChaGraRaw =
|
|
1138
|
-
const storCtlModRaw =
|
|
1139
|
-
const vaChaMaxRaw =
|
|
1140
|
-
const minRsvPctRaw =
|
|
1141
|
-
const chaStateRaw =
|
|
1142
|
-
const storAvalRaw =
|
|
1143
|
-
const inBatVRaw =
|
|
1144
|
-
const chaStRaw =
|
|
1145
|
-
const outWRteRaw =
|
|
1146
|
-
const inWRteRaw =
|
|
1147
|
-
const inOutWRteWinTmsRaw =
|
|
1148
|
-
const inOutWRteRvrtTmsRaw =
|
|
1149
|
-
const inOutWRteRmpTmsRaw =
|
|
1150
|
-
const chaGriSetRaw =
|
|
1162
|
+
// Extract raw values from buffer
|
|
1163
|
+
const wChaMaxRaw = this.extractValue(buffer, 2, 'uint16');
|
|
1164
|
+
const wChaGraRaw = this.extractValue(buffer, 3, 'uint16');
|
|
1165
|
+
const wDisChaGraRaw = this.extractValue(buffer, 4, 'uint16');
|
|
1166
|
+
const storCtlModRaw = this.extractValue(buffer, 5, 'uint16');
|
|
1167
|
+
const vaChaMaxRaw = this.extractValue(buffer, 6, 'uint16');
|
|
1168
|
+
const minRsvPctRaw = this.extractValue(buffer, 7, 'uint16');
|
|
1169
|
+
const chaStateRaw = this.extractValue(buffer, 8, 'uint16');
|
|
1170
|
+
const storAvalRaw = this.extractValue(buffer, 9, 'uint16');
|
|
1171
|
+
const inBatVRaw = this.extractValue(buffer, 10, 'uint16');
|
|
1172
|
+
const chaStRaw = this.extractValue(buffer, 11, 'uint16');
|
|
1173
|
+
const outWRteRaw = this.extractValue(buffer, 12, 'int16');
|
|
1174
|
+
const inWRteRaw = this.extractValue(buffer, 13, 'int16');
|
|
1175
|
+
const inOutWRteWinTmsRaw = this.extractValue(buffer, 14, 'uint16');
|
|
1176
|
+
const inOutWRteRvrtTmsRaw = this.extractValue(buffer, 15, 'uint16');
|
|
1177
|
+
const inOutWRteRmpTmsRaw = this.extractValue(buffer, 16, 'uint16');
|
|
1178
|
+
const chaGriSetRaw = this.extractValue(buffer, 17, 'uint16');
|
|
1151
1179
|
// Map charge state and log non-scaled fields
|
|
1152
1180
|
const chaStName = this.mapBatteryChargeState(chaStRaw);
|
|
1153
1181
|
this.logRegisterRead(124, 5, 'storCtlMod', storCtlModRaw, 'bitfield16');
|
|
@@ -1206,6 +1234,7 @@ class SunspecModbusClient {
|
|
|
1206
1234
|
data.dischargePower = Math.abs((data.outWRte / 100) * data.wChaMax);
|
|
1207
1235
|
console.log(`Calculated Discharge Power (inWRte: ${data.outWRte}, wChaMax: ${data.wChaMax}): ${data.dischargePower?.toFixed(2)} W`);
|
|
1208
1236
|
}
|
|
1237
|
+
console.debug('[Model 124] Battery Data:', data);
|
|
1209
1238
|
return data;
|
|
1210
1239
|
}
|
|
1211
1240
|
else if (model.id === 802) {
|
|
@@ -1228,7 +1257,7 @@ class SunspecModbusClient {
|
|
|
1228
1257
|
dischargePower = Math.abs(baseData.w);
|
|
1229
1258
|
}
|
|
1230
1259
|
}
|
|
1231
|
-
|
|
1260
|
+
const result = {
|
|
1232
1261
|
blockNumber: 802,
|
|
1233
1262
|
blockAddress: model.address,
|
|
1234
1263
|
blockLength: model.length,
|
|
@@ -1242,6 +1271,8 @@ class SunspecModbusClient {
|
|
|
1242
1271
|
chargePower,
|
|
1243
1272
|
dischargePower,
|
|
1244
1273
|
};
|
|
1274
|
+
console.debug('[Model 802] Battery Data:', result);
|
|
1275
|
+
return result;
|
|
1245
1276
|
}
|
|
1246
1277
|
else {
|
|
1247
1278
|
// Handle other battery models (803) if needed
|
|
@@ -1366,29 +1397,30 @@ class SunspecModbusClient {
|
|
|
1366
1397
|
console.log('Battery model 124 not found');
|
|
1367
1398
|
return null;
|
|
1368
1399
|
}
|
|
1369
|
-
|
|
1370
|
-
console.log(`Reading Battery Controls from Model 124 at base address: ${baseAddr}`);
|
|
1400
|
+
console.log(`Reading Battery Controls from Model 124 at base address: ${model.address}`);
|
|
1371
1401
|
try {
|
|
1372
|
-
// Read
|
|
1402
|
+
// Read entire model block in a single Modbus call
|
|
1403
|
+
const buffer = await this.readModelBlock(model);
|
|
1404
|
+
// Extract scale factors from buffer
|
|
1373
1405
|
const scaleFactors = {
|
|
1374
|
-
WChaMax_SF:
|
|
1375
|
-
MinRsvPct_SF:
|
|
1376
|
-
InOutWRte_SF:
|
|
1406
|
+
WChaMax_SF: this.extractValue(buffer, 18, 'int16'),
|
|
1407
|
+
MinRsvPct_SF: this.extractValue(buffer, 21, 'int16'),
|
|
1408
|
+
InOutWRte_SF: this.extractValue(buffer, 25, 'int16')
|
|
1377
1409
|
};
|
|
1378
1410
|
this.logRegisterRead(124, 18, 'WChaMax_SF', scaleFactors.WChaMax_SF, 'int16');
|
|
1379
1411
|
this.logRegisterRead(124, 21, 'MinRsvPct_SF', scaleFactors.MinRsvPct_SF, 'int16');
|
|
1380
1412
|
this.logRegisterRead(124, 25, 'InOutWRte_SF', scaleFactors.InOutWRte_SF, 'int16');
|
|
1381
|
-
//
|
|
1382
|
-
const wChaMaxRaw =
|
|
1383
|
-
const storCtlModRaw =
|
|
1384
|
-
const minRsvPctRaw =
|
|
1385
|
-
const outWRteRaw =
|
|
1386
|
-
const inWRteRaw =
|
|
1387
|
-
const chaGriSetRaw =
|
|
1413
|
+
// Extract raw values from buffer
|
|
1414
|
+
const wChaMaxRaw = this.extractValue(buffer, 2, 'uint16');
|
|
1415
|
+
const storCtlModRaw = this.extractValue(buffer, 5, 'uint16');
|
|
1416
|
+
const minRsvPctRaw = this.extractValue(buffer, 7, 'uint16');
|
|
1417
|
+
const outWRteRaw = this.extractValue(buffer, 12, 'int16');
|
|
1418
|
+
const inWRteRaw = this.extractValue(buffer, 13, 'int16');
|
|
1419
|
+
const chaGriSetRaw = this.extractValue(buffer, 17, 'uint16');
|
|
1388
1420
|
this.logRegisterRead(124, 5, 'storCtlMod', storCtlModRaw, 'bitfield16');
|
|
1389
1421
|
this.logRegisterRead(124, 17, 'chaGriSet', chaGriSetRaw, 'enum16');
|
|
1390
1422
|
// Apply scale factors and return control settings
|
|
1391
|
-
|
|
1423
|
+
const controls = {
|
|
1392
1424
|
storCtlMod: !this.isUnimplementedValue(storCtlModRaw, 'bitfield16') ? storCtlModRaw : undefined,
|
|
1393
1425
|
chaGriSet: !this.isUnimplementedValue(chaGriSetRaw, 'enum16') ? chaGriSetRaw : undefined,
|
|
1394
1426
|
wChaMax: this.applyScaleFactor(wChaMaxRaw, scaleFactors.WChaMax_SF, 'uint16', 'Max Charge Power', 2, 124),
|
|
@@ -1396,6 +1428,8 @@ class SunspecModbusClient {
|
|
|
1396
1428
|
outWRte: this.applyScaleFactor(outWRteRaw, scaleFactors.InOutWRte_SF, 'int16', 'Discharge Rate', 12, 124),
|
|
1397
1429
|
minRsvPct: this.applyScaleFactor(minRsvPctRaw, scaleFactors.MinRsvPct_SF, 'uint16', 'Min Reserve Percent', 7, 124)
|
|
1398
1430
|
};
|
|
1431
|
+
console.debug('[Model 124] Battery Controls:', controls);
|
|
1432
|
+
return controls;
|
|
1399
1433
|
}
|
|
1400
1434
|
catch (error) {
|
|
1401
1435
|
console.error(`Error reading battery controls: ${error}`);
|
|
@@ -1417,8 +1451,7 @@ class SunspecModbusClient {
|
|
|
1417
1451
|
console.log('No meter model found');
|
|
1418
1452
|
return null;
|
|
1419
1453
|
}
|
|
1420
|
-
|
|
1421
|
-
console.log(`Reading Meter Data from Model ${model.id} at base address: ${baseAddr}`);
|
|
1454
|
+
console.log(`Reading Meter Data from Model ${model.id} at base address: ${model.address}`);
|
|
1422
1455
|
try {
|
|
1423
1456
|
// Different meter models have different register offsets
|
|
1424
1457
|
console.log(`Meter is Model ${model.id}`);
|
|
@@ -1461,18 +1494,20 @@ class SunspecModbusClient {
|
|
|
1461
1494
|
importOffset = 46; // TotWhImp - Total Wh Imported (acc32)
|
|
1462
1495
|
energySFOffset = 54; // TotWh_SF - Total Energy scale factor
|
|
1463
1496
|
}
|
|
1464
|
-
// Read
|
|
1465
|
-
const
|
|
1466
|
-
|
|
1467
|
-
const
|
|
1497
|
+
// Read entire model block in a single Modbus call
|
|
1498
|
+
const buffer = await this.readModelBlock(model);
|
|
1499
|
+
// Extract scale factors from buffer
|
|
1500
|
+
const powerSF = this.extractValue(buffer, powerSFOffset, 'int16');
|
|
1501
|
+
const freqSF = this.extractValue(buffer, freqSFOffset, 'int16');
|
|
1502
|
+
const energySF = this.extractValue(buffer, energySFOffset, 'int16');
|
|
1468
1503
|
this.logRegisterRead(model.id, powerSFOffset, 'W_SF', powerSF, 'int16');
|
|
1469
1504
|
this.logRegisterRead(model.id, freqSFOffset, 'Hz_SF', freqSF, 'int16');
|
|
1470
1505
|
this.logRegisterRead(model.id, energySFOffset, 'TotWh_SF', energySF, 'int16');
|
|
1471
|
-
//
|
|
1472
|
-
const powerRaw =
|
|
1473
|
-
const freqRaw =
|
|
1474
|
-
const exportRaw =
|
|
1475
|
-
const importRaw =
|
|
1506
|
+
// Extract raw values from buffer
|
|
1507
|
+
const powerRaw = this.extractValue(buffer, powerOffset, 'int16');
|
|
1508
|
+
const freqRaw = this.extractValue(buffer, freqOffset, 'uint16');
|
|
1509
|
+
const exportRaw = this.extractValue(buffer, exportOffset, 'uint32', 2);
|
|
1510
|
+
const importRaw = this.extractValue(buffer, importOffset, 'uint32', 2);
|
|
1476
1511
|
this.logRegisterRead(model.id, exportOffset, 'TotWhExp', exportRaw, 'acc32');
|
|
1477
1512
|
this.logRegisterRead(model.id, importOffset, 'TotWhImp', importRaw, 'acc32');
|
|
1478
1513
|
// Calculate final values with scale factors
|
|
@@ -1480,13 +1515,15 @@ class SunspecModbusClient {
|
|
|
1480
1515
|
const frequency = this.applyScaleFactor(freqRaw, freqSF, 'uint16', 'Frequency', freqOffset, model.id);
|
|
1481
1516
|
const exportedEnergy = this.applyScaleFactor(exportRaw, energySF, "acc32", 'Exported Energy', exportOffset, model.id);
|
|
1482
1517
|
const importedEnergy = this.applyScaleFactor(importRaw, energySF, "acc32", 'Imported Energy', importOffset, model.id);
|
|
1483
|
-
|
|
1518
|
+
const data = {
|
|
1484
1519
|
blockNumber: model.id,
|
|
1485
1520
|
totalPower,
|
|
1486
1521
|
frequency,
|
|
1487
1522
|
exportedEnergy,
|
|
1488
1523
|
importedEnergy
|
|
1489
1524
|
};
|
|
1525
|
+
console.debug(`[Model ${model.id}] Meter Data:`, data);
|
|
1526
|
+
return data;
|
|
1490
1527
|
}
|
|
1491
1528
|
catch (error) {
|
|
1492
1529
|
console.error(`Error reading meter data: ${error}`);
|
|
@@ -1502,30 +1539,28 @@ class SunspecModbusClient {
|
|
|
1502
1539
|
console.error('Common block model not found');
|
|
1503
1540
|
return null;
|
|
1504
1541
|
}
|
|
1505
|
-
|
|
1506
|
-
console.log(`Reading Common Block - Model address: ${model.address}, Base address for data: ${baseAddr}`);
|
|
1542
|
+
console.log(`Reading Common Block - Model address: ${model.address}`);
|
|
1507
1543
|
try {
|
|
1508
|
-
// Read
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
const
|
|
1516
|
-
const manufacturer = await this.readRegisterValue(manufacturerAddr, 16, 'string');
|
|
1544
|
+
// Read entire model block in a single Modbus call
|
|
1545
|
+
const buffer = await this.readModelBlock(model);
|
|
1546
|
+
// Common block offsets are relative to the model start (after ID and Length header,
|
|
1547
|
+
// but readModelBlock reads from model.address which includes the data area).
|
|
1548
|
+
// The offsets below are relative to the data start within the model block.
|
|
1549
|
+
// Since model.address points to the first data register (after the 2-register header
|
|
1550
|
+
// is already accounted for in model discovery), offset 0 = first data register.
|
|
1551
|
+
const manufacturer = this.extractValue(buffer, 2, 'string', 16);
|
|
1517
1552
|
this.logRegisterRead(1, 0, 'Manufacturer', manufacturer, 'string');
|
|
1518
|
-
const modelName =
|
|
1553
|
+
const modelName = this.extractValue(buffer, 18, 'string', 16);
|
|
1519
1554
|
this.logRegisterRead(1, 16, 'Model', modelName, 'string');
|
|
1520
|
-
const options =
|
|
1555
|
+
const options = this.extractValue(buffer, 34, 'string', 8);
|
|
1521
1556
|
this.logRegisterRead(1, 32, 'Options', options, 'string');
|
|
1522
|
-
const version =
|
|
1557
|
+
const version = this.extractValue(buffer, 42, 'string', 8);
|
|
1523
1558
|
this.logRegisterRead(1, 40, 'Version', version, 'string');
|
|
1524
|
-
const serialNumber =
|
|
1559
|
+
const serialNumber = this.extractValue(buffer, 50, 'string', 16);
|
|
1525
1560
|
this.logRegisterRead(1, 48, 'Serial Number', serialNumber, 'string');
|
|
1526
|
-
const deviceAddress =
|
|
1561
|
+
const deviceAddress = this.extractValue(buffer, 66, 'uint16');
|
|
1527
1562
|
this.logRegisterRead(1, 64, 'Device Address', deviceAddress, 'uint16');
|
|
1528
|
-
|
|
1563
|
+
const data = {
|
|
1529
1564
|
manufacturer,
|
|
1530
1565
|
model: modelName,
|
|
1531
1566
|
options,
|
|
@@ -1533,6 +1568,8 @@ class SunspecModbusClient {
|
|
|
1533
1568
|
serialNumber,
|
|
1534
1569
|
deviceAddress
|
|
1535
1570
|
};
|
|
1571
|
+
console.debug('[Model 1] Common Block:', data);
|
|
1572
|
+
return data;
|
|
1536
1573
|
}
|
|
1537
1574
|
catch (error) {
|
|
1538
1575
|
console.error(`Error reading common block: ${error}`);
|
|
@@ -1573,20 +1610,21 @@ class SunspecModbusClient {
|
|
|
1573
1610
|
console.log('Settings model 121 not found');
|
|
1574
1611
|
return null;
|
|
1575
1612
|
}
|
|
1576
|
-
const baseAddr = model.address;
|
|
1577
1613
|
try {
|
|
1578
|
-
// Read
|
|
1614
|
+
// Read entire model block in a single Modbus call
|
|
1615
|
+
const buffer = await this.readModelBlock(model);
|
|
1616
|
+
// Extract scale factors from buffer (offsets 22-31)
|
|
1579
1617
|
const scaleFactors = {
|
|
1580
|
-
WMax_SF:
|
|
1581
|
-
VRef_SF:
|
|
1582
|
-
VRefOfs_SF:
|
|
1583
|
-
VMinMax_SF:
|
|
1584
|
-
VAMax_SF:
|
|
1585
|
-
VArMax_SF:
|
|
1586
|
-
WGra_SF:
|
|
1587
|
-
PFMin_SF:
|
|
1588
|
-
MaxRmpRte_SF:
|
|
1589
|
-
ECPNomHz_SF:
|
|
1618
|
+
WMax_SF: this.extractValue(buffer, 22, 'int16'),
|
|
1619
|
+
VRef_SF: this.extractValue(buffer, 23, 'int16'),
|
|
1620
|
+
VRefOfs_SF: this.extractValue(buffer, 24, 'int16'),
|
|
1621
|
+
VMinMax_SF: this.extractValue(buffer, 25, 'int16'),
|
|
1622
|
+
VAMax_SF: this.extractValue(buffer, 26, 'int16'),
|
|
1623
|
+
VArMax_SF: this.extractValue(buffer, 27, 'int16'),
|
|
1624
|
+
WGra_SF: this.extractValue(buffer, 28, 'int16'),
|
|
1625
|
+
PFMin_SF: this.extractValue(buffer, 29, 'int16'),
|
|
1626
|
+
MaxRmpRte_SF: this.extractValue(buffer, 30, 'int16'),
|
|
1627
|
+
ECPNomHz_SF: this.extractValue(buffer, 31, 'int16')
|
|
1590
1628
|
};
|
|
1591
1629
|
this.logRegisterRead(121, 22, 'WMax_SF', scaleFactors.WMax_SF, 'int16');
|
|
1592
1630
|
this.logRegisterRead(121, 23, 'VRef_SF', scaleFactors.VRef_SF, 'int16');
|
|
@@ -1598,55 +1636,57 @@ class SunspecModbusClient {
|
|
|
1598
1636
|
this.logRegisterRead(121, 29, 'PFMin_SF', scaleFactors.PFMin_SF, 'int16');
|
|
1599
1637
|
this.logRegisterRead(121, 30, 'MaxRmpRte_SF', scaleFactors.MaxRmpRte_SF, 'int16');
|
|
1600
1638
|
this.logRegisterRead(121, 31, 'ECPNomHz_SF', scaleFactors.ECPNomHz_SF, 'int16');
|
|
1601
|
-
//
|
|
1602
|
-
const vArActRaw =
|
|
1603
|
-
const clcTotVARaw =
|
|
1604
|
-
const connPhRaw =
|
|
1639
|
+
// Extract non-scaled fields from buffer
|
|
1640
|
+
const vArActRaw = this.extractValue(buffer, 17, 'uint16');
|
|
1641
|
+
const clcTotVARaw = this.extractValue(buffer, 18, 'uint16');
|
|
1642
|
+
const connPhRaw = this.extractValue(buffer, 21, 'uint16');
|
|
1605
1643
|
this.logRegisterRead(121, 17, 'VArAct', vArActRaw, 'enum16');
|
|
1606
1644
|
this.logRegisterRead(121, 18, 'ClcTotVA', clcTotVARaw, 'enum16');
|
|
1607
1645
|
this.logRegisterRead(121, 21, 'ConnPh', connPhRaw, 'enum16');
|
|
1608
|
-
|
|
1646
|
+
const settings = {
|
|
1609
1647
|
blockNumber: 121,
|
|
1610
1648
|
blockAddress: model.address,
|
|
1611
1649
|
blockLength: model.length,
|
|
1612
1650
|
// Power settings - Offset 2
|
|
1613
|
-
WMax: this.applyScaleFactor(
|
|
1651
|
+
WMax: this.applyScaleFactor(this.extractValue(buffer, 2, 'uint16'), scaleFactors.WMax_SF, 'uint16', 'Max Power', 2, 121),
|
|
1614
1652
|
WMax_SF: scaleFactors.WMax_SF,
|
|
1615
1653
|
// Voltage settings - Offsets 3-6
|
|
1616
|
-
VRef: this.applyScaleFactor(
|
|
1654
|
+
VRef: this.applyScaleFactor(this.extractValue(buffer, 3, 'uint16'), scaleFactors.VRef_SF, 'uint16', 'Voltage Reference', 3, 121),
|
|
1617
1655
|
VRef_SF: scaleFactors.VRef_SF,
|
|
1618
|
-
VRefOfs: this.applyScaleFactor(
|
|
1656
|
+
VRefOfs: this.applyScaleFactor(this.extractValue(buffer, 4, 'int16'), scaleFactors.VRefOfs_SF, 'int16', 'Voltage Reference Offset', 4, 121),
|
|
1619
1657
|
VRefOfs_SF: scaleFactors.VRefOfs_SF,
|
|
1620
|
-
VMax: this.applyScaleFactor(
|
|
1621
|
-
VMin: this.applyScaleFactor(
|
|
1658
|
+
VMax: this.applyScaleFactor(this.extractValue(buffer, 5, 'uint16'), scaleFactors.VMinMax_SF, 'uint16', 'Max Voltage', 5, 121),
|
|
1659
|
+
VMin: this.applyScaleFactor(this.extractValue(buffer, 6, 'uint16'), scaleFactors.VMinMax_SF, 'uint16', 'Min Voltage', 6, 121),
|
|
1622
1660
|
VMinMax_SF: scaleFactors.VMinMax_SF,
|
|
1623
1661
|
// Apparent power settings - Offset 7
|
|
1624
|
-
VAMax: this.applyScaleFactor(
|
|
1662
|
+
VAMax: this.applyScaleFactor(this.extractValue(buffer, 7, 'uint16'), scaleFactors.VAMax_SF, 'uint16', 'Max Apparent Power', 7, 121),
|
|
1625
1663
|
VAMax_SF: scaleFactors.VAMax_SF,
|
|
1626
1664
|
// Reactive power settings - Offsets 8-11
|
|
1627
|
-
VArMaxQ1: this.applyScaleFactor(
|
|
1628
|
-
VArMaxQ2: this.applyScaleFactor(
|
|
1629
|
-
VArMaxQ3: this.applyScaleFactor(
|
|
1630
|
-
VArMaxQ4: this.applyScaleFactor(
|
|
1665
|
+
VArMaxQ1: this.applyScaleFactor(this.extractValue(buffer, 8, 'int16'), scaleFactors.VArMax_SF, 'int16', 'Max Reactive Power Q1', 8, 121),
|
|
1666
|
+
VArMaxQ2: this.applyScaleFactor(this.extractValue(buffer, 9, 'int16'), scaleFactors.VArMax_SF, 'int16', 'Max Reactive Power Q2', 9, 121),
|
|
1667
|
+
VArMaxQ3: this.applyScaleFactor(this.extractValue(buffer, 10, 'int16'), scaleFactors.VArMax_SF, 'int16', 'Max Reactive Power Q3', 10, 121),
|
|
1668
|
+
VArMaxQ4: this.applyScaleFactor(this.extractValue(buffer, 11, 'int16'), scaleFactors.VArMax_SF, 'int16', 'Max Reactive Power Q4', 11, 121),
|
|
1631
1669
|
VArMax_SF: scaleFactors.VArMax_SF,
|
|
1632
1670
|
// Ramp rate settings - Offset 12
|
|
1633
|
-
WGra: this.applyScaleFactor(
|
|
1671
|
+
WGra: this.applyScaleFactor(this.extractValue(buffer, 12, 'uint16'), scaleFactors.WGra_SF, 'uint16', 'Power Ramp Rate', 12, 121),
|
|
1634
1672
|
WGra_SF: scaleFactors.WGra_SF,
|
|
1635
1673
|
// Power factor settings - Offsets 13-16
|
|
1636
|
-
PFMinQ1: this.applyScaleFactor(
|
|
1637
|
-
PFMinQ2: this.applyScaleFactor(
|
|
1638
|
-
PFMinQ3: this.applyScaleFactor(
|
|
1639
|
-
PFMinQ4: this.applyScaleFactor(
|
|
1674
|
+
PFMinQ1: this.applyScaleFactor(this.extractValue(buffer, 13, 'int16'), scaleFactors.PFMin_SF, 'int16', 'Min Power Factor Q1', 13, 121),
|
|
1675
|
+
PFMinQ2: this.applyScaleFactor(this.extractValue(buffer, 14, 'int16'), scaleFactors.PFMin_SF, 'int16', 'Min Power Factor Q2', 14, 121),
|
|
1676
|
+
PFMinQ3: this.applyScaleFactor(this.extractValue(buffer, 15, 'int16'), scaleFactors.PFMin_SF, 'int16', 'Min Power Factor Q3', 15, 121),
|
|
1677
|
+
PFMinQ4: this.applyScaleFactor(this.extractValue(buffer, 16, 'int16'), scaleFactors.PFMin_SF, 'int16', 'Min Power Factor Q4', 16, 121),
|
|
1640
1678
|
PFMin_SF: scaleFactors.PFMin_SF,
|
|
1641
1679
|
// Other settings - Offsets 17-21
|
|
1642
1680
|
VArAct: vArActRaw,
|
|
1643
1681
|
ClcTotVA: clcTotVARaw,
|
|
1644
|
-
MaxRmpRte: this.applyScaleFactor(
|
|
1682
|
+
MaxRmpRte: this.applyScaleFactor(this.extractValue(buffer, 19, 'uint16'), scaleFactors.MaxRmpRte_SF, 'uint16', 'Max Ramp Rate', 19, 121),
|
|
1645
1683
|
MaxRmpRte_SF: scaleFactors.MaxRmpRte_SF,
|
|
1646
|
-
ECPNomHz: this.applyScaleFactor(
|
|
1684
|
+
ECPNomHz: this.applyScaleFactor(this.extractValue(buffer, 20, 'uint16'), scaleFactors.ECPNomHz_SF, 'uint16', 'Nominal Frequency', 20, 121),
|
|
1647
1685
|
ECPNomHz_SF: scaleFactors.ECPNomHz_SF,
|
|
1648
1686
|
ConnPh: connPhRaw
|
|
1649
1687
|
};
|
|
1688
|
+
console.debug('[Model 121] Inverter Settings:', settings);
|
|
1689
|
+
return settings;
|
|
1650
1690
|
}
|
|
1651
1691
|
catch (error) {
|
|
1652
1692
|
console.error(`Error reading inverter settings: ${error}`);
|
|
@@ -1662,34 +1702,35 @@ class SunspecModbusClient {
|
|
|
1662
1702
|
console.log('Controls model 123 not found');
|
|
1663
1703
|
return null;
|
|
1664
1704
|
}
|
|
1665
|
-
const baseAddr = model.address;
|
|
1666
1705
|
try {
|
|
1667
|
-
// Read
|
|
1706
|
+
// Read entire model block in a single Modbus call
|
|
1707
|
+
const buffer = await this.readModelBlock(model);
|
|
1708
|
+
// Extract scale factors from buffer (offsets 21-23)
|
|
1668
1709
|
const scaleFactors = {
|
|
1669
|
-
WMaxLimPct_SF:
|
|
1670
|
-
OutPFSet_SF:
|
|
1671
|
-
VArPct_SF:
|
|
1710
|
+
WMaxLimPct_SF: this.extractValue(buffer, 21, 'int16'),
|
|
1711
|
+
OutPFSet_SF: this.extractValue(buffer, 22, 'int16'),
|
|
1712
|
+
VArPct_SF: this.extractValue(buffer, 23, 'int16')
|
|
1672
1713
|
};
|
|
1673
1714
|
this.logRegisterRead(123, 21, 'WMaxLimPct_SF', scaleFactors.WMaxLimPct_SF, 'int16');
|
|
1674
1715
|
this.logRegisterRead(123, 22, 'OutPFSet_SF', scaleFactors.OutPFSet_SF, 'int16');
|
|
1675
1716
|
this.logRegisterRead(123, 23, 'VArPct_SF', scaleFactors.VArPct_SF, 'int16');
|
|
1676
|
-
//
|
|
1677
|
-
const connWinTmsRaw =
|
|
1678
|
-
const connRvrtTmsRaw =
|
|
1679
|
-
const connRaw =
|
|
1680
|
-
const wMaxLimPctWinTmsRaw =
|
|
1681
|
-
const wMaxLimPctRvrtTmsRaw =
|
|
1682
|
-
const wMaxLimPctRmpTmsRaw =
|
|
1683
|
-
const wMaxLimEnaRaw =
|
|
1684
|
-
const outPFSetWinTmsRaw =
|
|
1685
|
-
const outPFSetRvrtTmsRaw =
|
|
1686
|
-
const outPFSetRmpTmsRaw =
|
|
1687
|
-
const outPFSetEnaRaw =
|
|
1688
|
-
const vArPctWinTmsRaw =
|
|
1689
|
-
const vArPctRvrtTmsRaw =
|
|
1690
|
-
const vArPctRmpTmsRaw =
|
|
1691
|
-
const vArPctModRaw =
|
|
1692
|
-
const vArPctEnaRaw =
|
|
1717
|
+
// Extract non-scaled fields from buffer
|
|
1718
|
+
const connWinTmsRaw = this.extractValue(buffer, 0, 'uint16');
|
|
1719
|
+
const connRvrtTmsRaw = this.extractValue(buffer, 1, 'uint16');
|
|
1720
|
+
const connRaw = this.extractValue(buffer, 2, 'uint16');
|
|
1721
|
+
const wMaxLimPctWinTmsRaw = this.extractValue(buffer, 4, 'uint16');
|
|
1722
|
+
const wMaxLimPctRvrtTmsRaw = this.extractValue(buffer, 5, 'uint16');
|
|
1723
|
+
const wMaxLimPctRmpTmsRaw = this.extractValue(buffer, 6, 'uint16');
|
|
1724
|
+
const wMaxLimEnaRaw = this.extractValue(buffer, 7, 'uint16');
|
|
1725
|
+
const outPFSetWinTmsRaw = this.extractValue(buffer, 9, 'uint16');
|
|
1726
|
+
const outPFSetRvrtTmsRaw = this.extractValue(buffer, 10, 'uint16');
|
|
1727
|
+
const outPFSetRmpTmsRaw = this.extractValue(buffer, 11, 'uint16');
|
|
1728
|
+
const outPFSetEnaRaw = this.extractValue(buffer, 12, 'uint16');
|
|
1729
|
+
const vArPctWinTmsRaw = this.extractValue(buffer, 16, 'uint16');
|
|
1730
|
+
const vArPctRvrtTmsRaw = this.extractValue(buffer, 17, 'uint16');
|
|
1731
|
+
const vArPctRmpTmsRaw = this.extractValue(buffer, 18, 'uint16');
|
|
1732
|
+
const vArPctModRaw = this.extractValue(buffer, 19, 'uint16');
|
|
1733
|
+
const vArPctEnaRaw = this.extractValue(buffer, 20, 'uint16');
|
|
1693
1734
|
this.logRegisterRead(123, 0, 'Conn_WinTms', connWinTmsRaw, 'uint16');
|
|
1694
1735
|
this.logRegisterRead(123, 1, 'Conn_RvrtTms', connRvrtTmsRaw, 'uint16');
|
|
1695
1736
|
this.logRegisterRead(123, 2, 'Conn', connRaw, 'enum16');
|
|
@@ -1715,23 +1756,23 @@ class SunspecModbusClient {
|
|
|
1715
1756
|
Conn_RvrtTms: connRvrtTmsRaw,
|
|
1716
1757
|
Conn: connRaw,
|
|
1717
1758
|
// Power limit control - Offsets 3-7
|
|
1718
|
-
WMaxLimPct: this.applyScaleFactor(
|
|
1759
|
+
WMaxLimPct: this.applyScaleFactor(this.extractValue(buffer, 3, 'uint16'), scaleFactors.WMaxLimPct_SF, 'uint16', 'Power Limit Percentage', 3, 123),
|
|
1719
1760
|
WMaxLimPct_SF: scaleFactors.WMaxLimPct_SF,
|
|
1720
1761
|
WMaxLimPct_WinTms: wMaxLimPctWinTmsRaw,
|
|
1721
1762
|
WMaxLimPct_RvrtTms: wMaxLimPctRvrtTmsRaw,
|
|
1722
1763
|
WMaxLimPct_RmpTms: wMaxLimPctRmpTmsRaw,
|
|
1723
1764
|
WMaxLim_Ena: wMaxLimEnaRaw,
|
|
1724
1765
|
// Power factor control - Offsets 8-12
|
|
1725
|
-
OutPFSet: this.applyScaleFactor(
|
|
1766
|
+
OutPFSet: this.applyScaleFactor(this.extractValue(buffer, 8, 'int16'), scaleFactors.OutPFSet_SF, 'int16', 'Output Power Factor Set', 8, 123),
|
|
1726
1767
|
OutPFSet_SF: scaleFactors.OutPFSet_SF,
|
|
1727
1768
|
OutPFSet_WinTms: outPFSetWinTmsRaw,
|
|
1728
1769
|
OutPFSet_RvrtTms: outPFSetRvrtTmsRaw,
|
|
1729
1770
|
OutPFSet_RmpTms: outPFSetRmpTmsRaw,
|
|
1730
1771
|
OutPFSet_Ena: outPFSetEnaRaw,
|
|
1731
1772
|
// Reactive power control - Offsets 13-20
|
|
1732
|
-
VArWMaxPct: this.applyScaleFactor(
|
|
1733
|
-
VArMaxPct: this.applyScaleFactor(
|
|
1734
|
-
VArAvalPct: this.applyScaleFactor(
|
|
1773
|
+
VArWMaxPct: this.applyScaleFactor(this.extractValue(buffer, 13, 'int16'), scaleFactors.VArPct_SF, 'int16', 'Reactive Power at Max Power %', 13, 123),
|
|
1774
|
+
VArMaxPct: this.applyScaleFactor(this.extractValue(buffer, 14, 'int16'), scaleFactors.VArPct_SF, 'int16', 'Max Reactive Power %', 14, 123),
|
|
1775
|
+
VArAvalPct: this.applyScaleFactor(this.extractValue(buffer, 15, 'int16'), scaleFactors.VArPct_SF, 'int16', 'Available Reactive Power %', 15, 123),
|
|
1735
1776
|
VArPct_SF: scaleFactors.VArPct_SF,
|
|
1736
1777
|
VArPct_WinTms: vArPctWinTmsRaw,
|
|
1737
1778
|
VArPct_RvrtTms: vArPctRvrtTmsRaw,
|
|
@@ -1739,6 +1780,7 @@ class SunspecModbusClient {
|
|
|
1739
1780
|
VArPct_Mod: vArPctModRaw,
|
|
1740
1781
|
VArPct_Ena: vArPctEnaRaw
|
|
1741
1782
|
};
|
|
1783
|
+
console.debug('[Model 123] Inverter Controls:', controls);
|
|
1742
1784
|
return controls;
|
|
1743
1785
|
}
|
|
1744
1786
|
catch (error) {
|