@enyo-energy/sunspec-sdk 0.0.38 → 0.0.40

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.
@@ -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 all scale factors first using fault-tolerant reader
457
- console.log(`Reading Inverter Data from Model ${model.id} at base address: ${baseAddr}`);
458
- const scaleFactors = await this.readInverterScaleFactors(baseAddr);
459
- // Read values using fault-tolerant reader with proper data types
460
- // Read raw voltage values to check for unimplemented phases
461
- const voltageANRaw = await this.readRegisterValue(baseAddr + 10, 1, 'uint16');
462
- const voltageBNRaw = await this.readRegisterValue(baseAddr + 11, 1, 'uint16');
463
- const voltageCNRaw = await this.readRegisterValue(baseAddr + 12, 1, 'uint16');
464
- // Read raw values for fields that need IIFEs removed
465
- const acCurrentRaw = await this.readRegisterValue(baseAddr + 2, 1, 'uint16');
466
- const acPowerRaw = await this.readRegisterValue(baseAddr + 14, 1, 'int16');
467
- const dcCurrentRaw = await this.readRegisterValue(baseAddr + 27, 1, 'uint16');
468
- const dcVoltageRaw = await this.readRegisterValue(baseAddr + 28, 1, 'uint16');
469
- const dcPowerRaw = await this.readRegisterValue(baseAddr + 30, 1, 'int16');
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(await this.readRegisterValue(baseAddr + 3, 1, 'uint16'), scaleFactors.A_SF, 'uint16', 'Phase A Current', 3, 103),
477
- phaseBCurrent: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 4, 1, 'uint16'), scaleFactors.A_SF, 'uint16', 'Phase B Current', 4, 103),
478
- phaseCCurrent: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 5, 1, 'uint16'), scaleFactors.A_SF, 'uint16', 'Phase C Current', 5, 103),
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(await this.readRegisterValue(baseAddr + 7, 1, 'uint16'), scaleFactors.V_SF, 'uint16', 'Voltage AB', 7, 103),
481
- voltageBC: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 8, 1, 'uint16'), scaleFactors.V_SF, 'uint16', 'Voltage BC', 8, 103),
482
- voltageCA: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 9, 1, 'uint16'), scaleFactors.V_SF, 'uint16', 'Voltage CA', 9, 103),
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(await this.readRegisterValue(baseAddr + 18, 1, 'uint16'), scaleFactors.VA_SF, 'uint16', 'Apparent Power', 18, 103),
489
- reactivePower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 20, 1, 'int16'), scaleFactors.VAr_SF, 'int16', 'Reactive Power', 20, 103),
490
- powerFactor: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 22, 1, 'int16'), scaleFactors.PF_SF, 'int16', 'Power Factor', 22, 103),
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(await this.readRegisterValue(baseAddr + 16, 1, 'uint16'), scaleFactors.Hz_SF, 'uint16', 'Frequency', 16, 103),
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(await this.readRegisterValue(baseAddr + 32, 1, 'int16'), scaleFactors.Tmp_SF, 'int16', 'Cabinet Temperature', 32, 103),
499
- heatSinkTemperature: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 34, 1, 'int16'), scaleFactors.Tmp_SF, 'int16', 'Heat Sink Temperature', 34, 103),
500
- transformerTemperature: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 35, 1, 'int16'), scaleFactors.Tmp_SF, 'int16', 'Transformer Temperature', 35, 103),
501
- otherTemperature: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 36, 1, 'int16'), scaleFactors.Tmp_SF, 'int16', 'Other Temperature', 36, 103),
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: await this.readRegisterValue(baseAddr + 38, 1, 'uint16'),
504
- vendorState: await this.readRegisterValue(baseAddr + 39, 1, 'uint16'),
538
+ operatingState: this.extractValue(buffer, 38, 'uint16'),
539
+ vendorState: this.extractValue(buffer, 39, 'uint16'),
505
540
  // Event bitfields - Offsets 40-51
506
- events: await this.readRegisterValue(baseAddr + 40, 2, 'uint32'),
507
- events2: await this.readRegisterValue(baseAddr + 42, 2, 'uint32'),
508
- vendorEvents1: await this.readRegisterValue(baseAddr + 44, 2, 'uint32'),
509
- vendorEvents2: await this.readRegisterValue(baseAddr + 46, 2, 'uint32'),
510
- vendorEvents3: await this.readRegisterValue(baseAddr + 48, 2, 'uint32'),
511
- vendorEvents4: await this.readRegisterValue(baseAddr + 50, 2, 'uint32')
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,7 +555,7 @@ 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 = await this.readRegisterValue(baseAddr + 24, 2, 'uint32');
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)
@@ -536,20 +571,19 @@ export class SunspecModbusClient {
536
571
  * Read single phase inverter data (Model 101)
537
572
  */
538
573
  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
574
  try {
543
- console.log(`Reading Single-Phase Inverter Data from Model 101 at base address: ${baseAddr}`);
544
- // Read scale factors for single phase model
575
+ console.log(`Reading Single-Phase Inverter Data from Model 101 at base address: ${model.address}`);
576
+ // Read entire model block in a single Modbus call
577
+ const buffer = await this.readModelBlock(model);
578
+ // Extract scale factors from buffer
545
579
  const scaleFactors = {
546
- A_SF: await this.readRegisterValue(baseAddr + 6, 1, 'int16'),
547
- V_SF: await this.readRegisterValue(baseAddr + 13, 1, 'int16'),
548
- W_SF: await this.readRegisterValue(baseAddr + 10, 1, 'int16'),
549
- Hz_SF: await this.readRegisterValue(baseAddr + 12, 1, 'int16'),
550
- DCA_SF: await this.readRegisterValue(baseAddr + 18, 1, 'int16'),
551
- DCV_SF: await this.readRegisterValue(baseAddr + 19, 1, 'int16'),
552
- DCW_SF: await this.readRegisterValue(baseAddr + 21, 1, 'int16')
580
+ A_SF: this.extractValue(buffer, 6, 'int16'),
581
+ V_SF: this.extractValue(buffer, 13, 'int16'),
582
+ W_SF: this.extractValue(buffer, 10, 'int16'),
583
+ Hz_SF: this.extractValue(buffer, 12, 'int16'),
584
+ DCA_SF: this.extractValue(buffer, 18, 'int16'),
585
+ DCV_SF: this.extractValue(buffer, 19, 'int16'),
586
+ DCW_SF: this.extractValue(buffer, 21, 'int16')
553
587
  };
554
588
  this.logRegisterRead(101, 6, 'A_SF', scaleFactors.A_SF, 'int16');
555
589
  this.logRegisterRead(101, 13, 'V_SF', scaleFactors.V_SF, 'int16');
@@ -558,15 +592,15 @@ export class SunspecModbusClient {
558
592
  this.logRegisterRead(101, 18, 'DCA_SF', scaleFactors.DCA_SF, 'int16');
559
593
  this.logRegisterRead(101, 19, 'DCV_SF', scaleFactors.DCV_SF, 'int16');
560
594
  this.logRegisterRead(101, 21, 'DCW_SF', scaleFactors.DCW_SF, 'int16');
561
- // Read raw values
562
- const acCurrentRaw = await this.readRegisterValue(baseAddr + 2, 1, 'uint16');
563
- const voltageRaw = await this.readRegisterValue(baseAddr + 7, 1, 'uint16');
564
- const acPowerRaw = await this.readRegisterValue(baseAddr + 9, 1, 'int16');
565
- const freqRaw = await this.readRegisterValue(baseAddr + 11, 1, 'uint16');
566
- const dcCurrentRaw = await this.readRegisterValue(baseAddr + 14, 1, 'uint16');
567
- const dcVoltageRaw = await this.readRegisterValue(baseAddr + 15, 1, 'uint16');
568
- const dcPowerRaw = await this.readRegisterValue(baseAddr + 20, 1, 'int16');
569
- const stateRaw = await this.readRegisterValue(baseAddr + 24, 1, 'uint16');
595
+ // Extract raw values from buffer
596
+ const acCurrentRaw = this.extractValue(buffer, 2, 'uint16');
597
+ const voltageRaw = this.extractValue(buffer, 7, 'uint16');
598
+ const acPowerRaw = this.extractValue(buffer, 9, 'int16');
599
+ const freqRaw = this.extractValue(buffer, 11, 'uint16');
600
+ const dcCurrentRaw = this.extractValue(buffer, 14, 'uint16');
601
+ const dcVoltageRaw = this.extractValue(buffer, 15, 'uint16');
602
+ const dcPowerRaw = this.extractValue(buffer, 20, 'int16');
603
+ const stateRaw = this.extractValue(buffer, 24, 'uint16');
570
604
  this.logRegisterRead(101, 24, 'Operating State', stateRaw, 'enum16');
571
605
  return {
572
606
  blockNumber: 101,
@@ -586,22 +620,22 @@ export class SunspecModbusClient {
586
620
  }
587
621
  }
588
622
  /**
589
- * Read inverter scale factors
623
+ * Extract inverter scale factors from a pre-read model buffer
590
624
  */
591
- async readInverterScaleFactors(baseAddr) {
625
+ extractInverterScaleFactors(buffer) {
592
626
  const scaleFactors = {
593
- A_SF: await this.readRegisterValue(baseAddr + 6, 1, 'int16'), // Offset 6
594
- V_SF: await this.readRegisterValue(baseAddr + 13, 1, 'int16'), // Offset 13
595
- W_SF: await this.readRegisterValue(baseAddr + 15, 1, 'int16'), // Offset 15
596
- Hz_SF: await this.readRegisterValue(baseAddr + 17, 1, 'int16'), // Offset 17
597
- VA_SF: await this.readRegisterValue(baseAddr + 19, 1, 'int16'), // Offset 19
598
- VAr_SF: await this.readRegisterValue(baseAddr + 21, 1, 'int16'), // Offset 21
599
- PF_SF: await this.readRegisterValue(baseAddr + 23, 1, 'int16'), // Offset 23
600
- WH_SF: await this.readRegisterValue(baseAddr + 26, 1, 'int16'), // Offset 26
601
- DCA_SF: await this.readRegisterValue(baseAddr + 28, 1, 'int16'), // Offset 28
602
- DCV_SF: await this.readRegisterValue(baseAddr + 29, 1, 'int16'), // Offset 29
603
- DCW_SF: await this.readRegisterValue(baseAddr + 31, 1, 'int16'), // Offset 31
604
- Tmp_SF: await this.readRegisterValue(baseAddr + 36, 1, 'int16') // Offset 36
627
+ A_SF: this.extractValue(buffer, 6, 'int16'), // Offset 6
628
+ V_SF: this.extractValue(buffer, 13, 'int16'), // Offset 13
629
+ W_SF: this.extractValue(buffer, 15, 'int16'), // Offset 15
630
+ Hz_SF: this.extractValue(buffer, 17, 'int16'), // Offset 17
631
+ VA_SF: this.extractValue(buffer, 19, 'int16'), // Offset 19
632
+ VAr_SF: this.extractValue(buffer, 21, 'int16'), // Offset 21
633
+ PF_SF: this.extractValue(buffer, 23, 'int16'), // Offset 23
634
+ WH_SF: this.extractValue(buffer, 26, 'int16'), // Offset 26
635
+ DCA_SF: this.extractValue(buffer, 28, 'int16'), // Offset 28
636
+ DCV_SF: this.extractValue(buffer, 29, 'int16'), // Offset 29
637
+ DCW_SF: this.extractValue(buffer, 31, 'int16'), // Offset 31
638
+ Tmp_SF: this.extractValue(buffer, 36, 'int16') // Offset 36
605
639
  };
606
640
  this.logRegisterRead(103, 6, 'A_SF', scaleFactors.A_SF, 'int16');
607
641
  this.logRegisterRead(103, 13, 'V_SF', scaleFactors.V_SF, 'int16');
@@ -663,38 +697,89 @@ export class SunspecModbusClient {
663
697
  *
664
698
  * @returns Object containing all scale factors or null if model not found
665
699
  */
700
+ /**
701
+ * Extract MPPT scale factors from a pre-read model buffer
702
+ */
703
+ extractMPPTScaleFactors(buffer) {
704
+ const scaleFactors = {
705
+ DCA_SF: this.extractValue(buffer, 2, 'int16'),
706
+ DCV_SF: this.extractValue(buffer, 3, 'int16'),
707
+ DCW_SF: this.extractValue(buffer, 4, 'int16'),
708
+ DCWH_SF: this.extractValue(buffer, 5, 'int16'),
709
+ };
710
+ this.logRegisterRead(160, 2, 'DCA_SF', scaleFactors.DCA_SF, 'int16');
711
+ this.logRegisterRead(160, 3, 'DCV_SF', scaleFactors.DCV_SF, 'int16');
712
+ this.logRegisterRead(160, 4, 'DCW_SF', scaleFactors.DCW_SF, 'int16');
713
+ this.logRegisterRead(160, 5, 'DCWH_SF', scaleFactors.DCWH_SF, 'int16');
714
+ return scaleFactors;
715
+ }
666
716
  async readMPPTScaleFactors() {
667
717
  const model = this.findModel(SunspecModelId.MPPT);
668
718
  if (!model) {
669
719
  console.log('MPPT model 160 not found');
670
720
  return null;
671
721
  }
672
- const baseAddr = model.address;
673
722
  try {
674
- // MPPT modules are repeating blocks, calculate offset for specific module
675
- const moduleAddr = baseAddr;
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;
723
+ const buffer = await this.readModelBlock(model);
724
+ return this.extractMPPTScaleFactors(buffer);
692
725
  }
693
726
  catch (error) {
694
727
  console.error(`Error reading MPPT scale factors: ${error}`);
695
728
  return null;
696
729
  }
697
730
  }
731
+ /**
732
+ * Extract MPPT module data from a pre-read model buffer
733
+ */
734
+ extractMPPTModuleData(buffer, model, moduleId, scaleFactors) {
735
+ const fixedBlockScaleFactorsOffset = 10;
736
+ const moduleSize = 20;
737
+ const offset = (moduleId - 1) * moduleSize;
738
+ const moduleOffset = fixedBlockScaleFactorsOffset + offset;
739
+ const id = this.extractValue(buffer, moduleOffset, 'uint16');
740
+ const idString = this.extractValue(buffer, moduleOffset + 1, 'string', 8);
741
+ const dcCurrentRaw = this.extractValue(buffer, moduleOffset + 9, 'uint16');
742
+ const dcVoltageRaw = this.extractValue(buffer, moduleOffset + 10, 'uint16');
743
+ const dcPowerRaw = this.extractValue(buffer, moduleOffset + 11, 'uint16');
744
+ const dcEnergyRaw = this.extractValue(buffer, moduleOffset + 12, 'uint32', 2);
745
+ const temperatureRaw = this.extractValue(buffer, moduleOffset + 16, 'int16');
746
+ const dcst = this.extractValue(buffer, moduleOffset + 17, 'uint16');
747
+ this.logRegisterRead(160, 0, `MPPT ${moduleId} ID`, id, 'uint16');
748
+ this.logRegisterRead(160, 1, `MPPT ${moduleId} String ID`, idString, 'string');
749
+ if (this.isUnimplementedValue(id, 'uint16') &&
750
+ this.isUnimplementedValue(dcCurrentRaw, 'uint16') &&
751
+ this.isUnimplementedValue(dcVoltageRaw, 'uint16') &&
752
+ this.isUnimplementedValue(dcPowerRaw, 'uint16')) {
753
+ console.log(`MPPT module ${moduleId} appears to be unconnected (all values are 0xFFFF)`);
754
+ return null;
755
+ }
756
+ const temperatureScaleFactor = -1;
757
+ this.logRegisterRead(160, 12, `MPPT ${moduleId} DC Energy`, dcEnergyRaw, 'acc32');
758
+ const timestampRaw = this.extractValue(buffer, moduleOffset + 14, 'uint32', 2);
759
+ this.logRegisterRead(160, 14, `MPPT ${moduleId} Timestamp`, timestampRaw, 'uint32');
760
+ this.logRegisterRead(160, 17, `MPPT ${moduleId} Operating State`, dcst, 'enum16');
761
+ return {
762
+ blockNumber: 160,
763
+ blockAddress: model.address,
764
+ blockLength: model.length,
765
+ id: id,
766
+ stringId: idString,
767
+ dcCurrent: this.applyScaleFactor(dcCurrentRaw, scaleFactors.DCA_SF, 'uint16', `MPPT ${moduleId} DC Current`, 9, 160),
768
+ dcCurrentSF: scaleFactors.DCA_SF,
769
+ dcVoltage: this.applyScaleFactor(dcVoltageRaw, scaleFactors.DCV_SF, 'uint16', `MPPT ${moduleId} DC Voltage`, 10, 160),
770
+ dcVoltageSF: scaleFactors.DCV_SF,
771
+ dcPower: this.applyScaleFactor(dcPowerRaw, scaleFactors.DCW_SF, 'uint16', `MPPT ${moduleId} DC Power`, 11, 160),
772
+ dcPowerSF: scaleFactors.DCW_SF,
773
+ dcEnergy: !this.isUnimplementedValue(dcEnergyRaw, 'acc32')
774
+ ? dcEnergyRaw * Math.pow(10, scaleFactors.DCWH_SF)
775
+ : undefined,
776
+ dcEnergySF: scaleFactors.DCWH_SF,
777
+ timestamp: timestampRaw,
778
+ temperature: this.applyScaleFactor(temperatureRaw, temperatureScaleFactor, 'int16', `MPPT ${moduleId} Temperature`, 16, 160),
779
+ temperatureSF: temperatureScaleFactor,
780
+ operatingState: dcst,
781
+ };
782
+ }
698
783
  /**
699
784
  * Read MPPT data from Model 160
700
785
  */
@@ -704,68 +789,11 @@ export class SunspecModbusClient {
704
789
  console.log('MPPT model 160 not found');
705
790
  return null;
706
791
  }
707
- const baseAddr = model.address;
708
792
  try {
709
- const fixedBlockScaleFactorsOffset = 10;
710
- // MPPT modules are repeating blocks, calculate offset for specific module
711
- const moduleSize = 20; // Size of each MPPT module based on CSV (offsets 0-20)
712
- const offset = (moduleId - 1) * moduleSize;
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;
793
+ // Read entire model block in a single Modbus call
794
+ const buffer = await this.readModelBlock(model);
795
+ const scaleFactors = this.extractMPPTScaleFactors(buffer);
796
+ return this.extractMPPTModuleData(buffer, model, moduleId, scaleFactors);
769
797
  }
770
798
  catch (error) {
771
799
  console.error(`Error reading MPPT data for module ${moduleId}: ${error}`);
@@ -777,24 +805,18 @@ export class SunspecModbusClient {
777
805
  */
778
806
  async readAllMPPTData() {
779
807
  const mpptData = [];
780
- // Find the MPPT model first
781
808
  const model = this.findModel(SunspecModelId.MPPT);
782
809
  if (!model) {
783
810
  console.log('MPPT model 160 not found');
784
811
  return [];
785
812
  }
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
813
  try {
794
- const baseAddr = model.address;
795
- // Register 8 contains the number of modules
796
- const count = await this.readRegisterValue(baseAddr + 8, 1, 'uint16');
797
- // Validate the module count
814
+ // Read entire model block in a single Modbus call
815
+ const buffer = await this.readModelBlock(model);
816
+ const scaleFactors = this.extractMPPTScaleFactors(buffer);
817
+ // Read the module count from register 8
818
+ let moduleCount = 4; // Default fallback value
819
+ const count = this.extractValue(buffer, 8, 'uint16');
798
820
  if (!this.isUnimplementedValue(count, 'uint16') && count > 0 && count <= 20) {
799
821
  moduleCount = count;
800
822
  console.log(`MPPT module count from register 8: ${moduleCount}`);
@@ -802,27 +824,25 @@ export class SunspecModbusClient {
802
824
  else {
803
825
  console.log(`Invalid or unimplemented module count (${count}), using default: ${moduleCount}`);
804
826
  }
805
- }
806
- catch (error) {
807
- console.error(`Error reading module count from register 8: ${error}, using default: ${moduleCount}`);
808
- }
809
- // Read each MPPT module based on the actual count
810
- for (let i = 1; i <= moduleCount; i++) {
811
- try {
812
- const data = await this.readMPPTData(i);
813
- console.log(`MPPT ${i} has id ${data?.id} (${data?.stringId}) with ${data?.dcPower}W`);
814
- // Only include if we got valid data (not null) and it has actual values
815
- if (data &&
816
- (data.dcCurrent !== undefined ||
817
- data.dcVoltage !== undefined ||
818
- data.dcPower !== undefined)) {
819
- mpptData.push(data);
827
+ // Extract each MPPT module from the same buffer
828
+ for (let i = 1; i <= moduleCount; i++) {
829
+ try {
830
+ const data = this.extractMPPTModuleData(buffer, model, i, scaleFactors);
831
+ console.log(`MPPT ${i} has id ${data?.id} (${data?.stringId}) with ${data?.dcPower}W`);
832
+ if (data &&
833
+ (data.dcCurrent !== undefined ||
834
+ data.dcVoltage !== undefined ||
835
+ data.dcPower !== undefined)) {
836
+ mpptData.push(data);
837
+ }
838
+ }
839
+ catch (error) {
840
+ console.debug(`Could not read MPPT module ${i}: ${error}`);
820
841
  }
821
842
  }
822
- catch (error) {
823
- console.debug(`Could not read MPPT module ${i}: ${error}`);
824
- // Continue to try other modules
825
- }
843
+ }
844
+ catch (error) {
845
+ console.error(`Error reading all MPPT data: ${error}`);
826
846
  }
827
847
  return mpptData;
828
848
  }
@@ -919,22 +939,22 @@ export class SunspecModbusClient {
919
939
  }
920
940
  }
921
941
  /**
922
- * Read Model 802 scale factors (offsets 52-63)
942
+ * Extract Model 802 scale factors from a pre-read buffer (offsets 52-63)
923
943
  */
924
- async readBatteryBaseScaleFactors(baseAddr) {
944
+ extractBatteryBaseScaleFactors(buffer) {
925
945
  const scaleFactors = {
926
- AHRtg_SF: await this.readRegisterValue(baseAddr + 52, 1, 'int16'),
927
- WHRtg_SF: await this.readRegisterValue(baseAddr + 53, 1, 'int16'),
928
- WChaDisChaMax_SF: await this.readRegisterValue(baseAddr + 54, 1, 'int16'),
929
- DisChaRte_SF: await this.readRegisterValue(baseAddr + 55, 1, 'int16'),
930
- SoC_SF: await this.readRegisterValue(baseAddr + 56, 1, 'int16'),
931
- DoD_SF: await this.readRegisterValue(baseAddr + 57, 1, 'int16'),
932
- SoH_SF: await this.readRegisterValue(baseAddr + 58, 1, 'int16'),
933
- V_SF: await this.readRegisterValue(baseAddr + 59, 1, 'int16'),
934
- CellV_SF: await this.readRegisterValue(baseAddr + 60, 1, 'int16'),
935
- A_SF: await this.readRegisterValue(baseAddr + 61, 1, 'int16'),
936
- AMax_SF: await this.readRegisterValue(baseAddr + 62, 1, 'int16'),
937
- W_SF: await this.readRegisterValue(baseAddr + 63, 1, 'int16'),
946
+ AHRtg_SF: this.extractValue(buffer, 52, 'int16'),
947
+ WHRtg_SF: this.extractValue(buffer, 53, 'int16'),
948
+ WChaDisChaMax_SF: this.extractValue(buffer, 54, 'int16'),
949
+ DisChaRte_SF: this.extractValue(buffer, 55, 'int16'),
950
+ SoC_SF: this.extractValue(buffer, 56, 'int16'),
951
+ DoD_SF: this.extractValue(buffer, 57, 'int16'),
952
+ SoH_SF: this.extractValue(buffer, 58, 'int16'),
953
+ V_SF: this.extractValue(buffer, 59, 'int16'),
954
+ CellV_SF: this.extractValue(buffer, 60, 'int16'),
955
+ A_SF: this.extractValue(buffer, 61, 'int16'),
956
+ AMax_SF: this.extractValue(buffer, 62, 'int16'),
957
+ W_SF: this.extractValue(buffer, 63, 'int16'),
938
958
  };
939
959
  this.logRegisterRead(802, 52, 'AHRtg_SF', scaleFactors.AHRtg_SF, 'int16');
940
960
  this.logRegisterRead(802, 53, 'WHRtg_SF', scaleFactors.WHRtg_SF, 'int16');
@@ -959,51 +979,52 @@ export class SunspecModbusClient {
959
979
  console.log('Battery Base model 802 not found');
960
980
  return null;
961
981
  }
962
- const baseAddr = model.address;
963
- console.log(`Reading Battery Base Data from Model 802 at base address: ${baseAddr}`);
982
+ console.log(`Reading Battery Base Data from Model 802 at base address: ${model.address}`);
964
983
  try {
965
- // Read scale factors first (offsets 52-63)
966
- const sf = await this.readBatteryBaseScaleFactors(baseAddr);
967
- // Read raw values
968
- const ahRtgRaw = await this.readRegisterValue(baseAddr + 2, 1, 'uint16');
969
- const whRtgRaw = await this.readRegisterValue(baseAddr + 3, 1, 'uint16');
970
- const wChaRteMaxRaw = await this.readRegisterValue(baseAddr + 4, 1, 'uint16');
971
- const wDisChaRteMaxRaw = await this.readRegisterValue(baseAddr + 5, 1, 'uint16');
972
- const disChaRteRaw = await this.readRegisterValue(baseAddr + 6, 1, 'uint16');
973
- const soCMaxRaw = await this.readRegisterValue(baseAddr + 7, 1, 'uint16');
974
- const soCMinRaw = await this.readRegisterValue(baseAddr + 8, 1, 'uint16');
975
- const soCRsvMaxRaw = await this.readRegisterValue(baseAddr + 9, 1, 'uint16');
976
- const soCRsvMinRaw = await this.readRegisterValue(baseAddr + 10, 1, 'uint16');
977
- const soCRaw = await this.readRegisterValue(baseAddr + 11, 1, 'uint16');
978
- const doDRaw = await this.readRegisterValue(baseAddr + 12, 1, 'uint16');
979
- const soHRaw = await this.readRegisterValue(baseAddr + 13, 1, 'uint16');
980
- const nCycRaw = await this.readRegisterValue(baseAddr + 14, 2, 'uint32');
981
- const chaStRaw = await this.readRegisterValue(baseAddr + 16, 1, 'uint16');
982
- const locRemCtlRaw = await this.readRegisterValue(baseAddr + 17, 1, 'uint16');
983
- const typRaw = await this.readRegisterValue(baseAddr + 21, 1, 'uint16');
984
- const stateRaw = await this.readRegisterValue(baseAddr + 22, 1, 'uint16');
985
- const evt1Raw = await this.readRegisterValue(baseAddr + 26, 2, 'uint32');
986
- const evt2Raw = await this.readRegisterValue(baseAddr + 28, 2, 'uint32');
987
- const evtVnd1Raw = await this.readRegisterValue(baseAddr + 30, 2, 'uint32');
988
- const evtVnd2Raw = await this.readRegisterValue(baseAddr + 32, 2, 'uint32');
989
- const vRaw = await this.readRegisterValue(baseAddr + 34, 1, 'uint16');
990
- const vMaxRaw = await this.readRegisterValue(baseAddr + 35, 1, 'uint16');
991
- const vMinRaw = await this.readRegisterValue(baseAddr + 36, 1, 'uint16');
992
- const cellVMaxRaw = await this.readRegisterValue(baseAddr + 37, 1, 'uint16');
993
- const cellVMaxStrRaw = await this.readRegisterValue(baseAddr + 38, 1, 'uint16');
994
- const cellVMaxModRaw = await this.readRegisterValue(baseAddr + 39, 1, 'uint16');
995
- const cellVMinRaw = await this.readRegisterValue(baseAddr + 40, 1, 'uint16');
996
- const cellVMinStrRaw = await this.readRegisterValue(baseAddr + 41, 1, 'uint16');
997
- const cellVMinModRaw = await this.readRegisterValue(baseAddr + 42, 1, 'uint16');
998
- const cellVAvgRaw = await this.readRegisterValue(baseAddr + 43, 1, 'uint16');
999
- const aRaw = await this.readRegisterValue(baseAddr + 44, 1, 'int16');
1000
- const aChaMaxRaw = await this.readRegisterValue(baseAddr + 45, 1, 'uint16');
1001
- const aDisChaMaxRaw = await this.readRegisterValue(baseAddr + 46, 1, 'uint16');
1002
- const wRaw = await this.readRegisterValue(baseAddr + 47, 1, 'int16');
1003
- const reqInvStateRaw = await this.readRegisterValue(baseAddr + 48, 1, 'uint16');
1004
- const reqWRaw = await this.readRegisterValue(baseAddr + 49, 1, 'int16');
1005
- const setOpRaw = await this.readRegisterValue(baseAddr + 50, 1, 'uint16');
1006
- const setInvStateRaw = await this.readRegisterValue(baseAddr + 51, 1, 'uint16');
984
+ // Read entire model block in a single Modbus call
985
+ const buffer = await this.readModelBlock(model);
986
+ // Extract scale factors from buffer (offsets 52-63)
987
+ const sf = this.extractBatteryBaseScaleFactors(buffer);
988
+ // Extract raw values from buffer
989
+ const ahRtgRaw = this.extractValue(buffer, 2, 'uint16');
990
+ const whRtgRaw = this.extractValue(buffer, 3, 'uint16');
991
+ const wChaRteMaxRaw = this.extractValue(buffer, 4, 'uint16');
992
+ const wDisChaRteMaxRaw = this.extractValue(buffer, 5, 'uint16');
993
+ const disChaRteRaw = this.extractValue(buffer, 6, 'uint16');
994
+ const soCMaxRaw = this.extractValue(buffer, 7, 'uint16');
995
+ const soCMinRaw = this.extractValue(buffer, 8, 'uint16');
996
+ const soCRsvMaxRaw = this.extractValue(buffer, 9, 'uint16');
997
+ const soCRsvMinRaw = this.extractValue(buffer, 10, 'uint16');
998
+ const soCRaw = this.extractValue(buffer, 11, 'uint16');
999
+ const doDRaw = this.extractValue(buffer, 12, 'uint16');
1000
+ const soHRaw = this.extractValue(buffer, 13, 'uint16');
1001
+ const nCycRaw = this.extractValue(buffer, 14, 'uint32', 2);
1002
+ const chaStRaw = this.extractValue(buffer, 16, 'uint16');
1003
+ const locRemCtlRaw = this.extractValue(buffer, 17, 'uint16');
1004
+ const typRaw = this.extractValue(buffer, 21, 'uint16');
1005
+ const stateRaw = this.extractValue(buffer, 22, 'uint16');
1006
+ const evt1Raw = this.extractValue(buffer, 26, 'uint32', 2);
1007
+ const evt2Raw = this.extractValue(buffer, 28, 'uint32', 2);
1008
+ const evtVnd1Raw = this.extractValue(buffer, 30, 'uint32', 2);
1009
+ const evtVnd2Raw = this.extractValue(buffer, 32, 'uint32', 2);
1010
+ const vRaw = this.extractValue(buffer, 34, 'uint16');
1011
+ const vMaxRaw = this.extractValue(buffer, 35, 'uint16');
1012
+ const vMinRaw = this.extractValue(buffer, 36, 'uint16');
1013
+ const cellVMaxRaw = this.extractValue(buffer, 37, 'uint16');
1014
+ const cellVMaxStrRaw = this.extractValue(buffer, 38, 'uint16');
1015
+ const cellVMaxModRaw = this.extractValue(buffer, 39, 'uint16');
1016
+ const cellVMinRaw = this.extractValue(buffer, 40, 'uint16');
1017
+ const cellVMinStrRaw = this.extractValue(buffer, 41, 'uint16');
1018
+ const cellVMinModRaw = this.extractValue(buffer, 42, 'uint16');
1019
+ const cellVAvgRaw = this.extractValue(buffer, 43, 'uint16');
1020
+ const aRaw = this.extractValue(buffer, 44, 'int16');
1021
+ const aChaMaxRaw = this.extractValue(buffer, 45, 'uint16');
1022
+ const aDisChaMaxRaw = this.extractValue(buffer, 46, 'uint16');
1023
+ const wRaw = this.extractValue(buffer, 47, 'int16');
1024
+ const reqInvStateRaw = this.extractValue(buffer, 48, 'uint16');
1025
+ const reqWRaw = this.extractValue(buffer, 49, 'int16');
1026
+ const setOpRaw = this.extractValue(buffer, 50, 'uint16');
1027
+ const setInvStateRaw = this.extractValue(buffer, 51, 'uint16');
1007
1028
  // Map enum fields
1008
1029
  const chaStName = this.mapBatteryChargeState(chaStRaw);
1009
1030
  const typName = this.mapBatteryType(typRaw);
@@ -1103,22 +1124,23 @@ export class SunspecModbusClient {
1103
1124
  console.log('No battery model found');
1104
1125
  return null;
1105
1126
  }
1106
- const baseAddr = model.address;
1107
- console.log(`Reading Battery Data from Model ${model.id} at base address: ${baseAddr}`);
1127
+ console.log(`Reading Battery Data from Model ${model.id} at base address: ${model.address}`);
1108
1128
  try {
1109
1129
  if (model.id === 124) {
1110
1130
  // Model 124: Basic Storage Controls
1111
1131
  console.log('Using Model 124 (Basic Storage Controls)');
1112
- // Read scale factors first (offsets 18-25)
1132
+ // Read entire model block in a single Modbus call
1133
+ const buffer = await this.readModelBlock(model);
1134
+ // Extract scale factors from buffer (offsets 18-25)
1113
1135
  const scaleFactors = {
1114
- WChaMax_SF: await this.readRegisterValue(baseAddr + 18, 1, 'int16'),
1115
- WChaDisChaGra_SF: await this.readRegisterValue(baseAddr + 19, 1, 'int16'),
1116
- VAChaMax_SF: await this.readRegisterValue(baseAddr + 20, 1, 'int16'),
1117
- MinRsvPct_SF: await this.readRegisterValue(baseAddr + 21, 1, 'int16'),
1118
- ChaState_SF: await this.readRegisterValue(baseAddr + 22, 1, 'int16'),
1119
- StorAval_SF: await this.readRegisterValue(baseAddr + 23, 1, 'int16'),
1120
- InBatV_SF: await this.readRegisterValue(baseAddr + 24, 1, 'int16'),
1121
- InOutWRte_SF: await this.readRegisterValue(baseAddr + 25, 1, 'int16')
1136
+ WChaMax_SF: this.extractValue(buffer, 18, 'int16'),
1137
+ WChaDisChaGra_SF: this.extractValue(buffer, 19, 'int16'),
1138
+ VAChaMax_SF: this.extractValue(buffer, 20, 'int16'),
1139
+ MinRsvPct_SF: this.extractValue(buffer, 21, 'int16'),
1140
+ ChaState_SF: this.extractValue(buffer, 22, 'int16'),
1141
+ StorAval_SF: this.extractValue(buffer, 23, 'int16'),
1142
+ InBatV_SF: this.extractValue(buffer, 24, 'int16'),
1143
+ InOutWRte_SF: this.extractValue(buffer, 25, 'int16')
1122
1144
  };
1123
1145
  this.logRegisterRead(124, 18, 'WChaMax_SF', scaleFactors.WChaMax_SF, 'int16');
1124
1146
  this.logRegisterRead(124, 19, 'WChaDisChaGra_SF', scaleFactors.WChaDisChaGra_SF, 'int16');
@@ -1128,23 +1150,23 @@ export class SunspecModbusClient {
1128
1150
  this.logRegisterRead(124, 23, 'StorAval_SF', scaleFactors.StorAval_SF, 'int16');
1129
1151
  this.logRegisterRead(124, 24, 'InBatV_SF', scaleFactors.InBatV_SF, 'int16');
1130
1152
  this.logRegisterRead(124, 25, 'InOutWRte_SF', scaleFactors.InOutWRte_SF, 'int16');
1131
- // Read raw values
1132
- const wChaMaxRaw = await this.readRegisterValue(baseAddr + 2, 1, 'uint16');
1133
- const wChaGraRaw = await this.readRegisterValue(baseAddr + 3, 1, 'uint16');
1134
- const wDisChaGraRaw = await this.readRegisterValue(baseAddr + 4, 1, 'uint16');
1135
- const storCtlModRaw = await this.readRegisterValue(baseAddr + 5, 1, 'uint16');
1136
- const vaChaMaxRaw = await this.readRegisterValue(baseAddr + 6, 1, 'uint16');
1137
- const minRsvPctRaw = await this.readRegisterValue(baseAddr + 7, 1, 'uint16');
1138
- const chaStateRaw = await this.readRegisterValue(baseAddr + 8, 1, 'uint16');
1139
- const storAvalRaw = await this.readRegisterValue(baseAddr + 9, 1, 'uint16');
1140
- const inBatVRaw = await this.readRegisterValue(baseAddr + 10, 1, 'uint16');
1141
- const chaStRaw = await this.readRegisterValue(baseAddr + 11, 1, 'uint16');
1142
- const outWRteRaw = await this.readRegisterValue(baseAddr + 12, 1, 'int16');
1143
- const inWRteRaw = await this.readRegisterValue(baseAddr + 13, 1, 'int16');
1144
- const inOutWRteWinTmsRaw = await this.readRegisterValue(baseAddr + 14, 1, 'uint16');
1145
- const inOutWRteRvrtTmsRaw = await this.readRegisterValue(baseAddr + 15, 1, 'uint16');
1146
- const inOutWRteRmpTmsRaw = await this.readRegisterValue(baseAddr + 16, 1, 'uint16');
1147
- const chaGriSetRaw = await this.readRegisterValue(baseAddr + 17, 1, 'uint16');
1153
+ // Extract raw values from buffer
1154
+ const wChaMaxRaw = this.extractValue(buffer, 2, 'uint16');
1155
+ const wChaGraRaw = this.extractValue(buffer, 3, 'uint16');
1156
+ const wDisChaGraRaw = this.extractValue(buffer, 4, 'uint16');
1157
+ const storCtlModRaw = this.extractValue(buffer, 5, 'uint16');
1158
+ const vaChaMaxRaw = this.extractValue(buffer, 6, 'uint16');
1159
+ const minRsvPctRaw = this.extractValue(buffer, 7, 'uint16');
1160
+ const chaStateRaw = this.extractValue(buffer, 8, 'uint16');
1161
+ const storAvalRaw = this.extractValue(buffer, 9, 'uint16');
1162
+ const inBatVRaw = this.extractValue(buffer, 10, 'uint16');
1163
+ const chaStRaw = this.extractValue(buffer, 11, 'uint16');
1164
+ const outWRteRaw = this.extractValue(buffer, 12, 'int16');
1165
+ const inWRteRaw = this.extractValue(buffer, 13, 'int16');
1166
+ const inOutWRteWinTmsRaw = this.extractValue(buffer, 14, 'uint16');
1167
+ const inOutWRteRvrtTmsRaw = this.extractValue(buffer, 15, 'uint16');
1168
+ const inOutWRteRmpTmsRaw = this.extractValue(buffer, 16, 'uint16');
1169
+ const chaGriSetRaw = this.extractValue(buffer, 17, 'uint16');
1148
1170
  // Map charge state and log non-scaled fields
1149
1171
  const chaStName = this.mapBatteryChargeState(chaStRaw);
1150
1172
  this.logRegisterRead(124, 5, 'storCtlMod', storCtlModRaw, 'bitfield16');
@@ -1363,25 +1385,26 @@ export class SunspecModbusClient {
1363
1385
  console.log('Battery model 124 not found');
1364
1386
  return null;
1365
1387
  }
1366
- const baseAddr = model.address;
1367
- console.log(`Reading Battery Controls from Model 124 at base address: ${baseAddr}`);
1388
+ console.log(`Reading Battery Controls from Model 124 at base address: ${model.address}`);
1368
1389
  try {
1369
- // Read scale factors
1390
+ // Read entire model block in a single Modbus call
1391
+ const buffer = await this.readModelBlock(model);
1392
+ // Extract scale factors from buffer
1370
1393
  const scaleFactors = {
1371
- WChaMax_SF: await this.readRegisterValue(baseAddr + 18, 1, 'int16'),
1372
- MinRsvPct_SF: await this.readRegisterValue(baseAddr + 21, 1, 'int16'),
1373
- InOutWRte_SF: await this.readRegisterValue(baseAddr + 25, 1, 'int16')
1394
+ WChaMax_SF: this.extractValue(buffer, 18, 'int16'),
1395
+ MinRsvPct_SF: this.extractValue(buffer, 21, 'int16'),
1396
+ InOutWRte_SF: this.extractValue(buffer, 25, 'int16')
1374
1397
  };
1375
1398
  this.logRegisterRead(124, 18, 'WChaMax_SF', scaleFactors.WChaMax_SF, 'int16');
1376
1399
  this.logRegisterRead(124, 21, 'MinRsvPct_SF', scaleFactors.MinRsvPct_SF, 'int16');
1377
1400
  this.logRegisterRead(124, 25, 'InOutWRte_SF', scaleFactors.InOutWRte_SF, 'int16');
1378
- // Read raw values
1379
- const wChaMaxRaw = await this.readRegisterValue(baseAddr + 2, 1, 'uint16');
1380
- const storCtlModRaw = await this.readRegisterValue(baseAddr + 5, 1, 'uint16');
1381
- const minRsvPctRaw = await this.readRegisterValue(baseAddr + 7, 1, 'uint16');
1382
- const outWRteRaw = await this.readRegisterValue(baseAddr + 12, 1, 'int16');
1383
- const inWRteRaw = await this.readRegisterValue(baseAddr + 13, 1, 'int16');
1384
- const chaGriSetRaw = await this.readRegisterValue(baseAddr + 17, 1, 'uint16');
1401
+ // Extract raw values from buffer
1402
+ const wChaMaxRaw = this.extractValue(buffer, 2, 'uint16');
1403
+ const storCtlModRaw = this.extractValue(buffer, 5, 'uint16');
1404
+ const minRsvPctRaw = this.extractValue(buffer, 7, 'uint16');
1405
+ const outWRteRaw = this.extractValue(buffer, 12, 'int16');
1406
+ const inWRteRaw = this.extractValue(buffer, 13, 'int16');
1407
+ const chaGriSetRaw = this.extractValue(buffer, 17, 'uint16');
1385
1408
  this.logRegisterRead(124, 5, 'storCtlMod', storCtlModRaw, 'bitfield16');
1386
1409
  this.logRegisterRead(124, 17, 'chaGriSet', chaGriSetRaw, 'enum16');
1387
1410
  // Apply scale factors and return control settings
@@ -1414,8 +1437,7 @@ export class SunspecModbusClient {
1414
1437
  console.log('No meter model found');
1415
1438
  return null;
1416
1439
  }
1417
- const baseAddr = model.address;
1418
- console.log(`Reading Meter Data from Model ${model.id} at base address: ${baseAddr}`);
1440
+ console.log(`Reading Meter Data from Model ${model.id} at base address: ${model.address}`);
1419
1441
  try {
1420
1442
  // Different meter models have different register offsets
1421
1443
  console.log(`Meter is Model ${model.id}`);
@@ -1458,18 +1480,20 @@ export class SunspecModbusClient {
1458
1480
  importOffset = 46; // TotWhImp - Total Wh Imported (acc32)
1459
1481
  energySFOffset = 54; // TotWh_SF - Total Energy scale factor
1460
1482
  }
1461
- // Read scale factors
1462
- const powerSF = await this.readRegisterValue(baseAddr + powerSFOffset, 1, 'int16');
1463
- const freqSF = await this.readRegisterValue(baseAddr + freqSFOffset, 1, 'int16');
1464
- const energySF = await this.readRegisterValue(baseAddr + energySFOffset, 1, 'int16');
1483
+ // Read entire model block in a single Modbus call
1484
+ const buffer = await this.readModelBlock(model);
1485
+ // Extract scale factors from buffer
1486
+ const powerSF = this.extractValue(buffer, powerSFOffset, 'int16');
1487
+ const freqSF = this.extractValue(buffer, freqSFOffset, 'int16');
1488
+ const energySF = this.extractValue(buffer, energySFOffset, 'int16');
1465
1489
  this.logRegisterRead(model.id, powerSFOffset, 'W_SF', powerSF, 'int16');
1466
1490
  this.logRegisterRead(model.id, freqSFOffset, 'Hz_SF', freqSF, 'int16');
1467
1491
  this.logRegisterRead(model.id, energySFOffset, 'TotWh_SF', energySF, 'int16');
1468
- // Read raw values
1469
- const powerRaw = await this.readRegisterValue(baseAddr + powerOffset, 1, 'int16');
1470
- const freqRaw = await this.readRegisterValue(baseAddr + freqOffset, 1, 'uint16');
1471
- const exportRaw = await this.readRegisterValue(baseAddr + exportOffset, 2, 'uint32');
1472
- const importRaw = await this.readRegisterValue(baseAddr + importOffset, 2, 'uint32');
1492
+ // Extract raw values from buffer
1493
+ const powerRaw = this.extractValue(buffer, powerOffset, 'int16');
1494
+ const freqRaw = this.extractValue(buffer, freqOffset, 'uint16');
1495
+ const exportRaw = this.extractValue(buffer, exportOffset, 'uint32', 2);
1496
+ const importRaw = this.extractValue(buffer, importOffset, 'uint32', 2);
1473
1497
  this.logRegisterRead(model.id, exportOffset, 'TotWhExp', exportRaw, 'acc32');
1474
1498
  this.logRegisterRead(model.id, importOffset, 'TotWhImp', importRaw, 'acc32');
1475
1499
  // Calculate final values with scale factors
@@ -1499,28 +1523,26 @@ export class SunspecModbusClient {
1499
1523
  console.error('Common block model not found');
1500
1524
  return null;
1501
1525
  }
1502
- const baseAddr = model.address + 2; // Skip ID and Length
1503
- console.log(`Reading Common Block - Model address: ${model.address}, Base address for data: ${baseAddr}`);
1526
+ console.log(`Reading Common Block - Model address: ${model.address}`);
1504
1527
  try {
1505
- // Read all strings using fault-tolerant reader with proper data type conversion
1506
- // Common block offsets are relative to the start of the data (after ID and Length)
1507
- const manufacturerAddr = baseAddr; // Offset 0-15 (16 registers) from data start
1508
- const modelAddr = baseAddr + 16; // Offset 16-31 (16 registers) from data start
1509
- const optionsAddr = baseAddr + 32; // Offset 32-39 (8 registers) from data start
1510
- const versionAddr = baseAddr + 40; // Offset 40-47 (8 registers) from data start
1511
- const serialAddr = baseAddr + 48; // Offset 48-63 (16 registers) from data start
1512
- const deviceAddrAddr = baseAddr + 64; // Offset 64 from data start
1513
- const manufacturer = await this.readRegisterValue(manufacturerAddr, 16, 'string');
1528
+ // Read entire model block in a single Modbus call
1529
+ const buffer = await this.readModelBlock(model);
1530
+ // Common block offsets are relative to the model start (after ID and Length header,
1531
+ // but readModelBlock reads from model.address which includes the data area).
1532
+ // The offsets below are relative to the data start within the model block.
1533
+ // Since model.address points to the first data register (after the 2-register header
1534
+ // is already accounted for in model discovery), offset 0 = first data register.
1535
+ const manufacturer = this.extractValue(buffer, 2, 'string', 16);
1514
1536
  this.logRegisterRead(1, 0, 'Manufacturer', manufacturer, 'string');
1515
- const modelName = await this.readRegisterValue(modelAddr, 16, 'string');
1537
+ const modelName = this.extractValue(buffer, 18, 'string', 16);
1516
1538
  this.logRegisterRead(1, 16, 'Model', modelName, 'string');
1517
- const options = await this.readRegisterValue(optionsAddr, 8, 'string');
1539
+ const options = this.extractValue(buffer, 34, 'string', 8);
1518
1540
  this.logRegisterRead(1, 32, 'Options', options, 'string');
1519
- const version = await this.readRegisterValue(versionAddr, 8, 'string');
1541
+ const version = this.extractValue(buffer, 42, 'string', 8);
1520
1542
  this.logRegisterRead(1, 40, 'Version', version, 'string');
1521
- const serialNumber = await this.readRegisterValue(serialAddr, 16, 'string');
1543
+ const serialNumber = this.extractValue(buffer, 50, 'string', 16);
1522
1544
  this.logRegisterRead(1, 48, 'Serial Number', serialNumber, 'string');
1523
- const deviceAddress = await this.readRegisterValue(deviceAddrAddr, 1, 'uint16');
1545
+ const deviceAddress = this.extractValue(buffer, 66, 'uint16');
1524
1546
  this.logRegisterRead(1, 64, 'Device Address', deviceAddress, 'uint16');
1525
1547
  return {
1526
1548
  manufacturer,
@@ -1570,20 +1592,21 @@ export class SunspecModbusClient {
1570
1592
  console.log('Settings model 121 not found');
1571
1593
  return null;
1572
1594
  }
1573
- const baseAddr = model.address;
1574
1595
  try {
1575
- // Read scale factors first (offsets 22-31)
1596
+ // Read entire model block in a single Modbus call
1597
+ const buffer = await this.readModelBlock(model);
1598
+ // Extract scale factors from buffer (offsets 22-31)
1576
1599
  const scaleFactors = {
1577
- WMax_SF: await this.readRegisterValue(baseAddr + 22, 1, 'int16'),
1578
- VRef_SF: await this.readRegisterValue(baseAddr + 23, 1, 'int16'),
1579
- VRefOfs_SF: await this.readRegisterValue(baseAddr + 24, 1, 'int16'),
1580
- VMinMax_SF: await this.readRegisterValue(baseAddr + 25, 1, 'int16'),
1581
- VAMax_SF: await this.readRegisterValue(baseAddr + 26, 1, 'int16'),
1582
- VArMax_SF: await this.readRegisterValue(baseAddr + 27, 1, 'int16'),
1583
- WGra_SF: await this.readRegisterValue(baseAddr + 28, 1, 'int16'),
1584
- PFMin_SF: await this.readRegisterValue(baseAddr + 29, 1, 'int16'),
1585
- MaxRmpRte_SF: await this.readRegisterValue(baseAddr + 30, 1, 'int16'),
1586
- ECPNomHz_SF: await this.readRegisterValue(baseAddr + 31, 1, 'int16')
1600
+ WMax_SF: this.extractValue(buffer, 22, 'int16'),
1601
+ VRef_SF: this.extractValue(buffer, 23, 'int16'),
1602
+ VRefOfs_SF: this.extractValue(buffer, 24, 'int16'),
1603
+ VMinMax_SF: this.extractValue(buffer, 25, 'int16'),
1604
+ VAMax_SF: this.extractValue(buffer, 26, 'int16'),
1605
+ VArMax_SF: this.extractValue(buffer, 27, 'int16'),
1606
+ WGra_SF: this.extractValue(buffer, 28, 'int16'),
1607
+ PFMin_SF: this.extractValue(buffer, 29, 'int16'),
1608
+ MaxRmpRte_SF: this.extractValue(buffer, 30, 'int16'),
1609
+ ECPNomHz_SF: this.extractValue(buffer, 31, 'int16')
1587
1610
  };
1588
1611
  this.logRegisterRead(121, 22, 'WMax_SF', scaleFactors.WMax_SF, 'int16');
1589
1612
  this.logRegisterRead(121, 23, 'VRef_SF', scaleFactors.VRef_SF, 'int16');
@@ -1595,10 +1618,10 @@ export class SunspecModbusClient {
1595
1618
  this.logRegisterRead(121, 29, 'PFMin_SF', scaleFactors.PFMin_SF, 'int16');
1596
1619
  this.logRegisterRead(121, 30, 'MaxRmpRte_SF', scaleFactors.MaxRmpRte_SF, 'int16');
1597
1620
  this.logRegisterRead(121, 31, 'ECPNomHz_SF', scaleFactors.ECPNomHz_SF, 'int16');
1598
- // Read non-scaled fields first for logging
1599
- const vArActRaw = await this.readRegisterValue(baseAddr + 17, 1, 'uint16');
1600
- const clcTotVARaw = await this.readRegisterValue(baseAddr + 18, 1, 'uint16');
1601
- const connPhRaw = await this.readRegisterValue(baseAddr + 21, 1, 'uint16');
1621
+ // Extract non-scaled fields from buffer
1622
+ const vArActRaw = this.extractValue(buffer, 17, 'uint16');
1623
+ const clcTotVARaw = this.extractValue(buffer, 18, 'uint16');
1624
+ const connPhRaw = this.extractValue(buffer, 21, 'uint16');
1602
1625
  this.logRegisterRead(121, 17, 'VArAct', vArActRaw, 'enum16');
1603
1626
  this.logRegisterRead(121, 18, 'ClcTotVA', clcTotVARaw, 'enum16');
1604
1627
  this.logRegisterRead(121, 21, 'ConnPh', connPhRaw, 'enum16');
@@ -1607,40 +1630,40 @@ export class SunspecModbusClient {
1607
1630
  blockAddress: model.address,
1608
1631
  blockLength: model.length,
1609
1632
  // Power settings - Offset 2
1610
- WMax: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 2, 1, 'uint16'), scaleFactors.WMax_SF, 'uint16', 'Max Power', 2, 121),
1633
+ WMax: this.applyScaleFactor(this.extractValue(buffer, 2, 'uint16'), scaleFactors.WMax_SF, 'uint16', 'Max Power', 2, 121),
1611
1634
  WMax_SF: scaleFactors.WMax_SF,
1612
1635
  // Voltage settings - Offsets 3-6
1613
- VRef: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 3, 1, 'uint16'), scaleFactors.VRef_SF, 'uint16', 'Voltage Reference', 3, 121),
1636
+ VRef: this.applyScaleFactor(this.extractValue(buffer, 3, 'uint16'), scaleFactors.VRef_SF, 'uint16', 'Voltage Reference', 3, 121),
1614
1637
  VRef_SF: scaleFactors.VRef_SF,
1615
- VRefOfs: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 4, 1, 'int16'), scaleFactors.VRefOfs_SF, 'int16', 'Voltage Reference Offset', 4, 121),
1638
+ VRefOfs: this.applyScaleFactor(this.extractValue(buffer, 4, 'int16'), scaleFactors.VRefOfs_SF, 'int16', 'Voltage Reference Offset', 4, 121),
1616
1639
  VRefOfs_SF: scaleFactors.VRefOfs_SF,
1617
- VMax: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 5, 1, 'uint16'), scaleFactors.VMinMax_SF, 'uint16', 'Max Voltage', 5, 121),
1618
- VMin: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 6, 1, 'uint16'), scaleFactors.VMinMax_SF, 'uint16', 'Min Voltage', 6, 121),
1640
+ VMax: this.applyScaleFactor(this.extractValue(buffer, 5, 'uint16'), scaleFactors.VMinMax_SF, 'uint16', 'Max Voltage', 5, 121),
1641
+ VMin: this.applyScaleFactor(this.extractValue(buffer, 6, 'uint16'), scaleFactors.VMinMax_SF, 'uint16', 'Min Voltage', 6, 121),
1619
1642
  VMinMax_SF: scaleFactors.VMinMax_SF,
1620
1643
  // Apparent power settings - Offset 7
1621
- VAMax: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 7, 1, 'uint16'), scaleFactors.VAMax_SF, 'uint16', 'Max Apparent Power', 7, 121),
1644
+ VAMax: this.applyScaleFactor(this.extractValue(buffer, 7, 'uint16'), scaleFactors.VAMax_SF, 'uint16', 'Max Apparent Power', 7, 121),
1622
1645
  VAMax_SF: scaleFactors.VAMax_SF,
1623
1646
  // Reactive power settings - Offsets 8-11
1624
- VArMaxQ1: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 8, 1, 'int16'), scaleFactors.VArMax_SF, 'int16', 'Max Reactive Power Q1', 8, 121),
1625
- VArMaxQ2: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 9, 1, 'int16'), scaleFactors.VArMax_SF, 'int16', 'Max Reactive Power Q2', 9, 121),
1626
- VArMaxQ3: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 10, 1, 'int16'), scaleFactors.VArMax_SF, 'int16', 'Max Reactive Power Q3', 10, 121),
1627
- VArMaxQ4: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 11, 1, 'int16'), scaleFactors.VArMax_SF, 'int16', 'Max Reactive Power Q4', 11, 121),
1647
+ VArMaxQ1: this.applyScaleFactor(this.extractValue(buffer, 8, 'int16'), scaleFactors.VArMax_SF, 'int16', 'Max Reactive Power Q1', 8, 121),
1648
+ VArMaxQ2: this.applyScaleFactor(this.extractValue(buffer, 9, 'int16'), scaleFactors.VArMax_SF, 'int16', 'Max Reactive Power Q2', 9, 121),
1649
+ VArMaxQ3: this.applyScaleFactor(this.extractValue(buffer, 10, 'int16'), scaleFactors.VArMax_SF, 'int16', 'Max Reactive Power Q3', 10, 121),
1650
+ VArMaxQ4: this.applyScaleFactor(this.extractValue(buffer, 11, 'int16'), scaleFactors.VArMax_SF, 'int16', 'Max Reactive Power Q4', 11, 121),
1628
1651
  VArMax_SF: scaleFactors.VArMax_SF,
1629
1652
  // Ramp rate settings - Offset 12
1630
- WGra: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 12, 1, 'uint16'), scaleFactors.WGra_SF, 'uint16', 'Power Ramp Rate', 12, 121),
1653
+ WGra: this.applyScaleFactor(this.extractValue(buffer, 12, 'uint16'), scaleFactors.WGra_SF, 'uint16', 'Power Ramp Rate', 12, 121),
1631
1654
  WGra_SF: scaleFactors.WGra_SF,
1632
1655
  // Power factor settings - Offsets 13-16
1633
- PFMinQ1: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 13, 1, 'int16'), scaleFactors.PFMin_SF, 'int16', 'Min Power Factor Q1', 13, 121),
1634
- PFMinQ2: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 14, 1, 'int16'), scaleFactors.PFMin_SF, 'int16', 'Min Power Factor Q2', 14, 121),
1635
- PFMinQ3: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 15, 1, 'int16'), scaleFactors.PFMin_SF, 'int16', 'Min Power Factor Q3', 15, 121),
1636
- PFMinQ4: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 16, 1, 'int16'), scaleFactors.PFMin_SF, 'int16', 'Min Power Factor Q4', 16, 121),
1656
+ PFMinQ1: this.applyScaleFactor(this.extractValue(buffer, 13, 'int16'), scaleFactors.PFMin_SF, 'int16', 'Min Power Factor Q1', 13, 121),
1657
+ PFMinQ2: this.applyScaleFactor(this.extractValue(buffer, 14, 'int16'), scaleFactors.PFMin_SF, 'int16', 'Min Power Factor Q2', 14, 121),
1658
+ PFMinQ3: this.applyScaleFactor(this.extractValue(buffer, 15, 'int16'), scaleFactors.PFMin_SF, 'int16', 'Min Power Factor Q3', 15, 121),
1659
+ PFMinQ4: this.applyScaleFactor(this.extractValue(buffer, 16, 'int16'), scaleFactors.PFMin_SF, 'int16', 'Min Power Factor Q4', 16, 121),
1637
1660
  PFMin_SF: scaleFactors.PFMin_SF,
1638
1661
  // Other settings - Offsets 17-21
1639
1662
  VArAct: vArActRaw,
1640
1663
  ClcTotVA: clcTotVARaw,
1641
- MaxRmpRte: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 19, 1, 'uint16'), scaleFactors.MaxRmpRte_SF, 'uint16', 'Max Ramp Rate', 19, 121),
1664
+ MaxRmpRte: this.applyScaleFactor(this.extractValue(buffer, 19, 'uint16'), scaleFactors.MaxRmpRte_SF, 'uint16', 'Max Ramp Rate', 19, 121),
1642
1665
  MaxRmpRte_SF: scaleFactors.MaxRmpRte_SF,
1643
- ECPNomHz: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 20, 1, 'uint16'), scaleFactors.ECPNomHz_SF, 'uint16', 'Nominal Frequency', 20, 121),
1666
+ ECPNomHz: this.applyScaleFactor(this.extractValue(buffer, 20, 'uint16'), scaleFactors.ECPNomHz_SF, 'uint16', 'Nominal Frequency', 20, 121),
1644
1667
  ECPNomHz_SF: scaleFactors.ECPNomHz_SF,
1645
1668
  ConnPh: connPhRaw
1646
1669
  };
@@ -1659,34 +1682,35 @@ export class SunspecModbusClient {
1659
1682
  console.log('Controls model 123 not found');
1660
1683
  return null;
1661
1684
  }
1662
- const baseAddr = model.address;
1663
1685
  try {
1664
- // Read scale factors first (offsets 21-23)
1686
+ // Read entire model block in a single Modbus call
1687
+ const buffer = await this.readModelBlock(model);
1688
+ // Extract scale factors from buffer (offsets 21-23)
1665
1689
  const scaleFactors = {
1666
- WMaxLimPct_SF: await this.readRegisterValue(baseAddr + 21, 1, 'int16'),
1667
- OutPFSet_SF: await this.readRegisterValue(baseAddr + 22, 1, 'int16'),
1668
- VArPct_SF: await this.readRegisterValue(baseAddr + 23, 1, 'int16')
1690
+ WMaxLimPct_SF: this.extractValue(buffer, 21, 'int16'),
1691
+ OutPFSet_SF: this.extractValue(buffer, 22, 'int16'),
1692
+ VArPct_SF: this.extractValue(buffer, 23, 'int16')
1669
1693
  };
1670
1694
  this.logRegisterRead(123, 21, 'WMaxLimPct_SF', scaleFactors.WMaxLimPct_SF, 'int16');
1671
1695
  this.logRegisterRead(123, 22, 'OutPFSet_SF', scaleFactors.OutPFSet_SF, 'int16');
1672
1696
  this.logRegisterRead(123, 23, 'VArPct_SF', scaleFactors.VArPct_SF, 'int16');
1673
- // Read non-scaled fields
1674
- const connWinTmsRaw = await this.readRegisterValue(baseAddr, 1, 'uint16');
1675
- const connRvrtTmsRaw = await this.readRegisterValue(baseAddr + 1, 1, 'uint16');
1676
- const connRaw = await this.readRegisterValue(baseAddr + 2, 1, 'uint16');
1677
- const wMaxLimPctWinTmsRaw = await this.readRegisterValue(baseAddr + 4, 1, 'uint16');
1678
- const wMaxLimPctRvrtTmsRaw = await this.readRegisterValue(baseAddr + 5, 1, 'uint16');
1679
- const wMaxLimPctRmpTmsRaw = await this.readRegisterValue(baseAddr + 6, 1, 'uint16');
1680
- const wMaxLimEnaRaw = await this.readRegisterValue(baseAddr + 7, 1, 'uint16');
1681
- const outPFSetWinTmsRaw = await this.readRegisterValue(baseAddr + 9, 1, 'uint16');
1682
- const outPFSetRvrtTmsRaw = await this.readRegisterValue(baseAddr + 10, 1, 'uint16');
1683
- const outPFSetRmpTmsRaw = await this.readRegisterValue(baseAddr + 11, 1, 'uint16');
1684
- const outPFSetEnaRaw = await this.readRegisterValue(baseAddr + 12, 1, 'uint16');
1685
- const vArPctWinTmsRaw = await this.readRegisterValue(baseAddr + 16, 1, 'uint16');
1686
- const vArPctRvrtTmsRaw = await this.readRegisterValue(baseAddr + 17, 1, 'uint16');
1687
- const vArPctRmpTmsRaw = await this.readRegisterValue(baseAddr + 18, 1, 'uint16');
1688
- const vArPctModRaw = await this.readRegisterValue(baseAddr + 19, 1, 'uint16');
1689
- const vArPctEnaRaw = await this.readRegisterValue(baseAddr + 20, 1, 'uint16');
1697
+ // Extract non-scaled fields from buffer
1698
+ const connWinTmsRaw = this.extractValue(buffer, 0, 'uint16');
1699
+ const connRvrtTmsRaw = this.extractValue(buffer, 1, 'uint16');
1700
+ const connRaw = this.extractValue(buffer, 2, 'uint16');
1701
+ const wMaxLimPctWinTmsRaw = this.extractValue(buffer, 4, 'uint16');
1702
+ const wMaxLimPctRvrtTmsRaw = this.extractValue(buffer, 5, 'uint16');
1703
+ const wMaxLimPctRmpTmsRaw = this.extractValue(buffer, 6, 'uint16');
1704
+ const wMaxLimEnaRaw = this.extractValue(buffer, 7, 'uint16');
1705
+ const outPFSetWinTmsRaw = this.extractValue(buffer, 9, 'uint16');
1706
+ const outPFSetRvrtTmsRaw = this.extractValue(buffer, 10, 'uint16');
1707
+ const outPFSetRmpTmsRaw = this.extractValue(buffer, 11, 'uint16');
1708
+ const outPFSetEnaRaw = this.extractValue(buffer, 12, 'uint16');
1709
+ const vArPctWinTmsRaw = this.extractValue(buffer, 16, 'uint16');
1710
+ const vArPctRvrtTmsRaw = this.extractValue(buffer, 17, 'uint16');
1711
+ const vArPctRmpTmsRaw = this.extractValue(buffer, 18, 'uint16');
1712
+ const vArPctModRaw = this.extractValue(buffer, 19, 'uint16');
1713
+ const vArPctEnaRaw = this.extractValue(buffer, 20, 'uint16');
1690
1714
  this.logRegisterRead(123, 0, 'Conn_WinTms', connWinTmsRaw, 'uint16');
1691
1715
  this.logRegisterRead(123, 1, 'Conn_RvrtTms', connRvrtTmsRaw, 'uint16');
1692
1716
  this.logRegisterRead(123, 2, 'Conn', connRaw, 'enum16');
@@ -1712,23 +1736,23 @@ export class SunspecModbusClient {
1712
1736
  Conn_RvrtTms: connRvrtTmsRaw,
1713
1737
  Conn: connRaw,
1714
1738
  // Power limit control - Offsets 3-7
1715
- WMaxLimPct: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 3, 1, 'uint16'), scaleFactors.WMaxLimPct_SF, 'uint16', 'Power Limit Percentage', 3, 123),
1739
+ WMaxLimPct: this.applyScaleFactor(this.extractValue(buffer, 3, 'uint16'), scaleFactors.WMaxLimPct_SF, 'uint16', 'Power Limit Percentage', 3, 123),
1716
1740
  WMaxLimPct_SF: scaleFactors.WMaxLimPct_SF,
1717
1741
  WMaxLimPct_WinTms: wMaxLimPctWinTmsRaw,
1718
1742
  WMaxLimPct_RvrtTms: wMaxLimPctRvrtTmsRaw,
1719
1743
  WMaxLimPct_RmpTms: wMaxLimPctRmpTmsRaw,
1720
1744
  WMaxLim_Ena: wMaxLimEnaRaw,
1721
1745
  // Power factor control - Offsets 8-12
1722
- OutPFSet: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 8, 1, 'int16'), scaleFactors.OutPFSet_SF, 'int16', 'Output Power Factor Set', 8, 123),
1746
+ OutPFSet: this.applyScaleFactor(this.extractValue(buffer, 8, 'int16'), scaleFactors.OutPFSet_SF, 'int16', 'Output Power Factor Set', 8, 123),
1723
1747
  OutPFSet_SF: scaleFactors.OutPFSet_SF,
1724
1748
  OutPFSet_WinTms: outPFSetWinTmsRaw,
1725
1749
  OutPFSet_RvrtTms: outPFSetRvrtTmsRaw,
1726
1750
  OutPFSet_RmpTms: outPFSetRmpTmsRaw,
1727
1751
  OutPFSet_Ena: outPFSetEnaRaw,
1728
1752
  // Reactive power control - Offsets 13-20
1729
- VArWMaxPct: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 13, 1, 'int16'), scaleFactors.VArPct_SF, 'int16', 'Reactive Power at Max Power %', 13, 123),
1730
- VArMaxPct: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 14, 1, 'int16'), scaleFactors.VArPct_SF, 'int16', 'Max Reactive Power %', 14, 123),
1731
- VArAvalPct: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 15, 1, 'int16'), scaleFactors.VArPct_SF, 'int16', 'Available Reactive Power %', 15, 123),
1753
+ VArWMaxPct: this.applyScaleFactor(this.extractValue(buffer, 13, 'int16'), scaleFactors.VArPct_SF, 'int16', 'Reactive Power at Max Power %', 13, 123),
1754
+ VArMaxPct: this.applyScaleFactor(this.extractValue(buffer, 14, 'int16'), scaleFactors.VArPct_SF, 'int16', 'Max Reactive Power %', 14, 123),
1755
+ VArAvalPct: this.applyScaleFactor(this.extractValue(buffer, 15, 'int16'), scaleFactors.VArPct_SF, 'int16', 'Available Reactive Power %', 15, 123),
1732
1756
  VArPct_SF: scaleFactors.VArPct_SF,
1733
1757
  VArPct_WinTms: vArPctWinTmsRaw,
1734
1758
  VArPct_RvrtTms: vArPctRvrtTmsRaw,