Skip to content

Commit 0f5c738

Browse files
0xAHAclaude
andcommitted
fix: backup box sensors missing on MIN/TL-XH + diagnostic scanner DTC bypass (#352)
Two bugs preventing backup box sensors from appearing on MIN 4200TL-XH: 1. read_all_data() never populated box_* GrowattData fields The 3000-range registers (3281-3342) were read into _register_cache correctly by the gap-detection chunker, but there was no code mapping cache -> dataclass. All 10 box_* fields stayed at default 0 permanently, so box_connect_flag was always 0 and the deferred sensor mechanism never added the 8 conditional sensors. Fix: add _read_backup_box_data() method called from read_all_data(). 2. Diagnostic scanner overwrote correct DTC result with MOD heuristic DTC 5100 correctly identifies TL-XH. When register 30099 = 0 (legacy protocol), confidence is downgraded from Very High to High. The early-return guard checked confidence == Very High so it fired only on the non-downgraded path, allowing register heuristics to re-classify the device as MOD 6000-15000TL3-XH. Fix: guard now checks dtc_code + profile_key presence instead of confidence level. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 0d5835f commit 0f5c738

5 files changed

Lines changed: 94 additions & 5 deletions

File tree

README.md

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

55
![HACS Badge](https://img.shields.io/badge/HACS-Custom-orange.svg)
6-
![Version](https://img.shields.io/badge/Version-1.0.12-blue.svg)
6+
![Version](https://img.shields.io/badge/Version-1.0.13-blue.svg)
77
[![GitHub Issues](https://img.shields.io/github/issues/0xAHA/Growatt_ModbusTCP.svg)](https://github.com/0xAHA/Growatt_ModbusTCP/issues)
88
[![GitHub Stars](https://img.shields.io/github/stars/0xAHA/Growatt_ModbusTCP.svg?style=social)](https://github.com/0xAHA/Growatt_ModbusTCP)
99

RELEASENOTES.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,33 @@
44

55
---
66

7+
## v1.0.13
8+
9+
Issues: #352
10+
11+
- **Fix: backup box sensors now appear on MIN/TL-XH inverters (#352):**
12+
All 9 backup box sensors (`box_bypass_status`, `box_work_mode`, `box_error_code`,
13+
`box_warning_code`, `box_temperature`, `box_grid_voltage`, `box_grid_power`,
14+
`box_load_power`, `box_relay_status`) were missing from Home Assistant despite the
15+
hardware reporting live data. Root cause: `read_all_data()` cached the 3000-range
16+
registers correctly but had no code to map them into the `GrowattData` dataclass fields —
17+
all 10 `box_*` fields remained permanently at their default value of 0. Adds a new
18+
`_read_backup_box_data()` method that populates all backup box fields from the register
19+
cache using the existing `_find_register_by_name` / `_get_register_value` pattern.
20+
Only `box_connect_flag` must be non-zero for the other 8 conditional sensors to appear;
21+
`box_connect_flag` itself is always created so its value is always visible.
22+
23+
- **Fix: diagnostic scanner correctly identifies MIN/TL-XH when DTC confidence is downgraded (#352):**
24+
The DTC-based detection correctly identifies a MIN 4200TL-XH as `tl_xh_3000_10000_v201`
25+
(DTC 5100 → "Very High" confidence). However, when register 30099 reads 0 (legacy
26+
protocol), confidence is downgraded to "High" to indicate the legacy profile variant should
27+
be used. The early-return guard checked `confidence == "Very High"` and therefore missed
28+
the downgraded case, allowing register heuristics to overwrite the correct DTC result with
29+
"MOD 6000-15000TL3-XH (Hybrid)". Fixed by checking `detection.get("dtc_code") and
30+
detection.get("profile_key")` instead — any matched DTC always wins over heuristics.
31+
32+
---
33+
734
## v1.0.12
835

936
Issues: #351

custom_components/growatt_modbus/diagnostic.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1517,8 +1517,10 @@ def reg_exists(addr):
15171517
if protocol_ver >= 201:
15181518
detection["reasoning"].append(f" → VPP Protocol V{protocol_str} - supports advanced features")
15191519

1520-
# If DTC detected model, skip other detection logic
1521-
if detection["confidence"] == "Very High":
1520+
# If DTC detected model, skip other detection logic.
1521+
# Also returns when confidence was downgraded to "High" by proto_ver==0 — a matched DTC
1522+
# always wins over register heuristics regardless of protocol version confidence.
1523+
if detection.get("dtc_code") and detection.get("profile_key"):
15221524
return detection
15231525

15241526
# Check register ranges (only successful reads with non-zero values)

custom_components/growatt_modbus/growatt_modbus.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1892,7 +1892,10 @@ def _read_sparse(addr_list: list, fatal: bool = False) -> bool:
18921892

18931893
# Battery Data (if available - storage/hybrid models)
18941894
self._read_battery_data(data)
1895-
1895+
1896+
# Backup Box Data (Growatt ARK transfer switch, regs 3281-3342)
1897+
self._read_backup_box_data(data)
1898+
18961899
# Temperatures
18971900
inverter_temp_addr = self._find_register_by_name('inverter_temp')
18981901
ipm_temp_addr = self._find_register_by_name('ipm_temp')
@@ -2996,6 +2999,63 @@ def _read_battery_data(self, data: GrowattData) -> None:
29962999
except Exception as e:
29973000
logger.debug(f"Battery data not available: {e}")
29983001

3002+
def _read_backup_box_data(self, data: GrowattData) -> None:
3003+
"""Populate backup box (Growatt ARK) fields from cached 3000-range registers."""
3004+
try:
3005+
# Connection flag — must be read first so sensors.py deferred mechanism
3006+
# can gate the remaining 8 conditional sensors on box_connect_flag == 1.
3007+
addr = self._find_register_by_name('box_connect_flag')
3008+
if addr is None:
3009+
return # Profile has no backup box registers — skip entirely
3010+
value = self._get_register_value(addr)
3011+
if value is not None:
3012+
data.box_connect_flag = int(value)
3013+
3014+
for attr, reg_name in (
3015+
('box_bypass_status', 'box_bypass_status'),
3016+
('box_work_mode', 'box_work_mode'),
3017+
('box_error_code', 'box_error_code'),
3018+
('box_warning_code', 'box_warning_code'),
3019+
('box_relay_status', 'box_relay_status'),
3020+
):
3021+
a = self._find_register_by_name(reg_name)
3022+
if a is not None:
3023+
v = self._get_register_value(a)
3024+
if v is not None:
3025+
setattr(data, attr, int(v))
3026+
3027+
for attr, reg_name in (
3028+
('box_temperature', 'box_temperature'),
3029+
('box_grid_voltage', 'box_grid_voltage'),
3030+
):
3031+
a = self._find_register_by_name(reg_name)
3032+
if a is not None:
3033+
v = self._get_register_value(a)
3034+
if v is not None:
3035+
setattr(data, attr, float(v))
3036+
3037+
# 32-bit paired: call _get_register_value on the _low address
3038+
for attr, low_name in (
3039+
('box_grid_power', 'box_grid_power_low'),
3040+
('box_load_power', 'box_load_power_low'),
3041+
):
3042+
a = self._find_register_by_name(low_name)
3043+
if a is not None:
3044+
v = self._get_register_value(a)
3045+
if v is not None:
3046+
setattr(data, attr, float(v))
3047+
3048+
if data.box_connect_flag:
3049+
logger.debug(
3050+
"Backup box: connect=%d bypass=%d mode=%d temp=%.0f°C "
3051+
"grid=%.0fW load=%.0fW relay=%d",
3052+
data.box_connect_flag, data.box_bypass_status, data.box_work_mode,
3053+
data.box_temperature, data.box_grid_power, data.box_load_power,
3054+
data.box_relay_status,
3055+
)
3056+
except Exception as e:
3057+
logger.debug(f"Backup box data not available: {e}")
3058+
29993059
def _read_device_info(self, data: GrowattData) -> None:
30003060
"""Read device info from holding registers"""
30013061

custom_components/growatt_modbus/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@
1212
"pymodbus>=3.0.0",
1313
"pyserial>=3.4"
1414
],
15-
"version": "1.0.12"
15+
"version": "1.0.13"
1616
}

0 commit comments

Comments
 (0)