Optimizing energy storage and heating with Rust

PV and Batteries are awesome
After being hit with a flood and having my energy storage destroyed, rebuilding that was one of the first priorities. It was very clear that even my most optimistic estimates of an 8%-9% ROI for PV and energy storage were way underestimating just how much money such systems save. With over 9 years of practical experience I can say that they literally pay for themselves, and anyone telling you differently simply doesn't know. The only exceptions are places with hyperinflated prices, installations that were sold at massively inflated markups, or the polar circle. I realize that this statement alone is already going to draw the usual "But I heard solar doesn't solve..." - I am beyond caring at this point. It works, even if you don't believe in it.
But for those of you who still have their browser tab open, I got something for you that may be useful.
The Market regulates itself, sometimes
I've been on EPEX-based energy pricing for some time now. This means that the cost of each kWh depends very much on the whole supply/demand thing. Not only that, but since October 1st 2025 EPEX switched from hourly pricing to 15-minute time intervals.
And here's another little fact that will draw the ire of those who drank the fossil fuel industry cool-aid: The main factor for EPEX prices, aside from demand, is renewables. Whenever there's plenty of wind and solar power, the EPEX price index regularly goes down to zero. Or sometimes even negative. When south european solar farms get hit with unfiltered sunrays during cool early spring days, it's not unusual to see prices of -40ct/kWh - yes, that's minus, meaning you get paid for using energy.
On the flipside, the peaks during morning (when everyone makes their morning toast and heats up their homes) and evening (when everyone comes home to cook dinner and turns on the heating and TV) are the most expensive, with prices regularly increasing from 10ct/kWh to 50, 100, or even 200ct/kWh.
As an example, here's a price chart for a day with completely overcast sky and almost no wind, showing mostly demand-driven pricing:
There are no problems, only solutions
The price spikes are usually relatively short. If you're on an annual contract that just counts the overall kWh consumed, you hardly care about any of this. But once you take EPEX pricing, you gain the benefits - and drawbacks - of such dynamic pricing. In the simplest case, you can just choose to NOT turn on your dishwasher at 18:00, and instead program it for midnight. You might opt to charge your car during the off-hours instead of right after you come home. Things like that.
With a battery and a heatpump, things get more difficult but also potentially more rewarding. In my case, the compressor of my heatpump produces not only heat for the house but also supplies the hot water storage with calories. While I can regulate the house temperature, I always had the issue that the thing would still turn on just to heat the water. This often happened at the most stupid times, i.e. during price spikes.
Ok so I have two levers to play with: For one, I can set a "minimum SOC" for the battery, meaning that if it is currently at 20% charge and I set it to 60% minimum SOC, it will start to charge from whatever energy source is available - PV, Grid, it doesn't care.
The second lever is the heatpump. During the repairs after the flood I learned more about that than I wanted to know, so now I know that there's a control signal that starts up the compressor. It is perfectly safe to inhibit that signal, in fact there's a timer relay in there that delays startup by 5 minutes to prevent "fast oscillation" which stresses the compressor.
Formulating a plan of attack
The battery SOC is easily controlled via SSH. I am setting a configuration value via DBUS in the Raspi that's controlling the Victron Multigrid inverters. This has the added benefit of me always being able to override the setting via their web UI.
For the heatpump, I purchased a Shelly relay that can switch the 220V control signal that's controlling the Allen-Bradley SMC-3 Soft-Start controller. There's one wire going to IN1+IN2, and that's the one I interrupt with the relay. The Shelly relay offers a simple HTTP REST API for control.
(Yes, those mud splats are leftovers from the flood)
The software
The task is clear:
- Regularly fetch energy pricing from EPEX for today and tomorrow
- Detect price spikes and dips
- Control the battery minimum SOC depending on pricing, and on whether it's summer or winter (winter gets less PV input)
- Control the relay inhibiting the heatpump, but don't make folks feel uncomfortably cold either
Item 2 on the list was a bit of a challenge, because you don't want to start charging at the very lowest price, but when the dip starts. Likewise, inhibiting the heatpump for too long is not desirable either, so there's several times that prevent excessive switching as well as waiting out price spikes for too long. In the end, saving money is important, but not freezing is even more important.
I chose to implement this in Rust of course since I wanted this to work for years without supervision. I've had such services running using Python, PHP and Java, and crashes and restarts have always been a matter of "when, not if". And while Rust doesn't magically solve all problems, it sure is a hell of a lot more stable in practice.
The resulting software is available here: https://github.com/dividebysandwich/energy-controller
I've added a TUI interface for displaying the price curve, as this makes it a lot easier to follow and monitor the decisions made by the system. When force-charging the batteries, I set the SOC to 60% in winter and 40% in summer. The reason for this is that I want to store enough energy to get through a price spike, but I don't want to charge the batteries from the grid so much that the eventual PV power input has to be fed back to the grid. You will obviously need to tweak these settings for your system, specifically for your PV array and your battery size. What you should aim for is to store enough energy to get through a morning or evening spike.
For longer spikes there's really not much you can do other than to shut down stuff. Or simply pay.
Results so far
In the few weeks that I've had this running during fall, I have saved approximately 30 euros. I realize this won't help as much when energy prices go up in winter, and my heating will have to run several times a day just to keep a comfortable room temperature throughout the house. But I find it very satisfying to watch this system make its own decisions on when to charge the batteries, and so far whenever I've heard the inverters spool up and checked the price, I always felt that I would've done the same thing myself.
I hope this may be useful for someone out there. But even if it isn't, that's still fine - it works for me, I learned a lot, and comparing my implementation with similar functionality built into commercial systems is always fun. Some of them wait for the absolute lowest price and thus often miss half of significant price dips. That said, I am certain my implementation isn't perfect either. But as they often say in open source development: Patches welcome!