Conversation
Introduce a new hardware-level battery mode that prevents charging while still allowing discharge. This is implemented via the SunSpec Model 124 InWRte register (charge rate limit set to 0%), which has been verified on Fronius GEN24 hardware. The mode is mapped as case 4 in battery controller templates: - StorCtl_Mod=1 (limit charge rate) - InWRte=0% (zero charge rate) Template support is added for Fronius GEN24, Fronius Verto Plus, and the generic SunSpec inverter control template. The Solar API template returns ErrNotAvailable as it has no equivalent HTTP endpoint. E3DC and SOC-based battery limit controllers are also updated to handle the new mode.
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- The new
case: 4 # nochargevalue is duplicated as a magic number across multiple templates; consider centralizing this mapping toBatteryNoCharge(e.g. via a shared constant or helper) to avoid future divergence if modes are reordered or extended. - In the SunSpec inverter control template, the no-charge mode additionally sets
InOutWRte_RvrtTmsto 0s, while the Fronius GEN24 and Verto Plus templates do not; consider aligning these behaviors or documenting why the revert time is handled differently per template.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The new `case: 4 # nocharge` value is duplicated as a magic number across multiple templates; consider centralizing this mapping to `BatteryNoCharge` (e.g. via a shared constant or helper) to avoid future divergence if modes are reordered or extended.
- In the SunSpec inverter control template, the no-charge mode additionally sets `InOutWRte_RvrtTms` to 0s, while the Fronius GEN24 and Verto Plus templates do not; consider aligning these behaviors or documenting why the revert time is handled differently per template.
## Individual Comments
### Comment 1
<location path="templates/definition/meter/fronius-gen24.yaml" line_range="251-260" />
<code_context>
uri: {{ .host }}:{{ .port }}
id: 1
value: 124:0:OutWRte
+ - case: 4 # nocharge
+ set:
+ source: sequence
+ set:
+ - source: const
+ value: 1
+ set:
+ source: sunspec
+ uri: {{ .host }}:{{ .port }}
+ id: 1
+ value: 124:0:StorCtl_Mod
+ - source: const
+ value: 0 # %
+ set:
+ source: sunspec
+ uri: {{ .host }}:{{ .port }}
+ id: 1
+ value: 124:0:InWRte
{{- include "battery-params" . }}
{{- end }}
</code_context>
<issue_to_address>
**question (bug_risk):** Align `nocharge` handling with sunspec inverter control regarding `InOutWRte_RvrtTms`.
In `sunspec-inverter-control.yaml`, the `nocharge` case also sets `InOutWRte_RvrtTms` to `0s`. Here and in `fronius-vertoplus.yaml`, `nocharge` only sets `StorCtl_Mod` and `InWRte`. If these backends are intended to behave equivalently, consider also setting `InOutWRte_RvrtTms` here, or confirm that leaving it unset is intentional due to device behavior or defaults.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| - case: 4 # nocharge | ||
| set: | ||
| source: sequence | ||
| set: | ||
| - source: const | ||
| value: 1 | ||
| set: | ||
| source: sunspec | ||
| uri: {{ .host }}:{{ .port }} | ||
| id: 1 |
There was a problem hiding this comment.
question (bug_risk): Align nocharge handling with sunspec inverter control regarding InOutWRte_RvrtTms.
In sunspec-inverter-control.yaml, the nocharge case also sets InOutWRte_RvrtTms to 0s. Here and in fronius-vertoplus.yaml, nocharge only sets StorCtl_Mod and InWRte. If these backends are intended to behave equivalently, consider also setting InOutWRte_RvrtTms here, or confirm that leaving it unset is intentional due to device behavior or defaults.
|
@larsxschneider #27801 mentions battery health. Imho battery health is more and more becoming a non-concern. It is known that home batteries typically die because calendric aging. |
Correct. Here “excess energy” means any energy that is not used for car charging or the house load is fed back to the grid.
Agreed. I’m happy to follow up with a separate PR to rename the other modes accordingly.
My use case is battery health / longevity. With some battery technologies it can be beneficial to hold the battery at ~50% SOC for extended periods. For example, during winter months with little or no solar generation, holding at ~50% for a month or two could make sense (even considering that batteries also age calendrically). It might also be useful for price/market-driven behavior: if export compensation is higher in the morning than at midday, you could export in the morning and then charge the battery later when export value is lower. (Disclaimer: I don’t currently use dynamic export pricing and don’t know how common/realistic this is in practice.)
Could you point me to where optimizer-related capabilities are modeled/exposed today, or to a similar existing capability as an example? Also, where can I find more documentation or an overview of the optimizer? I’d like to set it up in my own system to better understand how it works end-to-end. |
|
Optimizer is in andig/evopt where the readme links to the original idea. |
|
To chime in with a use case: Thanks to Solarspitzengesetz my PV from 2025 does not receive grid feed-in reimbursement when EPEX spot market prices are negative. We've had that situation now already for a couple of days this year, last year it was a total of ~500h. On days like this the best strategy is to lock battery charging (and feed in to the grid) and reserve home battery charging for those slots when there's negative grid price and feed in is not reimbursed. This maximizes self-consumption. The optimizer should model this fine with the feed in tariff going to 0 in those hours. I haven't figure out how I can set up the tariff that way in my evcc.yaml, but i guess the calc plugin or so + Btw. I'm not that concerned about my battery health. I use a simple "prevent discharge in fast charge mode" + a dummy loadpoint that i set to fast charge to preserve 20% SoC on longer stretches of low PV energy. It would actually be cool if that active home battery control setting could be set directly without a dummy loadpoint via API. |
|
I may not understand your requirements and reasoning fully but I think the optimizer model allows most of this already. This may not be obvious because the optimizer does not work based on rules but on constraints and an optimization goal (i.e. cost). Here's my take:
What if find new in this discussion is the idea to incentivse the reduction of the time the battery sits at 100% or 0% SOC. If you think you can attach a value to it (e.g. 0.01 ct/(hour * %SOC)) for each hour and %-point away from SOC, this would be an addition to the model. Note that it would require to actually put a value to the damage that being lose to 100% / 0% causes: The optimizer will take this value to trade 100%SOC off against the cost benefits from using the full battery capacity. The necessity to keep the model convex will BTW require the introduction of binary variables in order to model this. However, likely the effect on the computation time will be limited due to their predictability. |
|
Added a test implementation for the discussed feature of charging costs to batteries sitting at very high or very low SOC in order to account for higher life time depletion: #evcc-io/optimizer#62 You can specify a price per hour for sitting at 100% SOC, using a linear cost model starting at 80% SOC. E.g. if you assume a life time of 100000 hours (about 10 years), 400€/kWh invest, 10kWh capacity that would make 0.04€/hour battery cost deprecation. If you believe that sitting at 100% SOC reduces life time to 50%, specify 0.02 E/hour as cost. I have to correct myself, the added cost model is convex in itself and does not require binary variables. |
|
Imho the full list of battery modes seems to become:
|
Full hold maybe? Combination of hold charge/discharge to bypass battery completely. |
|
Yes, you would need a full hold as well. Or split the charge and discharge mode. Victron systems for example allow controlling both separately. Those take a grid-setpoint input (zero, plus or minus) and fields to allow or disallow charge and discharge. That means you can tell the battery to keep grid-neutral which is setpoint=0, charge:allow, discharge:allow. Or do so only in one direction (set either charge or discharge to disabled). Or cause grid import or export by some amount (septoint=1000 or -1000 for example). |
Yes, I agree, the optimizer will have to consider the control limitations oft the battery inverter. |
This is a stripped down version of #27801 based on @andig 's comment that a no-charge mode might be acceptable.
I realized that I should be able to implement what I want to do with only the no-charge mode and that might be useful for others too.
@andig I hope you are not annoyed because of this second PR.
Introduce a new hardware-level battery mode that prevents charging while still allowing discharge. This is implemented via the SunSpec Model 124 InWRte register (charge rate limit set to 0%), which has been verified on Fronius GEN24 hardware.
The mode is mapped as case 4 in battery controller templates:
Template support is added for Fronius GEN24, Fronius Verto Plus, and the generic SunSpec inverter control template. The Solar API template returns ErrNotAvailable as it has no equivalent HTTP endpoint.
E3DC and SOC-based battery limit controllers are also updated to handle the new mode.