Files
goodwe-addon/README.md
T
glenn schrooyenandClaude Opus 5.5 a4a7ff695e
checks / unit (pull_request) Successful in 40m29s
infra-pin / fresh (pull_request) Successful in 1s
DOC-8 review: say the field guide PDF is rebuilt, still v0.1.0
README and FIELD-GUIDE.md said the PDF was not rebuilt; it was, on
2026-10-17. Its content is still v0.1.0 and outdated. Rewrap the
DOCS.md intro line. Text only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 03:27:35 +02:00

135 lines
7.0 KiB
Markdown

# GoodWe RS485 Controller — Home Assistant add-on repository
Replaces a GoodWe battery inverter's vendor controller with a local one: holds
net grid exchange at zero by sending the inverter a grid reading over its RS485
EMS port, and runs the monthly battery maintenance cycle the vendor box was doing.
```
repository.yaml add-on repository metadata
goodwe_controller/ the add-on
config.yaml manifest + options schema (the per-site config form)
app/ the controller
control.py the control law — pure functions, no I/O
maintenance.py the monthly cycle state machine
hass.py Supervisor/Core API access
store.py persistent state in /data
mqtt.py optional status entities
web.py ingress UI — routes only, status and configuration
config_page.py the configuration page's markup, style and script
configui.py its field model, built from the Supervisor's own
schema + translations — owns no key, label or rule
selfconfig.py reading and writing this add-on's own options,
with the validate-first / compare-and-swap gate
main.py orchestration, heartbeat, failsafe behaviour
test_control.py runnable checks — no framework needed
test_maintenance.py runnable checks — walks a full cycle in fake time
DOCS.md the add-on's documentation tab
MUTATION.md how to run a mutation so the result means something
tools/mutation_run.sh the mutation harness - purges bytecode, then grades
tools/supervisor_selfapi_probe.py what the Supervisor really permits and
returns, run against its own release image
firmware/goodwe-master.yaml ESPHome config for the T-CAN485
estop/rs485_log.py optional RS485 e-stop / bus witness
docs/GoodWe-RS485-Field-Guide.pdf the guide techs carry (10pp, A4; rebuilt 2026-10-17, content still v0.1.0 and outdated)
docs/build_guide.py its single source - rebuilds the HTML and PDF
FIELD-GUIDE.md how to edit and rebuild the guide
docs/RELEASE.md how a change reaches the battery - `master` is
protected, releases go via PR from `release/1.0`
```
**Picking this up after a break? Read `HANDOVER.md` first** — current state,
what is known broken, and what to do next.
## Install
Settings → Add-ons → Add-on store → ⋮ → **Repositories** → add
```
https://gittea.kammenstraatha.duckdns.org/admin/goodwe-addon
```
then install **GoodWe RS485 Controller**.
Then turn on **Watchdog** on the add-on's Info tab — see
[Before you start](goodwe_controller/DOCS.md#before-you-start).
⚠️ **The repository must be PUBLIC for this to work.** Home Assistant clones it
anonymously; against a private repo the store shows nothing and gives no useful
error. If it must stay private, install locally instead: copy
`goodwe_controller/` into the machine's `/addons/` folder, then
`ha store reload` — it appears as a Local add-on.
⚠️ **Bump `version:` in `goodwe_controller/config.yaml` for every change.**
Supervisor keys the built image by that version, so without a bump it silently
reuses the old image and your fix appears not to work — on your bench and on
every client's machine.
Requires **Home Assistant OS or Supervised**. Add-ons cannot be installed on HA
Container or Core.
## Read this first
**The inverter holds its last command forever.** It has no meter-timeout of its
own — a controller that dies mid-command leaves the battery running until a
human intervenes. Measured on real hardware: 5 kW of discharge held for 113
seconds after a controller went silent.
Three layers exist because of that, and only the third covers the Home Assistant
machine itself dying:
1. **ESP32 watchdog** — no fresh setpoint for ~30 s → command 0 W, and keep
commanding it.
2. **Wind-down before firmware updates** — 0 W written before the update starts.
3. **RS485 e-stop (optional)** — writes 0 W after 30 s of total bus silence.
Sites sold without the e-stop must have the client acknowledgement in the field
guide (Step 7b) signed.
## Development
The control law and the maintenance machine are pure Python with no Home
Assistant imports, so they run anywhere:
```bash
cd goodwe_controller
python3 test_control.py
python3 test_maintenance.py
```
Both must pass before shipping any change. To prove a check *bites* - the way
almost every claim in this repository was established - mutate the module and
run `tools/mutation_run.sh`, which clears `__pycache__` first. It is not
optional: a same-size edit reverted inside one second leaves CPython reusing the
old bytecode, and the run grades code that is not there. `goodwe_controller/MUTATION.md`
has the mechanism, the measurement, and what the flags do not do. They are not unit-test theatre —
each assertion corresponds to a rule whose absence produced an observed failure
on real hardware, and the comments in `control.py` say which.
### CI and the infra pin
CI (`.gitea/workflows/checks.yml`) runs every `test_*.py`, with `admin/goodwe` checked out next to this repo at the commit in `.gitea/infra-pin`, so `test_freeze.py` grades against the real `sim/hwsim.py`. If that checkout fails, the job fails. The local stub is used only in local runs, and it prints `STUB USED` when it is.
The pin tracks `admin/goodwe` **release/1.0**, not master. Two non-required checks go red when release/1.0 has moved past the pin: `infra-pin / fresh` here, on any add-on PR or push to release/1.0, and `addon-pin / fresh` in `admin/goodwe`, on every push to its release/1.0. Nothing re-runs `addon-pin` after a bump, so it stays red on that admin/goodwe commit; `infra-pin / fresh` green on the bump is the all-clear.
**Bump the pin** whenever either is red. Set `.gitea/infra-pin` to the new release/1.0 SHA (`git ls-remote <gitea>/admin/goodwe.git refs/heads/release/1.0`) in a PR, and let `checks / unit` grade the tests against it. CI reads `admin/goodwe` with the Actions secret `INFRA_READ_TOKEN`.
## Compatibility
**Check the serial number, not the model name.** Characters 6-8 of the serial are
the platform tag:
| tag | platform | verdict |
|---|---|---|
| `ESU EMU ESA BPS BPU EMJ IJL` | 105 — ES/EM/BP, AA55-era | **supported** |
| `SPB SPN` | 745 — SBP **G2** (`GW…-SBP-20`) | no |
| `ESN ESC` | 745 — ES **G2** (`GW…-ES-20`) | no |
| `ETU EHU BTU …` | 205/745/753 — ET/EH/BT hybrids | no |
| `SDT DST MSU NSU …` | PV-only inverters — no battery to control | no |
A "-20" or "G2" suffix means a different platform with a different
protocol. The tags come from the `goodwe` library's `PLATFORM_105_MODELS`, which
is also what Home Assistant's own integration uses to pick a protocol.
- GoodWe **ES / BP family** inverters (AA55 generation)
- The protocol is **reverse-engineered**. There is no vendor contract, and a
firmware change on GoodWe's side could break every installation at once. Say
so when you sell it.