Skip to content

Commit 3d202e3

Browse files
authored
Merge pull request #2281 from rosenrot00/fix/growatt-apx-bms1-module1-registers
Fix Growatt APX BMS1 module 1 registers
2 parents a201b84 + c3f848d commit 3d202e3

2 files changed

Lines changed: 113 additions & 7 deletions

File tree

custom_components/solax_modbus/plugin_growatt.py

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import logging
2-
from dataclasses import dataclass
2+
from dataclasses import dataclass, replace
33
from typing import Any
44

55
from homeassistant.components.number import NumberDeviceClass
@@ -94,6 +94,12 @@
9494
MPPT10 = 0x400000
9595
ALL_MPPT_GROUP = MPPT3 | MPPT4 | MPPT6 | MPPT8 | MPPT10
9696

97+
# DLP MID 30KTL3-XH units expose BMS1 module 1 through the APX input-register block.
98+
APX_BMS_INPUT = 0x800000
99+
ALL_APX_BMS_REGISTER_GROUP = APX_BMS_INPUT
100+
101+
APX_BMS_INPUT_SERIAL_PREFIXES = ["DLP"]
102+
97103
ALLDEFAULT = 0 # should be equivalent to HYBRID | AC | GEN | GEN2 | GEN3 | GEN4 | X1 | X3
98104

99105
# SPF models known to expose only one PV input / MPPT even if the generic SPF block defines PV2 sensors.
@@ -6695,6 +6701,7 @@ def value_function_inverter_module(initval: int, descr: Any, datadict: dict[str,
66956701
scale=value_function_module_status,
66966702
allowedtypes=HYBRID | GEN4,
66976703
entity_registry_enabled_default=False,
6704+
blacklist=APX_BMS_INPUT_SERIAL_PREFIXES,
66986705
icon="mdi:battery",
66996706
),
67006707
GrowattModbusSensorEntityDescription(
@@ -6716,10 +6723,11 @@ def value_function_inverter_module(initval: int, descr: Any, datadict: dict[str,
67166723
device_class=SensorDeviceClass.BATTERY,
67176724
state_class=SensorStateClass.MEASUREMENT,
67186725
register=5882,
6719-
register_type=REG_HOLDING, ### HOLDING!!!
6726+
register_type=REG_HOLDING,
67206727
register_data_type=REGISTER_U16,
67216728
allowedtypes=HYBRID | GEN4,
67226729
entity_registry_enabled_default=False,
6730+
blacklist=APX_BMS_INPUT_SERIAL_PREFIXES,
67236731
icon="mdi:battery-heart",
67246732
),
67256733
GrowattModbusSensorEntityDescription(
@@ -6729,11 +6737,12 @@ def value_function_inverter_module(initval: int, descr: Any, datadict: dict[str,
67296737
device_class=SensorDeviceClass.VOLTAGE,
67306738
state_class=SensorStateClass.MEASUREMENT,
67316739
register=5883,
6732-
register_type=REG_HOLDING, ### HOLDING!!!
6740+
register_type=REG_HOLDING,
67336741
register_data_type=REGISTER_U16,
67346742
scale=0.1,
67356743
allowedtypes=GEN4 | HYBRID,
67366744
entity_registry_enabled_default=False,
6745+
blacklist=APX_BMS_INPUT_SERIAL_PREFIXES,
67376746
icon="mdi:battery",
67386747
),
67396748
GrowattModbusSensorEntityDescription(
@@ -6743,11 +6752,12 @@ def value_function_inverter_module(initval: int, descr: Any, datadict: dict[str,
67436752
device_class=SensorDeviceClass.CURRENT,
67446753
state_class=SensorStateClass.MEASUREMENT,
67456754
register=5884,
6746-
register_type=REG_HOLDING, ### HOLDING!!!
6755+
register_type=REG_HOLDING,
67476756
register_data_type=REGISTER_U16,
67486757
scale=value_function_bms_module_combined_current,
67496758
allowedtypes=GEN4 | HYBRID,
67506759
entity_registry_enabled_default=False,
6760+
blacklist=APX_BMS_INPUT_SERIAL_PREFIXES,
67516761
icon="mdi:battery",
67526762
),
67536763
GrowattModbusSensorEntityDescription(
@@ -6762,6 +6772,7 @@ def value_function_inverter_module(initval: int, descr: Any, datadict: dict[str,
67626772
register_data_type=REGISTER_U16,
67636773
entity_registry_enabled_default=True,
67646774
allowedtypes=GEN4 | HYBRID,
6775+
blacklist=APX_BMS_INPUT_SERIAL_PREFIXES,
67656776
icon="mdi:battery",
67666777
),
67676778
GrowattModbusSensorEntityDescription(
@@ -6771,11 +6782,12 @@ def value_function_inverter_module(initval: int, descr: Any, datadict: dict[str,
67716782
device_class=SensorDeviceClass.ENERGY,
67726783
state_class=SensorStateClass.TOTAL_INCREASING,
67736784
register=5887, # maybe 5886 and U32
6774-
register_type=REG_HOLDING, ### HOLDING!!!
6785+
register_type=REG_HOLDING,
67756786
register_data_type=REGISTER_U16, # maybe U32 but then change register to 5886
67766787
scale=0.1,
67776788
allowedtypes=GEN4 | HYBRID,
67786789
entity_registry_enabled_default=False,
6790+
blacklist=APX_BMS_INPUT_SERIAL_PREFIXES,
67796791
icon="mdi:battery",
67806792
),
67816793
GrowattModbusSensorEntityDescription(
@@ -6790,6 +6802,7 @@ def value_function_inverter_module(initval: int, descr: Any, datadict: dict[str,
67906802
scale=0.1,
67916803
allowedtypes=GEN4 | HYBRID,
67926804
entity_registry_enabled_default=False,
6805+
blacklist=APX_BMS_INPUT_SERIAL_PREFIXES,
67936806
icon="mdi:battery",
67946807
),
67956808
GrowattModbusSensorEntityDescription(
@@ -6804,6 +6817,7 @@ def value_function_inverter_module(initval: int, descr: Any, datadict: dict[str,
68046817
scale=0.1,
68056818
allowedtypes=GEN4 | HYBRID,
68066819
entity_registry_enabled_default=False,
6820+
blacklist=APX_BMS_INPUT_SERIAL_PREFIXES,
68076821
icon="mdi:battery",
68086822
),
68096823
GrowattModbusSensorEntityDescription(
@@ -6815,6 +6829,7 @@ def value_function_inverter_module(initval: int, descr: Any, datadict: dict[str,
68156829
scale=value_function_module_warning_text,
68166830
allowedtypes=HYBRID | GEN4,
68176831
entity_registry_enabled_default=False,
6832+
blacklist=APX_BMS_INPUT_SERIAL_PREFIXES,
68186833
icon="mdi:battery",
68196834
),
68206835
GrowattModbusSensorEntityDescription(
@@ -6825,6 +6840,7 @@ def value_function_inverter_module(initval: int, descr: Any, datadict: dict[str,
68256840
register_data_type=REGISTER_U16,
68266841
allowedtypes=GEN4 | HYBRID,
68276842
entity_registry_enabled_default=False,
6843+
blacklist=APX_BMS_INPUT_SERIAL_PREFIXES,
68286844
icon="mdi:battery",
68296845
),
68306846
GrowattModbusSensorEntityDescription(
@@ -9174,6 +9190,33 @@ def value_function_inverter_module(initval: int, descr: Any, datadict: dict[str,
91749190
),
91759191
]
91769192

9193+
APX_BMS1_MODULE1_INPUT_REGISTERS = {
9194+
"bms_1_module_1_status": 5080,
9195+
"bms_1_module_1_soh": 5082,
9196+
"bms_1_module_1_volt": 5083,
9197+
"bms_1_module_1_combined_current": 5084,
9198+
"bms_1_module_1_combined_power": 5085,
9199+
"bms_1_module_1_toe": 5087,
9200+
"bms_1_module_1_max_cell_temp": 5090,
9201+
"bms_1_module_1_min_cell_temp": 5091,
9202+
"bms_1_module_1_warning_text": 5098,
9203+
"bms_1_module_1_charge_cycles": 5108,
9204+
}
9205+
9206+
# Keep the established 588x holding-register descriptions for other Growatt models,
9207+
# and create a DLP-only 508x input-register variant from the same metadata.
9208+
SENSOR_TYPES.extend(
9209+
replace(
9210+
description,
9211+
register=APX_BMS1_MODULE1_INPUT_REGISTERS[description.key],
9212+
register_type=REG_INPUT,
9213+
allowedtypes=description.allowedtypes | APX_BMS_INPUT,
9214+
blacklist=None,
9215+
)
9216+
for description in tuple(SENSOR_TYPES)
9217+
if description.key in APX_BMS1_MODULE1_INPUT_REGISTERS
9218+
)
9219+
91779220

91789221
TIME_TYPES = [
91799222
GrowattModbusTimeEntityDescription(
@@ -9684,7 +9727,7 @@ def value_function_inverter_module(initval: int, descr: Any, datadict: dict[str,
96849727
"KMN": HYBRID | GEN4 | X3, # MID 17000 TL3-XH Hybrid, 2 MPPT
96859728
"KNN": HYBRID | GEN4 | X3 | MPPT3, # MID 25000 TL3-XH Hybrid, 3 MPPT
96869729
"RKM": HYBRID | GEN4 | X3 | MPPT3, # MID 30000 TL3-XH Hybrid, 3 MPPT
9687-
"DLP": HYBRID | GEN4 | X3 | MPPT3, # MID 30000 TL3-XH Hybrid, 3 MPPT
9730+
"DLP": HYBRID | GEN4 | X3 | MPPT3 | APX_BMS_INPUT, # MID 30000 TL3-XH Hybrid, 3 MPPT
96889731
# MOD BP hybrid
96899732
"FMP": HYBRID | GEN4 | X3, # MOD 5000 TL3-XH (BP) Hybrid, 2 MPPT
96909733
"FPP": HYBRID | GEN4 | X3, # MOD 7000 TL3-XH (BP) Hybrid, 2 MPPT
@@ -9825,12 +9868,13 @@ def matchInverterWithMask(
98259868
epsmatch = ((inverterspec & entitymask & ALL_EPS_GROUP) != 0) or (entitymask & ALL_EPS_GROUP == 0)
98269869
dcbmatch = ((inverterspec & entitymask & ALL_DCB_GROUP) != 0) or (entitymask & ALL_DCB_GROUP == 0)
98279870
mpptmatch = ((inverterspec & entitymask & ALL_MPPT_GROUP) != 0) or (entitymask & ALL_MPPT_GROUP == 0)
9871+
apx_bms_register_match = ((inverterspec & entitymask & ALL_APX_BMS_REGISTER_GROUP) != 0) or (entitymask & ALL_APX_BMS_REGISTER_GROUP == 0)
98289872
blacklisted = False
98299873
if blacklist:
98309874
for start in blacklist:
98319875
if serialnumber.startswith(start):
98329876
blacklisted = True
9833-
return (genmatch and xmatch and hybmatch and epsmatch and dcbmatch and mpptmatch) and not blacklisted
9877+
return (genmatch and xmatch and hybmatch and epsmatch and dcbmatch and mpptmatch and apx_bms_register_match) and not blacklisted
98349878

98359879

98369880
ENERGY_DASHBOARD_MAPPING = EnergyDashboardMapping(
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from custom_components.solax_modbus.const import REG_HOLDING, REG_INPUT
2+
from custom_components.solax_modbus.plugin_growatt import (
3+
SENSOR_TYPES,
4+
SERIAL_PREFIX_TYPES,
5+
GrowattModbusSensorEntityDescription,
6+
plugin_instance,
7+
)
8+
9+
10+
def _matching_module1_descriptions(serial_number: str) -> dict[str, GrowattModbusSensorEntityDescription]:
11+
inverter_type = SERIAL_PREFIX_TYPES[serial_number[:3]]
12+
descriptions: dict[str, GrowattModbusSensorEntityDescription] = {}
13+
for description in SENSOR_TYPES:
14+
if not description.key.startswith("bms_1_module_1_") or not plugin_instance.matchInverterWithMask(
15+
inverter_type, description.allowedtypes, serial_number, description.blacklist
16+
):
17+
continue
18+
assert description.key not in descriptions, f"duplicate matching description for {serial_number}: {description.key}"
19+
descriptions[description.key] = description
20+
return descriptions
21+
22+
23+
def test_dlp_bms1_module1_uses_apx_input_registers() -> None:
24+
expected_input_registers = {
25+
"bms_1_module_1_status": 5080,
26+
"bms_1_module_1_soh": 5082,
27+
"bms_1_module_1_volt": 5083,
28+
"bms_1_module_1_combined_current": 5084,
29+
"bms_1_module_1_combined_power": 5085,
30+
"bms_1_module_1_toe": 5087,
31+
"bms_1_module_1_max_cell_temp": 5090,
32+
"bms_1_module_1_min_cell_temp": 5091,
33+
"bms_1_module_1_warning_text": 5098,
34+
"bms_1_module_1_charge_cycles": 5108,
35+
}
36+
descriptions = _matching_module1_descriptions("DLP1234567")
37+
38+
for key, register in expected_input_registers.items():
39+
description = descriptions[key]
40+
assert description.register == register
41+
assert description.register_type == REG_INPUT
42+
43+
44+
def test_jcm_bms1_module1_keeps_holding_registers() -> None:
45+
expected_holding_registers = {
46+
"bms_1_module_1_status": 5880,
47+
"bms_1_module_1_soh": 5882,
48+
"bms_1_module_1_volt": 5883,
49+
"bms_1_module_1_combined_current": 5884,
50+
"bms_1_module_1_combined_power": 5885,
51+
"bms_1_module_1_toe": 5887,
52+
"bms_1_module_1_max_cell_temp": 5890,
53+
"bms_1_module_1_min_cell_temp": 5891,
54+
"bms_1_module_1_warning_text": 5898,
55+
"bms_1_module_1_charge_cycles": 5908,
56+
}
57+
descriptions = _matching_module1_descriptions("JCM0D12345")
58+
59+
for key, register in expected_holding_registers.items():
60+
description = descriptions[key]
61+
assert description.register == register
62+
assert description.register_type == REG_HOLDING

0 commit comments

Comments
 (0)