Skip to content

Commit edbd203

Browse files
authored
Merge branch 'dev' into claude/growatt-inverter-emulator-01CoSswioNUhF3Kh2jHuEDZG
2 parents c3cf32c + d794b23 commit edbd203

2 files changed

Lines changed: 121 additions & 33 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Growatt Modbus Integration for Home Assistant ☀️
22

33
![HACS Badge](https://img.shields.io/badge/HACS-Custom-orange.svg)
4-
![Version](https://img.shields.io/badge/Version-0.0.7b1-blue.svg)
4+
![Version](https://img.shields.io/badge/Version-0.0.7b2-blue.svg)
55
[![GitHub Issues](https://img.shields.io/github/issues/0xAHA/Growatt_ModbusTCP.svg)](https://github.com/0xAHA/Growatt_ModbusTCP/issues)
66
[![GitHub Stars](https://img.shields.io/github/stars/0xAHA/Growatt_ModbusTCP.svg?style=social)](https://github.com/0xAHA/Growatt_ModbusTCP)
77

custom_components/growatt_modbus/growatt_modbus.py

Lines changed: 120 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,27 @@ class GrowattData:
7272
pv3_power: float = 0.0 # W
7373
pv_total_power: float = 0.0 # W
7474

75-
# AC Output
75+
# AC Output (generic - usually Phase R for 3-phase)
7676
ac_voltage: float = 0.0 # V
7777
ac_current: float = 0.0 # A
7878
ac_power: float = 0.0 # W
7979
ac_frequency: float = 0.0 # Hz
80+
81+
# Three-Phase AC Output (individual phases)
82+
ac_voltage_r: float = 0.0 # V (Phase R/L1)
83+
ac_current_r: float = 0.0 # A (Phase R/L1)
84+
ac_power_r: float = 0.0 # W (Phase R/L1)
85+
ac_voltage_s: float = 0.0 # V (Phase S/L2)
86+
ac_current_s: float = 0.0 # A (Phase S/L2)
87+
ac_power_s: float = 0.0 # W (Phase S/L2)
88+
ac_voltage_t: float = 0.0 # V (Phase T/L3)
89+
ac_current_t: float = 0.0 # A (Phase T/L3)
90+
ac_power_t: float = 0.0 # W (Phase T/L3)
91+
92+
# Line-to-Line Voltages (3-phase only)
93+
ac_voltage_rs: float = 0.0 # V
94+
ac_voltage_st: float = 0.0 # V
95+
ac_voltage_tr: float = 0.0 # V
8096

8197
# Power Flow (storage/hybrid models)
8298
power_to_user: float = 0.0 # W
@@ -438,8 +454,9 @@ def read_all_data(self) -> Optional[GrowattData]:
438454

439455
# Read 3000 range if needed - MIN/MOD models
440456
if has_3000_range:
441-
logger.debug("Reading 3000 range (3000-3110)")
442-
registers = self.read_input_registers(3000, 111)
457+
logger.debug("Reading 3000 range (3000-3250)")
458+
# Extended range to cover MOD battery registers up to 3250
459+
registers = self.read_input_registers(3000, 251)
443460
if registers is None:
444461
logger.error("Failed to read main input register block")
445462
return None
@@ -448,12 +465,12 @@ def read_all_data(self) -> Optional[GrowattData]:
448465
for i, value in enumerate(registers):
449466
self._register_cache[3000 + i] = value
450467

451-
# Read 31000 range if needed - MOD battery/BMS data
468+
# Read 31000 range if needed - MOD extended battery/BMS range
452469
if has_31000_range:
453-
logger.debug("Reading 31000 range (31100-31299)")
454-
registers = self.read_input_registers(31100, 200)
470+
logger.debug("Reading 31000 range (31100-31300)")
471+
registers = self.read_input_registers(31100, 201)
455472
if registers is None:
456-
logger.warning("Failed to read 31000 register block (MOD battery data may be unavailable)")
473+
logger.warning("Failed to read extended battery register block (31100+)")
457474
# Don't return None - continue with what we have
458475
else:
459476
# Populate cache
@@ -509,12 +526,12 @@ def read_all_data(self) -> Optional[GrowattData]:
509526
# Calculate from strings if not available
510527
data.pv_total_power = data.pv1_power + data.pv2_power + data.pv3_power
511528

512-
# AC Output
529+
# AC Output (generic - will use Phase R via alias for 3-phase)
513530
ac_voltage_addr = self._find_register_by_name('ac_voltage')
514531
ac_current_addr = self._find_register_by_name('ac_current')
515532
ac_power_addr = self._find_register_by_name('ac_power_low')
516533
ac_freq_addr = self._find_register_by_name('ac_frequency')
517-
534+
518535
if ac_voltage_addr:
519536
data.ac_voltage = self._get_register_value(ac_voltage_addr) or 0.0
520537
if ac_current_addr:
@@ -523,6 +540,51 @@ def read_all_data(self) -> Optional[GrowattData]:
523540
data.ac_power = self._get_register_value(ac_power_addr) or 0.0
524541
if ac_freq_addr:
525542
data.ac_frequency = self._get_register_value(ac_freq_addr) or 0.0
543+
544+
# Three-Phase AC Output (individual phases)
545+
# Phase R
546+
ac_voltage_r_addr = self._find_register_by_name('ac_voltage_r')
547+
ac_current_r_addr = self._find_register_by_name('ac_current_r')
548+
ac_power_r_addr = self._find_register_by_name('ac_power_r_low')
549+
if ac_voltage_r_addr:
550+
data.ac_voltage_r = self._get_register_value(ac_voltage_r_addr) or 0.0
551+
if ac_current_r_addr:
552+
data.ac_current_r = self._get_register_value(ac_current_r_addr) or 0.0
553+
if ac_power_r_addr:
554+
data.ac_power_r = self._get_register_value(ac_power_r_addr) or 0.0
555+
556+
# Phase S
557+
ac_voltage_s_addr = self._find_register_by_name('ac_voltage_s')
558+
ac_current_s_addr = self._find_register_by_name('ac_current_s')
559+
ac_power_s_addr = self._find_register_by_name('ac_power_s_low')
560+
if ac_voltage_s_addr:
561+
data.ac_voltage_s = self._get_register_value(ac_voltage_s_addr) or 0.0
562+
if ac_current_s_addr:
563+
data.ac_current_s = self._get_register_value(ac_current_s_addr) or 0.0
564+
if ac_power_s_addr:
565+
data.ac_power_s = self._get_register_value(ac_power_s_addr) or 0.0
566+
567+
# Phase T
568+
ac_voltage_t_addr = self._find_register_by_name('ac_voltage_t')
569+
ac_current_t_addr = self._find_register_by_name('ac_current_t')
570+
ac_power_t_addr = self._find_register_by_name('ac_power_t_low')
571+
if ac_voltage_t_addr:
572+
data.ac_voltage_t = self._get_register_value(ac_voltage_t_addr) or 0.0
573+
if ac_current_t_addr:
574+
data.ac_current_t = self._get_register_value(ac_current_t_addr) or 0.0
575+
if ac_power_t_addr:
576+
data.ac_power_t = self._get_register_value(ac_power_t_addr) or 0.0
577+
578+
# Line-to-Line Voltages
579+
ac_voltage_rs_addr = self._find_register_by_name('line_voltage_rs')
580+
ac_voltage_st_addr = self._find_register_by_name('line_voltage_st')
581+
ac_voltage_tr_addr = self._find_register_by_name('line_voltage_tr')
582+
if ac_voltage_rs_addr:
583+
data.ac_voltage_rs = self._get_register_value(ac_voltage_rs_addr) or 0.0
584+
if ac_voltage_st_addr:
585+
data.ac_voltage_st = self._get_register_value(ac_voltage_st_addr) or 0.0
586+
if ac_voltage_tr_addr:
587+
data.ac_voltage_tr = self._get_register_value(ac_voltage_tr_addr) or 0.0
526588

527589
# Power Flow (if available - storage/hybrid models)
528590
power_to_user_addr = self._find_register_by_name('power_to_user_low')
@@ -624,11 +686,15 @@ def write_register(self, register: int, value: int) -> bool:
624686

625687

626688
def _find_register_by_name(self, name: str) -> Optional[int]:
627-
"""Find register address by its name"""
689+
"""Find register address by its name or alias"""
628690
input_regs = self.register_map['input_registers']
629691
for addr, reg_info in input_regs.items():
692+
# Check exact name match
630693
if reg_info['name'] == name:
631694
return addr
695+
# Check alias match (for 3-phase compatibility)
696+
if reg_info.get('alias') == name:
697+
return addr
632698
return None
633699

634700
def _read_energy_breakdown(self, data: GrowattData) -> None:
@@ -691,31 +757,53 @@ def _read_battery_data(self, data: GrowattData) -> None:
691757
data.battery_temp = self._get_register_value(addr) or 0.0
692758
logger.debug(f"Battery temp from reg {addr}: {data.battery_temp}°C")
693759

694-
# Charge power (try to read from registers first)
695-
addr = self._find_register_by_name('charge_power_low')
696-
if addr:
697-
raw_low = self._register_cache.get(addr, 0)
698-
pair_addr = self._find_register_by_name('charge_power_high')
699-
raw_high = self._register_cache.get(pair_addr, 0) if pair_addr else 0
700-
data.charge_power = self._get_register_value(addr) or 0.0
701-
logger.debug(f"Charge power: HIGH={raw_high} (reg {pair_addr}), LOW={raw_low} (reg {addr}) → {data.charge_power}W")
702-
elif data.battery_voltage > 0 and data.battery_current < 0:
703-
# Fallback: Calculate from V×I when charging (negative current)
704-
data.charge_power = data.battery_voltage * abs(data.battery_current)
705-
logger.debug(f"Charge power (calculated): {data.battery_voltage}V × {abs(data.battery_current)}A = {data.charge_power}W")
706-
707-
# Discharge power (try to read from registers first)
708-
addr = self._find_register_by_name('discharge_power_low')
760+
# Battery power (signed: positive=charging, negative=discharging)
761+
# Try new signed battery_power register first (MOD series @ 31126)
762+
addr = self._find_register_by_name('battery_power_low')
709763
if addr:
710764
raw_low = self._register_cache.get(addr, 0)
711-
pair_addr = self._find_register_by_name('discharge_power_high')
765+
pair_addr = self._find_register_by_name('battery_power_high')
712766
raw_high = self._register_cache.get(pair_addr, 0) if pair_addr else 0
713-
data.discharge_power = self._get_register_value(addr) or 0.0
714-
logger.debug(f"Discharge power: HIGH={raw_high} (reg {pair_addr}), LOW={raw_low} (reg {addr}) → {data.discharge_power}W")
715-
elif data.battery_voltage > 0 and data.battery_current > 0:
716-
# Fallback: Calculate from V×I when discharging (positive current)
717-
data.discharge_power = data.battery_voltage * data.battery_current
718-
logger.debug(f"Discharge power (calculated): {data.battery_voltage}V × {data.battery_current}A = {data.discharge_power}W")
767+
battery_power = self._get_register_value(addr) or 0.0
768+
logger.debug(f"Battery power (signed): HIGH={raw_high} (reg {pair_addr}), LOW={raw_low} (reg {addr}) → {battery_power}W")
769+
770+
# Split into charge/discharge based on sign
771+
if battery_power > 0:
772+
data.charge_power = battery_power
773+
data.discharge_power = 0.0
774+
logger.debug(f" → Charging: {data.charge_power}W")
775+
elif battery_power < 0:
776+
data.charge_power = 0.0
777+
data.discharge_power = abs(battery_power)
778+
logger.debug(f" → Discharging: {data.discharge_power}W")
779+
else:
780+
data.charge_power = 0.0
781+
data.discharge_power = 0.0
782+
else:
783+
# Fallback: Try old separate charge/discharge registers (SPH series)
784+
addr = self._find_register_by_name('charge_power_low')
785+
if addr:
786+
raw_low = self._register_cache.get(addr, 0)
787+
pair_addr = self._find_register_by_name('charge_power_high')
788+
raw_high = self._register_cache.get(pair_addr, 0) if pair_addr else 0
789+
data.charge_power = self._get_register_value(addr) or 0.0
790+
logger.debug(f"Charge power: HIGH={raw_high} (reg {pair_addr}), LOW={raw_low} (reg {addr}) → {data.charge_power}W")
791+
elif data.battery_voltage > 0 and data.battery_current < 0:
792+
# Fallback: Calculate from V×I when charging (negative current)
793+
data.charge_power = data.battery_voltage * abs(data.battery_current)
794+
logger.debug(f"Charge power (calculated): {data.battery_voltage}V × {abs(data.battery_current)}A = {data.charge_power}W")
795+
796+
addr = self._find_register_by_name('discharge_power_low')
797+
if addr:
798+
raw_low = self._register_cache.get(addr, 0)
799+
pair_addr = self._find_register_by_name('discharge_power_high')
800+
raw_high = self._register_cache.get(pair_addr, 0) if pair_addr else 0
801+
data.discharge_power = self._get_register_value(addr) or 0.0
802+
logger.debug(f"Discharge power: HIGH={raw_high} (reg {pair_addr}), LOW={raw_low} (reg {addr}) → {data.discharge_power}W")
803+
elif data.battery_voltage > 0 and data.battery_current > 0:
804+
# Fallback: Calculate from V×I when discharging (positive current)
805+
data.discharge_power = data.battery_voltage * data.battery_current
806+
logger.debug(f"Discharge power (calculated): {data.battery_voltage}V × {data.battery_current}A = {data.discharge_power}W")
719807

720808
# Charge energy today
721809
addr = self._find_register_by_name('charge_energy_today_low')

0 commit comments

Comments
 (0)