commit
6ed34124e8
|
@ -162,7 +162,9 @@ deploy_nerves_hub_firmware_steps: &deploy_nerves_hub_firmware_steps
|
|||
- run:
|
||||
name: Sign Image
|
||||
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:
|
||||
name: Publish to NervesHub
|
||||
working_directory: /nerves/build/farmbot_os
|
||||
|
|
19
CHANGELOG.md
19
CHANGELOG.md
|
@ -1,5 +1,24 @@
|
|||
# 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
|
||||
|
||||
* Dependency updates
|
||||
|
|
|
@ -2,12 +2,9 @@
|
|||
# :floppy_disk: Latest OS Image Downloads
|
||||
<!-- 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 |
|
||||
| --- | --- | --- |
|
||||
| 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 |
|
||||
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).
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
"skip_files": [
|
||||
"lib/farmbot_celery_script/compiler/tools.ex"
|
||||
]
|
||||
],
|
||||
"minimum_coverage": 53,
|
||||
"treat_no_relevant_lines_as_covered": true
|
||||
}
|
||||
|
|
|
@ -92,7 +92,6 @@
|
|||
"calibrate",
|
||||
"change_ownership",
|
||||
"check_updates",
|
||||
"dump_info",
|
||||
"emergency_lock",
|
||||
"emergency_unlock",
|
||||
"execute",
|
||||
|
@ -832,18 +831,6 @@
|
|||
],
|
||||
"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_body_types": [],
|
||||
|
@ -1170,7 +1157,6 @@
|
|||
"calibrate",
|
||||
"change_ownership",
|
||||
"check_updates",
|
||||
"dump_info",
|
||||
"emergency_lock",
|
||||
"emergency_unlock",
|
||||
"execute",
|
||||
|
@ -1242,7 +1228,6 @@
|
|||
"calibrate",
|
||||
"change_ownership",
|
||||
"check_updates",
|
||||
"dump_info",
|
||||
"emergency_lock",
|
||||
"emergency_unlock",
|
||||
"execute",
|
||||
|
|
|
@ -31,11 +31,6 @@ defmodule FarmbotCeleryScript.AST.Factory do
|
|||
)
|
||||
end
|
||||
|
||||
def dump_info(%AST{} = ast) do
|
||||
ast
|
||||
|> add_body_node(new(:dump_info))
|
||||
end
|
||||
|
||||
def emergency_lock(%AST{} = ast) do
|
||||
ast
|
||||
|> add_body_node(new(:emergency_lock))
|
||||
|
|
|
@ -279,12 +279,6 @@ defmodule FarmbotCeleryScript.Compiler do
|
|||
end
|
||||
end
|
||||
|
||||
def dump_info(_, _env) do
|
||||
quote location: :keep do
|
||||
FarmbotCeleryScript.SysCalls.dump_info()
|
||||
end
|
||||
end
|
||||
|
||||
defp print_compiled_code(compiled) do
|
||||
IO.puts("========")
|
||||
|
||||
|
|
|
@ -29,7 +29,6 @@ defmodule FarmbotCeleryScript.SysCalls do
|
|||
@callback check_update() :: ok_or_error
|
||||
@callback coordinate(x :: number, y :: number, z :: number) ::
|
||||
%{x: number(), y: number(), z: number()} | error
|
||||
@callback dump_info() :: ok_or_error
|
||||
@callback emergency_lock() :: ok_or_error
|
||||
@callback emergency_unlock() :: 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])
|
||||
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
|
||||
ok_or_error(sys_calls, :emergency_lock, [])
|
||||
end
|
||||
|
|
|
@ -28,9 +28,6 @@ defmodule FarmbotCeleryScript.SysCalls.Stubs do
|
|||
@impl true
|
||||
def coordinate(x, y, z), do: error(:coordinate, [x, y, z])
|
||||
|
||||
@impl true
|
||||
def dump_info(), do: error(:dump_info, [])
|
||||
|
||||
@impl true
|
||||
def emergency_lock(), do: error(:emergency_lock, [])
|
||||
|
||||
|
|
|
@ -3,7 +3,10 @@ defmodule FarmbotCeleryScript.Corpus.NodeTest do
|
|||
alias FarmbotCeleryScript.Corpus
|
||||
|
||||
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]" =
|
||||
inspect(Corpus.sequence())
|
||||
a =
|
||||
"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
|
||||
|
|
|
@ -35,7 +35,6 @@ config :farmbot_core, FarmbotCore.BotState.FileSystem,
|
|||
sleep_time: 200
|
||||
|
||||
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_server: "https://my.farm.bot",
|
||||
default_dns_name: "my.farm.bot",
|
||||
|
|
|
@ -9,7 +9,6 @@ defmodule FarmbotCore.Asset do
|
|||
Repo,
|
||||
Device,
|
||||
DeviceCert,
|
||||
DiagnosticDump,
|
||||
FarmwareEnv,
|
||||
FirstPartyFarmware,
|
||||
FarmwareInstallation,
|
||||
|
@ -54,6 +53,7 @@ defmodule FarmbotCore.Asset do
|
|||
if device = Repo.get_by(Device, id: id) do
|
||||
Repo.delete!(device)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
|
@ -78,13 +78,14 @@ defmodule FarmbotCore.Asset do
|
|||
end
|
||||
|
||||
def update_farm_event!(farm_event, params) do
|
||||
farm_event =
|
||||
farm_event |>
|
||||
FarmEvent.changeset(params)
|
||||
farm_event =
|
||||
farm_event
|
||||
|> FarmEvent.changeset(params)
|
||||
|> Repo.update!()
|
||||
|
||||
if farm_event.executable_type == "Regimen" do
|
||||
regimen_instance = get_regimen_instance(farm_event)
|
||||
|
||||
if regimen_instance do
|
||||
regimen_instance
|
||||
|> Repo.preload([:farm_event, :regimen])
|
||||
|
@ -111,9 +112,11 @@ defmodule FarmbotCore.Asset do
|
|||
|
||||
def get_farm_event_execution(%FarmEvent{} = farm_event, scheduled_at) do
|
||||
Repo.one(
|
||||
from e in FarmEvent.Execution,
|
||||
where: e.farm_event_local_id == ^farm_event.local_id
|
||||
and e.scheduled_at == ^scheduled_at
|
||||
from(e in FarmEvent.Execution,
|
||||
where:
|
||||
e.farm_event_local_id == ^farm_event.local_id and
|
||||
e.scheduled_at == ^scheduled_at
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -149,6 +152,7 @@ defmodule FarmbotCore.Asset do
|
|||
if fbos_config = Repo.get_by(FbosConfig, id: id) do
|
||||
Repo.delete!(fbos_config)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
|
@ -177,6 +181,7 @@ defmodule FarmbotCore.Asset do
|
|||
if firmware_config = Repo.get_by(FirmwareConfig, id: id) do
|
||||
Repo.delete!(firmware_config)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
|
@ -192,12 +197,19 @@ defmodule FarmbotCore.Asset do
|
|||
end
|
||||
|
||||
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 ri in RegimenInstance, where: ri.regimen_id == ^regimen.local_id and ri.farm_event_id == ^farm_event.local_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
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
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)
|
||||
|> Ecto.Changeset.put_assoc(:regimen, regimen)
|
||||
|> Ecto.Changeset.put_assoc(:farm_event, farm_event)
|
||||
|
@ -207,7 +219,7 @@ defmodule FarmbotCore.Asset do
|
|||
def delete_regimen_instance!(%RegimenInstance{} = ri) do
|
||||
Repo.delete!(ri)
|
||||
end
|
||||
|
||||
|
||||
def add_execution_to_regimen_instance!(%RegimenInstance{} = ri, params \\ %{}) do
|
||||
%RegimenInstance.Execution{}
|
||||
|> RegimenInstance.Execution.changeset(params)
|
||||
|
@ -217,9 +229,11 @@ defmodule FarmbotCore.Asset do
|
|||
|
||||
def get_regimen_instance_execution(%RegimenInstance{} = ri, scheduled_at) do
|
||||
Repo.one(
|
||||
from e in RegimenInstance.Execution,
|
||||
where: e.regimen_instance_local_id == ^ri.local_id
|
||||
and e.scheduled_at == ^scheduled_at
|
||||
from(e in RegimenInstance.Execution,
|
||||
where:
|
||||
e.regimen_instance_local_id == ^ri.local_id and
|
||||
e.scheduled_at == ^scheduled_at
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -248,7 +262,7 @@ defmodule FarmbotCore.Asset do
|
|||
|
||||
@doc "Returns all points matching Point.pointer_type"
|
||||
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()
|
||||
|> sort_points(order_by)
|
||||
end
|
||||
|
@ -257,7 +271,7 @@ defmodule FarmbotCore.Asset do
|
|||
points
|
||||
|> Enum.group_by(&group_points_by(&1, 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()
|
||||
end
|
||||
|
||||
|
@ -272,7 +286,6 @@ defmodule FarmbotCore.Asset do
|
|||
def group_sort({lgroup, _}, {rgroup, _}, "yx_descending"), do: lgroup >= rgroup
|
||||
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_descending"), do: ly >= ry
|
||||
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
|
||||
case Repo.get_by(PointGroup, params) do
|
||||
nil ->
|
||||
nil ->
|
||||
nil
|
||||
|
||||
%{sort_type: nil} = group ->
|
||||
group
|
||||
%{sort_type: nil} = group ->
|
||||
group
|
||||
|
||||
%{point_ids: unsorted, sort_type: sort_by} = point_group ->
|
||||
sorted =
|
||||
Repo.all(from p in Point, where: p.id in ^unsorted)
|
||||
sorted =
|
||||
Repo.all(from(p in Point, where: p.id in ^unsorted))
|
||||
|> sort_points(sort_by)
|
||||
|> Enum.map(&Map.fetch!(&1, :id))
|
||||
|
||||
|
@ -308,7 +321,7 @@ defmodule FarmbotCore.Asset do
|
|||
end
|
||||
|
||||
def update_point_group!(point_group, params) do
|
||||
updated =
|
||||
updated =
|
||||
point_group
|
||||
|> PointGroup.changeset(params)
|
||||
|> Repo.update!()
|
||||
|
@ -322,11 +335,11 @@ defmodule FarmbotCore.Asset do
|
|||
for asset <- farm_events ++ regimen_instances do
|
||||
# TODO(Connor) this might be worth creating a behaviour for
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
updated
|
||||
end
|
||||
|
||||
|
@ -335,14 +348,16 @@ defmodule FarmbotCore.Asset do
|
|||
end
|
||||
|
||||
def uses_point_group?(%FarmEvent{body: body}, %PointGroup{id: point_group_id}) do
|
||||
|
||||
any_body_node_uses_point_group?(body, point_group_id)
|
||||
end
|
||||
|
||||
def uses_point_group?(%Regimen{body: body, regimen_items: regimen_items}, %PointGroup{id: point_group_id}) do
|
||||
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)
|
||||
def uses_point_group?(%Regimen{body: body, regimen_items: regimen_items}, %PointGroup{
|
||||
id: point_group_id
|
||||
}) do
|
||||
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
|
||||
|
||||
def uses_point_group?(%RegimenInstance{farm_event: farm_event, regimen: regimen}, point_group) do
|
||||
|
@ -350,11 +365,13 @@ defmodule FarmbotCore.Asset do
|
|||
end
|
||||
|
||||
def any_body_node_uses_point_group?(body, point_group_id) do
|
||||
Enum.find(body, fn
|
||||
Enum.find(body, fn
|
||||
%{
|
||||
kind: "execute",
|
||||
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: %{
|
||||
"data_value" => %{
|
||||
|
@ -364,7 +381,8 @@ defmodule FarmbotCore.Asset do
|
|||
"label" => "parent"
|
||||
},
|
||||
kind: "parameter_application"
|
||||
} -> true
|
||||
} ->
|
||||
true
|
||||
|
||||
%{
|
||||
args: %{
|
||||
|
@ -375,8 +393,11 @@ defmodule FarmbotCore.Asset do
|
|||
"label" => "parent"
|
||||
},
|
||||
kind: "parameter_application"
|
||||
} -> true
|
||||
_ -> false
|
||||
} ->
|
||||
true
|
||||
|
||||
_ ->
|
||||
false
|
||||
end)
|
||||
end
|
||||
|
||||
|
@ -407,6 +428,7 @@ defmodule FarmbotCore.Asset do
|
|||
def new_public_key_from_home!() do
|
||||
public_key_path = Path.join([System.get_env("HOME"), ".ssh", "id_rsa.pub"])
|
||||
public_key = File.read!(public_key_path)
|
||||
|
||||
%PublicKey{}
|
||||
|> PublicKey.changeset(%{public_key: public_key})
|
||||
|> Repo.insert()
|
||||
|
@ -417,7 +439,7 @@ defmodule FarmbotCore.Asset do
|
|||
|> PublicKey.changeset(%{public_key: public_key})
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
|
||||
## End PublicKey
|
||||
|
||||
## Begin Regimen
|
||||
|
@ -435,18 +457,23 @@ defmodule FarmbotCore.Asset do
|
|||
end
|
||||
|
||||
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
|
||||
IO.puts "deleting regimen instance: #{inspect(ri)}"
|
||||
IO.puts("deleting regimen instance: #{inspect(ri)}")
|
||||
delete_regimen_instance!(ri)
|
||||
end
|
||||
|
||||
Repo.delete!(regimen)
|
||||
end
|
||||
|
||||
@doc "Update an existing regimen"
|
||||
def update_regimen!(regimen, params) do
|
||||
regimen_instances = Repo.all(from ri in RegimenInstance, where: ri.regimen_id == ^regimen.local_id)
|
||||
|> Repo.preload([:farm_event, :regimen])
|
||||
regimen_instances =
|
||||
Repo.all(from(ri in RegimenInstance, where: ri.regimen_id == ^regimen.local_id))
|
||||
|> Repo.preload([:farm_event, :regimen])
|
||||
|
||||
for ri <- regimen_instances do
|
||||
ri
|
||||
|> RegimenInstance.changeset(%{updated_at: DateTime.utc_now()})
|
||||
|
@ -469,23 +496,31 @@ defmodule FarmbotCore.Asset do
|
|||
|
||||
def update_sequence!(%Sequence{} = sequence, params \\ %{}) do
|
||||
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
|
||||
|> 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
|
||||
farm_events =
|
||||
Repo.all(
|
||||
from(f in FarmEvent,
|
||||
where:
|
||||
f.executable_type == "Sequence" and
|
||||
f.executable_id == ^sequence_id
|
||||
)
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
%{regimen: nil} -> false
|
||||
end)
|
||||
|
||||
for asset <- farm_events ++ regimen_instances do
|
||||
FarmbotCore.AssetSupervisor.update_child(asset)
|
||||
end
|
||||
|
@ -507,10 +542,11 @@ defmodule FarmbotCore.Asset do
|
|||
def get_farmware_manifest(package) do
|
||||
first_party_farmwares = Repo.all(from(fwi in FirstPartyFarmware, select: fwi.manifest))
|
||||
regular_farmwares = Repo.all(from(fwi in FarmwareInstallation, select: fwi.manifest))
|
||||
|
||||
Enum.find(
|
||||
first_party_farmwares ++ regular_farmwares,
|
||||
fn
|
||||
%{package: pkg} -> pkg == package
|
||||
first_party_farmwares ++ regular_farmwares,
|
||||
fn
|
||||
%{package: pkg} -> pkg == package
|
||||
_ -> false
|
||||
end
|
||||
)
|
||||
|
@ -519,10 +555,11 @@ defmodule FarmbotCore.Asset do
|
|||
def get_farmware_installation(package) do
|
||||
first_party_farmwares = Repo.all(from(fwi in FirstPartyFarmware))
|
||||
regular_farmwares = Repo.all(from(fwi in FarmwareInstallation))
|
||||
|
||||
Enum.find(
|
||||
first_party_farmwares ++ regular_farmwares,
|
||||
fn
|
||||
%{manifest: %{package: pkg}} -> pkg == package
|
||||
first_party_farmwares ++ regular_farmwares,
|
||||
fn
|
||||
%{manifest: %{package: pkg}} -> pkg == package
|
||||
_ -> false
|
||||
end
|
||||
)
|
||||
|
@ -530,12 +567,14 @@ defmodule FarmbotCore.Asset do
|
|||
|
||||
def upsert_farmware_manifest_by_id(id, params) do
|
||||
fwi = Repo.get_by(FarmwareInstallation, id: id) || %FarmwareInstallation{}
|
||||
|
||||
FarmwareInstallation.changeset(fwi, params)
|
||||
|> Repo.insert_or_update()
|
||||
end
|
||||
|
||||
def upsert_first_party_farmware_manifest_by_id(id, params) do
|
||||
fwi = Repo.get_by(FirstPartyFarmware, id: id) || %FirstPartyFarmware{}
|
||||
|
||||
FirstPartyFarmware.changeset(fwi, params)
|
||||
|> Repo.insert_or_update()
|
||||
end
|
||||
|
@ -557,12 +596,14 @@ defmodule FarmbotCore.Asset do
|
|||
|
||||
def new_farmware_env(params) do
|
||||
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
|
||||
else
|
||||
_ -> %FarmwareEnv{}
|
||||
end
|
||||
|
||||
fwe =
|
||||
with key when is_binary(key) <- key,
|
||||
[fwe | _] <- Repo.all(from(fwe in FarmwareEnv, where: fwe.key == ^key)) do
|
||||
fwe
|
||||
else
|
||||
_ -> %FarmwareEnv{}
|
||||
end
|
||||
|
||||
FarmwareEnv.changeset(fwe, params)
|
||||
|> Repo.insert_or_update()
|
||||
|
@ -596,7 +637,7 @@ defmodule FarmbotCore.Asset do
|
|||
Sensor.changeset(%Sensor{}, params)
|
||||
|> Repo.insert!()
|
||||
end
|
||||
|
||||
|
||||
def update_sensor!(sensor, params) do
|
||||
sensor
|
||||
|> Sensor.changeset(params)
|
||||
|
@ -624,15 +665,6 @@ defmodule FarmbotCore.Asset do
|
|||
|
||||
## End SensorReading
|
||||
|
||||
## Begin DiagnosticDump
|
||||
|
||||
def new_diagnostic_dump(params) do
|
||||
DiagnosticDump.changeset(%DiagnosticDump{}, params)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
## End DiagnosticDump
|
||||
|
||||
## Begin DeviceCert
|
||||
|
||||
def new_device_cert(params) do
|
||||
|
|
|
@ -4,6 +4,7 @@ defmodule FarmbotCore.Asset.Command do
|
|||
"""
|
||||
require Logger
|
||||
alias FarmbotCore.{Asset, Asset.Repo}
|
||||
|
||||
alias FarmbotCore.Asset.{
|
||||
Device,
|
||||
FarmEvent,
|
||||
|
@ -44,31 +45,31 @@ defmodule FarmbotCore.Asset.Command do
|
|||
:ok
|
||||
end
|
||||
|
||||
def update(Device, _id, params) do
|
||||
def update(Device, _id, params) do
|
||||
Asset.update_device!(params)
|
||||
:ok
|
||||
end
|
||||
|
||||
def update(FbosConfig, id, nil) do
|
||||
def update(FbosConfig, id, nil) do
|
||||
Asset.delete_fbos_config!(id)
|
||||
:ok
|
||||
end
|
||||
|
||||
def update(FbosConfig, _id, params) do
|
||||
def update(FbosConfig, _id, params) do
|
||||
Asset.update_fbos_config!(params)
|
||||
:ok
|
||||
end
|
||||
|
||||
def update(FirmwareConfig, id, nil) do
|
||||
def update(FirmwareConfig, id, nil) do
|
||||
Asset.delete_firmware_config!(id)
|
||||
:ok
|
||||
end
|
||||
|
||||
def update(FirmwareConfig, _id, params) do
|
||||
|
||||
def update(FirmwareConfig, _id, params) do
|
||||
Asset.update_firmware_config!(params)
|
||||
:ok
|
||||
end
|
||||
|
||||
|
||||
# Deletion use case:
|
||||
# TODO(Connor) put checks for deleting Device, FbosConfig and FirmwareConfig
|
||||
|
||||
|
@ -96,12 +97,12 @@ defmodule FarmbotCore.Asset.Command do
|
|||
:ok
|
||||
end
|
||||
|
||||
def update(FarmwareEnv, id, params) do
|
||||
def update(FarmwareEnv, id, params) do
|
||||
Asset.upsert_farmware_env_by_id(id, params)
|
||||
:ok
|
||||
end
|
||||
|
||||
def update(FarmwareInstallation, id, params) do
|
||||
|
||||
def update(FarmwareInstallation, id, params) do
|
||||
Asset.upsert_farmware_manifest_by_id(id, params)
|
||||
:ok
|
||||
end
|
||||
|
@ -112,15 +113,17 @@ defmodule FarmbotCore.Asset.Command do
|
|||
|
||||
def update(FarmEvent, id, params) do
|
||||
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)
|
||||
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def update(PublicKey, id, params) do
|
||||
old = Asset.get_public_key(id)
|
||||
|
||||
if old,
|
||||
do: Asset.update_public_key!(old, params),
|
||||
else: Asset.new_public_key!(params)
|
||||
|
@ -130,33 +133,37 @@ defmodule FarmbotCore.Asset.Command do
|
|||
|
||||
def update(Regimen, id, params) do
|
||||
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)
|
||||
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def update(Sensor, id, params) do
|
||||
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)
|
||||
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def update(SensorReading, id, params) do
|
||||
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)
|
||||
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def update(Sequence, id, params) do
|
||||
old = Asset.get_sequence(id)
|
||||
|
||||
if old,
|
||||
do: Asset.update_sequence!(old, params),
|
||||
else: Asset.new_sequence!(params)
|
||||
|
@ -172,6 +179,7 @@ defmodule FarmbotCore.Asset.Command do
|
|||
|
||||
def update(PointGroup, id, params) do
|
||||
old = Asset.get_point_group(id: id)
|
||||
|
||||
if old,
|
||||
do: Asset.update_point_group!(old, params),
|
||||
else: Asset.new_point_group!(params)
|
||||
|
@ -181,8 +189,9 @@ defmodule FarmbotCore.Asset.Command do
|
|||
|
||||
# Catch-all use case:
|
||||
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)
|
||||
|
||||
case Repo.get_by(mod, id: id) do
|
||||
nil ->
|
||||
struct!(mod)
|
||||
|
@ -205,28 +214,27 @@ defmodule FarmbotCore.Asset.Command do
|
|||
mod.changeset(asset, params)
|
||||
end
|
||||
|
||||
defp as_module!("Device"), do: Asset.Device
|
||||
defp as_module!("DiagnosticDump"), do: Asset.DiagnosticDump
|
||||
defp as_module!("FarmEvent"), do: Asset.FarmEvent
|
||||
defp as_module!("FarmwareEnv"), do: Asset.FarmwareEnv
|
||||
defp as_module!("FirstPartyFarmware"), do: Asset.FirstPartyFarmware
|
||||
defp as_module!("FarmwareInstallation"), do: Asset.FarmwareInstallation
|
||||
defp as_module!("FbosConfig"), do: Asset.FbosConfig
|
||||
defp as_module!("FirmwareConfig"), do: Asset.FirmwareConfig
|
||||
defp as_module!("Peripheral"), do: Asset.Peripheral
|
||||
defp as_module!("PinBinding"), do: Asset.PinBinding
|
||||
defp as_module!("Point"), do: Asset.Point
|
||||
defp as_module!("PointGroup"), do: Asset.PointGroup
|
||||
defp as_module!("Regimen"), do: Asset.Regimen
|
||||
defp as_module!("Sensor"), do: Asset.Sensor
|
||||
defp as_module!("SensorReading"), do: Asset.SensorReading
|
||||
defp as_module!("Sequence"), do: Asset.Sequence
|
||||
defp as_module!("Tool"), do: Asset.Tool
|
||||
defp as_module!("Device"), do: Asset.Device
|
||||
defp as_module!("FarmEvent"), do: Asset.FarmEvent
|
||||
defp as_module!("FarmwareEnv"), do: Asset.FarmwareEnv
|
||||
defp as_module!("FirstPartyFarmware"), do: Asset.FirstPartyFarmware
|
||||
defp as_module!("FarmwareInstallation"), do: Asset.FarmwareInstallation
|
||||
defp as_module!("FbosConfig"), do: Asset.FbosConfig
|
||||
defp as_module!("FirmwareConfig"), do: Asset.FirmwareConfig
|
||||
defp as_module!("Peripheral"), do: Asset.Peripheral
|
||||
defp as_module!("PinBinding"), do: Asset.PinBinding
|
||||
defp as_module!("Point"), do: Asset.Point
|
||||
defp as_module!("PointGroup"), do: Asset.PointGroup
|
||||
defp as_module!("Regimen"), do: Asset.Regimen
|
||||
defp as_module!("Sensor"), do: Asset.Sensor
|
||||
defp as_module!("SensorReading"), do: Asset.SensorReading
|
||||
defp as_module!("Sequence"), do: Asset.Sequence
|
||||
defp as_module!("Tool"), do: Asset.Tool
|
||||
|
||||
defp as_module!(module) when is_atom(module) do
|
||||
as_module!(List.last(Module.split(module)))
|
||||
end
|
||||
|
||||
|
||||
defp as_module!(kind) when is_binary(kind) do
|
||||
raise("""
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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("power_off"), do: "Power Off"
|
||||
defp format_action("read_status"), do: "Read Status"
|
||||
defp format_action("reboot"), do: "Reboot"
|
||||
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
|
||||
end
|
||||
|
|
|
@ -9,7 +9,6 @@ defmodule FarmbotCore.Asset.Private.LocalMeta do
|
|||
alias FarmbotCore.Asset.{
|
||||
Repo,
|
||||
Device,
|
||||
DiagnosticDump,
|
||||
DeviceCert,
|
||||
FarmEvent,
|
||||
FarmwareEnv,
|
||||
|
@ -47,13 +46,6 @@ defmodule FarmbotCore.Asset.Private.LocalMeta do
|
|||
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,
|
||||
foreign_key: :asset_local_id,
|
||||
type: :binary_id,
|
||||
|
@ -174,8 +166,7 @@ defmodule FarmbotCore.Asset.Private.LocalMeta do
|
|||
"firmware_configs",
|
||||
"fbos_configs",
|
||||
"farmware_installations",
|
||||
"farmware_envs",
|
||||
"diagnostic_dumps"
|
||||
"farmware_envs"
|
||||
])
|
||||
|> unsafe_validate_unique([:table, :asset_local_id], Repo,
|
||||
message: "LocalMeta already exists."
|
||||
|
|
|
@ -41,7 +41,6 @@ defmodule FarmbotCore.Asset.Sync do
|
|||
embeds_many(:devices, Item)
|
||||
embeds_many(:firmware_configs, Item)
|
||||
embeds_many(:fbos_configs, Item)
|
||||
embeds_many(:diagnostic_dumps, Item)
|
||||
embeds_many(:farm_events, Item)
|
||||
embeds_many(:farmware_envs, Item)
|
||||
embeds_many(:first_party_farmwares, Item)
|
||||
|
@ -65,7 +64,6 @@ defmodule FarmbotCore.Asset.Sync do
|
|||
devices: Enum.map(sync.device, &Item.render/1),
|
||||
fbos_configs: Enum.map(sync.fbos_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),
|
||||
farmware_envs: Enum.map(sync.farmware_envs, &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(:fbos_configs)
|
||||
|> cast_embed(:firmware_configs)
|
||||
|> cast_embed(:diagnostic_dumps)
|
||||
|> cast_embed(:farm_events)
|
||||
|> cast_embed(:farmware_envs)
|
||||
|> cast_embed(:farmware_installations)
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
defmodule FarmbotCore.AssetHelpers do
|
||||
@moduledoc """
|
||||
Helpers for the console at runtime.
|
||||
|
||||
|
||||
Example:
|
||||
iex()> use FarmbotCore.AssetHelpers
|
||||
iex()> Repo.all(Device)
|
||||
[%Device{}]
|
||||
"""
|
||||
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_opts) do
|
||||
require Logger
|
||||
Logger.warn "Don't use this in production please!"
|
||||
Logger.warn("Don't use this in production please!")
|
||||
|
||||
quote do
|
||||
import Ecto.Query
|
||||
alias FarmbotCore.Asset
|
||||
|
||||
alias Asset.{
|
||||
Repo,
|
||||
Device,
|
||||
DeviceCert,
|
||||
DiagnosticDump,
|
||||
FarmwareEnv,
|
||||
FarmwareInstallation,
|
||||
FirstPartyFarmware,
|
||||
|
@ -40,4 +41,4 @@ defmodule FarmbotCore.AssetHelpers do
|
|||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -33,7 +33,7 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.Peripheral do
|
|||
{:noreply, state}
|
||||
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}
|
||||
end
|
||||
|
||||
|
@ -47,15 +47,15 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.Peripheral do
|
|||
Logger.debug("Read peripheral: #{peripheral.label}")
|
||||
rpc = peripheral_to_rpc(peripheral)
|
||||
case FarmbotCeleryScript.execute(rpc, make_ref()) do
|
||||
:ok ->
|
||||
:ok ->
|
||||
Logger.debug("Read peripheral: #{peripheral.label} ok")
|
||||
{:noreply, state}
|
||||
|
||||
{:error, reason} when errors < 5 ->
|
||||
|
||||
{:error, reason} when errors < 5 ->
|
||||
Logger.error("Read peripheral: #{peripheral.label} error: #{reason} errors=#{state.errors}")
|
||||
Process.send_after(self(), :timeout, @retry_ms)
|
||||
{:noreply, %{state | errors: state.errors + 1}}
|
||||
|
||||
|
||||
{:error, reason} when errors == 5 ->
|
||||
Logger.error("Read peripheral: #{peripheral.label} error: #{reason} errors=5 not trying again.")
|
||||
{: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.)
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
|
||||
use GenServer
|
||||
require 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
|
||||
%Sequence{name: name} = seq ->
|
||||
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing #{name}")
|
||||
|
||||
AST.decode(seq)
|
||||
|> execute(state)
|
||||
|
||||
|
@ -84,24 +85,24 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.PinBinding do
|
|||
end
|
||||
end
|
||||
|
||||
def handle_cast(:trigger, %{pin_binding: %{special_action: "dump_info"} = pin_binding} = state) do
|
||||
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Dump Info")
|
||||
AST.Factory.new()
|
||||
|> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}")
|
||||
|> AST.Factory.dump_info()
|
||||
|> execute(state)
|
||||
end
|
||||
|
||||
def handle_cast(:trigger, %{pin_binding: %{special_action: "emergency_lock"} = pin_binding} = state) do
|
||||
def handle_cast(
|
||||
:trigger,
|
||||
%{pin_binding: %{special_action: "emergency_lock"} = pin_binding} = state
|
||||
) do
|
||||
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Emergency Lock")
|
||||
|
||||
AST.Factory.new()
|
||||
|> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}")
|
||||
|> AST.Factory.emergency_lock()
|
||||
|> 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")
|
||||
|
||||
AST.Factory.new()
|
||||
|> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}")
|
||||
|> 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
|
||||
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Power Off")
|
||||
|
||||
AST.Factory.new()
|
||||
|> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}")
|
||||
|> AST.Factory.power_off()
|
||||
|> execute(state)
|
||||
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")
|
||||
|
||||
AST.Factory.new()
|
||||
|> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}")
|
||||
|> 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
|
||||
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Reboot")
|
||||
|
||||
AST.Factory.new()
|
||||
|> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}")
|
||||
|> 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
|
||||
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Sync")
|
||||
|
||||
AST.Factory.new()
|
||||
|> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}")
|
||||
|> 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
|
||||
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Take Photo")
|
||||
|
||||
AST.Factory.new()
|
||||
|> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}")
|
||||
|> AST.Factory.take_photo()
|
||||
|
@ -182,10 +191,13 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.PinBinding do
|
|||
|
||||
defp execute(%AST{} = ast, state) do
|
||||
case FarmbotCeleryScript.execute(ast, make_ref()) do
|
||||
:ok -> :ok
|
||||
:ok ->
|
||||
:ok
|
||||
|
||||
{:error, reason} ->
|
||||
FarmbotCore.Logger.error 1, "error executing #{state.pin_binding}: #{reason}"
|
||||
FarmbotCore.Logger.error(1, "error executing #{state.pin_binding}: #{reason}")
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ defmodule FarmbotCore.FarmwareRuntime do
|
|||
Handles execution of Farmware plugins.
|
||||
"""
|
||||
|
||||
alias Avrdude.MuonTrapAdapter
|
||||
alias FarmbotCeleryScript.AST
|
||||
alias FarmbotCore.Asset.FarmwareInstallation.Manifest
|
||||
alias FarmbotCore.AssetWorker.FarmbotCore.Asset.FarmwareInstallation
|
||||
|
@ -113,8 +112,7 @@ defmodule FarmbotCore.FarmwareRuntime do
|
|||
# Start the plugin.
|
||||
Logger.debug("spawning farmware: #{exec} #{manifest.args}")
|
||||
|
||||
{cmd, _} =
|
||||
spawn_monitor(MuonTrapAdapter, :cmd, ["sh", ["-c", "#{exec} #{manifest.args}"], opts])
|
||||
{cmd, _} = spawn_monitor(MuonTrap, :cmd, ["sh", ["-c", "#{exec} #{manifest.args}"], opts])
|
||||
|
||||
state = %State{
|
||||
caller: caller,
|
||||
|
@ -156,8 +154,7 @@ defmodule FarmbotCore.FarmwareRuntime do
|
|||
end
|
||||
|
||||
def handle_info({:step_complete, ref, :ok}, %{scheduler_ref: ref} = state) do
|
||||
label = UUID.uuid4()
|
||||
result = %AST{kind: :rpc_ok, args: %{label: label}, body: []}
|
||||
result = %AST{kind: :rpc_ok, args: %{label: state.rpc.args.label}, body: []}
|
||||
|
||||
ipc = add_header(result)
|
||||
_reply = PipeWorker.write(state.response_pipe_handle, ipc)
|
||||
|
|
|
@ -82,31 +82,50 @@ defmodule FarmbotCore.FirmwareSideEffects do
|
|||
[_, _, _, "R"] ->
|
||||
_ = Leds.red(:solid)
|
||||
:ok = BotState.set_firmware_hardware("arduino")
|
||||
[_, _, _, "R", _] ->
|
||||
_ = Leds.red(:solid)
|
||||
:ok = BotState.set_firmware_hardware("arduino")
|
||||
|
||||
# Farmduino
|
||||
[_, _, _, "F"] ->
|
||||
_ = Leds.red(:solid)
|
||||
:ok = BotState.set_firmware_hardware("farmduino")
|
||||
[_, _, _, "F", _] ->
|
||||
_ = Leds.red(:solid)
|
||||
:ok = BotState.set_firmware_hardware("farmduino")
|
||||
|
||||
# Farmduino V14
|
||||
[_, _, _, "G"] ->
|
||||
_ = Leds.red(:solid)
|
||||
:ok = BotState.set_firmware_hardware("farmduino_k14")
|
||||
[_, _, _, "G", _] ->
|
||||
_ = Leds.red(:solid)
|
||||
:ok = BotState.set_firmware_hardware("farmduino_k14")
|
||||
|
||||
# Farmduino V15
|
||||
[_, _, _, "H"] ->
|
||||
_ = Leds.red(:solid)
|
||||
:ok = BotState.set_firmware_hardware("farmduino_k15")
|
||||
[_, _, _, "H", _] ->
|
||||
_ = Leds.red(:solid)
|
||||
:ok = BotState.set_firmware_hardware("farmduino_k15")
|
||||
|
||||
# Express V10
|
||||
[_, _, _, "E"] ->
|
||||
_ = Leds.red(:solid)
|
||||
:ok = BotState.set_firmware_hardware("express_k10")
|
||||
[_, _, _, "E", _] ->
|
||||
_ = Leds.red(:solid)
|
||||
:ok = BotState.set_firmware_hardware("express_k10")
|
||||
|
||||
[_, _, _, "S"] ->
|
||||
_ = Leds.red(:slow_blink)
|
||||
:ok = BotState.set_firmware_version("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
|
||||
|
||||
|
|
|
@ -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}
|
||||
import Ecto.Query
|
||||
@log_types [:info, :debug, :busy, :warn, :success, :error, :fun, :assertion]
|
||||
|
||||
@doc "Send a debug message to log endpoints"
|
||||
defmacro debug(verbosity, message, meta \\ []) do
|
||||
|
@ -57,23 +58,27 @@ defmodule FarmbotCore.Logger do
|
|||
|
||||
def insert_log!(params) do
|
||||
changeset = Log.changeset(%Log{}, params)
|
||||
|
||||
try do
|
||||
hash = Ecto.Changeset.get_field(changeset, :hash)
|
||||
|
||||
case Repo.get_by(Log, hash: hash) do
|
||||
nil ->
|
||||
nil ->
|
||||
Repo.insert!(changeset)
|
||||
old ->
|
||||
params =
|
||||
|
||||
old ->
|
||||
params =
|
||||
params
|
||||
|> Map.put(:inserted_at, DateTime.utc_now)
|
||||
|> Map.put(:inserted_at, DateTime.utc_now())
|
||||
|> Map.put(:duplicates, old.duplicates + 1)
|
||||
old
|
||||
|> Log.changeset(params)
|
||||
|> Repo.update!()
|
||||
|
||||
old
|
||||
|> Log.changeset(params)
|
||||
|> Repo.update!()
|
||||
end
|
||||
catch
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
@ -94,13 +99,16 @@ defmodule FarmbotCore.Logger do
|
|||
|
||||
@doc false
|
||||
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
|
||||
is_binary(message) and is_list(meta) do
|
||||
when level in @log_types and
|
||||
is_number(verbosity) and
|
||||
is_binary(message) and
|
||||
is_list(meta) do
|
||||
fun =
|
||||
case env.function do
|
||||
{fun, ar} -> "#{fun}/#{ar}"
|
||||
nil -> "no_function"
|
||||
end
|
||||
|
||||
%{
|
||||
level: level,
|
||||
verbosity: verbosity,
|
||||
|
@ -116,29 +124,8 @@ defmodule FarmbotCore.Logger do
|
|||
|
||||
@doc false
|
||||
def dispatch_log(params) do
|
||||
params
|
||||
|> insert_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
|
||||
log = insert_log!(params)
|
||||
FarmbotCore.LogExecutor.execute(log)
|
||||
end
|
||||
|
||||
@doc "Helper function for deciding if a message should be logged or not."
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
defmodule FarmbotExt.API.DirtyWorker.Supervisor do
|
||||
@moduledoc """
|
||||
Responsible for supervising assets that will need to be
|
||||
uploaded to the API via a `POST` or `PUT` request.
|
||||
Responsible for supervising assets that will need to be
|
||||
uploaded to the API via a `POST` or `PUT` request.
|
||||
"""
|
||||
|
||||
use Supervisor
|
||||
|
@ -10,7 +10,6 @@ defmodule FarmbotExt.API.DirtyWorker.Supervisor do
|
|||
alias FarmbotCore.Asset.{
|
||||
Device,
|
||||
DeviceCert,
|
||||
DiagnosticDump,
|
||||
FarmEvent,
|
||||
FarmwareEnv,
|
||||
FarmwareInstallation,
|
||||
|
@ -39,7 +38,6 @@ defmodule FarmbotExt.API.DirtyWorker.Supervisor do
|
|||
{DirtyWorker, DeviceCert},
|
||||
{DirtyWorker, FbosConfig},
|
||||
{DirtyWorker, FirmwareConfig},
|
||||
{DirtyWorker, DiagnosticDump},
|
||||
{DirtyWorker, FarmEvent},
|
||||
{DirtyWorker, FarmwareEnv},
|
||||
{DirtyWorker, FarmwareInstallation},
|
||||
|
|
|
@ -9,7 +9,6 @@ defmodule FarmbotExt.API.EagerLoader.Supervisor do
|
|||
|
||||
alias FarmbotCore.Asset.{
|
||||
Device,
|
||||
DiagnosticDump,
|
||||
FarmEvent,
|
||||
FarmwareEnv,
|
||||
FirstPartyFarmware,
|
||||
|
@ -42,7 +41,6 @@ defmodule FarmbotExt.API.EagerLoader.Supervisor do
|
|||
def init(_args) do
|
||||
children = [
|
||||
{EagerLoader, Device},
|
||||
{EagerLoader, DiagnosticDump},
|
||||
{EagerLoader, FarmEvent},
|
||||
{EagerLoader, FarmwareEnv},
|
||||
{EagerLoader, FirstPartyFarmware},
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
# If you run "mix test --cover", coverage assets end up here.
|
||||
/cover/
|
||||
*.coverdata
|
||||
|
||||
# The directory Mix downloads your dependencies sources to.
|
||||
/deps/
|
||||
|
@ -23,4 +24,4 @@ erl_crash.dump
|
|||
farmbot_firmware-*.tar
|
||||
|
||||
# Ignore vcr files
|
||||
*.txt
|
||||
*.txt
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
PREFIX=$(PWD)/priv
|
||||
BUILD=$(PWD)/_build
|
||||
ARDUINO_SRC_DIR=$(PWD)/c_src/farmbot-arduino-firmware
|
||||
BUILD=$(ARDUINO_SRC_DIR)/_build
|
||||
|
||||
.PHONY: all clean fbos_arduino_firmware
|
||||
|
||||
|
@ -24,4 +24,4 @@ $(PREFIX):
|
|||
mkdir -p $(PREFIX)
|
||||
|
||||
$(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:
|
||||
* `:boot`
|
||||
* `:transport_boot`
|
||||
* `:no_config`
|
||||
* `:configuration`
|
||||
`command` will fail with `{:error, state}`
|
||||
|
@ -443,10 +444,13 @@ defmodule FarmbotFirmware do
|
|||
# the `configuration_queue` and in the `command_queue`.
|
||||
def handle_call(:close_transport, _from, %{status: s} = state)
|
||||
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)
|
||||
|
||||
state =
|
||||
next_state =
|
||||
goto(
|
||||
%{
|
||||
state
|
||||
|
@ -459,7 +463,7 @@ defmodule FarmbotFirmware do
|
|||
:transport_boot
|
||||
)
|
||||
|
||||
{:reply, :ok, state}
|
||||
{:reply, :ok, next_state}
|
||||
end
|
||||
|
||||
def handle_call(:close_transport, _, %{status: s} = state) do
|
||||
|
@ -501,8 +505,14 @@ defmodule FarmbotFirmware do
|
|||
end
|
||||
end
|
||||
|
||||
def handle_call({_tag, _code} = gcode, from, state) do
|
||||
handle_command(gcode, from, state)
|
||||
def handle_call({tag, {kind, args}}, from, state) do
|
||||
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
|
||||
|
||||
@doc false
|
||||
|
@ -542,7 +552,7 @@ defmodule FarmbotFirmware do
|
|||
# If not in an acceptable state, return an error immediately.
|
||||
def handle_command(_, _, %{status: s} = state)
|
||||
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
|
||||
|
||||
def handle_command({tag, {_, _}} = code, {pid, _ref}, state) do
|
||||
|
|
|
@ -93,9 +93,9 @@ defmodule FarmbotFirmware.Command do
|
|||
Application.put_env(:farmbot_firmware, __MODULE__, new)
|
||||
end
|
||||
|
||||
defp debug?() do
|
||||
def debug?() do
|
||||
Application.get_env(:farmbot_firmware, __MODULE__)[:debug_log] || false
|
||||
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
|
||||
|
|
|
@ -164,15 +164,15 @@ defmodule FarmbotFirmware.GCODE.Decoder do
|
|||
end
|
||||
|
||||
@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(
|
||||
[
|
||||
<<arg::binary-1, "A", val0::binary>>,
|
||||
<<arg::binary-1, "B", val1::binary>> | rest
|
||||
],
|
||||
acc
|
||||
) do
|
||||
def decode_end_stops(
|
||||
[
|
||||
<<arg::binary-1, "A", val0::binary>>,
|
||||
<<arg::binary-1, "B", val1::binary>> | rest
|
||||
],
|
||||
acc
|
||||
) do
|
||||
dc = String.downcase(arg)
|
||||
|
||||
acc =
|
||||
|
@ -185,19 +185,18 @@ defmodule FarmbotFirmware.GCODE.Decoder do
|
|||
decode_end_stops(rest, acc)
|
||||
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]
|
||||
|
||||
defp decode_pv(["P" <> param_id, "V" <> value]) do
|
||||
def decode_pv(["P" <> param_id, "V" <> value]) do
|
||||
param = Param.decode(String.to_integer(param_id))
|
||||
{value, ""} = Float.parse(value)
|
||||
[{param, value}]
|
||||
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
|
||||
|> String.downcase()
|
||||
|
@ -209,7 +208,7 @@ defmodule FarmbotFirmware.GCODE.Decoder do
|
|||
end
|
||||
end
|
||||
|
||||
defp decode_ints([], acc), do: Enum.reverse(acc)
|
||||
def decode_ints([], acc), do: Enum.reverse(acc)
|
||||
|
||||
@spec decode_echo(binary()) :: [binary()]
|
||||
defp decode_echo(str) when is_binary(str) do
|
||||
|
|
|
@ -27,6 +27,11 @@ defmodule FarmbotFirmware.PackageUtils do
|
|||
|> assert_exists()
|
||||
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),
|
||||
do: {:error, "unknown firmware hardware: #{hardware}"}
|
||||
|
||||
|
|
|
@ -226,6 +226,7 @@ defmodule FarmbotFirmware.Param do
|
|||
@seconds "(seconds)"
|
||||
@steps "(steps)"
|
||||
@steps_per_mm "(steps/mm)"
|
||||
@steps_per_s "(steps/s)"
|
||||
@milliamps "(milliamps)"
|
||||
|
||||
@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)}
|
||||
|
||||
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),
|
||||
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),
|
||||
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),
|
||||
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),
|
||||
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),
|
||||
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),
|
||||
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),
|
||||
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),
|
||||
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),
|
||||
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)}
|
||||
|
||||
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),
|
||||
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),
|
||||
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),
|
||||
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)}
|
||||
|
||||
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),
|
||||
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),
|
||||
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),
|
||||
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)}
|
||||
|
||||
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),
|
||||
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),
|
||||
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),
|
||||
do: {"use encoders for positioning, x-axis", nil, format_bool(value)}
|
||||
|
|
|
@ -11,26 +11,24 @@ defmodule FarmbotFirmware.Request do
|
|||
{:ok, GCODE.t()}
|
||||
| {:error,
|
||||
: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, {_tag, {kind, _}} = code) do
|
||||
if kind not in [
|
||||
:parameter_read,
|
||||
:status_read,
|
||||
:pin_read,
|
||||
:end_stops_read,
|
||||
:position_read,
|
||||
:software_version_read
|
||||
] do
|
||||
if kind not in @whitelist do
|
||||
raise ArgumentError, "#{kind} is not a valid request."
|
||||
end
|
||||
|
||||
case GenServer.call(firmware_server, code, :infinity) do
|
||||
{:ok, tag} ->
|
||||
wait_for_request_result(tag, code)
|
||||
|
||||
{:error, status} ->
|
||||
{:error, status}
|
||||
{:ok, tag} -> wait_for_request_result(tag, code)
|
||||
{:error, status} -> {:error, status}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -38,16 +36,26 @@ defmodule FarmbotFirmware.Request do
|
|||
request(firmware_server, {to_string(:rand.uniform(100)), code})
|
||||
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:
|
||||
# if this function `receive`s
|
||||
# * report_error
|
||||
# * report_invalid
|
||||
# * report_emergency_lock
|
||||
# it needs to return an error.
|
||||
#
|
||||
# If this function `receive`s
|
||||
# * report_success
|
||||
# when no valid data has been collected from `wait_for_request_result_process`
|
||||
# it needs to return an error.
|
||||
#
|
||||
# If this function `receive`s
|
||||
# * report_success
|
||||
# when valid data has been collected from `wait_for_request_result_process`
|
||||
|
@ -81,10 +89,7 @@ defmodule FarmbotFirmware.Request do
|
|||
{tag, report} ->
|
||||
wait_for_request_result_process(report, tag, code, result)
|
||||
after
|
||||
10_000 ->
|
||||
if result,
|
||||
do: {:ok, {tag, result}},
|
||||
else: {:error, "timeout waiting for request to complete"}
|
||||
10_000 -> request_timeout(tag, code, result)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -215,7 +215,7 @@ defmodule FarmbotFirmware.StubTransport do
|
|||
resp_codes = [
|
||||
GCODE.new(:report_echo, [GCODE.encode(code)]),
|
||||
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)
|
||||
]
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ defmodule FarmbotFirmware.UARTTransport do
|
|||
device = Keyword.fetch!(args, :device)
|
||||
handle_gcode = Keyword.fetch!(args, :handle_gcode)
|
||||
reset = Keyword.get(args, :reset)
|
||||
{:ok, uart} = uart_adapter().start_link()
|
||||
{:ok, uart} = UartDefaultAdapter.start_link()
|
||||
|
||||
{:ok,
|
||||
%{
|
||||
|
@ -27,11 +27,11 @@ defmodule FarmbotFirmware.UARTTransport do
|
|||
end
|
||||
|
||||
def terminate(_, %{uart: uart}) do
|
||||
uart_adapter().stop(uart)
|
||||
UartDefaultAdapter.stop(uart)
|
||||
end
|
||||
|
||||
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),
|
||||
:ok <- reset(state) do
|
||||
|
@ -55,7 +55,7 @@ defmodule FarmbotFirmware.UARTTransport do
|
|||
|
||||
def handle_call(code, _from, state) do
|
||||
str = GCODE.encode(code)
|
||||
r = uart_adapter().write(state.uart, str)
|
||||
r = UartDefaultAdapter.write(state.uart, str)
|
||||
{:reply, r, state}
|
||||
end
|
||||
|
||||
|
@ -68,10 +68,6 @@ defmodule FarmbotFirmware.UARTTransport do
|
|||
end
|
||||
|
||||
def open(uart_pid, device_path, opts) do
|
||||
uart_adapter().open(uart_pid, device_path, opts)
|
||||
end
|
||||
|
||||
def uart_adapter() do
|
||||
UartDefaultAdapter
|
||||
UartDefaultAdapter.open(uart_pid, device_path, opts)
|
||||
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
|
||||
doctest FarmbotFirmware
|
||||
|
||||
test "initialization (WIP)" do
|
||||
pid =
|
||||
start_supervised!({
|
||||
FarmbotFirmware,
|
||||
transport: FarmbotFirmware.StubTransport,
|
||||
side_effects: FarmbotCore.FirmwareSideEffects
|
||||
})
|
||||
def try_command(pid, cmd) do
|
||||
GenServer.call(pid, cmd, :infinity)
|
||||
end
|
||||
|
||||
# WIP
|
||||
assert is_pid(pid)
|
||||
def firmware_server do
|
||||
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
|
||||
|
|
|
@ -383,6 +383,81 @@ defmodule FarmbotFirmware.GCODETest do
|
|||
|
||||
assert "R06 Z2 Q1" =
|
||||
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
|
||||
|
||||
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)
|
||||
Mimic.copy(FarmbotFirmware.UartDefaultAdapter)
|
||||
Mimic.copy(Circuits.UART)
|
||||
Mimic.copy(File)
|
||||
ExUnit.start()
|
||||
|
|
|
@ -18,4 +18,5 @@ erl_crash.dump
|
|||
*.sqlite3
|
||||
auth_secret.exs
|
||||
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.EctoMigrator,
|
||||
expected_fw_versions: ["6.4.2.F", "6.4.2.R", "6.4.2.G"],
|
||||
default_firmware_io_logs: false,
|
||||
default_server: "https://my.farm.bot",
|
||||
default_ntp_server_1: "0.pool.ntp.org",
|
||||
|
@ -74,9 +73,7 @@ config :farmbot, FarmbotOS.System,
|
|||
system_tasks: FarmbotOS.Platform.Host.SystemTasks
|
||||
|
||||
config :farmbot, FarmbotOS.Configurator,
|
||||
data_layer: FarmbotOS.Configurator.ConfigDataLayer,
|
||||
network_layer: FarmbotOS.Configurator.FakeNetworkLayer,
|
||||
telemetry_layer: FarmbotOS.Configurator.DetsTelemetryLayer
|
||||
network_layer: FarmbotOS.Configurator.FakeNetworkLayer
|
||||
|
||||
config :farmbot, FarmbotOS.Platform.Supervisor,
|
||||
platform_children: [
|
||||
|
|
|
@ -43,4 +43,3 @@ config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FbosConfig,
|
|||
firmware_flash_attempt_threshold: 0
|
||||
|
||||
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_reset_fun(reset_fun)
|
||||
|
||||
Avrdude.MuonTrapAdapter.cmd("avrdude", args,
|
||||
MuonTrap.cmd("avrdude", args,
|
||||
into: IO.stream(:stdio, :line),
|
||||
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
|
||||
use Plug.Router
|
||||
use Plug.Debugger, otp_app: :farmbot
|
||||
alias FarmbotOS.Configurator.ConfigDataLayer
|
||||
|
||||
plug(Plug.Logger)
|
||||
plug(Plug.Static, from: {:farmbot, "priv/static"}, at: "/")
|
||||
plug(Plug.Parsers, parsers: [:urlencoded, :multipart])
|
||||
|
@ -21,13 +23,10 @@ defmodule FarmbotOS.Configurator.Router do
|
|||
plug(:match)
|
||||
plug(:dispatch)
|
||||
|
||||
@data_layer Application.get_env(:farmbot, FarmbotOS.Configurator)[:data_layer]
|
||||
@network_layer Application.get_env(:farmbot, FarmbotOS.Configurator)[
|
||||
:network_layer
|
||||
]
|
||||
@telemetry_layer Application.get_env(:farmbot, FarmbotOS.Configurator)[
|
||||
:telemetry_layer
|
||||
]
|
||||
@telemetry_layer FarmbotOS.Configurator.DetsTelemetryLayer
|
||||
|
||||
# Trigger for captive portal for various operating systems
|
||||
get("/gen_204", do: redir(conn, "/"))
|
||||
|
@ -270,6 +269,11 @@ defmodule FarmbotOS.Configurator.Router do
|
|||
get "/finish" do
|
||||
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
|
||||
%{
|
||||
"ifname" => _,
|
||||
|
@ -277,10 +281,12 @@ defmodule FarmbotOS.Configurator.Router do
|
|||
"auth_config_password" => _,
|
||||
"auth_config_server" => _
|
||||
} ->
|
||||
FarmbotCore.Logger.debug(1, "Configuration success!")
|
||||
save_config(get_session(conn))
|
||||
render_page(conn, "finish")
|
||||
|
||||
_ ->
|
||||
FarmbotCore.Logger.debug(1, "Configuration FAIL")
|
||||
redir(conn, "/")
|
||||
end
|
||||
end
|
||||
|
@ -355,23 +361,23 @@ defmodule FarmbotOS.Configurator.Router do
|
|||
end
|
||||
|
||||
defp load_last_reset_reason do
|
||||
@data_layer.load_last_reset_reason()
|
||||
ConfigDataLayer.load_last_reset_reason()
|
||||
end
|
||||
|
||||
defp load_email do
|
||||
@data_layer.load_email()
|
||||
ConfigDataLayer.load_email()
|
||||
end
|
||||
|
||||
defp load_password do
|
||||
@data_layer.load_password()
|
||||
ConfigDataLayer.load_password()
|
||||
end
|
||||
|
||||
def load_server do
|
||||
@data_layer.load_server()
|
||||
ConfigDataLayer.load_server()
|
||||
end
|
||||
|
||||
defp save_config(conf) do
|
||||
@data_layer.save_config(conf)
|
||||
ConfigDataLayer.save_config(conf)
|
||||
end
|
||||
|
||||
defp list_interfaces() do
|
||||
|
|
|
@ -18,7 +18,6 @@ defmodule FarmbotOS.SysCalls do
|
|||
alias FarmbotOS.SysCalls.{
|
||||
ChangeOwnership,
|
||||
CheckUpdate,
|
||||
DumpInfo,
|
||||
Farmware,
|
||||
FactoryReset,
|
||||
FlashFirmware,
|
||||
|
@ -52,9 +51,6 @@ defmodule FarmbotOS.SysCalls do
|
|||
@impl true
|
||||
defdelegate change_ownership(email, secret, server), to: ChangeOwnership
|
||||
|
||||
@impl true
|
||||
defdelegate dump_info(), to: DumpInfo
|
||||
|
||||
@impl true
|
||||
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
|
||||
@moduledoc false
|
||||
|
||||
require Logger
|
||||
# alias FarmbotCeleryScript.AST
|
||||
require FarmbotCore.Logger
|
||||
alias FarmbotCore.{Asset, AssetSupervisor, FarmwareRuntime}
|
||||
alias FarmbotExt.API.ImageUploader
|
||||
@farmware_timeout 30_000
|
||||
|
||||
def update_farmware(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
|
||||
receive do
|
||||
{:error, :farmware_exit} ->
|
||||
:ok
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, inspect(reason)}
|
||||
{:error, :farmware_exit} -> :ok
|
||||
{:error, reason} -> {:error, inspect(reason)}
|
||||
after
|
||||
30_000 ->
|
||||
Logger.debug("Force stopping farmware: #{inspect(farmware_runtime)}")
|
||||
FarmwareRuntime.stop(farmware_runtime)
|
||||
:ok
|
||||
@farmware_timeout -> farmware_timeout(farmware_runtime)
|
||||
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
|
||||
|
|
|
@ -32,10 +32,7 @@ defmodule FarmbotOS.SysCalls.FlashFirmware do
|
|||
{_, 0} <- Avrdude.flash(hex_file, tty, fun) do
|
||||
FarmbotCore.Logger.success(2, "Firmware flashed successfully!")
|
||||
|
||||
%{
|
||||
# firmware_hardware: package,
|
||||
firmware_path: tty
|
||||
}
|
||||
%{firmware_path: tty}
|
||||
|> Asset.update_fbos_config!()
|
||||
|> Private.mark_dirty!(%{})
|
||||
|
||||
|
@ -62,15 +59,19 @@ defmodule FarmbotOS.SysCalls.FlashFirmware do
|
|||
end
|
||||
end
|
||||
|
||||
defp find_reset_fun(_) do
|
||||
config = Application.get_env(:farmbot_firmware, FarmbotFirmware)
|
||||
defp find_reset_fun("express_k10") do
|
||||
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
|
||||
Logger.error("using reset function: #{inspect(config)}")
|
||||
{:ok, &module.reset/0}
|
||||
else
|
||||
Logger.error("no reset function is going to be used #{inspect(config)}")
|
||||
{:ok, fn -> :ok end}
|
||||
end
|
||||
defp find_reset_fun(_) do
|
||||
FarmbotCore.Logger.debug(3, "Using default reset function")
|
||||
fun = &FarmbotFirmware.NullReset.reset/0
|
||||
{:ok, fun}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -69,39 +69,10 @@ defmodule FarmbotOS.SysCalls.Movement do
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
defp do_move_absolute(x, y, z, speed, retries, errors \\ [])
|
||||
|
||||
# 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
|
||||
defp do_move_absolute(x, y, z, speed) 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),
|
||||
|
@ -120,15 +91,18 @@ defmodule FarmbotOS.SysCalls.Movement do
|
|||
{:error, "emergency_lock"}
|
||||
|
||||
{:error, reason} ->
|
||||
FarmbotCore.Logger.error(
|
||||
1,
|
||||
"Movement failed. Retrying up to #{retries} more time(s)"
|
||||
)
|
||||
handle_movement_error(reason)
|
||||
|
||||
do_move_absolute(x, y, z, speed, retries - 1, [reason | errors])
|
||||
reason ->
|
||||
handle_movement_error(reason)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_movement_error(reason) do
|
||||
msg = "Movement failed. #{inspect(reason)}"
|
||||
FarmbotCore.Logger.error(1, msg)
|
||||
end
|
||||
|
||||
def calibrate(axis) do
|
||||
axis = assert_axis!(axis)
|
||||
|
||||
|
@ -173,13 +147,6 @@ defmodule FarmbotOS.SysCalls.Movement do
|
|||
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),
|
||||
do: axis
|
||||
|
||||
|
@ -187,7 +154,6 @@ defmodule FarmbotOS.SysCalls.Movement do
|
|||
do: String.to_existing_atom(axis)
|
||||
|
||||
defp assert_axis!(axis) do
|
||||
# {:error, "unknown axis #{axis}"}
|
||||
raise("unknown axis #{axis}")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -118,7 +118,7 @@ defmodule FarmbotOS.MixProject do
|
|||
{:circuits_gpio, "~> 0.4.3", targets: @all_targets},
|
||||
{:circuits_i2c, "~> 0.3.5", 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_wifi, "~> 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"},
|
||||
"mimic": {:hex, :mimic, "1.1.3", "3bad83d5271b4faa7bbfef587417a6605cbbc802a353395d446a1e5f46fe7115", [: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_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"},
|
||||
|
@ -89,7 +89,7 @@
|
|||
"uboot_env": {:hex, :uboot_env, "0.1.1", "b01e3ec0973e99473234f27839e29e63b5b81eba6a136a18a78d049d4813d6c5", [:mix], [], "hexpm"},
|
||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "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_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"},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
defmodule FarmbotOS.Platform.Target.Configurator.Validator do
|
||||
@moduledoc """
|
||||
VintageNet.Technology that handles turning Farmbot's internal
|
||||
network representation into either a VintageNetEthernet
|
||||
VintageNet.Technology that handles turning Farmbot's internal
|
||||
network representation into either a VintageNetEthernet
|
||||
or VintageNetWiFi RawConfig.
|
||||
"""
|
||||
|
||||
|
@ -71,9 +71,8 @@ defmodule FarmbotOS.Platform.Target.Configurator.Validator do
|
|||
|
||||
defp to_ipv4(%{
|
||||
ipv4_method: "static",
|
||||
# TODO(Connor) fix nameservers
|
||||
# name_servers: name_servers,
|
||||
# domain: domain,
|
||||
name_servers: name_servers,
|
||||
domain: domain,
|
||||
ipv4_address: ipv4_address,
|
||||
ipv4_gateway: ipv4_gateway,
|
||||
ipv4_subnet_mask: ipv4_subnet_mask
|
||||
|
@ -82,7 +81,9 @@ defmodule FarmbotOS.Platform.Target.Configurator.Validator do
|
|||
method: :static,
|
||||
address: ipv4_address,
|
||||
netmask: ipv4_subnet_mask,
|
||||
gateway: ipv4_gateway
|
||||
gateway: ipv4_gateway,
|
||||
name_servers: name_servers,
|
||||
domain: domain
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ defmodule FarmbotOS.Platform.Target.Network do
|
|||
_ = maybe_hack_tzdata()
|
||||
_ = init_net_kernel()
|
||||
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
|
||||
first_connect? = is_first_connect?()
|
||||
|
||||
|
@ -135,6 +135,7 @@ defmodule FarmbotOS.Platform.Target.Network do
|
|||
end
|
||||
|
||||
vintage_net_config = to_vintage_net(config)
|
||||
FarmbotCore.Logger.info(3, inspect(vintage_net_config))
|
||||
|
||||
FarmbotTelemetry.event(:network, :interface_configure, nil,
|
||||
interface: ifname
|
||||
|
@ -260,7 +261,7 @@ defmodule FarmbotOS.Platform.Target.Network do
|
|||
""")
|
||||
|
||||
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
|
||||
""")
|
||||
|
||||
|
@ -270,11 +271,11 @@ defmodule FarmbotOS.Platform.Target.Network do
|
|||
def handle_info({VintageNet, property, old, new, _meta}, state) do
|
||||
Logger.debug("""
|
||||
Unknown property change: #{inspect(property)}
|
||||
old:
|
||||
old:
|
||||
|
||||
#{inspect(old, limit: :infinity)}
|
||||
|
||||
new:
|
||||
new:
|
||||
|
||||
#{inspect(new, limit: :infinity)}
|
||||
""")
|
||||
|
@ -284,12 +285,12 @@ defmodule FarmbotOS.Platform.Target.Network do
|
|||
|
||||
def handle_info({:network_not_found_timer, minutes}, state) do
|
||||
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.
|
||||
""")
|
||||
|
||||
FarmbotOS.System.factory_reset("""
|
||||
Farmbot has been disconnected from the network for
|
||||
Farmbot has been disconnected from the network for
|
||||
#{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>
|
||||
<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="">
|
||||
|
||||
<label for="ntp_server_1"> NTP Pool Server 1 </label>
|
||||
|
|
|
@ -5,10 +5,23 @@ defmodule FarmbotOs.AvrdudeTest do
|
|||
|
||||
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
|
||||
File.touch("/tmp/wow")
|
||||
|
||||
expect(Avrdude.MuonTrapAdapter, :cmd, fn cmd, args, opts ->
|
||||
expect(MuonTrap, :cmd, 1, fn cmd, args, opts ->
|
||||
assert cmd == "avrdude"
|
||||
|
||||
assert args == [
|
||||
|
@ -35,4 +48,35 @@ defmodule FarmbotOs.AvrdudeTest do
|
|||
"YOLO"
|
||||
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
|
||||
|
|
|
@ -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
|
||||
alias FarmbotOS.Configurator.Router
|
||||
alias FarmbotOS.Configurator.ConfigDataLayer
|
||||
|
||||
use ExUnit.Case, async: true
|
||||
use Plug.Test
|
||||
|
||||
|
@ -7,6 +9,30 @@ defmodule FarmbotOS.Configurator.RouterTest do
|
|||
setup :verify_on_exit!
|
||||
|
||||
@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
|
||||
FarmbotOS.Configurator.ConfigDataLayer
|
||||
|
@ -19,19 +45,25 @@ defmodule FarmbotOS.Configurator.RouterTest do
|
|||
assert conn.resp_body =~ "whoops!"
|
||||
end
|
||||
|
||||
test "redirect to index" do
|
||||
FarmbotOS.Configurator.ConfigDataLayer
|
||||
|> expect(:load_last_reset_reason, fn -> nil end)
|
||||
test "redirects" do
|
||||
redirects = [
|
||||
"/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")
|
||||
conn = Router.call(conn, @opts)
|
||||
redir = redirected_to(conn)
|
||||
assert redir == "/"
|
||||
|
||||
conn = conn(:get, redir)
|
||||
conn = Router.call(conn, @opts)
|
||||
redir = redirected_to(conn)
|
||||
assert redir == "/network"
|
||||
Enum.map(redirects, fn path ->
|
||||
conn = conn(:get, path)
|
||||
conn = Router.call(conn, @opts)
|
||||
redir = redirected_to(conn)
|
||||
assert redir == "/"
|
||||
end)
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
# 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"
|
||||
test "/scheduler_debugger" do
|
||||
kon = get_con("/scheduler_debugger")
|
||||
assert String.contains?(kon.resp_body, "scheduler_debugger.js")
|
||||
assert String.contains?(kon.resp_body, "<title>Scheduler Debugger</title>")
|
||||
end
|
||||
|
||||
defp redirected_to(conn, status) when is_atom(status) do
|
||||
redirected_to(conn, Plug.Conn.Status.code(status))
|
||||
test "/logger" do
|
||||
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
|
||||
|
||||
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"
|
||||
test "/api/telemetry/cpu_usage" do
|
||||
{:ok, json} = Jason.decode(get_con("/api/telemetry/cpu_usage").resp_body)
|
||||
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
|
||||
|
||||
defp redirected_to(conn, status) do
|
||||
raise "expected redirection with status #{status}, got: #{conn.status}"
|
||||
test "/finish" do
|
||||
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
|
||||
|
|
|
@ -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)
|
||||
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)
|
||||
Mimic.copy(FarmbotCore.Asset)
|
||||
Mimic.copy(FarmbotFirmware)
|
||||
Mimic.copy(FarmbotOS.Configurator.ConfigDataLayer)
|
||||
Mimic.copy(FarmbotOS.Configurator.FakeNetworkLayer)
|
||||
Mimic.copy(FarmbotOS.Configurator.DetsTelemetryLayer)
|
||||
Mimic.copy(Avrdude.MuonTrapAdapter)
|
||||
Mimic.copy(FarmbotOS.Configurator.FakeNetworkLayer)
|
||||
Mimic.copy(File)
|
||||
Mimic.copy(MuonTrap)
|
||||
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