A site controller in Rust that sits between a grid operator and a commercial prosumer site. It speaks IEC 60870-5-104 to the operator's control centre and SunSpec Modbus to the inverters, chargers, heat pump and battery. A model-predictive planner schedules the site against real day-ahead prices; a real-time safety layer applies each country's rules: §14a EnWG in Germany, the ElWG feed-in cap in Austria, the 3% curtailment budget and flexibility contracts in Switzerland. What runs below is that code, compiled to WebAssembly.
One commercial site connected to the grid: 120 kWp of solar panels on the roof, four 22 kW chargers for its electric delivery vans (they come back in the late afternoon, charge overnight and leave in the morning), a 14 kW heat pump that heats the building and a 100 kWh battery.
On winter evenings everyone draws power at once and the neighbourhood transformer can overload. The German §14a EnWG lets the grid operator reduce chargers, heat pumps and batteries for a while, as long as a guaranteed minimum is left.
The Grid Edge Gateway is the software between the grid operator and the depot. It receives the operator's commands (IEC 104), drives the devices (Modbus), keeps to the rules and, if you choose, plans the day by the electricity price. In the live demo you are the grid operator: press "Dim" and watch the site obey.
During the reduction the vans fall behind. When it ends, every depot in the neighbourhood catches up at the same moment, and the peak can end up higher than with no reduction at all. This is the rebound. The feeder calculator simulates many depots together to find the least harmful way to bring them back.
The device stops answering Modbus. Watch the fallbacks: a charger drops to its own 6 A failsafe and the battery goes idle after 30 s; without the meter the budget shrinks to the floor.
Since 2025 a German grid operator may connect a plant beyond the grid's firm capacity if the plant accepts a limit (§ 17 (2b) EnWG). The capacity map found, for each HV/MV district of the Saarland, the day of 2025 on which flexible plants lose the most, and the share of their output allowed in each hour. Play that day here: the grid operator sends the limit over IEC 104 every five minutes, and the gateway keeps the depot's export under it, using the energy on site and in the battery before it curtails the PV.
Share of a flexible plant's output the substation allows, hour by hour
When a §14a reduction ends at the same time for many sites, their chargers and heat pumps catch up at once. The research behind this project (20 sites × 10 days × 33 cases, docs/study/rebound) found that a slow restart spreads the rebound over more minutes while its peak stays about as high, and that a site planning by price avoids most of it. Test any combination yourself: the page simulates every site with the gateway's own code and compares it with the same evening without a reduction.
Pick a feeder on the left and press Calculate. Each site is simulated from 06:00 with the gateway's code; the evening then plays back here, minute by minute.
Each square is one site: green below its share of the transformer, amber near it, red above, blue exporting.
Twenty depots on one feeder, simulated on every evening of 2025 with that day's real electricity prices and measured weather. A grid operator only reduces consumption on days the transformer would otherwise overload. Each square is one day: pick a transformer size and a site controller, then click a day to see its evening.
Loading the year…
| Data | What is used | Source | Kind |
|---|---|---|---|
| Electricity prices | Hourly day-ahead prices of the bidding zone DE-LU for every hour of 2025; where 15-minute products exist (DE-LU and AT from 1 October) the hourly mean | SMARD.de (Bundesnetzagentur), CC BY 4.0, through the Energy-Charts API, stored in year2025_data.rs | real, Germany |
| Sun and temperature, any day of 2025 | Hourly air temperature and global irradiance measured in Stuttgart; PV output = irradiance × 0.85 | Open-Meteo historical weather API (ERA5 reanalysis, Copernicus/ECMWF), CC BY 4.0, stored in year2025_data.rs | real, Germany |
| Reduction rules (Germany) | Minimum power Pmin,14a = 4.2 kW per device, simultaneity factor, 5-minute gradual release, proof of compliance | BNetzA decision BK6-22-300, Anlage 1; §14a EnWG | real, Germany |
| Feed-in rules (Germany) | 60% cap for new systems without a smart meter; no payment in negative hours | Solarspitzengesetz (BGBl. 2025 I Nr. 51); EEG §51 | real, Germany |
| Rules in Austria and Switzerland | 70% feed-in cap; 3% free curtailment budget; flexibility contracts | ElWG (BGBl. I 91/2025); StromVG Art. 17c; StromVV Art. 19b–19d | real, AT and CH |
| Sun and temperature, the two study days | Clear-sky solar curve and daily temperature for south-west Germany, with a random cloudiness per day (sunny, mixed, overcast), used by the Monte Carlo and rebound studies | Model in climate.rs, parameters set by hand | modelled |
| Vans | Arrival time, energy wanted and hours plugged in for each van; "varied times" shifts each depot by up to ±1 h and ±30% energy | Model in sim.rs and feeder.rs | modelled |
| Building and other loads | First-order thermal model of the building; 10–40 kW of other consumption | Model in sim.rs | modelled |
| Tariff | Day-ahead price + 12 ct/kWh of grid fees and levies; demand charge of 100 €/kW a year | Illustrative values of the order of German commercial tariffs, in climate.rs | assumed |
The feeder calculator and the year study follow the German §14a rule, so they always use the German bidding zone and the weather of Stuttgart.
The buttons are the extreme days of 2025 in Germany. 20 January was a Dunkelflaute, with little wind or sun and 583 €/MWh at 17:00, the highest price of the year. On days like this, German grid operators are most likely to reduce heat pumps and chargers. 11 May, a sunny Sunday, had the lowest price of the year, −250 €/MWh at 13:00, the situation feed-in limits exist for. 22 November was the coldest day and 1 July the start of a heatwave with an evening price spike.
Any other day of 2025 can be picked as well: it runs on that day's real prices and measured weather. The section 2025, evening by evening runs all 365 evenings for a feeder of 20 depots. This replays 2025 as it happened. Day-ahead prices are published one day in advance, so a whole year of them cannot be forecast, and the planner only needs the next 24 hours.
The gateway is an IEC 60870-5-104 controlled station, the protocol German DSOs require for telecontrol of plants from 100 kW. Commands are confirmed (or refused with a negative confirmation), measurements go out spontaneously with time tags, and the link runs over TLS with client certificates in the spirit of IEC 62351-3. The protocol stack is written from scratch and tested against lib60870.
A planner (MPC) looks 24 hours ahead every 15 minutes and schedules battery, cars and heat pump against prices, the demand charge, departures, comfort and battery ageing. Underneath, a real-time safety layer runs every second and follows the plan only as far as the rules allow: the consumption floor, feed-in limits, the 6 A car minimum and departure deadlines never depend on the plan being right.
Inverters and meter are found by walking the SunSpec model chain. Inverter limits are written without a revert timeout, so they stay in force if the gateway dies; chargers get a failsafe current of 6 A, which alone is below 4.2 kW. DSO commands are persisted, so a dimming survives a restart.
Every number on this page comes from one of these models. Each card gives the equation, the values used and the reason for the choice, with a link to the rule or to the code.
n is the number of controllable devices behind the energy manager (4 chargers, the battery and the 14 kW heat pump). GZF is the simultaneity factor of the ruling: 0.8 for 2 devices, falling to 0.45 from 9.
Why. The ruling fixes this floor. The gateway may never grant less, so it is computed from the rule itself and tested against the ruling's own examples.
SPV is the smallest PV surplus measured over the last 30 s, recomputed every second. The 0.3 kW margin absorbs meter noise and the delay between a setpoint and the device reaching it.
Why. The ruling lets a site use its own PV on top of the floor. Taking the smallest recent value means a passing cloud cannot push the grid draw over the limit, and without the margin measurement noise alone would.
B0 is the budget when the reduction ends, Prated the sum of the devices' rated power, D the longest random wait (0, 10 or 30 min in the calculator).
Why. A step back to full power makes every site on a feeder jump at the same second. The ramp is the German rule; the random wait is the idea of the British smart-charging regulation (SI 2021/1467), tested here to see whether it helps.
Three phases at 230 V. The car stops charging below 6 A, so the power is either zero or at least 4.14 kW.
Why. Chargers are controlled by current, as in IEC 61851. The 6 A step is why a floor of 4.2 kW per device makes sense and why the planner leaves minimum powers to the real-time layer.
E is the stored energy, Pc and Pd charge and discharge power, η the one-way efficiency.
Why. The simplest model that loses energy in both directions. That is what makes charging and discharging at once never pay, so the planner needs no binary variables.
One thermal mass (first-order RC model). Time constant C/UA = 15 h. Internal gains 3 kW in working hours, 1 kW otherwise. Comfort floor 20 °C from 06:00 to 22:00, 17 °C at night, ceiling 23 °C.
Why. A 15-hour time constant lets the building store heat for a few hours. That is the flexibility the planner uses to heat before a price peak or a reduction.
GHI is the measured global horizontal irradiance of the hour; 0.85 is the performance ratio of a flat rooftop system. On the two study days, c is a random cloudiness per day and the sun's path is a clear-sky curve.
Why. Measured irradiance gives real days, real clouds included. The clear-sky model is kept for the forecaster, which, like a real energy manager, does not know the day's weather in advance.
pDA is the day-ahead price in €/MWh. P̂ is the highest quarter-hour of import in the billing period; the plan carries h/8760 of the yearly charge.
Why. A dynamic tariff indexed to the day-ahead market, as German suppliers must offer. Exported PV earns nothing in negative hours (EEG §51). The demand charge is what makes a smart plan avoid new peaks.
96 steps of 15 minutes, about 1,550 variables with four cars. Ageing 0.03 €/kWh plus a penalty outside 20–80% charge; discomfort 20 €/K·h; energy a car leaves without 1 €/kWh. V values the energy left in the battery at the end.
Why. A convex quadratic program has one optimum, a certificate when there is none, and a predictable solve time of about 16 ms. A mixed-integer model would add exactness only where the real-time layer already handles it.
σ is the standard deviation of the PV forecast. The reserve is kept only during and just before an expected reduction, the one time a shortfall cannot be bought from the grid.
Why. Cloudy hours come together, so errors are added linearly. Summing them over the whole day asked for more reserve than the battery holds.
Li is the grid exchange of depot i in one-minute means, Lred the evening with the reduction and Lbase the same evening without it, with the same weather and fleet.
Why. Comparing each evening with itself without a reduction isolates the effect of the reduction. The difference is the rebound, measured, with no need to assume it.
n runs, sample standard deviation s, Student's t quantile for a 95% interval. Strategies are compared day by day on the same random weather.
Why. Pairing removes the day-to-day variation of the weather from the comparison, so a difference of a few euros is visible with 30 days.
C is the transformer capacity of the feeder. The grid operator is assumed to reduce only on evenings that would otherwise go over it.
Why. It answers the practical question: on how many evenings is a reduction needed, and on how many does it cause a second overload.
Each reduction event produces a record (start, end, budget, measured draw). Each record's hash includes the previous one.
Why. The ruling asks the operator to be able to prove what happened. A chain of hashes makes any later change to one record visible.
f is the limit in force after the country's rules: the grid operator's setpoint (IOA 5002), the Austrian 70% cap, or zero once the Swiss free budget is used up outside an emergency.
Why. Only the export is limited. The depot, the chargers and the battery still use all the PV they can, so only the surplus above the limit is lost. Inverter limits are written without a timeout, so they stay in force if the gateway stops.
Each plan is a convex quadratic program over 96 quarter-hours: battery dynamics with losses and an ageing cost per kWh cycled, a first-order thermal model of the building, each car's energy request and departure time (as ISO 15118 passes them to the charger), the DSO's announced dimming window as a constraint, day-ahead prices, and a demand charge on the highest quarter-hour of import. It is solved by Clarabel, an interior-point solver written in Rust. The same solver runs in the gateway, in the study and in this page. Forecast errors are handled where they matter: the battery keeps a reserve only in and just before an expected dimming, the one time a shortfall cannot be bought from the grid.
A Monte Carlo study measured it. The depot ran through 30 random-weather days each in spring and winter, on the real prices of those days, with the DSO dimming from 17:30 to 19:30. The same code ran 720 times.
| Per day | Spring · 6 Apr 2025 | Winter · 20 Jan 2025 |
|---|---|---|
| Rules alone: energy, battery ageing and peak charge | 126.6 € | 357.9 € |
| MPC | −25.6 ± 2.2 € (−20%) | −59.7 ± 2.4 € (−17%) |
| Highest quarter-hour of import | 89 → 34 kW | 110 → 57 kW |
| MPC that ignores the demand charge | −15.6 €, peak up to 93 kW | −44.4 €, peak up to 118 kW |
| Chance-constrained or robust MPC | same as MPC | same as MPC |
| Cars that left without their energy | 0 | 0 |
Most of the value comes from prices, the peak and the building's thermal mass. Forecast errors matter much less, because the real-time layer absorbs them within seconds and §14a guarantees a floor. With the dimming moved to the afternoon, the chance-constrained reserve cost 5 € a day in spring and bought only a few seconds less above the floor. A plan that ignores the demand charge piles charging into the cheapest hours and raises the peak, and for a German commercial site that pays a Leistungspreis it can lose money.
| Consumption | Feed-in | Source | |
|---|---|---|---|
| DE | §14a EnWG: the DSO may dim heat pumps, chargers and batteries, but must leave Pmin,14a (4.2 kW per device, simultaneity factor for several behind an EMS) | DSO setpoints for plants over 100 kW; new systems without smart meter capped at 60% (Solarspitzengesetz 2025) | BNetzA BK6-22-300, Anlage 1 |
| AT | No statutory minimum like §14a was found in the sources checked; modelled as a flexibility contract (minimum power, minutes per day) | Spitzenkappung: the DSO may cap feed-in of new or extended PV at up to 70% of module peak power; a dynamic version is planned for 2028 | ElWG, BGBl. I 91/2025 |
| CH | Flexibility is used by contract, with pay; the owner may forbid uses that existed before 2026 | Guaranteed, unpaid curtailment of at most 3% of the yearly energy at the connection point; unlimited in an immediate, serious threat | StromVG Art. 17c; StromVV Art. 19b–19d (in force 1 Jan 2026) |
| IOA | Type | Direction | Meaning |
|---|---|---|---|
| 5001 | C_SC_NA_1 | DSO → site | Reduce consumption ON / OFF (§14a in DE, contract in AT/CH) |
| 5002 | C_SE_NC_1 | DSO → site | Feed-in limit, % of installed PV (0–100; other values are refused) |
| 5003 | C_SC_NA_1 | DSO → site | Emergency ON / OFF: overrides day limits, budgets and opt-outs |
| 1001 | M_ME_TF_1 | site → DSO | Active power at the grid connection, kW (+ import) |
| 1002 | M_ME_TF_1 | site → DSO | PV active power, kW |
| 1003 | M_ME_TF_1 | site → DSO | Controllable devices (steuVE), kW |
| 1004 | M_ME_TF_1 | site → DSO | steuVE power drawn from the grid, kW (the quantity §14a limits) |
| 1005 | M_ME_TF_1 | site → DSO | Consumption floor while dimmed, kW (Pmin,14a in DE) |
| 1006 | M_ME_TF_1 | site → DSO | Feed-in limit in force, % (feedback of 5002) |
| 1007 | M_ME_TF_1 | site → DSO | PV limit sent to the inverters, % |
| 1008 | M_ME_TF_1 | site → DSO | Feed-in limit in force after country rules, % |
| 1009 | M_ME_TF_1 | site → DSO | Battery power, kW (+ charging) |
| 1010 | M_ME_TF_1 | site → DSO | Battery state of charge, % |
| 1011 | M_ME_TF_1 | site → DSO | Consumption dimmed today, minutes |
| 1012 | M_ME_TF_1 | site → DSO | PV energy curtailed this year, kWh |
| 1013 | M_ME_TF_1 | site → DSO | Free curtailment budget used, % (CH) |
| 2001 | M_SP_TB_1 | site → DSO | Consumption dimming active (feedback of 5001) |
| 2002 | M_SP_TB_1 | site → DSO | Gradual release in progress |
| 2003 | M_SP_TB_1 | site → DSO | Meter fallback (grid measurement lost) |
| 2004 | M_SP_TB_1 | site → DSO | Field device fault |
| 2005 | M_SP_TB_1 | site → DSO | Emergency active (feedback of 5003) |
| 2006 | M_SP_TB_1 | site → DSO | Contract day limit reached, dimming refused |
| 2007 | M_SP_TB_1 | site → DSO | Curtailment budget used up |