Help with the Maths/Javascript on this one please

Guys,

I have my 3 inverters feeding me information - one part of which is state of charge (SOC) of the batteries that are attached to each unit.

These will slowly creep out of sync over time - i would like a way dynamically vary the SOC to try and balance them all up

So lets say

  1. AvailableSolar = 3000w
  2. Number of inverters online = 3
  3. SOC1 = 30, SOC2 = 34, SOC3 = 38

At the moment i am taking the AvailableSolar and dividing it by the number of inverters online to give me a charging rate that is then converted to a percentage for each inverter - they are all given the same rate - so 1000w per inverter translates to 20%

What i would like to do is (as a first crude measurement)

Find the inverter with the highest SOC
Find the inverter with the lowest SOC
Take 20% of the charge away from the inverter with the highest SOC and give that to the lowest inverter

So my function at the moment spits out (every 10 seconds) a single number - lets call it 20 in this case - which is telling each inverter to charge at 20% rate

So i would like to take the Highest SOC inverter and take 4% to to take it to 16 and add that 4% to the lowest to take it to 24%

So my question is how do i with the 3 inverters identify which one has the highest and lowest SOC

I thought i could read the global variables into function variables such as below and then put them into an array -this is not working - but surely there must be a simpler way ?

let sbpleftsoc = global.get("Battery.SBP-Left-SOC");
let sbpmiddlesoc = global.get("Battery.SBP-Middle-SOC");
let sbprightsoc = global.get("Battery.SBP-Right-SOC");

let inverterssoc = [sbpleftsoc, sbpmiddlesoc, sbprightsoc]; // Get max value of an array in Javascript
node.warn("inverterssoc = " + inverterssoc);
msg.payload = Math.max.apply(null, inverterssoc);

Craig

Sorry a screenshot of the current Global variables as well

image

Craig

I have looked at the Sort node and can not see how that would work here - whilst still enabling me to identify which inverter is which

Craig

Craig

try

let sbpleftsoc = global.get("Battery.SBP-Left.SOC");

etc. Note dot rather than dash between Left and SOC

also

msg.payload = Math.max(...inverterssoc)

will also work

to find out WHICH inverter is max then use

maxInverter = inverterssoc.indexOf(msg.payload)

0 = left
1 = middle
2 = right

You can sort array to [0] would be lowest and [2] would be highest
eg.

[{"id":"fb3c9a6d.75065","type":"inject","z":"b779de97.b1b46","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"SOC1\": 1, \"SOC2\": 1, \"SOC3\": 90}","payloadType":"json","x":270,"y":2040,"wires":[["b30e7aa1.428f38"]]},{"id":"b30e7aa1.428f38","type":"function","z":"b779de97.b1b46","name":"","func":"let socs = msg.payload; // for testing uncomment below for input from context\n/*let socs = {\"soc1\": global.get(\"Battery.SBP-Left.SOC\") || 0,\n            \"soc2\": global.get(\"Battery.SBP-Middle.SOC\") || 0,\n            \"soc3\": global.get(\"Battery.SBP-Right.SOC\") || 0};*/\nlet target = 20;\nlet change = 4;\nlet socs_sorted = Object.values(socs).sort((a,b) => a-b) // lowest [0] highest [2]\nfor(const key in socs){\n    //node.warn(offset);\n    if (socs[key] === socs_sorted[2]){\n        socs[key] = target - change * socs_sorted.filter((e)=>socs_sorted[0]===e).length;\n    }else if (socs[key] === socs_sorted[0]){\n        socs[key] = target + change * socs_sorted.filter((e)=>socs_sorted[2]===e).length;\n    }else{\n        socs[key] = target;\n    }\n}\nmsg.payload = socs;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":450,"y":2220,"wires":[["f44c4a9.93ec0b8"]]},{"id":"739bacd9.656854","type":"inject","z":"b779de97.b1b46","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"SOC1\": 3, \"SOC2\": 30, \"SOC3\": 88}","payloadType":"json","x":180,"y":2120,"wires":[["b30e7aa1.428f38"]]},{"id":"efecca6.bce9938","type":"inject","z":"b779de97.b1b46","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"SOC1\": 10, \"SOC2\": 20, \"SOC3\": 30}","payloadType":"json","x":270,"y":2200,"wires":[["b30e7aa1.428f38"]]},{"id":"f44c4a9.93ec0b8","type":"debug","z":"b779de97.b1b46","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":680,"y":2160,"wires":[]}]
let socs = msg.payload; // for testing uncomment below for input from context
/*let socs = {"soc1": global.get("Battery.SBP-Left.SOC") || 0,
            "soc2": global.get("Battery.SBP-Middle.SOC") || 0,
            "soc3": global.get("Battery.SBP-Right.SOC") || 0};*/
let target = 20;
let change = 4;
let socs_sorted = Object.values(socs).sort((a,b) => a-b) // lowest [0] highest [2]
for(const key in socs){
    //node.warn(offset);
    if (socs[key] === socs_sorted[2]){
        socs[key] = target - change * socs_sorted.filter((e)=>socs_sorted[0]===e).length;
    }else if (socs[key] === socs_sorted[0]){
        socs[key] = target + change * socs_sorted.filter((e)=>socs_sorted[2]===e).length;
    }else{
        socs[key] = target;
    }
}
msg.payload = socs;
return msg;

Thanks Buckskin - obviously too long looking at the keyboard today and not focusing properly.

Craig

OK let me play with this and try to understand what you are doing here !!

Thanks for taking so much time on this - will report back !

Craig

slight change to function. To account for rare situation that all 3 socs are the same value

let socs = msg.payload; // for testing uncomment below for input from context
/*let socs = {"soc1": global.get("Battery.SBP-Left.SOC") || 0,
            "soc2": global.get("Battery.SBP-Middle.SOC") || 0,
            "soc3": global.get("Battery.SBP-Right.SOC") || 0};*/
let target = 20;
let change = 4;
let socs_sorted = Object.values(socs).sort((a,b) => a-b) // lowest [0] highest [2]
let count0 = socs_sorted.filter((e)=>socs_sorted[0]===e).length;
let count2 = socs_sorted.filter((e)=>socs_sorted[2]===e).length;

for(const key in socs){
    if (count0 < 3 && socs[key] === socs_sorted[2]){
        socs[key] = target - change * count0;
    }else if (count2 < 3 && socs[key] === socs_sorted[0]){
        socs[key] = target + change * count2;
    }else{
        socs[key] = target;
    }
}
msg.payload = socs;
return msg;

Thanks so much for all the effort on this -i have to step through this and TRY to understand what it is doing.

Craig

After taking your hints for my stupidity and then your hint re Index of function i have gotten this far now

//output 1 = SBPLeft
//Output 2 = SBPMiddle
//Output 3 = SBPRight

let ProposedChargingLevel = msg.payload

//Read out Global Variables
let sbpleftsoc = global.get("Battery.SBP-Left.SOC");
let sbpmiddlesoc = global.get("Battery.SBP-Middle.SOC");
let sbprightsoc = global.get("Battery.SBP-Right.SOC");

//Set them up as an array
let InvertersSOC = [sbpleftsoc, sbpmiddlesoc, sbprightsoc]; // Get max value of an array in Javascript
node.warn("inverterssoc = " + InvertersSOC);

msg.MaxInverterSOC = Math.max(...InvertersSOC);
MaxInverter = InvertersSOC.indexOf(msg.MaxInverterSOC);
msg.MaxInverter = MaxInverter;


msg.MinInverterSOC = Math.min(...InvertersSOC);
MinInverter = InvertersSOC.indexOf(msg.MinInverterSOC);
msg.MinInverter = MinInverter;

if (msg.MaxInverterSOC - msg.MinInverterSOC > 1) {
    node.warn("Difference bigger than 1 in SOC");
    ProposedChargingLevelMinInverter = ProposedChargingLevel * 1.2;
    ProposedChargingLevelMaxInverter = ProposedChargingLevel * 0.8;
    
}

return msg;

I have setup the function node with 3 outputs so now just to work out how to push the correct values out of each port - 3AM time for bed and hit this tomorrow

Thanks for all your help and hints guys

Craig