126 lines
3.5 KiB
Elixir
126 lines
3.5 KiB
Elixir
defmodule Farmbot.Asset.FarmEvent do
|
|
@moduledoc """
|
|
FarmEvent's are events that happen on a schedule.
|
|
When it is time for the event to execute one of several things may happen:
|
|
|
|
* A Regimen gets started.
|
|
* A Sequence will execute.
|
|
"""
|
|
|
|
@on_load :load_nif
|
|
def load_nif do
|
|
require Elixir.Logger
|
|
nif_file = '#{:code.priv_dir(:farmbot_core)}/build_calendar'
|
|
|
|
case :erlang.load_nif(nif_file, 0) do
|
|
:ok -> :ok
|
|
{:error, {:reload, _}} -> :ok
|
|
{:error, reason} -> Elixir.Logger.warn("Failed to load nif: #{inspect(reason)}")
|
|
end
|
|
end
|
|
|
|
@callback schedule_event(map, DateTime.t) :: any
|
|
|
|
alias Farmbot.Asset.FarmEvent
|
|
alias Farmbot.EctoTypes.ModuleType
|
|
alias Farmbot.EctoTypes.TermType
|
|
|
|
use Ecto.Schema
|
|
import Ecto.Changeset
|
|
require Farmbot.Logger
|
|
|
|
@primary_key {:local_id, :binary_id, autogenerate: true}
|
|
schema "farm_events" do
|
|
field(:id, :integer)
|
|
field(:start_time, :string)
|
|
field(:end_time, :string)
|
|
field(:repeat, :integer)
|
|
field(:time_unit, :string)
|
|
field(:executable_type, ModuleType.FarmEvent)
|
|
field(:executable_id, :integer)
|
|
field(:calendar, TermType)
|
|
end
|
|
|
|
@required_fields [
|
|
:id,
|
|
:start_time,
|
|
:end_time,
|
|
:repeat,
|
|
:time_unit,
|
|
:executable_type,
|
|
:executable_id
|
|
]
|
|
|
|
def changeset(%FarmEvent{} = farm_event, params \\ %{}) do
|
|
farm_event
|
|
|> build_calendar
|
|
|> cast(params, @required_fields ++ [:calendar])
|
|
|> validate_required(@required_fields)
|
|
|> unique_constraint(:id)
|
|
end
|
|
|
|
@compile {:inline, [build_calendar: 1]}
|
|
def build_calendar(%FarmEvent{executable_type: Farmbot.Asset.Regimen} = fe),
|
|
do: fe
|
|
|
|
def build_calendar(%FarmEvent{calendar: nil} = fe),
|
|
do: build_calendar(%{fe | calendar: []})
|
|
|
|
def build_calendar(%FarmEvent{time_unit: "never"} = fe), do: %{fe | calendar: [fe.start_time]}
|
|
|
|
def build_calendar(%FarmEvent{calendar: calendar} = fe)
|
|
when is_list(calendar) do
|
|
current_time_seconds = :os.system_time(:second)
|
|
|
|
start_time_seconds =
|
|
DateTime.from_iso8601(fe.start_time)
|
|
|> elem(1)
|
|
|> DateTime.to_unix(:second)
|
|
|
|
end_time_seconds =
|
|
DateTime.from_iso8601(fe.end_time) |> elem(1) |> DateTime.to_unix(:second)
|
|
|
|
repeat = fe.repeat
|
|
repeat_frequency_seconds = time_unit_to_seconds(fe.time_unit)
|
|
|
|
new_calendar =
|
|
do_build_calendar(
|
|
current_time_seconds,
|
|
start_time_seconds,
|
|
end_time_seconds,
|
|
repeat,
|
|
repeat_frequency_seconds
|
|
)
|
|
|> Enum.map(&DateTime.from_unix!(&1))
|
|
|> Enum.map(&DateTime.to_iso8601(&1))
|
|
|
|
%{fe | calendar: new_calendar}
|
|
end
|
|
|
|
# This should be replaced. YOU WILL KNOW if not.
|
|
def do_build_calendar(
|
|
now_seconds,
|
|
start_time_seconds,
|
|
end_time_seconds,
|
|
repeat,
|
|
repeat_frequency_seconds
|
|
) do
|
|
Farmbot.Logger.error(1, "Using (very) slow calendar builder!")
|
|
grace_period_cutoff_seconds = now_seconds - 60
|
|
|
|
Range.new(start_time_seconds, end_time_seconds)
|
|
|> Enum.take_every(repeat * repeat_frequency_seconds)
|
|
|> Enum.filter(&Kernel.>(&1, grace_period_cutoff_seconds))
|
|
|> Enum.take(3)
|
|
|> Enum.map(&Kernel.-(&1, div(&1, 60)))
|
|
end
|
|
|
|
@compile {:inline, [time_unit_to_seconds: 1]}
|
|
defp time_unit_to_seconds("minutely"), do: 60
|
|
defp time_unit_to_seconds("hourly"), do: 60 * 60
|
|
defp time_unit_to_seconds("daily"), do: 60 * 60 * 24
|
|
defp time_unit_to_seconds("weekly"), do: 60 * 60 * 24 * 7
|
|
defp time_unit_to_seconds("monthly"), do: 60 * 60 * 24 * 30
|
|
defp time_unit_to_seconds("yearly"), do: 60 * 60 * 24 * 365
|
|
end
|