Implement and test Enigmas for firmware_missing
* Test for create_or_update_enigma!() and clear_enigma()pull/763/head
parent
9dc6450b61
commit
4c5a093e2c
|
@ -14,9 +14,18 @@ defmodule FarmbotCore.Asset.Private do
|
|||
import Ecto.Changeset, warn: false
|
||||
|
||||
@doc "Creates a new Enigma."
|
||||
def new_enigma(params) do
|
||||
Enigma.changeset(%Enigma{}, params)
|
||||
|> Repo.insert()
|
||||
def create_or_update_enigma!(params) do
|
||||
enigma = if problem_tag = params[:problem_tag] do
|
||||
find_enigma_by_problem_tag(problem_tag) || %Enigma{}
|
||||
else
|
||||
%Enigma{}
|
||||
end
|
||||
|
||||
Enigma.changeset(enigma, params) |> Repo.insert_or_update!()
|
||||
end
|
||||
|
||||
def find_enigma_by_problem_tag(problem_tag) do
|
||||
Repo.get_by(Enigma, problem_tag: problem_tag)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@ -24,13 +33,12 @@ defmodule FarmbotCore.Asset.Private do
|
|||
problem_tag.
|
||||
"""
|
||||
def clear_enigma(problem_tag) do
|
||||
Repo.get_by(problem_tag: problem_tag)
|
||||
|> case do
|
||||
nil -> :ok
|
||||
%Enigma{} = enigma ->
|
||||
Repo.delete!(enigma)
|
||||
:ok
|
||||
end
|
||||
case find_enigma_by_problem_tag(problem_tag) do
|
||||
nil -> :ok
|
||||
%Enigma{} = enigma ->
|
||||
Repo.delete!(enigma)
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Lists `module` objects that still need to be POSTed to the API."
|
||||
|
|
|
@ -10,12 +10,14 @@ defmodule FarmbotCore.Asset.Private.Enigma do
|
|||
schema "enigmas" do
|
||||
field(:priority, :integer)
|
||||
field(:problem_tag, :string)
|
||||
|
||||
field(:monitor, :boolean, defualt: true)
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def changeset(enigma, params) do
|
||||
enigma
|
||||
|> cast(params, [:priority, :problem_tag])
|
||||
|> cast(params, [:priority, :problem_tag, :monitor])
|
||||
|> validate_required([:priority, :problem_tag])
|
||||
end
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ defmodule FarmbotCore.Config.Repo.Migrations.AddEnigmasTable do
|
|||
add(:local_id, :binary_id, primary_key: true)
|
||||
add(:problem_tag, :string)
|
||||
add(:priority, :integer)
|
||||
|
||||
add(:monitor, :boolean)
|
||||
timestamps(inserted_at: :created_at, type: :utc_datetime)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
alias FarmbotCore.Asset.Private
|
||||
|
||||
Private.new_enigma(%{priority: 100, problem_tag: "firmware.missing"})
|
|
@ -0,0 +1,34 @@
|
|||
defmodule FarmbotCore.Asset.PrivateTest do
|
||||
use ExUnit.Case, async: true
|
||||
alias FarmbotCore.Asset.{Private, Repo, Private.Enigma}
|
||||
|
||||
import Farmbot.TestSupport.AssetFixtures,
|
||||
only: [
|
||||
enigma: 0
|
||||
]
|
||||
|
||||
describe "enigmas" do
|
||||
test "create_or_update_enigma!() returns :ok" do
|
||||
result = enigma()
|
||||
assert result.priority == 100
|
||||
assert result.problem_tag == "firmware.missing"
|
||||
assert result.created_at
|
||||
|
||||
result2 =
|
||||
Private.create_or_update_enigma!(%{
|
||||
problem_tag: result.problem_tag,
|
||||
priority: 50
|
||||
})
|
||||
|
||||
assert result.local_id == result2.local_id
|
||||
assert result2.priority == 50
|
||||
end
|
||||
|
||||
test "clear_enigma() clears out enigmas by problem_tag" do
|
||||
enigma1 = enigma()
|
||||
assert Repo.get_by(Enigma, problem_tag: enigma1.problem_tag)
|
||||
Private.clear_enigma(enigma1.problem_tag)
|
||||
refute Repo.get_by(Enigma, problem_tag: enigma1.problem_tag)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -171,8 +171,8 @@ defmodule FarmbotFirmware do
|
|||
@doc """
|
||||
Opens the transport,
|
||||
"""
|
||||
def open_transport(server \\ __MODULE__) do
|
||||
GenServer.call(server, :open_transport)
|
||||
def open_transport(server \\ __MODULE__, module, args \\ []) do
|
||||
GenServer.call(server, {:open_transport, module, args})
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@ -295,12 +295,19 @@ defmodule FarmbotFirmware do
|
|||
{:reply, {:error, s}, state}
|
||||
end
|
||||
|
||||
def handle_call(:open_transport, _from, %{status: s} = state) when s == :transport_boot do
|
||||
def handle_call({:open_transport, module, args}, _from, %{status: s} = state)
|
||||
when s == :transport_boot do
|
||||
# Add an anon function that transport implementations should call.
|
||||
fw = self()
|
||||
fun = fn {_, _} = code -> GenServer.cast(fw, code) end
|
||||
transport_args = Keyword.put(args, :handle_gcode, fun)
|
||||
next_state = %{state | transport: module, transport_args: transport_args}
|
||||
|
||||
send(self(), :timeout)
|
||||
{:reply, :ok, state}
|
||||
{:reply, :ok, next_state}
|
||||
end
|
||||
|
||||
def handle_call(:open_transport, _from, %{status: s} = state) do
|
||||
def handle_call({:open_transport, _module, _args}, _from, %{status: s} = state) do
|
||||
{:reply, {:error, s}, state}
|
||||
end
|
||||
|
||||
|
|
|
@ -9,8 +9,6 @@ defmodule FarmbotOS.FirmwareTTYDetector do
|
|||
alias FarmbotCore.FirmwareSideEffects
|
||||
alias FarmbotCore.{Asset, BotState}
|
||||
|
||||
import FarmbotCore.Config, only: [get_config_value: 3, update_config_value: 4]
|
||||
|
||||
@expected_names Application.get_env(:farmbot, __MODULE__)[:expected_names]
|
||||
@expected_names ||
|
||||
Mix.raise("""
|
||||
|
@ -20,135 +18,44 @@ defmodule FarmbotOS.FirmwareTTYDetector do
|
|||
expected_names: ["ttyS0", "ttyNotReal"]
|
||||
""")
|
||||
|
||||
@error_ms 5000
|
||||
def tty(server \\ __MODULE__) do
|
||||
GenServer.call(server, :tty)
|
||||
end
|
||||
|
||||
def start_link(args) do
|
||||
GenServer.start_link(__MODULE__, args, name: __MODULE__)
|
||||
def tty!(server \\ __MODULE__) do
|
||||
tty(server) || raise "No TTY detected"
|
||||
end
|
||||
|
||||
def start_link(args, opts \\ [name: __MODULE__]) do
|
||||
GenServer.start_link(__MODULE__, args, opts)
|
||||
end
|
||||
|
||||
def init([]) do
|
||||
{:ok, %{device: nil, open: false, version: nil}, 0}
|
||||
{:ok, nil, 0}
|
||||
end
|
||||
|
||||
def terminate(_, _) do
|
||||
System.cmd("killall", ["-9", "avrdude"], into: IO.stream(:stdio, :line))
|
||||
def handle_call(:tty, _, detected_tty) do
|
||||
{:reply, detected_tty, detected_tty}
|
||||
end
|
||||
|
||||
def handle_info(:timeout, %{device: nil} = state) do
|
||||
case get_config_value(:string, "settings", "firmware_hardware") do
|
||||
nil ->
|
||||
{:noreply, state, @error_ms}
|
||||
def handle_info(:timeout, state) do
|
||||
enumerated = UART.enumerate() |> Map.to_list()
|
||||
{:noreply, state, {:continue, enumerated}}
|
||||
end
|
||||
|
||||
_hw ->
|
||||
available = UART.enumerate() |> Map.to_list()
|
||||
{:noreply, state, {:continue, available}}
|
||||
def handle_continue([{name, _} | rest], state) do
|
||||
if farmbot_tty?(name) do
|
||||
{:noreply, name}
|
||||
else
|
||||
{:noreply, state, {:continue, rest}}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(:timeout, %{device: _device, open: false} = state) do
|
||||
case get_config_value(:bool, "settings", "firmware_needs_flash") do
|
||||
true -> handle_flash(state)
|
||||
false -> handle_open(state)
|
||||
end
|
||||
def handle_continue([], state) do
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info(:timeout, %{device: _device, open: true, version: nil} = state) do
|
||||
case FarmbotFirmware.request({:software_version_read, []}) do
|
||||
{:ok, {_, {:report_software_version, [version]}}} ->
|
||||
{:noreply, %{state | version: version}}
|
||||
|
||||
_ ->
|
||||
{:noreply, state, 5000}
|
||||
end
|
||||
defp farmbot_tty?(file_path) do
|
||||
file_path in @expected_names
|
||||
end
|
||||
|
||||
def handle_info({:DOWN, _ref, :process, _pid, reason}, state) do
|
||||
{:stop, reason, state}
|
||||
end
|
||||
|
||||
def handle_continue([{name, _} | _rest], %{device: nil} = state)
|
||||
when name in @expected_names do
|
||||
FarmbotCore.Logger.debug(3, "Found tty: #{name}")
|
||||
{:noreply, %{state | device: name}, 0}
|
||||
end
|
||||
|
||||
def handle_continue([{name, _} | rest], %{device: nil} = state) do
|
||||
FarmbotCore.Logger.debug(3, "#{name} not a valid Farmbot tty")
|
||||
{:noreply, state, {:continue, rest}}
|
||||
end
|
||||
|
||||
def handle_continue([], %{device: nil} = state) do
|
||||
{:noreply, state, @error_ms}
|
||||
end
|
||||
|
||||
defp handle_flash(state) do
|
||||
dir = Application.app_dir(:farmbot, ["priv"])
|
||||
|
||||
case get_config_value(:string, "settings", "firmware_hardware") do
|
||||
"arduino" ->
|
||||
flash_fw(Path.join(dir, "arduino_firmware.hex"), state)
|
||||
|
||||
"farmduino" ->
|
||||
flash_fw(Path.join(dir, "farmduino.hex"), state)
|
||||
|
||||
"farmduino_k14" ->
|
||||
flash_fw(Path.join(dir, "farmduino_k14.hex"), state)
|
||||
|
||||
nil ->
|
||||
{:noreply, state, @error_ms}
|
||||
|
||||
other ->
|
||||
Logger.error("Unknown arduino firmware #{other}")
|
||||
{:stop, {:unknown_firmware, other}, state}
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_open(state) do
|
||||
opts = [
|
||||
device: state.device,
|
||||
transport: UARTTransport,
|
||||
side_effects: FirmwareSideEffects
|
||||
]
|
||||
|
||||
child_spec = {Farmbot.Firmware, opts}
|
||||
|
||||
case Supervisor.start_child(FirmwareSupervisor, child_spec) do
|
||||
{:ok, pid} ->
|
||||
# This might cause some sort of race condition.
|
||||
hw = get_config_value(:string, "settings", "firmware_hardware")
|
||||
|
||||
Asset.update_fbos_config!(%{
|
||||
firmware_path: state.device,
|
||||
firmware_hardware: hw
|
||||
})
|
||||
|
||||
:ok = BotState.set_config_value(:firmware_hardware, hw)
|
||||
Process.monitor(pid)
|
||||
{:noreply, %{state | open: true, version: nil}, 5000}
|
||||
|
||||
error ->
|
||||
{:stop, error, state}
|
||||
end
|
||||
end
|
||||
|
||||
defp flash_fw(fw_file, state) do
|
||||
args =
|
||||
~w"-q -q -patmega2560 -cwiring -P#{dev(state.device)} -b115200 -D -V -Uflash:w:#{fw_file}:i"
|
||||
|
||||
opts = [stderr_to_stdout: true, into: IO.stream(:stdio, :line)]
|
||||
res = System.cmd("avrdude", args, opts)
|
||||
|
||||
case res do
|
||||
{_, 0} ->
|
||||
update_config_value(:bool, "settings", "firmware_needs_flash", false)
|
||||
{:noreply, state, 0}
|
||||
|
||||
_ ->
|
||||
Logger.error("firmware flash failed")
|
||||
{:noreply, state, @error_ms}
|
||||
end
|
||||
end
|
||||
|
||||
defp dev("/dev/" <> _ = device), do: IO.inspect(device, label: "DEVICE")
|
||||
defp dev("tty" <> _ = dev), do: IO.inspect(Path.join("/dev", dev), label: "DEVICE")
|
||||
end
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
defmodule FarmbotOS.Init.EnigmaFirmwareMissing do
|
||||
alias FarmbotCore.Asset.Private
|
||||
alias FarmbotCore.EnigmaHandler
|
||||
alias FarmbotFirmware.UARTTransport
|
||||
|
||||
require FarmbotCore.Logger
|
||||
|
||||
def setup() do
|
||||
EnigmaHandler.register_up("firmware.missing", &enigma_up/1)
|
||||
EnigmaHandler.register_down("firmware.missing", &enigma_down/1)
|
||||
|
||||
needs_flash? =
|
||||
FarmbotCore.Config.get_config_value(:string, "settings", "firmware_needs_flash")
|
||||
|
||||
firmware_hardware = FarmbotCore.Asset.fbos_config(:firmware_hardware)
|
||||
situation = {needs_flash?, firmware_hardware}
|
||||
|
||||
case situation do
|
||||
{true, firmware_hardware} when is_binary(firmware_hardware) ->
|
||||
FarmbotCore.Logger.warn(1, "firmware needs flashed- creating `firmware.missing` enigma")
|
||||
Private.create_or_update_enigma!(%{priority: 100, problem_tag: "firmware.missing"})
|
||||
|
||||
# Ignore fw/hw
|
||||
FarmbotCore.Asset.update_fbos_config!(%{
|
||||
firmware_hardware: nil,
|
||||
firmware_path: nil
|
||||
})
|
||||
|
||||
:ok
|
||||
|
||||
{false, firmware_hardware} when is_binary(firmware_hardware) ->
|
||||
swap_transport(FarmbotCore.Asset.fbos_config(:firmware_path))
|
||||
|
||||
{_, nil} ->
|
||||
FarmbotCore.Logger.warn(1, "firmware needs flashed- creating `firmware.missing` enigma")
|
||||
Private.create_or_update_enigma!(%{priority: 100, problem_tag: "firmware.missing"})
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
def enigma_up(_) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def enigma_down(_) do
|
||||
swap_transport(FarmbotCore.Asset.fbos_config(:firmware_path))
|
||||
end
|
||||
|
||||
def swap_transport(tty) do
|
||||
# Swap transport on FW module.
|
||||
# Close tranpsort if it is open currently.
|
||||
_ = FarmbotFirmware.close_transport()
|
||||
:ok = FarmbotFirmware.open_transport(UARTTransport, device: tty)
|
||||
end
|
||||
end
|
|
@ -2,15 +2,27 @@ defmodule FarmbotOS.SysCalls.FlashFirmware do
|
|||
alias FarmbotFirmware
|
||||
|
||||
def flash_firmware(package) do
|
||||
FarmbotCore.Asset.Private.clear_enigma("firmware.missing")
|
||||
hex_file = find_hex_file(package)
|
||||
tty = find_tty()
|
||||
Avrdude.flash_firmware(hex_file, "?")
|
||||
raise package
|
||||
end
|
||||
|
||||
defp find_tty() do
|
||||
File.ls("/dev/ttylol*")
|
||||
tty =
|
||||
FarmbotOS.FirmwareTTYDetector.tty() ||
|
||||
raise """
|
||||
Expected a tty to exist, but none was found.
|
||||
"""
|
||||
|
||||
case Avrdude.flash_firmware(hex_file, tty) do
|
||||
{_, 0} ->
|
||||
FarmbotCore.Asset.update_fbos_config!(%{
|
||||
firmware_hardware: package,
|
||||
firmware_path: tty
|
||||
})
|
||||
|
||||
FarmbotCore.Asset.Private.clear_enigma("firmware.missing")
|
||||
:ok
|
||||
|
||||
_ ->
|
||||
{:error, "avrdude_failure"}
|
||||
end
|
||||
end
|
||||
|
||||
defp find_hex_file("arduino") do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
defmodule Farmbot.TestSupport.AssetFixtures do
|
||||
alias FarmbotCore.Asset
|
||||
alias FarmbotCore.Asset.{Repo, FarmEvent, FbosConfig, Regimen, Sequence}
|
||||
alias FarmbotCore.Asset.{Repo, FarmEvent, FbosConfig, Regimen, Sequence, Private}
|
||||
|
||||
def persistent_regimen(regimen_params, farm_event_params, params \\ %{}) do
|
||||
regimen = regimen(regimen_params)
|
||||
|
@ -92,4 +92,12 @@ defmodule Farmbot.TestSupport.AssetFixtures do
|
|||
|> FarmEvent.changeset(params)
|
||||
|> Repo.insert!()
|
||||
end
|
||||
|
||||
def enigma() do
|
||||
Private.create_or_update_enigma!(%{
|
||||
problem_tag: "firmware.missing",
|
||||
priority: 100,
|
||||
monitor: false
|
||||
})
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue