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