@enyo-energy/sunspec-sdk 0.0.39 → 0.0.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,11 +558,12 @@ class SunspecModbusClient {
523
558
  this.logRegisterRead(103, 48, 'Vendor Events 3', data.vendorEvents3, 'bitfield32');
524
559
  this.logRegisterRead(103, 50, 'Vendor Events 4', data.vendorEvents4, 'bitfield32');
525
560
  // Read AC Energy (32-bit accumulator) - Offset 24-25
526
- const acEnergy = 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)
530
565
  : undefined;
566
+ console.debug('[Model 103] Inverter Data:', data);
531
567
  return data;
532
568
  }
533
569
  catch (error) {
@@ -539,20 +575,19 @@ class SunspecModbusClient {
539
575
  * Read single phase inverter data (Model 101)
540
576
  */
541
577
  async readSinglePhaseInverterData(model) {
542
- // Similar to 3-phase but with fewer phase-specific values
543
- // Implementation would be similar but simplified
544
- const baseAddr = model.address;
545
578
  try {
546
- console.log(`Reading Single-Phase Inverter Data from Model 101 at base address: ${baseAddr}`);
547
- // Read scale factors for single phase model
579
+ console.log(`Reading Single-Phase Inverter Data from Model 101 at base address: ${model.address}`);
580
+ // Read entire model block in a single Modbus call
581
+ const buffer = await this.readModelBlock(model);
582
+ // Extract scale factors from buffer
548
583
  const scaleFactors = {
549
- A_SF: 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')
584
+ A_SF: this.extractValue(buffer, 6, 'int16'),
585
+ V_SF: this.extractValue(buffer, 13, 'int16'),
586
+ W_SF: this.extractValue(buffer, 10, 'int16'),
587
+ Hz_SF: this.extractValue(buffer, 12, 'int16'),
588
+ DCA_SF: this.extractValue(buffer, 18, 'int16'),
589
+ DCV_SF: this.extractValue(buffer, 19, 'int16'),
590
+ DCW_SF: this.extractValue(buffer, 21, 'int16')
556
591
  };
557
592
  this.logRegisterRead(101, 6, 'A_SF', scaleFactors.A_SF, 'int16');
558
593
  this.logRegisterRead(101, 13, 'V_SF', scaleFactors.V_SF, 'int16');
@@ -561,17 +596,17 @@ class SunspecModbusClient {
561
596
  this.logRegisterRead(101, 18, 'DCA_SF', scaleFactors.DCA_SF, 'int16');
562
597
  this.logRegisterRead(101, 19, 'DCV_SF', scaleFactors.DCV_SF, 'int16');
563
598
  this.logRegisterRead(101, 21, 'DCW_SF', scaleFactors.DCW_SF, 'int16');
564
- // 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');
599
+ // Extract raw values from buffer
600
+ const acCurrentRaw = this.extractValue(buffer, 2, 'uint16');
601
+ const voltageRaw = this.extractValue(buffer, 7, 'uint16');
602
+ const acPowerRaw = this.extractValue(buffer, 9, 'int16');
603
+ const freqRaw = this.extractValue(buffer, 11, 'uint16');
604
+ const dcCurrentRaw = this.extractValue(buffer, 14, 'uint16');
605
+ const dcVoltageRaw = this.extractValue(buffer, 15, 'uint16');
606
+ const dcPowerRaw = this.extractValue(buffer, 20, 'int16');
607
+ const stateRaw = this.extractValue(buffer, 24, 'uint16');
573
608
  this.logRegisterRead(101, 24, 'Operating State', stateRaw, 'enum16');
574
- return {
609
+ const data = {
575
610
  blockNumber: 101,
576
611
  voltageAN: this.applyScaleFactor(voltageRaw, scaleFactors.V_SF, 'uint16', 'Voltage AN', 7, 101),
577
612
  acCurrent: this.applyScaleFactor(acCurrentRaw, scaleFactors.A_SF, 'uint16', 'AC Current', 2, 101),
@@ -582,6 +617,8 @@ class SunspecModbusClient {
582
617
  dcPower: this.applyScaleFactor(dcPowerRaw, scaleFactors.DCW_SF, 'int16', 'DC Power', 20, 101),
583
618
  operatingState: stateRaw
584
619
  };
620
+ console.debug('[Model 101] Single Phase Inverter Data:', data);
621
+ return data;
585
622
  }
586
623
  catch (error) {
587
624
  console.error(`Error reading single phase inverter data: ${error}`);
@@ -589,22 +626,22 @@ class SunspecModbusClient {
589
626
  }
590
627
  }
591
628
  /**
592
- * Read inverter scale factors
629
+ * Extract inverter scale factors from a pre-read model buffer
593
630
  */
594
- async readInverterScaleFactors(baseAddr) {
631
+ extractInverterScaleFactors(buffer) {
595
632
  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
633
+ A_SF: this.extractValue(buffer, 6, 'int16'), // Offset 6
634
+ V_SF: this.extractValue(buffer, 13, 'int16'), // Offset 13
635
+ W_SF: this.extractValue(buffer, 15, 'int16'), // Offset 15
636
+ Hz_SF: this.extractValue(buffer, 17, 'int16'), // Offset 17
637
+ VA_SF: this.extractValue(buffer, 19, 'int16'), // Offset 19
638
+ VAr_SF: this.extractValue(buffer, 21, 'int16'), // Offset 21
639
+ PF_SF: this.extractValue(buffer, 23, 'int16'), // Offset 23
640
+ WH_SF: this.extractValue(buffer, 26, 'int16'), // Offset 26
641
+ DCA_SF: this.extractValue(buffer, 28, 'int16'), // Offset 28
642
+ DCV_SF: this.extractValue(buffer, 29, 'int16'), // Offset 29
643
+ DCW_SF: this.extractValue(buffer, 31, 'int16'), // Offset 31
644
+ Tmp_SF: this.extractValue(buffer, 36, 'int16') // Offset 36
608
645
  };
609
646
  this.logRegisterRead(103, 6, 'A_SF', scaleFactors.A_SF, 'int16');
610
647
  this.logRegisterRead(103, 13, 'V_SF', scaleFactors.V_SF, 'int16');
@@ -666,38 +703,91 @@ class SunspecModbusClient {
666
703
  *
667
704
  * @returns Object containing all scale factors or null if model not found
668
705
  */
706
+ /**
707
+ * Extract MPPT scale factors from a pre-read model buffer
708
+ */
709
+ extractMPPTScaleFactors(buffer) {
710
+ const scaleFactors = {
711
+ DCA_SF: this.extractValue(buffer, 2, 'int16'),
712
+ DCV_SF: this.extractValue(buffer, 3, 'int16'),
713
+ DCW_SF: this.extractValue(buffer, 4, 'int16'),
714
+ DCWH_SF: this.extractValue(buffer, 5, 'int16'),
715
+ };
716
+ this.logRegisterRead(160, 2, 'DCA_SF', scaleFactors.DCA_SF, 'int16');
717
+ this.logRegisterRead(160, 3, 'DCV_SF', scaleFactors.DCV_SF, 'int16');
718
+ this.logRegisterRead(160, 4, 'DCW_SF', scaleFactors.DCW_SF, 'int16');
719
+ this.logRegisterRead(160, 5, 'DCWH_SF', scaleFactors.DCWH_SF, 'int16');
720
+ return scaleFactors;
721
+ }
669
722
  async readMPPTScaleFactors() {
670
723
  const model = this.findModel(sunspec_interfaces_js_1.SunspecModelId.MPPT);
671
724
  if (!model) {
672
725
  console.log('MPPT model 160 not found');
673
726
  return null;
674
727
  }
675
- const baseAddr = model.address;
676
728
  try {
677
- // 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;
729
+ const buffer = await this.readModelBlock(model);
730
+ return this.extractMPPTScaleFactors(buffer);
695
731
  }
696
732
  catch (error) {
697
733
  console.error(`Error reading MPPT scale factors: ${error}`);
698
734
  return null;
699
735
  }
700
736
  }
737
+ /**
738
+ * Extract MPPT module data from a pre-read model buffer
739
+ */
740
+ extractMPPTModuleData(buffer, model, moduleId, scaleFactors) {
741
+ const fixedBlockScaleFactorsOffset = 10;
742
+ const moduleSize = 20;
743
+ const offset = (moduleId - 1) * moduleSize;
744
+ const moduleOffset = fixedBlockScaleFactorsOffset + offset;
745
+ const id = this.extractValue(buffer, moduleOffset, 'uint16');
746
+ const idString = this.extractValue(buffer, moduleOffset + 1, 'string', 8);
747
+ const dcCurrentRaw = this.extractValue(buffer, moduleOffset + 9, 'uint16');
748
+ const dcVoltageRaw = this.extractValue(buffer, moduleOffset + 10, 'uint16');
749
+ const dcPowerRaw = this.extractValue(buffer, moduleOffset + 11, 'uint16');
750
+ const dcEnergyRaw = this.extractValue(buffer, moduleOffset + 12, 'uint32', 2);
751
+ const temperatureRaw = this.extractValue(buffer, moduleOffset + 16, 'int16');
752
+ const dcst = this.extractValue(buffer, moduleOffset + 17, 'uint16');
753
+ this.logRegisterRead(160, 0, `MPPT ${moduleId} ID`, id, 'uint16');
754
+ this.logRegisterRead(160, 1, `MPPT ${moduleId} String ID`, idString, 'string');
755
+ if (this.isUnimplementedValue(id, 'uint16') &&
756
+ this.isUnimplementedValue(dcCurrentRaw, 'uint16') &&
757
+ this.isUnimplementedValue(dcVoltageRaw, 'uint16') &&
758
+ this.isUnimplementedValue(dcPowerRaw, 'uint16')) {
759
+ console.log(`MPPT module ${moduleId} appears to be unconnected (all values are 0xFFFF)`);
760
+ return null;
761
+ }
762
+ const temperatureScaleFactor = -1;
763
+ this.logRegisterRead(160, 12, `MPPT ${moduleId} DC Energy`, dcEnergyRaw, 'acc32');
764
+ const timestampRaw = this.extractValue(buffer, moduleOffset + 14, 'uint32', 2);
765
+ this.logRegisterRead(160, 14, `MPPT ${moduleId} Timestamp`, timestampRaw, 'uint32');
766
+ this.logRegisterRead(160, 17, `MPPT ${moduleId} Operating State`, dcst, 'enum16');
767
+ const data = {
768
+ blockNumber: 160,
769
+ blockAddress: model.address,
770
+ blockLength: model.length,
771
+ id: id,
772
+ stringId: idString,
773
+ dcCurrent: this.applyScaleFactor(dcCurrentRaw, scaleFactors.DCA_SF, 'uint16', `MPPT ${moduleId} DC Current`, 9, 160),
774
+ dcCurrentSF: scaleFactors.DCA_SF,
775
+ dcVoltage: this.applyScaleFactor(dcVoltageRaw, scaleFactors.DCV_SF, 'uint16', `MPPT ${moduleId} DC Voltage`, 10, 160),
776
+ dcVoltageSF: scaleFactors.DCV_SF,
777
+ dcPower: this.applyScaleFactor(dcPowerRaw, scaleFactors.DCW_SF, 'uint16', `MPPT ${moduleId} DC Power`, 11, 160),
778
+ dcPowerSF: scaleFactors.DCW_SF,
779
+ dcEnergy: !this.isUnimplementedValue(dcEnergyRaw, 'acc32')
780
+ ? dcEnergyRaw * Math.pow(10, scaleFactors.DCWH_SF)
781
+ : undefined,
782
+ dcEnergySF: scaleFactors.DCWH_SF,
783
+ timestamp: timestampRaw,
784
+ temperature: this.applyScaleFactor(temperatureRaw, temperatureScaleFactor, 'int16', `MPPT ${moduleId} Temperature`, 16, 160),
785
+ temperatureSF: temperatureScaleFactor,
786
+ operatingState: dcst,
787
+ };
788
+ console.debug(`[Model 160] MPPT Module ${moduleId} Data:`, data);
789
+ return data;
790
+ }
701
791
  /**
702
792
  * Read MPPT data from Model 160
703
793
  */
@@ -707,68 +797,11 @@ class SunspecModbusClient {
707
797
  console.log('MPPT model 160 not found');
708
798
  return null;
709
799
  }
710
- const baseAddr = model.address;
711
800
  try {
712
- 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;
801
+ // Read entire model block in a single Modbus call
802
+ const buffer = await this.readModelBlock(model);
803
+ const scaleFactors = this.extractMPPTScaleFactors(buffer);
804
+ return this.extractMPPTModuleData(buffer, model, moduleId, scaleFactors);
772
805
  }
773
806
  catch (error) {
774
807
  console.error(`Error reading MPPT data for module ${moduleId}: ${error}`);
@@ -780,24 +813,18 @@ class SunspecModbusClient {
780
813
  */
781
814
  async readAllMPPTData() {
782
815
  const mpptData = [];
783
- // Find the MPPT model first
784
816
  const model = this.findModel(sunspec_interfaces_js_1.SunspecModelId.MPPT);
785
817
  if (!model) {
786
818
  console.log('MPPT model 160 not found');
787
819
  return [];
788
820
  }
789
- const scaleFactors = await this.readMPPTScaleFactors();
790
- if (!scaleFactors) {
791
- console.error(`Failed to read scale factors for MPPT`);
792
- return [];
793
- }
794
- // Read the module count from register 8
795
- let moduleCount = 4; // Default fallback value
796
821
  try {
797
- 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
822
+ // Read entire model block in a single Modbus call
823
+ const buffer = await this.readModelBlock(model);
824
+ const scaleFactors = this.extractMPPTScaleFactors(buffer);
825
+ // Read the module count from register 8
826
+ let moduleCount = 4; // Default fallback value
827
+ const count = this.extractValue(buffer, 8, 'uint16');
801
828
  if (!this.isUnimplementedValue(count, 'uint16') && count > 0 && count <= 20) {
802
829
  moduleCount = count;
803
830
  console.log(`MPPT module count from register 8: ${moduleCount}`);
@@ -805,27 +832,25 @@ class SunspecModbusClient {
805
832
  else {
806
833
  console.log(`Invalid or unimplemented module count (${count}), using default: ${moduleCount}`);
807
834
  }
808
- }
809
- 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);
835
+ // Extract each MPPT module from the same buffer
836
+ for (let i = 1; i <= moduleCount; i++) {
837
+ try {
838
+ const data = this.extractMPPTModuleData(buffer, model, i, scaleFactors);
839
+ console.log(`MPPT ${i} has id ${data?.id} (${data?.stringId}) with ${data?.dcPower}W`);
840
+ if (data &&
841
+ (data.dcCurrent !== undefined ||
842
+ data.dcVoltage !== undefined ||
843
+ data.dcPower !== undefined)) {
844
+ mpptData.push(data);
845
+ }
846
+ }
847
+ catch (error) {
848
+ console.debug(`Could not read MPPT module ${i}: ${error}`);
823
849
  }
824
850
  }
825
- catch (error) {
826
- console.debug(`Could not read MPPT module ${i}: ${error}`);
827
- // Continue to try other modules
828
- }
851
+ }
852
+ catch (error) {
853
+ console.error(`Error reading all MPPT data: ${error}`);
829
854
  }
830
855
  return mpptData;
831
856
  }
@@ -922,22 +947,22 @@ class SunspecModbusClient {
922
947
  }
923
948
  }
924
949
  /**
925
- * Read Model 802 scale factors (offsets 52-63)
950
+ * Extract Model 802 scale factors from a pre-read buffer (offsets 52-63)
926
951
  */
927
- async readBatteryBaseScaleFactors(baseAddr) {
952
+ extractBatteryBaseScaleFactors(buffer) {
928
953
  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'),
954
+ AHRtg_SF: this.extractValue(buffer, 52, 'int16'),
955
+ WHRtg_SF: this.extractValue(buffer, 53, 'int16'),
956
+ WChaDisChaMax_SF: this.extractValue(buffer, 54, 'int16'),
957
+ DisChaRte_SF: this.extractValue(buffer, 55, 'int16'),
958
+ SoC_SF: this.extractValue(buffer, 56, 'int16'),
959
+ DoD_SF: this.extractValue(buffer, 57, 'int16'),
960
+ SoH_SF: this.extractValue(buffer, 58, 'int16'),
961
+ V_SF: this.extractValue(buffer, 59, 'int16'),
962
+ CellV_SF: this.extractValue(buffer, 60, 'int16'),
963
+ A_SF: this.extractValue(buffer, 61, 'int16'),
964
+ AMax_SF: this.extractValue(buffer, 62, 'int16'),
965
+ W_SF: this.extractValue(buffer, 63, 'int16'),
941
966
  };
942
967
  this.logRegisterRead(802, 52, 'AHRtg_SF', scaleFactors.AHRtg_SF, 'int16');
943
968
  this.logRegisterRead(802, 53, 'WHRtg_SF', scaleFactors.WHRtg_SF, 'int16');
@@ -962,51 +987,52 @@ class SunspecModbusClient {
962
987
  console.log('Battery Base model 802 not found');
963
988
  return null;
964
989
  }
965
- const baseAddr = model.address;
966
- console.log(`Reading Battery Base Data from Model 802 at base address: ${baseAddr}`);
990
+ console.log(`Reading Battery Base Data from Model 802 at base address: ${model.address}`);
967
991
  try {
968
- // Read 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');
992
+ // Read entire model block in a single Modbus call
993
+ const buffer = await this.readModelBlock(model);
994
+ // Extract scale factors from buffer (offsets 52-63)
995
+ const sf = this.extractBatteryBaseScaleFactors(buffer);
996
+ // Extract raw values from buffer
997
+ const ahRtgRaw = this.extractValue(buffer, 2, 'uint16');
998
+ const whRtgRaw = this.extractValue(buffer, 3, 'uint16');
999
+ const wChaRteMaxRaw = this.extractValue(buffer, 4, 'uint16');
1000
+ const wDisChaRteMaxRaw = this.extractValue(buffer, 5, 'uint16');
1001
+ const disChaRteRaw = this.extractValue(buffer, 6, 'uint16');
1002
+ const soCMaxRaw = this.extractValue(buffer, 7, 'uint16');
1003
+ const soCMinRaw = this.extractValue(buffer, 8, 'uint16');
1004
+ const soCRsvMaxRaw = this.extractValue(buffer, 9, 'uint16');
1005
+ const soCRsvMinRaw = this.extractValue(buffer, 10, 'uint16');
1006
+ const soCRaw = this.extractValue(buffer, 11, 'uint16');
1007
+ const doDRaw = this.extractValue(buffer, 12, 'uint16');
1008
+ const soHRaw = this.extractValue(buffer, 13, 'uint16');
1009
+ const nCycRaw = this.extractValue(buffer, 14, 'uint32', 2);
1010
+ const chaStRaw = this.extractValue(buffer, 16, 'uint16');
1011
+ const locRemCtlRaw = this.extractValue(buffer, 17, 'uint16');
1012
+ const typRaw = this.extractValue(buffer, 21, 'uint16');
1013
+ const stateRaw = this.extractValue(buffer, 22, 'uint16');
1014
+ const evt1Raw = this.extractValue(buffer, 26, 'uint32', 2);
1015
+ const evt2Raw = this.extractValue(buffer, 28, 'uint32', 2);
1016
+ const evtVnd1Raw = this.extractValue(buffer, 30, 'uint32', 2);
1017
+ const evtVnd2Raw = this.extractValue(buffer, 32, 'uint32', 2);
1018
+ const vRaw = this.extractValue(buffer, 34, 'uint16');
1019
+ const vMaxRaw = this.extractValue(buffer, 35, 'uint16');
1020
+ const vMinRaw = this.extractValue(buffer, 36, 'uint16');
1021
+ const cellVMaxRaw = this.extractValue(buffer, 37, 'uint16');
1022
+ const cellVMaxStrRaw = this.extractValue(buffer, 38, 'uint16');
1023
+ const cellVMaxModRaw = this.extractValue(buffer, 39, 'uint16');
1024
+ const cellVMinRaw = this.extractValue(buffer, 40, 'uint16');
1025
+ const cellVMinStrRaw = this.extractValue(buffer, 41, 'uint16');
1026
+ const cellVMinModRaw = this.extractValue(buffer, 42, 'uint16');
1027
+ const cellVAvgRaw = this.extractValue(buffer, 43, 'uint16');
1028
+ const aRaw = this.extractValue(buffer, 44, 'int16');
1029
+ const aChaMaxRaw = this.extractValue(buffer, 45, 'uint16');
1030
+ const aDisChaMaxRaw = this.extractValue(buffer, 46, 'uint16');
1031
+ const wRaw = this.extractValue(buffer, 47, 'int16');
1032
+ const reqInvStateRaw = this.extractValue(buffer, 48, 'uint16');
1033
+ const reqWRaw = this.extractValue(buffer, 49, 'int16');
1034
+ const setOpRaw = this.extractValue(buffer, 50, 'uint16');
1035
+ const setInvStateRaw = this.extractValue(buffer, 51, 'uint16');
1010
1036
  // Map enum fields
1011
1037
  const chaStName = this.mapBatteryChargeState(chaStRaw);
1012
1038
  const typName = this.mapBatteryType(typRaw);
@@ -1082,6 +1108,7 @@ class SunspecModbusClient {
1082
1108
  setOp: !this.isUnimplementedValue(setOpRaw, 'enum16') ? setOpRaw : undefined,
1083
1109
  setInvState: !this.isUnimplementedValue(setInvStateRaw, 'enum16') ? setInvStateRaw : undefined,
1084
1110
  };
1111
+ console.debug('[Model 802] Battery Base Data:', data);
1085
1112
  return data;
1086
1113
  }
1087
1114
  catch (error) {
@@ -1106,22 +1133,23 @@ class SunspecModbusClient {
1106
1133
  console.log('No battery model found');
1107
1134
  return null;
1108
1135
  }
1109
- const baseAddr = model.address;
1110
- console.log(`Reading Battery Data from Model ${model.id} at base address: ${baseAddr}`);
1136
+ console.log(`Reading Battery Data from Model ${model.id} at base address: ${model.address}`);
1111
1137
  try {
1112
1138
  if (model.id === 124) {
1113
1139
  // Model 124: Basic Storage Controls
1114
1140
  console.log('Using Model 124 (Basic Storage Controls)');
1115
- // Read scale factors first (offsets 18-25)
1141
+ // Read entire model block in a single Modbus call
1142
+ const buffer = await this.readModelBlock(model);
1143
+ // Extract scale factors from buffer (offsets 18-25)
1116
1144
  const scaleFactors = {
1117
- WChaMax_SF: 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')
1145
+ WChaMax_SF: this.extractValue(buffer, 18, 'int16'),
1146
+ WChaDisChaGra_SF: this.extractValue(buffer, 19, 'int16'),
1147
+ VAChaMax_SF: this.extractValue(buffer, 20, 'int16'),
1148
+ MinRsvPct_SF: this.extractValue(buffer, 21, 'int16'),
1149
+ ChaState_SF: this.extractValue(buffer, 22, 'int16'),
1150
+ StorAval_SF: this.extractValue(buffer, 23, 'int16'),
1151
+ InBatV_SF: this.extractValue(buffer, 24, 'int16'),
1152
+ InOutWRte_SF: this.extractValue(buffer, 25, 'int16')
1125
1153
  };
1126
1154
  this.logRegisterRead(124, 18, 'WChaMax_SF', scaleFactors.WChaMax_SF, 'int16');
1127
1155
  this.logRegisterRead(124, 19, 'WChaDisChaGra_SF', scaleFactors.WChaDisChaGra_SF, 'int16');
@@ -1131,23 +1159,23 @@ class SunspecModbusClient {
1131
1159
  this.logRegisterRead(124, 23, 'StorAval_SF', scaleFactors.StorAval_SF, 'int16');
1132
1160
  this.logRegisterRead(124, 24, 'InBatV_SF', scaleFactors.InBatV_SF, 'int16');
1133
1161
  this.logRegisterRead(124, 25, 'InOutWRte_SF', scaleFactors.InOutWRte_SF, 'int16');
1134
- // 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');
1162
+ // Extract raw values from buffer
1163
+ const wChaMaxRaw = this.extractValue(buffer, 2, 'uint16');
1164
+ const wChaGraRaw = this.extractValue(buffer, 3, 'uint16');
1165
+ const wDisChaGraRaw = this.extractValue(buffer, 4, 'uint16');
1166
+ const storCtlModRaw = this.extractValue(buffer, 5, 'uint16');
1167
+ const vaChaMaxRaw = this.extractValue(buffer, 6, 'uint16');
1168
+ const minRsvPctRaw = this.extractValue(buffer, 7, 'uint16');
1169
+ const chaStateRaw = this.extractValue(buffer, 8, 'uint16');
1170
+ const storAvalRaw = this.extractValue(buffer, 9, 'uint16');
1171
+ const inBatVRaw = this.extractValue(buffer, 10, 'uint16');
1172
+ const chaStRaw = this.extractValue(buffer, 11, 'uint16');
1173
+ const outWRteRaw = this.extractValue(buffer, 12, 'int16');
1174
+ const inWRteRaw = this.extractValue(buffer, 13, 'int16');
1175
+ const inOutWRteWinTmsRaw = this.extractValue(buffer, 14, 'uint16');
1176
+ const inOutWRteRvrtTmsRaw = this.extractValue(buffer, 15, 'uint16');
1177
+ const inOutWRteRmpTmsRaw = this.extractValue(buffer, 16, 'uint16');
1178
+ const chaGriSetRaw = this.extractValue(buffer, 17, 'uint16');
1151
1179
  // Map charge state and log non-scaled fields
1152
1180
  const chaStName = this.mapBatteryChargeState(chaStRaw);
1153
1181
  this.logRegisterRead(124, 5, 'storCtlMod', storCtlModRaw, 'bitfield16');
@@ -1206,6 +1234,7 @@ class SunspecModbusClient {
1206
1234
  data.dischargePower = Math.abs((data.outWRte / 100) * data.wChaMax);
1207
1235
  console.log(`Calculated Discharge Power (inWRte: ${data.outWRte}, wChaMax: ${data.wChaMax}): ${data.dischargePower?.toFixed(2)} W`);
1208
1236
  }
1237
+ console.debug('[Model 124] Battery Data:', data);
1209
1238
  return data;
1210
1239
  }
1211
1240
  else if (model.id === 802) {
@@ -1228,7 +1257,7 @@ class SunspecModbusClient {
1228
1257
  dischargePower = Math.abs(baseData.w);
1229
1258
  }
1230
1259
  }
1231
- return {
1260
+ const result = {
1232
1261
  blockNumber: 802,
1233
1262
  blockAddress: model.address,
1234
1263
  blockLength: model.length,
@@ -1242,6 +1271,8 @@ class SunspecModbusClient {
1242
1271
  chargePower,
1243
1272
  dischargePower,
1244
1273
  };
1274
+ console.debug('[Model 802] Battery Data:', result);
1275
+ return result;
1245
1276
  }
1246
1277
  else {
1247
1278
  // Handle other battery models (803) if needed
@@ -1366,29 +1397,30 @@ class SunspecModbusClient {
1366
1397
  console.log('Battery model 124 not found');
1367
1398
  return null;
1368
1399
  }
1369
- const baseAddr = model.address;
1370
- console.log(`Reading Battery Controls from Model 124 at base address: ${baseAddr}`);
1400
+ console.log(`Reading Battery Controls from Model 124 at base address: ${model.address}`);
1371
1401
  try {
1372
- // Read scale factors
1402
+ // Read entire model block in a single Modbus call
1403
+ const buffer = await this.readModelBlock(model);
1404
+ // Extract scale factors from buffer
1373
1405
  const scaleFactors = {
1374
- WChaMax_SF: 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')
1406
+ WChaMax_SF: this.extractValue(buffer, 18, 'int16'),
1407
+ MinRsvPct_SF: this.extractValue(buffer, 21, 'int16'),
1408
+ InOutWRte_SF: this.extractValue(buffer, 25, 'int16')
1377
1409
  };
1378
1410
  this.logRegisterRead(124, 18, 'WChaMax_SF', scaleFactors.WChaMax_SF, 'int16');
1379
1411
  this.logRegisterRead(124, 21, 'MinRsvPct_SF', scaleFactors.MinRsvPct_SF, 'int16');
1380
1412
  this.logRegisterRead(124, 25, 'InOutWRte_SF', scaleFactors.InOutWRte_SF, 'int16');
1381
- // 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');
1413
+ // Extract raw values from buffer
1414
+ const wChaMaxRaw = this.extractValue(buffer, 2, 'uint16');
1415
+ const storCtlModRaw = this.extractValue(buffer, 5, 'uint16');
1416
+ const minRsvPctRaw = this.extractValue(buffer, 7, 'uint16');
1417
+ const outWRteRaw = this.extractValue(buffer, 12, 'int16');
1418
+ const inWRteRaw = this.extractValue(buffer, 13, 'int16');
1419
+ const chaGriSetRaw = this.extractValue(buffer, 17, 'uint16');
1388
1420
  this.logRegisterRead(124, 5, 'storCtlMod', storCtlModRaw, 'bitfield16');
1389
1421
  this.logRegisterRead(124, 17, 'chaGriSet', chaGriSetRaw, 'enum16');
1390
1422
  // Apply scale factors and return control settings
1391
- return {
1423
+ const controls = {
1392
1424
  storCtlMod: !this.isUnimplementedValue(storCtlModRaw, 'bitfield16') ? storCtlModRaw : undefined,
1393
1425
  chaGriSet: !this.isUnimplementedValue(chaGriSetRaw, 'enum16') ? chaGriSetRaw : undefined,
1394
1426
  wChaMax: this.applyScaleFactor(wChaMaxRaw, scaleFactors.WChaMax_SF, 'uint16', 'Max Charge Power', 2, 124),
@@ -1396,6 +1428,8 @@ class SunspecModbusClient {
1396
1428
  outWRte: this.applyScaleFactor(outWRteRaw, scaleFactors.InOutWRte_SF, 'int16', 'Discharge Rate', 12, 124),
1397
1429
  minRsvPct: this.applyScaleFactor(minRsvPctRaw, scaleFactors.MinRsvPct_SF, 'uint16', 'Min Reserve Percent', 7, 124)
1398
1430
  };
1431
+ console.debug('[Model 124] Battery Controls:', controls);
1432
+ return controls;
1399
1433
  }
1400
1434
  catch (error) {
1401
1435
  console.error(`Error reading battery controls: ${error}`);
@@ -1417,8 +1451,7 @@ class SunspecModbusClient {
1417
1451
  console.log('No meter model found');
1418
1452
  return null;
1419
1453
  }
1420
- const baseAddr = model.address;
1421
- console.log(`Reading Meter Data from Model ${model.id} at base address: ${baseAddr}`);
1454
+ console.log(`Reading Meter Data from Model ${model.id} at base address: ${model.address}`);
1422
1455
  try {
1423
1456
  // Different meter models have different register offsets
1424
1457
  console.log(`Meter is Model ${model.id}`);
@@ -1461,18 +1494,20 @@ class SunspecModbusClient {
1461
1494
  importOffset = 46; // TotWhImp - Total Wh Imported (acc32)
1462
1495
  energySFOffset = 54; // TotWh_SF - Total Energy scale factor
1463
1496
  }
1464
- // Read 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');
1497
+ // Read entire model block in a single Modbus call
1498
+ const buffer = await this.readModelBlock(model);
1499
+ // Extract scale factors from buffer
1500
+ const powerSF = this.extractValue(buffer, powerSFOffset, 'int16');
1501
+ const freqSF = this.extractValue(buffer, freqSFOffset, 'int16');
1502
+ const energySF = this.extractValue(buffer, energySFOffset, 'int16');
1468
1503
  this.logRegisterRead(model.id, powerSFOffset, 'W_SF', powerSF, 'int16');
1469
1504
  this.logRegisterRead(model.id, freqSFOffset, 'Hz_SF', freqSF, 'int16');
1470
1505
  this.logRegisterRead(model.id, energySFOffset, 'TotWh_SF', energySF, 'int16');
1471
- // 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');
1506
+ // Extract raw values from buffer
1507
+ const powerRaw = this.extractValue(buffer, powerOffset, 'int16');
1508
+ const freqRaw = this.extractValue(buffer, freqOffset, 'uint16');
1509
+ const exportRaw = this.extractValue(buffer, exportOffset, 'uint32', 2);
1510
+ const importRaw = this.extractValue(buffer, importOffset, 'uint32', 2);
1476
1511
  this.logRegisterRead(model.id, exportOffset, 'TotWhExp', exportRaw, 'acc32');
1477
1512
  this.logRegisterRead(model.id, importOffset, 'TotWhImp', importRaw, 'acc32');
1478
1513
  // Calculate final values with scale factors
@@ -1480,13 +1515,15 @@ class SunspecModbusClient {
1480
1515
  const frequency = this.applyScaleFactor(freqRaw, freqSF, 'uint16', 'Frequency', freqOffset, model.id);
1481
1516
  const exportedEnergy = this.applyScaleFactor(exportRaw, energySF, "acc32", 'Exported Energy', exportOffset, model.id);
1482
1517
  const importedEnergy = this.applyScaleFactor(importRaw, energySF, "acc32", 'Imported Energy', importOffset, model.id);
1483
- return {
1518
+ const data = {
1484
1519
  blockNumber: model.id,
1485
1520
  totalPower,
1486
1521
  frequency,
1487
1522
  exportedEnergy,
1488
1523
  importedEnergy
1489
1524
  };
1525
+ console.debug(`[Model ${model.id}] Meter Data:`, data);
1526
+ return data;
1490
1527
  }
1491
1528
  catch (error) {
1492
1529
  console.error(`Error reading meter data: ${error}`);
@@ -1502,30 +1539,28 @@ class SunspecModbusClient {
1502
1539
  console.error('Common block model not found');
1503
1540
  return null;
1504
1541
  }
1505
- const baseAddr = model.address + 2; // Skip ID and Length
1506
- console.log(`Reading Common Block - Model address: ${model.address}, Base address for data: ${baseAddr}`);
1542
+ console.log(`Reading Common Block - Model address: ${model.address}`);
1507
1543
  try {
1508
- // Read 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');
1544
+ // Read entire model block in a single Modbus call
1545
+ const buffer = await this.readModelBlock(model);
1546
+ // Common block offsets are relative to the model start (after ID and Length header,
1547
+ // but readModelBlock reads from model.address which includes the data area).
1548
+ // The offsets below are relative to the data start within the model block.
1549
+ // Since model.address points to the first data register (after the 2-register header
1550
+ // is already accounted for in model discovery), offset 0 = first data register.
1551
+ const manufacturer = this.extractValue(buffer, 2, 'string', 16);
1517
1552
  this.logRegisterRead(1, 0, 'Manufacturer', manufacturer, 'string');
1518
- const modelName = await this.readRegisterValue(modelAddr, 16, 'string');
1553
+ const modelName = this.extractValue(buffer, 18, 'string', 16);
1519
1554
  this.logRegisterRead(1, 16, 'Model', modelName, 'string');
1520
- const options = await this.readRegisterValue(optionsAddr, 8, 'string');
1555
+ const options = this.extractValue(buffer, 34, 'string', 8);
1521
1556
  this.logRegisterRead(1, 32, 'Options', options, 'string');
1522
- const version = await this.readRegisterValue(versionAddr, 8, 'string');
1557
+ const version = this.extractValue(buffer, 42, 'string', 8);
1523
1558
  this.logRegisterRead(1, 40, 'Version', version, 'string');
1524
- const serialNumber = await this.readRegisterValue(serialAddr, 16, 'string');
1559
+ const serialNumber = this.extractValue(buffer, 50, 'string', 16);
1525
1560
  this.logRegisterRead(1, 48, 'Serial Number', serialNumber, 'string');
1526
- const deviceAddress = await this.readRegisterValue(deviceAddrAddr, 1, 'uint16');
1561
+ const deviceAddress = this.extractValue(buffer, 66, 'uint16');
1527
1562
  this.logRegisterRead(1, 64, 'Device Address', deviceAddress, 'uint16');
1528
- return {
1563
+ const data = {
1529
1564
  manufacturer,
1530
1565
  model: modelName,
1531
1566
  options,
@@ -1533,6 +1568,8 @@ class SunspecModbusClient {
1533
1568
  serialNumber,
1534
1569
  deviceAddress
1535
1570
  };
1571
+ console.debug('[Model 1] Common Block:', data);
1572
+ return data;
1536
1573
  }
1537
1574
  catch (error) {
1538
1575
  console.error(`Error reading common block: ${error}`);
@@ -1573,20 +1610,21 @@ class SunspecModbusClient {
1573
1610
  console.log('Settings model 121 not found');
1574
1611
  return null;
1575
1612
  }
1576
- const baseAddr = model.address;
1577
1613
  try {
1578
- // Read scale factors first (offsets 22-31)
1614
+ // Read entire model block in a single Modbus call
1615
+ const buffer = await this.readModelBlock(model);
1616
+ // Extract scale factors from buffer (offsets 22-31)
1579
1617
  const scaleFactors = {
1580
- WMax_SF: 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')
1618
+ WMax_SF: this.extractValue(buffer, 22, 'int16'),
1619
+ VRef_SF: this.extractValue(buffer, 23, 'int16'),
1620
+ VRefOfs_SF: this.extractValue(buffer, 24, 'int16'),
1621
+ VMinMax_SF: this.extractValue(buffer, 25, 'int16'),
1622
+ VAMax_SF: this.extractValue(buffer, 26, 'int16'),
1623
+ VArMax_SF: this.extractValue(buffer, 27, 'int16'),
1624
+ WGra_SF: this.extractValue(buffer, 28, 'int16'),
1625
+ PFMin_SF: this.extractValue(buffer, 29, 'int16'),
1626
+ MaxRmpRte_SF: this.extractValue(buffer, 30, 'int16'),
1627
+ ECPNomHz_SF: this.extractValue(buffer, 31, 'int16')
1590
1628
  };
1591
1629
  this.logRegisterRead(121, 22, 'WMax_SF', scaleFactors.WMax_SF, 'int16');
1592
1630
  this.logRegisterRead(121, 23, 'VRef_SF', scaleFactors.VRef_SF, 'int16');
@@ -1598,55 +1636,57 @@ class SunspecModbusClient {
1598
1636
  this.logRegisterRead(121, 29, 'PFMin_SF', scaleFactors.PFMin_SF, 'int16');
1599
1637
  this.logRegisterRead(121, 30, 'MaxRmpRte_SF', scaleFactors.MaxRmpRte_SF, 'int16');
1600
1638
  this.logRegisterRead(121, 31, 'ECPNomHz_SF', scaleFactors.ECPNomHz_SF, 'int16');
1601
- // 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');
1639
+ // Extract non-scaled fields from buffer
1640
+ const vArActRaw = this.extractValue(buffer, 17, 'uint16');
1641
+ const clcTotVARaw = this.extractValue(buffer, 18, 'uint16');
1642
+ const connPhRaw = this.extractValue(buffer, 21, 'uint16');
1605
1643
  this.logRegisterRead(121, 17, 'VArAct', vArActRaw, 'enum16');
1606
1644
  this.logRegisterRead(121, 18, 'ClcTotVA', clcTotVARaw, 'enum16');
1607
1645
  this.logRegisterRead(121, 21, 'ConnPh', connPhRaw, 'enum16');
1608
- return {
1646
+ const settings = {
1609
1647
  blockNumber: 121,
1610
1648
  blockAddress: model.address,
1611
1649
  blockLength: model.length,
1612
1650
  // Power settings - Offset 2
1613
- WMax: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 2, 1, 'uint16'), scaleFactors.WMax_SF, 'uint16', 'Max Power', 2, 121),
1651
+ WMax: this.applyScaleFactor(this.extractValue(buffer, 2, 'uint16'), scaleFactors.WMax_SF, 'uint16', 'Max Power', 2, 121),
1614
1652
  WMax_SF: scaleFactors.WMax_SF,
1615
1653
  // Voltage settings - Offsets 3-6
1616
- VRef: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 3, 1, 'uint16'), scaleFactors.VRef_SF, 'uint16', 'Voltage Reference', 3, 121),
1654
+ VRef: this.applyScaleFactor(this.extractValue(buffer, 3, 'uint16'), scaleFactors.VRef_SF, 'uint16', 'Voltage Reference', 3, 121),
1617
1655
  VRef_SF: scaleFactors.VRef_SF,
1618
- VRefOfs: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 4, 1, 'int16'), scaleFactors.VRefOfs_SF, 'int16', 'Voltage Reference Offset', 4, 121),
1656
+ VRefOfs: this.applyScaleFactor(this.extractValue(buffer, 4, 'int16'), scaleFactors.VRefOfs_SF, 'int16', 'Voltage Reference Offset', 4, 121),
1619
1657
  VRefOfs_SF: scaleFactors.VRefOfs_SF,
1620
- VMax: this.applyScaleFactor(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),
1658
+ VMax: this.applyScaleFactor(this.extractValue(buffer, 5, 'uint16'), scaleFactors.VMinMax_SF, 'uint16', 'Max Voltage', 5, 121),
1659
+ VMin: this.applyScaleFactor(this.extractValue(buffer, 6, 'uint16'), scaleFactors.VMinMax_SF, 'uint16', 'Min Voltage', 6, 121),
1622
1660
  VMinMax_SF: scaleFactors.VMinMax_SF,
1623
1661
  // Apparent power settings - Offset 7
1624
- VAMax: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 7, 1, 'uint16'), scaleFactors.VAMax_SF, 'uint16', 'Max Apparent Power', 7, 121),
1662
+ VAMax: this.applyScaleFactor(this.extractValue(buffer, 7, 'uint16'), scaleFactors.VAMax_SF, 'uint16', 'Max Apparent Power', 7, 121),
1625
1663
  VAMax_SF: scaleFactors.VAMax_SF,
1626
1664
  // Reactive power settings - Offsets 8-11
1627
- VArMaxQ1: this.applyScaleFactor(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),
1665
+ VArMaxQ1: this.applyScaleFactor(this.extractValue(buffer, 8, 'int16'), scaleFactors.VArMax_SF, 'int16', 'Max Reactive Power Q1', 8, 121),
1666
+ VArMaxQ2: this.applyScaleFactor(this.extractValue(buffer, 9, 'int16'), scaleFactors.VArMax_SF, 'int16', 'Max Reactive Power Q2', 9, 121),
1667
+ VArMaxQ3: this.applyScaleFactor(this.extractValue(buffer, 10, 'int16'), scaleFactors.VArMax_SF, 'int16', 'Max Reactive Power Q3', 10, 121),
1668
+ VArMaxQ4: this.applyScaleFactor(this.extractValue(buffer, 11, 'int16'), scaleFactors.VArMax_SF, 'int16', 'Max Reactive Power Q4', 11, 121),
1631
1669
  VArMax_SF: scaleFactors.VArMax_SF,
1632
1670
  // Ramp rate settings - Offset 12
1633
- WGra: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 12, 1, 'uint16'), scaleFactors.WGra_SF, 'uint16', 'Power Ramp Rate', 12, 121),
1671
+ WGra: this.applyScaleFactor(this.extractValue(buffer, 12, 'uint16'), scaleFactors.WGra_SF, 'uint16', 'Power Ramp Rate', 12, 121),
1634
1672
  WGra_SF: scaleFactors.WGra_SF,
1635
1673
  // Power factor settings - Offsets 13-16
1636
- PFMinQ1: this.applyScaleFactor(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),
1674
+ PFMinQ1: this.applyScaleFactor(this.extractValue(buffer, 13, 'int16'), scaleFactors.PFMin_SF, 'int16', 'Min Power Factor Q1', 13, 121),
1675
+ PFMinQ2: this.applyScaleFactor(this.extractValue(buffer, 14, 'int16'), scaleFactors.PFMin_SF, 'int16', 'Min Power Factor Q2', 14, 121),
1676
+ PFMinQ3: this.applyScaleFactor(this.extractValue(buffer, 15, 'int16'), scaleFactors.PFMin_SF, 'int16', 'Min Power Factor Q3', 15, 121),
1677
+ PFMinQ4: this.applyScaleFactor(this.extractValue(buffer, 16, 'int16'), scaleFactors.PFMin_SF, 'int16', 'Min Power Factor Q4', 16, 121),
1640
1678
  PFMin_SF: scaleFactors.PFMin_SF,
1641
1679
  // Other settings - Offsets 17-21
1642
1680
  VArAct: vArActRaw,
1643
1681
  ClcTotVA: clcTotVARaw,
1644
- MaxRmpRte: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 19, 1, 'uint16'), scaleFactors.MaxRmpRte_SF, 'uint16', 'Max Ramp Rate', 19, 121),
1682
+ MaxRmpRte: this.applyScaleFactor(this.extractValue(buffer, 19, 'uint16'), scaleFactors.MaxRmpRte_SF, 'uint16', 'Max Ramp Rate', 19, 121),
1645
1683
  MaxRmpRte_SF: scaleFactors.MaxRmpRte_SF,
1646
- ECPNomHz: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 20, 1, 'uint16'), scaleFactors.ECPNomHz_SF, 'uint16', 'Nominal Frequency', 20, 121),
1684
+ ECPNomHz: this.applyScaleFactor(this.extractValue(buffer, 20, 'uint16'), scaleFactors.ECPNomHz_SF, 'uint16', 'Nominal Frequency', 20, 121),
1647
1685
  ECPNomHz_SF: scaleFactors.ECPNomHz_SF,
1648
1686
  ConnPh: connPhRaw
1649
1687
  };
1688
+ console.debug('[Model 121] Inverter Settings:', settings);
1689
+ return settings;
1650
1690
  }
1651
1691
  catch (error) {
1652
1692
  console.error(`Error reading inverter settings: ${error}`);
@@ -1662,34 +1702,35 @@ class SunspecModbusClient {
1662
1702
  console.log('Controls model 123 not found');
1663
1703
  return null;
1664
1704
  }
1665
- const baseAddr = model.address;
1666
1705
  try {
1667
- // Read scale factors first (offsets 21-23)
1706
+ // Read entire model block in a single Modbus call
1707
+ const buffer = await this.readModelBlock(model);
1708
+ // Extract scale factors from buffer (offsets 21-23)
1668
1709
  const scaleFactors = {
1669
- WMaxLimPct_SF: 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')
1710
+ WMaxLimPct_SF: this.extractValue(buffer, 21, 'int16'),
1711
+ OutPFSet_SF: this.extractValue(buffer, 22, 'int16'),
1712
+ VArPct_SF: this.extractValue(buffer, 23, 'int16')
1672
1713
  };
1673
1714
  this.logRegisterRead(123, 21, 'WMaxLimPct_SF', scaleFactors.WMaxLimPct_SF, 'int16');
1674
1715
  this.logRegisterRead(123, 22, 'OutPFSet_SF', scaleFactors.OutPFSet_SF, 'int16');
1675
1716
  this.logRegisterRead(123, 23, 'VArPct_SF', scaleFactors.VArPct_SF, 'int16');
1676
- // 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');
1717
+ // Extract non-scaled fields from buffer
1718
+ const connWinTmsRaw = this.extractValue(buffer, 0, 'uint16');
1719
+ const connRvrtTmsRaw = this.extractValue(buffer, 1, 'uint16');
1720
+ const connRaw = this.extractValue(buffer, 2, 'uint16');
1721
+ const wMaxLimPctWinTmsRaw = this.extractValue(buffer, 4, 'uint16');
1722
+ const wMaxLimPctRvrtTmsRaw = this.extractValue(buffer, 5, 'uint16');
1723
+ const wMaxLimPctRmpTmsRaw = this.extractValue(buffer, 6, 'uint16');
1724
+ const wMaxLimEnaRaw = this.extractValue(buffer, 7, 'uint16');
1725
+ const outPFSetWinTmsRaw = this.extractValue(buffer, 9, 'uint16');
1726
+ const outPFSetRvrtTmsRaw = this.extractValue(buffer, 10, 'uint16');
1727
+ const outPFSetRmpTmsRaw = this.extractValue(buffer, 11, 'uint16');
1728
+ const outPFSetEnaRaw = this.extractValue(buffer, 12, 'uint16');
1729
+ const vArPctWinTmsRaw = this.extractValue(buffer, 16, 'uint16');
1730
+ const vArPctRvrtTmsRaw = this.extractValue(buffer, 17, 'uint16');
1731
+ const vArPctRmpTmsRaw = this.extractValue(buffer, 18, 'uint16');
1732
+ const vArPctModRaw = this.extractValue(buffer, 19, 'uint16');
1733
+ const vArPctEnaRaw = this.extractValue(buffer, 20, 'uint16');
1693
1734
  this.logRegisterRead(123, 0, 'Conn_WinTms', connWinTmsRaw, 'uint16');
1694
1735
  this.logRegisterRead(123, 1, 'Conn_RvrtTms', connRvrtTmsRaw, 'uint16');
1695
1736
  this.logRegisterRead(123, 2, 'Conn', connRaw, 'enum16');
@@ -1715,23 +1756,23 @@ class SunspecModbusClient {
1715
1756
  Conn_RvrtTms: connRvrtTmsRaw,
1716
1757
  Conn: connRaw,
1717
1758
  // Power limit control - Offsets 3-7
1718
- WMaxLimPct: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 3, 1, 'uint16'), scaleFactors.WMaxLimPct_SF, 'uint16', 'Power Limit Percentage', 3, 123),
1759
+ WMaxLimPct: this.applyScaleFactor(this.extractValue(buffer, 3, 'uint16'), scaleFactors.WMaxLimPct_SF, 'uint16', 'Power Limit Percentage', 3, 123),
1719
1760
  WMaxLimPct_SF: scaleFactors.WMaxLimPct_SF,
1720
1761
  WMaxLimPct_WinTms: wMaxLimPctWinTmsRaw,
1721
1762
  WMaxLimPct_RvrtTms: wMaxLimPctRvrtTmsRaw,
1722
1763
  WMaxLimPct_RmpTms: wMaxLimPctRmpTmsRaw,
1723
1764
  WMaxLim_Ena: wMaxLimEnaRaw,
1724
1765
  // Power factor control - Offsets 8-12
1725
- OutPFSet: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 8, 1, 'int16'), scaleFactors.OutPFSet_SF, 'int16', 'Output Power Factor Set', 8, 123),
1766
+ OutPFSet: this.applyScaleFactor(this.extractValue(buffer, 8, 'int16'), scaleFactors.OutPFSet_SF, 'int16', 'Output Power Factor Set', 8, 123),
1726
1767
  OutPFSet_SF: scaleFactors.OutPFSet_SF,
1727
1768
  OutPFSet_WinTms: outPFSetWinTmsRaw,
1728
1769
  OutPFSet_RvrtTms: outPFSetRvrtTmsRaw,
1729
1770
  OutPFSet_RmpTms: outPFSetRmpTmsRaw,
1730
1771
  OutPFSet_Ena: outPFSetEnaRaw,
1731
1772
  // Reactive power control - Offsets 13-20
1732
- VArWMaxPct: this.applyScaleFactor(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),
1773
+ VArWMaxPct: this.applyScaleFactor(this.extractValue(buffer, 13, 'int16'), scaleFactors.VArPct_SF, 'int16', 'Reactive Power at Max Power %', 13, 123),
1774
+ VArMaxPct: this.applyScaleFactor(this.extractValue(buffer, 14, 'int16'), scaleFactors.VArPct_SF, 'int16', 'Max Reactive Power %', 14, 123),
1775
+ VArAvalPct: this.applyScaleFactor(this.extractValue(buffer, 15, 'int16'), scaleFactors.VArPct_SF, 'int16', 'Available Reactive Power %', 15, 123),
1735
1776
  VArPct_SF: scaleFactors.VArPct_SF,
1736
1777
  VArPct_WinTms: vArPctWinTmsRaw,
1737
1778
  VArPct_RvrtTms: vArPctRvrtTmsRaw,
@@ -1739,6 +1780,7 @@ class SunspecModbusClient {
1739
1780
  VArPct_Mod: vArPctModRaw,
1740
1781
  VArPct_Ena: vArPctEnaRaw
1741
1782
  };
1783
+ console.debug('[Model 123] Inverter Controls:', controls);
1742
1784
  return controls;
1743
1785
  }
1744
1786
  catch (error) {