Dynamic ESS "Storage First" Green Mode (Concept)

Hi everyone,

I’ve been experimenting with Victron’s Dynamic ESS (DESS), and while the predictive intelligence is impressive, I find it a bit too “optimistic.” It often gambles on its own forecast by selling battery energy early in the day, leaving me with a lower buffer than I’m comfortable with if a cloud bank rolls in or a grid failure occurs.

I believe the “Perfect Green Mode” should prioritize Storage Security over Market Speculation. I’ve developed a Node-RED logic that combines the predictive intelligence of DESS with the rock-solid security of a user-defined “Feed-In SOC” buffer.

The Philosophy: “Storage First”

The core idea is to change the priority of how DESS handles your battery:

  1. Storage First (Below Buffer): If the SOC is below your safety threshold (e.g., 80%), grid feed-in is physically locked at 0W. We aren’t trading; we are securing our home.

  2. Injection Only for “True Surplus”: The system only plans to sell if the forecasted solar yield is mathematically certain to exceed your battery capacity (a “planned SOC” of >100%).

  3. Strategic Overflow: Once the buffer is reached, only the excess is injected during peak price windows. Your core buffer remains untouched as a hedge against forecast errors.

  4. The “Accelerator & Gate” Method: Unlike standard ESS, this logic actively drives the AcPowerSetPoint (The Accelerator) while simultaneously managing the MaxGridFeedIn (The Gate). This prevents the system from “leaking” energy when we want to hoard it.

This gives you the Predictive Intelligence of DESS (knowing when the peaks are and using forecasts) combined with the Storage Security of plain ESS. It stops the system from “gambling” with your backup.

Any toughts?

The Node-RED Logic


/**
 * Logic: 9kW Limit | 35% Floor | Automated Peak Detection
 */

// --- 1. SETTINGS ---
const MAX_EXPORT_LIMIT = -9000;   // 9kW total (3kW per phase)
const MIN_RESERVE_SOC = 35;       // Your 48h emergency buffer
const BATT_FULL_LEVEL = 98;       // Point to stop hoarding
const NOMINAL_VOLTAGE = 51.2;     // 16s LFP Battery

// --- 2. INPUTS ---
const { 
    soc, 
    capacityAh, 
    currentLimit, 
    solarRemainingFC, 
    loadRemainingFC,
    sellPrices 
} = msg.payload;

const hour = new Date().getHours();
const currentPrice = (Array.isArray(sellPrices)) ? sellPrices[hour] : 0;

// --- 3. DYNAMIC PEAK FINDER ---
// Scans DESS prices to find the highest value of the day
const maxPriceToday = (Array.isArray(sellPrices)) ? Math.max(...sellPrices) : 0;

// Identify if current hour is in the top 10% of price for the day
const isPeakWindow = (currentPrice >= maxPriceToday * 0.95) && currentPrice > 0;

// --- 4. THE DECISION ENGINE ---
let targetLimit = 0;
let statusReport = "";

if (typeof soc !== 'number' || typeof capacityAh !== 'number' || !sellPrices) {
    targetLimit = -1; // Fallback to hardware defaults
    statusReport = "DATA ERROR: Missing DESS data/SOC.";
} else {
    // A. Energetic Math
    const totalBankKWh = (capacityAh * NOMINAL_VOLTAGE) / 1000;
    const currentStoredKWh = (soc / 100) * totalBankKWh;
    const emptySpaceKWh = Math.max(0, totalBankKWh - currentStoredKWh);

    // netSolar = future generation minus future house consumption
    const netSolarComingKWh = Math.max(0, (solarRemainingFC || 0) - (loadRemainingFC || 0));
    const trueSurplusKWh = Math.max(0, netSolarComingKWh - emptySpaceKWh);

    // B. Strategy Selection
    if (soc >= BATT_FULL_LEVEL) {
        // STATE: FULL - Prevent solar waste
        targetLimit = -1;
        statusReport = `FULL (${soc}%): Exporting solar excess. Price: ${currentPrice}€`;
    } 
    else if (trueSurplusKWh > 0.05 && isPeakWindow && soc > MIN_RESERVE_SOC) {
        // STATE: PEAK ARBITRAGE
        // Tapered export: Clear surplus over 2h to avoid "cycling" stress
        let dynamicWatts = (trueSurplusKWh / 2) * 1000;
        targetLimit = Math.max(MAX_EXPORT_LIMIT, -Math.round(dynamicWatts));
        statusReport = `PEAK SELL: ${trueSurplusKWh.toFixed(1)}kWh surplus @ ${currentPrice}€`;
    } 
    else {
        // STATE: HOARDING/SAVING
        targetLimit = 0;
        statusReport = (trueSurplusKWh > 0) 
            ? `HOARDING: Waiting for Peak (${maxPriceToday}€). Surplus: ${trueSurplusKWh.toFixed(1)}kWh.` 
            : `STORING: Preserving buffer for 48h safety.`;
    }
}

// --- 5. EXECUTION ---
// Only update the GX device if the value actually changes
if (targetLimit !== currentLimit) {
    msg.payload = targetLimit;
    msg.statusMessage = statusReport;
    return msg;
}

return null;
1 Like

I share much of your concern… Storage should get more priority in the DESS scenario.

esp outside of the summer season, my solar is on many days not sufficient to satisfy the daily need. This let the system to constantly remain at its bottom % level…

Instead I would like it to behave in a way that at the end of the solar day I’m more or less ending with a 100%. If there is too much solar forecasted, I’m okay that it sells at the best price.

Likewise, in the winter times, when solar is insufficient, I want to have it buy at the most economical times, again being close to 100%

Is your flow already working proper?
Can you share the real nodered flow (it is not so clear what are your in & output nodes that provide the info and trigger the charging/discharging)