Sai piirhind ka lisatud. Antud variandis on let max_price = 50 .
Kood: Vali kõik
// This script is calculating next day heating time based on weather forecast, 
// and turn on your heating system for cheapest hours based on electricity market price.
// It's scheduled to run daily after 23:00 to set heating timeslots for next day.
// by Leivo Sepp, 14.01.2023
// Energy Market price is downloaded from Elering API https://dashboard.elering.ee/assets/api-doc.html#/nps-controller/getPriceUsingGET. 
// Weather forecast is based on free Open-Meteo API https://open-meteo.com/en/docs
// Set the country Estonia-ee, Finland-fi, Lthuania-lt, Latvia-lv
// No other countries support exist trough Elering API. 
let country = "ee";
// Parameter heatingCurve is used to set proper heating curve for your house. This is very personal and also crucial component.
// You can start with the default number 5, and take a look how this works for you.
// If you feel cold, then increase this number. If you feel too warm, then decrease this number.
// You can see the dependency of temperature and and this parameter from this visualization: "link will be here..."
// Heating hours are calculated by this quadratic equation: (startingTemp-avgTemp)^2 + (heatingCurve / powerFactor) * (startingTemp-avgTemp)
let heatingCurve = 3;
// Parameter startingTemp is used as starting point for heating curve.
// For example if startingTemp = 10, then the heating is not turned on for any temperature warmer than 10 degrees.
let startingTemp = 18;
// powerFactor is used to set quadratic equation parabola curve flat or steep. Change it with your own responsibility.
let powerFactor = 0.2;
// This parameter used to set a number of heating hours in a day in case the weather forecast fails. Number 1-24. 
// If Shelly was able to get the forecast, then this number is owerwritten by the heating curve calculation.
// For example if this number is set to 5 then Shelly will be turned on for 5 most cheapest hours during a day. 
// If the cheapest hours are 02:00, 04:00, 07:00, 15:00 and 16:00, then the Shelly is turned on 02-03, 04-05, 07-08 and 15-17 (two hours in a row).
let heatingTime = 5;
// If getting electricity prices from Elering fails, then use this number as a time to start Shelly. 2 means 02:00. 
// For example if there is no internet connection at all, and heatingTime=5, default_start_time=1 then the Shelly is turned on from 01:00 to 06:00
let default_start_time = 1;
// Keep this is_reverse value "false", I think 99% of the situations are required so.
// Rarely some heating systems requires reversed relay. Put this "true" if you are sure that your appliance requires so.
// For example my personal ground source heat pump requires reversed management. If Shelly relay is activated (ON), then the pump is turned off.
let is_reverse = false;
// This is timezone for EE, LT, LV and FI.
// Do not change this because it won't work currently for other timezones.
let timezone = 2;
// Maximum price above which the work programme will not be drawn up.
let max_price = 50; 
// some global variables
let openMeteoUrl = "https://api.open-meteo.com/v1/forecast?daily=temperature_2m_max,temperature_2m_min&timezone=auto";
let eleringUrl = "https://dashboard.elering.ee/api/nps/price";
let data_indx;
let sorted = [];
let weatherDate;
let dateStart;
let dateEnd;
let lat = JSON.stringify(Shelly.getComponentConfig("sys").location.lat);
let lon = JSON.stringify(Shelly.getComponentConfig("sys").location.lon);
let shellyUnixtime = Shelly.getComponentStatus("sys").unixtime;
// Crontab for running this script. 
// This script is run at random moment during the first 15 minutes after 23:00
// Random timing is used so that all clients wouldn't be polling the server exactly at same time
let minrand = JSON.stringify(Math.floor(Math.random() * 15));
let secrand = JSON.stringify(Math.floor(Math.random() * 59));
let script_schedule = secrand + " " + minrand + " " + "23 * * SUN,MON,TUE,WED,THU,FRI,SAT";
// Number for this script. If this doesn't work (as in earlier versions), get it from this url (use your own ip) http://192.168.33.1/rpc/Script.List
// You can check the schedules here (use your own ip) http://192.168.33.1/rpc/Schedule.List
let script_number = Shelly.getCurrentScriptId();
// Let's start with Shelly GetStatus to get location, date and time
function getShellyStatus() {
    let addDays = -1; //yesterday, used in case the scipt started before 3PM and we don't have tomorrow prices
    let shellyHour = JSON.parse(unixTimeToHumanReadable(shellyUnixtime, timezone, addDays).slice(11, 13));
    // Only after 3PM this script can calculate schedule for tomorrow as the energy prices are not available before 3PM
    if (shellyHour >= 15) {
        addDays = 0
    }
    let shellyTime = unixTimeToHumanReadable(shellyUnixtime, timezone, addDays);
    let shellyTimePlus1 = unixTimeToHumanReadable(shellyUnixtime, timezone, addDays + 1);
    // Let's prepare proper date-time formats for Elering query
    dateStart = shellyTime.slice(0, 10) + "T22:00Z";
    dateEnd = shellyTimePlus1.slice(0, 10) + "T21:00Z";
    // Let's make proper date format of getting wether forecast
    weatherDate = shellyTimePlus1.slice(0, 10);
    // Let's call Open-Meteo weather forecast API to get tomorrow min and max temperatures
    print("Starting to fetch weather data for ", weatherDate, " from Open-Meteo.com for your location:", lat, lon, ".")
    Shelly.call("HTTP.GET", { url: openMeteoUrl + "&latitude=" + lat + "&longitude=" + lon + "&start_date=" + weatherDate + "&end_date=" + weatherDate }, function (response) {
        if (response === null || JSON.parse(response.body)["error"]) {
            print("Getting temperature failed. Using default heatingTime parameter and will turn on heating fot ", heatingTime, " hours.");
        }
        else {
            let json = JSON.parse(response.body);
            // This is very simple way for temperature forecast, just averaging tomorrow min and max temperatures :) 
            let avgTempForecast = (json["daily"]["temperature_2m_max"][0] + json["daily"]["temperature_2m_min"][0]) / 2;
            // the next line is basically the "smart quadratic equation" which calculates the hetaing hours based on the temperature
            heatingTime = ((startingTemp - avgTempForecast) * (startingTemp - avgTempForecast) + (heatingCurve / powerFactor) * (startingTemp - avgTempForecast)) / 100;
            heatingTime = Math.ceil(heatingTime);
            if (heatingTime > 24) { heatingTime = 24; }
            print("Temperture forecast tomorrow", weatherDate, " is ", avgTempForecast, " heating is turned on for ", heatingTime, " hours.");
        }
        find_cheapest();
    }
    );
}
// This is the main function to proceed with the price sorting etc.
function find_cheapest() {
    // Let's get the electricity market price from Elering
    print("Starting to fetch market prices from Elering from ", dateStart, " to ", dateEnd, ".");
    Shelly.call("HTTP.GET", { url: eleringUrl + "?start=" + dateStart + "&end=" + dateEnd }, function (result) {
        if (result === null) {
            // If there is no result, then use the default_start_time and heatingTime
            print("Fetching market prices failed. Adding one big timeslot.");
            setTimer(is_reverse, heatingTime);
            addSchedules(sorted, default_start_time, default_start_time + 1);
        }
        else {
            // Let's hope we got good JSON result and we can proceed normally
            // Example of good json
            // let json = "{success: true,data: {ee: [{timestamp: 1673301600,price: 80.5900},"+
            // "{timestamp: 1673305200,price: 76.0500},{timestamp: 1673308800,price: 79.9500}]}}";   
            print("We got market prices, going to sort them from cheapest to most expensive ...");
            let json = JSON.parse(result.body);
            let pricesArray = json["data"][country];
            // Sort prices from smallest to largest
            sorted = sort(pricesArray, "price");
            print("Cheapest daily price:", sorted[0].price, " ", unixTimeToHumanReadable(sorted[0].timestamp, 2, 0));
            print("Most expensive daily price", sorted[sorted.length - 1].price, " ", unixTimeToHumanReadable(sorted[sorted.length - 1].timestamp, 2, 0));
            // The fact is that Shelly RPC calls are limited to 5, one is used already for HTTP.GET and we have only 4 left.
            // These 4 RPC calls are used here. 
            if (heatingTime - 4 < 1) { data_indx = heatingTime; }
            else { data_indx = 4; }
            print("Starting to add hours 0-3");
            addSchedules(sorted, 0, data_indx);
            // This is the hack with the timers to add more RPC calls. We simply add a 4 second delay between the timer actions :) 
            // Timers are called four times and each timer has four RPC calls to set up alltogether maximum 20 schedules.
            // The Timers in Shelly script are limited also to 5, as one is used to stop the script itself we can call maximum 4 timers.
            // For some reason I couldn't make this code smarter as calling timers seems not working from for-loop which would be the normal solution.
            if (heatingTime - 4 > 0) {
                Timer.set(5 * 1000, false, function () {
                    print("Starting to add hours 4-8");
                    if (heatingTime - 9 < 1) { data_indx = heatingTime; }
                    else { data_indx = 9; }
                    addSchedules(sorted, 4, data_indx);
                });
            }
            if (heatingTime - 9 > 0) {
                Timer.set(10 * 1000, false, function () {
                    print("Starting to add hours 9-13");
                    if (heatingTime - 14 < 1) { data_indx = heatingTime; }
                    else { data_indx = 14; }
                    addSchedules(sorted, 9, data_indx);
                });
            }
            if (heatingTime - 14 > 0) {
                Timer.set(15 * 1000, false, function () {
                    print("Starting to add hours 14-19");
                    if (heatingTime - 19 < 1) { data_indx = heatingTime; }
                    else { data_indx = 19; }
                    addSchedules(sorted, 14, data_indx);
                });
            }
            if (heatingTime - 19 > 0) {
                Timer.set(20 * 1000, false, function () {
                    print("Starting to add hours 19-23");
                    if (heatingTime - 24 < 1) { data_indx = heatingTime; }
                    else { data_indx = 24; }
                    addSchedules(sorted, 19, data_indx);
                });
            }
        }
    });
}
// Add schedulers, switching them on or off is depends on the "is_reverse" parameter
function addSchedules(sorted_prices, start_indx, data_indx) {
    for (let i = start_indx; i < data_indx; i++) {
        let hour, price;
        if (sorted_prices.length > 0) {
            hour = unixTimeToHumanReadable(sorted_prices[i].timestamp, 2, 0).slice(11, 13);
            price = sorted_prices[i].price;
        }
        else {
            hour = JSON.stringify(start_indx);
            price = "no price.";
        }
  if (price < max_price) {
 
        print("Scheduled start at: ", hour, " price: ", price);
        // Remove leading zeros from hour
        if (hour.slice(0, 1) === "0") { hour = hour.slice(1, 2); }
        // Set the start time crontab
        let timer_start = "0 0 " + hour + " * * SUN,MON,TUE,WED,THU,FRI,SAT";
        // Creating one hour schedulers 
        Shelly.call("Schedule.Create", {
            "id": 0, "enable": true, "timespec": timer_start,
            "calls": [{
                "method": "Switch.Set",
                "params": {
                    id: 0,
                    "on": !is_reverse
                }
            }]
        }
        )
   }
    }
}
// Shelly doesn't support any date-time management.
// With this very basic math we can convert unix time to Human readable format
function unixTimeToHumanReadable(seconds, timezone, addDay) {
    //add timezone
    seconds += 60 * 60 * timezone;
    //add days
    seconds += 60 * 60 * 24 * addDay;
    // Save the time in Human readable format
    let ans = "";
    // Number of days in month in normal year
    let daysOfMonth = [31, 28, 31, 30, 31, 30,
        31, 31, 30, 31, 30, 31];
    let currYear, daysTillNow, extraTime,
        extraDays, index, date, month, hours,
        minutes, secondss, flag = 0;
    // Calculate total days unix time T
    daysTillNow = Math.floor(seconds / (24 * 60 * 60));
    extraTime = seconds % (24 * 60 * 60);
    currYear = 1970;
    // Calculating current year
    while (true) {
        if (currYear % 400 === 0
            || (currYear % 4 === 0 && currYear % 100 !== 0)) {
            if (daysTillNow < 366) {
                break;
            }
            daysTillNow -= 366;
        }
        else {
            if (daysTillNow < 365) {
                break;
            }
            daysTillNow -= 365;
        }
        currYear += 1;
    }
    // Updating extradays because it will give days till previous day and we have include current day
    extraDays = daysTillNow + 1;
    if (currYear % 400 === 0 ||
        (currYear % 4 === 0 &&
            currYear % 100 !== 0))
        flag = 1;
    // Calculating MONTH and DATE
    month = 0; index = 0;
    if (flag === 1) {
        while (true) {
            if (index === 1) {
                if (extraDays - 29 < 0)
                    break;
                month += 1;
                extraDays -= 29;
            }
            else {
                if (extraDays - daysOfMonth[index] <= 0) {
                    break;
                }
                month += 1;
                extraDays -= daysOfMonth[index];
            }
            index += 1;
        }
    }
    else {
        while (true) {
            if (extraDays - daysOfMonth[index] <= 0) {
                break;
            }
            month += 1;
            extraDays -= daysOfMonth[index];
            index += 1;
        }
    }
    // Current Month
    if (extraDays > 0) {
        month += 1;
        date = extraDays;
    }
    else {
        if (month === 2 && flag === 1) {
            date = 29;
        }
        else {
            date = daysOfMonth[month - 1];
        }
    }
    // Calculating HH:MM:SS
    hours = Math.floor(extraTime / 3600);
    minutes = Math.floor((extraTime % 3600) / 60);
    secondss = Math.floor((extraTime % 3600) % 60);
    //add leading 0 to month, date, hour, minute, and seconds
    let monthStr, dateStr, hoursStr, minutesStr, secondsStr;
    if (month < 10) { monthStr = "0" + JSON.stringify(month); } else { monthStr = JSON.stringify(month); }
    if (date < 10) { dateStr = "0" + JSON.stringify(date); } else { dateStr = JSON.stringify(date); }
    if (hours < 10) { hoursStr = "0" + JSON.stringify(hours); } else { hoursStr = JSON.stringify(hours); }
    if (minutes < 10) { minutesStr = "0" + JSON.stringify(minutes); } else { minutesStr = JSON.stringify(minutes); }
    if (secondss < 10) { secondsStr = "0" + JSON.stringify(secondss); } else { secondsStr = JSON.stringify(secondss); }
    ans += JSON.stringify(currYear);
    ans += "-";
    ans += monthStr;
    ans += "-";
    ans += dateStr;
    ans += " ";
    ans += hoursStr;
    ans += ":";
    ans += minutesStr;
    ans += ":";
    ans += secondsStr;
    // Return the time
    return ans;
}
// Shelly doesnt support Javascript sort function so this basic math algorithm will do the sorting job
function sort(array, sortby) {
    // Sorting array from smallest to larger
    let i, j, k, min, max, min_indx, max_indx, tmp;
    j = array.length - 1;
    for (i = 0; i < j; i++) {
        min = max = array[i][sortby];
        min_indx = max_indx = i;
        for (k = i; k <= j; k++) {
            if (array[k][sortby] > max) {
                max = array[k][sortby];
                max_indx = k;
            }
            else if (array[k][sortby] < min) {
                min = array[k][sortby];
                min_indx = k;
            }
        }
        tmp = array[i];
        array.splice(i, 1, array[min_indx]);
        array.splice(min_indx, 1, tmp);
        if (array[min_indx][sortby] === max) {
            tmp = array[j];
            array.splice(j, 1, array[min_indx]);
            array.splice(min_indx, 1, tmp);
        }
        else {
            tmp = array[j];
            array.splice(j, 1, array[max_indx]);
            array.splice(max_indx, 1, tmp);
        }
        j--;
    }
    return array;
    // Huhh, array is finally sorted
}
// Delete all the schedulers before adding new ones
function deleteSchedulers() {
    print("Deleting all existing schedules ...");
    Shelly.call("Schedule.DeleteAll");
}
// Set automatic one hour countdown timer to flip the Shelly status
// Auto_on or auto_off is depends on the "is_reverse" parameter
// Delay_hour is the time period in hour. Shelly will translate this to seconds.
function setTimer(is_reverse, delay_hour) {
    let is_on;
    if (is_reverse) { is_on = "on" }
    else { is_on = "off" }
    print("Setting ", delay_hour, " hour auto_", is_on, "_delay.");
    Shelly.call("Switch.SetConfig", {
        "id": 0,
        config: {
            "name": "Switch0",
            "auto_on": is_reverse,
            "auto_on_delay": delay_hour * 61 * 60,
            "auto_off": !is_reverse,
            "auto_off_delay": delay_hour * 61 * 60
        }
    }
    )
}
function scheduleScript() {
    print("Creating schedule for this script with the following CRON", script_schedule);
    Shelly.call("Schedule.create", {
        "id": 3, "enable": true, "timespec": script_schedule,
        "calls": [{
            "method": "Script.start",
            "params": {
                "id": script_number
            }
        }]
    })
}
function stopScript() {
    // Stop this script in 1.5 minute from now
    Timer.set(100 * 1000, false, function () {
        print("Stopping the script ...");
        Shelly.call("Script.stop", { "id": script_number });
    });
}
deleteSchedulers();
getShellyStatus();
setTimer(is_reverse, 1);
scheduleScript();
stopScript();