Skip to content

Add NoCharge battery mode#27906

Open
larsxschneider wants to merge 1 commit intoevcc-io:masterfrom
larsxschneider:ls/bat-nocharge
Open

Add NoCharge battery mode#27906
larsxschneider wants to merge 1 commit intoevcc-io:masterfrom
larsxschneider:ls/bat-nocharge

Conversation

@larsxschneider
Copy link

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:

  • 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.

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.
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • 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.
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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +251 to +260
- case: 4 # nocharge
set:
source: sequence
set:
- source: const
value: 1
set:
source: sunspec
uri: {{ .host }}:{{ .port }}
id: 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@andig
Copy link
Member

andig commented Mar 4, 2026

@larsxschneider NoCharge means that all excess energy is fed in? NoCharge=HoldCharge similar to Hold=HoldDischarge. Maybe we should consistently rename the other modes, too (separate PR).

#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.
That said: what use cases would we need this for? Seems we'd also need a corresponding optimizer feature that allows exposing this battery capability?

/cc @ekkea @t0mas

@andig andig added the enhancement New feature or request label Mar 4, 2026
@andig andig changed the title battery: add BatteryNoCharge mode Add NoCharge battery mode Mar 4, 2026
@andig andig added backlog Things to do later labels Mar 4, 2026
@larsxschneider
Copy link
Author

@larsxschneider NoCharge means that all excess energy is fed in?

Correct. Here “excess energy” means any energy that is not used for car charging or the house load is fed back to the grid.

NoCharge=HoldCharge similar to Hold=HoldDischarge. Maybe we should consistently rename the other modes, too (separate PR).

Agreed. I’m happy to follow up with a separate PR to rename the other modes accordingly.

That said: what use cases would we need this for?

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.)

Seems we'd also need a corresponding optimizer feature that allows exposing this battery capability?

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.

@andig
Copy link
Member

andig commented Mar 5, 2026

Optimizer is in andig/evopt where the readme links to the original idea.

@JohannesRudolph
Copy link
Contributor

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 + energy-charts-api would do the trick. So I don't think it would need any special handling if we're only talking about maximizing monetary return.

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.

@ekkea
Copy link

ekkea commented Mar 14, 2026

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:

  1. if you have to deal with the "Solarspitzengesetz", just set the export-to-grid price to 0 for the hours where this is forecasted. The optimizer goal (maximize income, minimize costs) will drive the solution to not feeding into the grid at these times if (!) it is avoidable.
  2. if you want a battery not to charge above or below a certain SOC you can set ´s_min´ or ´s_max´ accordingly.

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.

@ekkea
Copy link

ekkea commented Mar 15, 2026

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.

@andig
Copy link
Member

andig commented Mar 15, 2026

Imho the full list of battery modes seems to become:

@harmendp
Copy link
Contributor

Imho the full list of battery modes seems to become:

Full hold maybe? Combination of hold charge/discharge to bypass battery completely.

@t0mas
Copy link
Contributor

t0mas commented Mar 17, 2026

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).

@ekkea
Copy link

ekkea commented Mar 17, 2026

That said: what use cases would we need this for? Seems we'd also need a corresponding optimizer feature that allows exposing this battery capability?

Yes, I agree, the optimizer will have to consider the control limitations oft the battery inverter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backlog Things to do later enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants