Skip to content

Commit 6ea3570

Browse files
0xAHAclaude
andcommitted
docs: fix the DTC table on the VPP protocol page (4th copy, drifted)
Reported: the MAX models added yesterday were missing from the published VPP protocol page. They were -- along with five more. This page carried its own DTC table, which nobody had counted as a copy. So there were four: auto_detection.py, diagnostic.py, troubleshooting/dtc-debugging.md and this one. Six codes were missing here (3501, 5401 and the whole MAX/MAX-X family 5000/5500/5501/5502), 3735 was named 'SPA 3000-6000TL BL' where the spec says 'SPA 3000TL BL-UP', and the 5603 row had fallen outside the table so it rendered as loose text. Regenerated from DTC_REGISTRY, and now carries the same confirmed vs unconfirmed column as the troubleshooting page -- the distinction matters more on a developer page, since that is where someone would go to decide whether a mapping can be trusted. Scoped to VPP Table 3-1, with a pointer to the full reference for the legacy and off-grid codes that live at holding register 43 instead. The docs-drift test now covers this page in both directions: every Table 3-1 code present, and every model name matching the registry. Verified by reverting the 3735 name and confirming it fails. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 58a37a6 commit 6ea3570

2 files changed

Lines changed: 93 additions & 24 deletions

File tree

docs/developer/protocol-vpp.md

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,32 +29,52 @@
2929
The DTC (Device Type Code) is stored at holding register 30000 and uniquely identifies
3030
the inverter model. The integration reads this at startup for automatic model detection.
3131

32-
| DTC Code | Model |
33-
| --- | --- |
34-
| 3502 | SPH 3000-6000TL BL |
35-
| 3503 | SPH 3000-6000TL HU |
36-
| 3504 | SPH 3000-6000TL HUB |
37-
| 3601 | SPH 4000-10000TL3 BH-UP |
38-
| 3701 | SPA 1000-3000TL BL |
39-
| 3715 | SPA 3000-6000TL AU |
40-
| 3716 | SPA 3000-6000TL AUB |
41-
| 3725 | SPA 4000-10000TL3 BH-UP |
42-
| 3735 | SPA 3000-6000TL BL |
43-
| 5001 | MID 17–25KTL3-X / MID 20–30KTL3-X2 |
44-
| 5002 | MID 33–36KTL3-X(Pro.E) / MOD 3–15KTL3-X |
45-
| 5003 | MAC 30–70KTL3-X |
46-
| 5100 | MIN 2500-6000TL-XH/XH(P) |
47-
| 5200 | MIC/MIN 2500-6000TL-X/X2 |
48-
| 5201 | MIN 7000-10000TL-X/X2 |
49-
| 5400 | MOD-XH / MID-XH |
50-
| 5600 | WIS 100K-AM; WIT 50–100K-H/HE/HU/A/AE/AU; WIT 50–100K-H/HE/HU/A/AE/AU-US; WIT 28–55K-H/HE/HU/A/AE/AU-US L2 |
51-
| 5601 | WIT 29.9–50K-XHU (commercial hybrid with battery — confirmed via hardware scan Issue #338) |
52-
| 5800 | WIS 210K |
53-
| 5801 | WIS 215K-AM |
32+
This table is generated from `DTC_REGISTRY` in `auto_detection.py`, the single source of
33+
truth, and is checked against it by the test suite.
34+
35+
**Profile mapping** is a separate question from the DTC itself. The code is read from the
36+
device and identifies the model reliably; whether the profile it selects is *correct* for
37+
that model has, for most entries, never been verified against hardware. See
38+
[DTC Debugging](../troubleshooting/dtc-debugging.md) for what that means in practice.
39+
40+
| DTC Code | Model | Profile mapping |
41+
| --- | --- | --- |
42+
| 3501 | SPH 3000-6000TL BL | ⚠️ Unconfirmed |
43+
| 3502 | SPH 3000-6000TL BL-UP | ✅ Confirmed |
44+
| 3503 | SPH 3000-6000TL HU | ⚠️ Unconfirmed |
45+
| 3504 | SPH 3000-6000TL HUB | ✅ Confirmed |
46+
| 3601 | SPH-TL3 4-10kW | ✅ Confirmed |
47+
| 3701 | SPA 1000-3000TL BL | ⚠️ Unconfirmed |
48+
| 3715 | SPA 3000-6000TL AU | ⚠️ Unconfirmed |
49+
| 3716 | SPA 3000-6000TL AUB | ⚠️ Unconfirmed |
50+
| 3725 | SPA-TL3 4-10kW | ⚠️ Unconfirmed |
51+
| 3735 | SPA 3000TL BL-UP | ⚠️ Unconfirmed |
52+
| 5001 | MID 17-25KTL3-X; MID 20-30KTL3-X2; MID 25-30KTL3-X2 Pro/X2 Pro.E; MID 33-50KTL3-X2/X2 Pro/X2 Pro.E; MID 30-40KTL3-X | ✅ Confirmed |
53+
| 5002 | MID 33-36KTL3-X(Pro.E); MID 3-33KTL3-X3; MOD 3-15KTL3-X; MOD 3-15KTL3-X2(Pro); MOD 12-20KTL3-X2; MOD 12-20KTL3-X2(E); MOD 3-33KTL3-X3 | ⚠️ Unconfirmed |
54+
| 5003 | MAC 30-70KTL3-X; MAC 15-36KTL3-XL; MAC 50-70KTL3-X2; MAC 30-36KTL3-XL2 | ⚠️ Unconfirmed |
55+
| 5000 | MAX 50-100KTL3 LV/MV | ⚠️ Unconfirmed |
56+
| 5500 | MAX 175-253KTL3-X HV | ⚠️ Unconfirmed |
57+
| 5501 | MAX 80-150KTL3-X LV/MV; MAX 100-150KYL3-X2 LV/MV | ⚠️ Unconfirmed |
58+
| 5502 | MAX 320-350KTL3-X | ⚠️ Unconfirmed |
59+
| 5100 | MIN 2500-6000TL-XH/XH2/XHE/XA | ✅ Confirmed |
60+
| 5200 | MIC 600-3300TL-X/X2/X2(Pro); MIN 2500-6000TL-X/X2/X2(Pro)/X2(Pro.E) | ⚠️ Unconfirmed |
61+
| 5201 | MIN 7-10KTL-X/X2/X2(E) | ✅ Confirmed |
62+
| 5400 | MOD 3-10KTL3-XH/BP; MID 11-30KTL3-XH; MID 8-15KTL3-XHL/JP | ✅ Confirmed |
63+
| 5401 | MOD 3-15KTL3-HU; MID 33-50KTL3-HU | ✅ Confirmed |
64+
| 5600 | WIS 100K-AM; WIT 50-100K-H/HE/HU/A/AE/AU (incl. -US); WIT 28-55K-H/HE/HU/A/AE/AU-US L2 | ⚠️ Unconfirmed |
65+
| 5601 | WIT 29.9-50K-XHU | ✅ Confirmed |
66+
| 5800 | WIS 210K | ⚠️ Unconfirmed |
67+
| 5801 | WIS 215K-AM | ⚠️ Unconfirmed |
68+
69+
### Not in the spec table
70+
71+
| DTC Code | Model | Profile mapping |
72+
| --- | --- | --- |
73+
| 5603 | WIT 4-15kW Hybrid | ✅ Confirmed |
5474

55-
| 5603 | WIT 4–15KTL3 (residential three-phase hybrid) — confirmed via live register read by community contributor (Issue #335); absent from V2.03 spec DTC table but follows V2.03 protocol structure |
75+
> **WIT residential models (4-15KTL3):** The VPP V2.03 spec (dated 2025.9.1) does **not** include the WIT 4-15KTL3 residential series in its DTC table — only commercial WIT (50K-100K) models appear. DTC 5603 was confirmed by a live register read (register 30000 = 5603 on a WIT 15KTL3, Issue #335) with protocol version register 30099 = 203, so the residential range follows V2.03 register structure despite being omitted from the spec's device table.
5676
57-
> **WIT residential models (4–15KTL3):** The VPP V2.03 spec (dated 2025.9.1) does **not** include the WIT 4–15KTL3 residential series in its DTC table. Only commercial WIT (50K–100K) models appear. DTC 5603 has been confirmed via live hardware register read (register 30000 = 5603 on a WIT 15KTL3) and protocol version register 30099 = 203 (V2.03), confirming the residential range follows V2.03 register structure despite the omission from the spec's device table.
77+
**Non-VPP models** are not listed here. Legacy V1.39 devices (MIC 2500-5500MTL-S, TL3-S, SPH/SPM 8000-10000TL-HU), off-grid SPF (3400-3403) and SPE (64541) carry their DTC at holding register **43** rather than 30000. They appear in the [full DTC reference](../troubleshooting/dtc-debugging.md).
5878

5979
---
6080

tests/test_dtc_registry.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,55 @@ def test_docs_flag_every_unconfirmed_mapping():
174174
assert "Confirmed" in row, f"DTC {dtc} is CONFIRMED in code but not shown as such in the docs"
175175

176176

177+
# ---------------------------------------------------------------------------
178+
# The VPP protocol page
179+
# ---------------------------------------------------------------------------
180+
#
181+
# docs/developer/protocol-vpp.md publishes its own DTC table, scoped to the VPP 2.03
182+
# Table 3-1 codes. It was a *fourth* copy alongside auto_detection.py, diagnostic.py and
183+
# dtc-debugging.md, and it had drifted: six codes missing (3501, 5401 and the whole
184+
# MAX/MAX-X family) and 3735 named "SPA 3000-6000TL BL" where the spec says
185+
# "SPA 3000TL BL-UP".
186+
187+
VPP_PAGE = Path(__file__).parent.parent / "docs" / "developer" / "protocol-vpp.md"
188+
189+
190+
def _vpp_page_dtcs() -> set[int]:
191+
"""DTC codes from the leading column of the page's DTC tables.
192+
193+
Restricted to the known DTC values because the page also documents register tables
194+
with numeric first columns, which a bare row match would sweep up.
195+
"""
196+
text = VPP_PAGE.read_text(encoding="utf-8")
197+
rows = {int(m) for m in re.findall(r"^\|\s*(\d{3,5})\s*\|", text, re.M)}
198+
return rows & (TABLE_3_1 | {5603})
199+
200+
201+
@pytest.mark.parametrize("dtc", sorted(TABLE_3_1))
202+
def test_vpp_page_documents_every_table_3_1_code(dtc):
203+
assert dtc in _vpp_page_dtcs(), (
204+
f"DTC {dtc} is in VPP 2.03 Table 3-1 but missing from {VPP_PAGE.name}"
205+
)
206+
207+
208+
def test_vpp_page_model_names_match_the_registry():
209+
"""A model name that drifts from the registry sends people looking for the wrong
210+
device — which is how 3735 ended up documented as a model it isn't."""
211+
text = VPP_PAGE.read_text(encoding="utf-8")
212+
problems = []
213+
for m in re.finditer(r"^\|\s*(\d{3,5})\s*\|\s*([^|]+?)\s*\|", text, re.M):
214+
code = int(m.group(1))
215+
if code not in DTC_REGISTRY:
216+
continue
217+
page_name = m.group(2).strip()
218+
expected = DTC_REGISTRY[code].model
219+
if page_name != expected:
220+
problems.append(f" {code}: page={page_name!r} registry={expected!r}")
221+
assert not problems, (
222+
f"{VPP_PAGE.name} model names differ from DTC_REGISTRY:\n" + "\n".join(problems)
223+
)
224+
225+
177226
def test_spa_mappings_are_not_marked_confirmed():
178227
"""Specific to #360, and the reason this file exists.
179228

0 commit comments

Comments
 (0)