Huawei: add HoldCharge#30598
Conversation
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- The new
holdchargecase duplicates themaxchargepowerwrite logic that was added to the other cases; consider extracting a shared sequence or helper template to avoid repeating the same47075/47077write blocks in multiple branches. - In the
holdchargesequence you explicitly zero47075while keeping47077atmaxdischargepower; if this asymmetry is intentional, a brief inline comment explaining why discharge is left at max while charge is zeroed would make the behavior clearer to future maintainers. - The
watchdoghandler’s newcase: 4uses asleepwithduration: 0s; if this case does not need any special handling, you can omit the branch entirely rather than adding a no-op step.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The new `holdcharge` case duplicates the `maxchargepower` write logic that was added to the other cases; consider extracting a shared sequence or helper template to avoid repeating the same `47075`/`47077` write blocks in multiple branches.
- In the `holdcharge` sequence you explicitly zero `47075` while keeping `47077` at `maxdischargepower`; if this asymmetry is intentional, a brief inline comment explaining why discharge is left at max while charge is zeroed would make the behavior clearer to future maintainers.
- The `watchdog` handler’s new `case: 4` uses a `sleep` with `duration: 0s`; if this case does not need any special handling, you can omit the branch entirely rather than adding a no-op step.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
Looks to be working. But I want to do some more stability testing, also see #30467. |
|
I am again experiencing a (reproducible) race-condition when switching from Splitting modes in |
|
Sounds as if the watchdog reset happens too late? It would be helpful if we could have a demo config that allows to reproduce this without having a Huawei inverter. |
|
Fix options, roughly in order of preference:
Imho 3 would be easier to understand unless this kills any eeprom register? |
Hope not. I am just trying to only do as much as necessary to keep the burden on those devices as low as possible... |
|
BTW... So far I haven't realized that error because as soon as the 1 minute watchdog expires, the forcible charging is disabled anyway. I just realized now because I am actively checking the register states after a mode switch... |
|
I measured response times when writing the respective registers and they indeed seem costly, between 500ms and multiple seconds(!) per register. |
|
So how do you want to proceed here? I think it makes a ton of sense to have a setup and a periodic phase for a state. Plus I want to keep the numbers of registers written low as writes are indeed expensive for Huawei. I will finish testing |
|
Up to you. Accept the broken config or do this. |
|
Finished all testing now (test plan see initial posting). Watchdog race-condition looks to be fixed via #31039. |
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- The
goblocks that clampmaxchargepowerto the rated max (register 37046) are repeated three times; consider extracting this into a reusable template/include or YAML anchor to reduce duplication and keep future changes consistent. - The
casevalues for the different modes (including the new4 // holdcharge) are hard-coded magic numbers; adding a centralized mapping or constants for these mode IDs would make the intent clearer and reduce the risk of mismatches with the API/batterymode layer.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `go` blocks that clamp `maxchargepower` to the rated max (register 37046) are repeated three times; consider extracting this into a reusable template/include or YAML anchor to reduce duplication and keep future changes consistent.
- The `case` values for the different modes (including the new `4 // holdcharge`) are hard-coded magic numbers; adding a centralized mapping or constants for these mode IDs would make the intent clearer and reduce the risk of mismatches with the API/batterymode layer.
## Individual Comments
### Comment 1
<location path="templates/definition/meter/huawei-sun2000-hybrid.yaml" line_range="352-355" />
<code_context>
+ address: 47075 # Max. Charge Power
+ type: writemultiple
+ encoding: uint32
+ script: |
+ limit := {{ .maxchargepower }}
+ out := rated
+ if limit < rated { out = limit }
+ out
# register 47077 is range-checked: a maxdischargepower above the
</code_context>
<issue_to_address>
**issue (bug_risk):** Consider guarding against negative maxchargepower when writing to a uint32 register
`limit` is an `int` but the register is `uint32`. If `maxchargepower` is ever negative (e.g. via misconfiguration), the value will be encoded as an invalid `uint32` (wrap/rejection risk). Please clamp the lower bound explicitly, e.g.:
```go
limit := {{ .maxchargepower }}
if limit < 0 { limit = 0 }
out := rated
if limit < rated { out = limit }
out
```
The same lower-bound clamp is needed for the corresponding discharge-power blocks that write to `uint32` registers.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| script: | | ||
| limit := {{ .maxchargepower }} | ||
| out := rated | ||
| if limit < rated { out = limit } |
There was a problem hiding this comment.
issue (bug_risk): Consider guarding against negative maxchargepower when writing to a uint32 register
limit is an int but the register is uint32. If maxchargepower is ever negative (e.g. via misconfiguration), the value will be encoded as an invalid uint32 (wrap/rejection risk). Please clamp the lower bound explicitly, e.g.:
limit := {{ .maxchargepower }}
if limit < 0 { limit = 0 }
out := rated
if limit < rated { out = limit }
outThe same lower-bound clamp is needed for the corresponding discharge-power blocks that write to uint32 registers.
SUN2000/SDongle Modbus Primer
Testing
States
normal: battery charged with surplus only / discharged to home ...hold: only discharging locked / charging with surplus still possible ...charge: force-charge atmaxchargepowerfrom surplus/grid combined ...holdcharge: only charging locked / discharging still possible ...State Transitions
normal→holdnormal→chargenormal→holdchargehold→normalhold→chargehold→holdchargeholdcharge→normalholdcharge→holdholdcharge→chargecharge→normalcharge→holdcharge→holdchargeRegister states are being checked with
modbus-clivia evcc Modbus Proxy:Please beware that when checking states with FusionSolar portal, displayed settings might not reflect register settings immediately! Fetching the whole configuration via the portal also puts quite a lot of load on the SUN2000 which may cause Modbus errors for evcc.
Remarks
Old configs may still have the old default of
10000formaxchargepowerset which is higher than what most installed battery setups support (only the LUNA2000-S1 with 3 battery packs supports a higher charge power). While the Forcible charge power register (47247) allows out-of-bounds values to be set, trying to set it for the Maximum charging power register (47075) introduced in this PR fails. The introduced clamping mechanism takes care of that. So this is not a breaking change. Broken configs will stay broken though.It would be great if Huawei had something similar to the SunSpec
StorCtl_Modregister. With Huawei, every mode requires quite some registers to be written for cleaning up every potential previous mode/state as the number of modes increases. Implementing all modes (force-charge, force-discharge, lock, lock-charge, lock-discharge) via register47100, also being guarded by a HW watchdog, would indeed be superb. It unfortunately only supports force-charge and force-discharge today.Out of scope for this PR
chargewhich an expiringwatchdogtook care of [LINK]Fixed via Watchdog: don't re-assert stale value after reset #31039