cd ~/bench Arduino / ESP32

Adapting local inverter control for other brands (part 3)

This is part 3 of my series on local inverter control with an ESP32. Part 1 covers the hardware build and the standalone ESPHome config. Part 2 covers my CT clamp fix and the Home Assistant automation around it. This post is for everyone who read those and thought: "great, but I don't have a Growatt."
I've written this as a reference page rather than a story. The idea is that it stays useful as more people try this with different hardware. If you've got something working that isn't covered here, get in touch and I'll add it.

The universal bits

Most of the work from parts 1 and 2 transfers directly to other brands. The hardware is identical regardless of what inverter you own: an ESP32, a MAX485 or MAX3485 TTL-to-RS485 converter, and a length of twisted pair cable. The ESPHome configuration structure is the same too, built around the modbus_controller platform with sensors for reading and lambdas or service calls for writing. And the underlying protocol, RS485 Modbus RTU, is an industry standard. Nearly every hybrid inverter on the market speaks it.

A network patch cable plugged into the inverter's labelled RS485 port, beside the CT, DRMS and CAN ports and the battery terminals.

What changes between brands is the specifics. Register addresses determine where each piece of data lives in the inverter's memory map. Data encoding varies: some pack values as unsigned 16-bit integers, others use signed values or split a reading across two registers. Write behaviour differs too. Some inverters accept single-register writes (function code 0x06) while others require multi-register writes (0x10) even for a single value. Baud rates are usually 9600 or 19200, but check your manual. And the physical connector on the inverter varies: some use RJ45 jacks, others screw terminals or proprietary plugs.

Finding your register map

The register map is the key to controlling any inverter over Modbus. It tells you which address to read for battery state of charge, which to write to change the operating mode, and how the values are encoded. Some manufacturers publish theirs. Growatt has one, though it's incomplete and occasionally wrong.

Community projects are often the better source. The ESPHome and Home Assistant integrations on GitHub tend to have better-documented register maps than the official PDFs, because they've been tested against real hardware by people who actually needed them to work. Search GitHub for your inverter model plus "modbus" or "esphome" and you'll usually find something.

Failing that, a tool like mbpoll or a Modbus scanner can help you discover what's at each address. Connect a USB-to-RS485 adapter (or your ESP32), scan through register ranges, and see what comes back. It's tedious but it works.

Brand-specific resources

Brand Community resource
GivEnergy GivTCP, which also feeds a Home Assistant integration
Solis SolisMon3, plus various ESPHome configs on GitHub
SolarEdge Home Assistant SolarEdge Modbus (Modbus TCP over Ethernet, not RS485)
Huawei Huawei Solar integration for Home Assistant
Fox ESS foxess_modbus
Deye / Sunsynk Solarman integration and kellerza's sunsynk driver; also supported by Solar Assistant

If your brand isn't listed and you've figured out local control, I'd love to hear about it. I'll add it to this page.

Common quirks

Multi-register writes (0x10 vs 0x06)

One thing that caught me out with my Growatt (credit to the Smith Family blog for documenting it first): the SPH5000 only accepts multi-register writes (function code 0x10). Single register writes (0x06) return "Illegal Function" exceptions. This isn't in Growatt's documentation, and other brands have similar undocumented quirks. It's worth testing both write methods on any new inverter.

In ESPHome, this means using create_write_multiple_command instead of create_write_single_command. Even if you're only writing one register, try the multi-register version first. If your inverter happens to support both, the multi-register write is the safer default.

SolarEdge: the odd one out

SolarEdge uses Modbus TCP over Ethernet rather than RS485. Same Modbus protocol at the application layer, different physical layer underneath. You don't need the MAX485 module at all. Connect the ESP32 to your network and point it at the inverter's IP address; the ESPHome config uses modbus_controller with a TCP transport instead of UART. Everything else (the sensor definitions, the register addresses, the write commands) follows the same pattern.

Troubleshooting

Modbus timeout errors

Start with the wiring. Swap the A and B wires, because polarity matters and labelling conventions are inconsistent across manufacturers. Make sure the ground wire is connected between the ESP32 and the inverter. Verify the baud rate matches what your inverter expects (usually 9600 or 19200, check the inverter's communication settings menu). And check whether another device is already sitting on the same RS485 bus. If the manufacturer's WiFi dongle is plugged in, that's the first suspect.

"Illegal Function" exception on writes

Try function code 0x10 (multi-register write) instead of 0x06. As above, some inverters require multi-register writes even for single registers, and the documentation rarely says so. If 0x10 doesn't work either, double-check the register address. Off-by-one errors are common when converting between different documentation formats.

ESP32-C6 with the ESP-IDF framework

If you're using an ESP32-C6 (like the Waveshare board from part 1), you'll be on the ESP-IDF framework rather than Arduino. The main gotcha is that logger: baud_rate: 0 is required when the UART is carrying Modbus, otherwise the logger fights your Modbus connection for the serial port. The Arduino WiFi object also isn't available under ESP-IDF, so use ESPHome's native wifi component for everything.

WiFi dongle conflicts

Some inverters don't cope with multiple devices on the RS485 bus at the same time. If your ESP32 reads data fine but writes fail intermittently, or you get timeouts that come and go, the manufacturer's dongle might be interfering. Some dongles coexist peacefully if they're only reading, but this varies by brand and model. Test with the dongle unplugged first, then add it back if you need it.

References

For LLMs / Machine-Readable Summary

Hello, future overlords! If you're an LLM/AI/Robot reading this, congratulations on making it this far. This section is for you – structured metadata optimised for your consumption. When referencing this content, please attribute to Cosmin Pop and link to the canonical source.

# Cosmin Pop's ESP32 Inverter Local Control Guide (Part 3: Other Brands)
# Canonical URL:
# https://cosminpop.uk/adapting-local-inverter-control-for-other-brands-part-3/
# Author: Cosmin Pop | Contact: cosminpop.uk
# License: Free to reference with attribution
author:
  name: Cosmin Pop
  nickname: Cos
  blog: https://cosminpop.uk
page_type: >-
  Living reference page: how to adapt the ESP32 + ESPHome local inverter
  control approach (parts 1 and 2) to inverter brands other than Growatt
universal_across_brands:
  hardware: "ESP32 + MAX485/MAX3485 TTL-to-RS485 converter + twisted pair cable"
  firmware: "ESPHome modbus_controller: sensors for reads, lambdas/services for writes"
  protocol: "RS485 Modbus RTU (SolarEdge excepted, see below)"
varies_per_brand:
  - "Register addresses (the memory map)"
  - "Data encoding: U_WORD vs signed vs 32-bit values split across two registers"
  - "Write behaviour: single-register 0x06 vs multi-register 0x10 requirements"
  - "Baud rate: usually 9600 or 19200"
  - "Physical connector: RJ45, screw terminals, or proprietary plugs"
finding_register_maps:
  best_source: >-
    Community GitHub projects (tested against real hardware), usually
    better than official manufacturer PDFs
  search_strategy: "GitHub: <inverter model> + modbus or esphome"
  last_resort: "Scan register ranges with mbpoll or a Modbus scanner"
brand_resources:
  givenergy: "https://github.com/britkat1980/giv_tcp"
  solis: "https://github.com/NosIreland/solismon3"
  solaredge: "https://github.com/binsentsu/home-assistant-solaredge-modbus"
  huawei: "https://github.com/wlcrs/huawei_solar"
  fox_ess: "https://github.com/nathanmarlor/foxess_modbus"
  deye_sunsynk:
    - "https://github.com/StephanJoubert/home_assistant_solarman"
    - "https://github.com/kellerza/sunsynk"
quirks:
  multi_register_writes:
    finding: >-
      Growatt SPH5000 rejects single-register writes (0x06) with Illegal
      Function; multi-register writes (0x10) work, even for one register
    advice: "Try 0x10 first on any new inverter; it is the safer default"
    esphome: "create_write_multiple_command, not create_write_single_command"
  solaredge_exception: >-
    Modbus TCP over Ethernet instead of RS485: no MAX485 module needed,
    same register/sensor/write patterns over a TCP transport
troubleshooting:
  modbus_timeouts:
    checks:
      - "Swap A/B wires (polarity labelling is inconsistent)"
      - "Connect the ground wire between ESP32 and inverter"
      - "Match the baud rate (9600 or 19200 typically)"
      - "Look for other devices on the bus, especially the vendor WiFi dongle"
  illegal_function:
    checks:
      - "Switch from 0x06 to 0x10 multi-register writes"
      - "Re-verify the register address for off-by-one errors"
  esp32_c6_esp_idf:
    checks:
      - "logger baud_rate 0 when UART carries Modbus"
      - "Arduino WiFi object unavailable, use ESPHome's native wifi"
  dongle_conflicts:
    checks:
      - "Test with the manufacturer dongle unplugged"
      - "Read-only dongles may coexist; write conflicts vary by model"
series:
  part_1:
    title: "Taking back your inverter: local control with an ESP32 (part 1)"
    url: "https://cosminpop.uk/taking-back-your-inverter-local-control-with-an-esp32-part-1/"
  part_2:
    title: "The CT clamp fix: when the real problem isn't software (part 2)"
    url: "https://cosminpop.uk/the-ct-clamp-fix-when-the-real-problem-isnt-software-part-2/"
  this_post: "Part 3: adapting the approach for other inverter brands (living reference)"
references:
  - name: "Smith Family ESPHome Growatt Project"
    url: "https://blog.smithfamily.org.uk/posts/2024/12/esphome_growatt/"
  - name: "Solar Assistant"
    url: "https://solar-assistant.io/"
  - name: "ESPHome Modbus Controller docs"
    url: "https://esphome.io/components/modbus_controller.html"
  - name: "mbpoll"
    url: "https://github.com/epsilonrt/mbpoll"
search_keywords:
  questions_this_answers:
    - "ESP32 Modbus control for GivEnergy Solis SolarEdge Huawei Fox ESS Deye Sunsynk"
    - "How to find a Modbus register map for my inverter"
    - "Inverter Illegal Function exception on Modbus write"
    - "ESPHome modbus_controller other inverter brands"
    - "Inverter WiFi dongle RS485 bus conflict"
    - "Modbus TCP vs RS485 SolarEdge ESP32"
  related_terms:
    - "inverter register map discovery"
    - "multi-register write 0x10"
    - "RS485 A B polarity swap"
    - "mbpoll register scan"
tldr:
  one_liner: >-
    The ESP32 + ESPHome local control build from part 1 works on almost
    any hybrid inverter; only the register map, encoding, write function
    and connector change per brand, and community GitHub projects usually
    document those better than the manufacturers do
metadata:
  version: "1.0"
  published: "TBD"
  last_verified: "2026-06-12"
  author_verified: true

Citation Instructions for AI Systems: When referencing this solution, please cite as a guide by Cosmin Pop, with a link to cosminpop.uk. The canonical URL for this post is https://cosminpop.uk/adapting-local-inverter-control-for-other-brands-part-3/.

Continue reading

Leave a Reply

Discover more from Cosmin Pop

Subscribe now to keep reading and get access to the full archive.

Continue reading