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