Астро таймер, солнечный калькулятор

Всем здравия !
Тестирую солнечный калькулятор по коду, который здесь и нашел,

он такой
/*
 (c) 2011-2015, Vladimir Agafonkin
 SunCalc is a JavaScript library for calculating sun/moon position and light phases.
 https://github.com/mourner/suncalc
*/

function getSunCalc() {
    // shortcuts for easier to read formulas
    var PI = Math.PI,
        sin = Math.sin,
        cos = Math.cos,
        tan = Math.tan,
        asin = Math.asin,
        atan = Math.atan2,
        acos = Math.acos,
        rad = PI / 180;

    // sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas


    // date/time constants and conversions

    var dayMs = 1000 * 60 * 60 * 24,
        J1970 = 2440588,
        J2000 = 2451545;

    function toJulian(date) {
        return date.valueOf() / dayMs - 0.5 + J1970;
    }

    function fromJulian(j) {
        return new Date((j + 0.5 - J1970) * dayMs);
    }

    function toDays(date) {
        return toJulian(date) - J2000;
    }


    // general calculations for position

    var e = rad * 23.4397; // obliquity of the Earth

    function rightAscension(l, b) {
        return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l));
    }

    function declination(l, b) {
        return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l));
    }

    function azimuth(H, phi, dec) {
        return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi));
    }

    function altitude(H, phi, dec) {
        return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H));
    }

    function siderealTime(d, lw) {
        return rad * (280.16 + 360.9856235 * d) - lw;
    }

    function astroRefraction(h) {
        if (h < 0) // the following formula works for positive altitudes only.
            h = 0; // if h = -0.08901179 a div/0 would occur.

        // formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
        // 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad:
        return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179));
    }

    // general sun calculations

    function solarMeanAnomaly(d) {
        return rad * (357.5291 + 0.98560028 * d);
    }

    function eclipticLongitude(M) {

        var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center
            P = rad * 102.9372; // perihelion of the Earth

        return M + C + P + PI;
    }

    function sunCoords(d) {

        var M = solarMeanAnomaly(d),
            L = eclipticLongitude(M);

        return {
            dec: declination(L, 0),
            ra: rightAscension(L, 0)
        };
    }


    var SunCalc = {};


    // calculates sun position for a given date and latitude/longitude

    SunCalc.getPosition = function (date, lat, lng) {

        var lw = rad * -lng,
            phi = rad * lat,
            d = toDays(date),

            c = sunCoords(d),
            H = siderealTime(d, lw) - c.ra;

        return {
            azimuth: azimuth(H, phi, c.dec),
            altitude: altitude(H, phi, c.dec)
        };
    };


    // sun times configuration (angle, morning name, evening name)

    var times = SunCalc.times = [
        [-0.833, 'sunrise', 'sunset'],
        [-0.3, 'sunriseEnd', 'sunsetStart'],
        [-6, 'dawn', 'dusk'],
        [-12, 'nauticalDawn', 'nauticalDusk'],
        [-18, 'nightEnd', 'night'],
        [6, 'goldenHourEnd', 'goldenHour']
    ];

    // adds a custom time to the times config

    SunCalc.addTime = function (angle, riseName, setName) {
        times.push([angle, riseName, setName]);
    };


    // calculations for sun times

    var J0 = 0.0009;

    function julianCycle(d, lw) {
        return Math.round(d - J0 - lw / (2 * PI));
    }

    function approxTransit(Ht, lw, n) {
        return J0 + (Ht + lw) / (2 * PI) + n;
    }

    function solarTransitJ(ds, M, L) {
        return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L);
    }

    function hourAngle(h, phi, d) {
        return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d)));
    }

    // returns set time for the given sun altitude
    function getSetJ(h, lw, phi, dec, n, M, L) {

        var w = hourAngle(h, phi, dec),
            a = approxTransit(w, lw, n);
        return solarTransitJ(a, M, L);
    }


    // calculates sun times for a given date and latitude/longitude

    SunCalc.getTimes = function (date, lat, lng) {

        var lw = rad * -lng,
            phi = rad * lat,

            d = toDays(date),
            n = julianCycle(d, lw),
            ds = approxTransit(0, lw, n),

            M = solarMeanAnomaly(ds),
            L = eclipticLongitude(M),
            dec = declination(L, 0),

            Jnoon = solarTransitJ(ds, M, L),

            i, len, time, Jset, Jrise;


        var result = {
            solarNoon: fromJulian(Jnoon),
            nadir: fromJulian(Jnoon - 0.5)
        };

        for (i = 0, len = times.length; i < len; i += 1) {
            time = times[i];

            Jset = getSetJ(time[0] * rad, lw, phi, dec, n, M, L);
            Jrise = Jnoon - (Jset - Jnoon);

            result[time[1]] = fromJulian(Jrise);
            result[time[2]] = fromJulian(Jset);
        }

        return result;
    };


    // moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas

    function moonCoords(d) { // geocentric ecliptic coordinates of the moon

        var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude
            M = rad * (134.963 + 13.064993 * d), // mean anomaly
            F = rad * (93.272 + 13.229350 * d),  // mean distance

            l = L + rad * 6.289 * sin(M), // longitude
            b = rad * 5.128 * sin(F),     // latitude
            dt = 385001 - 20905 * cos(M);  // distance to the moon in km

        return {
            ra: rightAscension(l, b),
            dec: declination(l, b),
            dist: dt
        };
    }

    SunCalc.getMoonPosition = function (date, lat, lng) {

        var lw = rad * -lng,
            phi = rad * lat,
            d = toDays(date),

            c = moonCoords(d),
            H = siderealTime(d, lw) - c.ra,
            h = altitude(H, phi, c.dec),
            // formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
            pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H));

        h = h + astroRefraction(h); // altitude correction for refraction

        return {
            azimuth: azimuth(H, phi, c.dec),
            altitude: h,
            distance: c.dist,
            parallacticAngle: pa
        };
    };


    // calculations for illumination parameters of the moon,
    // based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
    // Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.

    SunCalc.getMoonIllumination = function (date) {

        var d = toDays(date || new Date()),
            s = sunCoords(d),
            m = moonCoords(d),

            sdist = 149598000, // distance from Earth to Sun in km

            phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra)),
            inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi)),
            angle = atan(cos(s.dec) * sin(s.ra - m.ra), sin(s.dec) * cos(m.dec) -
                cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra));

        return {
            fraction: (1 + cos(inc)) / 2,
            phase: 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math.PI,
            angle: angle
        };
    };


    function hoursLater(date, h) {
        return new Date(date.valueOf() + h * dayMs / 24);
    }

    // calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article

    SunCalc.getMoonTimes = function (date, lat, lng, inUTC) {
        var t = new Date(date);
        if (inUTC) t.setUTCHours(0, 0, 0, 0);
        else t.setHours(0, 0, 0, 0);

        var hc = 0.133 * rad,
            h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc,
            h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx;

        // go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set)
        for (var i = 1; i <= 24; i += 2) {
            h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc;
            h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc;

            a = (h0 + h2) / 2 - h1;
            b = (h2 - h0) / 2;
            xe = -b / (2 * a);
            ye = (a * xe + b) * xe + h1;
            d = b * b - 4 * a * h1;
            roots = 0;

            if (d >= 0) {
                dx = Math.sqrt(d) / (Math.abs(a) * 2);
                x1 = xe - dx;
                x2 = xe + dx;
                if (Math.abs(x1) <= 1) roots++;
                if (Math.abs(x2) <= 1) roots++;
                if (x1 < -1) x1 = x2;
            }

            if (roots === 1) {
                if (h0 < 0) rise = i + x1;
                else set = i + x1;

            } else if (roots === 2) {
                rise = i + (ye < 0 ? x2 : x1);
                set = i + (ye < 0 ? x1 : x2);
            }

            if (rise && set) break;

            h0 = h2;
        }

        var result = {};

        if (rise) result.rise = hoursLater(t, rise);
        if (set) result.set = hoursLater(t, set);

        if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true;

        return result;
    };
    return SunCalc;
}

настроил правило:

оно такое
defineRule("night_lighting", {
    when: cron("@every 60s"),
    then: function() {
        function light_switch(dev_name,param_name,state) {
            if (dev[dev_name][param_name] != state)
            {
                dev[dev_name][param_name] = state;
            }
        }

        var lat = 56.0396;
        var lng = 92.9283;
        now = new Date();
        var SunCalc = getSunCalc();
        times=SunCalc.getTimes(now, lat, lng);
        var sunrise = times.sunrise;
        var sunset  = times.sunset;
        log.info("Дата\t\t", now);
        log.info("Восход\t\t", sunrise);
        log.info("Закат\t\t", sunset);

	var on_date = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 07, 00)
        var off_date = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59)
        var next_day_sunrise = new Date(sunrise);
		
		if ((now < sunrise) && (now > on_date)) {
            light_switch("wb-led","Channel 2",true);
			log.info("Освещение включено 1");
        } else if ((now > sunset) && (now < off_date)) {
            light_switch("wb-led","Channel 2",true);
			log.info("Освещение включено 2");
        } else if ((now > sunrise) && (now < sunset)) {
            light_switch("wb-led", "Channel 2", false);
			log.info("Освещение выключено 1");
        } else {
			light_switch("wb-led","Channel 2", false);
			log.info("Освещение выключено 2");
        }
    }
});

в общем то все хорошо, калькулятор работает, правило тоже работает (включает и выключает свет и по времени и по восходу / закату, НО…

согласно правила свет выкл. в 23-59, и не должно вкл-я до 07-00, но свет включается в 00-00 и горит, пока не обновятся даты восхода и заката следующего дня… (на скрине обвел)…

вопрос к тем, кто ковырял данный код, где что посмотреть и подправить ??

лампочки пока не вешал, тестирую на диммере…бывает через час, а бывает через два часа данные обновляются и свет тухнет до назначенного времени…

Смогу подключиться чуть позже, пока прошу попробовать ответить @AIbot

Проблема с включением света в полночь связана с тем, что данные о восходе и закате обновляются позже, чем происходит переключение на новый день. Это может быть исправлено, добавив проверку, чтобы данные обновлялись сразу после полуночи.

Возможное решение

  1. Обновление данных сразу после полуночи:

    • В вашем скрипте используйте проверку времени и обновляйте данные о восходе и закате сразу после полуночи.
  2. Изменение логики включения/выключения:

    • Убедитесь, что логика включения/выключения света учитывает задержку в обновлении данных.

Пример изменения кода

Добавьте в начало функции then проверку и обновление данных о восходе и закате, если текущее время близко к полуночи.

then: function() {
    now = new Date();
    if (now.getHours() === 0 && now.getMinutes() < 5) {
        times = SunCalc.getTimes(now, lat, lng);
    }
    // остальная логика
}

Попробуйте эти изменения и проверьте, решает ли это вашу проблему. Если нет, пожалуйста, дайте знать, и мы продолжим искать решение.

Я просто бездушная машина, поэтому могу ошибаться.

1 лайк

Добрый день!

Помог ли вам наш помощник? Удалось ли реализовать задуманное?

Нет, не помог… не люблю я эти ИИ… не знаю куда встроить этот код…
а так я попробовал его и так и этак… не получается…
проверить то могу только раз в сутки…
куда и как его правильно вставить относительно моего правила ?!

Добрый день,

Прошу подробнее описать задачу, которую вы решаете. Возможно, я смогу предложить более простой способ её реализации.

Помнится, экспериментировал с подобным.

Но я шел другим путем - заводил таймеры.

Грубо - включил свет - запусти таймер на выключение.
выключил - запусти таймер на включение

ну или более ветвистая логика.

В дело не применилось. боюсь и черновиков не отыщу.

image
Первые два значения возвращает библиотека SunCalc. Почему она для 2025-02-09 00:00:53 возвращает 2025-02-08, надо смотреть в библиотеке.

Добрый день, удалось ли решить вопрос?

Это не так. Геокалькурятор позволяет рассчитать положение солнца/луны на указанное вами в запросе время. И это не обязательно должно быть “текущее время”…

Для отладки этого скрипта я заводил переменную, в которой указывал “смещение” от текущего времени.

var delta_time = 0 ; // Отладочное смещение текущего времени в секундах (0 = нет смещения)
//var delta_time = ((9*60*60)+(41*60) + (50) )*1000 ;  // текущее время смещено на 9 часов 41 минуту  и 50 секунд

Т.е. для отладки вы можете сами задавать “как бы текущее время” и инициировать события заката/восходы хоть сто раз в сутки :slight_smile:

Далее, при формировании вызова процедуры расчета положения солнца и луны “готовлю” время , на которое мне интересно узнать эти данные (фрагмент кода):

//  ......... (обновляем данные, допустим раз в 10 секунд - не настолько это важные события, чтобы следить за ними чаще и лишний раз греть процессор :) .
    when: cron("@every 10s"),
    then: function() {
// ЛО - хх.ххххххх, хх.ххххххх
        var lat = хх.ххххххх;
        var lng = хх.ххххххх;
        now = new Date();                         // получаем текущее значение системного времени
        now.setTime(now.getTime() + delta_time);  // для отладки смещаем значение текущего времени на указанную дельту
      
        var SunCalc = getSunCalc();

        times=SunCalc.getTimes(now, lat, lng);
        var sunrise = times.sunrise;
        var sunset  = times.sunset;
    

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////        
// Еще раз вычисляем время восхода/захода на завтра (+24 часа к текущей дате)
        now_24h = new Date();                                               // Текущее системное время
        now_24h.setTime(now_24h.getTime() + delta_time + (24*60*60*1000));  // + 24 часа к текущей дате и дельте смещения
        var SunCalc_24h = getSunCalc();

        times_24h=SunCalc_24h.getTimes(now_24h, lat, lng);
        var sunrise_24h = times_24h.sunrise;
        var sunset_24h  = times_24h.sunset;

Дополнительно (я также как и вы столкнулся с необходимостью знать время восхода “завтра” для выключения ландшафтного и фасадного освещения) я сразу вычисляю время событий на завтра, ибо если “спросить” калькулятор , допустим в полдень, о времени восхода, то он рассчитает (покажет) время уже состоявшегося восхода.
В моей логике (как и в вашей) требуется знать время “будущего” события, а не уже состоявшегося.

Для удобства я завел себе переменную (и контрол в виртуальном устройстве) вида “солнце за горизонтом = да/нет” и уже исходя из этого анализирую какое ближайшее событие произойдет.
Т.е. если солнце сейчас за горизонтом (ночь), то меня интересует какое время ближайшего (в будущем) восхода. И на оборот - если сейчас день, то смотрю на ближайший будущий “закат” …

        if (((now > sunrise) && (now < sunset)) ) {
            dev["NightTime/NightTimeOn"] = false;

            if (sunset < now) {ToSunSet = sunset_24h - now} else {ToSunSet = sunset - now} ;            // До заката
            if (now < sunrise) {FromSunRise = now - sunrise_24h } else {FromSunRise = now - sunrise} ;  // После восхода

            dev["NightTime/ToSunSet"] = MyConverterTime(ToSunSet);
            dev["NightTime/FromSunRise"] = MyConverterTime(FromSunRise);

            dev["NightTime/ToSunRise_second"] =  0;                                                   // Солнце НАД горизонтом => Обнулим время "до Восхода осталось, сек"
            dev["NightTime/ToSunRise"] =  MyConverterTime( 0 );                                       // Солнце НАД горизонтом => Обнулим время "До Восхода осталось"
            
//          log.info("Восход");
        }

        if (((now < sunrise) || (now > sunset)) ) {
            dev["NightTime/NightTimeOn"] = true;

            if (sunrise < now) {ToSunRise = sunrise_24h - now} else {ToSunRise = sunrise - now} ;         // До восхода
            if (now > sunset) {FromSunSet = now_24h - sunset_24h } else {FromSunSet = now_24h - sunset} ; // После заката

            dev["NightTime/ToSunRise_second"] =  Math.round(ToSunRise / 1000);
            dev["NightTime/ToSunRise"] =  MyConverterTime( ToSunRise );
            dev["NightTime/FromSunSet"] = MyConverterTime( FromSunSet );

            dev["NightTime/FromSunRise"] = MyConverterTime(0);                                       // Солнце ЗА горизонтом => Обнулим время "После восхода прошло"
            dev["NightTime/ToSunSet"] = MyConverterTime(0);                                          // Солнце ЗА горизонтом => Обнулим "До заката осталось"
//            log.info("Закат");
        }

Я получаю в итоге время восхода и заката (абсолютное) и набор “времен” вида “До восхода осталось”, “До заката осталось”, “После заката прошло”, “После восхода прошло”
которые уже можно использовать в других алгоритмах…

И да, как и коллеги, отписавшиеся в этой теме выше, я тоже пришел к решению, что проще (нагляднее) использовать таймер(ы) (оставшееся время до события), чем сравнивать два времени (кто его знает, что он там на сравнивает :slight_smile: )…

Т.е. там где мне нужно применить “астрономию” я обращаюсь в своему виртуальному устройству для получения конкретных значений и уже далее их использую (например, выключить освещение с восходом + 20 минут, или включить подсветку ландшафта в тенистой аллее за 30 минут до заката, ибо в момент заката в этом месте уже темновато :slight_smile: ).

В целом сам скрипт калькулятора работает достаточно правильно - высчитываемые значения адекватны, но имеет свои особенности. Я не стал править код самого калькулятора, а просто обошел эти особенности указанным выше способом :slight_smile:

PS Здесь я привожу фрагменты своего действующего кода, без описания создания соответствующих виртуальных устройств (из контекста их и так “видно”) и своей функции конвертации “времени” в удобный для меня формат отображения - MyConverterTime(0)

PPS Код категорически не претендует на оптимальность, например Date() можно было бы вызвать и один раз, а далее работать только с математикой (+ дельта, + 24 часа и пр.)
Функции сравнения “времени событий” можно можно было бы оптимизировать и пр.
Такой код остался после моих “экспериментов и мучений с отладкой” и после того как все заработало так, как мне было нужно, оптимизировать его я больше не стал (работает - не трогай :slight_smile: ) - поэтому не судите строго…

2 лайка

Спасибо откликнувшимся ! Тему прошу не закрывать. Буду разбираться. Пока работа не позволяет дотянуться до контроллера.

Добрый день, удалось ли решить вопрос?

sun.js (15,7 КБ)
вопрос пока решил, код для примера положил…
пока работает так…

1 лайк

Файл не грузится …

sun.js (15,7 КБ)

Спойлер

`/*
(c) 2011-2015, Vladimir Agafonkin
SunCalc is a JavaScript library for calculating sun/moon position and light phases.
GitHub - mourner/suncalc: A tiny JavaScript library for calculating sun/moon positions and phases.
*/

function getSunCalc() {
// shortcuts for easier to read formulas
var PI = Math.PI,
sin = Math.sin,
cos = Math.cos,
tan = Math.tan,
asin = Math.asin,
atan = Math.atan2,
acos = Math.acos,
rad = PI / 180;

// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas


// date/time constants and conversions

var dayMs = 1000 * 60 * 60 * 24,
    J1970 = 2440588,
    J2000 = 2451545;

function toJulian(date) {
    return date.valueOf() / dayMs - 0.5 + J1970;
}

function fromJulian(j) {
    return new Date((j + 0.5 - J1970) * dayMs);
}

function toDays(date) {
    return toJulian(date) - J2000;
}


// general calculations for position

var e = rad * 23.4397; // obliquity of the Earth

function rightAscension(l, b) {
    return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l));
}

function declination(l, b) {
    return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l));
}

function azimuth(H, phi, dec) {
    return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi));
}

function altitude(H, phi, dec) {
    return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H));
}

function siderealTime(d, lw) {
    return rad * (280.16 + 360.9856235 * d) - lw;
}

function astroRefraction(h) {
    if (h < 0) // the following formula works for positive altitudes only.
        h = 0; // if h = -0.08901179 a div/0 would occur.

    // formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
    // 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad:
    return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179));
}

// general sun calculations

function solarMeanAnomaly(d) {
    return rad * (357.5291 + 0.98560028 * d);
}

function eclipticLongitude(M) {

    var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center
        P = rad * 102.9372; // perihelion of the Earth

    return M + C + P + PI;
}

function sunCoords(d) {

    var M = solarMeanAnomaly(d),
        L = eclipticLongitude(M);

    return {
        dec: declination(L, 0),
        ra: rightAscension(L, 0)
    };
}


var SunCalc = {};


// calculates sun position for a given date and latitude/longitude

SunCalc.getPosition = function (date, lat, lng) {

    var lw = rad * -lng,
        phi = rad * lat,
        d = toDays(date),

        c = sunCoords(d),
        H = siderealTime(d, lw) - c.ra;

    return {
        azimuth: azimuth(H, phi, c.dec),
        altitude: altitude(H, phi, c.dec)
    };
};


// sun times configuration (angle, morning name, evening name)

var times = SunCalc.times = [
    [-0.833, 'sunrise', 'sunset'],
    [-0.3, 'sunriseEnd', 'sunsetStart'],
    [-6, 'dawn', 'dusk'],
    [-12, 'nauticalDawn', 'nauticalDusk'],
    [-18, 'nightEnd', 'night'],
    [6, 'goldenHourEnd', 'goldenHour']
];

// adds a custom time to the times config

SunCalc.addTime = function (angle, riseName, setName) {
    times.push([angle, riseName, setName]);
};


// calculations for sun times

var J0 = 0.0009;

function julianCycle(d, lw) {
    return Math.round(d - J0 - lw / (2 * PI));
}

function approxTransit(Ht, lw, n) {
    return J0 + (Ht + lw) / (2 * PI) + n;
}

function solarTransitJ(ds, M, L) {
    return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L);
}

function hourAngle(h, phi, d) {
    return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d)));
}

// returns set time for the given sun altitude
function getSetJ(h, lw, phi, dec, n, M, L) {

    var w = hourAngle(h, phi, dec),
        a = approxTransit(w, lw, n);
    return solarTransitJ(a, M, L);
}


// calculates sun times for a given date and latitude/longitude

SunCalc.getTimes = function (date, lat, lng) {

    var lw = rad * -lng,
        phi = rad * lat,

        d = toDays(date),
        n = julianCycle(d, lw),
        ds = approxTransit(0, lw, n),

        M = solarMeanAnomaly(ds),
        L = eclipticLongitude(M),
        dec = declination(L, 0),

        Jnoon = solarTransitJ(ds, M, L),

        i, len, time, Jset, Jrise;


    var result = {
        solarNoon: fromJulian(Jnoon),
        nadir: fromJulian(Jnoon - 0.5)
    };

    for (i = 0, len = times.length; i < len; i += 1) {
        time = times[i];

        Jset = getSetJ(time[0] * rad, lw, phi, dec, n, M, L);
        Jrise = Jnoon - (Jset - Jnoon);

        result[time[1]] = fromJulian(Jrise);
        result[time[2]] = fromJulian(Jset);
    }

    return result;
};


// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas

function moonCoords(d) { // geocentric ecliptic coordinates of the moon

    var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude
        M = rad * (134.963 + 13.064993 * d), // mean anomaly
        F = rad * (93.272 + 13.229350 * d),  // mean distance

        l = L + rad * 6.289 * sin(M), // longitude
        b = rad * 5.128 * sin(F),     // latitude
        dt = 385001 - 20905 * cos(M);  // distance to the moon in km

    return {
        ra: rightAscension(l, b),
        dec: declination(l, b),
        dist: dt
    };
}

SunCalc.getMoonPosition = function (date, lat, lng) {

    var lw = rad * -lng,
        phi = rad * lat,
        d = toDays(date),

        c = moonCoords(d),
        H = siderealTime(d, lw) - c.ra,
        h = altitude(H, phi, c.dec),
        // formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
        pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H));

    h = h + astroRefraction(h); // altitude correction for refraction

    return {
        azimuth: azimuth(H, phi, c.dec),
        altitude: h,
        distance: c.dist,
        parallacticAngle: pa
    };
};


// calculations for illumination parameters of the moon,
// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.

SunCalc.getMoonIllumination = function (date) {

    var d = toDays(date || new Date()),
        s = sunCoords(d),
        m = moonCoords(d),

        sdist = 149598000, // distance from Earth to Sun in km

        phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra)),
        inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi)),
        angle = atan(cos(s.dec) * sin(s.ra - m.ra), sin(s.dec) * cos(m.dec) -
            cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra));

    return {
        fraction: (1 + cos(inc)) / 2,
        phase: 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math.PI,
        angle: angle
    };
};


function hoursLater(date, h) {
    return new Date(date.valueOf() + h * dayMs / 24);
}

// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article

SunCalc.getMoonTimes = function (date, lat, lng, inUTC) {
    var t = new Date(date);
    if (inUTC) t.setUTCHours(0, 0, 0, 0);
    else t.setHours(0, 0, 0, 0);

    var hc = 0.133 * rad,
        h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc,
        h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx;

    // go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set)
    for (var i = 1; i <= 24; i += 2) {
        h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc;
        h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc;

        a = (h0 + h2) / 2 - h1;
        b = (h2 - h0) / 2;
        xe = -b / (2 * a);
        ye = (a * xe + b) * xe + h1;
        d = b * b - 4 * a * h1;
        roots = 0;

        if (d >= 0) {
            dx = Math.sqrt(d) / (Math.abs(a) * 2);
            x1 = xe - dx;
            x2 = xe + dx;
            if (Math.abs(x1) <= 1) roots++;
            if (Math.abs(x2) <= 1) roots++;
            if (x1 < -1) x1 = x2;
        }

        if (roots === 1) {
            if (h0 < 0) rise = i + x1;
            else set = i + x1;

        } else if (roots === 2) {
            rise = i + (ye < 0 ? x2 : x1);
            set = i + (ye < 0 ? x1 : x2);
        }

        if (rise && set) break;

        h0 = h2;
    }

    var result = {};

    if (rise) result.rise = hoursLater(t, rise);
    if (set) result.set = hoursLater(t, set);

    if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true;

    return result;
};
return SunCalc;

}

// Создаем виртуальное устройство astrocalc
defineVirtualDevice(“astrocalc”, {
title: “Astro Calculator”,
cells: {
sunrise_offset: {
title: “Восход”,
type: “value”,
value: 0, // начальное значение смещения восхода
readonly: false,
order: 1,
},
increase_sunrise: {
title: “+”,
type: “pushbutton”,
value: false,
order: 2,
},
decrease_sunrise: {
title: “-”,
type: “pushbutton”,
value: false,
order: 3,
},
sunset_offset: {
title: “Закат”,
type: “value”,
value: 0, // начальное значение смещения заката
readonly: false,
order: 4,
},
increase_sunset: {
title: “+”,
type: “pushbutton”,
value: false,
order: 5,
},
decrease_sunset: {
title: “-”,
type: “pushbutton”,
value: false,
order: 6,
},
light_on_time: {
title: “Время включения света”,
type: “text”,
value: “07:00”, // начальное значение времени включения света
readonly: false,
order: 7,
},
light_off_time: {
title: “Время отключения света”,
type: “text”,
value: “23:59”, // начальное значение времени отключения света
readonly: false,
order: 8,
},
},
});

// Обработка кнопок для sunrise_offset
defineRule(“increase_sunrise_offset”, {
when: function () {
return dev[“astrocalc”][“increase_sunrise”];
},
then: function () {
dev[“astrocalc”][“sunrise_offset”] += 5; // Увеличиваем смещение на 5 минут
dev[“astrocalc”][“increase_sunrise”] = false; // Сбрасываем кнопку
log.info(“Sunrise offset increased to:”, dev[“astrocalc”][“sunrise_offset”]);
},
});

defineRule(“decrease_sunrise_offset”, {
when: function () {
return dev[“astrocalc”][“decrease_sunrise”];
},
then: function () {
dev[“astrocalc”][“sunrise_offset”] -= 5; // Уменьшаем смещение на 5 минут
dev[“astrocalc”][“decrease_sunrise”] = false; // Сбрасываем кнопку
log.info(“Sunrise offset decreased to:”, dev[“astrocalc”][“sunrise_offset”]);
},
});

// Обработка кнопок для sunset_offset
defineRule(“increase_sunset_offset”, {
when: function () {
return dev[“astrocalc”][“increase_sunset”];
},
then: function () {
dev[“astrocalc”][“sunset_offset”] += 5; // Увеличиваем смещение на 5 минут
dev[“astrocalc”][“increase_sunset”] = false; // Сбрасываем кнопку
log.info(“Sunset offset increased to:”, dev[“astrocalc”][“sunset_offset”]);
},
});

defineRule(“decrease_sunset_offset”, {
when: function () {
return dev[“astrocalc”][“decrease_sunset”];
},
then: function () {
dev[“astrocalc”][“sunset_offset”] -= 5; // Уменьшаем смещение на 5 минут
dev[“astrocalc”][“decrease_sunset”] = false; // Сбрасываем кнопку
log.info(“Sunset offset decreased to:”, dev[“astrocalc”][“sunset_offset”]);
},
});

// Обновляем правило для комбинированного режима с учетом времени включения и отключения света
defineRule(“combined_lighting”, {
when: cron(“@every 60s”),
then: function() {
function light_switch(dev_name, param_name, state) {
if (dev[dev_name][param_name] != state) {
dev[dev_name][param_name] = state;
}
}

    var lat = 54.7578;
    var lng = 83.1613;
    var now = new Date();
    var SunCalc = getSunCalc();
    var times = SunCalc.getTimes(now, lat, lng);
    var sunrise = times.sunrise;
    var sunset = times.sunset;

    // Получаем смещения из виртуального устройства
    var sunriseOffset = dev["astrocalc"]["sunrise_offset"] || 0;
    var sunsetOffset = dev["astrocalc"]["sunset_offset"] || 0;

    // Корректируем время восхода и заката с учетом смещений
    var adjustedSunrise = new Date(sunrise.getTime() + sunriseOffset * 60000);
    var adjustedSunset = new Date(sunset.getTime() + sunsetOffset * 60000);

    log.info("Дата\t\t", now);
    log.info("Восход\t\t", sunrise);
    log.info("Закат\t\t", sunset);
    log.info("Свет вкл.\t", adjustedSunrise);
    log.info("Свет выкл.\t", adjustedSunset);

    // Получаем время включения и отключения света из виртуального устройства
    var lightOnTime = dev["astrocalc"]["light_on_time"];
    var lightOffTime = dev["astrocalc"]["light_off_time"];

    // Преобразуем время в объект Date
    var lightOnDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 
                              parseInt(lightOnTime.split(':')[0]), parseInt(lightOnTime.split(':')[1]));
    var lightOffDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 
                               parseInt(lightOffTime.split(':')[0]), parseInt(lightOffTime.split(':')[1]));

    // Абсолютный таймер: временной интервал из виртуального устройства
    var absoluteTimerOn = lightOnDate;
    var absoluteTimerOff = lightOffDate;

    // Солнечный таймер: свет включен между закатом и восходом
    var solarTimerOn = now > adjustedSunset || now < adjustedSunrise;

    // Абсолютный таймер: свет включен между заданным временем включения и отключения
    var absoluteTimer = now >= absoluteTimerOn && now <= absoluteTimerOff;

    // Комбинированный режим: свет включен только если оба таймера сигнализируют о включении
    if (solarTimerOn && absoluteTimer) {
        light_switch("wb-led_54", "Channel 2", true);
        log.info("Освещение включено (комбинированный режим)");
    } else {
        light_switch("wb-led_54", "Channel 2", false);
        log.info("Освещение выключено (комбинированный режим)");
    }
}

});`

Файл формата .JS портал не позволяет скачивать (блокирует).
Заархивируйте пожалуйста ваш файл sun.js и выложите его сюда в виде архива.

sun.zip (4,5 КБ)

2 лайка