Автономный расчет восхода и заката

Добрый день.

Писал не сам, а мучал ИИ, но вроде получилось очень да же не плохо

Основные возможности

  • :white_check_mark: Точный расчет солнечных времен по координатам (на основе алгоритма SunCalc)
  • :white_check_mark: Автоматическая адаптация к сезонным изменениям длины дня
  • :white_check_mark: Поддержка часовых поясов - легко перенастраивается при переезде
  • :white_check_mark: Приоритет ручного управления - выключатели имеют высший приоритет
  • :white_check_mark: Визуальный мониторинг - все параметры доступны через виртуальное устройство
  • :white_check_mark: Надежность - не зависит от интернета и внешних сервисов

Виртуальное устройство sun_times :

  • Время восхода (формат HH:MM)
  • Время заката (формат HH:MM)
  • Время последнего обновления
  • Кнопка ручного обновления

Особенности реализации

Точность расчетов

Скрипт использует проверенный астрономический алгоритм, учитывающий:

  • Эксцентриситет орбиты Земли
  • Наклон земной оси (23.44°)
  • Атмосферную рефракцию (-0.833°)
  • Географические координаты

Надежность

  • Резервный алгоритм при ошибках расчета
  • Защита от некорректных данных
  • Подробное логирование для диагностики
  • Автовосстановление при сбоях

Производительность

  • Расчет выполняется 1 раз в сутки
  • Время хранится в виртуальном устройстве
  • Быстрое сравнение времени в минутах
// Настройки
var LATITUDE = 55.26535;     // Ваши координаты
var LONGITUDE = 37.74975;    // Ваши координаты  
var TIMEZONE_OFFSET = 3;     // Часовой пояс (MSK = UTC+3)

// Обновлённое виртуальное устройство с кнопкой
defineVirtualDevice("sun_times", {
  title: "Время восхода и заката",
  cells: {
    sunrise: {
      title: "Время восхода",
      type: "text",
      value: "06:00",
      readonly: true
    },
    sunset: {
      title: "Время заката", 
      type: "text",
      value: "18:00",
      readonly: true
    },
    last_updated: {
      title: "Последнее обновление",
      type: "text",
      value: "не обновлялось",
      readonly: true
    },
    update_button: {
      title: "Обновить сейчас",
      type: "pushbutton"
    }
  }
});

// Функция для преобразования времени в минуты (для сравнения)
function timeToMinutes(timeStr) {
    try {
        var parts = timeStr.split(':');
        if (parts.length !== 2) {
            throw new Error("Неверный формат времени: " + timeStr);
        }
        var hours = parseInt(parts[0], 10);
        var minutes = parseInt(parts[1], 10);
        
        if (isNaN(hours) || isNaN(minutes) || hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {
            throw new Error("Некорректное время: " + timeStr);
        }
        
        return hours * 60 + minutes;
    } catch (e) {
        log("Ошибка в timeToMinutes: " + e.toString());
        return 6 * 60; // Возвращаем 06:00 по умолчанию
    }
}

// Функция для красивого форматирования даты и времени (без padStart)
function formatDateTime(date) {
    try {
        if (!(date instanceof Date) || isNaN(date.getTime())) {
            return "неверная дата";
        }
        
        var year = date.getFullYear();
        var month = date.getMonth() + 1;
        var day = date.getDate();
        var hours = date.getHours();
        var minutes = date.getMinutes();
        var seconds = date.getSeconds();
        
        // Ручное добавление ведущих нулей (без padStart)
        month = month < 10 ? "0" + month : month;
        day = day < 10 ? "0" + day : day;
        hours = hours < 10 ? "0" + hours : hours;
        minutes = minutes < 10 ? "0" + minutes : minutes;
        seconds = seconds < 10 ? "0" + seconds : seconds;
        
        return year + "-" + month + "-" + day + " " + hours + ":" + minutes + ":" + seconds;
    } catch (e) {
        log("Ошибка в formatDateTime: " + e.toString());
        return "ошибка формата";
    }
}

// Функция для форматирования времени в HH:MM (без padStart)
function formatTime(date) {
    try {
        if (!(date instanceof Date) || isNaN(date.getTime())) {
            log("formatTime: date не является Date объектом");
            return "06:00";
        }
        
        var hours = date.getHours();
        var minutes = date.getMinutes();
        
        // Ручное добавление ведущих нулей
        var hoursStr = hours < 10 ? "0" + hours : "" + hours;
        var minutesStr = minutes < 10 ? "0" + minutes : "" + minutes;
        
        return hoursStr + ":" + minutesStr;
    } catch (e) {
        log("Ошибка в formatTime: " + e.toString());
        return "06:00";
    }
}

// Функция для получения локального времени
function getLocalTime() {
    var now = new Date();
    return new Date(now.getTime() + TIMEZONE_OFFSET * 60 * 60 * 1000);
}

// Точная функция расчета солнечных времен на основе SunCalc
function calculateSunTimes(date, lat, lng, timezoneOffset) {
    log("Точный расчет солнечных времен");
    
    try {
        // Константы
        var rad = Math.PI / 180;
        var dayMs = 1000 * 60 * 60 * 24;
        var J1970 = 2440588;
        var 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; 
        }

        function rightAscension(l, b) {
            var e = rad * 23.4397;
            return Math.atan2(Math.sin(l) * Math.cos(e) - Math.tan(b) * Math.sin(e), Math.cos(l));
        }

        function declination(l, b) {
            var e = rad * 23.4397;
            return Math.asin(Math.sin(b) * Math.cos(e) + Math.cos(b) * Math.sin(e) * Math.sin(l));
        }

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

        function eclipticLongitude(M) {
            var C = rad * (1.9148 * Math.sin(M) + 0.02 * Math.sin(2 * M) + 0.0003 * Math.sin(3 * M));
            var P = rad * 102.9372;
            return M + C + P + Math.PI;
        }

        function sunCoords(d) {
            var M = solarMeanAnomaly(d);
            var L = eclipticLongitude(M);
            return {
                dec: declination(L, 0),
                ra: rightAscension(L, 0)
            };
        }

        var J0 = 0.0009;

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

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

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

        function hourAngle(h, phi, d) {
            var cosH = (Math.sin(h) - Math.sin(phi) * Math.sin(d)) / (Math.cos(phi) * Math.cos(d));
            if (cosH > 1) return 0;
            if (cosH < -1) return Math.PI;
            return Math.acos(cosH);
        }

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

        var times = [
            [-0.833, 'sunrise', 'sunset']
        ];

        var lw = rad * -lng;
        var phi = rad * lat;
        var d = toDays(date);
        var n = julianCycle(d, lw);
        var ds = approxTransit(0, lw, n);
        var M = solarMeanAnomaly(ds);
        var L = eclipticLongitude(M);
        var dec = declination(L, 0);
        var Jnoon = solarTransitJ(ds, M, L);

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

        for (var i = 0; i < times.length; i++) {
            var time = times[i];
            var h0 = time[0] * rad;
            var Jset = getSetJ(h0, lw, phi, dec, n, M, L);
            var Jrise = Jnoon - (Jset - Jnoon);
            result[time[1]] = fromJulian(Jrise);
            result[time[2]] = fromJulian(Jset);
        }

        // Корректируем на часовой пояс
        var offsetMs = timezoneOffset * 60 * 60 * 1000;
        var sunriseLocal = new Date(result.sunrise.getTime() + offsetMs);
        var sunsetLocal = new Date(result.sunset.getTime() + offsetMs);

        log("Точный расчет:");
        log("Восход: " + formatDateTime(sunriseLocal));
        log("Закат: " + formatDateTime(sunsetLocal));

        return {
            sunrise: sunriseLocal,
            sunset: sunsetLocal
        };
    } catch (e) {
        log("Ошибка в точном calculateSunTimes: " + e.toString());
        // Возвращаем значения по умолчанию при ошибке
        var defaultTime = new Date(date);
        var sunrise = new Date(defaultTime);
        var sunset = new Date(defaultTime);
        sunrise.setHours(6, 0, 0, 0);
        sunset.setHours(18, 0, 0, 0);
        return {
            sunrise: sunrise,
            sunset: sunset
        };
    }
}

// Функция для обновления времени восхода и заката
function updateSunTimes() {
    log("Начало updateSunTimes");
    
    try {
        var localTime = getLocalTime();
        log("Локальное время: " + formatDateTime(localTime));
        
        var times = calculateSunTimes(new Date(), LATITUDE, LONGITUDE, TIMEZONE_OFFSET);
        log("Времена получены");
        
        if (!times || !times.sunrise || !times.sunset) {
            throw new Error("calculateSunTimes не вернул корректные данные");
        }
        
        var sunriseStr = formatTime(times.sunrise);
        var sunsetStr = formatTime(times.sunset);
        var updateTimeStr = formatDateTime(localTime);
        
        log("Форматированные времена: " + sunriseStr + " / " + sunsetStr);
        
        dev["sun_times/sunrise"] = sunriseStr;
        dev["sun_times/sunset"] = sunsetStr;
        dev["sun_times/last_updated"] = updateTimeStr;
        
        log("Успешно обновлено время восхода/заката: " + sunriseStr + " / " + sunsetStr);
        
    } catch (e) {
        log("Ошибка в updateSunTimes: " + e.toString());
        var localTime = getLocalTime();
        dev["sun_times/sunrise"] = "06:00";
        dev["sun_times/sunset"] = "18:00";
        dev["sun_times/last_updated"] = "Ошибка: " + formatDateTime(localTime);
    }
}

// Правила обновления времени восхода и заката
defineRule("update_sun_times_on_start", {
    asSoonAs: function() {
        return true;
    },
    then: function() {
        log("Первоначальное обновление времени восхода/заката");
        updateSunTimes();
    }
});

defineRule("update_sun_times_daily", {
    when: cron("0 0 * * *"), // Каждый день в 00:00
    then: function() {
        log("Ежедневное обновление времени восхода/заката");
        updateSunTimes();
    }
});

// Ручное обновление по кнопке в виртуальном устройстве
defineRule("manual_sun_times_update", {
    when: function() {
        return dev["sun_times/update_button"];
    },
    then: function() {
        log("Ручное обновление времени восхода/заката");
        updateSunTimes();
    }
});

Мне собственно нужно было получить ночное время, вот пример функции

// Функция определения ночного времени с правильным сравнением
function isNight() {
    try {
        var localTime = getLocalTime();
        var currentTime = formatTime(localTime);
        var currentMinutes = timeToMinutes(currentTime);
        
        var sunrise = dev["sun_times/sunrise"] || "06:00";
        var sunset = dev["sun_times/sunset"] || "18:00";
        
        var sunriseMinutes = timeToMinutes(sunrise);
        var sunsetMinutes = timeToMinutes(sunset);
        
        // Правильное сравнение времени в минутах
        var isNightTime;
        if (sunriseMinutes < sunsetMinutes) {
            // Нормальный случай: восход до заката
            isNightTime = currentMinutes < sunriseMinutes || currentMinutes >= sunsetMinutes;
        } else {
            // Экзотический случай: восход после заката (полярная ночь/день)
            isNightTime = currentMinutes < sunriseMinutes && currentMinutes >= sunsetMinutes;
        }
        
        log("Текущее время: " + currentTime + " (" + currentMinutes + " мин)" + 
            ", Восход: " + sunrise + " (" + sunriseMinutes + " мин)" + 
            ", Закат: " + sunset + " (" + sunsetMinutes + " мин)" + 
            ", Ночь: " + isNightTime);
        
        return isNightTime;
        
    } catch (e) {
        log("Ошибка в isNight(): " + e.toString());
        // Резервный вариант
        var hours = getLocalTime().getHours();
        return hours < 6 || hours >= 18;
    }
}

Всем удачи)

3 лайка

На днях тоже подобное реализовывал :grinning:

1 лайк

Спасибо, что поделились своими наработками!
Для коллекции оставлю ссылку на тему с обсуждением реализации правил с использованием SunCalc от Vladimir Agafonkin.

1 лайк