Finally almost finished my project

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

Finally almost finished my project

#1

Post by Andy »

It's taken years to get to this point but I've finally almost finished my project. Thanks to Joe for putting me in touch with the installer up north or it would never have happened. There have been a lot of ups and downs with the remortgage taking too long and then the first installer leaving me in the lurch and then an accident on delivery. I've probably ended up a year behind schedule and lost a huge amount of potential savings as the money has been sitting in the bank doing nothing apart from accumulating interest.

I originally had 6.42kW of ground mount panels hooked up to a SolarEdge HD5000 and now have added a further 12kW of panels attached to two MPPT 450/100 trackers combined with 8 Pylontech US5000 (38.4 kWh). The additional inverter/charger is a Victron Quattro 10000. The batteries can be charged from empty to full in about 3 hours at full production with most of the charging happening quite efficiently using the DC generation. The setup is similar to this Image. It gives us islanding mode which is essential to allow us to run our water supply to water the animals. Carrying 100s of liters of water from a near by stream for 8 days last year was not fun.

What I love about the Victron kit is how accessible it is. I am running a node-red instance on the cerboGX which is controlling the state of charge of the batteries and what the Quattro is doing depending on the forecast solar and heating load of the house. It aims to time shift the cheap rate electricity whilst making sure the batteries are empty to accept sunshine. I am downloading the predicted PV generation in 30 minute chunks from Solcast. The weather is downloaded from open weather map and that is used to forecast the heat pump heating demand. This is then used to predict the SOC down the line and make decisions based on that. I try to avoid using the battery in the cheap rate period if it will only have to be charged again in one of the future cheap rate periods.

I've also heavily modified the Victron ESS to make it a bit smarter. For instance when charging during the cheap periods using the built in schedule it will charge at 7700kW to hit the fixed SOC. This results in almost 15% of wasted energy. My code will target the minimum required SOC to get me through the next expensive period and then charge at the minimum rate to achieve that SOC during a cheap period. This can result in a 10% saving which certainly adds up over the year. There are numerous other small logic improvements which over 20 years will save £500 here and there :P

I've created a custom logging interface which is a hybrid of the SMA logging which I quite liked. It is all the charging/discharging/solar production and consumption+SOC data in one graph which works quite well. The node-red code does the necessary calculations to work out what is going where. This is logged in an influx DB. Grafana is then used to view the data. These are running in docker containers on a synology.

I've added a screen shot of yesterday where I was hoping to hit 100kWh for the first time this year. Some midday cloud spoilt that. I achieved Several saved up loads of washing, dishwasher, hoovering, House heating at -2->5ºC and 27kWh into the MG5, charging 450liters of hot water + charging the battery to get me through later in the day.

Image

The second/thrid image show a bit of time shifting (battery charging is the salmon colour).I've zoomed into the afternoon in the third image. This shows how useful it can be seeing the electricity usage. The little spikes in the latter stage are not an artefact but a leak in the pipe near the poly tunnel. It is causing the water pump in the borehole to come on at regular intervals. You can also see the self use of the battery, charging the battery from solar and grid and grid use for the load from 13:30 when the cheap rate starts.

Image

Image

The final image shows what I try not to do which is export as I don't get paid for it. That's a battle for another day to get the SEG despite not having an MCS certificate. I do have one for the SolarEdge so maybe I can just apply on that. I also have no signal in the area for a smart meter so that is another hurdle to overcome.

Image

There is daily logging of the money saved so I can see when I finally hit breakeven.(7 years ish). Deciding what is a saving is quite tricky. Previously we would try and do all washing/tumble drying etc in the cheap rate periods. But habits are already changing with the increased generation/timeshifting so savings are artificially boosted when I log use of the washing machine during an expensive period. I also have to download the myenergi history for the Zappie and Eddie to subtract them or it looks like a I am saving way more than I in fact am. We would never normally charge the car in the expensive period so I have to make sure that isn't being recorded as a £2.8/hr saving.

Image

Going forward I am looking to have 'plug and forget' charging of the car. Unfortunately the Zappie is quite limiting in that I can't control it beyond full charge rate or 1.3kW. There is no way to ask it to charge at a set rate which does often make it tricky to fit in the solar curve. Having the batteries charge first and then the car using exported power results in extra energy sent to the grid as the car will only take 7kW leaving 7kW or so going to the grid. It's better to have the batteries and car charging concurrently to make better use of the generation.
Last edited by Andy on Tue Mar 28, 2023 11:49 am, edited 1 time in total.
Andy
Posts: 487
Joined: Sun Aug 29, 2021 12:16 pm

Re: Finally almost finished my project

#2

Post by Andy »

The new panels are at 20º pitch and the existing panels are primarily 30º with the bottom row at 45º. I thought it would be interesting to plot the ratio of power out put per area between the two sets of panels.

This is from the sunny day from the plot in the previous post. In the morning the steeper panels perform better and then it transitions to the ones on the roof. I expect this to become more exaggerated as we enter summer. The panels on the ground mount also defrosted faster than those on the barn roof.
Image

On a cloudy day the flatter panels on the roof perform better as expected.
Image
Last edited by Andy on Tue Mar 28, 2023 11:54 am, edited 1 time in total.
NikoV6
Posts: 217
Joined: Thu Aug 11, 2022 6:42 am

Re: Finally almost finished my project

#3

Post by NikoV6 »

Hats off, that looks really good! Well done :idea:
14Kw Mitsubishi Ecodan ASHP
22 x JA Solar 455W Mono Perc Half Cell Silvers
Solis 8.0Kw 5G Dual MPPT Inverter
13.5Kw Tesla Powerwall
Zappi 2 Charger
iBoost
User avatar
Fintray
Posts: 1443
Joined: Mon May 31, 2021 6:37 pm
Location: Aberdeenshire

Re: Finally almost finished my project

#4

Post by Fintray »

That looks like a great system Andy, when are your open days for the tours? :D
3.87kWp PV
10.24kWp PV SolarEdge system
Tesla Powerwall 2
100 x 47mm Navitron tubes (still being installed!) Now likely to be removed for more PV.
MK2 PV router DHW diverter
Morso 5kW WBS
Vaillant AroTherm 10kW ASHP
Nissan Leaf
Andy
Posts: 487
Joined: Sun Aug 29, 2021 12:16 pm

Re: Finally almost finished my project

#5

Post by Andy »

Fintray wrote: Tue Mar 28, 2023 10:51 am That looks like a great system Andy, when are your open days for the tours? :D
Hehe, I've got to target geeks only. Most people visibly glaze over with this kind of stuff including my wife.
ALAN/ALAN D

Re: Finally almost finished my project

#6

Post by ALAN/ALAN D »

I was a bit concerned about the shading issues on the P.V. panels.
Strange way to mount them. :roll: :( :o :mrgreen:
Andy
Posts: 487
Joined: Sun Aug 29, 2021 12:16 pm

Re: Finally almost finished my project

#7

Post by Andy »

ALAN/ALAN D wrote: Tue Mar 28, 2023 11:47 am I was a bit concerned about the shading issues on the P.V. panels.
Strange way to mount them. :roll: :( :o :mrgreen:
haha, corrected. Yes, its amazing what you can fit on a Solaredge unit if you put your mind to it.
MrPablo
Posts: 267
Joined: Mon Oct 17, 2022 1:26 pm

Re: Finally almost finished my project

#8

Post by MrPablo »

A really nice idea to automatically define the maximum current when charging the battery from the grid, I hadn't truly considered the effect of efficiency.

Do you have a minimum charge current in the logic to get around the poor efficiency at very low power levels?
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

#9

Post by Andy »

MrPablo wrote: Tue Mar 28, 2023 8:56 pm Do you have a minimum charge current in the logic to get around the poor efficiency at very low power levels?
I forgot to mention that. Yes, I have it set at 1000W. I haven't been able to find graphs for my particular model but someone did the work for the multiplus 5000. I'm not sure the graph scales exactly for the larger inverter so I picked 1000 as a seemingly reasonable starter for ten.

At the moment it will start at the beginning of the charge period and end early. I'm planning on working out the start time using a charge rate of 1000 so that it finishes at the end. This will give the panels an opportunity to charge instead of the grid. My expensive rate is so high that it is imperative that the expensive periods are covered by the battery at the moment.

At the moment I am day: 46.639p and night 13.330p. Unfortunately this is changing to 39.856p and 20.635p in April so the savings to be had are not so great. I'm hoping I will only need the grid for a few days a month. This year hasn't been a great start with the worst quarter I have recorded so far.
Last edited by Andy on Tue Mar 28, 2023 9:24 pm, edited 1 time in total.
Andy
Posts: 487
Joined: Sun Aug 29, 2021 12:16 pm

Re: Finally almost finished my project

#10

Post by Andy »

The code from the 'charge calculations' node is below. I've only just started using javascript so there are probably better ways of doing things. It basically runs this cycle every 5 seconds determining what state is is in and then sets up the Victron to achieve the aim. Empty battery is something I am experimenting with to dump all the power into the car just before the sun comes up in order to maximise the amount of battery I can charge. It is based on the latest forecast just before sunrise so that the house isn't left short of power.

Code: Select all

const maxPowerHouseIn=230*60

function log(text) {
    // text = text
    // node.warn(text)
}

function efficiencyAt(power) {
    power = power / 1000
    const a = (power * .1) / 6    
    const eff = 0.9366 - a   //.966 was the original number
    return eff
}

class ChargeState {
    static HoldCharge = new ChargeState('HoldCharge');
    static UseBattery = new ChargeState('UseBattery');
    static EmptyBattey = new ChargeState('EmptyBattery')
    static ExpensivePeriod = new ChargeState('ExpensivePeriod')
    static ChargeBattery = new ChargeState('ChargeBattery');
    constructor(name) {
        this.name = name;
    }
    toString() {
        return `Color.${this.name}`;
    }
}

var debug={};
var msg2={};   
var msg3={};

const battery=global.get("state_Battery");
const SOC = msg.payload["BatterySOC"];
const batteryPower = msg.payload["BatteryPower"];
const setpoint=msg.payload["ESS_Setpoint"];
const mppt=msg.payload["PV_MPPT"] || 0;

// const PV_Power = msg.payload["PV_SE"] + mppt;
const PV_Power = flow.get("PV_SE") + mppt;
const consumption = msg.payload["consumption"];
var currentSchedule = global.get("CurrentSchedule");
const SOCInfo=flow.get("SOCInfo")
var maxInverterPower = 0;
var newSetpoint = 20;
var nodeWarn="green";

debug["Default"]={"battery":battery,"SOC":SOC,"setpoint":setpoint};

function getChargeStateInCheapPeriod() {
    var state =0
    
    var desiredSOC
    if (currentSchedule.override)
        desiredSOC=currentSchedule.SOC
    else 
        desiredSOC = SOCInfo.nextSOC // THis is enough to get through the period

    const notBelowSOC = SOCInfo.notBelowDuringCharge

    log("SOC:"+SOC+"notbel"+notBelowSOC+"desired"+desiredSOC)
    // if(SOCInfo.maxSOCToday==100)
    // {
    //     state = ChargeState.EmptyBattey
    // }else
     if ((SOC > notBelowSOC || SOCInfo.maxSOCToday==100) && SOC >desiredSOC && SOCInfo.correction==0)
        state = ChargeState.UseBattery
    else if (SOC >= desiredSOC)
        state = ChargeState.HoldCharge
    else
        state = ChargeState.ChargeBattery


    return state
}

//New charge state c
var state = ChargeState.ChargeBattery

if (currentSchedule.cheapPeriod){
    state=getChargeStateInCheapPeriod();
} else {
    state= ChargeState.ExpensivePeriod
}

// This will aim to hit the SOC at the end of the period
function getChargeRateToMeetSOC(desiredSOC){
    // Do the math to find the new charge rate
    var now = new Date();
    var hoursNow = now.getHours();
    var minutesNow = now.getMinutes();

    var startTimeStr = currentSchedule["start"];
    var startT = startTimeStr.split(":");

    var startHour = startT[0];
    var startMinute = startT[1];

    var startTime = new Date();
    startTime.setHours(startHour)
    startTime.setMinutes(startMinute);

    var timeR = currentSchedule["length"].split(":");
    var diff = timeR[0] * 60 + timeR[1] * 1;

    var endTime = new Date(startTime.valueOf() + diff * 60000);

    //Correct for times running over midnight
    if (endTime.valueOf() < now.valueOf()) {
        var etv = endTime.valueOf() - 24 * 60 * 60 * 1000;
        endTime = new Date(etv);
    }

    var remainingTime = (endTime.valueOf() - now.valueOf()) / 60000;
    var remainingSOC = desiredSOC - SOC;
    var powerRemaining = remainingSOC / 100 * 4800 * 8;
    var powerPerHour = Math.round(powerRemaining / remainingTime * 60);
    // How much needs to go into the battery.
    return powerPerHour
}

// Meets the battery charge discharge based on grid only.
function setPointToMeetBattery(chargeRate){
// node.warn(PV_Power)
    if (chargeRate>0) {
        chargeRate = chargeRate / efficiencyAt(chargeRate)
    } else {
        chargeRate = chargeRate / efficiencyAt(chargeRate)
    }

    // If this isn't added then the battery will discharge at about 100W
    // This currently causes oscillations.. needs damping
    if (chargeRate==0){
        const battery = global.get("state_Battery")
        chargeRate=-battery.power*.7
    }

    // new set point
    // Problem is the mppt... In the morning it is the only power and causes issues
    const setPoint= consumption - PV_Power + chargeRate;
    return setPoint
}

function clampValue(value,min,max){
   
    if (value <min)
        value = min
    else if (value>max)
        value = max

    return value
}

var powerPerHour=0
var chargerOn=false

function isSunny(){
    return msg.payload.PV_State != 0
}

log("state"+state.name)
//If we are not in a charge schedule then return 20 which will be the default state to use the battery
switch (state.name) {
    case ChargeState.ExpensivePeriod.name:
    {
        log ("expensive charge: "+0)
        if (isSunny())
            chargerOn = true  // This is required if pv is greater than load
         else 
            chargerOn= false // It is dark so we won't be charging
         
        maxInverterPower = -1
        newSetpoint = 0
        log("max inverter"+ maxInverterPower)
        break
    }
    case ChargeState.EmptyBattey.name:
    {
        maxInverterPower=10000
        // var chargeRate= getChargeRateToMeetSOC(SOCInfo.nextSOC)
        newSetpoint=setPointToMeetBattery(-maxInverterPower)
        chargerOn=false
        break
    }
    case ChargeState.UseBattery.name:
    {
            log("usebattery"+ 0)
        if (isSunny())
        {
            maxInverterPower = -1 // Without this the load won't be powered by mppt
            chargerOn = true // The battery won't be powered by excess sunlight without this.
        }else
        {
            var chargeRate = getChargeRateToMeetSOC(SOCInfo.notBelowDuringCharge)
            maxInverterPower =-1// clampValue(chargeRate, 1000, 9000) // Limit lower level to 1000 since it gets very inefficient below this level.
            chargerOn=false
        }
       
        newSetpoint = 0
        break
    }
    case ChargeState.HoldCharge.name:
    {
        chargerOn = msg.payload.PV_State > 0  // Charge if there is PV
       
       // We have a minimum value of 0 so that we are not exporting. This doesn't work. 
       if(msg.payload.PV_State!=0 && mppt>500)  //if day
            maxInverterPower=-1  // Do this or we won't export any extra power.
        else
            maxInverterPower=0 // Night time freeze the inverter
        
        if (SOC>98)
        newSetpoint=0
        else {
            //was clamped to 9000 for inverter. this is worng
            newSetpoint=clampValue(setPointToMeetBattery(0),0,maxPowerHouseIn) 
        }
        break
    }
    case ChargeState.ChargeBattery.name:
    {
        chargerOn=true
        maxInverterPower = -1 // depends on sunshine and loads

        var nextSOC=SOCInfo.nextSOC

        if (currentSchedule.override){
            nextSOC=currentSchedule.SOC
            nodeWarn="blue"
        }

        var chargeRate = getChargeRateToMeetSOC(nextSOC)
        //set the minimum level to 1000 if no solar.
        var minimumChargeRate
        if (!isSunny()){
            minimumChargeRate=1000
        } else {
            minimumChargeRate=0
        }
        chargeRate = clampValue(chargeRate, minimumChargeRate, 7300) // Limit lower level to 1000 since it gets very inefficient below this level.
        powerPerHour = chargeRate // set this value for the node status
        chargeRate=chargeRate
        newSetpoint=clampValue(setPointToMeetBattery(chargeRate),0,12000) // Be nice to the house input and make sure we don't export

        break
    }
}

const chargeZappie = false
if (chargeZappie) {
        /*
        grid = load-solar-battery
        battery=load-solar-grid

        -200=load-solar-4000
        load-solar=-4000
        */
        // var sum = load-solar
        const flag=context.get("flag")
        context.set("flag",!flag)
        var psp=context.get("lastpoint")

        // if (flag){
            const batteryCorrection=4400-batteryPower
            log("battery" + batteryPower+ " correction:"+batteryCorrection)
            newSetpoint= batteryCorrection*.5
        // } else {
            // newSetpoint=psp
        // }

        var dif=newSetpoint-psp
        log("nsp:" + newSetpoint + " dif:" + dif)
        newSetpoint=psp+.1*dif

        if(newSetpoint>0)
            newSetpoint=0


        context.set("lastpoint",newSetpoint)
}
newSetpoint = Math.round(newSetpoint);
msg.debug = debug;
msg.payload = newSetpoint;
msg2.payload = currentSchedule["SOC"];

/*Value types

0 - Charge allowed
1 - Charge disabled
*/
log("chargeron:"+chargerOn)
msg2.payload = chargerOn ? 0:1
// msg2.payload=0
msg3.payload = maxInverterPower
log("max inverter"+maxInverterPower)
const text = state.name + " SOC:" + SOCInfo.nextSOC +  "PowerRq: "+powerPerHour+"W,Set: "+newSetpoint
node.status({fill:nodeWarn,shape:"dot",text: text});


// set point, min soc, inverter level.
return [msg,msg2,msg3]
Post Reply