ESP32 Modbus TCP Proxy for Huawei SUN2000 / EMMA → Home Assistant

Hi everyone,

I've been running a Huawei SUN2000 with an EMMA-A02 and hit two frustrating problems that are well
known in this community:

  1. Modbus only allows one active client — HA and EVCC can't share the connection without stepping on
    each other.
  2. The EMMA restarts whenever it loses Huawei cloud connectivity, taking down HA sensors with it.

I solved both with a transparent Modbus TCP proxy running on an ESP32. It sits between the EMMA and
any number of clients (HA, EVCC, whatever), handles multiplexing, and maintains its own reconnection
logic independently of the cloud.

Features:

  • Up to 4 simultaneous Modbus TCP clients
  • Web dashboard showing connection status and client history
  • Live transaction log with register-level detail
  • Web-based config (WiFi / Ethernet, static or DHCP, EMMA IP/port)
  • Headless WiFi setup mode on first boot
  • OTA firmware updates

Screenshots:



(attach the three screenshots here)

Hardware: Any ESP32 with WiFi. I use it over Ethernet for stability but WiFi works fine too.

Repo: GitHub - mcamposv/modbusproxy: huawei modbus tcp proxy · GitHub

It's been working reliably in my setup. Happy to answer questions or take feedback — especially from
anyone running EMMA-A02 since support for that device is still a bit rough in the official
integration.

Nice — I ended up at the same architecture from the other direction, so it's
interesting to compare.

I hit the same wall (SUN2000 + SDongle, Home Assistant polling sensors while an
AC·THOR reads grid feed-in for surplus heating) and solved it in software instead
of on an ESP32: a small Python daemon that holds one connection to the inverter,
polls a register set on an interval and serves the cached values to any number of
Modbus TCP clients. Writes (FC6) pass straight through so control still works.

Two things I ran into that might be relevant to your implementation:

- Reads and writes behave very differently. Caching reads is easy; writes have to
  go through synchronously and you need to decide what to return if the inverter
  refuses them. I forward and pass the exception back rather than pretending success.
- Recovery matters more than the happy path. When the inverter briefly disappears,
  the useful behaviour is to keep serving the last known values and log the cache
  age, instead of failing every client at once. Otherwise you just move the outage
  one layer up.

Your EMMA-restart-on-cloud-disconnect point is interesting, I haven't seen that on
the SDongle. Does it recover on its own?

Mine, if useful for comparison: https://github.com/cloudapp-dev/sun2000-modbus-cache
(stdlib only, Docker/systemd, MIT)