Implement Regimens again, add tests and test support

* This also includes changes to all Farmbot API Assets adding
  a `monitor: :boolean` field. This allows tests to be run
  causing minimal side effects on the rest of the systems.
pull/974/head
Connor Rigby 2018-11-19 11:32:54 -08:00
parent a4c1eac2d2
commit 6e2a018598
No known key found for this signature in database
GPG Key ID: 29A88B24B70456E0
62 changed files with 371 additions and 91 deletions

View File

@ -12,3 +12,5 @@ alias Farmbot.Asset.{
PersistentRegimen,
Sequence
}
alias Farmbot.TestSupport.AssetFixtures

View File

@ -1,6 +1,7 @@
use Mix.Config
config :farmbot_core, Farmbot.AssetWorker.Farmbot.Asset.FarmEvent, checkup_time_ms: 10_000
config :farmbot_core, Farmbot.AssetWorker.Farmbot.Asset.PersistentRegimen, checkup_time_ms: 10_000
config :farmbot_core, Farmbot.AssetWorker.Farmbot.Asset.FarmwareInstallation,
error_retry_time_ms: 30_000,

View File

@ -4,6 +4,7 @@ config :farmbot_core, :behaviour,
celery_script_io_layer: Farmbot.TestSupport.CeleryScript.TestIOLayer
config :farmbot_core, Farmbot.AssetWorker.Farmbot.Asset.FarmEvent, checkup_time_ms: 1000
config :farmbot_core, Farmbot.AssetWorker.Farmbot.Asset.PersistentRegimen, checkup_time_ms: 1000
# must be lower than other timers
# To ensure other timers have time to timeout

View File

@ -65,7 +65,7 @@ defmodule Farmbot.Asset do
## Begin PersistentRegimen
def upsert_persistent_regimen(%Regimen{} = regimen, %FarmEvent{} = farm_event, params \\ %{}) do
def upsert_persistent_regimen!(%Regimen{} = regimen, %FarmEvent{} = farm_event, params \\ %{}) do
q =
from(pr in PersistentRegimen,
where: pr.regimen_id == ^regimen.local_id and pr.farm_event_id == ^farm_event.local_id
@ -78,7 +78,13 @@ defmodule Farmbot.Asset do
|> PersistentRegimen.changeset(params)
|> Ecto.Changeset.put_assoc(:regimen, regimen)
|> Ecto.Changeset.put_assoc(:farm_event, farm_event)
|> Repo.insert_or_update()
|> Repo.insert_or_update!()
end
def update_persistent_regimen!(%PersistentRegimen{} = pr, params \\ %{}) do
pr
|> PersistentRegimen.changeset(params)
|> Repo.update!()
end
## End PersistentRegimen

View File

@ -17,6 +17,7 @@ defmodule Farmbot.Asset.Device do
field(:name, :string)
field(:timezone, :string)
field(:monitor, :boolean, default: true)
timestamps()
end
@ -30,7 +31,7 @@ defmodule Farmbot.Asset.Device do
def changeset(device, params \\ %{}) do
device
|> cast(params, [:id, :name, :timezone, :created_at, :updated_at])
|> cast(params, [:id, :name, :timezone, :monitor, :created_at, :updated_at])
|> validate_required([])
end
end

View File

@ -20,6 +20,7 @@ defmodule Elixir.Farmbot.Asset.DiagnosticDump do
field(:firmware_state, :string)
field(:network_interface, :string)
field(:fbos_dmesg_dump, :string)
field(:monitor, :boolean, default: true)
timestamps()
end
@ -47,6 +48,7 @@ defmodule Elixir.Farmbot.Asset.DiagnosticDump do
:firmware_state,
:network_interface,
:fbos_dmesg_dump,
:monitor,
:created_at,
:updated_at
])

View File

@ -22,6 +22,7 @@ defmodule Elixir.Farmbot.Asset.FarmEvent do
# Private
field(:last_executed, :utc_datetime)
field(:monitor, :boolean, default: true)
timestamps()
end
@ -49,6 +50,7 @@ defmodule Elixir.Farmbot.Asset.FarmEvent do
:start_time,
:time_unit,
:last_executed,
:monitor,
:created_at,
:updated_at
])

View File

@ -15,6 +15,7 @@ defmodule Elixir.Farmbot.Asset.FarmwareEnv do
field(:key, :string)
field(:value, :string)
field(:monitor, :boolean, default: true)
timestamps()
end
@ -28,7 +29,7 @@ defmodule Elixir.Farmbot.Asset.FarmwareEnv do
def changeset(farmware_env, params \\ %{}) do
farmware_env
|> cast(params, [:id, :key, :value, :created_at, :updated_at])
|> cast(params, [:id, :key, :value, :monitor, :created_at, :updated_at])
|> validate_required([])
end
end

View File

@ -18,6 +18,7 @@ defmodule Farmbot.Asset.FarmwareInstallation do
field(:url, :string)
embeds_one(:manifest, Manifest, on_replace: :update)
field(:monitor, :boolean, default: true)
timestamps()
end
@ -30,7 +31,7 @@ defmodule Farmbot.Asset.FarmwareInstallation do
def changeset(farmware_installation, params \\ %{}) do
farmware_installation
|> cast(params, [:id, :url, :created_at, :updated_at])
|> cast(params, [:id, :url, :monitor, :created_at, :updated_at])
|> cast_embed(:manifest)
|> validate_required([])
end

View File

@ -27,6 +27,7 @@ defmodule Elixir.Farmbot.Asset.FbosConfig do
field(:sequence_body_log, :boolean)
field(:sequence_complete_log, :boolean)
field(:sequence_init_log, :boolean)
field(:monitor, :boolean, default: true)
timestamps()
end
@ -68,6 +69,7 @@ defmodule Elixir.Farmbot.Asset.FbosConfig do
:sequence_body_log,
:sequence_complete_log,
:sequence_init_log,
:monitor,
:created_at,
:updated_at
])

View File

@ -101,6 +101,7 @@ defmodule Elixir.Farmbot.Asset.FirmwareConfig do
field(:encoder_invert_x, :float)
field(:encoder_missed_steps_max_x, :float)
field(:movement_invert_motor_y, :float)
field(:monitor, :boolean, default: true)
timestamps()
end
@ -290,6 +291,7 @@ defmodule Elixir.Farmbot.Asset.FirmwareConfig do
:encoder_invert_x,
:encoder_missed_steps_max_x,
:movement_invert_motor_y,
:monitor,
:created_at,
:updated_at
])

View File

@ -17,6 +17,7 @@ defmodule Farmbot.Asset.Peripheral do
field(:pin, :integer)
field(:mode, :integer)
field(:label, :string)
field(:monitor, :boolean, default: true)
timestamps()
end
@ -31,7 +32,7 @@ defmodule Farmbot.Asset.Peripheral do
def changeset(peripheral, params \\ %{}) do
peripheral
|> cast(params, [:id, :pin, :mode, :label, :created_at, :updated_at])
|> cast(params, [:id, :pin, :mode, :label, :monitor, :created_at, :updated_at])
|> validate_required([])
end
end

View File

@ -7,14 +7,51 @@ defmodule Farmbot.Asset.PersistentRegimen do
schema "persistent_regimens" do
belongs_to(:regimen, Farmbot.Asset.Regimen, references: :local_id, type: :binary_id)
belongs_to(:farm_event, Farmbot.Asset.FarmEvent, references: :local_id, type: :binary_id)
field(:epoch, :utc_datetime)
field(:started_at, :utc_datetime)
field(:next, :utc_datetime)
# Can't use references here.
field(:next_sequence_id, :id)
field(:monitor, :boolean, default: true)
timestamps()
end
def changeset(persistent_regimen, params \\ %{}) do
persistent_regimen
|> cast(params, [:started_at])
|> cast(params, [:started_at, :next, :next_sequence_id, :monitor])
|> put_epoch()
|> cast_assoc(:regimen)
|> cast_assoc(:farm_event)
end
defp put_epoch(%{valid?: true} = changeset) do
started_at = get_field(changeset, :started_at) || DateTime.utc_now()
if get_field(changeset, :epoch) do
changeset
else
case build_epoch(started_at) do
{:ok, epoch} -> put_change(changeset, :epoch, epoch)
:error -> add_error(changeset, :epoch, "Missing timezone")
end
end
end
defp put_epoch(changeset), do: changeset
# returns midnight of today
@spec build_epoch(DateTime.t()) :: DateTime.t()
def build_epoch(%DateTime{} = datetime) do
case Farmbot.Asset.device().timezone do
nil ->
:error
tz ->
%DateTime{} = n = Timex.Timezone.convert(datetime, tz)
opts = [hours: -n.hour, seconds: -n.second, minutes: -n.minute]
localized_epoch = Timex.shift(n, opts)
epoch = Timex.Timezone.convert(localized_epoch, datetime.time_zone)
{:ok, epoch}
end
end
end

View File

@ -16,6 +16,7 @@ defmodule Farmbot.Asset.PinBinding do
field(:pin_num, :integer)
field(:sequence_id, :integer)
field(:special_action, :string)
field(:monitor, :boolean, default: true)
timestamps()
end
@ -30,7 +31,15 @@ defmodule Farmbot.Asset.PinBinding do
def changeset(pin_binding, params \\ %{}) do
pin_binding
|> cast(params, [:id, :pin_num, :sequence_id, :special_action, :created_at, :updated_at])
|> cast(params, [
:id,
:pin_num,
:sequence_id,
:special_action,
:monitor,
:created_at,
:updated_at
])
|> validate_required([])
|> validate_pin_num()
|> unique_constraint(:pin_num)

View File

@ -22,6 +22,7 @@ defmodule Farmbot.Asset.Point do
field(:x, :float)
field(:y, :float)
field(:z, :float)
field(:monitor, :boolean, default: true)
timestamps()
end
@ -53,6 +54,7 @@ defmodule Farmbot.Asset.Point do
:x,
:y,
:z,
:monitor,
:created_at,
:updated_at
])

View File

@ -28,6 +28,7 @@ defmodule Farmbot.Asset.Private.LocalMeta do
field(:status, :string)
field(:table, :string)
field(:asset_local_id, :binary_id)
field(:monitor, :boolean, default: true)
belongs_to(:device, Device,
foreign_key: :asset_local_id,

View File

@ -43,6 +43,7 @@ defmodule Farmbot.Asset.Regimen do
field(:name, :string)
embeds_many(:regimen_items, Item, on_replace: :delete)
field(:monitor, :boolean, default: true)
timestamps()
end
@ -56,7 +57,7 @@ defmodule Farmbot.Asset.Regimen do
def changeset(regimen, params \\ %{}) do
regimen
|> cast(params, [:id, :name, :created_at, :updated_at])
|> cast(params, [:id, :name, :monitor, :created_at, :updated_at])
|> cast_embed(:regimen_items)
|> validate_required([])
end

View File

@ -17,6 +17,7 @@ defmodule Farmbot.Asset.Sensor do
field(:pin, :integer)
field(:mode, :integer)
field(:label, :string)
field(:monitor, :boolean, default: true)
timestamps()
end
@ -31,7 +32,7 @@ defmodule Farmbot.Asset.Sensor do
def changeset(sensor, params \\ %{}) do
sensor
|> cast(params, [:id, :pin, :mode, :label, :created_at, :updated_at])
|> cast(params, [:id, :pin, :mode, :label, :monitor, :created_at, :updated_at])
|> validate_required([:id, :pin, :mode, :label])
end
end

View File

@ -20,6 +20,7 @@ defmodule Farmbot.Asset.SensorReading do
field(:x, :float)
field(:y, :float)
field(:z, :float)
field(:monitor, :boolean, default: true)
timestamps()
end
@ -38,7 +39,7 @@ defmodule Farmbot.Asset.SensorReading do
def changeset(sensor, params \\ %{}) do
sensor
|> cast(params, [:id, :mode, :pin, :value, :x, :y, :z, :created_at, :updated_at])
|> cast(params, [:id, :mode, :pin, :value, :x, :y, :z, :monitor, :created_at, :updated_at])
|> validate_required([])
end
end

View File

@ -18,6 +18,7 @@ defmodule Farmbot.Asset.Sequence do
field(:kind, :string)
field(:args, :map)
field(:body, {:array, :map})
field(:monitor, :boolean, default: true)
timestamps()
end
@ -33,7 +34,7 @@ defmodule Farmbot.Asset.Sequence do
def changeset(device, params \\ %{}) do
device
|> cast(params, [:id, :args, :name, :kind, :body, :created_at, :updated_at])
|> cast(params, [:id, :args, :name, :kind, :body, :monitor, :created_at, :updated_at])
|> validate_required([])
end
end

View File

@ -24,7 +24,7 @@ defmodule Farmbot.Asset.Supervisor do
Repo,
{AssetSupervisor, module: FbosConfig},
{AssetSupervisor, module: Device},
{AssetSupervisor, module: PersistentRegimen, preload: [:farm_event, :regimen]},
{AssetSupervisor, module: PersistentRegimen},
{AssetSupervisor, module: FarmEvent},
{AssetSupervisor, module: PinBinding},
{AssetSupervisor, module: Peripheral},

View File

@ -13,6 +13,7 @@ defmodule Farmbot.Asset.Tool do
)
field(:name, :string)
field(:monitor, :boolean, default: true)
timestamps()
end
@ -25,7 +26,7 @@ defmodule Farmbot.Asset.Tool do
def changeset(tool, params \\ %{}) do
tool
|> cast(params, [:id, :name, :created_at, :updated_at])
|> cast(params, [:id, :name, :monitor, :created_at, :updated_at])
|> validate_required([])
end
end

View File

@ -30,23 +30,31 @@ defmodule Farmbot.AssetMonitor do
# This is helpful for tests, but should probably be avoided
@doc false
def force_checkup do
GenServer.call(__MODULE__, :force_checkup)
GenServer.call(__MODULE__, :force_checkup, :infinity)
end
def init(_args) do
state = Map.new(order(), fn module -> {module, %{}} end)
state = Map.put(state, :order, order())
state = Map.put(state, :force_caller, nil)
state = Map.put(state, :force_callers, [])
{:ok, state, 0}
end
def handle_call(:force_checkup, caller, state) do
{:noreply, %{state | force_caller: caller}, 0}
{:noreply, %{state | force_callers: state.force_callers ++ [caller]}, 0}
end
def handle_info(:timeout, %{order: []} = state) do
if state.force_caller, do: GenServer.reply(state.force_caller, :ok)
{:noreply, %{state | order: order(), force_caller: nil}, @checkup_time_ms}
state = %{state | order: order()}
case state.force_callers do
[caller | rest] ->
GenServer.reply(caller, :ok)
{:noreply, %{state | force_callers: rest}, 0}
[] ->
{:noreply, state, @checkup_time_ms}
end
end
def handle_info(:timeout, state) do
@ -70,13 +78,19 @@ defmodule Farmbot.AssetMonitor do
Enum.reduce(expected, sub_state, fn %{local_id: id, updated_at: updated_at} = asset,
sub_state ->
cond do
asset.monitor == false ->
# Logger.debug("#{inspect(kind)} #{id} should not be monitored")
Map.put(sub_state, id, updated_at)
is_nil(sub_state[id]) ->
Logger.debug("#{inspect(kind)} #{id} needs to be started")
asset = Repo.preload(asset, Farmbot.AssetWorker.preload(asset))
Farmbot.AssetSupervisor.start_child(asset)
Map.put(sub_state, id, updated_at)
compare_datetimes(updated_at, sub_state[id]) == :gt ->
Logger.warn("#{inspect(kind)} #{id} needs to be updated")
asset = Repo.preload(asset, Farmbot.AssetWorker.preload(asset))
Farmbot.AssetSupervisor.update_child(asset)
Map.put(sub_state, id, updated_at)
@ -91,7 +105,7 @@ defmodule Farmbot.AssetMonitor do
Device,
FbosConfig,
FarmEvent,
# Peripheral,
Peripheral,
PersistentRegimen,
PinBinding,
FarmwareInstallation,

View File

@ -57,7 +57,7 @@ defmodule Farmbot.AssetSupervisor do
end
@doc false
def worker_spec(%{local_id: id} = asset) do
def worker_spec(%{local_id: id, monitor: true} = asset) do
%{
id: id,
start: {AssetWorker, :start_link, [asset]}
@ -73,11 +73,11 @@ defmodule Farmbot.AssetSupervisor do
@doc false
def init(args) do
module = Keyword.fetch!(args, :module)
preload = Keyword.get(args, :preload, [])
module
|> Repo.all()
|> Enum.map(&Repo.preload(&1, preload))
|> Enum.filter(fn %{monitor: mon} -> mon == false end)
|> Enum.map(&Repo.preload(&1, AssetWorker.preload(&1)))
|> Enum.map(&worker_spec/1)
|> Supervisor.init(strategy: :one_for_one)
end

View File

@ -1,3 +1,7 @@
defprotocol Farmbot.AssetWorker do
@doc "List of relational resources that need to be preloaded."
def preload(asset)
@doc "GenServer childspec callback."
def start_link(asset)
end

View File

@ -3,6 +3,8 @@ defimpl Farmbot.AssetWorker, for: Farmbot.Asset.Device do
use GenServer
import Farmbot.Config, only: [update_config_value: 4]
def preload(%Device{}), do: []
def start_link(%Device{} = device) do
GenServer.start_link(__MODULE__, [%Device{} = device])
end

View File

@ -1,4 +1,7 @@
defimpl Farmbot.AssetWorker, for: Farmbot.Asset.FarmEvent do
use GenServer
require Logger
alias Farmbot.{
Asset,
Asset.FarmEvent,
@ -6,9 +9,6 @@ defimpl Farmbot.AssetWorker, for: Farmbot.Asset.FarmEvent do
Asset.Sequence
}
require Logger
use GenServer
defstruct [:farm_event, :datetime]
alias __MODULE__, as: State
@ -18,12 +18,19 @@ defimpl Farmbot.AssetWorker, for: Farmbot.Asset.FarmEvent do
config :farmbot_core, #{__MODULE__}, checkup_time_ms: 10_000
""")
def preload(%FarmEvent{}), do: []
def start_link(farm_event) do
GenServer.start_link(__MODULE__, [farm_event])
end
def force_checkup(pid) when is_pid(pid) do
Logger.warn("Forcing timeout on #{inspect(pid)}")
send(pid, :timeout)
end
def init([farm_event]) do
Logger.disable(self())
# Logger.disable(self())
ensure_executable!(farm_event)
now = DateTime.utc_now()
@ -115,12 +122,12 @@ defimpl Farmbot.AssetWorker, for: Farmbot.Asset.FarmEvent do
defp ensure_executed!(%FarmEvent{last_executed: nil} = event, %Regimen{} = exe, next_dt) do
Logger.warn("Regimen: #{inspect(exe)} has not run before. Executing it.")
Asset.upsert_persistent_regimen(exe, event, %{started_at: next_dt})
Asset.upsert_persistent_regimen!(exe, event, %{started_at: next_dt})
Asset.update_farm_event!(event, %{last_executed: next_dt})
end
defp ensure_executed!(%FarmEvent{} = event, %Regimen{} = exe, _next_dt) do
Asset.upsert_persistent_regimen(exe, event)
Asset.upsert_persistent_regimen!(exe, event)
event
end

View File

@ -1,7 +1,10 @@
defimpl Farmbot.AssetWorker, for: Farmbot.Asset.FarmwareEnv do
alias Farmbot.Asset.FarmwareEnv
use GenServer
alias Farmbot.Asset.FarmwareEnv
def preload(%FarmwareEnv{}), do: []
def start_link(%FarmwareEnv{} = env) do
GenServer.start_link(__MODULE__, env)
end

View File

@ -1,13 +1,17 @@
defimpl Farmbot.AssetWorker, for: Farmbot.Asset.FarmwareInstallation do
use GenServer
require Farmbot.Logger
alias Farmbot.Asset.Repo
alias Farmbot.Asset.FarmwareInstallation, as: FWI
require Farmbot.Logger
config = Application.get_env(:farmbot_core, __MODULE__)
@install_dir config[:install_dir] || Mix.raise("Missing Install Dir")
@error_retry_time_ms config[:error_retry_time_ms] || 30_000
@manifest_name "manifest.json"
def preload(%FWI{}), do: []
def start_link(fwi) do
GenServer.start_link(__MODULE__, [fwi])
end

View File

@ -1,9 +1,12 @@
defimpl Farmbot.AssetWorker, for: Farmbot.Asset.FbosConfig do
use GenServer
require Logger
alias Farmbot.Asset.FbosConfig
import Farmbot.Config, only: [update_config_value: 4]
def preload(%FbosConfig{}), do: []
def start_link(%FbosConfig{} = fbos_config) do
GenServer.start_link(__MODULE__, [%FbosConfig{} = fbos_config])
end

View File

@ -1,10 +1,14 @@
defimpl Farmbot.AssetWorker, for: Farmbot.Asset.Peripheral do
use GenServer
require Farmbot.Logger
alias Farmbot.Asset.Peripheral
alias Farmbot.Core.CeleryScript
import Farmbot.CeleryScript.Utils
require Farmbot.Logger
@retry_ms 5_000
def preload(%Peripheral{}), do: []
def start_link(peripheral) do
GenServer.start_link(__MODULE__, [peripheral])
end

View File

@ -1,13 +1,90 @@
defimpl Farmbot.AssetWorker, for: Farmbot.Asset.PersistentRegimen do
use GenServer
require Logger
require Farmbot.Logger
import Farmbot.Config, only: [get_config_value: 3]
alias Farmbot.Asset
alias Farmbot.Asset.{PersistentRegimen, FarmEvent, Regimen}
alias Farmbot.Core.CeleryScript
@checkup_time_ms Application.get_env(:farmbot_core, __MODULE__)[:checkup_time_ms]
@checkup_time_ms ||
Mix.raise("""
config :farmbot_core, #{__MODULE__}, checkup_time_ms: 10_000
""")
def preload(%PersistentRegimen{}), do: [:farm_event, :regimen]
def start_link(persistent_regimen) do
GenServer.start_link(__MODULE__, [persistent_regimen])
end
def init([persistent_regimen]) do
{:ok, persistent_regimen}
with %Regimen{} <- persistent_regimen.regimen,
%FarmEvent{} <- persistent_regimen.farm_event do
{:ok, filter_items(persistent_regimen), 0}
else
_ -> {:stop, "Persistent Regimen not preloaded."}
end
end
def handle_info(:timeout, %PersistentRegimen{next: nil} = pr) do
persistent_regimen = filter_items(pr)
calculate_next(persistent_regimen, 0)
end
def handle_info(:timeout, %PersistentRegimen{} = pr) do
# Check if pr.next is around 2 minutes in the past
# positive if the first date/time comes after the second.
comp = Timex.diff(DateTime.utc_now(), pr.next, :minutes)
cond do
# now is more than 2 minutes past expected execution time
comp > 2 ->
Logger.warn(
"Regimen: #{pr.regimen.name || "regimen"} too late: #{comp} minutes difference."
)
calculate_next(pr)
true ->
Logger.warn(
"Regimen: #{pr.regimen.name || "regimen"} has not run before: #{comp} minutes difference."
)
exe = Asset.get_sequence!(id: pr.next_sequence_id)
CeleryScript.sequence(exe, fn _ -> :ok end)
calculate_next(pr)
end
end
defp calculate_next(pr, checkup_time_ms \\ @checkup_time_ms)
defp calculate_next(%{regimen: %{regimen_items: [next | rest]} = reg} = pr, checkup_time_ms) do
next_dt = Timex.shift(pr.epoch, milliseconds: next.time_offset)
params = %{next: next_dt, next_sequence_id: next.sequence_id}
# TODO(Connor) - This causes the active GenServer to be
# Restarted due to the `AssetMonitor`
pr = Asset.update_persistent_regimen!(pr, params)
pr = %{
pr
| regimen: %{reg | regimen_items: rest}
}
{:noreply, pr, checkup_time_ms}
end
defp calculate_next(%{regimen: %{regimen_items: []}} = pr, _) do
Farmbot.Logger.success(1, "#{pr.regimen.name || "regimen"} has no more items.")
{:noreply, pr, :hibernate}
end
defp filter_items(%PersistentRegimen{regimen: %Regimen{} = reg} = pr) do
items =
reg.regimen_items
|> Enum.sort(&(&1.time_offset <= &2.time_offset))
%{pr | regimen: %{reg | regimen_items: items}}
end
end

View File

@ -1,9 +1,8 @@
defimpl Farmbot.AssetWorker, for: Farmbot.Asset.PinBinding do
@gpio_handler Application.get_env(:farmbot_core, __MODULE__)[:gpio_handler]
@error_retry_time_ms Application.get_env(:farmbot_core, __MODULE__)[:error_retry_time_ms]
use GenServer
require Logger
require Farmbot.Logger
import Farmbot.CeleryScript.Utils
alias Farmbot.{
@ -13,6 +12,9 @@ defimpl Farmbot.AssetWorker, for: Farmbot.Asset.PinBinding do
Asset
}
@gpio_handler Application.get_env(:farmbot_core, __MODULE__)[:gpio_handler]
@error_retry_time_ms Application.get_env(:farmbot_core, __MODULE__)[:error_retry_time_ms]
@gpio_handler ||
Mix.raise("""
config :farmbot_core, #{__MODULE__}, gpio_handler: MyModule
@ -36,7 +38,7 @@ defimpl Farmbot.AssetWorker, for: Farmbot.Asset.PinBinding do
"""
@callback start_link(pin_number, trigger_fun) :: GenServer.on_start()
use GenServer
def preload(%PinBinding{}), do: []
def start_link(%PinBinding{} = pin_binding) do
GenServer.start_link(__MODULE__, [%PinBinding{} = pin_binding])

View File

@ -8,7 +8,7 @@ defmodule Farmbot.BotState.JobProgress do
defmodule Percent do
@moduledoc "Percent job."
defstruct status: :working, percent: 0, unit: :percent, type: :ota, time: nil
defstruct status: :working, percent: 0, unit: :percent, type: :ota, time: nil, file_type: nil
defimpl Inspect, for: __MODULE__ do
def inspect(%{percent: percent}, _) do
@ -28,7 +28,7 @@ defmodule Farmbot.BotState.JobProgress do
defmodule Bytes do
@moduledoc "Bytes job."
defstruct status: :working, bytes: 0, unit: :bytes, type: :ota, time: nil
defstruct status: :working, bytes: 0, unit: :bytes, type: :ota, time: nil, file_type: nil
defimpl Inspect, for: __MODULE__ do
def inspect(%{bytes: bytes}, _) do

View File

@ -96,6 +96,10 @@ defmodule FarmbotCore.MixProject do
["lib", "../test/support"]
end
defp elixirc_paths(:dev) do
["lib", "../test/support"]
end
defp elixirc_paths(_), do: ["lib"]
defp aliases,

View File

@ -7,6 +7,7 @@ defmodule Farmbot.Asset.Repo.Migrations.CreateDevicesTable do
add(:id, :id)
add(:name, :string)
add(:timezone, :string)
add(:monitor, :boolean, default: true)
timestamps(inserted_at: :created_at, type: :utc_datetime)
end
end

View File

@ -6,6 +6,7 @@ defmodule Farmbot.Asset.Repo.Migrations.CreateToolsTable do
add(:local_id, :binary_id, primary_key: true)
add(:id, :id)
add(:name, :string)
add(:monitor, :boolean, default: true)
timestamps(inserted_at: :created_at, type: :utc_datetime)
end
end

View File

@ -8,6 +8,7 @@ defmodule Farmbot.Asset.Repo.Migrations.CreatePeripheralsTable do
add(:pin, :integer)
add(:mode, :integer)
add(:label, :string)
add(:monitor, :boolean, default: true)
timestamps(inserted_at: :created_at, type: :utc_datetime)
end
end

View File

@ -8,6 +8,7 @@ defmodule Farmbot.Asset.Repo.Migrations.CreateSensorsTable do
add(:pin, :integer)
add(:mode, :integer)
add(:label, :string)
add(:monitor, :boolean, default: true)
timestamps(inserted_at: :created_at, type: :utc_datetime)
end
end

View File

@ -11,6 +11,7 @@ defmodule Farmbot.Asset.Repo.Migrations.CreateSensorReadingsTable do
add(:x, :float)
add(:y, :float)
add(:z, :float)
add(:monitor, :boolean, default: true)
timestamps(inserted_at: :created_at, type: :utc_datetime)
end
end

View File

@ -9,6 +9,7 @@ defmodule Farmbot.Asset.Repo.Migrations.CreateSequencesTable do
add(:kind, :string)
add(:args, :map)
add(:body, {:array, :map})
add(:monitor, :boolean, default: true)
timestamps(inserted_at: :created_at, type: :utc_datetime)
end
end

View File

@ -7,6 +7,7 @@ defmodule Farmbot.Asset.Repo.Migrations.CreateRegimensTable do
add(:id, :id)
add(:regimen_items, {:array, :map})
add(:name, :string)
add(:monitor, :boolean, default: true)
timestamps(inserted_at: :created_at, type: :utc_datetime)
end
end

View File

@ -8,6 +8,7 @@ defmodule Farmbot.Asset.Repo.Migrations.CreatePinBindingsTable do
add(:pin_num, :integer)
add(:sequence_id, :integer)
add(:special_action, :string)
add(:monitor, :boolean, default: true)
timestamps(inserted_at: :created_at, type: :utc_datetime)
end
end

View File

@ -14,6 +14,7 @@ defmodule Farmbot.Asset.Repo.Migrations.CreatePointsTable do
add(:x, :float)
add(:y, :float)
add(:z, :float)
add(:monitor, :boolean, default: true)
timestamps(inserted_at: :created_at, type: :utc_datetime)
end
end

View File

@ -12,6 +12,7 @@ defmodule Farmbot.Asset.Repo.Migrations.CreateFarmEventsTable do
add(:start_time, :utc_datetime)
add(:time_unit, :string)
add(:last_executed, :utc_datetime)
add(:monitor, :boolean, default: true)
timestamps(inserted_at: :created_at, type: :utc_datetime)
end
end

View File

@ -93,6 +93,7 @@ defmodule Elixir.Farmbot.Asset.Repo.Migrations.CreateFirmwareConfigsTable do
add(:encoder_invert_x, :float)
add(:encoder_missed_steps_max_x, :float)
add(:movement_invert_motor_y, :float)
add(:monitor, :boolean, default: true)
timestamps(inserted_at: :created_at, type: :utc_datetime)
end
end

View File

@ -19,6 +19,7 @@ defmodule Elixir.Farmbot.Asset.Repo.Migrations.CreateFbosConfigsTable do
add(:sequence_body_log, :boolean)
add(:sequence_complete_log, :boolean)
add(:sequence_init_log, :boolean)
add(:monitor, :boolean, default: true)
timestamps(inserted_at: :created_at, type: :utc_datetime)
end
end

View File

@ -7,6 +7,7 @@ defmodule Elixir.Farmbot.Asset.Repo.Migrations.CreateFarmwareInstallationsTable
add(:id, :id)
add(:url, :string)
add(:manifest, :map)
add(:monitor, :boolean, default: true)
timestamps(inserted_at: :created_at, type: :utc_datetime)
end
end

View File

@ -7,6 +7,7 @@ defmodule Elixir.Farmbot.Asset.Repo.Migrations.CreateFarmwareEnvsTable do
add(:id, :id)
add(:key, :string)
add(:value, :string)
add(:monitor, :boolean, default: true)
timestamps(inserted_at: :created_at, type: :utc_datetime)
end
end

View File

@ -12,6 +12,7 @@ defmodule Elixir.Farmbot.Asset.Repo.Migrations.CreateDiagnosticDumpsTable do
add(:firmware_state, :string)
add(:network_interface, :string)
add(:fbos_dmesg_dump, :string)
add(:monitor, :boolean, default: true)
timestamps(inserted_at: :created_at, type: :utc_datetime)
end
end

View File

@ -20,6 +20,7 @@ defmodule Elixir.Farmbot.Asset.Repo.Migrations.CreateSyncsTable do
add(:sequences, {:array, :map})
add(:tools, {:array, :map})
add(:now, :utc_datetime)
add(:monitor, :boolean, default: true)
timestamps(inserted_at: :created_at, type: :utc_datetime)
end
end

View File

@ -5,12 +5,17 @@ defmodule Farmbot.Asset.Repo.Migrations.CreatePersistentRegimensTable do
create table("persistent_regimens", primary_key: false) do
add(:local_id, :binary_id, primary_key: true)
add(:started_at, :utc_datetime)
add(:epoch, :utc_datetime)
add(:next, :utc_datetime)
add(:next_sequence_id, :id)
add(:regimen_id, references("regimens", type: :binary_id, column: :local_id))
add(:farm_event_id, references("farm_events", type: :binary_id, column: :local_id))
add(:monitor, :boolean, default: true)
timestamps(inserted_at: :created_at, type: :utc_datetime)
end
create(unique_index("persistent_regimens", [:local_id, :regimen_id, :farm_event_id]))
create(unique_index("persistent_regimens", :started_at))
create(unique_index("persistent_regimens", :epoch))
end
end

View File

@ -1,5 +1,5 @@
defmodule Farmbot.AssetMonitorTest do
use ExUnit.Case
use ExUnit.Case, async: false
alias Farmbot.Asset
alias Farmbot.AssetSupervisor
import Farmbot.TestSupport.AssetFixtures
@ -10,7 +10,8 @@ defmodule Farmbot.AssetMonitorTest do
seq = sequence()
reg = regimen(%{regimen_items: [%{time_offset: 100, sequence_id: seq.id}]})
event = regimen_event(reg)
{:ok, pr} = Asset.upsert_persistent_regimen(reg, event)
pr = Asset.upsert_persistent_regimen!(reg, event)
pr = Asset.update_persistent_regimen!(pr, %{monitor: true})
Farmbot.AssetMonitor.force_checkup()
@ -34,6 +35,7 @@ defmodule Farmbot.AssetMonitorTest do
end_time = Timex.shift(now, minutes: 10)
params = %{
monitor: true,
start_time: start_time,
end_time: end_time,
repeat: 1,

View File

@ -1,5 +1,5 @@
defmodule Farmbot.AssetTest do
use ExUnit.Case
use ExUnit.Case, async: true
alias Farmbot.Asset.{Repo, Regimen, PersistentRegimen}
alias Farmbot.Asset
import Farmbot.TestSupport.AssetFixtures
@ -9,17 +9,17 @@ defmodule Farmbot.AssetTest do
seq = sequence()
reg = regimen(%{regimen_items: [%{time_offset: 100, sequence_id: seq.id}]})
event = regimen_event(reg)
assert {:ok, %PersistentRegimen{}} = Asset.upsert_persistent_regimen(reg, event)
assert %PersistentRegimen{} = Asset.upsert_persistent_regimen!(reg, event)
end
test "updates a persisten regimen" do
seq = sequence()
reg = regimen(%{name: "old", regimen_items: [%{time_offset: 100, sequence_id: seq.id}]})
event = regimen_event(reg)
{:ok, pr} = Asset.upsert_persistent_regimen(reg, event)
pr = Asset.upsert_persistent_regimen!(reg, event)
assert pr.regimen.name == "old"
reg = Regimen.changeset(reg, %{name: "new"}) |> Repo.update!()
{:ok, pr} = Asset.upsert_persistent_regimen(reg, event)
pr = Asset.upsert_persistent_regimen!(reg, event)
assert pr.regimen.name == "new"
end
end

View File

@ -1,31 +1,12 @@
defmodule Farmbot.FarmEventWorkerTest do
use ExUnit.Case
use ExUnit.Case, async: true
alias Farmbot.Asset.FarmEvent
import Farmbot.TestSupport.AssetFixtures
alias Farmbot.TestSupport.CeleryScript.TestIOLayer
import Farmbot.TestSupport
describe "regimens" do
test "always ensure a regimen is started" do
seq = sequence()
reg = regimen(%{regimen_items: [%{time_offset: 100, sequence_id: seq.id}]})
now = DateTime.utc_now()
start_time = Timex.shift(now, minutes: -20)
end_time = Timex.shift(now, minutes: 10)
params = %{
start_time: start_time,
end_time: end_time,
repeat: 1,
time_unit: "never"
}
_event = regimen_event(reg, params)
refute :lookup_persistent_regimen_after_sleep?
end
end
# Regimen tests are in the PersistentRegimeWorker test
describe "sequences" do
# TODO(Connor) - this test isn't really that good
@ -45,9 +26,12 @@ defmodule Farmbot.FarmEventWorkerTest do
time_unit: "never"
}
assert %FarmEvent{} = sequence_event(seq, params)
assert %FarmEvent{} = fe = sequence_event(seq, params)
{:ok, pid} = Farmbot.AssetWorker.start_link(fe)
Farmbot.AssetWorker.Farmbot.Asset.FarmEvent.force_checkup(pid)
# This is not really that useful.
refute_receive ^ast, farm_event_timeout()
refute_receive ^ast, farm_event_timeout() + 5000
end
end
@ -67,8 +51,10 @@ defmodule Farmbot.FarmEventWorkerTest do
time_unit: "minutely"
}
assert %FarmEvent{} = sequence_event(seq, params)
assert_receive ^ast, farm_event_timeout()
assert %FarmEvent{} = fe = sequence_event(seq, params)
{:ok, pid} = Farmbot.AssetWorker.start_link(fe)
Farmbot.AssetWorker.Farmbot.Asset.FarmEvent.force_checkup(pid)
assert_receive ^ast, farm_event_timeout() + 5000
end
test "wont start an event after end_time" do
@ -86,7 +72,9 @@ defmodule Farmbot.FarmEventWorkerTest do
time_unit: "minutely"
}
assert %FarmEvent{} = sequence_event(seq, params)
assert %FarmEvent{} = fe = sequence_event(seq, params)
{:ok, pid} = Farmbot.AssetWorker.start_link(fe)
Farmbot.AssetWorker.Farmbot.Asset.FarmEvent.force_checkup(pid)
# This is not really that useful.
refute_receive ^ast
end

View File

@ -1,10 +1,14 @@
defmodule Farmbot.FbosConfigWorkerTest do
use ExUnit.Case
use ExUnit.Case, async: true
alias Farmbot.Asset.FbosConfig
import Farmbot.TestSupport.AssetFixtures
test "adds configs to bot state and config_storage" do
conf =
FbosConfig.changeset(%FbosConfig{}, %{
%FbosConfig{} =
conf =
fbos_config(%{
monitor: true,
arduino_debug_messages: true,
auto_sync: false,
beta_opt_in: true,
@ -12,19 +16,18 @@ defmodule Farmbot.FbosConfigWorkerTest do
firmware_hardware: "farmduino_k14",
firmware_input_log: false,
firmware_output_log: false,
id: 145,
network_not_found_timer: nil,
os_auto_update: false,
sequence_body_log: true,
sequence_complete_log: true,
sequence_init_log: true
})
|> Farmbot.Asset.Repo.insert!()
:ok = Farmbot.AssetMonitor.force_checkup()
{:ok, pid} = Farmbot.AssetWorker.start_link(conf)
send(pid, :timeout)
# Wait for the timeout to be dispatched
Process.sleep(100)
Process.sleep(200)
state_conf = Farmbot.BotState.fetch().configuration
assert state_conf.arduino_debug_messages == conf.arduino_debug_messages

View File

@ -0,0 +1,34 @@
defmodule Farmbot.PersistentRegimenWorkerTest do
use ExUnit.Case, async: true
alias Farmbot.{Asset.FarmEvent, Asset.PersistentRegimen}
import Farmbot.TestSupport.AssetFixtures
alias Farmbot.TestSupport.CeleryScript.TestIOLayer
import Farmbot.TestSupport
test "regimen executes a sequence" do
now = DateTime.utc_now()
start_time = Timex.shift(now, minutes: -20)
end_time = Timex.shift(now, minutes: 10)
{:ok, epoch} = PersistentRegimen.build_epoch(now)
offset = Timex.diff(now, epoch, :milliseconds) + 500
TestIOLayer.subscribe()
ast = TestIOLayer.debug_ast()
seq = sequence(%{body: [ast]})
reg = regimen(%{regimen_items: [%{time_offset: offset, sequence_id: seq.id}]})
params = %{
start_time: start_time,
end_time: end_time,
repeat: 1,
time_unit: "never"
}
assert %FarmEvent{} = fe = regimen_event(reg, params)
{:ok, pid} = Farmbot.AssetWorker.start_link(fe)
Farmbot.AssetWorker.Farmbot.Asset.FarmEvent.force_checkup(pid)
assert_receive ^ast, farm_event_timeout() + persistent_regimen_timeout() + 5000
end
end

View File

@ -4,4 +4,9 @@
# Ecto.Adapters.SQL.Sandbox.mode(Farmbot.Config.Repo, :auto)
# Ecto.Adapters.SQL.Sandbox.mode(Farmbot.Logger.Repo, {:shared, self()})
# Ecto.Adapters.SQL.Sandbox.mode(Farmbot.Asset.Repo, :auto)
tz = Timex.local().time_zone
Farmbot.Asset.Device.changeset(Farmbot.Asset.device(), %{timezone: tz})
|> Farmbot.Asset.Repo.insert_or_update!()
ExUnit.start()

View File

@ -1,5 +1,6 @@
use Mix.Config
config :farmbot_core, Farmbot.AssetWorker.Farmbot.Asset.FarmEvent, checkup_time_ms: 10_000
config :farmbot_core, Farmbot.AssetWorker.Farmbot.Asset.PersistentRegimen, checkup_time_ms: 10_000
config :farmbot_core, Elixir.Farmbot.AssetWorker.Farmbot.Asset.PinBinding,
gpio_handler: Farmbot.PinBindingWorker.StubGPIOHandler,

View File

@ -1,6 +1,7 @@
use Mix.Config
config :farmbot_core, Farmbot.AssetWorker.Farmbot.Asset.FarmEvent, checkup_time_ms: 10_000
config :farmbot_core, Farmbot.AssetWorker.Farmbot.Asset.PersistentRegimen, checkup_time_ms: 10_000
config :farmbot_core, Farmbot.AssetWorker.Farmbot.Asset.FarmwareInstallation,
error_retry_time_ms: 30_000,

View File

@ -1,19 +1,39 @@
defmodule Farmbot.TestSupport.AssetFixtures do
alias Farmbot.Asset.{Repo, Sequence, Regimen, FarmEvent}
alias Farmbot.Asset.{Repo, FarmEvent, FbosConfig, Regimen, Sequence}
def fbos_config(params \\ %{}) do
default = %{
id: :rand.uniform(10000),
monitor: false
}
FbosConfig
|> struct()
|> FbosConfig.changeset(Map.merge(default, params))
|> Repo.insert!()
end
def sequence(params \\ %{}) do
default = %{
id: :rand.uniform(10000),
monitor: false,
kind: "sequence",
args: %{},
body: []
}
Sequence
|> struct()
|> Sequence.changeset(
Map.merge(%{id: :rand.uniform(10000), kind: "sequence", args: %{}, body: []}, params)
)
|> Sequence.changeset(Map.merge(default, params))
|> Repo.insert!()
end
def regimen(params \\ %{}) do
default = %{id: :rand.uniform(10000), monitor: false, regimen_items: []}
Regimen
|> struct()
|> Regimen.changeset(Map.merge(%{id: :rand.uniform(10000), regimen_items: []}, params))
|> Regimen.changeset(Map.merge(default, params))
|> Repo.insert!()
end
@ -24,6 +44,7 @@ defmodule Farmbot.TestSupport.AssetFixtures do
Map.merge(
%{
id: :rand.uniform(1_000_000),
monitor: false,
executable_type: "Regimen",
executable_id: regimen.id,
start_time: now,
@ -47,6 +68,7 @@ defmodule Farmbot.TestSupport.AssetFixtures do
Map.merge(
%{
id: :rand.uniform(1_000_000),
monitor: false,
executable_type: "Sequence",
executable_id: sequence.id,
start_time: now,

View File

@ -1,15 +1,18 @@
defmodule Farmbot.TestSupport do
alias Farmbot.AssetWorker.Farmbot.Asset.{FarmEvent, PersistentRegimen}
def farm_event_timeout do
Application.get_env(:farmbot_core, Farmbot.AssetWorker.Farmbot.Asset.FarmEvent)[
Application.get_env(:farmbot_core, FarmEvent)[
:checkup_time_ms
] + asset_monitor_timeout()
]
end
def persistent_regimen_timeout do
Application.get_env(:farmbot_core, PersistentRegimen)[
:checkup_time_ms
]
end
def asset_monitor_timeout do
Application.get_env(:farmbot_core, Farmbot.AssetMonitor)[:checkup_time_ms] + grace()
end
def grace do
5000
Application.get_env(:farmbot_core, Farmbot.AssetMonitor)[:checkup_time_ms]
end
end