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