From 6e2a0185989326541d58930a8b647186728d98c8 Mon Sep 17 00:00:00 2001 From: Connor Rigby Date: Mon, 19 Nov 2018 11:32:54 -0800 Subject: [PATCH] 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. --- farmbot_core/.iex.exs | 2 + farmbot_core/config/config.exs | 1 + farmbot_core/config/test.exs | 1 + farmbot_core/lib/asset.ex | 10 ++- farmbot_core/lib/asset/device.ex | 3 +- farmbot_core/lib/asset/diagnostic_dump.ex | 2 + farmbot_core/lib/asset/farm_event.ex | 2 + farmbot_core/lib/asset/farmware_env.ex | 3 +- .../lib/asset/farmware_installation.ex | 3 +- farmbot_core/lib/asset/fbos_config.ex | 2 + farmbot_core/lib/asset/firmware_config.ex | 2 + farmbot_core/lib/asset/peripheral.ex | 3 +- farmbot_core/lib/asset/persistent_regimen.ex | 39 ++++++++- farmbot_core/lib/asset/pin_binding.ex | 11 ++- farmbot_core/lib/asset/point.ex | 2 + farmbot_core/lib/asset/private/local_meta.ex | 1 + farmbot_core/lib/asset/regimen.ex | 3 +- farmbot_core/lib/asset/sensor.ex | 3 +- farmbot_core/lib/asset/sensor_reading.ex | 3 +- farmbot_core/lib/asset/sequence.ex | 3 +- farmbot_core/lib/asset/supervisor.ex | 2 +- farmbot_core/lib/asset/tool.ex | 3 +- farmbot_core/lib/asset_monitor.ex | 26 ++++-- farmbot_core/lib/asset_supervisor.ex | 6 +- farmbot_core/lib/asset_worker.ex | 4 + .../lib/asset_workers/device_worker.ex | 2 + .../lib/asset_workers/farm_event_worker.ex | 19 +++-- .../lib/asset_workers/farmware_env_worker.ex | 5 +- .../farmware_installation_worker.ex | 6 +- .../lib/asset_workers/fbos_config_worker.ex | 3 + .../lib/asset_workers/peripheral_worker.ex | 6 +- .../persistent_regimen_worker.ex | 81 ++++++++++++++++++- .../lib/asset_workers/pin_binding_worker.ex | 10 ++- farmbot_core/lib/bot_state/job_progress.ex | 4 +- farmbot_core/mix.exs | 4 + .../20181017210735_create_devices_table.exs | 1 + .../20181017223659_create_tools_table.exs | 1 + ...0181017225445_create_peripherals_table.exs | 1 + .../20181017230326_create_sensors_table.exs | 1 + ...017230333_create_sensor_readings_table.exs | 1 + .../20181018005030_create_sequences_table.exs | 1 + .../20181018022229_create_regimens_table.exs | 1 + ...181018040229_create_pin_bindings_table.exs | 1 + .../20181019165701_create_points_table.exs | 1 + ...0181019172740_create_farm_events_table.exs | 1 + ...19180739_create_firmware_configs_table.exs | 1 + ...181019180816_create_fbos_configs_table.exs | 1 + ...11_create_farmware_installations_table.exs | 1 + ...81019180925_create_farmware_envs_table.exs | 1 + ...19180955_create_diagnostic_dumps_table.exs | 1 + .../20181022002449_create_syncs_table.exs | 1 + ...02450_create_persistent_regimens_table.exs | 5 ++ farmbot_core/test/asset_monitor_test.exs | 6 +- farmbot_core/test/asset_test.exs | 8 +- .../asset_workers/farm_event_worker_test.exs | 40 ++++----- .../asset_workers/fbos_config_worker_test.exs | 17 ++-- .../persistent_regimen_worker_test.exs | 34 ++++++++ farmbot_core/test/test_helper.exs | 5 ++ farmbot_ext/config/farmbot_core.exs | 1 + farmbot_os/config/config.exs | 1 + test/support/asset_fixtures.ex | 32 ++++++-- test/support/test_support.ex | 17 ++-- 62 files changed, 371 insertions(+), 91 deletions(-) create mode 100644 farmbot_core/test/asset_workers/persistent_regimen_worker_test.exs diff --git a/farmbot_core/.iex.exs b/farmbot_core/.iex.exs index 92b70905..a0cf71f2 100644 --- a/farmbot_core/.iex.exs +++ b/farmbot_core/.iex.exs @@ -12,3 +12,5 @@ alias Farmbot.Asset.{ PersistentRegimen, Sequence } + +alias Farmbot.TestSupport.AssetFixtures diff --git a/farmbot_core/config/config.exs b/farmbot_core/config/config.exs index efae108c..18fb80ac 100644 --- a/farmbot_core/config/config.exs +++ b/farmbot_core/config/config.exs @@ -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, diff --git a/farmbot_core/config/test.exs b/farmbot_core/config/test.exs index 427e8275..08064fb6 100644 --- a/farmbot_core/config/test.exs +++ b/farmbot_core/config/test.exs @@ -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 diff --git a/farmbot_core/lib/asset.ex b/farmbot_core/lib/asset.ex index 64c3a776..bbb7658f 100644 --- a/farmbot_core/lib/asset.ex +++ b/farmbot_core/lib/asset.ex @@ -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 diff --git a/farmbot_core/lib/asset/device.ex b/farmbot_core/lib/asset/device.ex index d8fc91fc..bac77035 100644 --- a/farmbot_core/lib/asset/device.ex +++ b/farmbot_core/lib/asset/device.ex @@ -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 diff --git a/farmbot_core/lib/asset/diagnostic_dump.ex b/farmbot_core/lib/asset/diagnostic_dump.ex index 489ff6d5..59ea06a0 100644 --- a/farmbot_core/lib/asset/diagnostic_dump.ex +++ b/farmbot_core/lib/asset/diagnostic_dump.ex @@ -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 ]) diff --git a/farmbot_core/lib/asset/farm_event.ex b/farmbot_core/lib/asset/farm_event.ex index 47122584..8d623af2 100644 --- a/farmbot_core/lib/asset/farm_event.ex +++ b/farmbot_core/lib/asset/farm_event.ex @@ -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 ]) diff --git a/farmbot_core/lib/asset/farmware_env.ex b/farmbot_core/lib/asset/farmware_env.ex index 48076392..2471ba28 100644 --- a/farmbot_core/lib/asset/farmware_env.ex +++ b/farmbot_core/lib/asset/farmware_env.ex @@ -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 diff --git a/farmbot_core/lib/asset/farmware_installation.ex b/farmbot_core/lib/asset/farmware_installation.ex index 72ec90e3..deba2212 100644 --- a/farmbot_core/lib/asset/farmware_installation.ex +++ b/farmbot_core/lib/asset/farmware_installation.ex @@ -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 diff --git a/farmbot_core/lib/asset/fbos_config.ex b/farmbot_core/lib/asset/fbos_config.ex index 438fbe78..655e6f52 100644 --- a/farmbot_core/lib/asset/fbos_config.ex +++ b/farmbot_core/lib/asset/fbos_config.ex @@ -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 ]) diff --git a/farmbot_core/lib/asset/firmware_config.ex b/farmbot_core/lib/asset/firmware_config.ex index a299927b..b31d4f92 100644 --- a/farmbot_core/lib/asset/firmware_config.ex +++ b/farmbot_core/lib/asset/firmware_config.ex @@ -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 ]) diff --git a/farmbot_core/lib/asset/peripheral.ex b/farmbot_core/lib/asset/peripheral.ex index 06bba9b8..4e0c3145 100644 --- a/farmbot_core/lib/asset/peripheral.ex +++ b/farmbot_core/lib/asset/peripheral.ex @@ -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 diff --git a/farmbot_core/lib/asset/persistent_regimen.ex b/farmbot_core/lib/asset/persistent_regimen.ex index 179acb23..751c8978 100644 --- a/farmbot_core/lib/asset/persistent_regimen.ex +++ b/farmbot_core/lib/asset/persistent_regimen.ex @@ -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 diff --git a/farmbot_core/lib/asset/pin_binding.ex b/farmbot_core/lib/asset/pin_binding.ex index f450e05d..3ca9e94c 100644 --- a/farmbot_core/lib/asset/pin_binding.ex +++ b/farmbot_core/lib/asset/pin_binding.ex @@ -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) diff --git a/farmbot_core/lib/asset/point.ex b/farmbot_core/lib/asset/point.ex index 5401fff9..24bb0943 100644 --- a/farmbot_core/lib/asset/point.ex +++ b/farmbot_core/lib/asset/point.ex @@ -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 ]) diff --git a/farmbot_core/lib/asset/private/local_meta.ex b/farmbot_core/lib/asset/private/local_meta.ex index ee95bff0..48da0cac 100644 --- a/farmbot_core/lib/asset/private/local_meta.ex +++ b/farmbot_core/lib/asset/private/local_meta.ex @@ -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, diff --git a/farmbot_core/lib/asset/regimen.ex b/farmbot_core/lib/asset/regimen.ex index 6d8e8fdf..f4cebdf2 100644 --- a/farmbot_core/lib/asset/regimen.ex +++ b/farmbot_core/lib/asset/regimen.ex @@ -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 diff --git a/farmbot_core/lib/asset/sensor.ex b/farmbot_core/lib/asset/sensor.ex index 738294eb..65566d33 100644 --- a/farmbot_core/lib/asset/sensor.ex +++ b/farmbot_core/lib/asset/sensor.ex @@ -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 diff --git a/farmbot_core/lib/asset/sensor_reading.ex b/farmbot_core/lib/asset/sensor_reading.ex index 685f6313..03544dd4 100644 --- a/farmbot_core/lib/asset/sensor_reading.ex +++ b/farmbot_core/lib/asset/sensor_reading.ex @@ -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 diff --git a/farmbot_core/lib/asset/sequence.ex b/farmbot_core/lib/asset/sequence.ex index 18393349..b8f5f621 100644 --- a/farmbot_core/lib/asset/sequence.ex +++ b/farmbot_core/lib/asset/sequence.ex @@ -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 diff --git a/farmbot_core/lib/asset/supervisor.ex b/farmbot_core/lib/asset/supervisor.ex index 37967b18..d0753ca0 100644 --- a/farmbot_core/lib/asset/supervisor.ex +++ b/farmbot_core/lib/asset/supervisor.ex @@ -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}, diff --git a/farmbot_core/lib/asset/tool.ex b/farmbot_core/lib/asset/tool.ex index ecfda1b1..db683da2 100644 --- a/farmbot_core/lib/asset/tool.ex +++ b/farmbot_core/lib/asset/tool.ex @@ -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 diff --git a/farmbot_core/lib/asset_monitor.ex b/farmbot_core/lib/asset_monitor.ex index 7ac754f2..d600235a 100644 --- a/farmbot_core/lib/asset_monitor.ex +++ b/farmbot_core/lib/asset_monitor.ex @@ -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, diff --git a/farmbot_core/lib/asset_supervisor.ex b/farmbot_core/lib/asset_supervisor.ex index d87ae67c..e7b19b30 100644 --- a/farmbot_core/lib/asset_supervisor.ex +++ b/farmbot_core/lib/asset_supervisor.ex @@ -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 diff --git a/farmbot_core/lib/asset_worker.ex b/farmbot_core/lib/asset_worker.ex index 98e7e1d5..f9a6174e 100644 --- a/farmbot_core/lib/asset_worker.ex +++ b/farmbot_core/lib/asset_worker.ex @@ -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 diff --git a/farmbot_core/lib/asset_workers/device_worker.ex b/farmbot_core/lib/asset_workers/device_worker.ex index 27317a2c..82802ce8 100644 --- a/farmbot_core/lib/asset_workers/device_worker.ex +++ b/farmbot_core/lib/asset_workers/device_worker.ex @@ -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 diff --git a/farmbot_core/lib/asset_workers/farm_event_worker.ex b/farmbot_core/lib/asset_workers/farm_event_worker.ex index 23a389e6..7040eb48 100644 --- a/farmbot_core/lib/asset_workers/farm_event_worker.ex +++ b/farmbot_core/lib/asset_workers/farm_event_worker.ex @@ -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 diff --git a/farmbot_core/lib/asset_workers/farmware_env_worker.ex b/farmbot_core/lib/asset_workers/farmware_env_worker.ex index c316c379..57b7da65 100644 --- a/farmbot_core/lib/asset_workers/farmware_env_worker.ex +++ b/farmbot_core/lib/asset_workers/farmware_env_worker.ex @@ -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 diff --git a/farmbot_core/lib/asset_workers/farmware_installation_worker.ex b/farmbot_core/lib/asset_workers/farmware_installation_worker.ex index 2e798c89..23760b5f 100644 --- a/farmbot_core/lib/asset_workers/farmware_installation_worker.ex +++ b/farmbot_core/lib/asset_workers/farmware_installation_worker.ex @@ -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 diff --git a/farmbot_core/lib/asset_workers/fbos_config_worker.ex b/farmbot_core/lib/asset_workers/fbos_config_worker.ex index 39945716..8ebf96ac 100644 --- a/farmbot_core/lib/asset_workers/fbos_config_worker.ex +++ b/farmbot_core/lib/asset_workers/fbos_config_worker.ex @@ -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 diff --git a/farmbot_core/lib/asset_workers/peripheral_worker.ex b/farmbot_core/lib/asset_workers/peripheral_worker.ex index e50add0f..1862ab13 100644 --- a/farmbot_core/lib/asset_workers/peripheral_worker.ex +++ b/farmbot_core/lib/asset_workers/peripheral_worker.ex @@ -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 diff --git a/farmbot_core/lib/asset_workers/persistent_regimen_worker.ex b/farmbot_core/lib/asset_workers/persistent_regimen_worker.ex index a2cf4b5a..657be126 100644 --- a/farmbot_core/lib/asset_workers/persistent_regimen_worker.ex +++ b/farmbot_core/lib/asset_workers/persistent_regimen_worker.ex @@ -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 diff --git a/farmbot_core/lib/asset_workers/pin_binding_worker.ex b/farmbot_core/lib/asset_workers/pin_binding_worker.ex index dc0de88e..e7e6b856 100644 --- a/farmbot_core/lib/asset_workers/pin_binding_worker.ex +++ b/farmbot_core/lib/asset_workers/pin_binding_worker.ex @@ -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]) diff --git a/farmbot_core/lib/bot_state/job_progress.ex b/farmbot_core/lib/bot_state/job_progress.ex index 70b33b79..ff57a7da 100644 --- a/farmbot_core/lib/bot_state/job_progress.ex +++ b/farmbot_core/lib/bot_state/job_progress.ex @@ -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 diff --git a/farmbot_core/mix.exs b/farmbot_core/mix.exs index 01c9ae7c..a8b83422 100644 --- a/farmbot_core/mix.exs +++ b/farmbot_core/mix.exs @@ -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, diff --git a/farmbot_core/priv/asset/migrations/20181017210735_create_devices_table.exs b/farmbot_core/priv/asset/migrations/20181017210735_create_devices_table.exs index e3363afd..425cfccf 100644 --- a/farmbot_core/priv/asset/migrations/20181017210735_create_devices_table.exs +++ b/farmbot_core/priv/asset/migrations/20181017210735_create_devices_table.exs @@ -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 diff --git a/farmbot_core/priv/asset/migrations/20181017223659_create_tools_table.exs b/farmbot_core/priv/asset/migrations/20181017223659_create_tools_table.exs index 7c012f9f..f76372dd 100644 --- a/farmbot_core/priv/asset/migrations/20181017223659_create_tools_table.exs +++ b/farmbot_core/priv/asset/migrations/20181017223659_create_tools_table.exs @@ -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 diff --git a/farmbot_core/priv/asset/migrations/20181017225445_create_peripherals_table.exs b/farmbot_core/priv/asset/migrations/20181017225445_create_peripherals_table.exs index 5722e93b..e24829b3 100644 --- a/farmbot_core/priv/asset/migrations/20181017225445_create_peripherals_table.exs +++ b/farmbot_core/priv/asset/migrations/20181017225445_create_peripherals_table.exs @@ -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 diff --git a/farmbot_core/priv/asset/migrations/20181017230326_create_sensors_table.exs b/farmbot_core/priv/asset/migrations/20181017230326_create_sensors_table.exs index b57d3f54..a15a9d8d 100644 --- a/farmbot_core/priv/asset/migrations/20181017230326_create_sensors_table.exs +++ b/farmbot_core/priv/asset/migrations/20181017230326_create_sensors_table.exs @@ -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 diff --git a/farmbot_core/priv/asset/migrations/20181017230333_create_sensor_readings_table.exs b/farmbot_core/priv/asset/migrations/20181017230333_create_sensor_readings_table.exs index 7440941b..53d4ef04 100644 --- a/farmbot_core/priv/asset/migrations/20181017230333_create_sensor_readings_table.exs +++ b/farmbot_core/priv/asset/migrations/20181017230333_create_sensor_readings_table.exs @@ -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 diff --git a/farmbot_core/priv/asset/migrations/20181018005030_create_sequences_table.exs b/farmbot_core/priv/asset/migrations/20181018005030_create_sequences_table.exs index 6607a515..9d1cbf80 100644 --- a/farmbot_core/priv/asset/migrations/20181018005030_create_sequences_table.exs +++ b/farmbot_core/priv/asset/migrations/20181018005030_create_sequences_table.exs @@ -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 diff --git a/farmbot_core/priv/asset/migrations/20181018022229_create_regimens_table.exs b/farmbot_core/priv/asset/migrations/20181018022229_create_regimens_table.exs index 36728058..477917a1 100644 --- a/farmbot_core/priv/asset/migrations/20181018022229_create_regimens_table.exs +++ b/farmbot_core/priv/asset/migrations/20181018022229_create_regimens_table.exs @@ -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 diff --git a/farmbot_core/priv/asset/migrations/20181018040229_create_pin_bindings_table.exs b/farmbot_core/priv/asset/migrations/20181018040229_create_pin_bindings_table.exs index b927bdbe..6ad5b2db 100644 --- a/farmbot_core/priv/asset/migrations/20181018040229_create_pin_bindings_table.exs +++ b/farmbot_core/priv/asset/migrations/20181018040229_create_pin_bindings_table.exs @@ -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 diff --git a/farmbot_core/priv/asset/migrations/20181019165701_create_points_table.exs b/farmbot_core/priv/asset/migrations/20181019165701_create_points_table.exs index 1c3f4750..5fbccd43 100644 --- a/farmbot_core/priv/asset/migrations/20181019165701_create_points_table.exs +++ b/farmbot_core/priv/asset/migrations/20181019165701_create_points_table.exs @@ -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 diff --git a/farmbot_core/priv/asset/migrations/20181019172740_create_farm_events_table.exs b/farmbot_core/priv/asset/migrations/20181019172740_create_farm_events_table.exs index c47e6446..01cc3f80 100644 --- a/farmbot_core/priv/asset/migrations/20181019172740_create_farm_events_table.exs +++ b/farmbot_core/priv/asset/migrations/20181019172740_create_farm_events_table.exs @@ -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 diff --git a/farmbot_core/priv/asset/migrations/20181019180739_create_firmware_configs_table.exs b/farmbot_core/priv/asset/migrations/20181019180739_create_firmware_configs_table.exs index 325a645a..202fe666 100644 --- a/farmbot_core/priv/asset/migrations/20181019180739_create_firmware_configs_table.exs +++ b/farmbot_core/priv/asset/migrations/20181019180739_create_firmware_configs_table.exs @@ -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 diff --git a/farmbot_core/priv/asset/migrations/20181019180816_create_fbos_configs_table.exs b/farmbot_core/priv/asset/migrations/20181019180816_create_fbos_configs_table.exs index e1c475a1..4ec1321f 100644 --- a/farmbot_core/priv/asset/migrations/20181019180816_create_fbos_configs_table.exs +++ b/farmbot_core/priv/asset/migrations/20181019180816_create_fbos_configs_table.exs @@ -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 diff --git a/farmbot_core/priv/asset/migrations/20181019180911_create_farmware_installations_table.exs b/farmbot_core/priv/asset/migrations/20181019180911_create_farmware_installations_table.exs index 01cc3b7f..70e1d3cf 100644 --- a/farmbot_core/priv/asset/migrations/20181019180911_create_farmware_installations_table.exs +++ b/farmbot_core/priv/asset/migrations/20181019180911_create_farmware_installations_table.exs @@ -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 diff --git a/farmbot_core/priv/asset/migrations/20181019180925_create_farmware_envs_table.exs b/farmbot_core/priv/asset/migrations/20181019180925_create_farmware_envs_table.exs index b6eca102..8b356abf 100644 --- a/farmbot_core/priv/asset/migrations/20181019180925_create_farmware_envs_table.exs +++ b/farmbot_core/priv/asset/migrations/20181019180925_create_farmware_envs_table.exs @@ -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 diff --git a/farmbot_core/priv/asset/migrations/20181019180955_create_diagnostic_dumps_table.exs b/farmbot_core/priv/asset/migrations/20181019180955_create_diagnostic_dumps_table.exs index bd424193..d1978314 100644 --- a/farmbot_core/priv/asset/migrations/20181019180955_create_diagnostic_dumps_table.exs +++ b/farmbot_core/priv/asset/migrations/20181019180955_create_diagnostic_dumps_table.exs @@ -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 diff --git a/farmbot_core/priv/asset/migrations/20181022002449_create_syncs_table.exs b/farmbot_core/priv/asset/migrations/20181022002449_create_syncs_table.exs index 77f5d8b4..38eb9872 100644 --- a/farmbot_core/priv/asset/migrations/20181022002449_create_syncs_table.exs +++ b/farmbot_core/priv/asset/migrations/20181022002449_create_syncs_table.exs @@ -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 diff --git a/farmbot_core/priv/asset/migrations/20181022002450_create_persistent_regimens_table.exs b/farmbot_core/priv/asset/migrations/20181022002450_create_persistent_regimens_table.exs index 97436fc9..5ea705c8 100644 --- a/farmbot_core/priv/asset/migrations/20181022002450_create_persistent_regimens_table.exs +++ b/farmbot_core/priv/asset/migrations/20181022002450_create_persistent_regimens_table.exs @@ -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 diff --git a/farmbot_core/test/asset_monitor_test.exs b/farmbot_core/test/asset_monitor_test.exs index ec96b12d..44f9a4ad 100644 --- a/farmbot_core/test/asset_monitor_test.exs +++ b/farmbot_core/test/asset_monitor_test.exs @@ -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, diff --git a/farmbot_core/test/asset_test.exs b/farmbot_core/test/asset_test.exs index 41867a4b..502e5025 100644 --- a/farmbot_core/test/asset_test.exs +++ b/farmbot_core/test/asset_test.exs @@ -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 diff --git a/farmbot_core/test/asset_workers/farm_event_worker_test.exs b/farmbot_core/test/asset_workers/farm_event_worker_test.exs index d5468b2e..1d4a4e97 100644 --- a/farmbot_core/test/asset_workers/farm_event_worker_test.exs +++ b/farmbot_core/test/asset_workers/farm_event_worker_test.exs @@ -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 diff --git a/farmbot_core/test/asset_workers/fbos_config_worker_test.exs b/farmbot_core/test/asset_workers/fbos_config_worker_test.exs index 49f7fe7f..2b55b83e 100644 --- a/farmbot_core/test/asset_workers/fbos_config_worker_test.exs +++ b/farmbot_core/test/asset_workers/fbos_config_worker_test.exs @@ -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 diff --git a/farmbot_core/test/asset_workers/persistent_regimen_worker_test.exs b/farmbot_core/test/asset_workers/persistent_regimen_worker_test.exs new file mode 100644 index 00000000..8f30f3a8 --- /dev/null +++ b/farmbot_core/test/asset_workers/persistent_regimen_worker_test.exs @@ -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 diff --git a/farmbot_core/test/test_helper.exs b/farmbot_core/test/test_helper.exs index 07430222..9c22c7b9 100644 --- a/farmbot_core/test/test_helper.exs +++ b/farmbot_core/test/test_helper.exs @@ -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() diff --git a/farmbot_ext/config/farmbot_core.exs b/farmbot_ext/config/farmbot_core.exs index 0449677c..01c80c34 100644 --- a/farmbot_ext/config/farmbot_core.exs +++ b/farmbot_ext/config/farmbot_core.exs @@ -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, diff --git a/farmbot_os/config/config.exs b/farmbot_os/config/config.exs index f0c4e48f..90d2478a 100644 --- a/farmbot_os/config/config.exs +++ b/farmbot_os/config/config.exs @@ -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, diff --git a/test/support/asset_fixtures.ex b/test/support/asset_fixtures.ex index 4c9a66c1..c73b82c2 100644 --- a/test/support/asset_fixtures.ex +++ b/test/support/asset_fixtures.ex @@ -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, diff --git a/test/support/test_support.ex b/test/support/test_support.ex index 8e9706ce..0ed412c2 100644 --- a/test/support/test_support.ex +++ b/test/support/test_support.ex @@ -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