I see you, and I raise you configurability per month of the year, and adjustments to minimum SoC based on the time of the day relative to sunrise and sunset.
Here’s the JavaScript for the “Calculate Minimum SoC” function node:
//Calculates the minimum SoC ESS setting. The strategy is to set a low minimum SoC just
//before sunrise, ramping it up to a higher minimum SoC just before sunset to leave enough
//margin for overnight power failures.
//-----------Configuration start-------------
//The daytime minimum SoC for the first of each month
const dayTimeMinimumSoCByMonth = [
20, //1 jan
20, //1 feb
20, //1 mar
20, //1 apr
20, //1 may
25, //1 jun
35, //1 jul
25, //1 aug
20, //1 sep
20, //1 oct
20, //1 nov
20 //1 dec
];
//The nighttime minimum SoC for the first of each month
const nightTimeMinimumSoCByMonth = [
40, //1 jan
40, //1 feb
40, //1 mar
40, //1 apr
40, //1 may
45, //1 jun
55, //1 jul
45, //1 aug
40, //1 sep
40, //1 oct
40, //1 nov
40 //1 dec
];
//The number of hours before sunrise from which the day time SoC applies
const sunriseMarginHours = 3;
//The maximum number of hours over which the minimum SoC is ramped up before sunset
const rampUpHours = 4;
//The number of hours before sunset by which the ramp-up must be finished
const sunsetMarginHours = 2;
//The desired margin between the current SoC and the minimum SoC to allow for some
//discharge during the ramp-up if the ramp-up can be done faster than rampUpHours
const dischargeMargin = 0;
//-----------Configuration end-------------
//The SoC setting interval in %
const minimumSoCInterval = 5;
//The sunrise hour on the first of each month
const sunriseHourByMonth = [
5.60, //1 jan
6.08, //1 feb
6.52, //1 mar
6.93, //1 apr
7.32, //1 may
7.68, //1 jun
7.85, //1 jul
7.62, //1 aug
7.05, //1 sep
6.35, //1 oct
5.73, //1 nov
5.43 //1 dec
];
//The sunset hour on the first of each month
const sunsetHourByMonth = [
19.98, //1 jan
19.83, //1 feb
19.35, //1 mar
18.65, //1 apr
18.05, //1 may
17.72, //1 jun
17.77, //1 jul
18.07, //1 aug
18.43, //1 sep
18.78, //1 oct
19.22, //1 nov
19.68 //1 dec
];
//The payload is the current SoC
currentSoC = msg.payload;
var currentDate = new Date();
var currentYear = currentDate.getFullYear();
var currentMonth = currentDate.getMonth(); //month is 0-based
var currentDay = currentDate.getDate(); //day is 1-based
var currentHour = currentDate.getHours() + currentDate.getMinutes() / 60;
var nextMonth = (currentMonth + 1) % 12; //month is 0-based
//day 0 is the last day of the previous month
var daysInCurrentMonth = new Date(currentYear, nextMonth, 0).getDate();
//perform a straight line interpolation of the sunrise and sunset times between months
var thisMonthFraction = (daysInCurrentMonth - currentDay + 1) / daysInCurrentMonth;
var nextMonthFraction = 1 - thisMonthFraction;
var sunriseHour = sunriseHourByMonth[currentMonth] * thisMonthFraction
+ sunriseHourByMonth[nextMonth] * nextMonthFraction;
var sunsetHour = sunsetHourByMonth[currentMonth] * thisMonthFraction
+ sunsetHourByMonth[nextMonth] * nextMonthFraction;
//The minimum SoC values during the day and night
var dayTimeMinimumSoC = dayTimeMinimumSoCByMonth[currentMonth] * thisMonthFraction
+ dayTimeMinimumSoCByMonth[nextMonth] * nextMonthFraction;
var nightTimeMinimumSoC = nightTimeMinimumSoCByMonth[currentMonth] * thisMonthFraction
+ nightTimeMinimumSoCByMonth[nextMonth] * nextMonthFraction;
//Round the minimum SoC values to the nearest interval
dayTimeMinimumSoC = Math.round(dayTimeMinimumSoC / minimumSoCInterval) * minimumSoCInterval;
nightTimeMinimumSoC = Math.round(nightTimeMinimumSoC / minimumSoCInterval) * minimumSoCInterval;
//The hour just before sunrise when the low minimum SoC is set
var daytimeSoCStartHour = sunriseHour - sunriseMarginHours;
//The hour when ramping up of the minimum SoC starts
var minimumSoCRampStartHour = sunsetHour - sunsetMarginHours - rampUpHours;
//Default to the night time minimum SoC
var minimumSoC = nightTimeMinimumSoC;
//Is the current hour just before sunrise?
if (currentHour >= daytimeSoCStartHour)
{
//Before the ramp-up time?
if (currentHour < minimumSoCRampStartHour)
{
//Before the ramp-up, so use the daytime minimum SoC
minimumSoC = dayTimeMinimumSoC;
}
else
{
//After the start of ramp-up to the nighttime minimum SoC
//Calculate the ramp-up fraction
var rampFraction = (currentHour - minimumSoCRampStartHour) / rampUpHours;
//Calculate the ramped up minimum SoC
minimumSoC = dayTimeMinimumSoC + (nightTimeMinimumSoC - dayTimeMinimumSoC) * rampFraction;
//If the current SoC already exceeds the calculated minimum, use the current SoC
//with a margin to allow for some discharge during the ramp-up
minimumSoC = Math.max(minimumSoC, currentSoC - dischargeMargin);
//Cap the calculated value at the nighttime minimum SoC
minimumSoC = Math.min(minimumSoC, nightTimeMinimumSoC);
}
}
//Round the minimum SoC down to the nearest multiple of 5%
minimumSoC = Math.floor(minimumSoC / minimumSoCInterval) * minimumSoCInterval;
msg.debugInfo = [
"fraction of month passed = " + nextMonthFraction,
"sunrise hour = " + sunriseHour,
"sunset hour = " + sunsetHour,
dayTimeMinimumSoC + "% SoC from hour " + daytimeSoCStartHour,
"SoC ramp-up to " + nightTimeMinimumSoC + "% from hour " + minimumSoCRampStartHour
+ " to hour " + (sunsetHour - sunsetMarginHours),
"current hour = " + currentHour,
"current SoC = " + currentSoC,
"calculated minimum SoC = " + minimumSoC + "%"
];
msg.payload = minimumSoC;
return msg;
Note the sunrise and sunset times are for Somerset West.