EnexaEMS Simulator v1.0
Simulation Overview
  • Input SummaryAll configuration parameters
  • Simulation AlgorithmsReactive vs Smart EMS explained
  • Result OverviewSide-by-side comparison
Configuration
  • Location SetupEquipment specs & constraints
  • FinancialsCosts, income & margins
  • Solar ProductionPV generation profile
  • Charging SessionsEV demand profile
  • Not ModeledKnown gaps & limitations
  • SmartEMS ConfigPlanner tuning parameters
Reactive BMS
  • BMS AlgorithmHow the reactive BMS works
  • BMS ReactiveRule-based simulation results
SmartEMS
  • SmartEMS AlgorithmHow the 2-layer optimizer works
  • SmartEMS ResultsOptimized simulation output

SmartEMS Algorithm v1 Original

Two-layer price-aware energy management: a planner that pre-computes optimal schedules, and a dispatcher that executes them in real-time.

Algorithm Versionv1 Active
Select which SmartEMS algorithm to use. Both share the same two-layer architecture but differ in how aggressively they exploit price signals.

Key Differences at a Glance

Featurev1 Originalv2 Improved
SOC target formulaReadiness + price penalty+ arbitrage boost + pre-charge
Moderate slot throttle100% idle rate20-60% (risk-scaled)
Arbitrage (cheap > expensive)NoYes (RTE-adjusted)
Pre-charging in cheap slotsNoYes (spread-justified)
EV serving in expensive slotsPV > Grid > BattPV > Batt > Grid
RTE in thresholdsNot factoredAll thresholds adjusted

Currently active: v1 (Original). Click "Run Simulation" in the sidebar to apply changes.

Two-Layer Architecture
SmartEMS separates planning (what we know ahead of time) from dispatching (real-time reactions to EV arrivals).

Layer 1: Planner (runs once, before the day)

Uses only data knowable 12-24h ahead. Produces a schedule that optimizes battery charging cost and readiness. Includes overnight pre-charge ramp for multi-day continuity.

Inputs (known ahead)

  • EPEX day-ahead prices (96 x 15-min slots)
  • PV weather forecast (~90% accurate)
  • Historical arrival probability (statistical)
  • Grid export config and tariff structure

Outputs (schedule for dispatcher)

  • SOC target curve (1440 min, dynamic %)
  • Grid import gate (96 slots, allowed/blocked/capped)
  • PV routing hints (1440 min, store vs export)

Day continuity model (06:00 reference)

The simulation day anchors at 06:00 with a reference SOC of 46%. Day continuity means: SOC at 06:00 start = SOC at 06:00 end.Pre-dawn ramp (00:00-08:22): From the reference SOC at midnight, the target ramps to the demand-start target. Cheap overnight hours ensure the battery is fully charged before the first EVs arrive.Active day (08:22-18:51): Readiness + price formula drives the dynamic target. Battery discharges to serve EVs and exploits price arbitrage.Evening recharge (18:51-05:59): After demand drops, the target ramps back toward reference SOC (46%) using cheap night rates, closing the daily cycle.Dispatcher start: SmartEMS starts at reference SOC (46%). Reactive BMS starts at its own configured target (80%).

Layer 2: Dispatcher (runs every minute, real-time)

Same reactive engine as BMS Reactive, but with 3 targeted upgrades.

Change 1: Dynamic SOC target

Fixed idleTargetSoc = 80% replaced by socTargetCurve[minute] which varies 46-86% across the day.

Change 2: Price-gated grid imports

Grid-to-battery recharging is rate-limited or blocked during expensive/peak price slots. Moderate slots at full idle rate (reduced only at risk > 70). Emergency override below 30% SOC.

Change 3: Smart PV routing

PV surplus is stored in battery when future import costs exceed current export revenue; exported otherwise.

Honest Tradeoff: Readiness vs. Cost

SmartEMS does NOT guarantee the same battery buffer as Reactive. During expensive price windows, SOC may be lower. Safety nets: (1) readiness boost always overrides at least 80% of price penalty (risk-scaled), (2) emergency override at 30% SOC forces grid charging regardless of price.The comparison page shows the exact risk metrics.

Step 1: Demand Readiness Score
Hourly EV arrival probability from public HPC data (Fastned, Ionity, EnBW). Convolved with a 45-minute look-ahead window to produce a minute-by-minute readiness score.

Hourly Arrival Probability (Saturday, Supermarket HPC)

Readiness Score (0-1, after 45-min look-ahead convolution)

Step 2: Price Zones (Percentile Classification)
Each 15-min slot classified by total import cost (EPEX + 8.0 ct fees) into quartiles. Bottom 25% = cheap, top 25% = peak.
Cheap (bottom 25%): opportunistic pre-charge toward ceiling, full-rateModerate: neutral SOC, throttled to 30% idle rateExpensive: -15% SOC, 7% rate capPeak (top 25%): -20% SOC, grid blocked
Step 3: Dynamic SOC Target Curve
06:00-anchored: charging (overnight), active day (readiness-driven), evening recharge. SmartEMS starts at 46% (reference SOC). Reactive baseline at 80%.
SmartEMS dynamic targetReactive fixed (80%)SOC floor (20%)Emergency override (30%)Evening recharge (18:51-05:45)
Step 4: PV Surplus Routing
Each minute: compare value of storing PV (avoided future import) vs exporting now (EPEX revenue). PV to EV is always first -- this only applies to surplus after EV + battery needs.
Store in battery (future savings exceed export)Export to grid (export pays more now)
SOC Target Formula (with Day Continuity)

// Day continuity: overnight ramp to morning readiness

// Simulates multi-day steady state without running multiple days.

// Overnight: baseSoc → ramp up → ready before demand → active day → formula handles evening naturally

baseSoc = (20 + 90) / 2 + (-0.75 * 12) = 46.0% // midpoint of SOC range - risk shift

// Find first meaningful demand window

// Scan readiness curve to find the first minute where readiness >= 0.3

// This is when arrival probability becomes significant (~25%+)

// The battery must be ready BEFORE this point, not scrambling during it

demandStart = first minute where readiness[m] > 0.3

morningTarget = baseSoc + readiness[demandStart] * 25 + max(0, priceMod[demandStart])

// morningTarget includes the readiness boost at demand start -- this is the

// SOC level needed to serve the first EVs without immediately drawing grid

// Phase 1: Overnight ramp (00:00 → demandStart)

overnightFloor[m] = baseSoc + (m / demandStart) * (morningTarget - baseSoc)

socTarget[m] = max(formula_target, overnightFloor[m])

// Gradually charges from ~46% using cheap overnight electricity

// Battery arrives at morningTarget just as the first EVs are expected

// Phase 2: Active day (demandStart → 19:59)

socTarget[m] = baseSoc + readinessBoost + priceMod + arbitrageBoost

// Readiness drives SOC up before expected arrivals (high readiness = high SOC)

// Price signal drives SOC down during expensive slots (avoid costly grid import)

// Safety clamp prevents price from erasing more than 80% of readiness boost

// Evening (20:00+): no artificial wind-down

// readiness drops to 0 → readinessBoost = 0

// prices are expensive/peak → priceMod = negative

// Formula naturally produces baseSoc-level targets

// Evening recharge: target ramps back toward referenceSoc (46%) using cheap night rates

// Ensures day continuity: SOC at 06:00 start ≈ SOC at 06:00 end

// SmartEMS dispatcher starts at referenceSoc (46%), not fixed 80%

// Reactive BMS starts at its own configured target (80%) -- each uses its natural steady state

// Risk factor modulation (risk = 75):

riskNorm = 75 / 100 = 0.75

baseSocShift = -riskNorm * 12 = -9.0 pct pts

safetyClamp = 0.5 + riskNorm * 0.4 = 0.80 // price can override this fraction of readiness

// For each minute m:

baseSoc = (socFloor + socCeiling) / 2 + baseSocShift = (20 + 90) / 2 + (-9.0) = 46.0%

readinessBoost = readiness[m] * 25 // 0 to +25%

priceMod = { cheap: +15, moderate: 0, expensive: -15, peak: -20 }

// NEW: Opportunistic pre-charging in cheap slots

if (zone == "cheap") {

headroom = socCeiling - baseSoc = 90 - 46.0 = 44.0%

cheapFill = headroom * (0.6 + riskNorm * 0.38) = 38.9%

priceMod = max(priceMod, cheapFill) // overrides standard boost

}

// NEW: Price spread arbitrage (6-hour look-ahead)

if (currentCost < medianCost * 0.7 && priceSpread > 0.03 EUR/kWh) {

spreadNorm = clamp((priceSpread - 0.03) / 0.12, 0, 1)

maxArbitrageBoost = usableRange * (0.3 + riskNorm * 0.2) = 31.5% max

arbitrageBoost = spreadNorm * maxArbitrageBoost

}

// Safety clamp: price can override at most 80% of readiness boost

if (priceMod < 0 && readinessBoost > 0)

priceMod = max(priceMod, -(readinessBoost * 0.80))

raw = clamp(baseSoc + readinessBoost + priceMod + arbitrageBoost, 23%, 90%)

// Then apply overnight ramp floor (00:00 to demandStart)

socTarget[m] = clamp(max(raw, overnightFloor[m]), 23%, 90%)

// NEW: Demand charge shaving (dispatcher, during EV sessions)

// Expensive/peak slots: priority = PV > Battery > Grid (capped)

// Cheap slots: priority = PV > Grid > Battery (standard)

if (zone == "expensive" || zone == "peak") {

batteryAssist = min(evNeed, dischargeRate * 1.0) // battery first

gridCap = gridLimit * max(0.5, 1 - riskNorm * 0.5) = 63% of grid limit

} else if (zone == "moderate") {

batteryAssist = min(evNeed, dischargeRate * (0.3 + riskNorm * 0.4)) = 60% of discharge rate

}

// Result: lower peak 15-min demand → reduced annual Leistungspreis