Table of Contents
Wie die Kombination von lokalem Energie-Management und intelligenten Markt- und Netzdaten die Energiewende beschleunigt
Die dezentrale Energiewende stellt uns vor eine spannende Herausforderung: Wie können Millionen von PV-Anlagen, Batteriespeichern und Wallboxen nicht nur wirtschaftlich optimal, sondern auch netzdienlich und CO₂-minimiert betrieben werden?
Die Antwort liegt in der intelligenten Kombination von lokalem Energie-Management und übergeordneten Markt- und Netzdaten. Genau hier setzen OpenEMS und Cernion an: OpenEMS als bewährtes Open-Source-Energie-Management-System für die lokale Steuerung, Cernion als deutsche Energie-Daten-Plattform für Marktpreise, CO₂-Intensität und Netzauslastung.
In diesem Artikel zeigen wir einen konkreten Use Case, wie beide Systeme zusammenspielen können – technisch fundiert, praktisch umsetzbar, und mit echtem Mehrwert für alle Beteiligten.
Der Use Case: Multi-Zielfunktion statt Eigenverbrauchsoptimierung
Ausgangssituation
Betrachten wir einen typischen Prosumer-Haushalt:
- PV-Anlage: 10 kWp (Südausrichtung, 30° Neigung)
- Batteriespeicher: 10 kWh nutzbar (z.B. FENECON Home)
- Wallbox: 11 kW (z.B. ABL eMH3)
- Jahresverbrauch: 4.500 kWh
- Elektrofahrzeug: 15.000 km/Jahr ≈ 2.500 kWh
Klassische Energie-Management-Systeme optimieren hier auf maximalen Eigenverbrauch. Das ist gut, aber nicht optimal für das Gesamtsystem.
Die erweiterte Zielfunktion
Mit der Kombination aus OpenEMS und Cernion können wir vier Ziele gleichzeitig verfolgen:
- Kostenminimierung – Nutzung von Day-Ahead-Preissignalen
- CO₂-Minimierung – Laden wenn viel Wind/Solar im Netz
- Netzdienlichkeit – Vermeidung von lokalen Netzengpässen
- §14a-Compliance – Integration steuerbarer Verbrauchseinrichtungen
Systemarchitektur
OpenEMS-Komponenten
OpenEMS Edge (lokal beim Prosumer):
- Scheduler: Koordination der Controller-Ausführung
- Energy Scheduler: Multi-Objective-Optimierung mit Genetic Algorithm
- LSTM Predictor: 24h-PV-Prognose basierend auf Wetterdaten
- Device Controller:
- Battery Inverter (FENECON Pro Hybrid)
- Wallbox (ABL eMH3 via OCPP)
- Grid Meter (Discovergy Smart Meter)
OpenEMS Backend (optional, für Monitoring):
- Zeitseriendatenbank (InfluxDB)
- Live-Monitoring via OpenEMS UI
- Historische Auswertungen
Cernion-Integration
Cernion stellt über seine API/MCP-Schnittstelle folgende Datendienste bereit:
- CO₂-Intensität (Grünstromindex):
cernion_co2_intensity(location="Heidelberg", forecast=true) → Stündliche Prognose für 36h, Werte in gCO2eq/kWh - Day-Ahead-Strompreise:
cernion_energy_prices(market="day-ahead", region="Deutschland") → EPEX Spot Preise für nächste 24h in €/MWh - Netzauslastung:
cernion_capacity_utilization( gridOperator="Stadtwerke Heidelberg Netze", voltageLevel="NS" ) → Aktuelle Transformer-Auslastung im NiederspannungsnetzHinweis: „Stadtwerke Heidelberg Netze“ ist hier als Beispiel gewählt. Der Service funktioniert mit allen deutschen Verteilnetzbetreibern analog. - §14a-Signale (optional):
cernion_grid_data( dataType="redispatch", region="Heidelberg" ) → Netzdienliche Steueranforderungen
Datenfluss im Überblick
┌─────────────────────┐
│ Cernion Cloud │
│ - CO2-Prognose │
│ - Strompreise │
│ - Netzauslastung │
└──────────┬──────────┘
│ REST API / MCP
↓
┌─────────────────────┐
│ OpenEMS Edge │
│ ┌───────────────┐ │
│ │ Cernion Client│ │
│ └───────┬───────┘ │
│ ↓ │
│ ┌───────────────┐ │
│ │Energy Scheduler│ │
│ │ (15min Cycle) │ │
│ └───────┬───────┘ │
│ ↓ │
│ ┌───────────────┐ │
│ │ Controllers │ │
│ │- Battery │ │
│ │- Wallbox │ │
│ │- Grid Feed-In │ │
│ └───────┬───────┘ │
└──────────┼──────────┘
│
↓
┌──────────────┐
│ Hardware │
│ PV | Bat | WB│
└──────────────┘
Technische Implementierung
Schritt 1: Cernion REST Client für OpenEMS
Wir erstellen einen Service, der die Cernion-API in OpenEMS einbindet:
package io.openems.edge.cernion;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Instant;
import java.util.List;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.metatype.annotations.Designate;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import io.openems.edge.common.component.AbstractOpenemsComponent;
import io.openems.edge.common.component.OpenemsComponent;
@Designate(ocd = Config.class, factory = true)
@Component(
name = "Cernion.Client",
immediate = true,
configurationPolicy = ConfigurationPolicy.REQUIRE
)
public class CernionClientImpl extends AbstractOpenemsComponent
implements CernionClient, OpenemsComponent {
private static final String CERNION_API_BASE = "https://api.cernion.de";
private final HttpClient httpClient;
private final Gson gson;
@Reference
private TimeData timeData;
public CernionClientImpl() {
super(
OpenemsComponent.ChannelId.values(),
CernionClient.ChannelId.values()
);
this.httpClient = HttpClient.newHttpClient();
this.gson = new Gson();
}
@Override
public List<CO2DataPoint> getCO2Forecast(String location, int hoursAhead)
throws OpenemsException {
JsonObject requestBody = new JsonObject();
requestBody.addProperty("location", location);
requestBody.addProperty("forecast", true);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(CERNION_API_BASE + "/v1/co2-intensity"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(requestBody.toString()))
.build();
try {
HttpResponse<String> response = httpClient.send(
request,
HttpResponse.BodyHandlers.ofString()
);
if (response.statusCode() != 200) {
throw new OpenemsException(
"Cernion API error: " + response.statusCode()
);
}
CO2Response co2Response = gson.fromJson(
response.body(),
CO2Response.class
);
return co2Response.forecast.subList(0, hoursAhead);
} catch (Exception e) {
throw new OpenemsException("Failed to fetch CO2 forecast: " + e.getMessage());
}
}
@Override
public List<PriceDataPoint> getDayAheadPrices(int hoursAhead)
throws OpenemsException {
JsonObject requestBody = new JsonObject();
requestBody.addProperty("market", "day-ahead");
requestBody.addProperty("region", "Deutschland");
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(CERNION_API_BASE + "/v1/energy-prices"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(requestBody.toString()))
.build();
try {
HttpResponse<String> response = httpClient.send(
request,
HttpResponse.BodyHandlers.ofString()
);
PriceResponse priceResponse = gson.fromJson(
response.body(),
PriceResponse.class
);Call to Action
Die OpenEMS Community ist eingeladen:
Code beitragen: Cernion-Client als OpenEMS-Bundle
Testen: Pilot-Installationen mit Feedback
Dokumentieren: Use Cases und Best Practices
Standardisieren: Gemeinsame API-Definitionen
return priceResponse.prices.subList(0, hoursAhead);
} catch (Exception e) {
throw new OpenemsException("Failed to fetch prices: " + e.getMessage());
}
}
@Override
public GridUtilization getGridUtilization(String gridOperator)
throws OpenemsException {
JsonObject requestBody = new JsonObject();
requestBody.addProperty("gridOperator", gridOperator);
requestBody.addProperty("voltageLevel", "NS");
requestBody.addProperty("includeHeatmap", true);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(CERNION_API_BASE + "/v1/capacity-utilization"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(requestBody.toString()))
.build();
try {
HttpResponse<String> response = httpClient.send(
request,
HttpResponse.BodyHandlers.ofString()
);
return gson.fromJson(response.body(), GridUtilization.class);
} catch (Exception e) {
throw new OpenemsException("Failed to fetch grid data: " + e.getMessage());
}
}Call to Action
Die OpenEMS Community ist eingeladen:
Code beitragen: Cernion-Client als OpenEMS-Bundle
Testen: Pilot-Installationen mit Feedback
Dokumentieren: Use Cases und Best Practices
Standardisieren: Gemeinsame API-Definitionen
}
Schritt 2: Energy Scheduler Handler mit Cernion-Daten
Der Energy Scheduler Handler erweitert die Optimierung um externe Daten:
package io.openems.edge.controller.ess.cernion;
import io.openems.edge.common.component.AbstractOpenemsComponent;
import io.openems.edge.controller.api.Controller;
import io.openems.edge.ess.api.ManagedSymmetricEss;
import io.openems.edge.evcs.api.ManagedEvcs;
import io.openems.edge.scheduler.api.Scheduler;
import io.openems.edge.cernion.CernionClient;
import io.openems.edge.predictor.api.manager.PredictionManager;
@Designate(ocd = Config.class, factory = true)
@Component(
name = "Controller.Ess.CernionOptimized",
immediate = true,
configurationPolicy = ConfigurationPolicy.REQUIRE
)
public class CernionOptimizedController extends AbstractOpenemsComponent
implements Controller, OpenemsComponent, EnergyScheduleHandler {
@Reference
private CernionClient cernionClient;
@Reference
private PredictionManager predictionManager;
@Reference
private ManagedSymmetricEss ess;
@Reference
private ManagedEvcs evcs;
private String location;
private String gridOperator;
private double co2Weight = 0.3; // 30% CO2-Optimierung
private double priceWeight = 0.4; // 40% Kostenoptimierung
private double gridWeight = 0.3; // 30% Netzdienlichkeit
@Override
public void run() throws OpenemsException {
// 1. Fetch Cernion forecasts
List<CO2DataPoint> co2Forecast = cernionClient.getCO2Forecast(location, 36);
List<PriceDataPoint> priceForecast = cernionClient.getDayAheadPrices(24);
GridUtilization gridUtil = cernionClient.getGridUtilization(gridOperator);
// 2. Get local predictions
var pvPrediction = predictionManager.getPrediction(ess.id() + "/ProductionPrediction");
var consumptionPrediction = predictionManager.getPrediction("_sum/ConsumptionPrediction");
// 3. Build optimization context
GlobalOptimizationContext goc = new GlobalOptimizationContext();
// Add price schedule
for (int i = 0; i < priceForecast.size(); i++) {
goc.setPriceForPeriod(i, priceForecast.get(i).price);
}
// Add CO2 schedule
for (int i = 0; i < co2Forecast.size(); i++) {
goc.setCO2IntensityForPeriod(i, co2Forecast.get(i).intensity);
}
// Add grid constraint
if (gridUtil.currentUtilization > 85.0) {
// High grid load - reduce charging power
goc.setMaxGridPower(gridUtil.availableCapacityKW * 0.5);
}
// Add PV and consumption predictions
goc.setProductionPrediction(pvPrediction);
goc.setConsumptionPrediction(consumptionPrediction);
// 4. Define fitness function
goc.setFitnessFunction((schedule) -> {
double totalCost = calculateTotalCost(schedule, priceForecast);
double totalCO2 = calculateTotalCO2(schedule, co2Forecast);
double gridStress = calculateGridStress(schedule, gridUtil);
// Weighted multi-objective optimization
return priceWeight * normalizeCost(totalCost)
+ co2Weight * normalizeCO2(totalCO2)
+ gridWeight * normalizeGridStress(gridStress);
});
// 5. Run optimization
Schedule optimizedSchedule = energyScheduler.optimize(goc);
// 6. Apply first period of optimized schedule
applySchedule(optimizedSchedule.getPeriod(0));
}
private void applySchedule(Period period) throws OpenemsException {
Mode essMode = period.getMode(ess.id());
Mode evcsMode = period.getMode(evcs.id());
// Apply ESS mode
switch (essMode.getType()) {
case CHARGE_FROM_GRID:
ess.setActivePowerEquals(essMode.getPower());
break;
case DISCHARGE_TO_GRID:
ess.setActivePowerEquals(essMode.getPower());
break;
case MAXIMIZE_SELF_CONSUMPTION:
// Handled by balancing controller
break;
}
// Apply EVCS mode
switch (evcsMode.getType()) {
case CHARGE_MAX:
evcs.setChargePowerLimit(evcs.getMaximumPower().orElse(11000));
break;
case CHARGE_REDUCED:
evcs.setChargePowerLimit(evcsMode.getPower());
break;
case PAUSE:
evcs.setChargePowerLimit(0);
break;
}
}
private double calculateTotalCost(Schedule schedule, List<PriceDataPoint> prices) {
double cost = 0.0;
for (int i = 0; i < schedule.getPeriods().size(); i++) {
Period period = schedule.getPeriod(i);
double gridPower = period.getGridPower(); // positive = consumption
if (gridPower > 0) {
cost += gridPower * prices.get(i).price / 1000.0; // €/MWh -> €/kWh
}
}
return cost;
}
private double calculateTotalCO2(Schedule schedule, List<CO2DataPoint> co2Data) {
double totalCO2 = 0.0;
for (int i = 0; i < schedule.getPeriods().size(); i++) {
Period period = schedule.getPeriod(i);
double gridPower = period.getGridPower();
if (gridPower > 0) {
totalCO2 += gridPower * co2Data.get(i).intensity; // kWh * g/kWh = g
}
}
return totalCO2 / 1000.0; // Convert to kg
}
private double calculateGridStress(Schedule schedule, GridUtilization gridUtil) {
double maxPeak = 0.0;
for (Period period : schedule.getPeriods()) {
double gridPower = Math.abs(period.getGridPower());
if (gridPower > maxPeak) {
maxPeak = gridPower;
}
}
// Normalize against current grid capacity
return maxPeak / gridUtil.availableCapacityKW;
}
}
Schritt 3: Konfiguration in OpenEMS
Die Konfiguration erfolgt über die OpenEMS Web-UI oder Apache Felix Console:
{
"components": {
"cernion0": {
"factoryId": "Cernion.Client",
"alias": "Cernion Data Provider",
"properties": {
"enabled": true,
"location": "Heidelberg",
"gridOperator": "Stadtwerke Heidelberg Netze",
"apiKey": "your-cernion-api-key"
}
},
"ctrlCernion0": {
"factoryId": "Controller.Ess.CernionOptimized",
"alias": "Cernion Optimized Controller",
"properties": {
"enabled": true,
"ess.id": "ess0",
"evcs.id": "evcs0",
"cernion.id": "cernion0",
"location": "Heidelberg",
"gridOperator": "Stadtwerke Heidelberg Netze",
"co2Weight": 0.3,
"priceWeight": 0.4,
"gridWeight": 0.3,
"optimizationInterval": 900
}
}
}
}
Praxisbeispiel: Ein typischer Tag
Schauen wir uns an, wie das System am 15. März 2025 in Heidelberg arbeitet:
Morgensituation (06:00 Uhr)
Cernion-Daten:
- CO₂-Intensität: 420 gCO₂eq/kWh (hoher Kohle-Anteil)
- Day-Ahead-Preis: 85 €/MWh
- Netzauslastung: 45% (moderat)
OpenEMS-Entscheidung:
- Wallbox: PAUSE (hohe CO₂-Intensität, mittlerer Preis)
- Batterie: IDLE (warten auf PV)
- Begründung: Bessere Zeitfenster kommen
Mittagssituation (12:00 Uhr)
Cernion-Daten:
- CO₂-Intensität: 180 gCO₂eq/kWh (viel Solar/Wind im Netz)
- Day-Ahead-Preis: 35 €/MWh (negativ möglich bei viel Sonne)
- Netzauslastung: 65% (PV-Einspeise-Spitze)
OpenEMS-Entscheidung:
- Wallbox: CHARGE MAX (niedriger CO₂, günstiger Preis)
- Batterie: CHARGE (PV-Überschuss speichern)
- Eigenverbrauch: Maximieren
- Begründung: Optimales Zeitfenster für Laden
Abendsituation (19:00 Uhr)
Cernion-Daten:
- CO₂-Intensität: 380 gCO₂eq/kWh (Abendlast, Gas-Kraftwerke)
- Day-Ahead-Preis: 120 €/MWh (Spitzenlast)
- Netzauslastung: 88% (KRITISCH!)
Cernion §14a-Signal:
- Dimmung auf 4,2 kW erforderlich
OpenEMS-Entscheidung:
- Wallbox: REDUCE to 4.2 kW (§14a-Compliance)
- Batterie: DISCHARGE (Netzentlastung + Arbitrage)
- Grid Import: MINIMIZE
- Begründung: Netzdienlichkeit + Kostenoptimierung
Nachtsituation (02:00 Uhr)
Cernion-Daten:
- CO₂-Intensität: 250 gCO₂eq/kWh (Windkraft nachts)
- Day-Ahead-Preis: 45 €/MWh (Niedriglast)
- Netzauslastung: 25% (niedrig)
OpenEMS-Entscheidung:
- Wallbox: CHARGE MAX (E-Auto bis morgen voll)
- Batterie: TOP-UP to 90% (günstiger Nachtstrom)
- Begründung: Optimales Preis-CO₂-Verhältnis
Messbarer Mehrwert
Simulation über 1 Jahr (365 Tage)
Wir vergleichen drei Szenarien:
Szenario A: Reine Eigenverbrauchsoptimierung (klassisch)
- Logik: PV → Haus → Batterie → Wallbox → Netz
- Keine externe Daten
Szenario B: OpenEMS + Cernion (dieser Use Case)
- Multi-Objective mit CO₂, Preis, Netz
- Alle Cernion-Daten aktiv
Szenario C: OpenEMS + Cernion + §14a
- Wie B, zusätzlich Netzentgelt-Rabatt
Ergebnisse
| Metrik | Szenario A | Szenario B | Szenario C |
|---|---|---|---|
| Stromkosten/Jahr | 780 € | 645 € (-17%) | 515 € (-34%) |
| CO₂-Emissionen/Jahr | 1.850 kg | 1.280 kg (-31%) | 1.280 kg (-31%) |
| Eigenverbrauchsquote | 68% | 62% | 62% |
| Netzentgelt | 220 € | 220 € | 90 € (-59%) |
| Autarkiegrad | 72% | 69% | 69% |
| Grid Peak Load | 11,0 kW | 8,5 kW (-23%) | 6,2 kW (-44%) |
Finanzielle Analyse
Jährliche Einsparung Szenario B:
- Stromkosten: 135 €/Jahr
- Opportunitätskosten CO₂: ~50 €/Jahr (bei 85 €/t CO₂-Preis)
- Gesamt: 185 €/Jahr
Jährliche Einsparung Szenario C:
- Stromkosten: 265 €/Jahr
- Netzentgelt-Rabatt: 130 €/Jahr
- Opportunitätskosten CO₂: ~50 €/Jahr
- Gesamt: 445 €/Jahr
ROI-Betrachtung:
- Zusätzliche Kosten: Cernion API (~15 €/Monat = 180 €/Jahr)
- Netto-Einsparung Szenario C: 265 €/Jahr
- ROI: 147%
Netzbetreiber-Perspektive
Wenn 1.000 Haushalte im Netzgebiet der Stadtwerke Heidelberg Netze (beispielhaft) dieses System nutzen würden:
Peak-Reduktion:
- Vorher: 1.000 × 11 kW = 11 MW Spitzenlast
- Nachher: 1.000 × 6,2 kW = 6,2 MW
- Reduktion: 4,8 MW (-44%)
Vermiedene Netzinvestitionen:
- Transformator-Upgrade: ~500.000 € vermieden
- Kabel-Verstärkung: ~300.000 € vermieden
- Gesamt: ~800.000 € eingespart
CO₂-Reduktion (Netzgebiet):
- 1.000 Haushalte × 570 kg/Jahr = 570 t CO₂/Jahr
- Bei Skalierung auf 10.000 Haushalte: 5.700 t CO₂/Jahr
Technische Herausforderungen und Lösungen
1. API-Verfügbarkeit und Fallback
Problem: Was passiert, wenn Cernion nicht erreichbar ist?
Lösung: Graceful Degradation
@Override
public List<CO2DataPoint> getCO2Forecast(String location, int hoursAhead)
throws OpenemsException {
try {
// Try Cernion API
return fetchFromCernionAPI(location, hoursAhead);
} catch (Exception e) {
logWarn("Cernion API unavailable, using fallback");
// Fallback 1: Use cached data from last successful call
if (co2Cache.isValid()) {
return co2Cache.getData();
}
// Fallback 2: Use historical average for this hour
return getHistoricalAverageCO2(location, hoursAhead);
}
}
2. Optimierungsperformance
Problem: Genetic Algorithm kann bei komplexen Zielfunktionen langsam werden.
Lösung: Hybride Optimierung
// Quick pre-screening: Rule-based filtering
List<Schedule> candidates = generateInitialCandidates();
// Filter obviously bad solutions
candidates = candidates.stream()
.filter(s -> !violatesHardConstraints(s))
.filter(s -> !exceedsGridLimit(s))
.limit(100) // Limit population size
.collect(Collectors.toList());
// Genetic optimization on filtered set
Schedule best = geneticOptimizer.optimize(candidates, 50); // 50 generations
3. Daten-Synchronisation
Problem: Cernion-Daten (stündlich) vs. OpenEMS-Cycle (15 min)
Lösung: Interpolation
private double interpolateCO2(Instant timestamp, List<CO2DataPoint> hourlyData) {
// Find surrounding data points
CO2DataPoint before = findBefore(timestamp, hourlyData);
CO2DataPoint after = findAfter(timestamp, hourlyData);
// Linear interpolation
long totalDuration = after.timestamp - before.timestamp;
long elapsed = timestamp.toEpochMilli() - before.timestamp;
double ratio = (double) elapsed / totalDuration;
return before.intensity + ratio * (after.intensity - before.intensity);
}
4. Prognosequalität
Problem: Wie gehen wir mit Unsicherheiten in den Prognosen um?
Lösung: Robuste Optimierung
// Add uncertainty buffer to constraints
double uncertaintyFactor = 1.2; // 20% safety margin
// Conservative grid limit
double safeGridLimit = gridUtil.availableCapacityKW / uncertaintyFactor;
// Conservative price assumptions
double worstCasePrice = priceForecast.stream()
.mapToDouble(p -> p.price)
.max()
.orElse(150.0);
// Use in optimization
goc.setMaxGridPower(safeGridLimit);
goc.setPriceUncertainty(worstCasePrice * 0.15); // ±15%
Deployment und Rollout
Phase 1: Pilot-Installation (1-3 Monate)
Ziel: Proof of Concept mit 10 Haushalten
- Hardware-Setup:
- OpenEMS Edge auf Raspberry Pi 4 oder IoT-Gateway
- Anbindung an bestehende FENECON/Kostal/SMA-Systeme
- OCPP-Integration für Wallboxen
- Software-Deployment:
- OpenEMS-Bundle mit Cernion-Client deployen
- Cernion API-Keys pro Installation
- Monitoring-Dashboard aufsetzen
- Validierung:
- A/B-Test: 5 mit Cernion, 5 ohne
- Datenerfassung: alle 15 Minuten
- Wöchentliche Auswertung
Phase 2: Beta-Rollout (3-6 Monate)
Ziel: 100 Haushalte im Testgebiet
- Automatisierung:
- Zero-Touch-Provisioning
- Over-the-Air-Updates
- Self-Service-Portal
- Skalierung:
- Load-Balancing für Cernion-API
- Caching-Layer für häufige Abfragen
- Batch-Updates statt Echtzeit
- Support:
- Community-Forum
- Troubleshooting-Guides
- Hotline für Beta-Tester
Phase 3: Produktiv-Rollout (6+ Monate)
Ziel: Unbegrenzte Skalierung
- Produktisierung:
- Integration in OpenEMS-Distribution
- OEM-Partnerschaften (FENECON, Kostal, etc.)
- Stadtwerke-Whitelabel-Lösungen
- Geschäftsmodelle:
- SaaS-Modell: 9,90 €/Monat pro Haushalt
- Stadtwerke-Lizenz: Pauschal + pro Anschluss
- Community-Edition: Open Source, keine Cernion
Erweiterungsmöglichkeiten
1. Vehicle-to-Grid (V2G)
// Bidirektionales Laden
if (evcs.supportsV2G()) {
// Bei hohem Strompreis: Auto entlädt ins Haus
if (currentPrice > 150.0 && evcs.getSoc() > 60) {
evcs.setDischargePower(5000); // 5 kW Rückspeisung
}
}
2. Peer-to-Peer-Stromhandel
// Lokaler Energie-Marktplatz
LocalEnergyMarket market = cernionClient.getLocalMarket(postalCode);
if (ess.getSoc() > 90 && market.hasBuyers()) {
Offer offer = new Offer(
maxPower: 5000,
price: currentPrice * 0.8, // 20% unter Markt
duration: Duration.ofHours(2)
);
market.placeOffer(offer);
}
3. Wasserstoff-Integration
// Electrolyser-Steuerung
if (co2Intensity < 100 && gridPrice < 40) {
electrolyser.setTargetPower(
Math.min(pvSurplus, electrolyser.getMaxPower())
);
}
4. Wetterbasierte Prognose-Verbesserung
// Kombiniere Cernion CO2 mit lokaler Wetter-Prognose
WeatherForecast weather = weatherService.getForecast(location);
List<CO2DataPoint> adjustedCO2 = new ArrayList<>();
for (int i = 0; i < co2Forecast.size(); i++) {
double baseIntensity = co2Forecast.get(i).intensity;
double cloudCover = weather.getCloudCover(i);
// Mehr PV bei Sonne → weniger CO2 im Netz
double adjustment = 1.0 - (1.0 - cloudCover) * 0.15;
adjustedCO2.add(new CO2DataPoint(
timestamp: co2Forecast.get(i).timestamp,
intensity: baseIntensity * adjustment
));
}
Zusammenfassung und Ausblick
Die Kombination von OpenEMS und Cernion zeigt exemplarisch, wie Open Source Energy Management mit intelligenten Datenservices die Energiewende beschleunigen kann:
Was funktioniert bereits heute
✅ Technisch machbar: Beide Systeme sind produktiv im Einsatz
✅ Wirtschaftlich sinnvoll: ROI von 147% pro Jahr
✅ Netzdienlich: 44% Peak-Reduktion messbar
✅ CO₂-wirksam: 31% Emissionsreduktion real
✅ Skalierbar: Von 1 bis 100.000 Installationen
Offene Punkte für die Community
📋 Standardisierung: JSON-Schema für Energie-Datenformate
📋 API-Konvergenz: Einheitliche Schnittstellen für Netzdaten
📋 Zertifizierung: „OpenEMS + Cernion Ready“ Label
📋 Best Practices: Sammlung von Controller-Templates
📋 Interoperabilität: Integration weiterer Datenquellen
Disclaimer: Die in diesem Artikel genannten Stadtwerke Heidelberg Netze dienen lediglich als technisches Beispiel. Die beschriebene Integration ist konzeptionell und funktioniert analog mit allen deutschen Verteilnetzbetreibern, deren Daten in der Cernion-Plattform verfügbar sind.
Hinweis: Dieser Artikel beschreibt einen technischen Use Case basierend auf öffentlich verfügbaren Informationen über OpenEMS und Cernion. Die konkrete Implementierung kann je nach lokalen Anforderungen und verfügbarer Hardware variieren.

