Implement and test Enigmas for firmware_missing

* Test for create_or_update_enigma!() and clear_enigma()
pull/763/head
Rick Carlino 2019-03-20 15:04:23 -05:00 committed by Connor Rigby
parent 9dc6450b61
commit 4c5a093e2c
No known key found for this signature in database
GPG Key ID: 29A88B24B70456E0
10 changed files with 177 additions and 145 deletions

View File

@ -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."

View File

@ -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

View File

@ -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

View File

@ -1,3 +0,0 @@
alias FarmbotCore.Asset.Private
Private.new_enigma(%{priority: 100, problem_tag: "firmware.missing"})

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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