commit
6ed34124e8
|
@ -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
|
||||||
|
|
19
CHANGELOG.md
19
CHANGELOG.md
|
@ -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
|
||||||
|
|
|
@ -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 |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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("========")
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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, [])
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
||||||
|
|
|
@ -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."
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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."
|
||||||
|
|
|
@ -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},
|
||||||
|
|
|
@ -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},
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"coverage_options": {
|
||||||
|
"treat_no_relevant_lines_as_covered": true,
|
||||||
|
"minimum_coverage": 55
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}"}
|
||||||
|
|
||||||
|
|
|
@ -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)}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -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
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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()
|
||||||
|
|
|
@ -18,4 +18,5 @@ erl_crash.dump
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
auth_secret.exs
|
auth_secret.exs
|
||||||
log
|
log
|
||||||
*.csv
|
*.csv
|
||||||
|
*.coverdata
|
||||||
|
|
|
@ -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: [
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"coverage_options": {
|
||||||
|
"treat_no_relevant_lines_as_covered": true,
|
||||||
|
"minimum_coverage": 29
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
|
|
@ -1,6 +0,0 @@
|
||||||
defmodule Avrdude.MuonTrapDefaultAdapter do
|
|
||||||
@behaviour Avrdude.MuonTrapAdapter
|
|
||||||
|
|
||||||
@impl Avrdude.MuonTrapAdapter
|
|
||||||
defdelegate cmd(exe, args, options), to: MuonTrap, as: :cmd
|
|
||||||
end
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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},
|
||||||
|
|
|
@ -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"},
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
Loading…
Reference in New Issue