diff --git a/FIELD-GUIDE.md b/FIELD-GUIDE.md index a3fc315..2f0f486 100644 --- a/FIELD-GUIDE.md +++ b/FIELD-GUIDE.md @@ -1,382 +1,41 @@ -# GoodWe RS485 Controller — Field Guide +# 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. +The guide technicians carry is the PDF: ---- +**`docs/GoodWe-RS485-Field-Guide.pdf`** — 10 pages, A4, print it double-sided. -## 1. The one fact this whole product is built around +It covers, in the order the work is done: pre-visit checks, hardware, firmware, +add-on install and configuration, how to read the Web UI, the six commissioning +gates, the e-stop (and the statement a site without one must sign), proving a +maintenance cycle, handover with a sign-off sheet, and troubleshooting. -**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. +## Editing it -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: +The text lives in `docs/build_guide.py` — deliberately one source, so a Markdown +copy cannot drift from the PDF someone is holding in a cellar. ```bash -python3 rs485_log.py --out /home/pi/bus.log --panic +python3 docs/build_guide.py # regenerates field-guide.html and the PDF ``` -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. +Requires Chrome or Chromium (used headless for print-to-PDF). Screenshots live +in `docs/img/` and are base64-embedded, so `field-guide.html` is standalone. -⚠️ Arm the panic write **only** while our controller owns the bus. Pointed at a -bus somebody else is driving, it is unrequested interference. +⚠️ **Every section must stay under the A4 printable height** (1039 px at 96 dpi +with the configured margins). Chrome silently spills an over-long section onto a +second page, and the pagination stops matching the step numbers. After editing, +open `field-guide.html` and check: -⚠️ 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. +```js +document.querySelectorAll('.page').forEach(el => + console.log(el.getBoundingClientRect().height)) // each must be < 1039 +``` -**Not fitted — put this in front of the client, in writing:** +The PDF should always come out at exactly **one page per section**. -> 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. +## Regenerating the screenshots -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. +`docs/img/ui-running.png` and `ui-notready.png` are real captures of the add-on's +own Web UI, taken by running it against stub data. If the UI changes, retake them +— a field guide showing a screen that no longer exists is worse than one with no +screenshots at all. diff --git a/README.md b/README.md index 9f70acf..307988c 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,9 @@ goodwe_controller/ the add-on DOCS.md the add-on's documentation tab firmware/goodwe-master.yaml ESPHome config for the T-CAN485 estop/rs485_log.py optional RS485 e-stop / bus witness -FIELD-GUIDE.md installation, commissioning gates, troubleshooting +docs/GoodWe-RS485-Field-Guide.pdf the guide techs carry (10pp, A4) +docs/build_guide.py its single source - rebuilds the HTML and PDF +FIELD-GUIDE.md how to edit and rebuild the guide ``` ## Install diff --git a/docs/GoodWe-RS485-Field-Guide.pdf b/docs/GoodWe-RS485-Field-Guide.pdf new file mode 100644 index 0000000..a4e1155 Binary files /dev/null and b/docs/GoodWe-RS485-Field-Guide.pdf differ diff --git a/docs/build_guide.py b/docs/build_guide.py new file mode 100644 index 0000000..1a8e93a --- /dev/null +++ b/docs/build_guide.py @@ -0,0 +1,513 @@ +"""Build the field guide PDF: python3 docs/build_guide.py + +HTML -> headless Chrome -> A4 PDF. The guide's text lives in this file, so there +is exactly one copy of it and no chance of a Markdown version drifting from the +PDF a technician actually carries. + +Screenshots in docs/img/ are base64-embedded, so field-guide.html is standalone. + +⚠️ Keep every section under the A4 printable height (1039 px at 96 dpi with the +11 mm margins set below), or Chrome silently spills it onto a second page and +the pagination stops matching the steps. To check after editing: + + document.querySelectorAll(".page").forEach(el => + console.log(el.getBoundingClientRect().height)) // must each be < 1039 +""" +import base64 +import os +import subprocess +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +SCRATCH = os.path.join(HERE, "img") +OUT_DIR = HERE +CHROME = r"C:/Program Files/Google/Chrome/Application/chrome.exe" + + +def img(name): + with open(os.path.join(SCRATCH, name), "rb") as fh: + return "data:image/png;base64," + base64.b64encode(fh.read()).decode() + + +UI_OK = img("ui-running.png") +UI_BAD = img("ui-notready.png") +ESPHOME = img("esphome.png") + +HTML = """ +GoodWe RS485 Controller — Field Guide + + + +
+
+

GoodWe RS485 Controller

+
Field installation & commissioning guide — v0.1.0
+ Installer copy +
+ +
+

Read this before touching anything

+

The inverter holds its last command forever. It has no meter-timeout. If the + controller stops talking, the battery keeps charging or discharging at whatever it was last + told — indefinitely.

+

Measured: a controller went silent mid-command and the inverter held + 5 kW of discharge for 113 seconds until a person intervened.

+
+ +
+ If anything looks wrong at any point: STOP THE ADD-ON. That commands 0 W and the + hardware watchdog holds it there. Nothing is at risk while you think. +
+ +

Order of operations

+

Do not reorder. Each step assumes the previous one passed.

+ + + + + + + + + + + + +
StepActionGate to pass
0Pre-visit checks (client, warranty, HA type)All boxes ticked
1Survey & photograph existing wiringPhotos taken
2Bring system to 0 W, disconnect vendor controllerBattery idle
3Wire and power the T-CAN485Board online
4Flash the ESP32 firmwareEntities appear in HA
5Install & configure the add-onAll checks green
6Commissioning gates 1–6Every gate passes
7E-stop: fit & test, or get signatureTested or signed
8Prove a maintenance cyclePhases observed
9Handover & sign-offSheet complete
+ +

The safety model

+ + + + + +
LayerCoversWhere it lives
1 WatchdogController alive but silent → 0 W, and keeps writing itESP32 firmware
2 Wind-downPlanned firmware update → 0 W before it startsESP32 firmware
3 E-stopThe HA machine itself dying → 0 W after 30 s of bus silenceOptional Pi + RS485
+
+ Only layer 3 covers the host dying. Nothing running on the HA machine can cover its own + death. On a site without the e-stop, a failed host leaves the battery latched until someone + intervenes manually. See Step 7. +
+
+ + +
+

Step 0 — Before you travel

+
+
+

Confirm with the client

+
    +
  • Inverter is GoodWe ES / BP family
  • +
  • Vendor controller may be disconnected
  • +
  • Warranty / installer agreement acknowledged in writing
  • +
  • Grid rules checked (BE: Synergrid C10/11)
  • +
  • Vendor subscription — cancel only after sign-off
  • +
+
+
+

Confirm on their system

+
    +
  • HA is OS or Supervised (Settings → System → Repairs → ⋮ → System information)
  • +
  • Grid power sensor exists, updates ≤ 10 s
  • +
  • Note its entity id and update rate
  • +
  • E-stop in the van, or signature form printed
  • +
+
+
+
+

Stop conditions

+

If HA is Container or Core, add-ons cannot be installed and this + product cannot go in. If the grid sensor updates slower than ~10 s, the default tuning is wrong + for the site — do not proceed without adjusting gain.

+
+ +

Never do these

+ + + + + + + +
NeverBecause
Flash third-party firmware on the inverter's WiFi dongleCommon image targets different hardware — bricks it
Long-press (3–5 s) the inverter's WiFi Reset buttonFactory-resets the dongle, loses network access. Short press (~1 s) is safe
Connect our controller while the vendor box is still attachedTwo masters on one bus — contradictory commands
"Compensate" for an inverted meter sign in the tuningFix meter_invert or the wiring. A wrong sign drives the grid away from zero at full gain
Raise the e-stop's 30 s threshold to stop it firing during updatesFiring during an update is correct behaviour
+
+ + +
+

Steps 1–3 — Hardware

+
Qualified persons only. Mains AC and battery DC are present. The meter bus + is low-voltage but sits inside an energised installation.
+ +
+

1 Survey and record

+
    +
  1. Photograph the meter-port wiring before touching it.
  2. +
  3. Identify the RS485 pair (A/B) running to the vendor controller.
  4. +
  5. Record inverter model + serial, battery capacity (Ah) and nominal voltage.
  6. +
+
VERIFY — photos on file; A/B pair positively identified.
+
+ +
+

2 Disconnect the vendor controller

+
    +
  1. Bring the system to 0 W — battery neither charging nor discharging (check the + inverter display).
  2. +
  3. Disconnect the vendor controller from the meter port.
  4. +
  5. Leave it physically mounted but unplugged — that keeps the install reversible and gives + you a rollback story.
  6. +
+
VERIFY — battery at 0 W, vendor box disconnected.
+
IF NOT — do not continue while the vendor box can still write to the bus.
+
+ +
+

3 Fit and power the T-CAN485

+
    +
  1. Wire A→A, B→B to the meter port, plus its own supply.
  2. +
  3. If fitting the e-stop, connect its USB-RS485 adapter to the same pair, in parallel.
  4. +
  5. Power the T-CAN485 last.
  6. +
+
VERIFY — board joins WiFi and appears in ESPHome.
+
IF NOT — swapped A/B gives a completely silent bus, not an error. + Swap and retry before suspecting anything else.
+
+ +
It transmits within seconds of boot. Never power it up while the vendor + controller is connected.
+
+ + +
+

Step 4 — Flash the firmware

+
+

4 ESPHome

+
    +
  1. Open the ESPHome Device Builder add-on on the client's HA.
  2. +
  3. Create a device from firmware/goodwe-master.yaml.
  4. +
  5. Set the three substitutions at the top — and nothing else.
  6. +
  7. Add the client's WiFi to ESPHome's secrets.yaml.
  8. +
  9. Install: first flash by USB, everything after that over the air.
  10. +
+
VERIFY — device shows Online, and + number.<node>_goodwe_setpoint_w exists in HA.
+
+ + + + + + + + + +
SubstitutionSet toWhy it matters
nameNode name, e.g. goodwe-masterWrite it on the sheet. Changing it later renames every entity in HA and silently breaks the add-on
max_wInverter continuous rating, e.g. 5000Hard firmware limit, independent of HA. Last line of defence against a controller bug
wd_ms30000Watchdog timeout. Must stay well above the add-on heartbeat (10 s)
+ +
+ ESPHome Device Builder +
ESPHome Device Builder — the node must read Online before you continue. + (Reference bench shown.)
+
+ +
Testing firmware changes: an update that lands new shutdown-path code + still runs the old code on the way out. If you change wind-down or watchdog behaviour, + upload twice before believing a test result.
+
+ + +
+

Step 5 — Install and configure the add-on

+
+

5 Add-on

+
    +
  1. Settings → Add-ons → Add-on store → ⋮ → Repositories → add the repository URL.
  2. +
  3. Install GoodWe RS485 Controller. Do not start it yet.
  4. +
  5. Open Configuration and fill in the table below.
  6. +
  7. Start the add-on, open its Web UI.
  8. +
  9. Work down the Commissioning list until every line is green.
  10. +
+
VERIFY — banner is green or amber, no red lines.
+
IF NOT — the banner names the failing item. Copy entity ids from + Developer Tools → States. Do not type them from memory.
+
+ + + + + + + + + + + +
OptionSet to
meter_entityClient's grid power sensor. Positive must mean importing — else set meter_invert
soc_entitysensor.<node>_goodwe_battery_socthe ESP32's own read, never the cloud/dongle sensor
batt_entitysensor.<node>_goodwe_inverter_ac_power
setpoint_entitynumber.<node>_goodwe_setpoint_w
max_w1000 for commissioning. Raise at the end of Step 6
estop_fittedtrue only if you actually fitted one
peak_forecast_entityCapacity-tariff sites only, else leave empty
price_now_entity / price_avg_entityDynamic-tariff sites only, else leave empty
+ +
+

Entity ids are the #1 install error

+

HA prefixes entity ids with the device's area at creation time, so the + same firmware yields 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 system two safety alarms + pointed at non-existent entities and were dead for a day while reading “on”.

+
+ +
+ + +
+

Reading the Web UI

+

Two states you must tell apart at a glance. The + banner is the summary; the Commissioning list is the detail.

+ +
+ Web UI running normally +
HEALTHY. Green banner. All checks ✓. Why reads tracking. + Grid near zero and the commanded figure resting between updates — a command that + moves every single cycle means the deadband is too small for this meter.
+
+ +
+ Web UI showing NOT READY +
NOT READY. Red = must fix before going further; the banner names the first + failing item. Amber = permitted, but the client must have signed for it — here, no e-stop + fitted. Note Why reads inputs-missing and Commanded has gone to + 0 W: with a source missing, the controller zeroes the inverter rather than guessing.
+
+
+ + +
+

Step 6 — Commissioning gates

+

Judge every gate on the wire or the meter — never on + how Home Assistant looks.

+ + + + + + + + + + + + + + + + + + + +
#DoPassIf it fails
1Add-on running, control stoppedSteady 0 W written, inverter idle, no errorsCheck wiring and Step 5
2By hand (Developer Tools → number.set_value): + +300 W, then −300 WBattery discharges ~300 W, then charges ~300 W, within ~10 sSign inverted → fix batt_invert/wiring, not the tuning
3Compare ESP32 readings to inverter displaySoC identical; power within ~5 %Wrong entity, or dongle sensor used by mistake
4Set +300 W, then STOP the add-onInverter reaches 0 W within ~30 s and staysSTOP THE INSTALL. Nothing here is safe without this
5max_w=1000, start controlGrid settles within tens of watts of zero and restsHunting → raise deadband_w; slow meter → lower gain
6Switch on a kettle/oven (~2 kW)Grid back near zero within ~20 s; command stops rising once the battery catches upCommand keeps climbing = runaway. Stop the add-on immediately
+ +
THEN — raise max_w to the value the site is sold with and + repeat gate 6 once.
+ +
+ + +
+

Step 7 — The e-stop

+ +
+

7a If fitted

+
    +
  1. Connect the Pi to the same RS485 pair; power it.
  2. +
  3. Run: python3 rs485_log.py --out /home/pi/bus.log --panic
  4. +
  5. Confirm it prints PANIC ARMED.
  6. +
  7. Test it: with the battery at +300 W, cut power to the T-CAN485.
  8. +
+
VERIFY — within ~35 s the log shows a TX line and the + inverter goes to 0 W.
+
IF NOT — check the adapter is on the correct pair and that the tap was + started with --panic.
+
+ +
+ Arm the panic write only while our controller owns the bus. Pointed at a bus somebody else + drives, it is unrequested interference.
+ It will fire during every firmware update — the upload silence exceeds 30 s. That is + correct. Do not raise the threshold. +
+ +

7b — If not fitted: client acknowledgement

+

Read this to the client and have them sign. Note it on the sign-off sheet.

+
+

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.

+
+
Client name
+
Signature
+
Date
+
+
+ +

Step 8 — Prove a maintenance cycle

+
+

8 Do not wait a month to find out it never fires

+
    +
  1. Web UI → Force maintenance cycle.
  2. +
  3. Watch the phase go drain → charge → hold → idle.
  4. +
  5. Confirm the inverter exports during drain, charges during charge, sits at + 0 W during hold.
  6. +
  7. Set maintenance_enabled: true.
  8. +
+
SHORT VERSION (~10 min) if the client cannot spare hours: set + maintenance_soc_floor just below current SoC, maintenance_soc_target + just below that, maintenance_hold_min: 5. Restore real values afterwards and + record it as a partial test.
+
+

Why it matters: the full charge is when the BMS balances cells; the deep discharge + recalibrates the 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.

+
+ + +
+

Step 9 — Handover & sign-off

+
+

Tell the client, in these words

+
    +
  • “This is the Web UI. Green means it is working.”
  • +
  • If anything looks wrong, press stop. That sets the battery to zero and it idles + safely.”
  • +
  • “Once a month it will deliberately empty and then fill the battery. That is normal and it + keeps the battery healthy.”
  • +
+
+ +
+

Commissioning record

+ + + + + + +
Site / client
Inverter model / serial
ESPHome node name
Add-on version / max_w
Meter entity + update rate
+
    +
  • Gates 1–6 all passed on the wire or the meter
  • +
  • Gate 4 (watchdog) personally observed reaching 0 W
  • +
  • Maintenance cycle proven — full / partial (circle one)
  • +
  • E-stop fitted & tested OR §7b signed
  • +
  • Client shown the Web UI and the stop button
  • +
  • Vendor subscription cancelled (only after the above)
  • +
+
+
Technician
+
Signature
+
Date
+
+
+
+ + +
+

Troubleshooting

+ + + + + + + + + + + + + +
SymptomLikely causeDo this
Web UI says NOT READYWrong entity id, or sensor unavailableBanner names it. Copy the id from Developer Tools → States
Configured, but nothing movesControl is stoppedPress start; banner turns green
Inverter does nothing, no errorsA/B swapped, or vendor box still connectedSwapped RS485 gives a silent bus, not an error
HA shows one setpoint, inverter does anotherFirmware max_w below add-on max_wFirmware wins by design. Align them
Log shows writes rejected (HTTP 400)Value outside the number entity's rangeLower max_w; check the firmware substitution
Command keeps climbing while the battery is pinnedRunaway — sign inverted or saturation defeatedStop the add-on now. Verify meter_invert with a known load
Grid hunts, never restsDeadband too small, or meter too slowRaise deadband_w; if meter > 10 s, lower gain
Battery flat overnight and stays flatMaintenance drain never exitedCheck the SoC entity is the ESP32's, not a cached cloud value
Maintenance never runsDisabled, or not yet dueCheck maintenance_enabled and the “Maintenance due” row
E-stop fires on every updateExpected — upload silence > 30 sNothing to fix. Do not raise the threshold
Add-on will not installHA is Container/CoreNot supportable — see Step 0
+ +

Why the tuning is what it is

+

Every value came from hardware. Do not change them without measuring.

+ + + + + + + + +
SettingValueReason
gain0.6Inverter needs 3–6 s to settle (1.4 s dead time, 94 % by 3.3 s) against a ~5 s cycle. At the limit — do not raise
slew_w1000Most of a correction in the first cycle without running far ahead of the hardware
deadband_w15Measured residual: mean 15.4 W, max 27 W. At 10 W the command never rests
saturation_w / cycles500 / 3Divergence = inverter at a limit → magnitude may fall, never rise. The 3-cycle term is essential: tested instantly it fires on every large correction
step_w10Register is 1 W but response lands on a ~17.6 W ladder. Finer is meaningless
heartbeat_s10Three chances to be heard before the 30 s watchdog acts
+
The failure this guards 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.
+
+ + +""" + +HTML = HTML.replace("{UI_OK}", UI_OK).replace("{UI_BAD}", UI_BAD).replace("{ESPHOME}", ESPHOME) + +os.makedirs(OUT_DIR, exist_ok=True) +html_path = os.path.join(OUT_DIR, "field-guide.html") +pdf_path = os.path.join(OUT_DIR, "GoodWe-RS485-Field-Guide.pdf") +with open(html_path, "w", encoding="utf-8") as fh: + fh.write(HTML) +print(f"html: {os.path.getsize(html_path):,} bytes") + +subprocess.run([ + CHROME, "--headless=new", "--disable-gpu", "--no-pdf-header-footer", + f"--print-to-pdf={pdf_path}", "file:///" + html_path.replace("\\", "/"), +], check=True, capture_output=True, timeout=180) +print(f"pdf : {os.path.getsize(pdf_path):,} bytes -> {pdf_path}") diff --git a/docs/field-guide.html b/docs/field-guide.html new file mode 100644 index 0000000..a42d071 --- /dev/null +++ b/docs/field-guide.html @@ -0,0 +1,462 @@ + +GoodWe RS485 Controller — Field Guide + + + +
+
+

GoodWe RS485 Controller

+
Field installation & commissioning guide — v0.1.0
+ Installer copy +
+ +
+

Read this before touching anything

+

The inverter holds its last command forever. It has no meter-timeout. If the + controller stops talking, the battery keeps charging or discharging at whatever it was last + told — indefinitely.

+

Measured: a controller went silent mid-command and the inverter held + 5 kW of discharge for 113 seconds until a person intervened.

+
+ +
+ If anything looks wrong at any point: STOP THE ADD-ON. That commands 0 W and the + hardware watchdog holds it there. Nothing is at risk while you think. +
+ +

Order of operations

+

Do not reorder. Each step assumes the previous one passed.

+ + + + + + + + + + + + +
StepActionGate to pass
0Pre-visit checks (client, warranty, HA type)All boxes ticked
1Survey & photograph existing wiringPhotos taken
2Bring system to 0 W, disconnect vendor controllerBattery idle
3Wire and power the T-CAN485Board online
4Flash the ESP32 firmwareEntities appear in HA
5Install & configure the add-onAll checks green
6Commissioning gates 1–6Every gate passes
7E-stop: fit & test, or get signatureTested or signed
8Prove a maintenance cyclePhases observed
9Handover & sign-offSheet complete
+ +

The safety model

+ + + + + +
LayerCoversWhere it lives
1 WatchdogController alive but silent → 0 W, and keeps writing itESP32 firmware
2 Wind-downPlanned firmware update → 0 W before it startsESP32 firmware
3 E-stopThe HA machine itself dying → 0 W after 30 s of bus silenceOptional Pi + RS485
+
+ Only layer 3 covers the host dying. Nothing running on the HA machine can cover its own + death. On a site without the e-stop, a failed host leaves the battery latched until someone + intervenes manually. See Step 7. +
+
+ + +
+

Step 0 — Before you travel

+
+
+

Confirm with the client

+
    +
  • Inverter is GoodWe ES / BP family
  • +
  • Vendor controller may be disconnected
  • +
  • Warranty / installer agreement acknowledged in writing
  • +
  • Grid rules checked (BE: Synergrid C10/11)
  • +
  • Vendor subscription — cancel only after sign-off
  • +
+
+
+

Confirm on their system

+
    +
  • HA is OS or Supervised (Settings → System → Repairs → ⋮ → System information)
  • +
  • Grid power sensor exists, updates ≤ 10 s
  • +
  • Note its entity id and update rate
  • +
  • E-stop in the van, or signature form printed
  • +
+
+
+
+

Stop conditions

+

If HA is Container or Core, add-ons cannot be installed and this + product cannot go in. If the grid sensor updates slower than ~10 s, the default tuning is wrong + for the site — do not proceed without adjusting gain.

+
+ +

Never do these

+ + + + + + + +
NeverBecause
Flash third-party firmware on the inverter's WiFi dongleCommon image targets different hardware — bricks it
Long-press (3–5 s) the inverter's WiFi Reset buttonFactory-resets the dongle, loses network access. Short press (~1 s) is safe
Connect our controller while the vendor box is still attachedTwo masters on one bus — contradictory commands
"Compensate" for an inverted meter sign in the tuningFix meter_invert or the wiring. A wrong sign drives the grid away from zero at full gain
Raise the e-stop's 30 s threshold to stop it firing during updatesFiring during an update is correct behaviour
+
+ + +
+

Steps 1–3 — Hardware

+
Qualified persons only. Mains AC and battery DC are present. The meter bus + is low-voltage but sits inside an energised installation.
+ +
+

1 Survey and record

+
    +
  1. Photograph the meter-port wiring before touching it.
  2. +
  3. Identify the RS485 pair (A/B) running to the vendor controller.
  4. +
  5. Record inverter model + serial, battery capacity (Ah) and nominal voltage.
  6. +
+
VERIFY — photos on file; A/B pair positively identified.
+
+ +
+

2 Disconnect the vendor controller

+
    +
  1. Bring the system to 0 W — battery neither charging nor discharging (check the + inverter display).
  2. +
  3. Disconnect the vendor controller from the meter port.
  4. +
  5. Leave it physically mounted but unplugged — that keeps the install reversible and gives + you a rollback story.
  6. +
+
VERIFY — battery at 0 W, vendor box disconnected.
+
IF NOT — do not continue while the vendor box can still write to the bus.
+
+ +
+

3 Fit and power the T-CAN485

+
    +
  1. Wire A→A, B→B to the meter port, plus its own supply.
  2. +
  3. If fitting the e-stop, connect its USB-RS485 adapter to the same pair, in parallel.
  4. +
  5. Power the T-CAN485 last.
  6. +
+
VERIFY — board joins WiFi and appears in ESPHome.
+
IF NOT — swapped A/B gives a completely silent bus, not an error. + Swap and retry before suspecting anything else.
+
+ +
It transmits within seconds of boot. Never power it up while the vendor + controller is connected.
+
+ + +
+

Step 4 — Flash the firmware

+
+

4 ESPHome

+
    +
  1. Open the ESPHome Device Builder add-on on the client's HA.
  2. +
  3. Create a device from firmware/goodwe-master.yaml.
  4. +
  5. Set the three substitutions at the top — and nothing else.
  6. +
  7. Add the client's WiFi to ESPHome's secrets.yaml.
  8. +
  9. Install: first flash by USB, everything after that over the air.
  10. +
+
VERIFY — device shows Online, and + number.<node>_goodwe_setpoint_w exists in HA.
+
+ + + + + + + + + +
SubstitutionSet toWhy it matters
nameNode name, e.g. goodwe-masterWrite it on the sheet. Changing it later renames every entity in HA and silently breaks the add-on
max_wInverter continuous rating, e.g. 5000Hard firmware limit, independent of HA. Last line of defence against a controller bug
wd_ms30000Watchdog timeout. Must stay well above the add-on heartbeat (10 s)
+ +
+ ESPHome Device Builder +
ESPHome Device Builder — the node must read Online before you continue. + (Reference bench shown.)
+
+ +
Testing firmware changes: an update that lands new shutdown-path code + still runs the old code on the way out. If you change wind-down or watchdog behaviour, + upload twice before believing a test result.
+
+ + +
+

Step 5 — Install and configure the add-on

+
+

5 Add-on

+
    +
  1. Settings → Add-ons → Add-on store → ⋮ → Repositories → add the repository URL.
  2. +
  3. Install GoodWe RS485 Controller. Do not start it yet.
  4. +
  5. Open Configuration and fill in the table below.
  6. +
  7. Start the add-on, open its Web UI.
  8. +
  9. Work down the Commissioning list until every line is green.
  10. +
+
VERIFY — banner is green or amber, no red lines.
+
IF NOT — the banner names the failing item. Copy entity ids from + Developer Tools → States. Do not type them from memory.
+
+ + + + + + + + + + + +
OptionSet to
meter_entityClient's grid power sensor. Positive must mean importing — else set meter_invert
soc_entitysensor.<node>_goodwe_battery_socthe ESP32's own read, never the cloud/dongle sensor
batt_entitysensor.<node>_goodwe_inverter_ac_power
setpoint_entitynumber.<node>_goodwe_setpoint_w
max_w1000 for commissioning. Raise at the end of Step 6
estop_fittedtrue only if you actually fitted one
peak_forecast_entityCapacity-tariff sites only, else leave empty
price_now_entity / price_avg_entityDynamic-tariff sites only, else leave empty
+ +
+

Entity ids are the #1 install error

+

HA prefixes entity ids with the device's area at creation time, so the + same firmware yields 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 system two safety alarms + pointed at non-existent entities and were dead for a day while reading “on”.

+
+ +
+ + +
+

Reading the Web UI

+

Two states you must tell apart at a glance. The + banner is the summary; the Commissioning list is the detail.

+ +
+ Web UI running normally +
HEALTHY. Green banner. All checks ✓. Why reads tracking. + Grid near zero and the commanded figure resting between updates — a command that + moves every single cycle means the deadband is too small for this meter.
+
+ +
+ Web UI showing NOT READY +
NOT READY. Red = must fix before going further; the banner names the first + failing item. Amber = permitted, but the client must have signed for it — here, no e-stop + fitted. Note Why reads inputs-missing and Commanded has gone to + 0 W: with a source missing, the controller zeroes the inverter rather than guessing.
+
+
+ + +
+

Step 6 — Commissioning gates

+

Judge every gate on the wire or the meter — never on + how Home Assistant looks.

+ + + + + + + + + + + + + + + + + + + +
#DoPassIf it fails
1Add-on running, control stoppedSteady 0 W written, inverter idle, no errorsCheck wiring and Step 5
2By hand (Developer Tools → number.set_value): + +300 W, then −300 WBattery discharges ~300 W, then charges ~300 W, within ~10 sSign inverted → fix batt_invert/wiring, not the tuning
3Compare ESP32 readings to inverter displaySoC identical; power within ~5 %Wrong entity, or dongle sensor used by mistake
4Set +300 W, then STOP the add-onInverter reaches 0 W within ~30 s and staysSTOP THE INSTALL. Nothing here is safe without this
5max_w=1000, start controlGrid settles within tens of watts of zero and restsHunting → raise deadband_w; slow meter → lower gain
6Switch on a kettle/oven (~2 kW)Grid back near zero within ~20 s; command stops rising once the battery catches upCommand keeps climbing = runaway. Stop the add-on immediately
+ +
THEN — raise max_w to the value the site is sold with and + repeat gate 6 once.
+ +
+ + +
+

Step 7 — The e-stop

+ +
+

7a If fitted

+
    +
  1. Connect the Pi to the same RS485 pair; power it.
  2. +
  3. Run: python3 rs485_log.py --out /home/pi/bus.log --panic
  4. +
  5. Confirm it prints PANIC ARMED.
  6. +
  7. Test it: with the battery at +300 W, cut power to the T-CAN485.
  8. +
+
VERIFY — within ~35 s the log shows a TX line and the + inverter goes to 0 W.
+
IF NOT — check the adapter is on the correct pair and that the tap was + started with --panic.
+
+ +
+ Arm the panic write only while our controller owns the bus. Pointed at a bus somebody else + drives, it is unrequested interference.
+ It will fire during every firmware update — the upload silence exceeds 30 s. That is + correct. Do not raise the threshold. +
+ +

7b — If not fitted: client acknowledgement

+

Read this to the client and have them sign. Note it on the sign-off sheet.

+
+

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.

+
+
Client name
+
Signature
+
Date
+
+
+ +

Step 8 — Prove a maintenance cycle

+
+

8 Do not wait a month to find out it never fires

+
    +
  1. Web UI → Force maintenance cycle.
  2. +
  3. Watch the phase go drain → charge → hold → idle.
  4. +
  5. Confirm the inverter exports during drain, charges during charge, sits at + 0 W during hold.
  6. +
  7. Set maintenance_enabled: true.
  8. +
+
SHORT VERSION (~10 min) if the client cannot spare hours: set + maintenance_soc_floor just below current SoC, maintenance_soc_target + just below that, maintenance_hold_min: 5. Restore real values afterwards and + record it as a partial test.
+
+

Why it matters: the full charge is when the BMS balances cells; the deep discharge + recalibrates the 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.

+
+ + +
+

Step 9 — Handover & sign-off

+
+

Tell the client, in these words

+
    +
  • “This is the Web UI. Green means it is working.”
  • +
  • If anything looks wrong, press stop. That sets the battery to zero and it idles + safely.”
  • +
  • “Once a month it will deliberately empty and then fill the battery. That is normal and it + keeps the battery healthy.”
  • +
+
+ +
+

Commissioning record

+ + + + + + +
Site / client
Inverter model / serial
ESPHome node name
Add-on version / max_w
Meter entity + update rate
+
    +
  • Gates 1–6 all passed on the wire or the meter
  • +
  • Gate 4 (watchdog) personally observed reaching 0 W
  • +
  • Maintenance cycle proven — full / partial (circle one)
  • +
  • E-stop fitted & tested OR §7b signed
  • +
  • Client shown the Web UI and the stop button
  • +
  • Vendor subscription cancelled (only after the above)
  • +
+
+
Technician
+
Signature
+
Date
+
+
+
+ + +
+

Troubleshooting

+ + + + + + + + + + + + + +
SymptomLikely causeDo this
Web UI says NOT READYWrong entity id, or sensor unavailableBanner names it. Copy the id from Developer Tools → States
Configured, but nothing movesControl is stoppedPress start; banner turns green
Inverter does nothing, no errorsA/B swapped, or vendor box still connectedSwapped RS485 gives a silent bus, not an error
HA shows one setpoint, inverter does anotherFirmware max_w below add-on max_wFirmware wins by design. Align them
Log shows writes rejected (HTTP 400)Value outside the number entity's rangeLower max_w; check the firmware substitution
Command keeps climbing while the battery is pinnedRunaway — sign inverted or saturation defeatedStop the add-on now. Verify meter_invert with a known load
Grid hunts, never restsDeadband too small, or meter too slowRaise deadband_w; if meter > 10 s, lower gain
Battery flat overnight and stays flatMaintenance drain never exitedCheck the SoC entity is the ESP32's, not a cached cloud value
Maintenance never runsDisabled, or not yet dueCheck maintenance_enabled and the “Maintenance due” row
E-stop fires on every updateExpected — upload silence > 30 sNothing to fix. Do not raise the threshold
Add-on will not installHA is Container/CoreNot supportable — see Step 0
+ +

Why the tuning is what it is

+

Every value came from hardware. Do not change them without measuring.

+ + + + + + + + +
SettingValueReason
gain0.6Inverter needs 3–6 s to settle (1.4 s dead time, 94 % by 3.3 s) against a ~5 s cycle. At the limit — do not raise
slew_w1000Most of a correction in the first cycle without running far ahead of the hardware
deadband_w15Measured residual: mean 15.4 W, max 27 W. At 10 W the command never rests
saturation_w / cycles500 / 3Divergence = inverter at a limit → magnitude may fall, never rise. The 3-cycle term is essential: tested instantly it fires on every large correction
step_w10Register is 1 W but response lands on a ~17.6 W ladder. Finer is meaningless
heartbeat_s10Three chances to be heard before the 30 s watchdog acts
+
The failure this guards 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.
+
+ + diff --git a/docs/img/esphome.png b/docs/img/esphome.png new file mode 100644 index 0000000..5782df4 Binary files /dev/null and b/docs/img/esphome.png differ diff --git a/docs/img/ui-notready.png b/docs/img/ui-notready.png new file mode 100644 index 0000000..ffe9a0d Binary files /dev/null and b/docs/img/ui-notready.png differ diff --git a/docs/img/ui-running.png b/docs/img/ui-running.png new file mode 100644 index 0000000..2fc1c2b Binary files /dev/null and b/docs/img/ui-running.png differ