A deep dive into the brain behind your energy system
Every 15 minutes, our optimizer wakes up, looks 24 hours into the future, considers everything it knows, and solves a math puzzle: "What's the cheapest way to keep the lights on?"
It's less "buy low, sell high trader" and more a very patient accountant who rechecks the math 96 times a day and refuses to spend a single cent without a good reason.
This post explains how it works, why it sometimes makes decisions that look wrong to a human, and where it's headed next.
The Architecture: Two Brains Working Together
The system has two distinct services that work as a team:
The Optimizer (the planner) runs every 15 minutes. It looks at forecasts, solves a mathematical optimization problem, and publishes a plan — a 96-step schedule covering the next 24 hours. Think of it as the strategist sitting in an office, staring at spreadsheets.
The Controller (the executor) runs every 30 seconds. It takes the optimizer's plan and actually controls your battery, respecting real-world constraints like your fuse limits and current phase imbalance. Think of it as the operator on the factory floor, making sure the plan + reality doesn't blow a fuse — literally.
They communicate through messages. The optimizer publishes a schedule, the controller picks it up and enforces it against reality.
Every 15 minutes: Every 30 seconds:
┌──────────────┐ Schedule ┌──────────────────┐
│ Optimizer │ ──────────────────> │ Controller │
│ (the plan) │ │ (the execution) │
└──────────────┘ └──────────────────┘
│ │
│ reads: │ reads:
│ • spot prices │ • optimizer schedule
│ • your load (measured) │ • actual phase currents
│ • solar production │ • actual fuse headroom
│ │
│ produces: │ produces:
│ • 96-step schedule │ • actual charge/discharge
│ (next 24 hours) │ command to your inverter
What the Optimizer Knows — The Three Forecasts
The optimizer works with three predictions. None of them are crystal balls. They're more like educated guesses that get updated every cycle.
1. Electricity Prices — The One It Actually Knows Well
Real spot prices from the Nordpool market, fetched per price area (SE1-SE4, DK1, etc.). These are actual 15-minute interval prices, not predictions. The API provides them for today and tomorrow (once published, typically around 13:00 CET for the next day).
The price for each time slot is calculated as:
effective_price = (spot_price + fixed_addition) * multiplier
Where fixed_addition and multiplier model things like grid fees, taxes, and time-of-use tariffs — costs that sit on top of the raw spot price.
Example: If the spot price at 18:00 is 0.50 SEK/kWh, the grid fee adds 0.35 SEK/kWh, and the tax multiplier is 1.25, the effective import price becomes (0.50 + 0.35) * 1.25 = 1.0625 SEK/kWh. The effective export price would typically be lower (no grid fee on selling back), which is why the optimizer won't just blindly arbitrage — it needs a real spread.
2. Your Consumption — Anchored in Reality, Relaxed Over Time
This is where it gets interesting. The optimizer doesn't assume a flat load. Here's exactly what it does:
- Right now (step 0): It takes your actual measured load — the average of all load samples received over the last 15 minutes. If you just turned on the oven, it knows.
- Steps 1-7 (next ~2 hours): It linearly blends from your actual load toward a typical daily load profile. So if your current load is 5,000W (oven on) but the profile says 1,200W at this time of day, it gradually transitions:
- Step 0: 5,000W (actual)
- Step 1: 4,457W
- Step 2: 3,914W
- Step 3: 3,371W
- …
- Step 7: 1,200W (profile)
- Steps 8-95 (2-24 hours out): Pure profile. The daily load curve is derived from your
daily_energy_consumption_whconfiguration, shaped by a time-of-day pattern based on actual Scandinavian consumption data (higher in morning and evening, lower at night).
Why this design? It's deliberately conservative. The optimizer would rather be surprised by more consumption than to underestimate and leave you short. The 8-step (~2 hour) relaxation means temporary spikes don't dominate the full 24-hour plan.
3. Solar Production — See It, Then Forget It
Same principle as consumption, but even more conservative:
- Right now (step 0): Actual measured PV production averaged over the last 15 minutes.
- Steps 1-7: Linear fade toward zero.
- Steps 8-95: Zero.
No weather API, no fancy ML prediction. Just "this is what I see, and I'll assume it won't last."
Why fade to zero instead of a profile? Because it's better to be surprised by more sun than to bet on sunshine and be wrong. If the optimizer assumes 3kW of solar production at 14:00 and clouds roll in, you'd import expensive peak electricity. By assuming zero, any actual solar production is a pleasant bonus that gets factored in at the next 15-minute replan.
Future improvement: This is the single biggest area for improvement. Adding actual solar forecasts (weather-based or learned from historical production patterns) would let the optimizer make much smarter decisions — especially for self-consumption optimization and avoiding unnecessary grid import during sunny hours.
The Math: How It Actually Decides
At its core, the optimizer solves a convex optimization problem using a technique called Model Predictive Control (MPC). Here's what that means in plain language.
The Objective: Minimize Your Electricity Cost
For each of the 96 time steps in the 24-hour horizon, the optimizer calculates:
cost = import_price × grid_import × dt - export_price × grid_export × dt
And it minimizes the sum of this across all 96 steps. That's it. The entire objective is: spend as little money as possible on electricity over the next 24 hours.
It doesn't maximize battery cycles. It doesn't try to reach 100% SoC. It doesn't charge "just in case." Every watt going in or out of the battery must earn its keep.
The Constraints: Physics Is Non-Negotiable
The optimizer can't just ignore reality. It must satisfy these constraints at every single time step:
Energy balance — what goes in must come out:
grid_power = battery_charging - battery_discharging + consumption - solar_production
Battery SoC tracking — accounting for round-trip losses:
SoC[t+1] = SoC[t] + (charge_efficiency × charge - discharge / discharge_efficiency) × dt / capacity
This is where the round-trip loss comes from. If your battery has 95% charge efficiency and 95% discharge efficiency, the round-trip is 0.95 × 0.95 = 0.9025, meaning ~10% of energy is lost. The optimizer knows this and factors it into every decision.
Battery limits:
- SoC must stay between min and max (e.g., 10% to 90%)
- Charge power can't exceed the inverter rating
- Discharge power can't exceed the inverter rating
Site power limits:
- Total grid import can't exceed your fuse rating
- Total grid export can't exceed your fuse rating
A Concrete Example
Say it's 14:00 on a Tuesday. Your battery is at 40% SoC (5 kWh battery). The optimizer sees:
| Time | Spot Price | Your Load | Solar |
|---|---|---|---|
| 14:00 | 0.30 SEK | 1,200W | 2,000W |
| 14:15 | 0.28 SEK | 1,200W | 1,700W |
| … | … | … | (fading to 0) |
| 17:00 | 0.85 SEK | 1,800W | 0W |
| 17:15 | 1.20 SEK | 2,000W | 0W |
| 17:30 | 1.35 SEK | 2,200W | 0W |
| 17:45 | 1.10 SEK | 1,800W | 0W |
| 18:00 | 0.80 SEK | 1,500W | 0W |
The optimizer thinks:
- Right now: Solar covers your load with 800W to spare. No need to import.
- 17:00-18:00: A price spike is coming. No solar. High load.
- The math: If I charge the battery now at 0.30 SEK and discharge during the spike at 1.20 SEK, even after 10% loss, I save
1.20 - 0.30/0.90 = 0.87 SEK/kWh. Worth it.
So the plan becomes: charge lightly during the afternoon (using excess solar + cheap grid), then discharge during the evening peak.
But if tomorrow's prices are flat at 0.40 SEK all day? The optimizer does… nothing. No charge, no discharge. Because after 10% losses, buying at 0.40 and selling at 0.40 means you lose money. The battery sits idle.
Why You Might Think the Plan Is Wrong
"Why isn't it charging?! Prices are cheap!"
The optimizer won't charge just because electricity is cheap. It needs a reason — specifically, an expensive period coming up where discharging actually saves money. Energy going into the battery and back out loses about 10%. If there's no price spread to cover that loss, charging is just burning energy for nothing.
It's like buying stuff on sale that you'll never use.
"Why is it just sitting there at 30%?"
A human would feel nervous with a low battery. "Fill it up! Just in case!" The optimizer doesn't have anxiety. It only stores what it has a plan for. Filling to 100% "just in case" costs real money with no guaranteed payoff. Every kWh stored has a cost (the price you paid + 10% losses).
"It's not charging overnight!"
Everyone assumes overnight = cheap = charge. But if tomorrow morning is also cheap, there's nowhere profitable to discharge. The optimizer only charges overnight if there's a daytime price spike worth targeting. If prices are 0.20 SEK/kWh all night and 0.25 SEK/kWh all day, the spread (0.05 SEK) doesn't even cover the 10% round-trip loss.
"The plan said charge, but it didn't happen"
The plan is a plan — not a guarantee. Several things can override it:
- Fuse protection: The controller monitors all three phases of your electrical connection. If phase currents approach the fuse limit, it backs off — even if the plan says "charge at 5kW." Safety always wins. The controller maintains a 10% safety margin below your fuse rating.
- Communication issues: If WiFi drops, the inverter firmware updates, or another app tries to control the same inverter, the plan can't be executed.
- Reality shifted: Your actual load spiked (you started the EV charger, the heat pump kicked in), eating up the headroom the plan assumed would be available.
The EV / V2X Thing
When your car plugs in and says "I need 80% by 7 AM", the optimizer uses a different strategy called lexicographic optimization. This means it has two priorities, in strict order:
- First priority (non-negotiable): Get the car to target SoC by the deadline.
- Second priority (nice to have): Do it as cheaply as possible.
So it doesn't just slam charge at full power. It finds the cheapest path to 80% — maybe charging hard during the 03:00 price dip, pausing during a mini-spike at 04:00, then finishing during another dip at 05:00.
What If 80% Is Physically Impossible?
If you plug in at 23:00 with 10% SoC and ask for 80% by 01:00, the optimizer doesn't crash or give up. It:
- Calculates the maximum achievable SoC by the deadline (say 45%)
- Charges as fast as physics allows to get there
- Reports a warning: "V2X target infeasible — can reach 45% by 01:00, or 80% by 04:30"
- Uses emergency mode with time-weighted urgency (charge sooner rather than later)
The Controller: Where Plan Meets Reality
Every 30 seconds, the controller does the following:
1. Check: Is Everything Fresh?
It verifies that all DERs (battery, meter, solar) have reported telemetry since the last cycle. If any device goes silent for 4 consecutive cycles (~2 minutes), the controller resets its state and re-subscribes to MQTT — assuming something went wrong with the connection.
2. Check: Is the Fuse Safe?
This is the most critical check. The controller reads the actual current on all three phases from your energy meter and calculates how much headroom remains before the fuse trips:
max_site_power = sqrt(3) × voltage × fuse_size
safety_margin = max_site_power × 10%
effective_limit = fuse_limit_from_phase_currents - safety_margin
If the most loaded phase is running hot, the effective limit shrinks. The controller will never command a battery to charge if doing so would risk tripping the fuse.
3. Distribute Power to DERs
Using the optimizer's goal (e.g., "import 2,000W from grid") and the current reality (actual load, actual solar), the controller calculates what each battery/charger should do. It uses a projection algorithm that:
- Respects each DER's power limits
- Stays within the fuse envelope
- Gets as close to the optimizer's target as physically possible
- Falls back gracefully when constraints conflict
The result is published as an MQTT control message that the gateway translates into actual inverter commands.
You Know More
Ultimately, you know more about your own behavior than the optimizer does or ever can. It doesn't know:
- You have a dinner party planned and need to cook a steak during peak price hours
- Prices are historically low right now and will surely rise tomorrow
- You're about to plug in the EV in 30 minutes
- The kids are home from school today and will run the gaming PC all afternoon
The optimizer plays the hand it's dealt. It can only react to what it measures and what the price signal tells it.
Where We're Going: The Roadmap
The current system is conservative by design. Here's what's actively being improved:
Better Solar Forecasts
The fade-to-zero approach is safe but leaves money on the table. We're working on integrating actual weather-based solar production forecasts so the optimizer can properly plan around sunny periods — charging batteries before the sun sets instead of just reacting.
Smarter Load Prediction
Right now, the load profile is based on typical Scandinavian consumption patterns. As we collect more data per site, we can learn your specific patterns: when your heat pump cycles, your typical EV charging schedule, your weekday vs. weekend behavior.
Multi-DER Coordination
The system already handles battery + V2X + EV charger combinations, but the interaction model is being refined. The goal is seamless coordination where your home battery, car, and any other flexible load all work together under one unified plan.
Grid Services
The same local control infrastructure that optimizes your battery for cost can also respond to grid signals — frequency regulation, demand response, capacity markets. This is where the <200ms local execution really matters: grid services require sub-second response that cloud APIs simply can't provide.
The Numbers
| What | Value | Why |
|---|---|---|
| Planning horizon | 24 hours (96 × 15-min steps) | Covers a full price cycle |
| Replan frequency | Every 15 minutes | Fast enough to catch reality shifts |
| Control frequency | Every 30 seconds | Fast enough for fuse protection |
| Fuse safety margin | 10% | Better safe than tripped |
| Forecast blend window | 8 steps (~2 hours) | Temporary spikes don't dominate the plan |
| Battery round-trip loss | ~10% (typical) | Charge efficiency × discharge efficiency |
| Price source | Nordpool spot market | Real 15-minute interval prices |
| Solver | Convex optimization | Mathematically proven optimal solution |
TL;DR
- Every 15 minutes, the optimizer solves a 24-hour math problem: minimize your electricity cost.
- It uses real spot prices, your actual recent load (blended toward a daily profile), and conservative solar estimates (fade to zero).
- It won't charge the battery unless there's a profitable price spread that covers the ~10% round-trip loss.
- Every 30 seconds, the controller enforces the plan while keeping you within fuse limits.
- The plan can be overridden by physics (fuse protection), communication issues, or reality being messier than the forecast.
- For EVs, it finds the cheapest path to your target SoC by the deadline — or the best it can do if the deadline is too tight.
- It's getting smarter: better solar forecasts, learned load patterns, and grid services are on the way.
The core philosophy: better to be conservative and reliable than clever and wrong.