Summary
select.py:51 gates all VPP control entities behind a hardcoded register-map name
check:
is_wit = str(register_map_name).upper() in ("WIT_4000_15000TL3", "WIT_29900_50000TL3_XHU")
A MOD 10KTL3-XH (MOD_6000_15000TL3_XH) therefore gets no VPP controls.
Measured on hardware: this inverter does support remote power control. #143
describes the WIT gate as a conservative choice to avoid regressions elsewhere; it
contains no evidence that other families lack the capability.
30407 alone does nothing
With 30409=5, 30408=2, 30407=1 but 30100=0: battery charge power did not
move for three minutes (2600 W, flat), and the duration timer did not run (30407
still 1 after 180 s). This is presumably why the capability is easy to miss.
With control_authority it works
Write order 30409 → 30408 → 30407=1 → 30100=1, so the documented hazard state
(30100 enabled without 30407) never occurs:
12:22 charge 2608 W to grid 1117 W before
12:23 charge 303 W to grid 3452 W VPP active (5 %)
12:24 charge 302 W to grid 3461 W
12:25 charge 2609 W to grid 1149 W after disabling
Transfer function is linear
Three points, each held ~90 s:
| 30409 |
battery charge power |
| 5 % |
302 W |
| 10 % |
700 W |
| 20 % |
1466 W |
77.6 W per percentage point, offset −86 W. Predicting 20 % from the first two
points gave 1496 W against a measured 1466 W.
The reference is the battery, not a setting. Raising 3311
(uw_ac_charging_max_power_limit) from 7.5 kW to 15.0 kW left the 20 % result
unchanged (1466 → 1463 W). Full scale is ≈7.25 kW, consistent with a 15 kWh
battery at 0.5C and with the highest charge power ever recorded on this system
(7247 W, when the classic charge power rate was set to 100 %).
Three findings that belong in any implementation
1. ⚠️ The power command is a target, not a limit — and it overrides
allow_grid_charge. At 30409=100 with insufficient PV:
+0s charge 2592 W from grid 0 W
+5s charge 4767 W from grid 912 W <<< aborted by our safety threshold
The inverter actively climbed toward the commanded power and imported from the
grid while allow_grid_charge (3049) was 0. At 5/10/20 % only downward
limiting is visible, which gives a misleading impression of a cap. An
implementation should bound the commanded percentage against available PV unless
grid import is explicitly intended.
2. The duration expires but the registers do not clear. With 30408=2
(minutes), the power constraint released after ~128 s and normal self-consumption
resumed — but 30407, 30409 and 30100 all remained set for the full 240 s
observation window. Active/inactive cannot be inferred from the register
values.
3. Register 30474 mirrors the last commanded setpoint. It tracked 5 → 10 → 20
→ 100 across four runs, does not revert on disable, and a direct write is accepted
but ignored (echo returns the written value, read-back keeps the old one). It was
the only unexpected change in a full 1150-register before/after snapshot.
Suggested change
Replace the hardcoded name check with a capability probe, or extend the list to
MOD. A safe probe exists: FC6 to 30408 with its own current value returns
exception 2 on inverters that lack the address space — verified, since this
inverter answers exception 2 for genuinely unimplemented addresses such as 1090
and 1092 — and succeeds where the range is present.
Given finding 1, MOD support should ship with a guard against commanding more
power than PV can supply, unless the user opts into grid import.
Offer
Happy to run further measurements. The harness used here has an explicit
allowlist, a hard deny list on 30100/30407/30410, abort thresholds on grid import,
and full-register before/after snapshots — the 30474 side effect was only found
because of the snapshot diff.
Environment
|
|
| Integration |
v1.5.4 |
| Inverter |
Growatt MOD 10KTL3-XH (3-phase hybrid, backup box, 15 kWh battery) |
| DTC |
5400 (VPP reg 30000 and legacy reg 43) |
| Protocol |
V2.02 |
| Firmware |
DN1.0 |
| Detected profile |
mod_6000_15000tl3_xh_v201 (auto-detection, accepted, "Very High" confidence) |
| Transport |
Elfin EW41 RS485→WiFi gateway, Modbus gateway mode, TCP 502, 9600 8N1 |
Measurements were made with a standalone zero-dependency MBAP client using the
same framing as pymodbus, to rule out integration-side handling. Diagnostics file
and Universal Register Scanner CSVs available on request.
Summary
select.py:51gates all VPP control entities behind a hardcoded register-map namecheck:
A MOD 10KTL3-XH (
MOD_6000_15000TL3_XH) therefore gets no VPP controls.Measured on hardware: this inverter does support remote power control. #143
describes the WIT gate as a conservative choice to avoid regressions elsewhere; it
contains no evidence that other families lack the capability.
30407 alone does nothing
With
30409=5,30408=2,30407=1but30100=0: battery charge power did notmove for three minutes (2600 W, flat), and the duration timer did not run (30407
still 1 after 180 s). This is presumably why the capability is easy to miss.
With control_authority it works
Write order
30409 → 30408 → 30407=1 → 30100=1, so the documented hazard state(30100 enabled without 30407) never occurs:
Transfer function is linear
Three points, each held ~90 s:
77.6 W per percentage point, offset −86 W. Predicting 20 % from the first two
points gave 1496 W against a measured 1466 W.
The reference is the battery, not a setting. Raising 3311
(
uw_ac_charging_max_power_limit) from 7.5 kW to 15.0 kW left the 20 % resultunchanged (1466 → 1463 W). Full scale is ≈7.25 kW, consistent with a 15 kWh
battery at 0.5C and with the highest charge power ever recorded on this system
(7247 W, when the classic charge power rate was set to 100 %).
Three findings that belong in any implementation
1.⚠️ The power command is a target, not a limit — and it overrides
allow_grid_charge. At30409=100with insufficient PV:The inverter actively climbed toward the commanded power and imported from the
grid while
allow_grid_charge(3049) was 0. At 5/10/20 % only downwardlimiting is visible, which gives a misleading impression of a cap. An
implementation should bound the commanded percentage against available PV unless
grid import is explicitly intended.
2. The duration expires but the registers do not clear. With
30408=2(minutes), the power constraint released after ~128 s and normal self-consumption
resumed — but 30407, 30409 and 30100 all remained set for the full 240 s
observation window. Active/inactive cannot be inferred from the register
values.
3. Register 30474 mirrors the last commanded setpoint. It tracked 5 → 10 → 20
→ 100 across four runs, does not revert on disable, and a direct write is accepted
but ignored (echo returns the written value, read-back keeps the old one). It was
the only unexpected change in a full 1150-register before/after snapshot.
Suggested change
Replace the hardcoded name check with a capability probe, or extend the list to
MOD. A safe probe exists: FC6 to 30408 with its own current value returns
exception 2 on inverters that lack the address space — verified, since this
inverter answers exception 2 for genuinely unimplemented addresses such as 1090
and 1092 — and succeeds where the range is present.
Given finding 1, MOD support should ship with a guard against commanding more
power than PV can supply, unless the user opts into grid import.
Offer
Happy to run further measurements. The harness used here has an explicit
allowlist, a hard deny list on 30100/30407/30410, abort thresholds on grid import,
and full-register before/after snapshots — the 30474 side effect was only found
because of the snapshot diff.
Environment
mod_6000_15000tl3_xh_v201(auto-detection, accepted, "Very High" confidence)Measurements were made with a standalone zero-dependency MBAP client using the
same framing as pymodbus, to rule out integration-side handling. Diagnostics file
and Universal Register Scanner CSVs available on request.