Merge pull request 'DOC-8: say EMS port, not meter bus' (#201) from DOC-8 into release/1.0
infra-pin / fresh (push) Successful in 1s

This commit was merged in pull request #201.
This commit is contained in:
2026-10-17 04:09:33 +02:00
11 changed files with 35 additions and 34 deletions
+3 -3
View File
@@ -4,9 +4,9 @@ The guide technicians carry is the PDF:
**`docs/GoodWe-RS485-Field-Guide.pdf`** — 10 pages, A4, print it double-sided. **`docs/GoodWe-RS485-Field-Guide.pdf`** — 10 pages, A4, print it double-sided.
⚠️ **Outdated: it is v0.1.0.** Its `slew_w` row is corrected in `build_guide.py` ⚠️ **Outdated: the content is still v0.1.0.** The PDF was rebuilt on 2026-10-17
and `field-guide.html`, but the PDF has not been rebuilt (see below). Where the for the EMS-port wording and the `slew_w` row only. Where the guide disagrees with
guide disagrees with `goodwe_controller/DOCS.md`, DOCS.md wins. `goodwe_controller/DOCS.md`, DOCS.md wins.
It covers, in the order the work is done: pre-visit checks, hardware, firmware, 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 add-on install and configuration, how to read the Web UI, the six commissioning
+1 -1
View File
@@ -156,7 +156,7 @@ that looks hung gets deleted.
**A GoodWe "-20" / G2 inverter cannot be driven by this.** **A GoodWe "-20" / G2 inverter cannot be driven by this.**
Platform comes from characters 6–8 of the serial number. Only Platform comes from characters 6–8 of the serial number. Only
`ESU EMU ESA BPS BPU EMJ IJL` — platform 105, the AA55-era meter bus — are `ESU EMU ESA BPS BPU EMJ IJL` — platform 105, the AA55-era generation — are
supported. SBP G2 (`SPB`/`SPN`) and ES G2 (`ESN`/`ESC`) are platform 745, a supported. SBP G2 (`SPB`/`SPN`) and ES G2 (`ESN`/`ESC`) are platform 745, a
different protocol. The reference unit is `BPS`. different protocol. The reference unit is `BPS`.
+6 -6
View File
@@ -1,8 +1,8 @@
# GoodWe RS485 Controller — Home Assistant add-on repository # GoodWe RS485 Controller — Home Assistant add-on repository
Replaces a GoodWe battery inverter's vendor controller with a local one: holds Replaces a GoodWe battery inverter's vendor controller with a local one: holds
net grid exchange at zero by emulating the inverter's smart meter over RS485, net grid exchange at zero by sending the inverter a grid reading over its RS485
and runs the monthly battery maintenance cycle the vendor box was doing. EMS port, and runs the monthly battery maintenance cycle the vendor box was doing.
``` ```
repository.yaml add-on repository metadata repository.yaml add-on repository metadata
@@ -30,7 +30,7 @@ goodwe_controller/ the add-on
returns, run against its own release image returns, run against its own release image
firmware/goodwe-master.yaml ESPHome config for the T-CAN485 firmware/goodwe-master.yaml ESPHome config for the T-CAN485
estop/rs485_log.py optional RS485 e-stop / bus witness estop/rs485_log.py optional RS485 e-stop / bus witness
docs/GoodWe-RS485-Field-Guide.pdf the guide techs carry (10pp, A4; outdated) 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 docs/build_guide.py its single source - rebuilds the HTML and PDF
FIELD-GUIDE.md how to edit and rebuild the guide FIELD-GUIDE.md how to edit and rebuild the guide
docs/RELEASE.md how a change reaches the battery - `master` is docs/RELEASE.md how a change reaches the battery - `master` is
@@ -118,17 +118,17 @@ the platform tag:
| tag | platform | verdict | | tag | platform | verdict |
|---|---|---| |---|---|---|
| `ESU EMU ESA BPS BPU EMJ IJL` | 105 — ES/EM/BP, AA55-era meter bus | **supported** | | `ESU EMU ESA BPS BPU EMJ IJL` | 105 — ES/EM/BP, AA55-era | **supported** |
| `SPB SPN` | 745 — SBP **G2** (`GW…-SBP-20`) | no | | `SPB SPN` | 745 — SBP **G2** (`GW…-SBP-20`) | no |
| `ESN ESC` | 745 — ES **G2** (`GW…-ES-20`) | no | | `ESN ESC` | 745 — ES **G2** (`GW…-ES-20`) | no |
| `ETU EHU BTU …` | 205/745/753 — ET/EH/BT hybrids | no | | `ETU EHU BTU …` | 205/745/753 — ET/EH/BT hybrids | no |
| `SDT DST MSU NSU …` | PV-only inverters — no battery to control | 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 meter 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 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. is also what Home Assistant's own integration uses to pick a protocol.
- GoodWe **ES / BP family** inverters (AA55 / RS485 meter-bus generation) - GoodWe **ES / BP family** inverters (AA55 generation)
- The protocol is **reverse-engineered**. There is no vendor contract, and a - 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 firmware change on GoodWe's side could break every installation at once. Say
so when you sell it. so when you sell it.
Binary file not shown.
+6 -6
View File
@@ -154,7 +154,7 @@ figcaption { font-size:9pt; color:#5b6169; margin-top:1.2mm; }
<table style="margin-bottom:2mm"> <table style="margin-bottom:2mm">
<tr><th style="width:34mm">Tag</th><th>Platform</th><th style="width:30mm">Verdict</th></tr> <tr><th style="width:34mm">Tag</th><th>Platform</th><th style="width:30mm">Verdict</th></tr>
<tr><td><code>ESU EMU ESA BPS<br>BPU EMJ IJL</code></td> <tr><td><code>ESU EMU ESA BPS<br>BPU EMJ IJL</code></td>
<td><b>Platform 105</b> — ES / EM / BP, AA55-era meter bus</td> <td><b>Platform 105</b> — ES / EM / BP, AA55-era</td>
<td><b>SUPPORTED</b></td></tr> <td><b>SUPPORTED</b></td></tr>
<tr><td><code>SPB SPN</code></td><td>SBP <b>G2</b> (GW…-SBP-20) — platform 745</td><td><b>NO</b></td></tr> <tr><td><code>SPB SPN</code></td><td>SBP <b>G2</b> (GW…-SBP-20) — platform 745</td><td><b>NO</b></td></tr>
<tr><td><code>ESN ESC</code></td><td>ES <b>G2</b> (GW…-ES-20) — platform 745</td><td><b>NO</b></td></tr> <tr><td><code>ESN ESC</code></td><td>ES <b>G2</b> (GW…-ES-20) — platform 745</td><td><b>NO</b></td></tr>
@@ -162,7 +162,7 @@ figcaption { font-size:9pt; color:#5b6169; margin-top:1.2mm; }
<tr><td><code>SDT DST MSU NSU …</code></td><td>PV-only inverters — <b>no battery to control</b></td><td><b>NO</b></td></tr> <tr><td><code>SDT DST MSU NSU …</code></td><td>PV-only inverters — <b>no battery to control</b></td><td><b>NO</b></td></tr>
</table> </table>
<p style="margin:0"><small>Reference unit: <code>95000BPS225W0290</code> → <code>BPS</code> → supported. <p style="margin:0"><small>Reference unit: <code>95000BPS225W0290</code> → <code>BPS</code> → supported.
A “-20” or “G2” suffix in the model name means a different platform with a different meter A “-20” or “G2” suffix in the model name means a different platform with a different
protocol. <b>If the tag is not on the supported list, stop — do not quote the job.</b></small></p> protocol. <b>If the tag is not on the supported list, stop — do not quote the job.</b></small></p>
</div> </div>
@@ -208,13 +208,13 @@ figcaption { font-size:9pt; color:#5b6169; margin-top:1.2mm; }
<!-- ============ PAGE 3 ============ --> <!-- ============ PAGE 3 ============ -->
<div class="page" id="p3"> <div class="page" id="p3">
<h2>Steps 1–3 — Hardware</h2> <h2>Steps 1–3 — Hardware</h2>
<div class="warn"><b>Qualified persons only.</b> Mains AC and battery DC are present. The meter bus <div class="warn"><b>Qualified persons only.</b> Mains AC and battery DC are present. The RS485 EMS port
is low-voltage but sits inside an energised installation.</div> is low-voltage but sits inside an energised installation.</div>
<div class="step"> <div class="step">
<h3><span class="num">1</span> Survey and record</h3> <h3><span class="num">1</span> Survey and record</h3>
<ol class="do"> <ol class="do">
<li><b>Photograph</b> the meter-port wiring before touching it.</li> <li><b>Photograph</b> the EMS-port wiring before touching it.</li>
<li>Identify the RS485 pair (A/B) running to the vendor controller.</li> <li>Identify the RS485 pair (A/B) running to the vendor controller.</li>
<li>Record inverter model + serial, battery capacity (Ah) and nominal voltage.</li> <li>Record inverter model + serial, battery capacity (Ah) and nominal voltage.</li>
</ol> </ol>
@@ -226,7 +226,7 @@ figcaption { font-size:9pt; color:#5b6169; margin-top:1.2mm; }
<ol class="do"> <ol class="do">
<li>Bring the system to <b>0 W</b> — battery neither charging nor discharging (check the <li>Bring the system to <b>0 W</b> — battery neither charging nor discharging (check the
inverter display).</li> inverter display).</li>
<li>Disconnect the vendor controller from the meter port.</li> <li>Disconnect the vendor controller from the inverter's EMS port.</li>
<li>Leave it physically mounted but unplugged — that keeps the install reversible and gives <li>Leave it physically mounted but unplugged — that keeps the install reversible and gives
you a rollback story.</li> you a rollback story.</li>
</ol> </ol>
@@ -237,7 +237,7 @@ figcaption { font-size:9pt; color:#5b6169; margin-top:1.2mm; }
<div class="step"> <div class="step">
<h3><span class="num">3</span> Fit and power the T-CAN485</h3> <h3><span class="num">3</span> Fit and power the T-CAN485</h3>
<ol class="do"> <ol class="do">
<li>Wire <b>A→A, B→B</b> to the meter port, plus its own supply.</li> <li>Wire <b>A→A, B→B</b> to the EMS port, plus its own supply.</li>
<li>If fitting the e-stop, connect its USB-RS485 adapter to the <b>same pair, in parallel</b>.</li> <li>If fitting the e-stop, connect its USB-RS485 adapter to the <b>same pair, in parallel</b>.</li>
<li>Power the T-CAN485 <b>last</b>.</li> <li>Power the T-CAN485 <b>last</b>.</li>
</ol> </ol>
+6 -6
View File
@@ -119,7 +119,7 @@ figcaption { font-size:9pt; color:#5b6169; margin-top:1.2mm; }
<table style="margin-bottom:2mm"> <table style="margin-bottom:2mm">
<tr><th style="width:34mm">Tag</th><th>Platform</th><th style="width:30mm">Verdict</th></tr> <tr><th style="width:34mm">Tag</th><th>Platform</th><th style="width:30mm">Verdict</th></tr>
<tr><td><code>ESU EMU ESA BPS<br>BPU EMJ IJL</code></td> <tr><td><code>ESU EMU ESA BPS<br>BPU EMJ IJL</code></td>
<td><b>Platform 105</b> — ES / EM / BP, AA55-era meter bus</td> <td><b>Platform 105</b> — ES / EM / BP, AA55-era</td>
<td><b>SUPPORTED</b></td></tr> <td><b>SUPPORTED</b></td></tr>
<tr><td><code>SPB SPN</code></td><td>SBP <b>G2</b> (GW…-SBP-20) — platform 745</td><td><b>NO</b></td></tr> <tr><td><code>SPB SPN</code></td><td>SBP <b>G2</b> (GW…-SBP-20) — platform 745</td><td><b>NO</b></td></tr>
<tr><td><code>ESN ESC</code></td><td>ES <b>G2</b> (GW…-ES-20) — platform 745</td><td><b>NO</b></td></tr> <tr><td><code>ESN ESC</code></td><td>ES <b>G2</b> (GW…-ES-20) — platform 745</td><td><b>NO</b></td></tr>
@@ -127,7 +127,7 @@ figcaption { font-size:9pt; color:#5b6169; margin-top:1.2mm; }
<tr><td><code>SDT DST MSU NSU …</code></td><td>PV-only inverters — <b>no battery to control</b></td><td><b>NO</b></td></tr> <tr><td><code>SDT DST MSU NSU …</code></td><td>PV-only inverters — <b>no battery to control</b></td><td><b>NO</b></td></tr>
</table> </table>
<p style="margin:0"><small>Reference unit: <code>95000BPS225W0290</code> → <code>BPS</code> → supported. <p style="margin:0"><small>Reference unit: <code>95000BPS225W0290</code> → <code>BPS</code> → supported.
A “-20” or “G2” suffix in the model name means a different platform with a different meter A “-20” or “G2” suffix in the model name means a different platform with a different
protocol. <b>If the tag is not on the supported list, stop — do not quote the job.</b></small></p> protocol. <b>If the tag is not on the supported list, stop — do not quote the job.</b></small></p>
</div> </div>
@@ -173,13 +173,13 @@ figcaption { font-size:9pt; color:#5b6169; margin-top:1.2mm; }
<!-- ============ PAGE 3 ============ --> <!-- ============ PAGE 3 ============ -->
<div class="page" id="p3"> <div class="page" id="p3">
<h2>Steps 1–3 — Hardware</h2> <h2>Steps 1–3 — Hardware</h2>
<div class="warn"><b>Qualified persons only.</b> Mains AC and battery DC are present. The meter bus <div class="warn"><b>Qualified persons only.</b> Mains AC and battery DC are present. The RS485 EMS port
is low-voltage but sits inside an energised installation.</div> is low-voltage but sits inside an energised installation.</div>
<div class="step"> <div class="step">
<h3><span class="num">1</span> Survey and record</h3> <h3><span class="num">1</span> Survey and record</h3>
<ol class="do"> <ol class="do">
<li><b>Photograph</b> the meter-port wiring before touching it.</li> <li><b>Photograph</b> the EMS-port wiring before touching it.</li>
<li>Identify the RS485 pair (A/B) running to the vendor controller.</li> <li>Identify the RS485 pair (A/B) running to the vendor controller.</li>
<li>Record inverter model + serial, battery capacity (Ah) and nominal voltage.</li> <li>Record inverter model + serial, battery capacity (Ah) and nominal voltage.</li>
</ol> </ol>
@@ -191,7 +191,7 @@ figcaption { font-size:9pt; color:#5b6169; margin-top:1.2mm; }
<ol class="do"> <ol class="do">
<li>Bring the system to <b>0 W</b> — battery neither charging nor discharging (check the <li>Bring the system to <b>0 W</b> — battery neither charging nor discharging (check the
inverter display).</li> inverter display).</li>
<li>Disconnect the vendor controller from the meter port.</li> <li>Disconnect the vendor controller from the inverter's EMS port.</li>
<li>Leave it physically mounted but unplugged — that keeps the install reversible and gives <li>Leave it physically mounted but unplugged — that keeps the install reversible and gives
you a rollback story.</li> you a rollback story.</li>
</ol> </ol>
@@ -202,7 +202,7 @@ figcaption { font-size:9pt; color:#5b6169; margin-top:1.2mm; }
<div class="step"> <div class="step">
<h3><span class="num">3</span> Fit and power the T-CAN485</h3> <h3><span class="num">3</span> Fit and power the T-CAN485</h3>
<ol class="do"> <ol class="do">
<li>Wire <b>A→A, B→B</b> to the meter port, plus its own supply.</li> <li>Wire <b>A→A, B→B</b> to the EMS port, plus its own supply.</li>
<li>If fitting the e-stop, connect its USB-RS485 adapter to the <b>same pair, in parallel</b>.</li> <li>If fitting the e-stop, connect its USB-RS485 adapter to the <b>same pair, in parallel</b>.</li>
<li>Power the T-CAN485 <b>last</b>.</li> <li>Power the T-CAN485 <b>last</b>.</li>
</ol> </ol>
+6 -5
View File
@@ -1,8 +1,9 @@
# GoodWe RS485 Controller # GoodWe RS485 Controller
Drives a GoodWe ES/BP battery inverter over its RS485 meter bus: holds net grid Drives a GoodWe ES/BP battery inverter over its RS485 EMS port: sends it a grid
exchange at zero, and runs a monthly battery maintenance cycle so the BMS can reading to hold net grid exchange at zero, and runs a monthly battery
balance cells and recalibrate its coulomb counter. maintenance cycle so the BMS can balance cells and recalibrate its coulomb
counter.
Installers: the printed field guide (`docs/GoodWe-RS485-Field-Guide.pdf`) is Installers: the printed field guide (`docs/GoodWe-RS485-Field-Guide.pdf`) is
v0.1.0 and outdated; where it disagrees with this page, this page wins. v0.1.0 and outdated; where it disagrees with this page, this page wins.
@@ -33,8 +34,8 @@ v0.1.0 and outdated; where it disagrees with this page, this page wins.
You need: You need:
- A GoodWe **ES / BP family** inverter (AA55 / RS485 meter-bus generation) - A GoodWe **ES / BP family** inverter (AA55 generation)
- The vendor's meter-emulating controller **disconnected** from the bus - The vendor's controller **disconnected** from the inverter's EMS port
- A control board: a T-CAN485 (ESP32) flashed with - A control board: a T-CAN485 (ESP32) flashed with
`firmware/goodwe-master.yaml` `firmware/goodwe-master.yaml`
- A grid-power sensor already working in Home Assistant, updating every ~5–10 s - A grid-power sensor already working in Home Assistant, updating every ~5–10 s
+1 -1
View File
@@ -3,7 +3,7 @@
WHAT THIS THING DOES, in one paragraph: it reads the house's net grid power from WHAT THIS THING DOES, in one paragraph: it reads the house's net grid power from
Home Assistant, decides how hard the battery should charge or discharge to hold Home Assistant, decides how hard the battery should charge or discharge to hold
that at zero, and writes that figure to an ESP32 which puts it on the inverter's that at zero, and writes that figure to an ESP32 which puts it on the inverter's
RS485 meter bus. Once a month it runs a full low->full battery cycle so the BMS RS485 EMS port. Once a month it runs a full low->full battery cycle so the BMS
can balance cells and recalibrate its coulomb counter. can balance cells and recalibrate its coulomb counter.
⚠️ THE ONE THING TO UNDERSTAND BEFORE CHANGING ANY OF THIS: the inverter holds ⚠️ THE ONE THING TO UNDERSTAND BEFORE CHANGING ANY OF THIS: the inverter holds
+1 -1
View File
@@ -29,7 +29,7 @@ _LOG = logging.getLogger("goodwe.mqtt")
DEVICE = { DEVICE = {
"identifiers": ["goodwe_rs485_controller"], "identifiers": ["goodwe_rs485_controller"],
"name": "GoodWe", "name": "GoodWe",
"manufacturer": "GoodWe (via RS485 meter emulation)", "manufacturer": "GoodWe (via RS485 EMS port)",
"model": "ES/BP series", "model": "ES/BP series",
} }
+2 -2
View File
@@ -2,8 +2,8 @@ name: GoodWe RS485 Controller
version: "0.18.2" version: "0.18.2"
slug: goodwe_controller slug: goodwe_controller
description: >- description: >-
Drives a GoodWe ES/BP battery inverter over RS485 by emulating its smart Drives a GoodWe ES/BP battery inverter over its RS485 EMS port by sending it a
meter, holding net grid exchange at zero and running a monthly battery grid reading, holding net grid exchange at zero and running a monthly battery
maintenance cycle. maintenance cycle.
url: https://gittea.kammenstraatha.duckdns.org/admin/goodwe-addon url: https://gittea.kammenstraatha.duckdns.org/admin/goodwe-addon
arch: arch:
+3 -3
View File
@@ -104,7 +104,7 @@ def check(name, cond):
# release/1.0 rather than against release/1.0. # release/1.0 rather than against release/1.0.
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
_DEV = ('{"identifiers": ["goodwe_rs485_controller"], "name": "GoodWe", ' _DEV = ('{"identifiers": ["goodwe_rs485_controller"], "name": "GoodWe", '
'"manufacturer": "GoodWe (via RS485 meter emulation)", ' '"manufacturer": "GoodWe (via RS485 EMS port)", '
'"model": "ES/BP series"}') '"model": "ES/BP series"}')
BASELINE = [ BASELINE = [
@@ -442,7 +442,7 @@ for c_ in unit_cfgs:
print(f" regression pin (sha256 of the eight payloads): {_pin}") print(f" regression pin (sha256 of the eight payloads): {_pin}")
check("the per-unit payloads are the ones this PR was reviewed with " check("the per-unit payloads are the ones this PR was reviewed with "
"(REGRESSION PIN, not a correctness claim - see the comment)", "(REGRESSION PIN, not a correctness claim - see the comment)",
_pin == "eb5022b0455158d7") _pin == "b4274714d8557c37")
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
print("\n4. zero overlap between A's entities and B's") print("\n4. zero overlap between A's entities and B's")
@@ -976,7 +976,7 @@ def announce_p2(units=(), omit=(), include=(), modes=(), retract=(),
# `git show ed739d4:goodwe_controller/app/mqtt.py` and this loop. # `git show ed739d4:goodwe_controller/app/mqtt.py` and this loop.
ROSTERS = [[], ["inverter-a"], ["inverter-a", "inverter-b"], ["a", "b", "c"], ROSTERS = [[], ["inverter-a"], ["inverter-a", "inverter-b"], ["a", "b", "c"],
["inverter-b", "Inverter B", "x", "y"], ["café"]] ["inverter-b", "Inverter B", "x", "y"], ["café"]]
ED739D4_OFF = "501a7d93782a260b" ED739D4_OFF = "057be1d8ad7bb582" # DOC-8: ed739d4 digest with only the manufacturer string changed
_h, _n, _subs_off = hashlib.sha256(), 0, [] _h, _n, _subs_off = hashlib.sha256(), 0, []
for _r in ROSTERS: for _r in ROSTERS:
for _om in [(), ("p1_age",), ("p1_age", "grid")]: for _om in [(), ("p1_age",), ("p1_age", "grid")]: