使用回调自动计算日出日落时间
本示例描述了一个根据给定时间和坐标计算日出日落时间的模型。该模型有望成为物联网设备模型的子系统。为了自动计算和测试模型的算法,我们积极使用了模型的回调。因此,该模型无需使用脚本即可自动确定当前日期、位置和时区。
模型简介
模型开发的主要目标是创建一个子系统,用于计算太阳运动参数,并对当前日期和坐标进行不同类型的可能设置。该子系统的使用方向是 "物联网 "的各种设备,支持从实时时钟或 NTP 服务器接收当前日期、静态设置坐标或通过 GPS 自动确定坐标。
要测试这样一个子系统的算法,就必须能够改变设置当前日期的方式。该模型使用 回调,可以自动定义不同设置方式下的必要输入参数。
模型示例
示例模型是IoT_sunrise_sunset.engee
。其主要计算模块是*"时间等式 "、"太阳偏角 "、"时角 "和"日出日落时间 "*子系统。

块Multiport Switch
(紫色)用于切换计算模型的输入数据。块Constant
(绿松石色)--从工作区向模型传递常量和变量。图块Inport
和Outport
(橙色)用作子系统的输入和输出,以便在控制器程序中交换数值。程序块Ramp
形成一个变化信号,模拟当日序列号的变化,以测试算法在一年中的运行情况。此外,程序块Gain
还用于在计算子系统之间转换计量单位。
日出日落时间的计算
模型子系统根据以下表达式进行计算:
子系统 "时间方程 " (Equation_of_Time
):
其中
- 时间等式,min;
- 辅助变量,rad;
- 当前年份;
- sideric year;
- 当前日期(按年份顺序排列,其中第 1 天为 1 月 1 日)。
子系统 "太阳倾角 " (Declination
):
其中 是地球 1 天内沿轨道运行的角度,弧度;
- 是从冬至日开始的当天地球沿轨道运行的角度,弧度;
- 地球轨道的偏心率;
- 辅助变量,rad;
- 地球自转轴的倾角,rad;
- 太阳偏角,弧度。
子系统 "时钟角度 " (hour_angle
):
其中 是观测点的纬度,rad;
- 时角,小时。
这个表达式没有考虑水平视差、视半径和太阳折射的影响。
子系统 "日出、日落时间 " (Sunrise_Suncet_time
):
其中 - 日出时间,h;
- 日落时间,小时
- 直接上升时间,小时
- 观测点经度,度;
- 小时区,小时。
计算表达式来自示例末尾给出的文献资料。
设置输入参数
模型示例支持几种设置输入变量的方法:
* - 可通过工作区中的变量current_year
或子系统端口from_MCU_Y
设置。两者之间的切换由工作区中的变量year_set_mode
的值完成。默认情况下,year_set_mode = 1
;
* - 可由工作区或子系统端口from_MCU_GCS
的变量矢量latitude
、longitude
和time_zone
设置。它们之间的切换由工作区中的变量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 = Axial_tilt = 23.4372 * pi / 180; # 地球自转轴的倾斜度,弧度(23°26′14″)
days_in_year = 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
日数 = 0
纬度 = 0.0
经度 = 0.0
时区 = 0
# 设置模型的默认模式
day_number_set_mode = 1
year_set_mode = 1
GCS_set_mode = 1
# 通过 IP 进行 "计算
## 加载库
import Pkg.add("Gumbo")
Pkg.add("Gumbo")
Pkg.add("HTTP")
Pkg.add("AbstractTrees")
## 连接库
使用 HTTP、Gumbo、AbstractTrees
## 从网上获取页面
site = HTTP.get("https://ip2geolocation.com/");
site_code = parsehtml(String(site.body));
site_body = site_code.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]
latitude_IP = parse(Float64, latitude_IP)
longitude_IP = parse(Float64, longitude_IP)
часовой_пояс = тело_сайта[1][1][2][2][1][1][3][8][1][1][1][1][1][12][2][1].text[20:22]
hour_zone = parse(Int64, hour_zone)
# 默认时间和位置设置
latitude = latitude_IP
longitude = longitude_IP
time_zone = time_zone
current_year = year_now = Dates.value(Year(now())
结束
从代码注释中可以看到,这个回调函数声明了常量和变量,定义了常量和变量的默认值。请注意,为确定位置和时区,模型会调用一个外部网站,该网站会根据连接点的 IP 确定地理坐标,并将页面作为 HTML 对象传递到 Engee 工作区。在代码生成过程中,您可以在CG_start.jl
文件中设置CG_start = 1
标志,以手动定义变量赋值通道。
</> PresaveFunc
回调选项卡包含以下代码:
``朱莉娅
如果 (day_number_set_mode == 1)
year_year = current_year = Dates.value(Year(now())
latitude = latitude_IP
longitude = longitude_IP
time_zone = time_zone
结束
如果 (day_number_set_mode == 2)
year_now = current_year = Dates.value(Year(now())
month_now = current_month = Dates.value(Month(now())
day_now = current_day = Dates.value(Day(now())
N1 = floor(275 * month_month / 9);
N2 = floor((month_now + 9) / 12);
N3 = (1 + floor((year_now - 4 * floor(year_now / 4) + 2) / 3));
day_by_order = day_number = Int(N1 - (N2 * N3) + day_now - 30)
latitude = latitude_IP
longitude = longitude_IP
time_zone = time_zone
结束
该函数起辅助作用--当日工作模式改变时,需要更新输入变量的值。因此,在更改工作模式并保存模型后,新的变量值将被传送到工作区。
</> 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) / S.M. Ponomarev, N.I. Lapin, M.A. Faddeev, A.P. Gajulina; ed. by S.M. Ponomarev.- N. Novgorod: Izd-voor NNGU named after N.I. Lobachevsky, 2018.- 351 с.
2.普通天文学教程 / P. I. Bakulin, E. V. Kononovich, V. I. Moroz.- Moscow: Nauka, 1976. - Cherny M.A. Aviation astronomy.- Moscow: Transport, 1978.- 208 с.
4.Almanac for Computers, 1990 published by Nautical Almanac Office United States Naval Observatory Washington, DC 20392