# GoodWe RS485 Controller — Field Guide For installing technicians. Read section 1 before the first site visit; work through sections 5–11 in order at the site; do not sign off until section 12 is complete. --- ## 1. The one fact this whole product is built around **The inverter holds the last command it understood, forever.** It has no meter-timeout of its own. If whatever is driving it stops talking, it does not fall back to idle, to a safe value, or to anything else — it keeps doing exactly what it was last told, indefinitely. This is not a theory. On the reference system a controller went silent mid-command and the inverter held **5 kW of discharge for 113 seconds**, until a person noticed and intervened. Everything else in this guide follows from that: | layer | covers | where it lives | |---|---|---| | 1 — watchdog | controller alive but silent | ESP32 firmware: no fresh setpoint for 30 s → command 0 W **and keep commanding it** | | 2 — wind-down | planned firmware update | ESP32 firmware: 0 W written *before* the update starts | | 3 — e-stop | **the controller or its host is dead** | optional RS485 e-stop: writes 0 W after 30 s of total bus silence | **Layer 3 is the only thing that covers the Home Assistant machine dying.** Nothing running on that machine can cover its own death. On a site without the e-stop, a failed Pi or a pulled plug leaves the battery running at whatever it was last commanded until a human intervenes. Section 10 tells you what to say about that, in writing. > Stopping is the failure mode, not the fix. Anything in this system that finds > itself in doubt must command **0 W** — never hold the last value "to be safe". --- ## 2. Before you go: what to confirm with the client Do not treat these as formalities. Each has ended an installation. - [ ] **Inverter is a GoodWe ES / BP family unit** (the AA55 / RS485 meter-bus generation). Other GoodWe families use a different protocol and are not supported. - [ ] **There is an existing controller on the meter bus** (vendor box emulating a smart meter). Confirm what it is, and that the client accepts it being **disconnected**. - [ ] **Warranty and installer agreement.** Replacing the vendor controller may affect both. Get the client's written acknowledgement. This is a commercial question, and it is cheaper to ask than to discover. - [ ] **Grid connection rules.** In Belgium the installation is governed (Synergrid C10/11). Modifying how the inverter is driven can touch the DSO agreement. Confirm the client's position before quoting. - [ ] **Any active vendor subscription** — cancel *after* the replacement is proven, not before. - [ ] **Home Assistant is HA OS or Supervised.** Add-ons cannot be installed on HA Container or Core. Check Settings → System → Repairs → System information. **If it says Container, this product cannot be installed.** - [ ] **A working grid-power sensor exists in HA** with a fast update (≤ ~10 s). HomeWizard P1, DSMR, Shelly EM are all fine. Note its update rate — see §9. - [ ] **E-stop fitted or not** — and if not, that the client has signed the statement in §10. --- ## 3. What is in the box **Base SKU** - T-CAN485 (ESP32 + RS485 transceiver), pre-flashed or flashed on site - Wiring loom to the inverter's meter port - The add-on (installed from your repository URL) **E-stop upgrade** - Raspberry Pi (any model with USB) + USB-RS485 adapter - Pre-loaded `rs485_log.py` - Tap wiring to the same bus --- ## 4. How the pieces fit ``` grid meter ──► Home Assistant ──► GoodWe RS485 Controller add-on (client's) (client's) │ │ number._goodwe_setpoint_w ▼ T-CAN485 (ESP32) │ RS485, 9600 8N1, AA55/Modbus ▼ GoodWe inverter meter port ▲ │ passive tap + emergency write Pi e-stop (optional) ``` The add-on never talks to the inverter directly. It writes one number; the ESP32 turns that into meter frames at a steady cadence and owns the safety timing. --- ## 5. Site survey and safety ⚠️ **Qualified persons only.** The inverter carries mains AC and battery DC. The meter bus itself is low-voltage, but you are working inside an energised installation. 1. Photograph the existing wiring at the inverter's meter port **before** touching anything. 2. Identify the RS485 pair (A/B) going to the vendor controller. 3. Note the inverter model and serial from its label. 4. Record the battery: capacity (Ah), nominal voltage, and the inverter's depth-of-discharge setting if visible. **Never do these:** - ❌ Do not flash third-party firmware onto the inverter's WiFi dongle. The widely circulated image targets different hardware and will brick it. - ❌ Do not long-press (3–5 s) the inverter's WiFi Reset/Reload button. It factory-resets the dongle and you lose network access to it. A short press (~1 s) is safe. - ❌ Do not connect our controller while the vendor controller is still attached. **Two masters on one bus is the one configuration that can produce contradictory commands.** Disconnect the vendor box first, at 0 W. --- ## 6. Hardware installation 1. **Bring the system to 0 W.** Set the vendor controller to idle if it allows it, or simply confirm on the inverter display that the battery is neither charging nor discharging. 2. **Disconnect the vendor controller** from the meter port. Leave it physically installed but disconnected if the client wants a reversible install — that also gives you a rollback story if you ever need one. 3. **Wire the T-CAN485** to the meter port: A→A, B→B, plus its own power supply. Observe polarity; swapped A/B produces a completely silent bus, not an error. 4. If fitting the **e-stop**, connect the USB-RS485 adapter to the *same* pair, in parallel. It is passive until it decides to act. 5. Power up the T-CAN485 **last**. ⚠️ It begins transmitting within seconds of boot. Never power it up while the vendor controller is still connected. --- ## 7. Flash the ESP32 Use the ESPHome Device Builder add-on on the client's HA, or your own laptop. 1. Copy `firmware/goodwe-master.yaml` into ESPHome. 2. Set the three substitutions at the top — **and only those**: - `name` — the node name. Write it on the commissioning sheet. **Changing it later renames every entity in HA and silently breaks the add-on's config.** - `max_w` — the inverter's continuous rating (e.g. `5000`). This is a hard firmware limit, independent of anything HA asks for, and it is the last line of defence against a controller bug. - `wd_ms` — watchdog timeout, default `30000`. Must stay comfortably above the add-on's heartbeat. 3. Add the client's WiFi credentials to ESPHome's `secrets.yaml`. 4. Install. First flash is by USB; everything after that is over the air. ⚠️ **A firmware update that lands new shutdown-path code still runs the OLD code on the way out.** If you change the wind-down or watchdog behaviour, upload twice before believing a test result. --- ## 8. Install and configure the add-on 1. Settings → Add-ons → Add-on store → ⋮ → **Repositories** → add your repo URL. 2. Install **GoodWe RS485 Controller**. Do not start it yet. 3. Open **Configuration** and fill in: | option | what to put | notes | |---|---|---| | `meter_entity` | the client's grid power sensor | **+ must mean importing.** If theirs is the other way round, set `meter_invert` | | `soc_entity` | `sensor._goodwe_battery_soc` | **the ESP32's own read**, not the inverter's cloud/dongle sensor | | `batt_entity` | `sensor._goodwe_inverter_ac_power` | same — the ESP32's read | | `setpoint_entity` | `number._goodwe_setpoint_w` | what the add-on writes | | `max_w` | start at **1000** for commissioning | raise after §9 passes | | `estop_fitted` | true only if you actually fitted one | drives the warning banner | | `peak_forecast_entity` | capacity-tariff sites only | leave empty elsewhere | | `price_now_entity` / `price_avg_entity` | dynamic-tariff sites only | leave empty on fixed tariffs | ⚠️ **Get the entity ids exactly right.** Home Assistant prefixes entity ids with the device's *area* at creation time, so the same firmware produces `sensor.goodwe_master_...` on one site and `sensor.cellar_goodwe_master_...` on another. **A wrong entity id is not an error anywhere in HA** — it simply never produces a value. On the reference install two safety alarms pointed at non-existent entities and were dead for a day while their state read "on". Copy ids from Developer Tools → States. Do not type them from memory. 4. Start the add-on and open its **Web UI** (ingress panel). 5. Work down the **Commissioning** list until every line is green. It tells you what is wrong in words. Do not proceed while anything is red. --- ## 9. Commissioning — the acceptance gates **Every gate is judged on the wire or the meter, never on "it looks right in Home Assistant".** If you fitted the e-stop, its log is your witness; if not, use the inverter's own display and the client's meter. ### Gate 1 — the inverter answers us Add-on running, control **stopped**. Expect a steady 0 W command being written, the inverter idle, no errors in the add-on log. If the e-stop is fitted, `bus.log` shows our write frames being ACKed: ``` F7 10 05 6E 00 02 04 00 03 00 00 A6 D0 our write, 0 W F7 10 05 6E 00 02 34 4F inverter ACK ``` ✅ Pass: frames ACKed, no CRC errors, inverter idle. ### Gate 2 — we can move power, both directions With control still stopped, set the setpoint by hand from Developer Tools (`number.set_value` on the setpoint entity): - **+300 W** → battery discharges ~300 W within ~10 s - **−300 W** → battery charges ~300 W ✅ Pass: both signs work and the inverter's reported power follows within ~10 s. ❌ If the sign is inverted, fix `batt_invert` / your wiring — **do not** "compensate" in the tuning. ### Gate 3 — telemetry agrees Compare the ESP32's readings against the inverter display or the vendor app: state of charge should match exactly; power within a few percent (conversion loss). ✅ Pass: SoC identical, power within ~5 %. ### Gate 4 — the watchdog (**the important one**) Set +300 W by hand, confirm it is running, then **stop the add-on**. ✅ Pass: the inverter reaches **0 W within ~30 s** and stays there. ❌ Fail: anything still moving after a minute. Stop the installation and investigate — without this, nothing else in this product is safe. Restart the add-on afterwards. ### Gate 5 — the closed loop Set `max_w` to 1000, start control from the Web UI, and watch the client's grid power. ✅ Pass: grid settles to within a few tens of watts of zero and *stays* there, with the command resting rather than hunting continuously. ### Gate 6 — a real load step Switch on a kettle or oven (~2 kW). ✅ Pass: grid returns to near zero within ~20 s, and the command does **not** keep climbing after the battery has caught up. A command that keeps rising while the battery is pinned is runaway — stop immediately and see §13. Then raise `max_w` to the value the site is sold with (typically the inverter rating) and repeat Gate 6 once. ### Gate 7 — the maintenance cycle See §11. **Do not sign off without it.** --- ## 10. The e-stop, and what it means when it is absent **Fitted:** connect the Pi to the same RS485 pair, power it, and run: ```bash python3 rs485_log.py --out /home/pi/bus.log --panic ``` Confirm it prints `PANIC ARMED`. Test it: with the battery at +300 W, cut power to the T-CAN485. Within ~35 s the log must show a ` TX ` line and the inverter must go to 0 W. ⚠️ Arm the panic write **only** while our controller owns the bus. Pointed at a bus somebody else is driving, it is unrequested interference. ⚠️ Expect it to fire during every firmware update — the upload silence exceeds 30 s. That is correct behaviour, not a fault. Do not raise the threshold to silence it. **Not fitted — put this in front of the client, in writing:** > Without the RS485 e-stop, if the Home Assistant machine fails, loses power, or > its storage fails, the battery inverter will continue charging or discharging > at whatever level it was last commanded, indefinitely, until someone > intervenes manually. The inverter has no automatic fallback of its own. The > e-stop is the only component that prevents this. Have them acknowledge it. Note it on the commissioning sheet. --- ## 11. Prove the maintenance cycle The monthly cycle takes the battery low, then charges it fully and holds it there. It exists so the BMS can **balance cells** and **recalibrate its coulomb counter**. Skipping it breaks nothing visibly — it degrades the pack over months, and the first symptom is a state-of-charge reading nobody can trust. **Do not wait a month to discover the schedule does not fire.** Force one: 1. Web UI → **Force maintenance cycle**. 2. Watch the phase go `drain → charge → hold → idle`. 3. Confirm the inverter actually exports during `drain`, actually charges during `charge`, and sits at 0 W during `hold`. A full cycle takes hours. If the client cannot spare the time on the day, temporarily set `maintenance_soc_floor` just below current SoC and `maintenance_soc_target` just below it again, plus `maintenance_hold_min: 5` — that walks the whole state machine in about ten minutes. **Put the real values back afterwards and note it on the sheet as a partial test.** Finally set `maintenance_enabled: true`. --- ## 12. Handover and sign-off - [ ] All gates in §9 passed, on the wire or the meter - [ ] Maintenance cycle proven (full or partial — state which) - [ ] E-stop fitted and tested, **or** client acknowledgement signed (§10) - [ ] `max_w` set to the agreed value - [ ] Node name, entity ids and add-on version written on the sheet - [ ] Client shown: the Web UI, the start/stop button, and what "stopped" means - [ ] Client told: **if anything looks wrong, stop the add-on** — that commands 0 W and the battery idles safely - [ ] Vendor subscription cancelled only *after* the above --- ## 13. Troubleshooting | symptom | likely cause | what to do | |---|---|---| | Web UI says **NOT READY** | an entity id is wrong or the sensor is unavailable | The banner names the failing item. Copy the id from Developer Tools → States | | Everything looks configured but nothing moves | control is stopped | Press start in the Web UI; the banner should turn green | | Inverter does nothing, no errors anywhere | A/B swapped, or the vendor controller is still connected | Swapped RS485 gives a *silent* bus, not an error | | HA shows one setpoint, the inverter does another | the firmware `max_w` is lower than the add-on's `max_w` | The firmware wins by design. Raise it there, or lower it in the add-on | | Writes rejected in the log (HTTP 400) | value outside the number entity's range | Lower `max_w`; check the firmware substitution | | Command keeps climbing while the battery is pinned | **runaway** — sign inverted, or saturation detection defeated | **Stop the add-on immediately.** Verify `meter_invert` with a known load | | Grid hunts continuously, never rests | deadband too small for that meter, or the meter is slow | Raise `deadband_w`; if the meter updates slower than ~10 s, lower `gain` | | Battery goes flat overnight and stays flat | maintenance `drain` phase never exited | Check the SoC entity is the ESP32's, not a cached cloud value | | Maintenance never runs | schedule disabled, or never became due | Check `maintenance_enabled`, and the "Maintenance due" row in the Web UI | | E-stop fires during every update | expected — update silence exceeds 30 s | Nothing to fix. Do not raise the threshold | | Add-on will not install | HA is Container/Core, not OS/Supervised | Not supportable. See §2 | **When in doubt: stop the add-on.** That commands 0 W, the watchdog holds it there, and nothing is at risk while you think. --- ## 14. Why the tuning is what it is Do not change these without measuring. Every value came from hardware. - **`gain` 0.6 per cycle** — a cycle is one meter update (~5 s). The inverter needs 3–6 s to settle: ~1.4 s dead time, 94 % of a step by 3.3 s. So the loop's next correction lands just as the plant arrives. **0.6 is at the limit — do not raise it**, and do not shorten the cycle below the meter's update rate. - **`slew_w` 1000 W per cycle** — most of a correction in the first cycle without letting the command run far ahead of the hardware. - **`deadband_w` 15 W** — measured residual while regulating: mean 15.4 W, max 27 W. At 10 W, ~69 % of cycles act and the command never rests. A resting command is a diagnostic asset: "flat for 70 s" is how you recognise a healthy loop at a glance. - **`saturation_w` 500 W over `saturation_cycles` 3** — command and reading diverging means the inverter is at a limit; then the magnitude may fall but never rise. **The 3-cycle duration term is essential**: tested instantaneously it fires on every large correction, because the plant itself lags. - **`step_w` 10 W** — the register is 1 W, but the inverter's response lands on a coarser ladder (~17.6 W measured at ~900 W). 10 W just avoids a visible staircase; finer is meaningless. - **`heartbeat_s` 10 s against a 30 s watchdog** — three chances to be heard before the hardware takes over. The failure this tuning is designed against is real: the vendor controller commanded **−14 547 W** against an inverter reporting −5250 W, and kept climbing for six minutes, because its integrator never stopped.