Автоматизация расчёта времени восхода и заката Солнца для при помощи обратных вызовов
Этот пример описывает модель расчёта времени восхода и заката Солнца исходя из заданного времени и координат. Эта модель, как предполагается, будет служить подсистемой для моделей устройств "Интернета вещей". Для автоматизации расчётов и тестирования алгоритмов модели активно задействованы её обратные вызовы. Благодаря этому модель без использования скриптов может автоматически определять текущую дату, местоположение и часовой пояс.
Введение
Первоочередная цель разработки модели - создание подсистемы расчёта параметров движения Солнца с различными типа возможного задания текущей даты и координат. Направление использования такой подсистемы - различные устройства "Интернета вещей", поддерживающие получение текущей даты от часов реального времени или NTP-сервера, статичную установку координат или их автоматическое определение по GPS.
Для тестирования алгоритмов такой подсистемы необходима возможность изменения способов задания текущей даты. В модели используются обратные вызовы, которые позволяют автоматизировать определение необходимых входных параметров при различных способах их задания.
Модель примера
Модель примера - IoT_sunrise_sunset.engee
. Её основные расчётные блоки - подсистемы "Уравнение времени", "Склонение Солнца", "Часовой угол" и "Время восхода, заката".

Блоки Multiport Switch
(лиловые) используются для переключения входных данных для модели расчётов. Блоки Constant
(бирюзовые) - передают в модель константы и переменные из рабочей области. Блоки Inport
и Outport
(оранжевые) используются в качестве входов и выходов подсистемы для обмена значениями в программе контроллера. Блок Ramp
формирует изменяющийся сигнал, имитирующий изменение порядкового номера текущего дня, для тестирования работы алгоритма в течение года. Также для преобразования единиц измерения между расчётными подсистемами используются блоки Gain
.
Расчёт времени восхода и заката
В подсистемах модели осуществляются расчёты согласно следующим выражениям:
Подсистема "Уравнение времени" (Equation_of_Time
):
где
- уравнение времени, мин;
- вспомогательная переменная, рад;
- текущий год;
- сидерический год;
- текущий день (по порядку в году, где день №1 - 1 января).
Подсистема "Склонение Солнца" (Declination
):
где - угол пройденный Землёй по орбите за 1 день, рад;
- угол пройденный Землёй по орбите на текущий день, начиная со дня зимнего солнцестояния, рад;
- эксцентриситет земной орбиты;
- вспомогательная переменная, рад;
- наклон оси вращения Земли, рад;
- склонение Солнца, рад.
Подсистема "Часовой угол" (hour_angle
):
где - широта точки наблюдения, рад;
- часовой угол, час.
Это выражение не учитывает влияние горизонтального параллакса, видимого радиуса и рефракции Солнца.
Подсистема "Время восхода, заката" (Sunrise_Suncet_time
):
где - время восхода, час;
- время заката, час;
- прямое восхождение, час;
- долгота точки наблюдения, град;
- часовая зона, час.
Расчётные выражения получены из литературных источников, приведённых в конце примера.
Задание входных параметров
Модель пример поддерживает несколько способов задания входных переменных:
- - может быть задана переменной
current_year
из рабочей области или из порта подсистемыfrom_MCU_Y
. Переключение между ними производится по значению переменнойyear_set_mode
из рабочей области. По умолчанию,year_set_mode = 1
; - - могут быть заданы вектором переменных
latitude
,longitude
иtime_zone
из рабочей области или из порта подсистемыfrom_MCU_GCS
. Переключение между ними производится по значению переменнойGCS_set_mode
из рабочей области. По умолчанию,GCS_set_mode = 1
; - - может быть задана блоком
Ramp
, переменнойday_number
из рабочей области или из порта подсистемыfrom_MCU_D
. Переключение между ними производится по значению переменнойday_number_set_mode
из рабочей области. По умолчанию,day_number_set_mode = 1
;БлокRamp
имеет наклон = 1, поэтому за 366 сек времени моделирования с шагом моделирования 1 сек будет смоделированы расчёты для каждого дня по порядку в течение всего заданного года.
Константы, приведённые в выражениях, заданы в обратных вызовах.
Обратные вызовы модели
Обратные вызовы модели этого примера используются для автоматизации получения актуальных переменных, тестирования во всех заданных режимах, конфигурирования под различные задачи.
Вкладка </> PostLoadFunc
обратных вызовов содержит следующий код:
# Астрономические константы
Эксцентриситет = Eccentricity = 0.0167; # эксцентриситет Земли, 2017 г.
Наклон_оси= Axial_tilt = 23.4372 * pi / 180; # наклон оси вращения Земли, радиан (23°26′14″)
дней_в_году = days_in_year = 365.256; # цикл вращения Земли вокруг Солнца, дней
# Подключение файла с флагом генерации кода
include("/user/start/examples/codegen/iot_sunrise_sunset_callbacks/CG_start.jl")
if (CG_start == 0) # Для генерации кода не нужно инициализировать переменные
# Объявление переменных
current_year = 0
current_month = 0
current_day = 0
day_number = 0
latitude = 0.0
longitude = 0.0
time_zone = 0
# Установление режимов работы модели по умолчанию
day_number_set_mode = 1
year_set_mode = 1
GCS_set_mode = 1
# "Вычисление" по IP
## Загружаем библиотеки
import Pkg;
Pkg.add("Gumbo")
Pkg.add("HTTP")
Pkg.add("AbstractTrees")
## Подключаем библиотеки
using HTTP, Gumbo, AbstractTrees
## Получаем страницу из сети
сайт = HTTP.get("https://ip2geolocation.com/");
код_сайта = parsehtml(String(сайт.body));
тело_сайта = код_сайта.root[2];
## Выбираем из страницы интересующие данные
широта_IP = тело_сайта[1][1][2][2][1][1][3][8][1][1][1][1][1][10][2][1].text[16:22]
долгота_IP = тело_сайта[1][1][2][2][1][1][3][8][1][1][1][1][1][11][2][1].text[16:22]
широта_IP = parse(Float64, широта_IP)
долгота_IP = parse(Float64, долгота_IP)
часовой_пояс = тело_сайта[1][1][2][2][1][1][3][8][1][1][1][1][1][12][2][1].text[20:22]
часовой_пояс = parse(Int64, часовой_пояс)
# Установки времени и местоположения по умолчанию
latitude = широта_IP
longitude = долгота_IP
time_zone = часовой_пояс
current_year = год_сейчас = Dates.value(Year(now()))
end
Как видно из комментариев кода, эта функция обратных вызовов объявляет константы и переменные, определяет константы и значения по умолчанию для переменных. Следует обратить внимание на то, что для определения местоположения и часовой зоны модель обращается на внешний сайт, который по IP точки подключения определяет географические координаты, и передаёт страницу в качестве HTML-объекта в рабочую область Engee. При этом в ходе генерации кода можно установить флаг CG_start = 1
в файле CG_start.jl
для того, чтобы вручную определять каналы задания переменных.
Вкладка </> PresaveFunc
обратных вызовов содержит следующий код:
if (day_number_set_mode == 1)
год_сейчас = current_year = Dates.value(Year(now()))
latitude = широта_IP
longitude = долгота_IP
time_zone = часовой_пояс
end
if (day_number_set_mode == 2)
год_сейчас = current_year = Dates.value(Year(now()))
месяц_сейчас = current_month = Dates.value(Month(now()))
день_сейчас = current_day = Dates.value(Day(now()))
N1 = floor(275 * месяц_сейчас / 9);
N2 = floor((месяц_сейчас + 9) / 12);
N3 = (1 + floor((год_сейчас - 4 * floor(год_сейчас / 4) + 2) / 3));
день_по_порядку = day_number = Int(N1 - (N2 * N3) + день_сейчас - 30)
latitude = широта_IP
longitude = долгота_IP
time_zone = часовой_пояс
end
Эта функция выполняет вспомогательную роль - при изменении режима задания текущего дня необходимо обновить значения входных переменных. Таким образом, после изменения режима задания и сохранения модели в рабочую область будут переданы новые значения переменных.
Вкладка </> CloseFunc
обратных вызовов содержит функцию engee.clear()
, "обернутую" в цикл проверки флага генерации кода. Таким образом, при закрытии модели будет автоматически очищена рабочая область, а исполнение этой функции обратных вызовов в результате генерации кода рабочую область не очистит .
Тестирование модели за полный цикл
Перейдем к тестированию алгоритма модели. Загрузим и выполним модель.
# @markdown **Загрузка и открытие модели:**
# @markdown Требуется ввести только имя модели
имя_модели = "IoT_sunrise_sunset" # @param {type:"string"}
папка_модели = "$(@__DIR__)/"
if имя_модели in [m.name for m in engee.get_all_models()]
модель = engee.open( имя_модели );
# модель = engee.gcm()
else
модель = engee.load( папка_модели*имя_модели*".engee" );
# модель = engee.gcm()
end
данные = engee.run(модель);
Имея вектор значений уравнения времени для каждого дня в году, можно построить его график:
# @markdown **Построение графиков:**
# @markdown Библиотека `Plots.jl`, бэкэнд `plotly()`
using Plots
plotly()
Ширина = 900 # @param {type:"integer"}
Высота = 300 # @param {type:"integer"}
Сигнал_X = "all_days" # @param {type:"string"}
Сигнал_Y = "discrepancy_min" # @param {type:"string"}
Заголовок = "Уравнение времени" # @param {type:"string"}
Подпись_X = "День по порядку" # @param {type:"string"}
Подпись_Y = "Уравнение времени, мин" # @param {type:"string"}
plot(size = (Ширина, Высота), legend = :none, title=Заголовок, xlabel=Подпись_X, ylabel=Подпись_Y)
plot!(данные[Сигнал_X].value, данные[Сигнал_Y].value;
lw = 2, line=:stem)
Из векторов значений уравнения времени и склонения Солнца в течение года можно построить аналемму Солнца для заданной точки наблюдения:
# @markdown **Построение графиков:**
# @markdown Библиотека `Plots.jl`, бэкэнд `plotly()`
using Plots
plotly()
Ширина = 900 # @param {type:"integer"}
Высота = 300 # @param {type:"integer"}
Сигнал_X = "discrepancy_min" # @param {type:"string"}
Сигнал_Y = "declination" # @param {type:"string"}
Заголовок = "Аналемма" # @param {type:"string"}
Подпись_X = "Уравнение времени, мин" # @param {type:"string"}
Подпись_Y = "Склонение, рад" # @param {type:"string"}
plot(size = (Ширина, Высота), xlims=(-45,45), legend = :none, title=Заголовок, xlabel=Подпись_X, ylabel=Подпись_Y)
plot!(данные[Сигнал_X].value, данные[Сигнал_Y].value;
lw = 2, seriestype=:scatter, color=:yellow, background_color_subplot = :cyan)
В завершение тестирования алгоритма построим графики времени восхода и заката для каждого дня в текущем году для координат точки наблюдения, определенных по IP:
# @markdown **Построение графиков:**
# @markdown Библиотека `Plots.jl`, бэкэнд `plotly()`
using Plots
plotly()
Ширина = 900 # @param {type:"integer"}
Высота = 300 # @param {type:"integer"}
Сигнал_X = "all_days" # @param {type:"string"}
Сигнал_Y1 = "sunrise" # @param {type:"string"}
Подпись_1 = "Время восхода" # @param {type:"string"}
Сигнал_Y2 = "sunset" # @param {type:"string"}
Подпись_2 = "Время заката" # @param {type: "string"}
Расположение_подписей = :topleft # @param [":none", ":topleft", ":top", ":topright", ":left", ":right", ":bottomleft",":bottom",":bottomright", ":outerright", ":outerleft", ":outertop", ":outerbottom", ":outertopright", ":outertopleft", ":outerbottomright", :outerbottomleft] {type:"raw"}
Заголовок = "Восходы и закаты" # @param {type:"string"}
Подпись_X = "День по порядку" # @param {type:"string"}
Подпись_Y = "Время, ч" # @param {type:"string"}
plot(size = (Ширина, Высота), legend = Расположение_подписей, title=Заголовок, xlabel=Подпись_X, ylabel=Подпись_Y)
plot!(данные[Сигнал_X].value, данные[Сигнал_Y1].value;
label = Подпись_1, lw = 2, fillcolor = :darkblue, fillrange = 0)
plot!(данные[Сигнал_X].value, данные[Сигнал_Y2].value;
label = Подпись_2, lw = 2, fillcolor = :yellow, fillrange = 0.01)
plot!(данные[Сигнал_X].value, 24*ones(366);
label = :none, lw = 2, fillcolor = :darkblue, fillrange = 0.01, color = :darkblue)
Графики, описывающие положение Солнца для заданной точки наблюдения соответствуют ожидаемым с учётом принятых выше допущений.
Определение времени восхода и заката сегодня
Для определения характеристик положения Солнца, в том числе, времени восхода и заката для сегодняшнего дня перейдём в соответствующий режим установки текущего дня, после чего сохраним:
day_number_set_mode = 2;
engee.save(модель, папка_модели*имя_модели*".engee"; force = true);
Для управления расчётами модели мы используем функции программного управления.
Теперь выведем сообщение, чтобы убедиться, что модель получает одно значение текущего дня:
import Printf
Printf.@printf "Сегодня %i день по порядку, где день №1 - 01.01.%i" день_по_порядку год_сейчас
Выполним модель, чтобы рассчитать время восхода и захода сегодня:
данные = engee.run( модель );
Переведем полученные значения времени восхода и заката Солнца из десятичного часового формата в формат целых часов, минут и секунд, после чего выведем сообщение с заданными и полученными значениями:
(дробная_в, часы_в) = modf(данные["sunrise"].value[день_по_порядку])
минуты_в = дробная_в*60
(дробная_в, минуты_в) = modf(минуты_в)
секунды_в = Int(round(дробная_в*60))
часы_в=Int(часы_в)
минуты_в=Int(минуты_в)
(дробная_з, часы_з) = modf(данные["sunset"].value[день_по_порядку])
минуты_з = дробная_з*60
(дробная_з, минуты_з) = modf(минуты_з)
секунды_з = Int(round(дробная_з*60))
часы_з=Int(часы_з)
минуты_з=Int(минуты_з)
Printf.@printf "Сегодня, %i-%i-%i г.\n" день_сейчас месяц_сейчас год_сейчас
Printf.@printf "в текущих координатах: %.2f с.ш. %.2f в.д.\n" широта_IP долгота_IP
Printf.@printf "в часовом поясе UTC+%i \n" часовой_пояс
Printf.@printf "время восхода Солнца - %i:%i:%i \n" часы_в минуты_в секунды_в
Printf.@printf "время заката Солнца - %i:%i:%i \n" часы_з минуты_з секунды_з
Время восхода и захода Солнца для заданной точки наблюдения соответствуют ожидаемым с учётом принятых ранее допущений.
Определение времени восхода и заката в заданный день по заданным координатам
Переменные текущего дня, года и точки наблюдения можно задать скриптом. Например, рассмотрим работу модели со следующими данными:
current_year = 2024
day_number = 256 # День программиста
latitude, longitude = (67.048060, 64.060560);
Так как здесь не происходит изменения режима задания входных переменных, сохранять модель не нужно. Выполним модель с новыми входными переменными:
данные = engee.run(модель);
Переведем полученные значения времени восхода и заката Солнца из десятичного часового формата в формат целых часов, минут и секунд, после чего выведем сообщение с заданными и полученными значениями:
(дробная_в, часы_в) = modf(данные["sunrise"].value[день_по_порядку])
минуты_в = дробная_в*60
(дробная_в, минуты_в) = modf(минуты_в)
секунды_в = Int(round(дробная_в*60))
часы_в=Int(часы_в)
минуты_в=Int(минуты_в)
(дробная_з, часы_з) = modf(данные["sunset"].value[день_по_порядку])
минуты_з = дробная_з*60
(дробная_з, минуты_з) = modf(минуты_з)
секунды_з = Int(round(дробная_з*60))
часы_з=Int(часы_з)
минуты_з=Int(минуты_з)
println("В день программиста")
Printf.@printf "в координатах: %.2f с.ш. %.2f в.д. \n" latitude longitude
Printf.@printf "в часовом поясе UTC+%i \n" часовой_пояс
Printf.@printf "время восхода Солнца - %i:%i:%i \n" часы_в минуты_в секунды_в
Printf.@printf "время заката Солнца - %i:%i:%i \n" часы_з минуты_з секунды_з
Время восхода и захода Солнца для заданной точки наблюдения соответствуют ожидаемым с учётом принятых ранее допущений.
Настройка подсистемы для установки координат и времени из периферии контроллера
Как итог разработки и тестирования модели перейдём к завершающей конфигурации модели - определения режимов задания входных переменных от микроконтроллера:
# в начале устанавливаем флаг генерации кода из модели
write(папка_модели*"CG_start.jl", "CG_start = 1")
# определяем каналы получения входных переменных
day_number_set_mode = 3
GCS_set_mode = 2
year_set_mode = 2;
Генерируем код из модели:
# @markdown **Генерация кода:**
# @markdown Папка для результатов генерации кода будет создана в папке скрипта:
папка = "code" # @param {type:"string"}
# @markdown Генерация кода для подсистемы:
включить = false # @param {type:"boolean"}
if(включить)
подсистема = "" # @param {type:"string"}
engee.generate_code( папка_модели*имя_модели*".engee", папка_модели*папка;
subsystem_name = подсистема)
else
engee.generate_code( папка_модели*имя_модели*".engee", папка_модели*папка)
end
# Сбрасываем флаг генерации кода
write(папка_модели*"CG_start.jl", "CG_start = 0");
Открыв сгенерированные файлы, можно убедиться, что входные переменные определяются из заданных каналов:



Заключение
В этом демонстрационном примере были рассмотрены способы и возможности работы с обратными вызовами модели Engee для эффективной работы и автоматизации процессов параметрирования, конфигурирования, тестирования и генерации кода модели. Описанный алгоритм воспроизводит с учетом принятых допущений заданные расчёты, а модель может быть переконфигурирована под различные задачи тестирования и генерации кода.
Список источников
- Астрономический календарь на 2019 год. Выпуск 19 (116) / С.М. Пономарев, Н.И. Лапин, М.А. Фаддеев, А.П. Гажулина; под ред. С.М. Пономарева. – Н. Новгород: Изд-во ННГУ им. Н.И. Лобачевского, 2018. – 351 с.
- Курс общей астрономии / П. И. Бакулин, Э. В. Кононович, В. И. Мороз. — М.: Наука, 1976.
- Черный М.А. Авиационная астрономия. - М.: Транспорт, 1978. - 208 с.
- Almanac for Computers, 1990 published by Nautical Almanac Office United States Naval Observatory Washington, DC 20392