Skip to content

Commit d2acc16

Browse files
0xAHAclaude
andcommitted
Bump version to v1.3.3 - fix four TL-XH2 register mappings (#361)
Checked against the Growatt VPP specification and a field scan from Richardmarkink. Three of the four were inherited from the first-generation MIN TL-XH profile, but all shipped in v1.2.1. 31204/31205 charge power (W) -> cumulative charge energy (kWh) 31208/31209 discharge power (W) -> cumulative discharge energy (kWh) 31215 battery current INT16 -> INT32 spanning 31215-31216 31222 battery temperature -> reserved; temperature is at 31223 The energy pair is unambiguous read as kWh: the scan gives 4.0/5.3 kWh daily and 37.9/29.2 kWh cumulative on a system with 72.7 kWh lifetime generation. As watts they are nonsense. The current fix is the same defect reported for WIT in #247, where -27.4 A appeared as -0.1 A: reading an INT32 as INT16 returns only the high word. Verified here - 2012 W over 403.7 V is 4.98 A, and 31216 reads 49 -> 4.9 A. Battery temperature may still read 0: the spec says 31223, which is used here, but that reads 0 on the MIN 4200TL-XH2 while 31224 (reserved for max battery temperature) reads 36.5 C. Left alone pending a field comparison rather than mapping a reserved register on a hunch. Also established: PV generation energy is not exposed over VPP at all. The input register table ends at 31599 and carries only battery energy, which is why a scan taken at 72.7 kWh / 7.5 kWh matched no register. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 139b9a8 commit d2acc16

4 files changed

Lines changed: 70 additions & 15 deletions

File tree

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +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.3.1-blue.svg)
7-
![Pre-release](https://img.shields.io/badge/Pre--release-1.3.2-orange.svg)
6+
![Version](https://img.shields.io/badge/Version-1.3.3-blue.svg)
87
[![GitHub Issues](https://img.shields.io/github/issues/0xAHA/Growatt_ModbusTCP.svg)](https://github.com/0xAHA/Growatt_ModbusTCP/issues)
98
[![GitHub Stars](https://img.shields.io/github/stars/0xAHA/Growatt_ModbusTCP.svg?style=social)](https://github.com/0xAHA/Growatt_ModbusTCP)
109

RELEASENOTES.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,45 @@
44

55
---
66

7-
## v1.3.2 — PRE-RELEASE
7+
## v1.3.3
8+
9+
Issues: #361
10+
11+
- **Fix: four wrong register mappings in the MIN TL-XH2 profile.**
12+
Checked against the Growatt VPP protocol specification and a field scan from
13+
@Richardmarkink. Three of the four were inherited from the first-generation MIN TL-XH
14+
profile rather than introduced by me, but all four shipped in v1.2.1.
15+
16+
| Register | Was | Actually |
17+
|---|---|---|
18+
| 31204/31205 | charge power (W) | **cumulative charge energy (kWh)** |
19+
| 31208/31209 | discharge power (W) | **cumulative discharge energy (kWh)** |
20+
| 31215 | battery current, single INT16 | **INT32 spanning 31215-31216** |
21+
| 31222 | battery temperature | reserved — temperature is at **31223** |
22+
23+
The energy ones are unambiguous once the values are read as kWh: the field scan gives
24+
4.0 and 5.3 kWh daily, 37.9 and 29.2 kWh cumulative, on a system with 72.7 kWh of
25+
lifetime generation. As watts — 37.9 W, 29.2 W — they are nonsense.
26+
27+
The current one is the same defect reported for WIT in **#247**, where −27.4 A appeared
28+
as −0.1 A. Reading an INT32 as INT16 returns only the high word, which is ~0 for any
29+
normal current. Verified here: battery power 2012 W over 403.7 V is 4.98 A, and 31216
30+
reads 49 → 4.9 A.
31+
32+
- **Known gap: battery temperature may still read 0 on TL-XH2.** The specification puts it
33+
at 31223, which is what this release uses — but that register reads 0 on the MIN
34+
4200TL-XH2, while 31224 ("reserved for maximum battery temperature") reads 36.5 °C.
35+
Mapping the reserved register on a hunch is how the earlier mistakes happened, so it is
36+
left alone pending a comparison against the app.
37+
38+
- **PV generation energy counters are not exposed over VPP at all.** The VPP input
39+
register table ends at 31599 and contains only *battery* energy — there is no Etotal or
40+
Etoday. That is why a scan taken while the inverter displayed 72.7 kWh / 7.5 kWh matched
41+
no register. Those sensors cannot be provided for VPP-only hardware from this range.
42+
43+
---
44+
45+
## v1.3.2
846

947
> ⚠️ **Pre-release.** Changes how every sensor entity is constructed. Please confirm the
1048
> integration loads and your sensors still have values before this is promoted.

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.3.2"
15+
"version": "1.3.3"
1616
}

custom_components/growatt_modbus/profiles/tl_xh.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -558,26 +558,44 @@
558558
31200: {'name': 'battery_power_high', 'scale': 1, 'unit': '', 'pair': 31201},
559559
31201: {'name': 'battery_power_low', 'scale': 1, 'unit': '', 'pair': 31200,
560560
'combined_scale': 0.1, 'combined_unit': 'W', 'signed': True},
561+
# Energy counters. Per VPP 2.03 §2.2 rows 66-69, 31202/31204/31206/31208 are ALL
562+
# energy in 0.1 kWh — daily and cumulative, charge and discharge. v1.2.1 mapped
563+
# 31204 and 31208 as charge/discharge POWER in watts, inherited from the first-gen
564+
# MIN TL-XH profile. Confirmed wrong against a field scan: those registers read
565+
# 37.9 and 29.2, coherent as cumulative kWh on a system with 72.7 kWh lifetime
566+
# generation, nonsensical as 37.9 W / 29.2 W (Issue #361).
561567
31202: {'name': 'charge_energy_today_high', 'scale': 1, 'unit': '', 'pair': 31203},
562568
31203: {'name': 'charge_energy_today_low', 'scale': 1, 'unit': '', 'pair': 31202,
563569
'combined_scale': 0.1, 'combined_unit': 'kWh'},
564-
31204: {'name': 'charge_power_high', 'scale': 1, 'unit': '', 'pair': 31205},
565-
31205: {'name': 'charge_power_low', 'scale': 1, 'unit': '', 'pair': 31204,
566-
'combined_scale': 0.1, 'combined_unit': 'W', 'signed': True},
570+
31204: {'name': 'charge_energy_total_high', 'scale': 1, 'unit': '', 'pair': 31205},
571+
31205: {'name': 'charge_energy_total_low', 'scale': 1, 'unit': '', 'pair': 31204,
572+
'combined_scale': 0.1, 'combined_unit': 'kWh',
573+
'desc': 'Cumulative battery charge (VPP 2.03 row 67)'},
567574
31206: {'name': 'discharge_energy_today_high', 'scale': 1, 'unit': '', 'pair': 31207},
568575
31207: {'name': 'discharge_energy_today_low', 'scale': 1, 'unit': '', 'pair': 31206,
569576
'combined_scale': 0.1, 'combined_unit': 'kWh'},
570-
31208: {'name': 'discharge_power_high', 'scale': 1, 'unit': '', 'pair': 31209},
571-
31209: {'name': 'discharge_power_low', 'scale': 1, 'unit': '', 'pair': 31208,
572-
'combined_scale': 0.1, 'combined_unit': 'W', 'signed': True},
577+
31208: {'name': 'discharge_energy_total_high', 'scale': 1, 'unit': '', 'pair': 31209},
578+
31209: {'name': 'discharge_energy_total_low', 'scale': 1, 'unit': '', 'pair': 31208,
579+
'combined_scale': 0.1, 'combined_unit': 'kWh',
580+
'desc': 'Cumulative battery discharge (VPP 2.03 row 69)'},
573581
31214: {'name': 'battery_voltage', 'scale': 0.1, 'unit': 'V', 'signed': True,
574-
'desc': 'Battery voltage (verified against app, Issue #361)'},
575-
31215: {'name': 'battery_current', 'scale': 0.1, 'unit': 'A', 'signed': True,
576-
'desc': 'Battery current'},
582+
'desc': 'Battery voltage (INT16; verified against app, Issue #361)'},
583+
# INT32 spanning 31215-31216, NOT a single register. Read as INT16 the value sits
584+
# in the high word and decodes to ~0 — the same defect reported for WIT in #247,
585+
# where -27.4 A appeared as -0.1 A. Verified here: battery power 2012 W over
586+
# 403.7 V = 4.98 A, and 31216 reads 49 -> 4.9 A.
587+
31215: {'name': 'battery_current_high', 'scale': 1, 'unit': '', 'pair': 31216},
588+
31216: {'name': 'battery_current_low', 'scale': 1, 'unit': '', 'pair': 31215,
589+
'combined_scale': 0.1, 'combined_unit': 'A', 'signed': True,
590+
'desc': 'Battery current (INT32; positive=charging)'},
577591
31217: {'name': 'battery_soc', 'scale': 1, 'unit': '%',
578592
'desc': 'Battery SOC (verified against app, Issue #361)'},
579-
31222: {'name': 'battery_temp', 'scale': 0.1, 'unit': '°C', 'signed': True,
580-
'desc': 'Battery temperature'},
593+
# Spec puts battery temperature at 31223, not 31222 — 31222 is the low word of the
594+
# reserved UINT32 at 31221 and reads 0. NOTE: 31223 also reads 0 on the MIN
595+
# 4200TL-XH2, while 31224 ("reserved for maximum battery temperature") reads 365
596+
# -> 36.5 °C. Awaiting a field comparison before preferring 31224 (Issue #361).
597+
31223: {'name': 'battery_temp', 'scale': 0.1, 'unit': '°C', 'signed': True,
598+
'desc': 'Battery temperature (VPP 2.03 row 78)'},
581599
},
582600
'holding_registers': {
583601
**VPP_V201_HOLDING_1P,

0 commit comments

Comments
 (0)