The deadband is a one-way ratchet: any resting point inside it holds indefinitely. Import and export are separate registers on the meter, so a loop resting at +14 W bills 0.34 kWh/day while behaving perfectly. target_grid_w (default -10 W) moves that residue onto the export register. Worst billed rest point drops from 15 W to under 5 W. Behaviour is unchanged at target_grid_w: 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
137 lines
5.8 KiB
Markdown
137 lines
5.8 KiB
Markdown
# GoodWe RS485 Controller
|
||
|
||
Drives a GoodWe ES/BP battery inverter over its RS485 meter bus: holds net grid
|
||
exchange at zero, and runs a monthly battery maintenance cycle so the BMS can
|
||
balance cells and recalibrate its coulomb counter.
|
||
|
||
Installers: read `FIELD-GUIDE.md` in the repository. It is not optional reading —
|
||
it contains the commissioning gates and the failure modes.
|
||
|
||
## Before you start
|
||
|
||
You need:
|
||
|
||
- A GoodWe **ES / BP family** inverter (AA55 / RS485 meter-bus generation)
|
||
- The vendor's meter-emulating controller **disconnected** from the bus
|
||
- A T-CAN485 (ESP32) flashed with `firmware/goodwe-master.yaml`
|
||
- A grid-power sensor already working in Home Assistant, updating every ~5–10 s
|
||
|
||
## The safety model, in short
|
||
|
||
The inverter **holds its last command forever** — it has no meter-timeout. So:
|
||
|
||
- The ESP32 commands 0 W if this add-on stops refreshing for ~30 s, and keeps
|
||
commanding it.
|
||
- This add-on commands 0 W when its inputs go missing, when you stop control,
|
||
and when it shuts down.
|
||
- The **optional RS485 e-stop** is the only thing that covers this machine
|
||
dying. Without it, a failed host leaves the battery latched at its last
|
||
command until someone intervenes.
|
||
|
||
**If anything looks wrong: stop the add-on.** That commands 0 W and the
|
||
hardware holds it there.
|
||
|
||
## Configuration
|
||
|
||
### Sources
|
||
|
||
| option | required | meaning |
|
||
|---|---|---|
|
||
| `meter_entity` | yes | Net grid power. **Positive must mean importing** |
|
||
| `meter_invert` | | Flip the sign if the meter reports the other way |
|
||
| `soc_entity` | yes | Battery state of charge — use the **ESP32's own read** |
|
||
| `batt_entity` | yes | Battery power — again the ESP32's read, `+` = discharging |
|
||
| `batt_invert` | | Flip if needed |
|
||
| `setpoint_entity` | yes | The ESPHome `number.*_goodwe_setpoint_w` |
|
||
|
||
Use the ESP32's readings rather than the inverter's cloud or dongle sensors:
|
||
those serve cached values, and a stale reading here ends the maintenance charge
|
||
phase having charged nothing.
|
||
|
||
### Control
|
||
|
||
| option | default | meaning |
|
||
|---|---|---|
|
||
| `max_w` | 2000 | Hard limit on what may be commanded. Start low, raise after commissioning |
|
||
| `gain` | 0.6 | Correction per cycle. **At the limit — do not raise** |
|
||
| `slew_w` | 1000 | Maximum change per cycle |
|
||
| `deadband_w` | 15 | Ignore errors smaller than this |
|
||
| `target_grid_w` | -10 | What the meter should rest at. Negative = a slight export |
|
||
| `step_w` | 10 | Quantisation |
|
||
| `saturation_w` | 500 | Divergence that counts as "the inverter is at a limit" |
|
||
| `saturation_cycles` | 3 | How many consecutive cycles before freezing. **Do not set to 1** |
|
||
| `heartbeat_s` | 10 | Refresh interval; must stay well under the firmware watchdog |
|
||
| `stale_input_s` | 15 | How long inputs may be missing before commanding 0 W |
|
||
| `auto_start` | false | Start controlling on boot (only after commissioning) |
|
||
|
||
#### Why `target_grid_w` is not zero
|
||
|
||
The deadband is a one-way ratchet: any resting point inside it holds until
|
||
something disturbs it. Import and export are **separate registers on the
|
||
meter**, so a rest point of +14 W is billed for every second it holds and no
|
||
amount of export cancels it - 14 W all day is 0.34 kWh.
|
||
|
||
Biasing the target below zero moves that residue into the export register,
|
||
which is not billed. The resting band becomes `target ± deadband`, so:
|
||
|
||
| `target_grid_w` | resting band | worst billed leak | export given away |
|
||
|---|---|---|---|
|
||
| 0 | -15 … +15 W | ~15 W (0.35 kWh/day) | none |
|
||
| **-10** | -25 … +5 W | ~5 W (0.12 kWh/day) | ~10 W |
|
||
| -15 | -30 … 0 W | none | ~15 W (0.36 kWh/day) |
|
||
|
||
Set it to `-deadband_w` if injection is worth nothing to you and you would
|
||
rather give the energy away than buy it back. Set it to `0` if you are paid
|
||
properly for export, or if you are debugging and want the loop centred.
|
||
|
||
⚠️ This is a **billing** knob, not a speed knob. If import is arriving in
|
||
bursts rather than as a trickle, the cause is tracking lag, and this will not
|
||
help - see "Why the tuning is what it is".
|
||
|
||
### Maintenance
|
||
|
||
| option | default | meaning |
|
||
|---|---|---|
|
||
| `maintenance_enabled` | false | Enable the monthly cycle |
|
||
| `maintenance_interval_days` | 28 | Minimum gap between cycles |
|
||
| `maintenance_start_hour` | 10 | Hour of day a due cycle begins |
|
||
| `maintenance_discharge_w` | 2500 | Drain rate (exports the surplus) |
|
||
| `maintenance_charge_w` | 2500 | Charge ceiling, capped again by peak headroom |
|
||
| `maintenance_soc_floor` | 11 | Drain target — stay just above the inverter's own floor |
|
||
| `maintenance_soc_target` | 99 | Charge target |
|
||
| `maintenance_hold_min` | 120 | Hold at full so the BMS can balance |
|
||
|
||
### Tariff (all optional)
|
||
|
||
| option | meaning |
|
||
|---|---|
|
||
| `peak_forecast_entity` | Quarter-hour demand forecast, for capacity-tariff markets. Empty = no cap |
|
||
| `peak_cap_w` | The site's capacity-tariff target |
|
||
| `price_now_entity`, `price_avg_entity` | Dynamic tariff. Empty = never force a paid grid top-up |
|
||
|
||
On a capacity-tariff site the maintenance charge is capped by the headroom left
|
||
under `peak_cap_w`, and if the forecast goes over the cap the charge-only clamp
|
||
is dropped so the battery can shave the peak instead. Money outranks the
|
||
maintenance schedule.
|
||
|
||
### Site
|
||
|
||
| option | meaning |
|
||
|---|---|
|
||
| `estop_fitted` | Whether the RS485 e-stop is installed. Drives the warning banner |
|
||
| `log_level` | `trace`/`debug`/`info`/`warning`/`error` |
|
||
|
||
## The Web UI
|
||
|
||
The ingress panel shows live values, why the controller is commanding what it
|
||
is, and a **Commissioning** checklist that names any problem in words. It also
|
||
carries the three buttons: start/stop control, force a maintenance cycle, and
|
||
abort one.
|
||
|
||
## Status entities
|
||
|
||
If an MQTT broker is available the add-on publishes setpoint, grid power,
|
||
battery power, state of charge, maintenance phase and controller status by MQTT
|
||
discovery. This is observability only — the controller works fine without a
|
||
broker, and MQTT problems can never affect control.
|