Skip to content

Commit f74ab91

Browse files
committed
Fix MOD battery power sensors using correct register 31126
Based on official Growatt documentation and user feedback, the MOD series inverters use register 31126 (signed INT32) for battery charge/discharge power instead of the old registers 3178-3181 which always return zero. Changes: - Update MOD profile to use register 31126-31127 for battery power (positive = charging, negative = discharging) - Add support for reading 31000-32000 register range (BDC/BMS data) - Extend 3000 range read to 3250 to cover all MOD battery registers - Update battery power reading logic to handle signed battery_power register - Fix register naming for battery energy registers to match code expectations - Maintain backward compatibility with SPH-style separate charge/discharge registers This resolves the issue where MOD users reported battery charge/discharge power sensors showing zero values. Refs: User scan data showing register 31126 contains valid battery power data
1 parent 20e8948 commit f74ab91

2 files changed

Lines changed: 76 additions & 41 deletions

File tree

custom_components/growatt_modbus/growatt_modbus.py

Lines changed: 62 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,7 @@ def read_all_data(self) -> Optional[GrowattData]:
410410
has_base_range = any(0 <= addr < 1000 for addr in addresses)
411411
has_storage_range = any(1000 <= addr < 2000 for addr in addresses)
412412
has_3000_range = any(3000 <= addr < 4000 for addr in addresses)
413+
has_31000_range = any(31000 <= addr < 32000 for addr in addresses)
413414

414415
# Read base range (0-124) if needed - SPH models
415416
if has_base_range:
@@ -435,18 +436,31 @@ def read_all_data(self) -> Optional[GrowattData]:
435436
for i, value in enumerate(registers):
436437
self._register_cache[1000 + i] = value
437438

438-
# Read 3000 range if needed - MIN models
439+
# Read 3000 range if needed - MIN/MOD models
439440
if has_3000_range:
440-
logger.debug("Reading 3000 range (3000-3110)")
441-
registers = self.read_input_registers(3000, 111)
441+
logger.debug("Reading 3000 range (3000-3250)")
442+
# Extended range to cover MOD battery registers up to 3250
443+
registers = self.read_input_registers(3000, 251)
442444
if registers is None:
443445
logger.error("Failed to read main input register block")
444446
return None
445-
447+
446448
# Populate cache
447449
for i, value in enumerate(registers):
448450
self._register_cache[3000 + i] = value
449-
451+
452+
# Read 31000 range if needed - MOD extended battery/BMS range
453+
if has_31000_range:
454+
logger.debug("Reading 31000 range (31100-31300)")
455+
registers = self.read_input_registers(31100, 201)
456+
if registers is None:
457+
logger.warning("Failed to read extended battery register block (31100+)")
458+
# Don't return None - continue with what we have
459+
else:
460+
# Populate cache
461+
for i, value in enumerate(registers):
462+
self._register_cache[31100 + i] = value
463+
450464
# Now extract values using the register map
451465
try:
452466
# Status
@@ -678,31 +692,53 @@ def _read_battery_data(self, data: GrowattData) -> None:
678692
data.battery_temp = self._get_register_value(addr) or 0.0
679693
logger.debug(f"Battery temp from reg {addr}: {data.battery_temp}°C")
680694

681-
# Charge power (try to read from registers first)
682-
addr = self._find_register_by_name('charge_power_low')
695+
# Battery power (signed: positive=charging, negative=discharging)
696+
# Try new signed battery_power register first (MOD series @ 31126)
697+
addr = self._find_register_by_name('battery_power_low')
683698
if addr:
684699
raw_low = self._register_cache.get(addr, 0)
685-
pair_addr = self._find_register_by_name('charge_power_high')
700+
pair_addr = self._find_register_by_name('battery_power_high')
686701
raw_high = self._register_cache.get(pair_addr, 0) if pair_addr else 0
687-
data.charge_power = self._get_register_value(addr) or 0.0
688-
logger.debug(f"Charge power: HIGH={raw_high} (reg {pair_addr}), LOW={raw_low} (reg {addr}) → {data.charge_power}W")
689-
elif data.battery_voltage > 0 and data.battery_current < 0:
690-
# Fallback: Calculate from V×I when charging (negative current)
691-
data.charge_power = data.battery_voltage * abs(data.battery_current)
692-
logger.debug(f"Charge power (calculated): {data.battery_voltage}V × {abs(data.battery_current)}A = {data.charge_power}W")
702+
battery_power = self._get_register_value(addr) or 0.0
703+
logger.debug(f"Battery power (signed): HIGH={raw_high} (reg {pair_addr}), LOW={raw_low} (reg {addr}) → {battery_power}W")
693704

694-
# Discharge power (try to read from registers first)
695-
addr = self._find_register_by_name('discharge_power_low')
696-
if addr:
697-
raw_low = self._register_cache.get(addr, 0)
698-
pair_addr = self._find_register_by_name('discharge_power_high')
699-
raw_high = self._register_cache.get(pair_addr, 0) if pair_addr else 0
700-
data.discharge_power = self._get_register_value(addr) or 0.0
701-
logger.debug(f"Discharge power: HIGH={raw_high} (reg {pair_addr}), LOW={raw_low} (reg {addr}) → {data.discharge_power}W")
702-
elif data.battery_voltage > 0 and data.battery_current > 0:
703-
# Fallback: Calculate from V×I when discharging (positive current)
704-
data.discharge_power = data.battery_voltage * data.battery_current
705-
logger.debug(f"Discharge power (calculated): {data.battery_voltage}V × {data.battery_current}A = {data.discharge_power}W")
705+
# Split into charge/discharge based on sign
706+
if battery_power > 0:
707+
data.charge_power = battery_power
708+
data.discharge_power = 0.0
709+
logger.debug(f" → Charging: {data.charge_power}W")
710+
elif battery_power < 0:
711+
data.charge_power = 0.0
712+
data.discharge_power = abs(battery_power)
713+
logger.debug(f" → Discharging: {data.discharge_power}W")
714+
else:
715+
data.charge_power = 0.0
716+
data.discharge_power = 0.0
717+
else:
718+
# Fallback: Try old separate charge/discharge registers (SPH series)
719+
addr = self._find_register_by_name('charge_power_low')
720+
if addr:
721+
raw_low = self._register_cache.get(addr, 0)
722+
pair_addr = self._find_register_by_name('charge_power_high')
723+
raw_high = self._register_cache.get(pair_addr, 0) if pair_addr else 0
724+
data.charge_power = self._get_register_value(addr) or 0.0
725+
logger.debug(f"Charge power: HIGH={raw_high} (reg {pair_addr}), LOW={raw_low} (reg {addr}) → {data.charge_power}W")
726+
elif data.battery_voltage > 0 and data.battery_current < 0:
727+
# Fallback: Calculate from V×I when charging (negative current)
728+
data.charge_power = data.battery_voltage * abs(data.battery_current)
729+
logger.debug(f"Charge power (calculated): {data.battery_voltage}V × {abs(data.battery_current)}A = {data.charge_power}W")
730+
731+
addr = self._find_register_by_name('discharge_power_low')
732+
if addr:
733+
raw_low = self._register_cache.get(addr, 0)
734+
pair_addr = self._find_register_by_name('discharge_power_high')
735+
raw_high = self._register_cache.get(pair_addr, 0) if pair_addr else 0
736+
data.discharge_power = self._get_register_value(addr) or 0.0
737+
logger.debug(f"Discharge power: HIGH={raw_high} (reg {pair_addr}), LOW={raw_low} (reg {addr}) → {data.discharge_power}W")
738+
elif data.battery_voltage > 0 and data.battery_current > 0:
739+
# Fallback: Calculate from V×I when discharging (positive current)
740+
data.discharge_power = data.battery_voltage * data.battery_current
741+
logger.debug(f"Discharge power (calculated): {data.battery_voltage}V × {data.battery_current}A = {data.discharge_power}W")
706742

707743
# Charge energy today
708744
addr = self._find_register_by_name('charge_energy_today_low')

custom_components/growatt_modbus/profiles/mod.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,14 @@
108108
3086: {'name': 'battery_derating_mode', 'scale': 1, 'unit': ''},
109109

110110
# Battery - Discharge/Charge Energy
111-
3125: {'name': 'battery_discharge_today_high', 'scale': 1, 'unit': '', 'pair': 3126},
112-
3126: {'name': 'battery_discharge_today_low', 'scale': 1, 'unit': '', 'pair': 3125, 'combined_scale': 0.1, 'combined_unit': 'kWh'},
113-
3127: {'name': 'battery_discharge_total_high', 'scale': 1, 'unit': '', 'pair': 3128},
114-
3128: {'name': 'battery_discharge_total_low', 'scale': 1, 'unit': '', 'pair': 3127, 'combined_scale': 0.1, 'combined_unit': 'kWh'},
115-
3129: {'name': 'battery_charge_today_high', 'scale': 1, 'unit': '', 'pair': 3130},
116-
3130: {'name': 'battery_charge_today_low', 'scale': 1, 'unit': '', 'pair': 3129, 'combined_scale': 0.1, 'combined_unit': 'kWh'},
117-
3131: {'name': 'battery_charge_total_high', 'scale': 1, 'unit': '', 'pair': 3132},
118-
3132: {'name': 'battery_charge_total_low', 'scale': 1, 'unit': '', 'pair': 3131, 'combined_scale': 0.1, 'combined_unit': 'kWh'},
111+
3125: {'name': 'discharge_energy_today_high', 'scale': 1, 'unit': '', 'pair': 3126},
112+
3126: {'name': 'discharge_energy_today_low', 'scale': 1, 'unit': '', 'pair': 3125, 'combined_scale': 0.1, 'combined_unit': 'kWh'},
113+
3127: {'name': 'discharge_energy_total_high', 'scale': 1, 'unit': '', 'pair': 3128},
114+
3128: {'name': 'discharge_energy_total_low', 'scale': 1, 'unit': '', 'pair': 3127, 'combined_scale': 0.1, 'combined_unit': 'kWh'},
115+
3129: {'name': 'charge_energy_today_high', 'scale': 1, 'unit': '', 'pair': 3130},
116+
3130: {'name': 'charge_energy_today_low', 'scale': 1, 'unit': '', 'pair': 3129, 'combined_scale': 0.1, 'combined_unit': 'kWh'},
117+
3131: {'name': 'charge_energy_total_high', 'scale': 1, 'unit': '', 'pair': 3132},
118+
3132: {'name': 'charge_energy_total_low', 'scale': 1, 'unit': '', 'pair': 3131, 'combined_scale': 0.1, 'combined_unit': 'kWh'},
119119

120120
# Battery State
121121
3144: {'name': 'priority_mode', 'scale': 1, 'unit': '', 'desc': '0=Load, 1=Battery, 2=Grid'},
@@ -124,13 +124,12 @@
124124
3171: {'name': 'battery_soc', 'scale': 1, 'unit': '%'},
125125
3176: {'name': 'battery_temp', 'scale': 0.1, 'unit': '°C'},
126126

127-
# NOTE: Registers 3178-3180 don't exist on some MOD hardware
128-
# If your inverter doesn't have these, battery power will be calculated from V×I
129-
# Battery Power (32-bit) - OPTIONAL, may not exist on all MOD hardware
130-
3178: {'name': 'discharge_power_high', 'scale': 1, 'unit': '', 'pair': 3179},
131-
3179: {'name': 'discharge_power_low', 'scale': 1, 'unit': '', 'pair': 3178, 'combined_scale': 0.1, 'combined_unit': 'W'},
132-
3180: {'name': 'charge_power_high', 'scale': 1, 'unit': '', 'pair': 3181},
133-
3181: {'name': 'charge_power_low', 'scale': 1, 'unit': '', 'pair': 3180, 'combined_scale': 0.1, 'combined_unit': 'W'},
127+
# NOTE: Old registers 3178-3181 don't work on most MOD hardware (always return 0)
128+
# Growatt confirmed the correct register is 31126 (BDC/BMS Battery Information range)
129+
# Battery Power (32-bit, signed) - Register 31126
130+
# Positive = charging, Negative = discharging
131+
31126: {'name': 'battery_power_high', 'scale': 1, 'unit': '', 'pair': 31127},
132+
31127: {'name': 'battery_power_low', 'scale': 1, 'unit': '', 'pair': 31126, 'combined_scale': 0.1, 'combined_unit': 'W', 'signed': True},
134133
},
135134
'holding_registers': {
136135
# Basic control

0 commit comments

Comments
 (0)