Engee 文档
Notebook

使用回调自动计算日出和日落时间

该示例描述了基于给定时间和坐标计算日出和日落时间的模型。 该模型应该作为"物联网"设备模型的子系统。 它的回调积极参与模型算法的自动计算和测试。 这允许模型在不使用脚本的情况下自动确定当前日期、位置和时区。

导言

模型开发的主要目标是创建一个子系统,用于计算太阳运动的参数,并使用当前日期和坐标的各种可能设置。 这种子系统的使用方向是各种物联网设备,这些设备支持从实时时钟或NTP服务器接收当前日期,静态坐标设置或自动GPS检测。
为了测试这样一个子系统的算法,有必要改变设置当前日期的方式。 模型使用[回调](https://engee.com/helpcenter/stable/ru/modeling/callbacks-engee.html ),允许您自动确定必要的输入参数,以进行各种设置。

示例模型

示例模型 - IoT_sunrise_sunset.engee. 它的主要计算块是子系统*"时间方程""太阳赤纬""小时角""日出,日落时间"*。

sunrise_sunset.png

街区 Multiport Switch (紫色)用于切换计算模型的输入数据。 街区 Constant (2)-将常量和变量从工作区传递到模型。 街区 InportOutport (橙色)用作子系统的输入和输出,用于在控制器程序中交换值。 座 Ramp 生成模拟当天序列号变化的变化信号,以测试算法全年的运行情况。 以下模块还用于在计算子系统之间转换测量单位: Gain.

计算日出和日落时间

根据以下表达式在模型的子系统中执行计算:

子系统*"时间方程"*(Equation_of_Time):

哪里
-时间方程,min;
-辅助变量,rad;
-本年度;
-sidereal年;
-当前日期(按年份顺序,其中第1天是1月1日)。

子系统*"太阳赤纬"*(Declination):

哪里 -地球在1天内在轨道上行进的角度,rad;
-从冬至开始的当天地球在轨道上通过的角度为rad;
-地球轨道的偏心率;
-辅助变量,rad;
-地球自转轴的倾斜,rad;
-太阳赤纬,我很高兴。

子系统*"时钟角度"*(hour_angle):

哪里 -观测点的纬度,rad;
-小时角,小时。
这个表达式没有考虑到水平视差、视半径和太阳折射的影响。

子系统*"日出,日落时间"*(Sunrise_Suncet_time):

哪里 -日出时间,一小时;
-日落时间,一小时;
-赤经,小时;
-观测点的经度,度;
-时区,小时。

计算的表达式是从示例末尾给出的文学来源获得的。

设置输入参数

示例模型支持几种设置输入变量的方法:

  • -可以设置为变量 current_year 从工作区或从子系统端口 from_MCU_Y. 它们之间的切换由变量的值执行 year_set_mode 从工作区。 默认情况下, year_set_mode = 1;
  • -可由变量向量指定 latitude, longitudetime_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秒的模拟步骤,将为每一天模拟计算,以便整个指定年份。

表达式中给出的常量在回调中设置。

模型回调

[回调](https://engee.com/helpcenter/stable/ru/modeling/callbacks-engee.html 此示例中的模型用于自动获取相关变量、在所有指定模式下进行测试以及为各种任务进行配置。

标签 </> PostLoadFunc 回调代码包含以下代码:

``'茱莉亚

天文常数

偏心=偏心=0.0167;#地球的偏心,2017
倾斜轴=Axial_tilt=23.4372*pi/180;#地球自转轴的倾斜,弧度(23°26'14")
days_in_year=365.256;#地球绕太阳的自转周期,日

使用包含代码生成标志连接文件

("/user/start/examples/codegen/iot_sunrise_sunset_callbacks/CG_start。jl")

if(CG_start==0)#无需初始化变量生成代码

#声明变量
当前_年=0
当前_月=0
当前日=0
日数=0
纬度=0.0
经度=0.0
time_zone=0

#设置模型的默认操作模式
day_number_set_mode=1 
year_set_mode=1 
GCS_set_mode=1

#按IP计算
##加载库
进口Pkg;
Pkg。添加("Gumbo")
Pkg。添加("HTTP")
Pkg。添加("抽象树")

##连接库
使用HTTP,Gumbo,AbstractTrees

##从网上获取网页
网站=HTTP.get("https://ip2geolocation.com /");
site code_=parsehtml(String(site.体));
站点主体=站点代码。根[2];

##从页面中选择您感兴趣的数据
LATITUDE_IR=body_site[1][1][2][2][1][1][3][8][1][1][1][1][1][10][2][1]. 文本[16:22]
纵向_ir=身体_site[1][1][2][2][1][1][3][8][1][1][1][1][1][11][2][1]. 文本[16:22]

LATITUDE_IR=parse(Float64,纬度)
经度=解析(Float64,经度)
clock_point=body_site[1][1][2][2][1][1][3][8][1][1][1][1][1][12][2][1].文本[20:22]
watch_box=parse(Int64,watch_box)

#默认时间和位置

设置纬度=latitude_ir
经度=经度_ir
time_zone=clock_range

current_year=year_such=日期。值(年(现在()))

结束


从代码注释中可以看出,这个回调函数声明了常量和变量,为变量定义了常量和默认值。 您应该注意这样一个事实,即要确定位置和时区,模型访问外部站点,该站点使用IP连接点确定地理坐标,并将页面作为HTML对象传输到Engee工作区。 与此同时,在代码生成期间,您可以设置标志 CG_start = 1 在文件中 CG_start.jl 以便手动定义用于设置变量的通道。

标签 </> PresaveFunc 回调代码包含以下代码:

``'茱莉亚
如果(day_number_set_mode==1)

year_such=current_year=日期。值(年(现在()))

纬度=latitude_p

经度=经度_p
time_zone=小时

结束

如果(day_number_set_mode==2)

year_such=current_year=日期。值(年(现在()))

month_such=current_month=日期。值(月(现在()))
day_such=current_day=日期。值(日(现在()))

N1=楼层(275*月/9);
N2=楼层((month_such+9)/12);
N3=(1+floor((year_number-4*floor(year_number/ 4) + 2) / 3));

day_number=day_number=Int(N1-(N2*N3)+day_such-30)

纬度=latitude_p

经度=经度_p
time_zone=小时

结束


此函数执行辅助角色-更改当前日期的任务模式时,必须更新输入变量的值。 因此,在更改任务模式并保存模型后,新的变量值将传输到工作区。

标签 </> CloseFunc 回调包含函数 engee.clear(),"包装"在一个代码生成标志检查循环。 因此,当模型关闭时,工作区将被自动清除,并且作为代码生成的结果执行此回调函数将不会清除工作区。

全周期模型测试

让我们继续测试模型算法。 让我们加载并执行模型。

In [ ]:
# @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(модель);
   Resolving package versions...
  No Changes to `~/.project/Project.toml`
  No Changes to `~/.project/Manifest.toml`
   Resolving package versions...
  No Changes to `~/.project/Project.toml`
  No Changes to `~/.project/Manifest.toml`
   Resolving package versions...
  No Changes to `~/.project/Project.toml`
  No Changes to `~/.project/Manifest.toml`

拥有一年中每一天的时间方程值的向量,您可以绘制其图形。:

In [ ]:
# @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)
Out[0]:

从一年中太阳的时间和赤纬方程的值的向量,可以为给定的观测点构造太阳的分析:

In [ ]:
# @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)
Out[0]:

在测试算法结束时,我们将为IP确定的观测点坐标绘制当年每一天的日出和日落时间图:

In [ ]:
# @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)
Out[0]:

考虑到上面的假设,描述给定观测点的太阳位置的图表与预期的图表相对应。

确定今天的日出和日落时间

要确定太阳位置的特征,包括今天的日出和日落时间,请切换到当前日期的适当设置模式,然后保存:

In [ ]:
day_number_set_mode = 2;
engee.save(модель, папка_модели*имя_модели*".engee"; force = true);

为了控制模型的计算,我们使用[软件控制]功能(https://engee.com/helpcenter/stable/ru/modeling/programmatic-modeling-functions.html )。
现在让我们打印一条消息,以确保模型获取当前日期的单个值。:

In [ ]:
import Printf
Printf.@printf "Сегодня %i день по порядку, где день №1 - 01.01.%i" день_по_порядку год_сейчас
Сегодня 303 день по порядку, где день №1 - 01.01.2024

让我们运行一个模型来计算今天的日出和日落时间。:

In [ ]:
данные = engee.run( модель );

我们将接收到的日出和日落时间值从十进制小时格式转换为整小时,分钟和秒的格式,之后我们将显示带有设置和接收值的消息。:

In [ ]:
(дробная_в, часы_в) = 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" часы_з минуты_з секунды_з
Сегодня, 29-10-2024 г.
в текущих координатах: 55.75 с.ш. 37.62 в.д.
в часовом поясе UTC+3 
время восхода Солнца - 7:35:44 
время заката Солнца - 16:50:17 

考虑到以前接受的假设,给定观测点的日出和日落时间与预期时间相对应。

使用指定坐标确定给定日期的日出和日落时间

可以使用脚本设置当前日期、年份和观测点的变量。 例如,考虑模型如何处理以下数据:

In [ ]:
current_year = 2024
day_number = 256        # День программиста
latitude, longitude = (67.048060, 64.060560);

由于设置输入变量的模式没有变化,因此无需保存模型。 让我们用新的输入变量运行模型:

In [ ]:
данные = engee.run(модель);

我们将接收到的日出和日落时间值从十进制小时格式转换为整小时,分钟和秒的格式,之后我们将显示带有设置和接收值的消息。:

In [ ]:
(дробная_в, часы_в) = 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" часы_з минуты_з секунды_з
В день программиста
в координатах: 67.05 с.ш. 64.06 в.д. 
в часовом поясе UTC+3 
время восхода Солнца - 3:59:50 
время заката Солнца - 17:18:18 

考虑到以前接受的假设,给定观测点的日出和日落时间与预期时间相对应。

配置子系统以从控制器的外围设置坐标和时间

作为模型开发和测试的结果,让我们继续进行模型的最终配置-确定从微控制器设置输入变量的模式。:

In [ ]:
# в начале устанавливаем флаг генерации кода из модели
write(папка_модели*"CG_start.jl", "CG_start = 1")

# определяем каналы получения входных переменных
day_number_set_mode = 3
GCS_set_mode = 2
year_set_mode = 2;

从模型生成代码:

In [ ]:
# @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");
[ Info: Generated code and artifacts: /user/start/examples/codegen/iot_sunrise_sunset_callbacks/code

通过打开生成的文件,您可以确保输入变量是从指定的通道确定的。:

sunrise_sunset_CG.png
sunrise_sunset_CG2.png
sunrise_sunset_CG3.png

结论

在这个演示中,我们讨论了使用Engee模型的回调来有效地操作和自动化模型的参数化、配置、测试和代码生成过程的方法和可能性。 所描述的算法基于接受的假设再现指定的计算,并且模型可以被重新配置用于各种测试和代码生成任务。

资料来源一览表

  1. 2019年的天文历法。 第19期(116)/S.M.Ponomarev,N.I.Lapin,M.A.Faddeev,A.P.Gazhulina;s.M.ponomarev编辑。 -诺夫哥罗德:N.I.Lobachevsky国立研究大学出版社,2018。 -351页
  2. 一般天文学课程/P.I.Bakulin,E.V.Kononovich,V.I.Moroz。 -M.:Nauka,1976。
  3. Cherny M.A.航空天文学。 -M.:运输,1978年。 -208页
  4. 计算机年鉴,1990年由航海年鉴办公室出版美国海军天文台华盛顿20392