Skip to content

Commit 6a64c44

Browse files
0xAHAclaude
andcommitted
docs: RS485 gateway guide, and correct the != credit in v1.4.0 notes
The v1.4.0 notes described rejecting over-long responses as an equally strong signal that costs nothing to catch. The field data says more than that: all 31 measured mismatches came back LONGER than requested, 30 of them exactly 125 registers whatever was asked for. A check for short responses would have caught none of them. The failure is not truncation, it is a complete valid response to an earlier request replayed to the current one -- so != count is the entire fix, not a refinement of it. Adds docs/troubleshooting/rs485-gateways.md collecting what has actually been measured: the Waveshare RS485 TO POE ETH (B) settings that work, the PUSR TCP-timeout and AutoFrame changes that helped on #360, the Olimex ESP32 bridge that did not, and the transparent-passthrough trap that makes a gateway unusable regardless of hardware quality. Also records two measurement mistakes worth not repeating: latency read from log timestamps that was actually the integration's own retry cycle, and a register scan run while the integration was still polling, which returned 9 successful reads out of 1304 on a working device. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent aac9727 commit 6a64c44

3 files changed

Lines changed: 106 additions & 0 deletions

File tree

RELEASENOTES.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,22 @@ Includes everything from the v1.3.7 pre-release.
3939
into polls that visibly fail, so your failure count may go **up**. That is the fix
4040
working.
4141

42+
**The guard checks `!= count`, not `< count`, and that turns out to be the whole
43+
fix.** Every mismatch measured in the field came back **longer** than requested —
44+
31 of them, 30 returning exactly 125 registers whatever was asked for. A length check
45+
for *short* responses would have caught **none of them**. The failure is not a
46+
truncated frame: it is a complete, valid response to an *earlier* request being
47+
replayed to the current one. @tdalejandro proposed the `!=` and then measured the data
48+
that showed it was doing all the work.
49+
50+
- **Which gateways are affected.** Two independent setups now bracket this. A Waveshare
51+
RS485 TO POE ETH (B) doing genuine Modbus-TCP-to-RTU translation showed **zero**
52+
mismatches and 26 days of clean statistics from *before* the guard existed — so on good
53+
hardware there was never anything to catch. A ShineWiFi-class serial bridge mismatches
54+
roughly one poll in three. A persistent socket is not the cause: the clean setup uses
55+
the same shared connection and the same 60 s interval. See
56+
[RS485 gateways](docs/troubleshooting/rs485-gateways.md).
57+
4258
- **The adaptive backoff never engaged on TCP connections** *(from v1.3.7)*. The shared
4359
path returned before reaching the failure counters.
4460

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ The integration polls your inverter directly over Modbus — the same protocol t
2626
- **[Raising an Issue](troubleshooting/raising-an-issue.md)** — what to include when reporting a bug
2727
- **[Diagnostic Service](troubleshooting/diagnostic-service.md)** — Universal Register Scanner
2828
- **[DTC Debugging](troubleshooting/dtc-debugging.md)** — device type code reference
29+
- **[RS485 Gateways](troubleshooting/rs485-gateways.md)** — which adapters work, and diagnosing one that doesn't
2930
- **[Adding Sensors](developer/adding-sensors.md)** — add registers and sensors to an existing profile
3031
- **[New Profile](developer/new-profile.md)** — support a new or unsupported inverter model
3132
- **[Release Notes](release-notes.md)** — changelog
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# RS485-to-TCP Gateways
2+
3+
Most problems that look like integration bugs turn out to be the box between Home Assistant and the inverter. This page collects what has actually been measured on real hardware, with the issue numbers behind each claim.
4+
5+
> A gateway can look correctly configured, pass a casual test, and still be the thing that's broken. Some fail loudly. The dangerous ones fail quietly.
6+
7+
---
8+
9+
## The one setting that matters most
10+
11+
Your gateway must do **Modbus TCP to RTU translation**, not transparent passthrough.
12+
13+
This integration uses `pymodbus`'s `ModbusTcpClient`, which speaks Modbus TCP with MBAP framing. A transparent passthrough forwards raw RTU bytes with no MBAP header, and the client cannot parse them. It will not work at all, no matter how good the hardware is.
14+
15+
That is a **mode setting**, not a hardware quality question. Check it before buying anything or blaming anything else.
16+
17+
---
18+
19+
## Field-tested hardware
20+
21+
### ✅ Waveshare RS485 TO POE ETH (B) — known good
22+
23+
Firmware V1.523, reported in [#367](https://github.com/0xAHA/Growatt_ModbusTCP/issues/367) on a MID 25KTL3-XH.
24+
25+
| Setting | Value |
26+
|---|---|
27+
| Protocol | `Modbus TCP to RTU`**not** "None"/transparent |
28+
| Instruction Timeout | 288 ms — must exceed the transmission time of your largest block |
29+
| RS485 Conflict Gap | 20 ms |
30+
31+
Measured: zero short/misaligned reads, 89 sensors populated, two full register scans of 2300 registers across 17 ranges with no read errors, and **26 days of statistics from before the v1.3.7 guard existed with no corrupt values at all**. On this gateway there was never anything to catch.
32+
33+
### ⚠️ PUSR / ShineWiFi-class serial bridges — replay stale frames
34+
35+
Reported in [#360](https://github.com/0xAHA/Growatt_ModbusTCP/issues/360) and [#367](https://github.com/0xAHA/Growatt_ModbusTCP/issues/367).
36+
37+
These can return **a complete, valid response to an earlier request** when answering the current one. Measured at roughly **one poll in three**, with 30 of 31 mismatches returning exactly 125 registers regardless of what was asked for.
38+
39+
Since v1.3.7 the integration detects this and discards the frame, so the data is safe — but you will see `Short/misaligned read at N: got X of Y registers` warnings. If you are on an older version, this is the failure mode that published a serial-number fragment as 85,893,614.8 W of AC power.
40+
41+
Two settings materially improved a PUSR unit on #360:
42+
43+
| Setting | Change | Why |
44+
|---|---|---|
45+
| TCP timeout | disabled → **30 s** | With it disabled, dead sessions are never reaped and eventually every connection slot is held by a connection to nobody |
46+
| UART AutoFrame | disabled → **100 ms** | Frame fragmentation causes the parser to lock onto the wrong byte offset and read a nonsense unit ID |
47+
48+
### ❌ Olimex ESP32-POE-ISO + `esphome_modbus_bridge` — unstable
49+
50+
Reported in [#367](https://github.com/0xAHA/Growatt_ModbusTCP/issues/367). Repeated dropouts, TCP host unreachable for two to three minutes at a time, recovering on its own with no pattern tied to load, time of day or PV production. RS485 bias resistors made no difference, which pointed at the network side rather than the serial side. Replaced with the Waveshare above.
51+
52+
---
53+
54+
## Diagnosing your own gateway
55+
56+
**Is it replaying stale frames?** Look for `Short/misaligned read` warnings. Note whether the count returned is *larger* than requested — a reply longer than the request cannot be a truncation, and points at a replayed earlier response.
57+
58+
**Is latency per-request or per-register?** This decides whether a smaller block size helps or hurts. Read the same register range at several block sizes and compare total time:
59+
60+
- If time scales with the number of registers, smaller blocks help.
61+
- If time is roughly **fixed per request**, smaller blocks are much worse.
62+
63+
On the PUSR unit in #367, 113 registers cost the same as 1 — every read landed in one of two clusters ~500 ms apart, which looks like an internal scheduling tick. Block size 1 would have meant ~113 requests of ~0.8 s each in place of a single 1.3 s read. **Block size 25 was kept.**
64+
65+
**Careful measuring latency from logs.** A 15-18 s figure reported on #367 turned out to be the integration's own failure cycle — a 10 s timeout plus reset and retry — not gateway latency. Measure with raw sockets and the integration disabled.
66+
67+
**Running a register scan?** Disable the integration entry first (**⋮ → Disable**, don't delete), wait ~30 s, then scan. The scanner opens a second connection, and on a sensitive gateway that contends with the poller. A scan taken while polling came back with 9 successful reads out of 1304 rows, every range reporting "no response" on a device that was working fine.
68+
69+
---
70+
71+
## Does a persistent connection cause this?
72+
73+
No — and this was tested directly.
74+
75+
The integration holds one socket per host:port across polls. It was suspected of allowing a stale frame to linger in the buffer, but the clean Waveshare setup uses **the same shared connection and the same 60 s interval** with zero mismatches. A persistent socket is not the mechanism; it is what exposes a gateway that replays. Since v1.3.7 a detected mismatch also drains the receive buffer, so a misaligned stream does not persist into the next read.
76+
77+
| Gateway | Socket | Result |
78+
|---|---|---|
79+
| ShineWiFi-class | persistent | mismatch ~1 poll in 3 |
80+
| ShineWiFi-class | fresh per read | 21/21 clean |
81+
| Waveshare RS485 TO POE ETH (B) | persistent | clean |
82+
83+
---
84+
85+
## Keeping the cloud app working
86+
87+
You do not necessarily have to choose. On both systems in #367 the inverter has a **`SYS COM` port separate from the USB port the ShineWiFi dongle occupies**, so a second RS485 master can run alongside the stock dongle. Home Assistant gets a local Modbus path, and the dongle keeps feeding Growatt's own app.
88+
89+
Note this is one local path and one cloud path — not two paths into Home Assistant. If you keep the dongle, be aware the Growatt cloud can overwrite local writes to control registers within seconds; the integration logs a `Write reversion detected` warning when it sees this.

0 commit comments

Comments
 (0)