Finally almost finished my project

Andy
Posts: 487
Joined: Sun Aug 29, 2021 12:16 pm

Re: Finally almost finished my project

#11

Post by Andy »

Solar Edge: This is placed on the AC OUT 2 of the Quattro since SolarEdge essentially said they didn't have a clue why it wasn't responding to raised frequencies to lower the power when in DG (diesel gen) mode. If there was a power cut and it was on the AC OUT 1 it would continue generating power even if the batteries were full leading to mutually assured destruction of most things as the voltage would push up.

AC Out 2 is dropped in islanding mode. I then have a changeover switch to reconnect all the dropped loads to AC Out 1 allowing me for example to make the most of the sunshine in the winter.

There is apparently a further relay in the Solaredge which allows signalling to shut down completely. I have yet to experiment with that but intend to get that running so that if the Quattro fails to drop the loads if its internal relay is broken then there is at least a backup solution.
Andy
Posts: 487
Joined: Sun Aug 29, 2021 12:16 pm

Re: Finally almost finished my project

#12

Post by Andy »

Logging to grafana to allow the SMA style view:

This is my node with the logic to do this. It was a PIA to get all the edge cases and I have some pretty screwy looking graphs at the start of testing. It seems pretty stable now. There is the odd glitch but that is due to the raw data that is occasionally messed up with zero's. I'll try and filter those out later.

Code: Select all

function log(message) {
    if (flow.get("smaFlag")) {
        node.warn(message)
    }
}
var payload =
{
    "solaredgepower": 5004,
    "BatteryPower": 6064,  //- discharge, positive charge
    "GridIn": 20,
    "acconsumption": 6310,
    "accoupled 1": 5004,
    "mpptspower": 7448.75,
    "vebus_power": -1286, //inverter -> acout/grid
    "dc_system": null
}

payload = msg.payload
payload.mpptspower = payload.mpptspowerL + payload.mpptspowerR


//Info i want
// Consumption = Grid + Battery + Solar
// Solar -> Feed+Battery+Grid
var solar = {
    "SolarToGrid": 0,
    "SolarToBattery": 0,
    "SolarToLoad": 0
}
var consumption = {
    "GridToBattery": 0,
    "GridToLoad": 0,
    "SolarToLoad": 0,
    "BatteryToLoad": 0
}

// grid = battery - solar + load
const inverter = {
    "inverter": payload.vebus_power,
    dischargingSolar: 0,
    dischargingBattery: 0,
    chargingFromGrid: 0,
    chargingFromSolar: 0
}

var pvSE= flow.get("solaredgepower")
var opvSE = flow.get("Osolaredgepower")

if (pvSE>5000 && opvSE)
    pvSE = opvSE
if (pvSE==0 && opvSE>100)
    pvSE=opvSE
flow.set("solaredgepower",0)
flow.set("Osolaredgepower",pvSE)

const input = {
    mpptpv: payload.mpptspowerL + payload.mpptspowerR,
    pvSE: pvSE,
    pv: pvSE + payload.mpptspower,
    battery: payload.BatteryPower,
    load: payload.acconsumption,
    grid: payload.GridIn,
    SOC: payload.SOC,
    inverter: inverter.inverter
}

/* 
solar = battery-grid+load
solar to battery = solar +grid-load
solar to grid = battery+load-solar
solar to load = solar -battery-load
*/
var output = {
    B2L: 0,
    B2G: 0,
    S2L: 0,
    S2G: 0,
    S2B: 0,
    S2I: 0,
    B2I: 0,
    G2L: 0,
    G2I: 0,
    G2B: 0
}
var B2L = 0
var B2G = 0
var S2L = 0
var S2G = 0
var S2B = 0
var S2I = 0
var B2I = 0
var G2L = 0
var G2I = 0
var G2B = 0
var sectionText = ""
log("start sma")

function calculateSMAValues(input) {
 
    const battery = input.battery
    const inverter = input.inverter
    const grid = input.grid
    const load = input.load
    const mpptpv = input.mpptpv
    const pvSE = input.pvSE


    if (inverter < 0) { // discharging
        sectionText = "inverter"
        B2I = (battery >= 0) ? 0.0 : (-battery)
        S2I = (battery >= 0) ? mpptpv - battery : mpptpv // If battery is charging then it is coming from mppt
        S2B = (battery > 0) ? battery : 0.0
        const ratio = S2I / (B2I + S2I)


        S2I = inverter * ratio  // If S2I -1000
        B2I = inverter - S2I //-1000--1000

        log("ratio :" + ratio)
        if (ratio > 1 || S2I > 16000) {
            var tmp = "ratio>1:" + ratio + "B2I" + B2I + "S2I" + S2I
            log(tmp)
            global.set("debug", tmp)

        }
        // log("B2i: "+B2I)
        //correct for losses through inverter
        // var ratio=-inverter/(S2I+B2I)
        // S2I=S2I
        // B2I = B2I 
        // S2B=S2B

        // log("grid:"+grid)

        if (grid == 0) {
            sectionText = sectionText + " da"
            log("da")
            S2L = pvSE - S2I
            B2L = -B2I
        } else if (grid > 0) {// grid coming in
            sectionText = sectionText + " db"
            log("db")
            log("pvse:" + pvSE + " s2i:" + S2I + " load:" + load + " grid:" + grid + " B2I:" + B2I)

            G2L = grid
            S2L = pvSE - S2I
            B2L = -B2I //load - grid - S2L //S2I+pvSE
            B2L = B2L < 0 ? 0 : B2L
            log("S2B:" + S2B + " G2L:" + G2L + " S2L:" + S2L + " b2l:" + B2L)
            if (S2B > 0 && G2L > 0) {
                if (S2B > G2L) {
                    sectionText = sectionText + " s2b>g2l"
                    log("s2b>g2l")
                    S2B = S2B - G2L
                    S2L = S2L + G2L
                    G2B = G2L
                    G2L = 0
                } else { //S2B<G2L
                    sectionText = sectionText + " s2b<g2l"
                    log("s2b<g2L")
                    
                    G2B = S2B
                    S2L = S2L + S2B
                    G2L = G2L - S2B
                    S2B = 0
                }
            }
        } else if (grid < 0) { // grid going out + discharging
            log("pvse:" + pvSE + " s2i:" + S2I + " load:" + load + " grid:" + grid + " B2I:" + B2I)
            if ((pvSE - S2I) <= load) {
                sectionText = sectionText + " dc"
                // (load = S2L+B2L)
                S2L = pvSE - S2I
                B2L = load - S2L
                B2G = -grid
            }
            else if ((pvSE - S2I) > load) {
                sectionText = sectionText + " dd"
                log("dd")
                S2L = load
                // S2G=pvSE+S2I-load //Causes fluctiations when sunny
                // pvSE + S2I

                S2G = pvSE - S2I - load
                if (S2G>-grid)
                    S2G = -grid

                B2G = -grid - S2G
                log("B2G" + B2G)
            }
        }
    } else if (inverter == 0) {
        if (pvSE >= load) {
            sectionText = sectionText + " ia"
            log("ia")
            S2L = load
            S2G = grid
        } else {
            sectionText = sectionText + " ib"
            log("ib")
            S2L = pvSE
            G2L = grid
        }
        S2B = mpptpv
    } else if (inverter > 0) //charging
    {
        if (pvSE >= load) {
            S2L = load
            if (grid > 0) { //Incoming grid
                sectionText = sectionText + " ca"
                log("ca")
                G2I = grid
                S2I = pvSE - load
            } else { //Exporting
                sectionText = sectionText + " cb"
                log("cb")
                S2G = -grid
                S2I = -grid - load + pvSE //grid-load+pvSE = inverter-pvSe+load
            }

            //s2l = 5001

        } else if (pvSE < load) {
            S2L = pvSE
            if (grid >= 0) {   //grid coming in.
                sectionText = sectionText + " cc"
                log("cc")
                G2L = load - pvSE
                G2I = grid - G2L
            }
        }

        const ratio = inverter / (G2I + S2I)
        G2I = G2I * ratio
        S2I = S2I * ratio
        // charging so whole load will be grid+S2L at this point
        var diff = load-S2L // guardanteed to be positive
        if (mpptpv+S2I>=diff) {
            S2L=load
            S2B=mpptpv+S2I-diff
            G2L=G2L-diff
            G2B=G2I+diff
        } else {
            S2L=S2L+mpptpv+S2I
            S2B=0
            G2L=G2L-mpptpv-S2I
            G2B=G2I+mpptpv+S2I
        }
        
        // Need S2L to be as big as possible. currently G2b is first.

    }
    output = {
        section: sectionText,
        B2L: B2L,
        B2G: B2G,
        S2L: S2L,
        S2G: S2G,
        S2B: S2B,
        S2I: S2I,
        B2I: B2I,
        G2L: G2L,
        G2I: G2I,
        G2B: G2B,
        load: load

    }

    return output

}

output = calculateSMAValues(input)

var error = false
var errorText=""
//Check for errors:
if (output.S2L + output.B2L + output.G2L != output.load) {
    error = true
    errorText = "load inputs greater than load"
}
if ((pvSE+input.mpptpv)> 16500 || output.S2B+output.S2G+output.S2L>16500){
    error=true 
    errorText="Excessive PV production"
}


if (error){
    // node.warn("Error")
    var errorsArray = flow.get("errors")
    const time = new Date(Date.now())
    const result = {
                error: errorText,
                time:time.toLocaleTimeString(), 
                input: input, 
                output: output
                }

    // node.warn(errorsArray)
    // node.warn(result)
    if (!errorsArray)
        errorsArray = [result]
    else
        errorsArray.push(result)
    
    flow.set("errors", errorsArray)
}
// zeros and nulls zero
function nz(value, name) {
    if (value < 0)
        log("WARNING negative value for. " + name + " " + value)
    value= value < 0 ? 0 : value
    if (value == 0)
        value=null
    return value
}

S2G = nz(output.S2G, "S2G")
S2B = nz(output.S2B, "S2B")
S2B = nz(output.S2B, "S2B")
S2L = nz(output.S2L, "S2L")
G2L = nz(output.G2L, "G2L")
B2L = nz(output.B2L, "B2L")
B2G = nz(output.B2G, "B2G")
G2B = nz(output.G2B, "G2B")

var load = nz(output.load)

var result = {
    solar2Grid: S2G,
    solar2Battery: S2B,
    solar2Load: S2L,
    battery2Load: B2L,
    "grid2Load": G2L,
    grid2Battery: G2B,
    battery2Grid: B2G,
    SOC : input.SOC,
    totalLoad: load
}
log ("sectiontext:"+sectionText)
msg.payload.pvse = pvSE
msg.payload.sectionText = sectionText
msg.powerRouting = result
msg.payload2 = msg.payload   // The raw values

msg.payload = [result, {
    device: "cerbo"
}];
msg.measurement = "SMAView";

log("end sma")
return msg
MrPablo
Posts: 267
Joined: Mon Oct 17, 2022 1:26 pm

Re: Finally almost finished my project

#13

Post by MrPablo »

Thanks for sharing all these details Andy, much appreciated!
I'll have a play with my node red logic tomorrow and test out implementing something very similar.
10x 405W JA Solar panels (4.05kWp) @ 5 degrees
3x 405W Longi panels (1.22kWp) @ 90 degrees
16.5kWh DIY LifePo4 battery
Solis inverter/charger
0.6kW Ripple WT
64kWh Kia E-Niro
Andy
Posts: 487
Joined: Sun Aug 29, 2021 12:16 pm

Re: Finally almost finished my project

#14

Post by Andy »

MrPablo wrote: Tue Mar 28, 2023 10:21 pm Thanks for sharing all these details Andy, much appreciated!
I'll have a play with my node red logic tomorrow and test out implementing something very similar.
There is obviously a lot more going on elsewhere but hopefully it's enough to get going. The key logic is all there for the charge rate.
MrPablo
Posts: 267
Joined: Mon Oct 17, 2022 1:26 pm

Re: Finally almost finished my project

#15

Post by MrPablo »

I've implemented my version of it by adapting the SQL query I currently use to spit out a load of data about solar forecast, likely load, estimated SOC, etc.
The required charge power will be returned each time the query is run, then a charge current cap based on required power / battery voltage will be sent to the inverter. I've set it to a min power of 1000w, with charge current being rounded up to the nearest 5A as required.
The good thing is that the charge current will be adapted if the solar forecast goes up or down, or house load changes, even mid-charge. A good hands-off solution!

Tonight will be the first true test, it'll be interesting to see how it performs.
10x 405W JA Solar panels (4.05kWp) @ 5 degrees
3x 405W Longi panels (1.22kWp) @ 90 degrees
16.5kWh DIY LifePo4 battery
Solis inverter/charger
0.6kW Ripple WT
64kWh Kia E-Niro
Andy
Posts: 487
Joined: Sun Aug 29, 2021 12:16 pm

Re: Finally almost finished my project

#16

Post by Andy »

MrPablo wrote: Wed Mar 29, 2023 3:06 pm I've implemented my version of it by adapting the SQL query I currently use to spit out a load of data about solar forecast, likely load, estimated SOC, etc.
The required charge power will be returned each time the query is run, then a charge current cap based on required power / battery voltage will be sent to the inverter. I've set it to a min power of 1000w, with charge current being rounded up to the nearest 5A as required.
The good thing is that the charge current will be adapted if the solar forecast goes up or down, or house load changes, even mid-charge. A good hands-off solution!

Tonight will be the first true test, it'll be interesting to see how it performs.
Cool, good luck. I'm still trying to work out my algorithm to not leave me short in the evening. I had to turn the house heating off this evening as my son came back from school and had a big bath which used rather more energy than I anticipated whilst regaining the cylinder heat.
sharpener
Posts: 381
Joined: Fri May 20, 2022 10:42 am

Re: Finally almost finished my project

#17

Post by sharpener »

Andy wrote: Wed Mar 29, 2023 9:56 pm
Cool, good luck. I'm still trying to work out my algorithm to not leave me short in the evening. I had to turn the house heating off this evening as my son came back from school and had a big bath which used rather more energy than I anticipated whilst regaining the cylinder heat.
Victron has what they call a BatteryLife feature which will seek to ensure the battery is charged to 100% every day.

It would be simple for them to adapt this so rather than limiting the depth of discharge they pre-charge at off-peak rates to a threshold which depends on the history i.e. if you run out before midnight it charges a bit more in the following off-peak period. That wouldn't even need a weather forecast feed. They talk obliquely of updates to their ESS Assistant but so far there has been no detail. In the mean time I am settling on charging to 50% at night which leaves room for more the following day but is also enough to cover a dull day. Even in light rain the panels generate enough to run the standing loads in the house.

But I wouldn't want to spend my time using NodeRed to code something as complex as @MrPablo has and then find Victron release something similar!
16 x 230W Upsolar panels S Devon, 4kW Steca, 3.9 MWh FITs/yr
8 x 405W Longi panels, 250/60 MPPT, 3.3 MWh/yr
Victron MultiPlus II-GX 48/5000/70-50
10.65 kWh Pylontec Force-L2
zappi 7kW EVCS
Villavent whole-house MVHR
5000l rainwater system
Vaillant 12kW HP
Andy
Posts: 487
Joined: Sun Aug 29, 2021 12:16 pm

Re: Finally almost finished my project

#18

Post by Andy »

sharpener wrote: Sun Apr 02, 2023 7:59 pm

Victron has what they call a BatteryLife feature which will seek to ensure the battery is charged to 100% every day.
I've tried this and unfortunately it just doesn't work for me. Historical weather is not a good indicator of future weather. If you have a few bad days it starts ramping up the lower limit and then you end up with too much in the battery on the next sunny day. Also you can end up charging and discharging the same energy in the cheap periods which costs more. It will also only charge at full charge rate.

My system has a two day look ahead and will keep the battery SOC high until the weather is forecast to be good. If so it then it ramps the SOC down through the expensive periods. If it charges fully from the sunshine and expects the same the next day it will also use the stored energy in the cheap periods.

I have actually posted the 'dynamic charge rate' to Victron as an idea to add to the ESS but not had any feedback on it.
User avatar
SafetyThird
Posts: 199
Joined: Wed Mar 23, 2022 11:32 am
Location: North Devon

Re: Finally almost finished my project

#19

Post by SafetyThird »

Andy that's a cracking system you have. Since installing my batteries last year I've been using Home Assistant and Solcast to look at predicted PV generation but have not yet got to figuring out how to get that to work with expected charging needs and overnight cheap rate charging. Currently I just set the battery pack to charge up during the cheap rate hours but that doesn't take account of what the coming day's solar might look like. Haven't had time to work on that but it's something for me to dig into later in the year. I'm sure I'll be asking questions of you at some point :)
6kw PV (24 x REC Solar AS REC 250PE)
Clausius 5-25kw GSHP
Luxpower Squirrel Pod
Pylontech 21kwh
Eddi Diverter
250l hot water tank with 2 immersions
2 x Woodwarm stoves
7 acres of old coppice woodland
Ripple Kirk Hill 3.8kw
Ripple Derril Water 3.963 kW
Andy
Posts: 487
Joined: Sun Aug 29, 2021 12:16 pm

Re: Finally almost finished my project

#20

Post by Andy »

SafetyThird wrote: Wed Apr 05, 2023 10:47 am Haven't had time to work on that but it's something for me to dig into later in the year. I'm sure I'll be asking questions of you at some point :)
It's certainly a tricky one and I am not expecting perfection. The hardest period is in the shoulder months when a lot of sunshine is forecast first thing and then it doesn't materialise. The winter is pretty much charge to max and forget. Likewise in the summer the amount of power generated and lack of heating requirement mean that a failure in predictions won't be too costly. I've been looking ahead to see the maximum forecast battery in the day as well and then keeping some of that difference in the morning. Then if it all goes totally wrong I can turn off the heating until the next cheap rate :P. At least during a working week I can as I would get too many complaints at the weekend.
Post Reply