Merge pull request #1148 from FarmBot/rel-9.1.0

Release v9.1.0
pull/1149/head v9.1.0
Rick Carlino 2020-02-12 20:49:46 -06:00 committed by GitHub
commit 6ed34124e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
80 changed files with 7367 additions and 14412 deletions

View File

@ -162,7 +162,9 @@ deploy_nerves_hub_firmware_steps: &deploy_nerves_hub_firmware_steps
- run: - run:
name: Sign Image name: Sign Image
working_directory: /nerves/build/farmbot_os working_directory: /nerves/build/farmbot_os
command: mix nerves_hub.firmware sign --key notreal /nerves/deploy/system/artifacts/farmbot-${MIX_TARGET}-$(cat ../VERSION).fw command: |
mix deps.get
mix nerves_hub.firmware sign --key notreal /nerves/deploy/system/artifacts/farmbot-${MIX_TARGET}-$(cat ../VERSION).fw
- run: - run:
name: Publish to NervesHub name: Publish to NervesHub
working_directory: /nerves/build/farmbot_os working_directory: /nerves/build/farmbot_os

View File

@ -1,5 +1,24 @@
# Changelog # Changelog
# 9.1.0
* Improved support for new FarmBot Express models
* Various firmware bug fixes for Express models.
* Bug fix for slow Farmware execution (Thanks, @jsimmonds2)
* Dependency upgrades
* Upgrade VintageNet (networking library)
* Removal of `dump_info` RPCs
* Numerous internal improvements, such as increasing test coverage and changing dependency injection scheme.
* Fix issue where firmware commands would be tried too many times.
# 9.0.4
* Bug fix for slow Farmware execution (Thanks, @jsimmonds2)
* Dependency upgrades
* Upgrade VintageNet (networking library)
* Removal of `dump_info` RPCs
* Numerous internal improvements, such as increasing test coverage and changing dependency injection scheme.
# 9.0.3 # 9.0.3
* Dependency updates * Dependency updates

View File

@ -2,12 +2,9 @@
# :floppy_disk: Latest OS Image Downloads # :floppy_disk: Latest OS Image Downloads
<!-- DON'T CHANGE THE TEXT ABOVE. It is used in documentation links. --> <!-- DON'T CHANGE THE TEXT ABOVE. It is used in documentation links. -->
Download the version of FarmBot OS that corresponds to the FarmBot kit and computer you have: The FarmBot OS release page has moved to [my.farm.bot/os](https://my.farm.bot/os)
| FarmBot Kit | Computer | Download Link | Old versions of FarmBot OS can be found [here](https://github.com/FarmBot/farmbot_os/releases). Please note that [FarmBot does not provide support for old versions of FBOS](https://software.farm.bot/docs/support-policy).
| --- | --- | --- |
| Genesis v1.2, Genesis v1.3, Genesis v1.4, Genesis XL v1.4 | Raspberry Pi 3 | [Download FBOS](https://github.com/FarmBot/farmbot_os/releases/download/v9.0.3/farmbot-rpi3-9.0.3.img) |
| Express v1.0, Express XL v1.0 | Raspberry Pi Zero W | Coming soon |
--- ---

View File

@ -1 +1 @@
9.0.3 9.1.0

View File

@ -1,5 +1,7 @@
{ {
"skip_files": [ "skip_files": [
"lib/farmbot_celery_script/compiler/tools.ex" "lib/farmbot_celery_script/compiler/tools.ex"
] ],
"minimum_coverage": 53,
"treat_no_relevant_lines_as_covered": true
} }

View File

@ -92,7 +92,6 @@
"calibrate", "calibrate",
"change_ownership", "change_ownership",
"check_updates", "check_updates",
"dump_info",
"emergency_lock", "emergency_lock",
"emergency_unlock", "emergency_unlock",
"execute", "execute",
@ -832,18 +831,6 @@
], ],
"docs": "" "docs": ""
}, },
{
"allowed_args": [],
"allowed_body_types": [],
"name": "dump_info",
"tags": [
"function",
"network_user",
"disk_user",
"api_writer"
],
"docs": "Sends an info dump to server administrators for troubleshooting."
},
{ {
"allowed_args": [], "allowed_args": [],
"allowed_body_types": [], "allowed_body_types": [],
@ -1170,7 +1157,6 @@
"calibrate", "calibrate",
"change_ownership", "change_ownership",
"check_updates", "check_updates",
"dump_info",
"emergency_lock", "emergency_lock",
"emergency_unlock", "emergency_unlock",
"execute", "execute",
@ -1242,7 +1228,6 @@
"calibrate", "calibrate",
"change_ownership", "change_ownership",
"check_updates", "check_updates",
"dump_info",
"emergency_lock", "emergency_lock",
"emergency_unlock", "emergency_unlock",
"execute", "execute",

View File

@ -31,11 +31,6 @@ defmodule FarmbotCeleryScript.AST.Factory do
) )
end end
def dump_info(%AST{} = ast) do
ast
|> add_body_node(new(:dump_info))
end
def emergency_lock(%AST{} = ast) do def emergency_lock(%AST{} = ast) do
ast ast
|> add_body_node(new(:emergency_lock)) |> add_body_node(new(:emergency_lock))

View File

@ -279,12 +279,6 @@ defmodule FarmbotCeleryScript.Compiler do
end end
end end
def dump_info(_, _env) do
quote location: :keep do
FarmbotCeleryScript.SysCalls.dump_info()
end
end
defp print_compiled_code(compiled) do defp print_compiled_code(compiled) do
IO.puts("========") IO.puts("========")

View File

@ -29,7 +29,6 @@ defmodule FarmbotCeleryScript.SysCalls do
@callback check_update() :: ok_or_error @callback check_update() :: ok_or_error
@callback coordinate(x :: number, y :: number, z :: number) :: @callback coordinate(x :: number, y :: number, z :: number) ::
%{x: number(), y: number(), z: number()} | error %{x: number(), y: number(), z: number()} | error
@callback dump_info() :: ok_or_error
@callback emergency_lock() :: ok_or_error @callback emergency_lock() :: ok_or_error
@callback emergency_unlock() :: ok_or_error @callback emergency_unlock() :: ok_or_error
@callback execute_script(package, args :: map()) :: ok_or_error @callback execute_script(package, args :: map()) :: ok_or_error
@ -172,10 +171,6 @@ defmodule FarmbotCeleryScript.SysCalls do
coord_or_error(sys_calls, :coordinate, [x, y, z]) coord_or_error(sys_calls, :coordinate, [x, y, z])
end end
def dump_info(sys_calls \\ @sys_calls) do
ok_or_error(sys_calls, :dump_info, [])
end
def emergency_lock(sys_calls \\ @sys_calls) do def emergency_lock(sys_calls \\ @sys_calls) do
ok_or_error(sys_calls, :emergency_lock, []) ok_or_error(sys_calls, :emergency_lock, [])
end end

View File

@ -28,9 +28,6 @@ defmodule FarmbotCeleryScript.SysCalls.Stubs do
@impl true @impl true
def coordinate(x, y, z), do: error(:coordinate, [x, y, z]) def coordinate(x, y, z), do: error(:coordinate, [x, y, z])
@impl true
def dump_info(), do: error(:dump_info, [])
@impl true @impl true
def emergency_lock(), do: error(:emergency_lock, []) def emergency_lock(), do: error(:emergency_lock, [])

View File

@ -3,7 +3,10 @@ defmodule FarmbotCeleryScript.Corpus.NodeTest do
alias FarmbotCeleryScript.Corpus alias FarmbotCeleryScript.Corpus
test "inspect" do test "inspect" do
assert "Sequence(version, locals) [calibrate, change_ownership, check_updates, dump_info, emergency_lock, emergency_unlock, execute, execute_script, factory_reset, find_home, flash_firmware, home, install_farmware, install_first_party_farmware, _if, move_absolute, move_relative, power_off, read_pin, read_status, reboot, remove_farmware, resource_update, send_message, set_servo_angle, set_user_env, sync, take_photo, toggle_pin, update_farmware, wait, write_pin, zero]" = a =
inspect(Corpus.sequence()) "Sequence(version, locals) [calibrate, change_ownership, check_updates, emergency_lock, emergency_unlock, execute, execute_script, factory_reset, find_home, flash_firmware, home, install_farmware, install_first_party_farmware, _if, move_absolute, move_relative, power_off, read_pin, read_status, reboot, remove_farmware, resource_update, send_message, set_servo_angle, set_user_env, sync, take_photo, toggle_pin, update_farmware, wait, write_pin, zero]"
b = inspect(Corpus.sequence())
assert a == b
end end
end end

View File

@ -35,7 +35,6 @@ config :farmbot_core, FarmbotCore.BotState.FileSystem,
sleep_time: 200 sleep_time: 200
config :farmbot_core, FarmbotCore.EctoMigrator, config :farmbot_core, FarmbotCore.EctoMigrator,
expected_fw_versions: ["6.4.2.F", "6.4.2.R", "6.4.2.G"],
default_firmware_io_logs: false, default_firmware_io_logs: false,
default_server: "https://my.farm.bot", default_server: "https://my.farm.bot",
default_dns_name: "my.farm.bot", default_dns_name: "my.farm.bot",

View File

@ -9,7 +9,6 @@ defmodule FarmbotCore.Asset do
Repo, Repo,
Device, Device,
DeviceCert, DeviceCert,
DiagnosticDump,
FarmwareEnv, FarmwareEnv,
FirstPartyFarmware, FirstPartyFarmware,
FarmwareInstallation, FarmwareInstallation,
@ -54,6 +53,7 @@ defmodule FarmbotCore.Asset do
if device = Repo.get_by(Device, id: id) do if device = Repo.get_by(Device, id: id) do
Repo.delete!(device) Repo.delete!(device)
end end
:ok :ok
end end
@ -78,13 +78,14 @@ defmodule FarmbotCore.Asset do
end end
def update_farm_event!(farm_event, params) do def update_farm_event!(farm_event, params) do
farm_event = farm_event =
farm_event |> farm_event
FarmEvent.changeset(params) |> FarmEvent.changeset(params)
|> Repo.update!() |> Repo.update!()
if farm_event.executable_type == "Regimen" do if farm_event.executable_type == "Regimen" do
regimen_instance = get_regimen_instance(farm_event) regimen_instance = get_regimen_instance(farm_event)
if regimen_instance do if regimen_instance do
regimen_instance regimen_instance
|> Repo.preload([:farm_event, :regimen]) |> Repo.preload([:farm_event, :regimen])
@ -111,9 +112,11 @@ defmodule FarmbotCore.Asset do
def get_farm_event_execution(%FarmEvent{} = farm_event, scheduled_at) do def get_farm_event_execution(%FarmEvent{} = farm_event, scheduled_at) do
Repo.one( Repo.one(
from e in FarmEvent.Execution, from(e in FarmEvent.Execution,
where: e.farm_event_local_id == ^farm_event.local_id where:
and e.scheduled_at == ^scheduled_at e.farm_event_local_id == ^farm_event.local_id and
e.scheduled_at == ^scheduled_at
)
) )
end end
@ -149,6 +152,7 @@ defmodule FarmbotCore.Asset do
if fbos_config = Repo.get_by(FbosConfig, id: id) do if fbos_config = Repo.get_by(FbosConfig, id: id) do
Repo.delete!(fbos_config) Repo.delete!(fbos_config)
end end
:ok :ok
end end
@ -177,6 +181,7 @@ defmodule FarmbotCore.Asset do
if firmware_config = Repo.get_by(FirmwareConfig, id: id) do if firmware_config = Repo.get_by(FirmwareConfig, id: id) do
Repo.delete!(firmware_config) Repo.delete!(firmware_config)
end end
:ok :ok
end end
@ -192,12 +197,19 @@ defmodule FarmbotCore.Asset do
end end
def get_regimen_instance(%FarmEvent{} = farm_event) do def get_regimen_instance(%FarmEvent{} = farm_event) do
regimen = Repo.one(from r in Regimen, where: r.id == ^farm_event.executable_id) regimen = Repo.one(from(r in Regimen, where: r.id == ^farm_event.executable_id))
regimen && Repo.one(from ri in RegimenInstance, where: ri.regimen_id == ^regimen.local_id and ri.farm_event_id == ^farm_event.local_id)
regimen &&
Repo.one(
from(ri in RegimenInstance,
where: ri.regimen_id == ^regimen.local_id and ri.farm_event_id == ^farm_event.local_id
)
)
end end
def new_regimen_instance!(%FarmEvent{} = farm_event, params \\ %{}) do def new_regimen_instance!(%FarmEvent{} = farm_event, params \\ %{}) do
regimen = Repo.one!(from r in Regimen, where: r.id == ^farm_event.executable_id) regimen = Repo.one!(from(r in Regimen, where: r.id == ^farm_event.executable_id))
RegimenInstance.changeset(%RegimenInstance{}, params) RegimenInstance.changeset(%RegimenInstance{}, params)
|> Ecto.Changeset.put_assoc(:regimen, regimen) |> Ecto.Changeset.put_assoc(:regimen, regimen)
|> Ecto.Changeset.put_assoc(:farm_event, farm_event) |> Ecto.Changeset.put_assoc(:farm_event, farm_event)
@ -207,7 +219,7 @@ defmodule FarmbotCore.Asset do
def delete_regimen_instance!(%RegimenInstance{} = ri) do def delete_regimen_instance!(%RegimenInstance{} = ri) do
Repo.delete!(ri) Repo.delete!(ri)
end end
def add_execution_to_regimen_instance!(%RegimenInstance{} = ri, params \\ %{}) do def add_execution_to_regimen_instance!(%RegimenInstance{} = ri, params \\ %{}) do
%RegimenInstance.Execution{} %RegimenInstance.Execution{}
|> RegimenInstance.Execution.changeset(params) |> RegimenInstance.Execution.changeset(params)
@ -217,9 +229,11 @@ defmodule FarmbotCore.Asset do
def get_regimen_instance_execution(%RegimenInstance{} = ri, scheduled_at) do def get_regimen_instance_execution(%RegimenInstance{} = ri, scheduled_at) do
Repo.one( Repo.one(
from e in RegimenInstance.Execution, from(e in RegimenInstance.Execution,
where: e.regimen_instance_local_id == ^ri.local_id where:
and e.scheduled_at == ^scheduled_at e.regimen_instance_local_id == ^ri.local_id and
e.scheduled_at == ^scheduled_at
)
) )
end end
@ -248,7 +262,7 @@ defmodule FarmbotCore.Asset do
@doc "Returns all points matching Point.pointer_type" @doc "Returns all points matching Point.pointer_type"
def get_all_points_by_type(type, order_by \\ "random") do def get_all_points_by_type(type, order_by \\ "random") do
(from p in Point, where: p.pointer_type == ^type and is_nil(p.discarded_at)) from(p in Point, where: p.pointer_type == ^type and is_nil(p.discarded_at))
|> Repo.all() |> Repo.all()
|> sort_points(order_by) |> sort_points(order_by)
end end
@ -257,7 +271,7 @@ defmodule FarmbotCore.Asset do
points points
|> Enum.group_by(&group_points_by(&1, order_by)) |> Enum.group_by(&group_points_by(&1, order_by))
|> Enum.sort(&group_sort(&1, &2, order_by)) |> Enum.sort(&group_sort(&1, &2, order_by))
|> Enum.map(fn({_group_index, group}) -> Enum.sort(group, &sort_points(&1, &2, order_by)) end) |> Enum.map(fn {_group_index, group} -> Enum.sort(group, &sort_points(&1, &2, order_by)) end)
|> List.flatten() |> List.flatten()
end end
@ -272,7 +286,6 @@ defmodule FarmbotCore.Asset do
def group_sort({lgroup, _}, {rgroup, _}, "yx_descending"), do: lgroup >= rgroup def group_sort({lgroup, _}, {rgroup, _}, "yx_descending"), do: lgroup >= rgroup
def group_sort(_, _, "random"), do: Enum.random([true, false]) def group_sort(_, _, "random"), do: Enum.random([true, false])
def sort_points(%{y: ly}, %{y: ry}, "xy_ascending"), do: ly <= ry def sort_points(%{y: ly}, %{y: ry}, "xy_ascending"), do: ly <= ry
def sort_points(%{y: ly}, %{y: ry}, "xy_descending"), do: ly >= ry def sort_points(%{y: ly}, %{y: ry}, "xy_descending"), do: ly >= ry
def sort_points(%{x: lx}, %{x: rx}, "yx_ascending"), do: lx <= rx def sort_points(%{x: lx}, %{x: rx}, "yx_ascending"), do: lx <= rx
@ -285,15 +298,15 @@ defmodule FarmbotCore.Asset do
def get_point_group(params) do def get_point_group(params) do
case Repo.get_by(PointGroup, params) do case Repo.get_by(PointGroup, params) do
nil -> nil ->
nil nil
%{sort_type: nil} = group -> %{sort_type: nil} = group ->
group group
%{point_ids: unsorted, sort_type: sort_by} = point_group -> %{point_ids: unsorted, sort_type: sort_by} = point_group ->
sorted = sorted =
Repo.all(from p in Point, where: p.id in ^unsorted) Repo.all(from(p in Point, where: p.id in ^unsorted))
|> sort_points(sort_by) |> sort_points(sort_by)
|> Enum.map(&Map.fetch!(&1, :id)) |> Enum.map(&Map.fetch!(&1, :id))
@ -308,7 +321,7 @@ defmodule FarmbotCore.Asset do
end end
def update_point_group!(point_group, params) do def update_point_group!(point_group, params) do
updated = updated =
point_group point_group
|> PointGroup.changeset(params) |> PointGroup.changeset(params)
|> Repo.update!() |> Repo.update!()
@ -322,11 +335,11 @@ defmodule FarmbotCore.Asset do
for asset <- farm_events ++ regimen_instances do for asset <- farm_events ++ regimen_instances do
# TODO(Connor) this might be worth creating a behaviour for # TODO(Connor) this might be worth creating a behaviour for
if uses_point_group?(asset, point_group) do if uses_point_group?(asset, point_group) do
Logger.debug "#{inspect(asset)} uses PointGroup: #{inspect(point_group)}. Reindexing it." Logger.debug("#{inspect(asset)} uses PointGroup: #{inspect(point_group)}. Reindexing it.")
FarmbotCore.AssetSupervisor.update_child(asset) FarmbotCore.AssetSupervisor.update_child(asset)
end end
end end
updated updated
end end
@ -335,14 +348,16 @@ defmodule FarmbotCore.Asset do
end end
def uses_point_group?(%FarmEvent{body: body}, %PointGroup{id: point_group_id}) do def uses_point_group?(%FarmEvent{body: body}, %PointGroup{id: point_group_id}) do
any_body_node_uses_point_group?(body, point_group_id) any_body_node_uses_point_group?(body, point_group_id)
end end
def uses_point_group?(%Regimen{body: body, regimen_items: regimen_items}, %PointGroup{id: point_group_id}) do def uses_point_group?(%Regimen{body: body, regimen_items: regimen_items}, %PointGroup{
any_body_node_uses_point_group?(body, point_group_id) || Enum.find(regimen_items, fn(%{sequence_id: sequence_id}) -> id: point_group_id
any_body_node_uses_point_group?(get_sequence(sequence_id).body, point_group_id) }) do
end) any_body_node_uses_point_group?(body, point_group_id) ||
Enum.find(regimen_items, fn %{sequence_id: sequence_id} ->
any_body_node_uses_point_group?(get_sequence(sequence_id).body, point_group_id)
end)
end end
def uses_point_group?(%RegimenInstance{farm_event: farm_event, regimen: regimen}, point_group) do def uses_point_group?(%RegimenInstance{farm_event: farm_event, regimen: regimen}, point_group) do
@ -350,11 +365,13 @@ defmodule FarmbotCore.Asset do
end end
def any_body_node_uses_point_group?(body, point_group_id) do def any_body_node_uses_point_group?(body, point_group_id) do
Enum.find(body, fn Enum.find(body, fn
%{ %{
kind: "execute", kind: "execute",
body: execute_body body: execute_body
} -> any_body_node_uses_point_group?(execute_body, point_group_id) } ->
any_body_node_uses_point_group?(execute_body, point_group_id)
%{ %{
args: %{ args: %{
"data_value" => %{ "data_value" => %{
@ -364,7 +381,8 @@ defmodule FarmbotCore.Asset do
"label" => "parent" "label" => "parent"
}, },
kind: "parameter_application" kind: "parameter_application"
} -> true } ->
true
%{ %{
args: %{ args: %{
@ -375,8 +393,11 @@ defmodule FarmbotCore.Asset do
"label" => "parent" "label" => "parent"
}, },
kind: "parameter_application" kind: "parameter_application"
} -> true } ->
_ -> false true
_ ->
false
end) end)
end end
@ -407,6 +428,7 @@ defmodule FarmbotCore.Asset do
def new_public_key_from_home!() do def new_public_key_from_home!() do
public_key_path = Path.join([System.get_env("HOME"), ".ssh", "id_rsa.pub"]) public_key_path = Path.join([System.get_env("HOME"), ".ssh", "id_rsa.pub"])
public_key = File.read!(public_key_path) public_key = File.read!(public_key_path)
%PublicKey{} %PublicKey{}
|> PublicKey.changeset(%{public_key: public_key}) |> PublicKey.changeset(%{public_key: public_key})
|> Repo.insert() |> Repo.insert()
@ -417,7 +439,7 @@ defmodule FarmbotCore.Asset do
|> PublicKey.changeset(%{public_key: public_key}) |> PublicKey.changeset(%{public_key: public_key})
|> Repo.insert() |> Repo.insert()
end end
## End PublicKey ## End PublicKey
## Begin Regimen ## Begin Regimen
@ -435,18 +457,23 @@ defmodule FarmbotCore.Asset do
end end
def delete_regimen!(regimen) do def delete_regimen!(regimen) do
regimen_instances = Repo.all(from ri in RegimenInstance, where: ri.regimen_id == ^regimen.local_id) regimen_instances =
Repo.all(from(ri in RegimenInstance, where: ri.regimen_id == ^regimen.local_id))
for ri <- regimen_instances do for ri <- regimen_instances do
IO.puts "deleting regimen instance: #{inspect(ri)}" IO.puts("deleting regimen instance: #{inspect(ri)}")
delete_regimen_instance!(ri) delete_regimen_instance!(ri)
end end
Repo.delete!(regimen) Repo.delete!(regimen)
end end
@doc "Update an existing regimen" @doc "Update an existing regimen"
def update_regimen!(regimen, params) do def update_regimen!(regimen, params) do
regimen_instances = Repo.all(from ri in RegimenInstance, where: ri.regimen_id == ^regimen.local_id) regimen_instances =
|> Repo.preload([:farm_event, :regimen]) Repo.all(from(ri in RegimenInstance, where: ri.regimen_id == ^regimen.local_id))
|> Repo.preload([:farm_event, :regimen])
for ri <- regimen_instances do for ri <- regimen_instances do
ri ri
|> RegimenInstance.changeset(%{updated_at: DateTime.utc_now()}) |> RegimenInstance.changeset(%{updated_at: DateTime.utc_now()})
@ -469,23 +496,31 @@ defmodule FarmbotCore.Asset do
def update_sequence!(%Sequence{} = sequence, params \\ %{}) do def update_sequence!(%Sequence{} = sequence, params \\ %{}) do
sequence_id = sequence.id sequence_id = sequence.id
farm_events = Repo.all(from f in FarmEvent,
where: f.executable_type == "Sequence"
and f.executable_id == ^sequence_id)
regimen_instances = RegimenInstance farm_events =
|> Repo.all() Repo.all(
|> Repo.preload([:regimen, :farm_event]) from(f in FarmEvent,
|> Enum.filter(fn where:
%{regimen: %{regimen_items: items}} -> f.executable_type == "Sequence" and
Enum.find(items, fn f.executable_id == ^sequence_id
%{sequence_id: ^sequence_id} -> true )
%{sequence_id: _} -> true )
regimen_instances =
RegimenInstance
|> Repo.all()
|> Repo.preload([:regimen, :farm_event])
|> Enum.filter(fn
%{regimen: %{regimen_items: items}} ->
Enum.find(items, fn
%{sequence_id: ^sequence_id} -> true
%{sequence_id: _} -> true
end)
%{regimen: nil} ->
false
end) end)
%{regimen: nil} -> false
end)
for asset <- farm_events ++ regimen_instances do for asset <- farm_events ++ regimen_instances do
FarmbotCore.AssetSupervisor.update_child(asset) FarmbotCore.AssetSupervisor.update_child(asset)
end end
@ -507,10 +542,11 @@ defmodule FarmbotCore.Asset do
def get_farmware_manifest(package) do def get_farmware_manifest(package) do
first_party_farmwares = Repo.all(from(fwi in FirstPartyFarmware, select: fwi.manifest)) first_party_farmwares = Repo.all(from(fwi in FirstPartyFarmware, select: fwi.manifest))
regular_farmwares = Repo.all(from(fwi in FarmwareInstallation, select: fwi.manifest)) regular_farmwares = Repo.all(from(fwi in FarmwareInstallation, select: fwi.manifest))
Enum.find( Enum.find(
first_party_farmwares ++ regular_farmwares, first_party_farmwares ++ regular_farmwares,
fn fn
%{package: pkg} -> pkg == package %{package: pkg} -> pkg == package
_ -> false _ -> false
end end
) )
@ -519,10 +555,11 @@ defmodule FarmbotCore.Asset do
def get_farmware_installation(package) do def get_farmware_installation(package) do
first_party_farmwares = Repo.all(from(fwi in FirstPartyFarmware)) first_party_farmwares = Repo.all(from(fwi in FirstPartyFarmware))
regular_farmwares = Repo.all(from(fwi in FarmwareInstallation)) regular_farmwares = Repo.all(from(fwi in FarmwareInstallation))
Enum.find( Enum.find(
first_party_farmwares ++ regular_farmwares, first_party_farmwares ++ regular_farmwares,
fn fn
%{manifest: %{package: pkg}} -> pkg == package %{manifest: %{package: pkg}} -> pkg == package
_ -> false _ -> false
end end
) )
@ -530,12 +567,14 @@ defmodule FarmbotCore.Asset do
def upsert_farmware_manifest_by_id(id, params) do def upsert_farmware_manifest_by_id(id, params) do
fwi = Repo.get_by(FarmwareInstallation, id: id) || %FarmwareInstallation{} fwi = Repo.get_by(FarmwareInstallation, id: id) || %FarmwareInstallation{}
FarmwareInstallation.changeset(fwi, params) FarmwareInstallation.changeset(fwi, params)
|> Repo.insert_or_update() |> Repo.insert_or_update()
end end
def upsert_first_party_farmware_manifest_by_id(id, params) do def upsert_first_party_farmware_manifest_by_id(id, params) do
fwi = Repo.get_by(FirstPartyFarmware, id: id) || %FirstPartyFarmware{} fwi = Repo.get_by(FirstPartyFarmware, id: id) || %FirstPartyFarmware{}
FirstPartyFarmware.changeset(fwi, params) FirstPartyFarmware.changeset(fwi, params)
|> Repo.insert_or_update() |> Repo.insert_or_update()
end end
@ -557,12 +596,14 @@ defmodule FarmbotCore.Asset do
def new_farmware_env(params) do def new_farmware_env(params) do
key = params["key"] || params[:key] key = params["key"] || params[:key]
fwe = with key when is_binary(key) <- key,
[fwe | _] <- Repo.all(from fwe in FarmwareEnv, where: fwe.key == ^key) do fwe =
fwe with key when is_binary(key) <- key,
else [fwe | _] <- Repo.all(from(fwe in FarmwareEnv, where: fwe.key == ^key)) do
_ -> %FarmwareEnv{} fwe
end else
_ -> %FarmwareEnv{}
end
FarmwareEnv.changeset(fwe, params) FarmwareEnv.changeset(fwe, params)
|> Repo.insert_or_update() |> Repo.insert_or_update()
@ -596,7 +637,7 @@ defmodule FarmbotCore.Asset do
Sensor.changeset(%Sensor{}, params) Sensor.changeset(%Sensor{}, params)
|> Repo.insert!() |> Repo.insert!()
end end
def update_sensor!(sensor, params) do def update_sensor!(sensor, params) do
sensor sensor
|> Sensor.changeset(params) |> Sensor.changeset(params)
@ -624,15 +665,6 @@ defmodule FarmbotCore.Asset do
## End SensorReading ## End SensorReading
## Begin DiagnosticDump
def new_diagnostic_dump(params) do
DiagnosticDump.changeset(%DiagnosticDump{}, params)
|> Repo.insert()
end
## End DiagnosticDump
## Begin DeviceCert ## Begin DeviceCert
def new_device_cert(params) do def new_device_cert(params) do

View File

@ -4,6 +4,7 @@ defmodule FarmbotCore.Asset.Command do
""" """
require Logger require Logger
alias FarmbotCore.{Asset, Asset.Repo} alias FarmbotCore.{Asset, Asset.Repo}
alias FarmbotCore.Asset.{ alias FarmbotCore.Asset.{
Device, Device,
FarmEvent, FarmEvent,
@ -44,31 +45,31 @@ defmodule FarmbotCore.Asset.Command do
:ok :ok
end end
def update(Device, _id, params) do def update(Device, _id, params) do
Asset.update_device!(params) Asset.update_device!(params)
:ok :ok
end end
def update(FbosConfig, id, nil) do def update(FbosConfig, id, nil) do
Asset.delete_fbos_config!(id) Asset.delete_fbos_config!(id)
:ok :ok
end end
def update(FbosConfig, _id, params) do def update(FbosConfig, _id, params) do
Asset.update_fbos_config!(params) Asset.update_fbos_config!(params)
:ok :ok
end end
def update(FirmwareConfig, id, nil) do def update(FirmwareConfig, id, nil) do
Asset.delete_firmware_config!(id) Asset.delete_firmware_config!(id)
:ok :ok
end end
def update(FirmwareConfig, _id, params) do def update(FirmwareConfig, _id, params) do
Asset.update_firmware_config!(params) Asset.update_firmware_config!(params)
:ok :ok
end end
# Deletion use case: # Deletion use case:
# TODO(Connor) put checks for deleting Device, FbosConfig and FirmwareConfig # TODO(Connor) put checks for deleting Device, FbosConfig and FirmwareConfig
@ -96,12 +97,12 @@ defmodule FarmbotCore.Asset.Command do
:ok :ok
end end
def update(FarmwareEnv, id, params) do def update(FarmwareEnv, id, params) do
Asset.upsert_farmware_env_by_id(id, params) Asset.upsert_farmware_env_by_id(id, params)
:ok :ok
end end
def update(FarmwareInstallation, id, params) do def update(FarmwareInstallation, id, params) do
Asset.upsert_farmware_manifest_by_id(id, params) Asset.upsert_farmware_manifest_by_id(id, params)
:ok :ok
end end
@ -112,15 +113,17 @@ defmodule FarmbotCore.Asset.Command do
def update(FarmEvent, id, params) do def update(FarmEvent, id, params) do
old = Asset.get_farm_event(id) old = Asset.get_farm_event(id)
if old,
do: Asset.update_farm_event!(old, params), if old,
do: Asset.update_farm_event!(old, params),
else: Asset.new_farm_event!(params) else: Asset.new_farm_event!(params)
:ok :ok
end end
def update(PublicKey, id, params) do def update(PublicKey, id, params) do
old = Asset.get_public_key(id) old = Asset.get_public_key(id)
if old, if old,
do: Asset.update_public_key!(old, params), do: Asset.update_public_key!(old, params),
else: Asset.new_public_key!(params) else: Asset.new_public_key!(params)
@ -130,33 +133,37 @@ defmodule FarmbotCore.Asset.Command do
def update(Regimen, id, params) do def update(Regimen, id, params) do
old = Asset.get_regimen(id) old = Asset.get_regimen(id)
if old,
do: Asset.update_regimen!(old, params), if old,
do: Asset.update_regimen!(old, params),
else: Asset.new_regimen!(params) else: Asset.new_regimen!(params)
:ok :ok
end end
def update(Sensor, id, params) do def update(Sensor, id, params) do
old = Asset.get_sensor(id) old = Asset.get_sensor(id)
if old,
do: Asset.update_sensor!(old, params), if old,
do: Asset.update_sensor!(old, params),
else: Asset.new_sensor!(params) else: Asset.new_sensor!(params)
:ok :ok
end end
def update(SensorReading, id, params) do def update(SensorReading, id, params) do
old = Asset.get_sensor_reading(id) old = Asset.get_sensor_reading(id)
if old,
do: Asset.update_sensor_reading!(old, params), if old,
do: Asset.update_sensor_reading!(old, params),
else: Asset.new_sensor_reading!(params) else: Asset.new_sensor_reading!(params)
:ok :ok
end end
def update(Sequence, id, params) do def update(Sequence, id, params) do
old = Asset.get_sequence(id) old = Asset.get_sequence(id)
if old, if old,
do: Asset.update_sequence!(old, params), do: Asset.update_sequence!(old, params),
else: Asset.new_sequence!(params) else: Asset.new_sequence!(params)
@ -172,6 +179,7 @@ defmodule FarmbotCore.Asset.Command do
def update(PointGroup, id, params) do def update(PointGroup, id, params) do
old = Asset.get_point_group(id: id) old = Asset.get_point_group(id: id)
if old, if old,
do: Asset.update_point_group!(old, params), do: Asset.update_point_group!(old, params),
else: Asset.new_point_group!(params) else: Asset.new_point_group!(params)
@ -181,8 +189,9 @@ defmodule FarmbotCore.Asset.Command do
# Catch-all use case: # Catch-all use case:
def update(asset_kind, id, params) do def update(asset_kind, id, params) do
Logger.warn "AssetCommand needs implementation: #{asset_kind}" Logger.warn("AssetCommand needs implementation: #{asset_kind}")
mod = as_module!(asset_kind) mod = as_module!(asset_kind)
case Repo.get_by(mod, id: id) do case Repo.get_by(mod, id: id) do
nil -> nil ->
struct!(mod) struct!(mod)
@ -205,28 +214,27 @@ defmodule FarmbotCore.Asset.Command do
mod.changeset(asset, params) mod.changeset(asset, params)
end end
defp as_module!("Device"), do: Asset.Device defp as_module!("Device"), do: Asset.Device
defp as_module!("DiagnosticDump"), do: Asset.DiagnosticDump defp as_module!("FarmEvent"), do: Asset.FarmEvent
defp as_module!("FarmEvent"), do: Asset.FarmEvent defp as_module!("FarmwareEnv"), do: Asset.FarmwareEnv
defp as_module!("FarmwareEnv"), do: Asset.FarmwareEnv defp as_module!("FirstPartyFarmware"), do: Asset.FirstPartyFarmware
defp as_module!("FirstPartyFarmware"), do: Asset.FirstPartyFarmware defp as_module!("FarmwareInstallation"), do: Asset.FarmwareInstallation
defp as_module!("FarmwareInstallation"), do: Asset.FarmwareInstallation defp as_module!("FbosConfig"), do: Asset.FbosConfig
defp as_module!("FbosConfig"), do: Asset.FbosConfig defp as_module!("FirmwareConfig"), do: Asset.FirmwareConfig
defp as_module!("FirmwareConfig"), do: Asset.FirmwareConfig defp as_module!("Peripheral"), do: Asset.Peripheral
defp as_module!("Peripheral"), do: Asset.Peripheral defp as_module!("PinBinding"), do: Asset.PinBinding
defp as_module!("PinBinding"), do: Asset.PinBinding defp as_module!("Point"), do: Asset.Point
defp as_module!("Point"), do: Asset.Point defp as_module!("PointGroup"), do: Asset.PointGroup
defp as_module!("PointGroup"), do: Asset.PointGroup defp as_module!("Regimen"), do: Asset.Regimen
defp as_module!("Regimen"), do: Asset.Regimen defp as_module!("Sensor"), do: Asset.Sensor
defp as_module!("Sensor"), do: Asset.Sensor defp as_module!("SensorReading"), do: Asset.SensorReading
defp as_module!("SensorReading"), do: Asset.SensorReading defp as_module!("Sequence"), do: Asset.Sequence
defp as_module!("Sequence"), do: Asset.Sequence defp as_module!("Tool"), do: Asset.Tool
defp as_module!("Tool"), do: Asset.Tool
defp as_module!(module) when is_atom(module) do defp as_module!(module) when is_atom(module) do
as_module!(List.last(Module.split(module))) as_module!(List.last(Module.split(module)))
end end
defp as_module!(kind) when is_binary(kind) do defp as_module!(kind) when is_binary(kind) do
raise(""" raise("""
Unknown kind: #{kind} Unknown kind: #{kind}

View File

@ -1,58 +0,0 @@
defmodule FarmbotCore.Asset.DiagnosticDump do
@moduledoc """
Just the DiagDump REST resource, used by FarmBot staff to help users debug
remote device problems.
"""
use FarmbotCore.Asset.Schema, path: "/api/diagnostic_dumps"
schema "diagnostic_dumps" do
field(:id, :id)
has_one(:local_meta, FarmbotCore.Asset.Private.LocalMeta,
on_delete: :delete_all,
references: :local_id,
foreign_key: :asset_local_id
)
field(:ticket_identifier, :string)
field(:fbos_commit, :string)
field(:fbos_version, :string)
field(:firmware_commit, :string)
field(:firmware_state, :string)
field(:network_interface, :string)
field(:fbos_dmesg_dump, :string)
field(:monitor, :boolean, default: true)
timestamps()
end
view diagnostic_dump do
%{
id: diagnostic_dump.id,
ticket_identifier: diagnostic_dump.ticket_identifier,
fbos_commit: diagnostic_dump.fbos_commit,
fbos_version: diagnostic_dump.fbos_version,
firmware_commit: diagnostic_dump.firmware_commit,
firmware_state: diagnostic_dump.firmware_state,
network_interface: diagnostic_dump.network_interface,
fbos_dmesg_dump: diagnostic_dump.fbos_dmesg_dump
}
end
def changeset(diagnostic_dump, params \\ %{}) do
diagnostic_dump
|> cast(params, [
:id,
:ticket_identifier,
:fbos_commit,
:fbos_version,
:firmware_commit,
:firmware_state,
:network_interface,
:fbos_dmesg_dump,
:monitor,
:created_at,
:updated_at
])
|> validate_required([])
end
end

View File

@ -100,16 +100,15 @@ defimpl String.Chars, for: FarmbotCore.Asset.PinBinding do
end end
defp special_action(button_number, action, pin_num) do defp special_action(button_number, action, pin_num) do
"Button #{button_number}: #{format_action(action)} (Pi #{pin_num})" "Button #{button_number}: #{format_action(action)} (Pi #{pin_num})"
end end
defp format_action("dump_info"), do: "Dump Info" defp format_action("emergency_lock"), do: "E-Stop"
defp format_action("emergency_lock"), do: "E-Stop"
defp format_action("emergency_unlock"), do: "E-Unlock" defp format_action("emergency_unlock"), do: "E-Unlock"
defp format_action("power_off"), do: "Power Off" defp format_action("power_off"), do: "Power Off"
defp format_action("read_status"), do: "Read Status" defp format_action("read_status"), do: "Read Status"
defp format_action("reboot"), do: "Reboot" defp format_action("reboot"), do: "Reboot"
defp format_action("sync"), do: "Sync" defp format_action("sync"), do: "Sync"
defp format_action("take_photo"), do: "Take Photo" defp format_action("take_photo"), do: "Take Photo"
defp format_action(_), do: nil defp format_action(_), do: nil
end end

View File

@ -9,7 +9,6 @@ defmodule FarmbotCore.Asset.Private.LocalMeta do
alias FarmbotCore.Asset.{ alias FarmbotCore.Asset.{
Repo, Repo,
Device, Device,
DiagnosticDump,
DeviceCert, DeviceCert,
FarmEvent, FarmEvent,
FarmwareEnv, FarmwareEnv,
@ -47,13 +46,6 @@ defmodule FarmbotCore.Asset.Private.LocalMeta do
define_field: false define_field: false
) )
belongs_to(:diagnostic_dump, DiagnosticDump,
foreign_key: :asset_local_id,
type: :binary_id,
references: :local_id,
define_field: false
)
belongs_to(:farm_event, FarmEvent, belongs_to(:farm_event, FarmEvent,
foreign_key: :asset_local_id, foreign_key: :asset_local_id,
type: :binary_id, type: :binary_id,
@ -174,8 +166,7 @@ defmodule FarmbotCore.Asset.Private.LocalMeta do
"firmware_configs", "firmware_configs",
"fbos_configs", "fbos_configs",
"farmware_installations", "farmware_installations",
"farmware_envs", "farmware_envs"
"diagnostic_dumps"
]) ])
|> unsafe_validate_unique([:table, :asset_local_id], Repo, |> unsafe_validate_unique([:table, :asset_local_id], Repo,
message: "LocalMeta already exists." message: "LocalMeta already exists."

View File

@ -41,7 +41,6 @@ defmodule FarmbotCore.Asset.Sync do
embeds_many(:devices, Item) embeds_many(:devices, Item)
embeds_many(:firmware_configs, Item) embeds_many(:firmware_configs, Item)
embeds_many(:fbos_configs, Item) embeds_many(:fbos_configs, Item)
embeds_many(:diagnostic_dumps, Item)
embeds_many(:farm_events, Item) embeds_many(:farm_events, Item)
embeds_many(:farmware_envs, Item) embeds_many(:farmware_envs, Item)
embeds_many(:first_party_farmwares, Item) embeds_many(:first_party_farmwares, Item)
@ -65,7 +64,6 @@ defmodule FarmbotCore.Asset.Sync do
devices: Enum.map(sync.device, &Item.render/1), devices: Enum.map(sync.device, &Item.render/1),
fbos_configs: Enum.map(sync.fbos_config, &Item.render/1), fbos_configs: Enum.map(sync.fbos_config, &Item.render/1),
firmware_configs: Enum.map(sync.firmware_config, &Item.render/1), firmware_configs: Enum.map(sync.firmware_config, &Item.render/1),
diagnostic_dumps: Enum.map(sync.diagnostic_dumps, &Item.render/1),
farm_events: Enum.map(sync.farm_events, &Item.render/1), farm_events: Enum.map(sync.farm_events, &Item.render/1),
farmware_envs: Enum.map(sync.farmware_envs, &Item.render/1), farmware_envs: Enum.map(sync.farmware_envs, &Item.render/1),
first_party_farmwares: Enum.map(sync.first_party_farmwares, &Item.render/1), first_party_farmwares: Enum.map(sync.first_party_farmwares, &Item.render/1),
@ -90,7 +88,6 @@ defmodule FarmbotCore.Asset.Sync do
|> cast_embed(:devices) |> cast_embed(:devices)
|> cast_embed(:fbos_configs) |> cast_embed(:fbos_configs)
|> cast_embed(:firmware_configs) |> cast_embed(:firmware_configs)
|> cast_embed(:diagnostic_dumps)
|> cast_embed(:farm_events) |> cast_embed(:farm_events)
|> cast_embed(:farmware_envs) |> cast_embed(:farmware_envs)
|> cast_embed(:farmware_installations) |> cast_embed(:farmware_installations)

View File

@ -1,25 +1,26 @@
defmodule FarmbotCore.AssetHelpers do defmodule FarmbotCore.AssetHelpers do
@moduledoc """ @moduledoc """
Helpers for the console at runtime. Helpers for the console at runtime.
Example: Example:
iex()> use FarmbotCore.AssetHelpers iex()> use FarmbotCore.AssetHelpers
iex()> Repo.all(Device) iex()> Repo.all(Device)
[%Device{}] [%Device{}]
""" """
@doc false @doc false
defmacro __using__(_opts) do defmacro __using__(_opts) do
require Logger require Logger
Logger.warn "Don't use this in production please!" Logger.warn("Don't use this in production please!")
quote do quote do
import Ecto.Query import Ecto.Query
alias FarmbotCore.Asset alias FarmbotCore.Asset
alias Asset.{ alias Asset.{
Repo, Repo,
Device, Device,
DeviceCert, DeviceCert,
DiagnosticDump,
FarmwareEnv, FarmwareEnv,
FarmwareInstallation, FarmwareInstallation,
FirstPartyFarmware, FirstPartyFarmware,
@ -40,4 +41,4 @@ defmodule FarmbotCore.AssetHelpers do
} }
end end
end end
end end

View File

@ -33,7 +33,7 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.Peripheral do
{:noreply, state} {:noreply, state}
end end
def handle_info(:timeout, %{fw_version: "8.0.0.S"} = state) do def handle_info(:timeout, %{fw_version: "8.0.0.S.stub"} = state) do
{:noreply, state} {:noreply, state}
end end
@ -47,15 +47,15 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.Peripheral do
Logger.debug("Read peripheral: #{peripheral.label}") Logger.debug("Read peripheral: #{peripheral.label}")
rpc = peripheral_to_rpc(peripheral) rpc = peripheral_to_rpc(peripheral)
case FarmbotCeleryScript.execute(rpc, make_ref()) do case FarmbotCeleryScript.execute(rpc, make_ref()) do
:ok -> :ok ->
Logger.debug("Read peripheral: #{peripheral.label} ok") Logger.debug("Read peripheral: #{peripheral.label} ok")
{:noreply, state} {:noreply, state}
{:error, reason} when errors < 5 -> {:error, reason} when errors < 5 ->
Logger.error("Read peripheral: #{peripheral.label} error: #{reason} errors=#{state.errors}") Logger.error("Read peripheral: #{peripheral.label} error: #{reason} errors=#{state.errors}")
Process.send_after(self(), :timeout, @retry_ms) Process.send_after(self(), :timeout, @retry_ms)
{:noreply, %{state | errors: state.errors + 1}} {:noreply, %{state | errors: state.errors + 1}}
{:error, reason} when errors == 5 -> {:error, reason} when errors == 5 ->
Logger.error("Read peripheral: #{peripheral.label} error: #{reason} errors=5 not trying again.") Logger.error("Read peripheral: #{peripheral.label} error: #{reason} errors=5 not trying again.")
{:noreply, state} {:noreply, state}

View File

@ -3,12 +3,12 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.PinBinding do
Worker for monitoring hardware GPIO. (not related to the mcu firmware.) Worker for monitoring hardware GPIO. (not related to the mcu firmware.)
Upon a button trigger, a `sequence`, or `special_action` will be executed by Upon a button trigger, a `sequence`, or `special_action` will be executed by
the CeleryScript Runtime. the CeleryScript Runtime.
This module also defines a behaviour that allows for abstracting and testing This module also defines a behaviour that allows for abstracting and testing
independent of GPIO hardware code. independent of GPIO hardware code.
""" """
use GenServer use GenServer
require Logger require Logger
require FarmbotCore.Logger require FarmbotCore.Logger
@ -75,6 +75,7 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.PinBinding do
case Asset.get_sequence(pin_binding.sequence_id) do case Asset.get_sequence(pin_binding.sequence_id) do
%Sequence{name: name} = seq -> %Sequence{name: name} = seq ->
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing #{name}") FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing #{name}")
AST.decode(seq) AST.decode(seq)
|> execute(state) |> execute(state)
@ -84,24 +85,24 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.PinBinding do
end end
end end
def handle_cast(:trigger, %{pin_binding: %{special_action: "dump_info"} = pin_binding} = state) do def handle_cast(
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Dump Info") :trigger,
AST.Factory.new() %{pin_binding: %{special_action: "emergency_lock"} = pin_binding} = state
|> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}") ) do
|> AST.Factory.dump_info()
|> execute(state)
end
def handle_cast(:trigger, %{pin_binding: %{special_action: "emergency_lock"} = pin_binding} = state) do
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Emergency Lock") FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Emergency Lock")
AST.Factory.new() AST.Factory.new()
|> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}") |> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}")
|> AST.Factory.emergency_lock() |> AST.Factory.emergency_lock()
|> execute(state) |> execute(state)
end end
def handle_cast(:trigger, %{pin_binding: %{special_action: "emergency_unlock"} = pin_binding} = state) do def handle_cast(
:trigger,
%{pin_binding: %{special_action: "emergency_unlock"} = pin_binding} = state
) do
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Emergency Unlock") FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Emergency Unlock")
AST.Factory.new() AST.Factory.new()
|> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}") |> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}")
|> AST.Factory.emergency_unlock() |> AST.Factory.emergency_unlock()
@ -110,14 +111,19 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.PinBinding do
def handle_cast(:trigger, %{pin_binding: %{special_action: "power_off"} = pin_binding} = state) do def handle_cast(:trigger, %{pin_binding: %{special_action: "power_off"} = pin_binding} = state) do
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Power Off") FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Power Off")
AST.Factory.new() AST.Factory.new()
|> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}") |> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}")
|> AST.Factory.power_off() |> AST.Factory.power_off()
|> execute(state) |> execute(state)
end end
def handle_cast(:trigger, %{pin_binding: %{special_action: "read_status"} = pin_binding} = state) do def handle_cast(
:trigger,
%{pin_binding: %{special_action: "read_status"} = pin_binding} = state
) do
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Read Status") FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Read Status")
AST.Factory.new() AST.Factory.new()
|> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}") |> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}")
|> AST.Factory.read_status() |> AST.Factory.read_status()
@ -126,6 +132,7 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.PinBinding do
def handle_cast(:trigger, %{pin_binding: %{special_action: "reboot"} = pin_binding} = state) do def handle_cast(:trigger, %{pin_binding: %{special_action: "reboot"} = pin_binding} = state) do
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Reboot") FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Reboot")
AST.Factory.new() AST.Factory.new()
|> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}") |> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}")
|> AST.Factory.reboot() |> AST.Factory.reboot()
@ -134,6 +141,7 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.PinBinding do
def handle_cast(:trigger, %{pin_binding: %{special_action: "sync"} = pin_binding} = state) do def handle_cast(:trigger, %{pin_binding: %{special_action: "sync"} = pin_binding} = state) do
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Sync") FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Sync")
AST.Factory.new() AST.Factory.new()
|> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}") |> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}")
|> AST.Factory.sync() |> AST.Factory.sync()
@ -142,6 +150,7 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.PinBinding do
def handle_cast(:trigger, %{pin_binding: %{special_action: "take_photo"} = pin_binding} = state) do def handle_cast(:trigger, %{pin_binding: %{special_action: "take_photo"} = pin_binding} = state) do
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Take Photo") FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Take Photo")
AST.Factory.new() AST.Factory.new()
|> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}") |> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}")
|> AST.Factory.take_photo() |> AST.Factory.take_photo()
@ -182,10 +191,13 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.PinBinding do
defp execute(%AST{} = ast, state) do defp execute(%AST{} = ast, state) do
case FarmbotCeleryScript.execute(ast, make_ref()) do case FarmbotCeleryScript.execute(ast, make_ref()) do
:ok -> :ok :ok ->
:ok
{:error, reason} -> {:error, reason} ->
FarmbotCore.Logger.error 1, "error executing #{state.pin_binding}: #{reason}" FarmbotCore.Logger.error(1, "error executing #{state.pin_binding}: #{reason}")
end end
{:noreply, state} {:noreply, state}
end end

View File

@ -3,7 +3,6 @@ defmodule FarmbotCore.FarmwareRuntime do
Handles execution of Farmware plugins. Handles execution of Farmware plugins.
""" """
alias Avrdude.MuonTrapAdapter
alias FarmbotCeleryScript.AST alias FarmbotCeleryScript.AST
alias FarmbotCore.Asset.FarmwareInstallation.Manifest alias FarmbotCore.Asset.FarmwareInstallation.Manifest
alias FarmbotCore.AssetWorker.FarmbotCore.Asset.FarmwareInstallation alias FarmbotCore.AssetWorker.FarmbotCore.Asset.FarmwareInstallation
@ -113,8 +112,7 @@ defmodule FarmbotCore.FarmwareRuntime do
# Start the plugin. # Start the plugin.
Logger.debug("spawning farmware: #{exec} #{manifest.args}") Logger.debug("spawning farmware: #{exec} #{manifest.args}")
{cmd, _} = {cmd, _} = spawn_monitor(MuonTrap, :cmd, ["sh", ["-c", "#{exec} #{manifest.args}"], opts])
spawn_monitor(MuonTrapAdapter, :cmd, ["sh", ["-c", "#{exec} #{manifest.args}"], opts])
state = %State{ state = %State{
caller: caller, caller: caller,
@ -156,8 +154,7 @@ defmodule FarmbotCore.FarmwareRuntime do
end end
def handle_info({:step_complete, ref, :ok}, %{scheduler_ref: ref} = state) do def handle_info({:step_complete, ref, :ok}, %{scheduler_ref: ref} = state) do
label = UUID.uuid4() result = %AST{kind: :rpc_ok, args: %{label: state.rpc.args.label}, body: []}
result = %AST{kind: :rpc_ok, args: %{label: label}, body: []}
ipc = add_header(result) ipc = add_header(result)
_reply = PipeWorker.write(state.response_pipe_handle, ipc) _reply = PipeWorker.write(state.response_pipe_handle, ipc)

View File

@ -82,31 +82,50 @@ defmodule FarmbotCore.FirmwareSideEffects do
[_, _, _, "R"] -> [_, _, _, "R"] ->
_ = Leds.red(:solid) _ = Leds.red(:solid)
:ok = BotState.set_firmware_hardware("arduino") :ok = BotState.set_firmware_hardware("arduino")
[_, _, _, "R", _] ->
_ = Leds.red(:solid)
:ok = BotState.set_firmware_hardware("arduino")
# Farmduino # Farmduino
[_, _, _, "F"] -> [_, _, _, "F"] ->
_ = Leds.red(:solid) _ = Leds.red(:solid)
:ok = BotState.set_firmware_hardware("farmduino") :ok = BotState.set_firmware_hardware("farmduino")
[_, _, _, "F", _] ->
_ = Leds.red(:solid)
:ok = BotState.set_firmware_hardware("farmduino")
# Farmduino V14 # Farmduino V14
[_, _, _, "G"] -> [_, _, _, "G"] ->
_ = Leds.red(:solid) _ = Leds.red(:solid)
:ok = BotState.set_firmware_hardware("farmduino_k14") :ok = BotState.set_firmware_hardware("farmduino_k14")
[_, _, _, "G", _] ->
_ = Leds.red(:solid)
:ok = BotState.set_firmware_hardware("farmduino_k14")
# Farmduino V15 # Farmduino V15
[_, _, _, "H"] -> [_, _, _, "H"] ->
_ = Leds.red(:solid) _ = Leds.red(:solid)
:ok = BotState.set_firmware_hardware("farmduino_k15") :ok = BotState.set_firmware_hardware("farmduino_k15")
[_, _, _, "H", _] ->
_ = Leds.red(:solid)
:ok = BotState.set_firmware_hardware("farmduino_k15")
# Express V10 # Express V10
[_, _, _, "E"] -> [_, _, _, "E"] ->
_ = Leds.red(:solid) _ = Leds.red(:solid)
:ok = BotState.set_firmware_hardware("express_k10") :ok = BotState.set_firmware_hardware("express_k10")
[_, _, _, "E", _] ->
_ = Leds.red(:solid)
:ok = BotState.set_firmware_hardware("express_k10")
[_, _, _, "S"] -> [_, _, _, "S"] ->
_ = Leds.red(:slow_blink) _ = Leds.red(:slow_blink)
:ok = BotState.set_firmware_version("none") :ok = BotState.set_firmware_version("none")
:ok = BotState.set_firmware_hardware("none") :ok = BotState.set_firmware_hardware("none")
[_, _, _, "S", _] ->
_ = Leds.red(:slow_blink)
:ok = BotState.set_firmware_version("none")
:ok = BotState.set_firmware_hardware("none")
end end
end end

View File

@ -0,0 +1,29 @@
# This module could have existed within FarmbotCore.Logger.
# Pulling this function into a different module facilitates
# mocking of tests.
defmodule FarmbotCore.LogExecutor do
alias FarmbotCore.Log
def execute(%Log{} = log) do
logger_meta = [
application: :farmbot,
function: log.function,
file: log.file,
line: log.line,
module: log.module,
channels: log.meta[:channels] || log.meta["channels"],
verbosity: log.verbosity,
assertion_passed: log.meta[:assertion_passed]
]
level = log.level
logger_level =
if level in [:info, :debug, :warn, :error],
do: level,
else: :info
Elixir.Logger.bare_log(logger_level, log, logger_meta)
log
end
end

View File

@ -5,6 +5,7 @@ defmodule FarmbotCore.Logger do
alias FarmbotCore.{Log, Logger.Repo} alias FarmbotCore.{Log, Logger.Repo}
import Ecto.Query import Ecto.Query
@log_types [:info, :debug, :busy, :warn, :success, :error, :fun, :assertion]
@doc "Send a debug message to log endpoints" @doc "Send a debug message to log endpoints"
defmacro debug(verbosity, message, meta \\ []) do defmacro debug(verbosity, message, meta \\ []) do
@ -57,23 +58,27 @@ defmodule FarmbotCore.Logger do
def insert_log!(params) do def insert_log!(params) do
changeset = Log.changeset(%Log{}, params) changeset = Log.changeset(%Log{}, params)
try do try do
hash = Ecto.Changeset.get_field(changeset, :hash) hash = Ecto.Changeset.get_field(changeset, :hash)
case Repo.get_by(Log, hash: hash) do case Repo.get_by(Log, hash: hash) do
nil -> nil ->
Repo.insert!(changeset) Repo.insert!(changeset)
old ->
params = old ->
params =
params params
|> Map.put(:inserted_at, DateTime.utc_now) |> Map.put(:inserted_at, DateTime.utc_now())
|> Map.put(:duplicates, old.duplicates + 1) |> Map.put(:duplicates, old.duplicates + 1)
old
|> Log.changeset(params) old
|> Repo.update!() |> Log.changeset(params)
|> Repo.update!()
end end
catch catch
kind, err -> kind, err ->
IO.warn("Error inserting log: #{kind} #{inspect(err)}", __STACKTRACE__) IO.warn("Error inserting log: #{kind} #{inspect(err)}", __STACKTRACE__)
Ecto.Changeset.apply_changes(changeset) Ecto.Changeset.apply_changes(changeset)
end end
end end
@ -94,13 +99,16 @@ defmodule FarmbotCore.Logger do
@doc false @doc false
def dispatch_log(%Macro.Env{} = env, level, verbosity, message, meta) def dispatch_log(%Macro.Env{} = env, level, verbosity, message, meta)
when level in [:info, :debug, :busy, :warn, :success, :error, :fun, :assertion] and is_number(verbosity) and when level in @log_types and
is_binary(message) and is_list(meta) do is_number(verbosity) and
is_binary(message) and
is_list(meta) do
fun = fun =
case env.function do case env.function do
{fun, ar} -> "#{fun}/#{ar}" {fun, ar} -> "#{fun}/#{ar}"
nil -> "no_function" nil -> "no_function"
end end
%{ %{
level: level, level: level,
verbosity: verbosity, verbosity: verbosity,
@ -116,29 +124,8 @@ defmodule FarmbotCore.Logger do
@doc false @doc false
def dispatch_log(params) do def dispatch_log(params) do
params log = insert_log!(params)
|> insert_log!() FarmbotCore.LogExecutor.execute(log)
|> elixir_log()
end
defp elixir_log(%Log{} = log) do
logger_meta = [
application: :farmbot,
function: log.function,
file: log.file,
line: log.line,
module: log.module,
channels: log.meta[:channels] || log.meta["channels"],
verbosity: log.verbosity,
assertion_passed: log.meta[:assertion_passed]
# TODO Connor - fix time
# time: time
]
level = log.level
logger_level = if level in [:info, :debug, :warn, :error], do: level, else: :info
Elixir.Logger.bare_log(logger_level, log, logger_meta)
log
end end
@doc "Helper function for deciding if a message should be logged or not." @doc "Helper function for deciding if a message should be logged or not."

View File

@ -1,7 +1,7 @@
defmodule FarmbotExt.API.DirtyWorker.Supervisor do defmodule FarmbotExt.API.DirtyWorker.Supervisor do
@moduledoc """ @moduledoc """
Responsible for supervising assets that will need to be Responsible for supervising assets that will need to be
uploaded to the API via a `POST` or `PUT` request. uploaded to the API via a `POST` or `PUT` request.
""" """
use Supervisor use Supervisor
@ -10,7 +10,6 @@ defmodule FarmbotExt.API.DirtyWorker.Supervisor do
alias FarmbotCore.Asset.{ alias FarmbotCore.Asset.{
Device, Device,
DeviceCert, DeviceCert,
DiagnosticDump,
FarmEvent, FarmEvent,
FarmwareEnv, FarmwareEnv,
FarmwareInstallation, FarmwareInstallation,
@ -39,7 +38,6 @@ defmodule FarmbotExt.API.DirtyWorker.Supervisor do
{DirtyWorker, DeviceCert}, {DirtyWorker, DeviceCert},
{DirtyWorker, FbosConfig}, {DirtyWorker, FbosConfig},
{DirtyWorker, FirmwareConfig}, {DirtyWorker, FirmwareConfig},
{DirtyWorker, DiagnosticDump},
{DirtyWorker, FarmEvent}, {DirtyWorker, FarmEvent},
{DirtyWorker, FarmwareEnv}, {DirtyWorker, FarmwareEnv},
{DirtyWorker, FarmwareInstallation}, {DirtyWorker, FarmwareInstallation},

View File

@ -9,7 +9,6 @@ defmodule FarmbotExt.API.EagerLoader.Supervisor do
alias FarmbotCore.Asset.{ alias FarmbotCore.Asset.{
Device, Device,
DiagnosticDump,
FarmEvent, FarmEvent,
FarmwareEnv, FarmwareEnv,
FirstPartyFarmware, FirstPartyFarmware,
@ -42,7 +41,6 @@ defmodule FarmbotExt.API.EagerLoader.Supervisor do
def init(_args) do def init(_args) do
children = [ children = [
{EagerLoader, Device}, {EagerLoader, Device},
{EagerLoader, DiagnosticDump},
{EagerLoader, FarmEvent}, {EagerLoader, FarmEvent},
{EagerLoader, FarmwareEnv}, {EagerLoader, FarmwareEnv},
{EagerLoader, FirstPartyFarmware}, {EagerLoader, FirstPartyFarmware},

View File

@ -3,6 +3,7 @@
# If you run "mix test --cover", coverage assets end up here. # If you run "mix test --cover", coverage assets end up here.
/cover/ /cover/
*.coverdata
# The directory Mix downloads your dependencies sources to. # The directory Mix downloads your dependencies sources to.
/deps/ /deps/
@ -23,4 +24,4 @@ erl_crash.dump
farmbot_firmware-*.tar farmbot_firmware-*.tar
# Ignore vcr files # Ignore vcr files
*.txt *.txt

View File

@ -1,6 +1,6 @@
PREFIX=$(PWD)/priv PREFIX=$(PWD)/priv
BUILD=$(PWD)/_build
ARDUINO_SRC_DIR=$(PWD)/c_src/farmbot-arduino-firmware ARDUINO_SRC_DIR=$(PWD)/c_src/farmbot-arduino-firmware
BUILD=$(ARDUINO_SRC_DIR)/_build
.PHONY: all clean fbos_arduino_firmware .PHONY: all clean fbos_arduino_firmware
@ -24,4 +24,4 @@ $(PREFIX):
mkdir -p $(PREFIX) mkdir -p $(PREFIX)
$(BUILD): $(BUILD):
mkdir -p $(BUILD) mkdir -p $(BUILD)

View File

@ -0,0 +1,6 @@
{
"coverage_options": {
"treat_no_relevant_lines_as_covered": true,
"minimum_coverage": 55
}
}

View File

@ -166,6 +166,7 @@ defmodule FarmbotFirmware do
If the firmware is in any of the following states: If the firmware is in any of the following states:
* `:boot` * `:boot`
* `:transport_boot`
* `:no_config` * `:no_config`
* `:configuration` * `:configuration`
`command` will fail with `{:error, state}` `command` will fail with `{:error, state}`
@ -443,10 +444,13 @@ defmodule FarmbotFirmware do
# the `configuration_queue` and in the `command_queue`. # the `configuration_queue` and in the `command_queue`.
def handle_call(:close_transport, _from, %{status: s} = state) def handle_call(:close_transport, _from, %{status: s} = state)
when s != :transport_boot do when s != :transport_boot do
true = Process.demonitor(state.transport_ref) if is_reference(state.transport_ref) do
true = Process.demonitor(state.transport_ref)
end
:ok = GenServer.stop(state.transport_pid, :normal) :ok = GenServer.stop(state.transport_pid, :normal)
state = next_state =
goto( goto(
%{ %{
state state
@ -459,7 +463,7 @@ defmodule FarmbotFirmware do
:transport_boot :transport_boot
) )
{:reply, :ok, state} {:reply, :ok, next_state}
end end
def handle_call(:close_transport, _, %{status: s} = state) do def handle_call(:close_transport, _, %{status: s} = state) do
@ -501,8 +505,14 @@ defmodule FarmbotFirmware do
end end
end end
def handle_call({_tag, _code} = gcode, from, state) do def handle_call({tag, {kind, args}}, from, state) do
handle_command(gcode, from, state) handle_command({tag, {kind, args}}, from, state)
end
# TODO(RICK): Not sure if this is required.
# Some commands were missing a tag.
def handle_call({kind, args}, from, state) do
handle_command({nil, {kind, args}}, from, state)
end end
@doc false @doc false
@ -542,7 +552,7 @@ defmodule FarmbotFirmware do
# If not in an acceptable state, return an error immediately. # If not in an acceptable state, return an error immediately.
def handle_command(_, _, %{status: s} = state) def handle_command(_, _, %{status: s} = state)
when s in [:transport_boot, :boot, :no_config, :configuration] do when s in [:transport_boot, :boot, :no_config, :configuration] do
{:reply, {:error, s}, state} {:reply, {:error, "Can't send command when in #{inspect(s)} state"}, state}
end end
def handle_command({tag, {_, _}} = code, {pid, _ref}, state) do def handle_command({tag, {_, _}} = code, {pid, _ref}, state) do

View File

@ -93,9 +93,9 @@ defmodule FarmbotFirmware.Command do
Application.put_env(:farmbot_firmware, __MODULE__, new) Application.put_env(:farmbot_firmware, __MODULE__, new)
end end
defp debug?() do def debug?() do
Application.get_env(:farmbot_firmware, __MODULE__)[:debug_log] || false Application.get_env(:farmbot_firmware, __MODULE__)[:debug_log] || false
end end
defp debug_log(msg), do: if(debug?(), do: Logger.debug(msg), else: :ok) def debug_log(msg), do: if(debug?(), do: Logger.debug(msg), else: :ok)
end end

View File

@ -164,15 +164,15 @@ defmodule FarmbotFirmware.GCODE.Decoder do
end end
@spec decode_end_stops([binary()], Keyword.t()) :: Keyword.t() @spec decode_end_stops([binary()], Keyword.t()) :: Keyword.t()
defp decode_end_stops(list, acc \\ []) def decode_end_stops(list, acc \\ [])
defp decode_end_stops( def decode_end_stops(
[ [
<<arg::binary-1, "A", val0::binary>>, <<arg::binary-1, "A", val0::binary>>,
<<arg::binary-1, "B", val1::binary>> | rest <<arg::binary-1, "B", val1::binary>> | rest
], ],
acc acc
) do ) do
dc = String.downcase(arg) dc = String.downcase(arg)
acc = acc =
@ -185,19 +185,18 @@ defmodule FarmbotFirmware.GCODE.Decoder do
decode_end_stops(rest, acc) decode_end_stops(rest, acc)
end end
defp decode_end_stops([], acc), do: acc def decode_end_stops([], acc), do: acc
def decode_end_stops(error, _acc), do: [parse_error: error]
defp decode_end_stops(error, _acc), do: [parse_error: error] def decode_pv(["P" <> param_id, "V" <> value]) do
defp decode_pv(["P" <> param_id, "V" <> value]) do
param = Param.decode(String.to_integer(param_id)) param = Param.decode(String.to_integer(param_id))
{value, ""} = Float.parse(value) {value, ""} = Float.parse(value)
[{param, value}] [{param, value}]
end end
defp decode_ints(pvm, acc \\ []) def decode_ints(pvm, acc \\ [])
defp decode_ints([<<arg::binary-1, val::binary>> | rest], acc) do def decode_ints([<<arg::binary-1, val::binary>> | rest], acc) do
arg = arg =
arg arg
|> String.downcase() |> String.downcase()
@ -209,7 +208,7 @@ defmodule FarmbotFirmware.GCODE.Decoder do
end end
end end
defp decode_ints([], acc), do: Enum.reverse(acc) def decode_ints([], acc), do: Enum.reverse(acc)
@spec decode_echo(binary()) :: [binary()] @spec decode_echo(binary()) :: [binary()]
defp decode_echo(str) when is_binary(str) do defp decode_echo(str) when is_binary(str) do

View File

@ -27,6 +27,11 @@ defmodule FarmbotFirmware.PackageUtils do
|> assert_exists() |> assert_exists()
end end
def find_hex_file("none") do
Application.app_dir(:farmbot_firmware, ["priv", "eeprom_clear.ino.hex"])
|> assert_exists()
end
def find_hex_file(hardware) when is_binary(hardware), def find_hex_file(hardware) when is_binary(hardware),
do: {:error, "unknown firmware hardware: #{hardware}"} do: {:error, "unknown firmware hardware: #{hardware}"}

View File

@ -226,6 +226,7 @@ defmodule FarmbotFirmware.Param do
@seconds "(seconds)" @seconds "(seconds)"
@steps "(steps)" @steps "(steps)"
@steps_per_mm "(steps/mm)" @steps_per_mm "(steps/mm)"
@steps_per_s "(steps/s)"
@milliamps "(milliamps)" @milliamps "(milliamps)"
@doc "Translates a param to a human readable string" @doc "Translates a param to a human readable string"
@ -349,31 +350,31 @@ defmodule FarmbotFirmware.Param do
do: {"steps per mm, z-axis", @steps_per_mm, format_float(value)} do: {"steps per mm, z-axis", @steps_per_mm, format_float(value)}
def to_human(:movement_min_spd_x, value), def to_human(:movement_min_spd_x, value),
do: {"minimum speed, x-axis", @steps_per_mm, format_float(value)} do: {"minimum speed, x-axis", @steps_per_s, format_float(value)}
def to_human(:movement_min_spd_y, value), def to_human(:movement_min_spd_y, value),
do: {"minimum speed, y-axis", @steps_per_mm, format_float(value)} do: {"minimum speed, y-axis", @steps_per_s, format_float(value)}
def to_human(:movement_min_spd_z, value), def to_human(:movement_min_spd_z, value),
do: {"minimum speed, z-axis", @steps_per_mm, format_float(value)} do: {"minimum speed, z-axis", @steps_per_s, format_float(value)}
def to_human(:movement_home_spd_x, value), def to_human(:movement_home_spd_x, value),
do: {"homing speed, x-axis", @steps_per_mm, format_float(value)} do: {"homing speed, x-axis", @steps_per_s, format_float(value)}
def to_human(:movement_home_spd_y, value), def to_human(:movement_home_spd_y, value),
do: {"homing speed, y-axis", @steps_per_mm, format_float(value)} do: {"homing speed, y-axis", @steps_per_s, format_float(value)}
def to_human(:movement_home_spd_z, value), def to_human(:movement_home_spd_z, value),
do: {"homing speed, z-axis", @steps_per_mm, format_float(value)} do: {"homing speed, z-axis", @steps_per_s, format_float(value)}
def to_human(:movement_max_spd_x, value), def to_human(:movement_max_spd_x, value),
do: {"max speed, x-axis", @steps_per_mm, format_float(value)} do: {"max speed, x-axis", @steps_per_s, format_float(value)}
def to_human(:movement_max_spd_y, value), def to_human(:movement_max_spd_y, value),
do: {"max speed, y-axis", @steps_per_mm, format_float(value)} do: {"max speed, y-axis", @steps_per_s, format_float(value)}
def to_human(:movement_max_spd_z, value), def to_human(:movement_max_spd_z, value),
do: {"max speed, z-axis", @steps_per_mm, format_float(value)} do: {"max speed, z-axis", @steps_per_s, format_float(value)}
def to_human(:movement_invert_2_endpoints_x, value), def to_human(:movement_invert_2_endpoints_x, value),
do: {"invert endstops, x-axis", nil, format_bool(value)} do: {"invert endstops, x-axis", nil, format_bool(value)}
@ -412,13 +413,13 @@ defmodule FarmbotFirmware.Param do
do: {"microsteps, z-axis", nil, format_float(value)} do: {"microsteps, z-axis", nil, format_float(value)}
def to_human(:encoder_enabled_x, value), def to_human(:encoder_enabled_x, value),
do: {"enable encoders, x-axis", nil, format_bool(value)} do: {"enable encoders / stall detection, x-axis", nil, format_bool(value)}
def to_human(:encoder_enabled_y, value), def to_human(:encoder_enabled_y, value),
do: {"enable encoders, y-axis", nil, format_bool(value)} do: {"enable encoders / stall detection, y-axis", nil, format_bool(value)}
def to_human(:encoder_enabled_z, value), def to_human(:encoder_enabled_z, value),
do: {"enable encoders, z-axis", nil, format_bool(value)} do: {"enable encoders / stall detection, z-axis", nil, format_bool(value)}
def to_human(:encoder_type_x, value), def to_human(:encoder_type_x, value),
do: {"encoder type, x-axis", nil, format_float(value)} do: {"encoder type, x-axis", nil, format_float(value)}
@ -430,13 +431,13 @@ defmodule FarmbotFirmware.Param do
do: {"encoder type, z-axis", nil, format_float(value)} do: {"encoder type, z-axis", nil, format_float(value)}
def to_human(:encoder_missed_steps_max_x, value), def to_human(:encoder_missed_steps_max_x, value),
do: {"max missed steps, x-axis", nil, format_float(value)} do: {"max missed steps, x-axis", @steps, format_float(value)}
def to_human(:encoder_missed_steps_max_y, value), def to_human(:encoder_missed_steps_max_y, value),
do: {"max missed steps, y-axis", nil, format_float(value)} do: {"max missed steps, y-axis", @steps, format_float(value)}
def to_human(:encoder_missed_steps_max_z, value), def to_human(:encoder_missed_steps_max_z, value),
do: {"max missed steps, z-axis", nil, format_float(value)} do: {"max missed steps, z-axis", @steps, format_float(value)}
def to_human(:encoder_scaling_x, value), def to_human(:encoder_scaling_x, value),
do: {"encoder scaling, x-axis", nil, format_float(value)} do: {"encoder scaling, x-axis", nil, format_float(value)}
@ -448,13 +449,13 @@ defmodule FarmbotFirmware.Param do
do: {"encoder scaling, z-axis", nil, format_float(value)} do: {"encoder scaling, z-axis", nil, format_float(value)}
def to_human(:encoder_missed_steps_decay_x, value), def to_human(:encoder_missed_steps_decay_x, value),
do: {"encoder missed steps decay, x-axis", nil, format_float(value)} do: {"missed step decay, x-axis", @steps, format_float(value)}
def to_human(:encoder_missed_steps_decay_y, value), def to_human(:encoder_missed_steps_decay_y, value),
do: {"encoder missed steps decay, y-axis", nil, format_float(value)} do: {"missed step decay, y-axis", @steps, format_float(value)}
def to_human(:encoder_missed_steps_decay_z, value), def to_human(:encoder_missed_steps_decay_z, value),
do: {"encoder missed steps decay, z-axis", nil, format_float(value)} do: {"missed step decay, z-axis", @steps, format_float(value)}
def to_human(:encoder_use_for_pos_x, value), def to_human(:encoder_use_for_pos_x, value),
do: {"use encoders for positioning, x-axis", nil, format_bool(value)} do: {"use encoders for positioning, x-axis", nil, format_bool(value)}

View File

@ -11,26 +11,24 @@ defmodule FarmbotFirmware.Request do
{:ok, GCODE.t()} {:ok, GCODE.t()}
| {:error, | {:error,
:invalid_command | :firmware_error | FarmbotFirmware.status()} :invalid_command | :firmware_error | FarmbotFirmware.status()}
@whitelist [
:parameter_read,
:status_read,
:pin_read,
:end_stops_read,
:position_read,
:software_version_read
]
def request(firmware_server \\ FarmbotFirmware, code) def request(firmware_server \\ FarmbotFirmware, code)
def request(firmware_server, {_tag, {kind, _}} = code) do def request(firmware_server, {_tag, {kind, _}} = code) do
if kind not in [ if kind not in @whitelist do
:parameter_read,
:status_read,
:pin_read,
:end_stops_read,
:position_read,
:software_version_read
] do
raise ArgumentError, "#{kind} is not a valid request." raise ArgumentError, "#{kind} is not a valid request."
end end
case GenServer.call(firmware_server, code, :infinity) do case GenServer.call(firmware_server, code, :infinity) do
{:ok, tag} -> {:ok, tag} -> wait_for_request_result(tag, code)
wait_for_request_result(tag, code) {:error, status} -> {:error, status}
{:error, status} ->
{:error, status}
end end
end end
@ -38,16 +36,26 @@ defmodule FarmbotFirmware.Request do
request(firmware_server, {to_string(:rand.uniform(100)), code}) request(firmware_server, {to_string(:rand.uniform(100)), code})
end end
def request_timeout(tag, code, result \\ nil) do
if result do
{:ok, {tag, result}}
else
{:error, "timeout waiting for request to complete: #{inspect(code)}"}
end
end
# This is a bit weird but let me explain: # This is a bit weird but let me explain:
# if this function `receive`s # if this function `receive`s
# * report_error # * report_error
# * report_invalid # * report_invalid
# * report_emergency_lock # * report_emergency_lock
# it needs to return an error. # it needs to return an error.
#
# If this function `receive`s # If this function `receive`s
# * report_success # * report_success
# when no valid data has been collected from `wait_for_request_result_process` # when no valid data has been collected from `wait_for_request_result_process`
# it needs to return an error. # it needs to return an error.
#
# If this function `receive`s # If this function `receive`s
# * report_success # * report_success
# when valid data has been collected from `wait_for_request_result_process` # when valid data has been collected from `wait_for_request_result_process`
@ -81,10 +89,7 @@ defmodule FarmbotFirmware.Request do
{tag, report} -> {tag, report} ->
wait_for_request_result_process(report, tag, code, result) wait_for_request_result_process(report, tag, code, result)
after after
10_000 -> 10_000 -> request_timeout(tag, code, result)
if result,
do: {:ok, {tag, result}},
else: {:error, "timeout waiting for request to complete"}
end end
end end

View File

@ -215,7 +215,7 @@ defmodule FarmbotFirmware.StubTransport do
resp_codes = [ resp_codes = [
GCODE.new(:report_echo, [GCODE.encode(code)]), GCODE.new(:report_echo, [GCODE.encode(code)]),
GCODE.new(:report_begin, [], tag), GCODE.new(:report_begin, [], tag),
GCODE.new(:report_software_version, ["8.0.0.S"]), GCODE.new(:report_software_version, ["8.0.0.S.stub"]),
GCODE.new(:report_success, [], tag) GCODE.new(:report_success, [], tag)
] ]

View File

@ -14,7 +14,7 @@ defmodule FarmbotFirmware.UARTTransport do
device = Keyword.fetch!(args, :device) device = Keyword.fetch!(args, :device)
handle_gcode = Keyword.fetch!(args, :handle_gcode) handle_gcode = Keyword.fetch!(args, :handle_gcode)
reset = Keyword.get(args, :reset) reset = Keyword.get(args, :reset)
{:ok, uart} = uart_adapter().start_link() {:ok, uart} = UartDefaultAdapter.start_link()
{:ok, {:ok,
%{ %{
@ -27,11 +27,11 @@ defmodule FarmbotFirmware.UARTTransport do
end end
def terminate(_, %{uart: uart}) do def terminate(_, %{uart: uart}) do
uart_adapter().stop(uart) UartDefaultAdapter.stop(uart)
end end
def handle_info(:timeout, %{open: false} = state) do def handle_info(:timeout, %{open: false} = state) do
opts = uart_adapter().generate_opts() opts = UartDefaultAdapter.generate_opts()
with :ok <- open(state.uart, state.device, opts), with :ok <- open(state.uart, state.device, opts),
:ok <- reset(state) do :ok <- reset(state) do
@ -55,7 +55,7 @@ defmodule FarmbotFirmware.UARTTransport do
def handle_call(code, _from, state) do def handle_call(code, _from, state) do
str = GCODE.encode(code) str = GCODE.encode(code)
r = uart_adapter().write(state.uart, str) r = UartDefaultAdapter.write(state.uart, str)
{:reply, r, state} {:reply, r, state}
end end
@ -68,10 +68,6 @@ defmodule FarmbotFirmware.UARTTransport do
end end
def open(uart_pid, device_path, opts) do def open(uart_pid, device_path, opts) do
uart_adapter().open(uart_pid, device_path, opts) UartDefaultAdapter.open(uart_pid, device_path, opts)
end
def uart_adapter() do
UartDefaultAdapter
end end
end end

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,68 @@
defmodule FarmbotFirmware.CommandTest do
use ExUnit.Case
use Mimic
setup :verify_on_exit!
alias FarmbotFirmware.Command
import ExUnit.CaptureLog
@subject FarmbotFirmware.Command
test "command() runs RPCs" do
arg = [transport: FarmbotFirmware.StubTransport]
{:ok, pid} = FarmbotFirmware.start_link(arg, [])
send(pid, :timeout)
assert {:error, :emergency_lock} ==
FarmbotFirmware.command(pid, {:command_emergency_lock, []})
assert :ok == FarmbotFirmware.command(pid, {:command_emergency_unlock, []})
cmd = {:parameter_write, [movement_stop_at_home_x: 0.0]}
assert :ok == FarmbotFirmware.command(pid, cmd)
end
test "command() refuses to run RPCs in :boot state" do
arg = [transport: FarmbotFirmware.StubTransport]
{:ok, pid} = FarmbotFirmware.start_link(arg, [])
send(pid, :timeout)
{:error, message} = @subject.command(pid, {:a, {:b, :c}})
assert "Can't send command when in :boot state" == message
end
test "enable_debug_logs" do
Application.put_env(:farmbot_firmware, @subject, foo: :bar, debug_log: false)
old_env = Application.get_env(:farmbot_firmware, @subject)
assert false == Keyword.fetch!(old_env, :debug_log)
assert :bar == Keyword.fetch!(old_env, :foo)
assert false == @subject.debug?()
refute capture_log(fn ->
@subject.debug_log("Never Shown")
end) =~ "Never Shown"
# === Change ENV settings
assert :ok ==
Command.enable_debug_logs()
assert capture_log(fn ->
@subject.debug_log("Good!")
end) =~ "Good!"
assert true == @subject.debug?()
new_env = Application.get_env(:farmbot_firmware, @subject)
assert true == Keyword.fetch!(new_env, :debug_log)
assert :bar == Keyword.fetch!(new_env, :foo)
# === And back again
assert :ok == Command.disable_debug_logs()
even_newer = Application.get_env(:farmbot_firmware, @subject)
assert false == Keyword.fetch!(even_newer, :debug_log)
assert :bar == Keyword.fetch!(even_newer, :foo)
assert false == @subject.debug?()
refute capture_log(fn ->
@subject.debug_log("Also Never Shown")
end) =~ "Also Never Shown"
end
end

View File

@ -0,0 +1,35 @@
defmodule FarmbotFirmware.GCODE.DecoderTest do
use ExUnit.Case
use Mimic
setup :verify_on_exit!
alias FarmbotFirmware.GCODE.Decoder
test "Decoder.decode_ints(pvm, acc \\ [])" do
assert [a: 1, b: 2, c: 3] == Decoder.decode_ints(["A1", "B2", "C3"])
end
test "Decoder.decode_pv" do
assert [param_config_ok: 3.0] == Decoder.decode_pv(["P2", "V3"])
end
# NOTE: Theese values are totally random and may
# not represent real-world use of the GCode.
test "Decoder.decode_floats" do
assert {:command_movement, []} == Decoder.do_decode("G00", ["XA0.0"])
assert {:report_load, [x: 0.0]} == Decoder.do_decode("R89", ["X0.0"])
assert {:report_encoders_raw, [x: 0.0]} == Decoder.do_decode("R85", ["X0"])
assert {:report_encoders_scaled, []} == Decoder.do_decode("R84", ["XA-0.0"])
assert {:report_position, []} == Decoder.do_decode("R82", ["XA-0"])
assert {:report_position_change, []} == Decoder.do_decode("R17", ["XB1.0"])
assert {:report_position_change, []} == Decoder.do_decode("R16", ["XB-1.0"])
assert {:report_position_change, []} == Decoder.do_decode("R15", ["XB10"])
assert {:report_position_change, [x: 1.0]} ==
Decoder.do_decode("R15", ["X1"])
assert {:report_position_change, []} == Decoder.do_decode("R16", ["YA1"])
assert {:command_movement, []} == Decoder.do_decode("G00", ["YB1"])
assert {:report_load, []} == Decoder.do_decode("R89", ["ZA1"])
assert {:report_position, []} == Decoder.do_decode("R82", ["ZB1"])
end
end

View File

@ -0,0 +1,21 @@
defmodule FarmbotFirmware.RequestTest do
use ExUnit.Case
use Mimic
setup :verify_on_exit!
alias FarmbotFirmware.Request
test "request whitelist" do
boom = fn ->
Request.request({:a, {:b, :c}})
end
assert_raise ArgumentError, boom
end
test "timeout handler" do
ok = Request.request_timeout(:tag, :code, :result)
error = Request.request_timeout(:tag, :code)
assert {:ok, {:tag, :result}} == ok
assert {:error, "timeout waiting for request to complete: :code"} == error
end
end

View File

@ -2,15 +2,55 @@ defmodule FarmbotFirmwareTest do
use ExUnit.Case use ExUnit.Case
doctest FarmbotFirmware doctest FarmbotFirmware
test "initialization (WIP)" do def try_command(pid, cmd) do
pid = GenServer.call(pid, cmd, :infinity)
start_supervised!({ end
FarmbotFirmware,
transport: FarmbotFirmware.StubTransport,
side_effects: FarmbotCore.FirmwareSideEffects
})
# WIP def firmware_server do
assert is_pid(pid) arg = [transport: FarmbotFirmware.StubTransport]
{:ok, pid} = FarmbotFirmware.start_link(arg, [])
send(pid, :timeout)
try_command(pid, {nil, {:command_emergency_lock, []}})
try_command(pid, {nil, {:command_emergency_unlock, []}})
pid
end
test "various command()s" do
pid = firmware_server()
cmds = [
# TEST VALUES EXTRACTED FROM A REAL BOT
# They may change over time, so a test failure
# is not necessarily indicitive of a defect,
# but it *IS* indicitve of a change (perhaps unepxected?)
# in runtime behavior.
#
# Approach with caution.
{:pin_mode_write, [p: 13, m: 1]},
{"1", {:position_write_zero, [:x]}},
{"23", {:parameter_write, [movement_invert_2_endpoints_y: 1.0]}},
{"24", {:parameter_write, [movement_stop_at_home_x: 0.0]}},
{"40", {:pin_write, [p: 13, v: 0, m: 0]}},
{"49", {:pin_mode_write, [p: 13, m: 1]}},
{"55", {:pin_mode_write, [p: 13, m: 1]}},
{"59", {:pin_mode_write, [p: 13, m: 1]}},
{"94", {:parameter_write, [movement_home_up_y: 1.0]}},
{"94", {:pin_write, [p: 13, v: 1, m: 0]}},
{"98", {:parameter_write, [movement_home_up_y: 0.0]}},
{"99", {:parameter_write, [movement_stop_at_home_x: 1.0]}},
{nil, {:command_emergency_lock, []}},
{nil, {:command_emergency_lock, []}},
{nil,
{:command_movement,
[x: 0.0, y: 0.0, z: 0.0, a: 400.0, b: 400.0, c: 400.0]}},
{nil,
{:command_movement,
[x: 0.0, y: 0.0, z: 10.0, a: 400.0, b: 400.0, c: 400.0]}},
{nil, {:command_emergency_lock, []}}
]
Enum.map(cmds, fn {tag, cmd} ->
assert {:ok, tag} = try_command(pid, {tag, cmd})
end)
end end
end end

View File

@ -383,6 +383,81 @@ defmodule FarmbotFirmware.GCODETest do
assert "R06 Z2 Q1" = assert "R06 Z2 Q1" =
GCODE.encode({"1", {:report_calibration_state, [z: :end]}}) GCODE.encode({"1", {:report_calibration_state, [z: :end]}})
assert "R15 X200.00" ==
GCODE.encode({nil, {:report_position_change, [{:x, 200.0}]}})
assert "R16 Y12.00" ==
GCODE.encode({nil, {:report_position_change, [{:y, 12.0}]}})
assert "R16 Z37.02" ==
GCODE.encode({nil, {:report_position_change, [{:z, 37.02}]}})
assert "R20" == GCODE.encode({nil, {:report_parameters_complete, []}})
assert "R21 P0 V1.20" ==
GCODE.encode(
{nil, {:report_parameter_value, [{:param_version, 1.2}]}}
)
assert "R41 P1 V2 M0 Q3" ==
GCODE.encode(
{nil,
{:report_pin_value, [{:p, 1}, {:v, 2}, {:m, 0}, {:q, 3}]}}
)
assert "R23 P0 V1.20" ==
GCODE.encode(
{nil,
{:report_calibration_parameter_value, [{:param_version, 1.2}]}}
)
enc_params = [xa: 1, xb: 2, ya: 3, yb: 4, za: 5, zb: 6]
assert "R81 XA1 XB2 YA3 YB4 ZA5 ZB6" ==
GCODE.encode({nil, {:report_end_stops, enc_params}})
pos_params = [x: 1.4, y: 2.3, z: 3.2, s: 4.1]
assert "R82 X1.40 Y2.30 Z3.20 S4.10" ==
GCODE.encode({nil, {:report_position, pos_params}})
params = [x: 1.4, y: 2.3, z: 3.2]
assert "R84 X1.40 Y2.30 Z3.20" ==
GCODE.encode({nil, {:report_encoders_scaled, params}})
params = [x: 1.4, y: 2.3, z: 3.2]
assert "R85 X1.40 Y2.30 Z3.20" ==
GCODE.encode({nil, {:report_encoders_raw, params}})
params = [x: 1.4, y: 2.3, z: 3.2]
assert "R89 X1.40 Y2.30 Z3.20" ==
GCODE.encode({nil, {:report_load, params}})
assert "G00 X0.00" ==
GCODE.encode({nil, {:command_movement_home, [:x]}})
assert "G00 Y0.00" ==
GCODE.encode({nil, {:command_movement_home, [:y]}})
assert "G00 Z0.00" ==
GCODE.encode({nil, {:command_movement_home, [:z]}})
assert(
"F21 P222" ==
GCODE.encode({nil, {:parameter_read, [:pin_guard_5_time_out]}})
)
p = {:calibration_parameter_write, [{:pin_guard_5_time_out, 1.2}]}
assert "F23 P222 V1.20" ==
GCODE.encode({nil, p})
assert "F42 P13" == GCODE.encode({nil, {:pin_read, [p: 13]}})
assert "F61 P54" == GCODE.encode({nil, {:servo_write, [p: 54]}})
end end
test "retry" do test "retry" do

View File

@ -0,0 +1,55 @@
defmodule FarmbotFirmware.PackageUtilsTest do
use ExUnit.Case
use Mimic
setup :verify_on_exit!
alias FarmbotFirmware.PackageUtils
test "package to string" do
assert PackageUtils.package_to_string("arduino") ==
"Arduino/RAMPS (Genesis v1.2)"
assert PackageUtils.package_to_string("farmduino") ==
"Farmduino (Genesis v1.3)"
assert PackageUtils.package_to_string("farmduino_k14") ==
"Farmduino (Genesis v1.4)"
assert PackageUtils.package_to_string("farmduino_k15") ==
"Farmduino (Genesis v1.5)"
assert PackageUtils.package_to_string("express_k10") ==
"Farmduino (Express v1.0)"
assert PackageUtils.package_to_string("misc") == "misc"
end
test "not finding files" do
expect(File, :exists?, 1, fn _fname ->
false
end)
{:error, error} = PackageUtils.find_hex_file("arduino")
assert String.contains?(error, "Please call `make arudino_firmware`.")
end
test "finding files" do
{:ok, path} = PackageUtils.find_hex_file("arduino")
assert String.contains?(path, "/farmbot_firmware/priv/arduino_firmware.hex")
{:ok, path} = PackageUtils.find_hex_file("farmduino")
assert String.contains?(path, "/farmbot_firmware/priv/farmduino.hex")
{:ok, path} = PackageUtils.find_hex_file("farmduino_k14")
assert String.contains?(path, "/farmbot_firmware/priv/farmduino_k14.hex")
{:ok, path} = PackageUtils.find_hex_file("farmduino_k15")
assert String.contains?(path, "/farmbot_firmware/priv/farmduino_k15.hex")
{:ok, path} = PackageUtils.find_hex_file("express_k10")
assert String.contains?(path, "/farmbot_firmware/priv/express_k10.hex")
assert {:error, "unknown firmware hardware: no"} ==
PackageUtils.find_hex_file("no")
end
end

View File

@ -0,0 +1,220 @@
defmodule FarmbotFirmware.ParamTest do
use ExUnit.Case
alias FarmbotFirmware.Param
test "to_human()" do
float_value = 1.23
seconds = "(seconds)"
steps = "(steps)"
steps_per_s = "(steps/s)"
steps_per_mm = "(steps/mm)"
assert Param.to_human(:param_test, 1) ==
{"param_test", nil, true}
assert Param.to_human(:param_config_ok, 1) ==
{"param_config_ok", nil, true}
assert Param.to_human(:param_use_eeprom, 1) ==
{"use eeprom", nil, true}
assert Param.to_human(:param_e_stop_on_mov_err, 1) ==
{"e-stop on movement errors", nil, true}
assert Param.to_human(:movement_timeout_x, float_value) ==
{"timeout after, x-axis", seconds, "1.2"}
assert Param.to_human(:movement_timeout_y, float_value) ==
{"timeout after, y-axis", seconds, "1.2"}
assert Param.to_human(:movement_timeout_z, float_value) ==
{"timeout after, z-axis", seconds, "1.2"}
assert Param.to_human(:movement_keep_active_x, 1) ==
{"always power motors, x-axis", nil, true}
assert Param.to_human(:movement_keep_active_y, 1) ==
{"always power motors, y-axis", nil, true}
assert Param.to_human(:movement_keep_active_z, 1) ==
{"always power motors, z-axis", nil, true}
assert Param.to_human(:movement_home_at_boot_x, 1) ==
{"find home on boot, x-axis", nil, true}
assert Param.to_human(:movement_home_at_boot_y, 1) ==
{"find home on boot, y-axis", nil, true}
assert Param.to_human(:movement_home_at_boot_z, 1) ==
{"find home on boot, z-axis", nil, true}
assert Param.to_human(:movement_invert_endpoints_x, 1) ==
{"swap endstops, x-axis", nil, true}
assert Param.to_human(:movement_invert_endpoints_y, 1) ==
{"swap endstops, y-axis", nil, true}
assert Param.to_human(:movement_invert_endpoints_z, 1) ==
{"swap endstops, z-axis", nil, true}
assert Param.to_human(:movement_enable_endpoints_x, 1) ==
{"enable endstops, x-axis", nil, true}
assert Param.to_human(:movement_enable_endpoints_y, 1) ==
{"enable endstops, y-axis", nil, true}
assert Param.to_human(:movement_enable_endpoints_z, 1) ==
{"enable endstops, z-axis", nil, true}
assert Param.to_human(:movement_invert_motor_x, 1) ==
{"invert motor, x-axis", nil, true}
assert Param.to_human(:movement_invert_motor_y, 1) ==
{"invert motor, y-axis", nil, true}
assert Param.to_human(:movement_invert_motor_z, 1) ==
{"invert motor, z-axis", nil, true}
assert Param.to_human(:movement_secondary_motor_x, 1) ==
{"enable 2nd x motor", nil, true}
assert Param.to_human(:movement_secondary_motor_invert_x, 1) ==
{"invert 2nd x motor", nil, true}
assert Param.to_human(:movement_stop_at_home_x, 1) ==
{"stop at home, x-axis", nil, true}
assert Param.to_human(:movement_stop_at_home_y, 1) ==
{"stop at home, y-axis", nil, true}
assert Param.to_human(:movement_stop_at_home_z, 1) ==
{"stop at home, z-axis", nil, true}
assert Param.to_human(:movement_step_per_mm_x, float_value) ==
{"steps per mm, x-axis", steps_per_mm, "1.2"}
assert Param.to_human(:movement_step_per_mm_y, float_value) ==
{"steps per mm, y-axis", steps_per_mm, "1.2"}
assert Param.to_human(:movement_step_per_mm_z, float_value) ==
{"steps per mm, z-axis", steps_per_mm, "1.2"}
assert Param.to_human(:movement_min_spd_x, float_value) ==
{"minimum speed, x-axis", steps_per_s, "1.2"}
assert Param.to_human(:movement_min_spd_y, float_value) ==
{"minimum speed, y-axis", steps_per_s, "1.2"}
assert Param.to_human(:movement_min_spd_z, float_value) ==
{"minimum speed, z-axis", steps_per_s, "1.2"}
assert Param.to_human(:movement_home_spd_x, float_value) ==
{"homing speed, x-axis", steps_per_s, "1.2"}
assert Param.to_human(:movement_home_spd_y, float_value) ==
{"homing speed, y-axis", steps_per_s, "1.2"}
assert Param.to_human(:movement_home_spd_z, float_value) ==
{"homing speed, z-axis", steps_per_s, "1.2"}
assert Param.to_human(:movement_max_spd_x, float_value) ==
{"max speed, x-axis", steps_per_s, "1.2"}
assert Param.to_human(:movement_max_spd_y, float_value) ==
{"max speed, y-axis", steps_per_s, "1.2"}
assert Param.to_human(:movement_max_spd_z, float_value) ==
{"max speed, z-axis", steps_per_s, "1.2"}
assert Param.to_human(:movement_invert_2_endpoints_x, 1) ==
{"invert endstops, x-axis", nil, true}
assert Param.to_human(:movement_invert_2_endpoints_y, 1) ==
{"invert endstops, y-axis", nil, true}
assert Param.to_human(:movement_invert_2_endpoints_z, 1) ==
{"invert endstops, z-axis", nil, true}
assert Param.to_human(:encoder_enabled_x, 1) ==
{"enable encoders / stall detection, x-axis", nil, true}
assert Param.to_human(:encoder_enabled_y, 1) ==
{"enable encoders / stall detection, y-axis", nil, true}
assert Param.to_human(:encoder_enabled_z, 1) ==
{"enable encoders / stall detection, z-axis", nil, true}
assert Param.to_human(:encoder_type_x, float_value) ==
{"encoder type, x-axis", nil, "1.2"}
assert Param.to_human(:encoder_type_y, float_value) ==
{"encoder type, y-axis", nil, "1.2"}
assert Param.to_human(:encoder_type_z, float_value) ==
{"encoder type, z-axis", nil, "1.2"}
assert Param.to_human(:encoder_scaling_x, float_value) ==
{"encoder scaling, x-axis", nil, "1.2"}
assert Param.to_human(:encoder_scaling_y, float_value) ==
{"encoder scaling, y-axis", nil, "1.2"}
assert Param.to_human(:encoder_scaling_z, float_value) ==
{"encoder scaling, z-axis", nil, "1.2"}
assert Param.to_human(:encoder_missed_steps_decay_x, float_value) ==
{"missed step decay, x-axis", steps, "1.2"}
assert Param.to_human(:encoder_missed_steps_decay_y, float_value) ==
{"missed step decay, y-axis", steps, "1.2"}
assert Param.to_human(:encoder_missed_steps_decay_z, float_value) ==
{"missed step decay, z-axis", steps, "1.2"}
assert Param.to_human(:encoder_use_for_pos_x, 1) ==
{"use encoders for positioning, x-axis", nil, true}
assert Param.to_human(:encoder_use_for_pos_y, 1) ==
{"use encoders for positioning, y-axis", nil, true}
assert Param.to_human(:encoder_use_for_pos_z, 1) ==
{"use encoders for positioning, z-axis", nil, true}
assert Param.to_human(:encoder_invert_x, 1) ==
{"invert encoders, x-axis", nil, true}
assert Param.to_human(:encoder_invert_y, 1) ==
{"invert encoders, y-axis", nil, true}
assert Param.to_human(:encoder_invert_z, 1) ==
{"invert encoders, z-axis", nil, true}
assert Param.to_human(:movement_stop_at_max_x, 1) ==
{"stop at max, x-axis", nil, true}
assert Param.to_human(:movement_stop_at_max_y, 1) ==
{"stop at max, y-axis", nil, true}
assert Param.to_human(:movement_stop_at_max_z, 1) ==
{"stop at max, z-axis", nil, true}
assert Param.to_human(:pin_guard_1_active_state, 0) ==
{"pin guard 1 safe state", nil, "HIGH"}
assert Param.to_human(:pin_guard_2_active_state, 0) ==
{"pin guard 2 safe state", nil, "HIGH"}
assert Param.to_human(:pin_guard_3_active_state, 0) ==
{"pin guard 3 safe state", nil, "HIGH"}
assert Param.to_human(:pin_guard_4_active_state, 0) ==
{"pin guard 4 safe state", nil, "HIGH"}
assert Param.to_human(:pin_guard_5_active_state, 0) ==
{"pin guard 5 safe state", nil, "HIGH"}
end
test "Handling of uknown parameters" do
assert :unknown_parameter == Param.decode(-999)
end
end

View File

@ -1,4 +1,5 @@
Application.ensure_all_started(:mimic) Application.ensure_all_started(:mimic)
Mimic.copy(FarmbotFirmware.UartDefaultAdapter) Mimic.copy(FarmbotFirmware.UartDefaultAdapter)
Mimic.copy(Circuits.UART) Mimic.copy(Circuits.UART)
Mimic.copy(File)
ExUnit.start() ExUnit.start()

View File

@ -18,4 +18,5 @@ erl_crash.dump
*.sqlite3 *.sqlite3
auth_secret.exs auth_secret.exs
log log
*.csv *.csv
*.coverdata

View File

@ -34,7 +34,6 @@ config :nerves, :firmware,
config :farmbot_core, FarmbotCore.AssetMonitor, checkup_time_ms: 5_000 config :farmbot_core, FarmbotCore.AssetMonitor, checkup_time_ms: 5_000
config :farmbot_core, FarmbotCore.EctoMigrator, config :farmbot_core, FarmbotCore.EctoMigrator,
expected_fw_versions: ["6.4.2.F", "6.4.2.R", "6.4.2.G"],
default_firmware_io_logs: false, default_firmware_io_logs: false,
default_server: "https://my.farm.bot", default_server: "https://my.farm.bot",
default_ntp_server_1: "0.pool.ntp.org", default_ntp_server_1: "0.pool.ntp.org",
@ -74,9 +73,7 @@ config :farmbot, FarmbotOS.System,
system_tasks: FarmbotOS.Platform.Host.SystemTasks system_tasks: FarmbotOS.Platform.Host.SystemTasks
config :farmbot, FarmbotOS.Configurator, config :farmbot, FarmbotOS.Configurator,
data_layer: FarmbotOS.Configurator.ConfigDataLayer, network_layer: FarmbotOS.Configurator.FakeNetworkLayer
network_layer: FarmbotOS.Configurator.FakeNetworkLayer,
telemetry_layer: FarmbotOS.Configurator.DetsTelemetryLayer
config :farmbot, FarmbotOS.Platform.Supervisor, config :farmbot, FarmbotOS.Platform.Supervisor,
platform_children: [ platform_children: [

View File

@ -43,4 +43,3 @@ config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FbosConfig,
firmware_flash_attempt_threshold: 0 firmware_flash_attempt_threshold: 0
config :plug, :validate_header_keys_during_test, true config :plug, :validate_header_keys_during_test, true
config :farmbot, :muon_trap_adapter, Avrdude.MuonTrapTestAdapter

View File

@ -0,0 +1,6 @@
{
"coverage_options": {
"treat_no_relevant_lines_as_covered": true,
"minimum_coverage": 29
}
}

View File

@ -31,7 +31,7 @@ defmodule Avrdude do
# call the function for resetting the line before executing avrdude. # call the function for resetting the line before executing avrdude.
call_reset_fun(reset_fun) call_reset_fun(reset_fun)
Avrdude.MuonTrapAdapter.cmd("avrdude", args, MuonTrap.cmd("avrdude", args,
into: IO.stream(:stdio, :line), into: IO.stream(:stdio, :line),
stderr_to_stdout: true stderr_to_stdout: true
) )

View File

@ -1,21 +0,0 @@
defmodule Avrdude.MuonTrapAdapter do
@type exe :: String.t()
@type args :: [String.t()]
@type io_stream :: Enumerable.t()
@type option :: {:into, io_stream()} | {:stderr_to_stdout, boolean()}
@callback cmd(exe, args, list(option)) :: {String.t(), non_neg_integer}
@doc false
def adapter do
Application.get_env(
:farmbot,
:muon_trap_adapter,
Avrdude.MuonTrapDefaultAdapter
)
end
def cmd(exe, args, options) do
adapter().cmd(exe, args, options)
end
end

View File

@ -1,6 +0,0 @@
defmodule Avrdude.MuonTrapDefaultAdapter do
@behaviour Avrdude.MuonTrapAdapter
@impl Avrdude.MuonTrapAdapter
defdelegate cmd(exe, args, options), to: MuonTrap, as: :cmd
end

View File

@ -7,6 +7,8 @@ defmodule FarmbotOS.Configurator.Router do
import Phoenix.HTML import Phoenix.HTML
use Plug.Router use Plug.Router
use Plug.Debugger, otp_app: :farmbot use Plug.Debugger, otp_app: :farmbot
alias FarmbotOS.Configurator.ConfigDataLayer
plug(Plug.Logger) plug(Plug.Logger)
plug(Plug.Static, from: {:farmbot, "priv/static"}, at: "/") plug(Plug.Static, from: {:farmbot, "priv/static"}, at: "/")
plug(Plug.Parsers, parsers: [:urlencoded, :multipart]) plug(Plug.Parsers, parsers: [:urlencoded, :multipart])
@ -21,13 +23,10 @@ defmodule FarmbotOS.Configurator.Router do
plug(:match) plug(:match)
plug(:dispatch) plug(:dispatch)
@data_layer Application.get_env(:farmbot, FarmbotOS.Configurator)[:data_layer]
@network_layer Application.get_env(:farmbot, FarmbotOS.Configurator)[ @network_layer Application.get_env(:farmbot, FarmbotOS.Configurator)[
:network_layer :network_layer
] ]
@telemetry_layer Application.get_env(:farmbot, FarmbotOS.Configurator)[ @telemetry_layer FarmbotOS.Configurator.DetsTelemetryLayer
:telemetry_layer
]
# Trigger for captive portal for various operating systems # Trigger for captive portal for various operating systems
get("/gen_204", do: redir(conn, "/")) get("/gen_204", do: redir(conn, "/"))
@ -270,6 +269,11 @@ defmodule FarmbotOS.Configurator.Router do
get "/finish" do get "/finish" do
FarmbotCore.Logger.debug(1, "Configuration complete") FarmbotCore.Logger.debug(1, "Configuration complete")
# TODO(Rick): This pattern match is not 100% accurate.
# TO see what I mean, try calling `save_config/1` with
# _only_ the parameters provided in the line below-
# it will crash as it is missing numerous keys.
# It might be good to add an error page or something.
case get_session(conn) do case get_session(conn) do
%{ %{
"ifname" => _, "ifname" => _,
@ -277,10 +281,12 @@ defmodule FarmbotOS.Configurator.Router do
"auth_config_password" => _, "auth_config_password" => _,
"auth_config_server" => _ "auth_config_server" => _
} -> } ->
FarmbotCore.Logger.debug(1, "Configuration success!")
save_config(get_session(conn)) save_config(get_session(conn))
render_page(conn, "finish") render_page(conn, "finish")
_ -> _ ->
FarmbotCore.Logger.debug(1, "Configuration FAIL")
redir(conn, "/") redir(conn, "/")
end end
end end
@ -355,23 +361,23 @@ defmodule FarmbotOS.Configurator.Router do
end end
defp load_last_reset_reason do defp load_last_reset_reason do
@data_layer.load_last_reset_reason() ConfigDataLayer.load_last_reset_reason()
end end
defp load_email do defp load_email do
@data_layer.load_email() ConfigDataLayer.load_email()
end end
defp load_password do defp load_password do
@data_layer.load_password() ConfigDataLayer.load_password()
end end
def load_server do def load_server do
@data_layer.load_server() ConfigDataLayer.load_server()
end end
defp save_config(conf) do defp save_config(conf) do
@data_layer.save_config(conf) ConfigDataLayer.save_config(conf)
end end
defp list_interfaces() do defp list_interfaces() do

View File

@ -18,7 +18,6 @@ defmodule FarmbotOS.SysCalls do
alias FarmbotOS.SysCalls.{ alias FarmbotOS.SysCalls.{
ChangeOwnership, ChangeOwnership,
CheckUpdate, CheckUpdate,
DumpInfo,
Farmware, Farmware,
FactoryReset, FactoryReset,
FlashFirmware, FlashFirmware,
@ -52,9 +51,6 @@ defmodule FarmbotOS.SysCalls do
@impl true @impl true
defdelegate change_ownership(email, secret, server), to: ChangeOwnership defdelegate change_ownership(email, secret, server), to: ChangeOwnership
@impl true
defdelegate dump_info(), to: DumpInfo
@impl true @impl true
defdelegate check_update(), to: CheckUpdate defdelegate check_update(), to: CheckUpdate

View File

@ -1,104 +0,0 @@
defmodule FarmbotOS.SysCalls.DumpInfo do
@moduledoc false
require FarmbotCore.Logger
require FarmbotTelemetry
alias FarmbotCore.{
Asset,
Asset.DiagnosticDump,
Asset.Private,
Config,
Project
}
def dump_info do
FarmbotCore.Logger.busy(1, "Recording diagnostic dump.")
ifname = get_network_config()
dmesg = dmesg()
fbos_commit = Project.commit()
fbos_version = Project.version()
fw_version = fw_version()
fw_commit = Project.arduino_commit()
fw_hardware = extract_fw_hardware(fw_version)
fw_data = fw_state(fw_version, fw_hardware)
params = %{
network_interface: ifname,
firmware_hardware: fw_hardware,
firmware_commit: fw_commit,
fbos_commit: fbos_commit,
fbos_version: fbos_version,
fbos_dmesg_dump: dmesg,
firmware_state: FarmbotCore.JSON.encode!(fw_data)
}
case Asset.new_diagnostic_dump(params) do
{:ok, diag} ->
_ = Private.mark_dirty!(diag, %{})
FarmbotCore.Logger.success(1, "Diagnostic dump recorded.")
FarmbotTelemetry.event(:diagnostic_dump, :record, nil,
diagnostic_dump: DiagnosticDump.render(diag)
)
:ok
{:error, changeset} ->
FarmbotTelemetry.event(:diagnostic_dump, :record_error, nil,
error: inspect(changeset)
)
{:error, "error creating diagnostic dump: #{inspect(changeset)}"}
end
end
defp get_network_config do
case Config.get_all_network_configs() do
[%{name: ifname} | _] -> ifname
_ -> nil
end
end
defp dmesg do
{dmesg, _status} = System.cmd("dmesg", [])
dmesg
end
defp fw_version do
case FarmbotFirmware.request({:software_version_read, []}) do
{:ok, {_, {:report_software_version, [version]}}} -> version
_ -> nil
end
end
defp extract_fw_hardware(nil), do: nil
defp extract_fw_hardware(str) do
case String.split(str, ".") do
[_, _, _, "R"] -> "arduino"
[_, _, _, "F"] -> "farmduino"
[_, _, _, "G"] -> "farmduino_k14"
[_, _, _, "H"] -> "farmduino_k15"
[_, _, _, "E"] -> "express_k10"
_ -> nil
end
end
defp fw_state(version, hardware) do
pid = Process.whereis(FarmbotFirmware)
if state = pid && :sys.get_state(pid) do
%{
firmware_hardware: hardware,
firmware_version: version,
busy: state.status == :busy,
serial_port: state.transport_args[:device],
locked: state.status == :emergency_lock,
current_command: inspect(state.command_queue)
}
else
%{error: "Firmware process is not running. Could not collect info."}
end
end
end

View File

@ -1,10 +1,10 @@
defmodule FarmbotOS.SysCalls.Farmware do defmodule FarmbotOS.SysCalls.Farmware do
@moduledoc false @moduledoc false
require Logger require FarmbotCore.Logger
# alias FarmbotCeleryScript.AST
alias FarmbotCore.{Asset, AssetSupervisor, FarmwareRuntime} alias FarmbotCore.{Asset, AssetSupervisor, FarmwareRuntime}
alias FarmbotExt.API.ImageUploader alias FarmbotExt.API.ImageUploader
@farmware_timeout 30_000
def update_farmware(farmware_name) do def update_farmware(farmware_name) do
with {:ok, installation} <- lookup_installation(farmware_name) do with {:ok, installation} <- lookup_installation(farmware_name) do
@ -48,16 +48,20 @@ defmodule FarmbotOS.SysCalls.Farmware do
def loop(farmware_runtime) do def loop(farmware_runtime) do
receive do receive do
{:error, :farmware_exit} -> {:error, :farmware_exit} -> :ok
:ok {:error, reason} -> {:error, inspect(reason)}
{:error, reason} ->
{:error, inspect(reason)}
after after
30_000 -> @farmware_timeout -> farmware_timeout(farmware_runtime)
Logger.debug("Force stopping farmware: #{inspect(farmware_runtime)}")
FarmwareRuntime.stop(farmware_runtime)
:ok
end end
end end
def farmware_timeout(farmware_runtime) do
time = @farmware_timeout / 1_000
runtime = inspect(farmware_runtime)
msg = "Farmware did not exit after #{time} seconds. Terminating #{runtime}"
FarmbotCore.Logger.info(2, msg)
FarmwareRuntime.stop(farmware_runtime)
:ok
end
end end

View File

@ -32,10 +32,7 @@ defmodule FarmbotOS.SysCalls.FlashFirmware do
{_, 0} <- Avrdude.flash(hex_file, tty, fun) do {_, 0} <- Avrdude.flash(hex_file, tty, fun) do
FarmbotCore.Logger.success(2, "Firmware flashed successfully!") FarmbotCore.Logger.success(2, "Firmware flashed successfully!")
%{ %{firmware_path: tty}
# firmware_hardware: package,
firmware_path: tty
}
|> Asset.update_fbos_config!() |> Asset.update_fbos_config!()
|> Private.mark_dirty!(%{}) |> Private.mark_dirty!(%{})
@ -62,15 +59,19 @@ defmodule FarmbotOS.SysCalls.FlashFirmware do
end end
end end
defp find_reset_fun(_) do defp find_reset_fun("express_k10") do
config = Application.get_env(:farmbot_firmware, FarmbotFirmware) FarmbotCore.Logger.debug(3, "Using special reset function for express")
# "magic" workaround to avoid compiler warnings.
# We used to inject this via App config, but it was
# error prone.
mod = :"Elixir.FarmbotOS.Platform.Target.FirmwareReset.GPIO"
fun = &mod.reset/0
{:ok, fun}
end
if module = config[:reset] do defp find_reset_fun(_) do
Logger.error("using reset function: #{inspect(config)}") FarmbotCore.Logger.debug(3, "Using default reset function")
{:ok, &module.reset/0} fun = &FarmbotFirmware.NullReset.reset/0
else {:ok, fun}
Logger.error("no reset function is going to be used #{inspect(config)}")
{:ok, fn -> :ok end}
end
end end
end end

View File

@ -69,39 +69,10 @@ defmodule FarmbotOS.SysCalls.Movement do
end end
def move_absolute(x, y, z, speed) do def move_absolute(x, y, z, speed) do
do_move_absolute(x, y, z, speed, max_retries()) do_move_absolute(x, y, z, speed)
end end
defp do_move_absolute(x, y, z, speed, retries, errors \\ []) defp do_move_absolute(x, y, z, speed) do
# This is the final attempt before movement is aborted.
defp do_move_absolute(x, y, z, speed, 0, errors) do
with {:ok, speed_x} <- param_read(:movement_max_spd_x),
{:ok, speed_y} <- param_read(:movement_max_spd_y),
{:ok, speed_z} <- param_read(:movement_max_spd_z),
params <- [
x: x / 1.0,
y: y / 1.0,
z: z / 1.0,
a: speed / 100 * (speed_x || 1),
b: speed / 100 * (speed_y || 1),
c: speed / 100 * (speed_z || 1)
],
:ok <- FarmbotFirmware.command({nil, {:command_movement, params}}) do
:ok
else
{:error, reason} ->
errors =
[reason | errors]
|> Enum.reverse()
|> Enum.map(&inspect/1)
|> Enum.join(", ")
{:error, "movement error(s): #{errors}"}
end
end
defp do_move_absolute(x, y, z, speed, retries, errors) do
with {:ok, speed_x} <- param_read(:movement_max_spd_x), with {:ok, speed_x} <- param_read(:movement_max_spd_x),
{:ok, speed_y} <- param_read(:movement_max_spd_y), {:ok, speed_y} <- param_read(:movement_max_spd_y),
{:ok, speed_z} <- param_read(:movement_max_spd_z), {:ok, speed_z} <- param_read(:movement_max_spd_z),
@ -120,15 +91,18 @@ defmodule FarmbotOS.SysCalls.Movement do
{:error, "emergency_lock"} {:error, "emergency_lock"}
{:error, reason} -> {:error, reason} ->
FarmbotCore.Logger.error( handle_movement_error(reason)
1,
"Movement failed. Retrying up to #{retries} more time(s)"
)
do_move_absolute(x, y, z, speed, retries - 1, [reason | errors]) reason ->
handle_movement_error(reason)
end end
end end
def handle_movement_error(reason) do
msg = "Movement failed. #{inspect(reason)}"
FarmbotCore.Logger.error(1, msg)
end
def calibrate(axis) do def calibrate(axis) do
axis = assert_axis!(axis) axis = assert_axis!(axis)
@ -173,13 +147,6 @@ defmodule FarmbotOS.SysCalls.Movement do
end end
end end
defp max_retries do
case param_read(:param_mov_nr_retry) do
{:ok, nr} -> floor(nr)
_ -> 3
end
end
defp assert_axis!(axis) when is_atom(axis), defp assert_axis!(axis) when is_atom(axis),
do: axis do: axis
@ -187,7 +154,6 @@ defmodule FarmbotOS.SysCalls.Movement do
do: String.to_existing_atom(axis) do: String.to_existing_atom(axis)
defp assert_axis!(axis) do defp assert_axis!(axis) do
# {:error, "unknown axis #{axis}"}
raise("unknown axis #{axis}") raise("unknown axis #{axis}")
end end
end end

View File

@ -118,7 +118,7 @@ defmodule FarmbotOS.MixProject do
{:circuits_gpio, "~> 0.4.3", targets: @all_targets}, {:circuits_gpio, "~> 0.4.3", targets: @all_targets},
{:circuits_i2c, "~> 0.3.5", targets: @all_targets}, {:circuits_i2c, "~> 0.3.5", targets: @all_targets},
{:toolshed, "~> 0.2", targets: @all_targets}, {:toolshed, "~> 0.2", targets: @all_targets},
{:vintage_net, "~> 0.7", targets: @all_targets}, {:vintage_net, "~> 0.7.5", targets: @all_targets},
{:vintage_net_ethernet, "~> 0.7.0", targets: @all_targets}, {:vintage_net_ethernet, "~> 0.7.0", targets: @all_targets},
{:vintage_net_wifi, "~> 0.7.0", targets: @all_targets}, {:vintage_net_wifi, "~> 0.7.0", targets: @all_targets},
{:vintage_net_direct, "~> 0.7.0", targets: @all_targets}, {:vintage_net_direct, "~> 0.7.0", targets: @all_targets},

View File

@ -45,7 +45,7 @@
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
"mimic": {:hex, :mimic, "1.1.3", "3bad83d5271b4faa7bbfef587417a6605cbbc802a353395d446a1e5f46fe7115", [:mix], [], "hexpm"}, "mimic": {:hex, :mimic, "1.1.3", "3bad83d5271b4faa7bbfef587417a6605cbbc802a353395d446a1e5f46fe7115", [:mix], [], "hexpm"},
"mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm"}, "mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm"},
"muontrap": {:hex, :muontrap, "0.5.0", "0b885a4095e990000d519441bccb8f037a9c4c35908720e7814a516a606be278", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, "muontrap": {:hex, :muontrap, "0.5.1", "98fe96d0e616ee518860803a37a29eb23ffc2ca900047cb1bb7fd37521010093", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"nerves": {:hex, :nerves, "1.5.3", "14abb71fa1ce0cd281ffb6ba743c6c896b664efc3c2dd542f8682a55602176d8", [:mix], [{:distillery, "~> 2.1", [hex: :distillery, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, "nerves": {:hex, :nerves, "1.5.3", "14abb71fa1ce0cd281ffb6ba743c6c896b664efc3c2dd542f8682a55602176d8", [:mix], [{:distillery, "~> 2.1", [hex: :distillery, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"nerves_firmware_ssh": {:hex, :nerves_firmware_ssh, "0.4.4", "12b0d9c84ec9f79c1b0ac0de1c575372ef972d0c58ce21c36bf354062c6222d9", [:mix], [{:nerves_runtime, "~> 0.6", [hex: :nerves_runtime, repo: "hexpm", optional: false]}], "hexpm"}, "nerves_firmware_ssh": {:hex, :nerves_firmware_ssh, "0.4.4", "12b0d9c84ec9f79c1b0ac0de1c575372ef972d0c58ce21c36bf354062c6222d9", [:mix], [{:nerves_runtime, "~> 0.6", [hex: :nerves_runtime, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_hub": {:hex, :nerves_hub, "0.7.4", "0e104cad468c3d601ed423e116ea3422fbd31b7eedb263bcb2a5c489dca8b53b", [:mix], [{:fwup, "~> 0.3.0", [hex: :fwup, repo: "hexpm", optional: false]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nerves_hub_cli, "~> 0.9", [hex: :nerves_hub_cli, repo: "hexpm", optional: false]}, {:nerves_runtime, "~> 0.8", [hex: :nerves_runtime, repo: "hexpm", optional: false]}, {:phoenix_client, "~> 0.7", [hex: :phoenix_client, repo: "hexpm", optional: false]}, {:websocket_client, "~> 1.3", [hex: :websocket_client, repo: "hexpm", optional: false]}, {:x509, "~> 0.5", [hex: :x509, repo: "hexpm", optional: false]}], "hexpm"}, "nerves_hub": {:hex, :nerves_hub, "0.7.4", "0e104cad468c3d601ed423e116ea3422fbd31b7eedb263bcb2a5c489dca8b53b", [:mix], [{:fwup, "~> 0.3.0", [hex: :fwup, repo: "hexpm", optional: false]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nerves_hub_cli, "~> 0.9", [hex: :nerves_hub_cli, repo: "hexpm", optional: false]}, {:nerves_runtime, "~> 0.8", [hex: :nerves_runtime, repo: "hexpm", optional: false]}, {:phoenix_client, "~> 0.7", [hex: :phoenix_client, repo: "hexpm", optional: false]}, {:websocket_client, "~> 1.3", [hex: :websocket_client, repo: "hexpm", optional: false]}, {:x509, "~> 0.5", [hex: :x509, repo: "hexpm", optional: false]}], "hexpm"},
@ -89,7 +89,7 @@
"uboot_env": {:hex, :uboot_env, "0.1.1", "b01e3ec0973e99473234f27839e29e63b5b81eba6a136a18a78d049d4813d6c5", [:mix], [], "hexpm"}, "uboot_env": {:hex, :uboot_env, "0.1.1", "b01e3ec0973e99473234f27839e29e63b5b81eba6a136a18a78d049d4813d6c5", [:mix], [], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
"uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm"}, "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm"},
"vintage_net": {:hex, :vintage_net, "0.7.2", "b14468e29704156f133a9342fd41c8cfc9bae0849bfa20a6859b29ac9e28166e", [:make, :mix], [{:busybox, "~> 0.1.4", [hex: :busybox, repo: "hexpm", optional: true]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:gen_state_machine, "~> 2.0.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}, {:muontrap, "~> 0.5.0", [hex: :muontrap, repo: "hexpm", optional: false]}], "hexpm"}, "vintage_net": {:hex, :vintage_net, "0.7.5", "4e843a3f029a32cd5644aa3397bbb1463e123b06848e5a3c99cefc58af395a2a", [:make, :mix], [{:busybox, "~> 0.1.4", [hex: :busybox, repo: "hexpm", optional: true]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:gen_state_machine, "~> 2.0.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}, {:muontrap, "~> 0.5.1", [hex: :muontrap, repo: "hexpm", optional: false]}], "hexpm"},
"vintage_net_direct": {:hex, :vintage_net_direct, "0.7.0", "6a8d95432fc2fb9a9e0225bf1a6dbb7c1ba097bb509a989e947ad3f44d2b0f28", [:mix], [{:one_dhcpd, "~> 0.2.3", [hex: :one_dhcpd, repo: "hexpm", optional: false]}, {:vintage_net, "~> 0.7.0", [hex: :vintage_net, repo: "hexpm", optional: false]}], "hexpm"}, "vintage_net_direct": {:hex, :vintage_net_direct, "0.7.0", "6a8d95432fc2fb9a9e0225bf1a6dbb7c1ba097bb509a989e947ad3f44d2b0f28", [:mix], [{:one_dhcpd, "~> 0.2.3", [hex: :one_dhcpd, repo: "hexpm", optional: false]}, {:vintage_net, "~> 0.7.0", [hex: :vintage_net, repo: "hexpm", optional: false]}], "hexpm"},
"vintage_net_ethernet": {:hex, :vintage_net_ethernet, "0.7.0", "b66a4da0846b4a55a471d15d8ab2bedbbef9c75c18f4e511c43777f7cfeffa09", [:mix], [{:vintage_net, "~> 0.7.0", [hex: :vintage_net, repo: "hexpm", optional: false]}], "hexpm"}, "vintage_net_ethernet": {:hex, :vintage_net_ethernet, "0.7.0", "b66a4da0846b4a55a471d15d8ab2bedbbef9c75c18f4e511c43777f7cfeffa09", [:mix], [{:vintage_net, "~> 0.7.0", [hex: :vintage_net, repo: "hexpm", optional: false]}], "hexpm"},
"vintage_net_wifi": {:hex, :vintage_net_wifi, "0.7.0", "be3d8275fa40ba357159033523606c74c8f612dbd60801840df60835453a6906", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:vintage_net, "~> 0.7.0", [hex: :vintage_net, repo: "hexpm", optional: false]}], "hexpm"}, "vintage_net_wifi": {:hex, :vintage_net_wifi, "0.7.0", "be3d8275fa40ba357159033523606c74c8f612dbd60801840df60835453a6906", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:vintage_net, "~> 0.7.0", [hex: :vintage_net, repo: "hexpm", optional: false]}], "hexpm"},

View File

@ -1,7 +1,7 @@
defmodule FarmbotOS.Platform.Target.Configurator.Validator do defmodule FarmbotOS.Platform.Target.Configurator.Validator do
@moduledoc """ @moduledoc """
VintageNet.Technology that handles turning Farmbot's internal VintageNet.Technology that handles turning Farmbot's internal
network representation into either a VintageNetEthernet network representation into either a VintageNetEthernet
or VintageNetWiFi RawConfig. or VintageNetWiFi RawConfig.
""" """
@ -71,9 +71,8 @@ defmodule FarmbotOS.Platform.Target.Configurator.Validator do
defp to_ipv4(%{ defp to_ipv4(%{
ipv4_method: "static", ipv4_method: "static",
# TODO(Connor) fix nameservers name_servers: name_servers,
# name_servers: name_servers, domain: domain,
# domain: domain,
ipv4_address: ipv4_address, ipv4_address: ipv4_address,
ipv4_gateway: ipv4_gateway, ipv4_gateway: ipv4_gateway,
ipv4_subnet_mask: ipv4_subnet_mask ipv4_subnet_mask: ipv4_subnet_mask
@ -82,7 +81,9 @@ defmodule FarmbotOS.Platform.Target.Configurator.Validator do
method: :static, method: :static,
address: ipv4_address, address: ipv4_address,
netmask: ipv4_subnet_mask, netmask: ipv4_subnet_mask,
gateway: ipv4_gateway gateway: ipv4_gateway,
name_servers: name_servers,
domain: domain
} }
end end

View File

@ -71,7 +71,7 @@ defmodule FarmbotOS.Platform.Target.Network do
_ = maybe_hack_tzdata() _ = maybe_hack_tzdata()
_ = init_net_kernel() _ = init_net_kernel()
send(self(), :setup) send(self(), :setup)
# If a secret exists, assume that # If a secret exists, assume that
# farmbot at one point has been connected to the internet # farmbot at one point has been connected to the internet
first_connect? = is_first_connect?() first_connect? = is_first_connect?()
@ -135,6 +135,7 @@ defmodule FarmbotOS.Platform.Target.Network do
end end
vintage_net_config = to_vintage_net(config) vintage_net_config = to_vintage_net(config)
FarmbotCore.Logger.info(3, inspect(vintage_net_config))
FarmbotTelemetry.event(:network, :interface_configure, nil, FarmbotTelemetry.event(:network, :interface_configure, nil,
interface: ifname interface: ifname
@ -260,7 +261,7 @@ defmodule FarmbotOS.Platform.Target.Network do
""") """)
FarmbotOS.System.factory_reset(""" FarmbotOS.System.factory_reset("""
Farmbot was unable to assosiate with the EAP network. Farmbot was unable to assosiate with the EAP network.
Please check the identity, password and method of connection Please check the identity, password and method of connection
""") """)
@ -270,11 +271,11 @@ defmodule FarmbotOS.Platform.Target.Network do
def handle_info({VintageNet, property, old, new, _meta}, state) do def handle_info({VintageNet, property, old, new, _meta}, state) do
Logger.debug(""" Logger.debug("""
Unknown property change: #{inspect(property)} Unknown property change: #{inspect(property)}
old: old:
#{inspect(old, limit: :infinity)} #{inspect(old, limit: :infinity)}
new: new:
#{inspect(new, limit: :infinity)} #{inspect(new, limit: :infinity)}
""") """)
@ -284,12 +285,12 @@ defmodule FarmbotOS.Platform.Target.Network do
def handle_info({:network_not_found_timer, minutes}, state) do def handle_info({:network_not_found_timer, minutes}, state) do
FarmbotCore.Logger.warn(1, """ FarmbotCore.Logger.warn(1, """
Farmbot has been disconnected from the network for Farmbot has been disconnected from the network for
#{minutes} minutes. Going down for factory reset. #{minutes} minutes. Going down for factory reset.
""") """)
FarmbotOS.System.factory_reset(""" FarmbotOS.System.factory_reset("""
Farmbot has been disconnected from the network for Farmbot has been disconnected from the network for
#{minutes} minutes. #{minutes} minutes.
""") """)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -23,7 +23,7 @@
<label for="ssh_key"> id_rsa.pub </label> <label for="ssh_key"> id_rsa.pub </label>
<input type="text" id="ssh_key" name="ssh_key" value=""> <input type="text" id="ssh_key" name="ssh_key" value="">
<label for="dns_name"> DNS test name </label> <label for="dns_name"> Domain for verifying DNS (eg: "my.farm.bot")</label>
<input type="text" id="dns_name" name="dns_name" value=""> <input type="text" id="dns_name" name="dns_name" value="">
<label for="ntp_server_1"> NTP Pool Server 1 </label> <label for="ntp_server_1"> NTP Pool Server 1 </label>

View File

@ -5,10 +5,23 @@ defmodule FarmbotOs.AvrdudeTest do
setup :verify_on_exit! setup :verify_on_exit!
test "failure handling" do
reset_fn = fn ->
raise RuntimeError, "foo"
end
expect(FarmbotCore.LogExecutor, :execute, 1, fn log ->
msg = log.message
assert String.contains?(msg, "%RuntimeError{message: \"foo\"}")
end)
Avrdude.call_reset_fun(reset_fn)
end
test "works" do test "works" do
File.touch("/tmp/wow") File.touch("/tmp/wow")
expect(Avrdude.MuonTrapAdapter, :cmd, fn cmd, args, opts -> expect(MuonTrap, :cmd, 1, fn cmd, args, opts ->
assert cmd == "avrdude" assert cmd == "avrdude"
assert args == [ assert args == [
@ -35,4 +48,35 @@ defmodule FarmbotOs.AvrdudeTest do
"YOLO" "YOLO"
end) end)
end end
test "handles /dev file names, also" do
File.touch("/tmp/wow")
expect(MuonTrap, :cmd, 1, fn cmd, args, opts ->
assert cmd == "avrdude"
assert args == [
"-patmega2560",
"-cwiring",
"-P/dev/null",
"-b115200",
"-D",
"-V",
"-Uflash:w:/tmp/wow:i"
]
assert opts == [
into: %IO.Stream{
device: :standard_io,
line_or_bytes: :line,
raw: false
},
stderr_to_stdout: true
]
end)
Avrdude.flash("/tmp/wow", "/dev/null", fn ->
"YOLO"
end)
end
end end

View File

@ -0,0 +1,105 @@
defmodule FarmbotOS.Configurator.ConfigDataLayerTest do
use ExUnit.Case
use Mimic
setup :verify_on_exit!
alias FarmbotOS.Configurator.ConfigDataLayer
@fake_params %{
"auth_config_email" => System.get_env("FARMBOT_EMAIL", "test@test.com"),
"auth_config_password" => System.get_env("FARMBOT_PASSWORD", "password123"),
"auth_config_server" =>
System.get_env("FARMBOT_SERVER", "http://localhost:3000"),
"ifname" => "eth0",
"iftype" => "wired",
"net_config_dns_name" => nil,
"net_config_domain" => nil,
"net_config_identity" => nil,
"net_config_ipv4_address" => "0.0.0.0",
"net_config_ipv4_gateway" => "0.0.0.0",
"net_config_ipv4_method" => "dhcp",
"net_config_ipv4_subnet_mask" => "255.255.0.0",
"net_config_name_servers" => nil,
"net_config_ntp1" => nil,
"net_config_ntp2" => nil,
"net_config_password" => nil,
"net_config_psk" => nil,
"net_config_reg_domain" => "US",
"net_config_security" => nil,
"net_config_ssh_key" => nil,
"net_config_ssid" => nil
}
test "failure: load_last_reset_reason" do
expect(File, :read, 1, fn _ -> nil end)
assert nil == ConfigDataLayer.load_last_reset_reason()
end
test "success: load_last_reset_reason" do
expect(File, :read, 1, fn _ -> {:ok, "testcase123"} end)
assert "testcase123" == ConfigDataLayer.load_last_reset_reason()
end
test "load_(server|email|password)()" do
:ok = ConfigDataLayer.save_config(@fake_params)
assert @fake_params["auth_config_server"] == ConfigDataLayer.load_server()
assert @fake_params["auth_config_password"] ==
ConfigDataLayer.load_password()
assert @fake_params["auth_config_email"] == ConfigDataLayer.load_email()
end
test "works" do
expected = %{
domain: nil,
identity: nil,
ipv4_address: "0.0.0.0",
ipv4_gateway: "0.0.0.0",
ipv4_method: "dhcp",
ipv4_subnet_mask: "255.255.0.0",
name: "eth0",
name_servers: nil,
password: nil,
psk: nil,
regulatory_domain: "US",
security: nil,
ssid: nil,
type: "wired"
}
FarmbotCore.Config
|> expect(:input_network_config!, 1, fn network_params ->
assert expected == network_params
end)
|> expect(:update_config_value, 7, fn
:string, "authorization", "email", email ->
assert email == @fake_params["auth_config_email"]
:ok
:string, "authorization", "password", pass ->
assert pass == @fake_params["auth_config_password"]
:ok
:string, "authorization", "server", server ->
assert server == @fake_params["auth_config_server"]
:ok
:string, "settings", "default_dns_name", nil ->
:ok
:string, "settings", "default_ntp_server_1", nil ->
:ok
:string, "settings", "default_ntp_server_2", nil ->
:ok
:string, "authorization", "secret", nil ->
:ok
_, _, _, _ ->
raise "NEVER"
end)
assert :ok == ConfigDataLayer.save_config(@fake_params)
end
end

View File

@ -0,0 +1,14 @@
defmodule FarmbotOS.Configurator.LoggerSocketTest do
use ExUnit.Case
use Mimic
alias FarmbotOS.Configurator.LoggerSocket
setup :verify_on_exit!
test "init/2" do
# TODO(Rick) Not sure what the real args are.
# Circle back to make this test more realistic
# later.
expected = {:cowboy_websocket, :foo, :bar}
assert expected == LoggerSocket.init(:foo, :bar)
end
end

View File

@ -1,5 +1,7 @@
defmodule FarmbotOS.Configurator.RouterTest do defmodule FarmbotOS.Configurator.RouterTest do
alias FarmbotOS.Configurator.Router alias FarmbotOS.Configurator.Router
alias FarmbotOS.Configurator.ConfigDataLayer
use ExUnit.Case, async: true use ExUnit.Case, async: true
use Plug.Test use Plug.Test
@ -7,6 +9,30 @@ defmodule FarmbotOS.Configurator.RouterTest do
setup :verify_on_exit! setup :verify_on_exit!
@opts Router.init([]) @opts Router.init([])
# Stolen from https://github.com/phoenixframework/phoenix/blob/3f157c30ceae8d1eb524fdd05b5e3de10e434c42/lib/phoenix/test/conn_test.ex#L438
defp redirected_to(conn, status \\ 302)
defp redirected_to(%Plug.Conn{state: :unset}, _status) do
raise "expected connection to have redirected but no response was set/sent"
end
defp redirected_to(conn, status) when is_atom(status) do
redirected_to(conn, Plug.Conn.Status.code(status))
end
defp redirected_to(%Plug.Conn{status: status} = conn, status) do
location = Plug.Conn.get_resp_header(conn, "location") |> List.first()
location || raise "no location header was set on redirected_to"
end
defp redirected_to(conn, status) do
raise "expected redirection with status #{status}, got: #{conn.status}"
end
defp get_con(path) do
conn = conn(:get, path)
Router.call(conn, @opts)
end
test "index after reset" do test "index after reset" do
FarmbotOS.Configurator.ConfigDataLayer FarmbotOS.Configurator.ConfigDataLayer
@ -19,19 +45,25 @@ defmodule FarmbotOS.Configurator.RouterTest do
assert conn.resp_body =~ "whoops!" assert conn.resp_body =~ "whoops!"
end end
test "redirect to index" do test "redirects" do
FarmbotOS.Configurator.ConfigDataLayer redirects = [
|> expect(:load_last_reset_reason, fn -> nil end) "/check_network_status.txt",
"/connecttest.txt",
"/gen_204",
"/generate_204",
"/hotspot-detect.html",
"/library/test/success.html",
"/redirect",
"/setup",
"/success.txt"
]
conn = conn(:get, "/setup") Enum.map(redirects, fn path ->
conn = Router.call(conn, @opts) conn = conn(:get, path)
redir = redirected_to(conn) conn = Router.call(conn, @opts)
assert redir == "/" redir = redirected_to(conn)
assert redir == "/"
conn = conn(:get, redir) end)
conn = Router.call(conn, @opts)
redir = redirected_to(conn)
assert redir == "/network"
end end
test "celeryscript requests don't get listed as last reset reason" do test "celeryscript requests don't get listed as last reset reason" do
@ -336,23 +368,60 @@ defmodule FarmbotOS.Configurator.RouterTest do
assert conn.status == 500 assert conn.status == 500
end end
# Stolen from https://github.com/phoenixframework/phoenix/blob/3f157c30ceae8d1eb524fdd05b5e3de10e434c42/lib/phoenix/test/conn_test.ex#L438 test "/scheduler_debugger" do
defp redirected_to(conn, status \\ 302) kon = get_con("/scheduler_debugger")
assert String.contains?(kon.resp_body, "scheduler_debugger.js")
defp redirected_to(%Plug.Conn{state: :unset}, _status) do assert String.contains?(kon.resp_body, "<title>Scheduler Debugger</title>")
raise "expected connection to have redirected but no response was set/sent"
end end
defp redirected_to(conn, status) when is_atom(status) do test "/logger" do
redirected_to(conn, Plug.Conn.Status.code(status)) kon = get_con("/logger")
words = [
"DateTime",
"Function",
"Level",
"Message",
"Module",
"Src"
]
Enum.map(words, fn word ->
assert String.contains?(kon.resp_body, word)
end)
end end
defp redirected_to(%Plug.Conn{status: status} = conn, status) do test "/api/telemetry/cpu_usage" do
location = Plug.Conn.get_resp_header(conn, "location") |> List.first() {:ok, json} = Jason.decode(get_con("/api/telemetry/cpu_usage").resp_body)
location || raise "no location header was set on redirected_to" assert Enum.count(json) == 10
zero = Enum.at(json, 0)
assert(is_binary(zero["class"]))
assert(is_binary(zero["timestamp"]))
assert(is_integer(zero["value"]))
end end
defp redirected_to(conn, status) do test "/finish" do
raise "expected redirection with status #{status}, got: #{conn.status}" expect(ConfigDataLayer, :save_config, 1, fn _conf ->
:ok
end)
# This data would crash in the real app because it is incomplete.
# Maybe we should add an error handler?
fake_session = %{
"ifname" => "MY_IFNAME",
"auth_config_email" => "MY_EMAIL",
"auth_config_password" => "MY_PASS",
"auth_config_server" => "MY_SERVER"
}
kon =
conn(:get, "/finish")
|> init_test_session(fake_session)
|> Router.call(@opts)
assert String.contains?(
kon.resp_body,
"If any configuration settings are incorrect, FarmBot will reset"
)
end end
end end

View File

@ -0,0 +1,148 @@
defmodule FarmbotOS.FarmbotOS.Lua.Ext.DataManipulationTest do
use ExUnit.Case
use Mimic
setup :verify_on_exit!
def lua(test_name, lua_code) do
FarmbotOS.Lua.eval_assertion(test_name, lua_code)
end
test "update_device()" do
expect(FarmbotCore.Asset, :update_device!, 1, fn params ->
assert %{"name" => "Test Farmbot"} == params
end)
lua_code = """
update_device({name = "Test Farmbot"})
return true
"""
assert true == lua("update device test", lua_code)
end
test "get_device/0" do
fake_device = %{fake: :device}
expect(FarmbotCore.Asset, :device, 1, fn -> fake_device end)
expect(FarmbotCore.Asset.Device, :render, 1, fn dev -> dev end)
lua_code = """
get_device()
return true
"""
assert true == lua("get device test", lua_code)
end
test "get_device/1" do
fake_device = %{name: "my farmbot", id: 23}
expect(FarmbotCore.Asset, :device, 1, fn -> fake_device end)
expect(FarmbotCore.Asset.Device, :render, 1, fn dev -> dev end)
lua_code = """
return get_device("id") == 23
"""
assert true == lua("get device test/1", lua_code)
end
test "update_fbos_config" do
expect(FarmbotCore.Asset, :update_fbos_config!, 1, fn params ->
assert params == %{"foo" => "bar"}
end)
lua_code = """
update_fbos_config({foo = "bar"})
return true
"""
assert true == lua("update_fbos_config test", lua_code)
end
test "get_fbos_config/1" do
fake_config = %{id: 47}
expect(FarmbotCore.Asset, :fbos_config, 1, fn -> fake_config end)
expect(FarmbotCore.Asset.FbosConfig, :render, 1, fn params -> params end)
lua_code = "return 47 == get_fbos_config(\"id\")"
assert true == lua("get_fbos_config", lua_code)
end
test "get_fbos_config/0" do
fake_config = %{id: 47, foo: "bar"}
expect(FarmbotCore.Asset, :fbos_config, 1, fn -> fake_config end)
expect(FarmbotCore.Asset.FbosConfig, :render, 1, fn params -> params end)
lua_code = """
c = get_fbos_config()
return (c.id == 47) and (c.foo == "bar")
"""
assert true == lua("get_fbos_config/1", lua_code)
end
test "update_firmware_config" do
expect(FarmbotCore.Asset, :update_firmware_config!, 1, fn params ->
assert params == %{"foo" => "bar"}
end)
lua_code = """
update_firmware_config({foo = "bar"})
return true
"""
assert true == lua("update_firmware_config test", lua_code)
end
test "get_firmware_config/1" do
fake_config = %{id: 47}
expect(FarmbotCore.Asset, :firmware_config, 1, fn -> fake_config end)
expect(FarmbotCore.Asset.FirmwareConfig, :render, 1, fn params -> params end)
lua_code = "return 47 == get_firmware_config(\"id\")"
assert true == lua("get_firmware_config", lua_code)
end
test "get_firmware_config/0" do
fake_config = %{id: 47, foo: "bar"}
expect(FarmbotCore.Asset, :firmware_config, 1, fn -> fake_config end)
expect(FarmbotCore.Asset.FirmwareConfig, :render, 1, fn params -> params end)
lua_code = """
c = get_firmware_config()
return (c.id == 47) and (c.foo == "bar")
"""
assert true == lua("get_firmware_config/1", lua_code)
end
test "new_farmware_env" do
expect(FarmbotCore.Asset, :new_farmware_env, 1, fn params -> params end)
lua_code = """
return new_farmware_env({foo = "bar"})
"""
assert true == lua("new_farmware_env/1", lua_code)
end
test "new_sensor_reading" do
expect(FarmbotCore.Asset, :new_sensor_reading!, 1, fn params -> params end)
lua_code = """
return new_sensor_reading({
pin = 0,
mode = 1,
value = 2,
x = 3,
y = 4,
z = 5,
})
"""
assert true == lua("new_farmware_env/1", lua_code)
end
end

View File

@ -0,0 +1,28 @@
defmodule FarmbotOS.LuaTest do
use ExUnit.Case
use Mimic
setup :verify_on_exit!
alias FarmbotOS.Lua
test "evaluates Lua" do
assert Lua.eval_assertion("Returns 'true'", "return true")
{:error, message1} = Lua.eval_assertion("Returns 'true'", "-1")
assert message1 ==
"failed to parse expression (line:1): syntax error before: '-'"
{:error, error} = Lua.eval_assertion("random error", "return (1/0)")
assert error == :badarith
end
test "assertion logs" do
# Hmmm
expect(FarmbotCore.LogExecutor, :execute, 1, fn log ->
assert log.level == "assertion"
assert log.message == "this is an assertion"
assert log.meta == %{assertion_passed: true, assertion_type: :assertion}
end)
Lua.log_assertion(true, :assertion, "this is an assertion")
end
end

View File

@ -0,0 +1,24 @@
defmodule FarmbotOS.SysCalls.FarmwareTest do
use ExUnit.Case, async: true
use Mimic
setup :verify_on_exit!
test "farmware_timeout" do
fake_pid = :FAKE_PID
expect(FarmbotCore.LogExecutor, :execute, fn log ->
expected =
"Farmware did not exit after 30.0 seconds. Terminating :FAKE_PID"
assert log.message == expected
:ok
end)
expect(FarmbotCore.FarmwareRuntime, :stop, fn pid ->
assert pid == fake_pid
end)
assert :ok == FarmbotOS.SysCalls.Farmware.farmware_timeout(fake_pid)
end
end

View File

@ -1,8 +0,0 @@
defmodule Avrdude.MuonTrapTestAdapter do
@behaviour Avrdude.MuonTrapAdapter
@impl Avrdude.MuonTrapAdapter
def cmd(exe, args, options) do
{exe, args, options}
end
end

View File

@ -1,10 +1,17 @@
Application.ensure_all_started(:mimic) Application.ensure_all_started(:mimic)
Mimic.copy(FarmbotCore.Asset)
Mimic.copy(FarmbotCore.Asset.Device)
Mimic.copy(FarmbotCore.Asset.FbosConfig)
Mimic.copy(FarmbotCore.Asset.FirmwareConfig)
Mimic.copy(FarmbotCore.Config)
Mimic.copy(FarmbotCore.FarmwareRuntime)
Mimic.copy(FarmbotCore.LogExecutor)
Mimic.copy(FarmbotExt.API.Reconciler) Mimic.copy(FarmbotExt.API.Reconciler)
Mimic.copy(FarmbotExt.API) Mimic.copy(FarmbotExt.API)
Mimic.copy(FarmbotCore.Asset)
Mimic.copy(FarmbotFirmware) Mimic.copy(FarmbotFirmware)
Mimic.copy(FarmbotOS.Configurator.ConfigDataLayer) Mimic.copy(FarmbotOS.Configurator.ConfigDataLayer)
Mimic.copy(FarmbotOS.Configurator.FakeNetworkLayer)
Mimic.copy(FarmbotOS.Configurator.DetsTelemetryLayer) Mimic.copy(FarmbotOS.Configurator.DetsTelemetryLayer)
Mimic.copy(Avrdude.MuonTrapAdapter) Mimic.copy(FarmbotOS.Configurator.FakeNetworkLayer)
Mimic.copy(File)
Mimic.copy(MuonTrap)
ExUnit.start() ExUnit.start()

View File

@ -1,15 +1 @@
defmodule Farmbot.TestSupport do
alias FarmbotCore.AssetWorker.FarmbotCore.Asset.{FarmEvent, RegimenInstance}
def farm_event_timeout do
Application.get_env(:farmbot_core, FarmEvent)[:checkup_time_ms]
end
def regimen_instance_timeout do
Application.get_env(:farmbot_core, RegimenInstance)[:checkup_time_ms]
end
def asset_monitor_timeout do
Application.get_env(:farmbot_core, Farmbot.AssetMonitor)[:checkup_time_ms]
end
end