Add ability to plug/unplug arduino without disaster

pull/363/head
connor rigby 2017-11-20 12:16:46 -08:00
parent 5bc7955488
commit 2eddf23878
11 changed files with 168 additions and 14 deletions

View File

@ -6,8 +6,8 @@ env = Mix.env()
config :logger,
utc_log: true,
handle_otp_reports: true,
handle_sasl_reports: true,
# handle_otp_reports: true,
# handle_sasl_reports: true,
backends: []
config :elixir, ansi_enabled: true

View File

@ -15,7 +15,8 @@ config :farmbot, data_path: "tmp/"
config :farmbot, :init, [
Farmbot.Host.Bootstrap.Configurator,
Farmbot.Host.TargetConfiguratorTest.Supervisor,
Farmbot.System.Debug
Farmbot.System.Udev,
Farmbot.System.Debug.Supervisor
]
# Transports.
@ -46,7 +47,7 @@ config :farmbot, Farmbot.System.ConfigStorage,
config :farmbot, :behaviour,
authorization: Farmbot.Bootstrap.Authorization,
system_tasks: Farmbot.Host.SystemTasks,
update_handler: Farmbot.Host.UpdateHandler,
firmware_handler: Farmbot.Firmware.UartHandler
update_handler: Farmbot.Host.UpdateHandler
# firmware_handler: Farmbot.Firmware.UartHandler
config :farmbot, :uart_handler, tty: "/dev/ttyACM0"

View File

@ -39,7 +39,8 @@ config :farmbot, :init, [
Farmbot.Target.Network.WaitForTime,
# Debug stuff
Farmbot.System.Debug
Farmbot.System.Debug,
Farmbot.Target.Uevent.Supervisor
]
config :farmbot, :transport, [

View File

@ -124,6 +124,10 @@ defmodule Farmbot.Firmware do
end
def handle_info({:EXIT, _pid, :normal}, state) do
{:stop, :normal, state}
end
def handle_info({:EXIT, _, reason}, state) do
Logger.error 1, "Firmware handler: #{state.handler_mod} died: #{inspect reason}"
{:ok, handler} = state.handler_mod.start_link()

View File

@ -126,9 +126,17 @@ defmodule Farmbot.Firmware.UartHandler do
)
end
def terminate(reason, state) do
if state.nerves do
UART.close(state.nerves)
UART.stop(reason)
end
end
# if there is an error, we assume something bad has happened, and we probably
# Are better off crashing here, and being restarted.
def handle_info({:nerves_uart, _, {:error, :eio}}, state) do
Logger.error 1, "UART device removed."
old_env = Application.get_env(:farmbot, :behaviour)
new_env = Keyword.put(old_env, :firmware_handler, Farmbot.Firmware.StubHandler)
Application.put_env(:farmbot, :behaviour, new_env)
@ -145,6 +153,19 @@ defmodule Farmbot.Firmware.UartHandler do
{:noreply, [], state}
end
def handle_info({:nerves_uart, _, {_, {:report_software_version, v}}}, state) do
expected = Application.get_env(:farmbot, :expected_fw_versions)
if v in expected do
{:noreply, [{:report_software_version, v}], state}
else
Logger.error 1, "Firmware version #{v} is not in expected versions: #{inspect expected}"
old_env = Application.get_env(:farmbot, :behaviour)
new_env = Keyword.put(old_env, :firmware_handler, Farmbot.Firmware.StubHandler)
Application.put_env(:farmbot, :behaviour, new_env)
{:stop, :normal, state}
end
end
def handle_info({:nerves_uart, _, {:echo, _}}, %{current_cmd: nil} = state) do
{:noreply, [], state}
end

View File

@ -25,6 +25,7 @@ defmodule Farmbot.Firmware.UartHandler.Update do
:ok = Nerves.UART.configure(uart, opts)
Logger.busy 3, "Waiting for firmware idle report."
do_fw_loop(uart, tty, :idle, hardware)
close(uart)
{:error, reason} ->
Logger.error 1, "Failed to connect to firmware for update: #{inspect reason}"
end
@ -34,7 +35,6 @@ defmodule Farmbot.Firmware.UartHandler.Update do
receive do
{:nerves_uart, _, {:error, reason}} ->
Logger.error 1, "Failed to connect to firmware for update during idle step: #{inspect reason}"
close(uart, tty)
{:nerves_uart, _, data} ->
if String.contains?(data, "R00") do
case flag do
@ -62,7 +62,6 @@ defmodule Farmbot.Firmware.UartHandler.Update do
receive do
{:nerves_uart, _, {:error, reason}} ->
Logger.error 1, "Failed to connect to firmware for update: #{inspect reason}"
close(uart, tty)
{:nerves_uart, _, data} ->
case String.split(data, "R83 ") do
[_] ->
@ -106,16 +105,17 @@ defmodule Farmbot.Firmware.UartHandler.Update do
avrdude("#{:code.priv_dir(:farmbot)}/arduino-firmware.hex", uart, tty)
end
defp close(uart, _tty) do
defp close(uart) do
if Process.alive?(uart) do
Nerves.UART.close(uart)
Nerves.UART.stop(uart)
close = Nerves.UART.close(uart)
stop = Nerves.UART.stop(uart)
Logger.warn 3, "CLOSE: #{inspect close} STOP: #{stop}"
Process.sleep(500) # to allow the FD to be closed.
end
end
def avrdude(fw_file, uart, tty) do
close(uart, tty)
close(uart)
case System.cmd("avrdude", ~w"-q -q -patmega2560 -cwiring -P#{tty} -b115200 -D -V -Uflash:w:#{fw_file}:i", [stderr_to_stdout: true, into: IO.stream(:stdio, :line)]) do
{_, 0} -> Logger.success 1, "Firmware flashed!"
{_, err_code} -> Logger.error 1, "Failed to flash Firmware! #{err_code}"

View File

@ -97,7 +97,7 @@ defmodule Farmbot.Mixfile do
{:wobserver, "~> 0.1.8"},
{:joken, "~> 1.1"},
{:socket, "~> 0.3"},
{:amqp, "~> 1.0.0-pre.2"}
{:amqp, "~> 1.0.0-pre.2"},
]
end
@ -108,7 +108,8 @@ defmodule Farmbot.Mixfile do
{:inch_ex, ">= 0.0.0", only: :dev},
{:excoveralls, "~> 0.6", only: :test},
{:mock, "~> 0.2.0", only: :test},
{:faker, "~> 0.9", only: :test }
{:faker, "~> 0.9", only: :test },
{:udev, github: "electricshaman/udev"}
]
end

View File

@ -58,6 +58,7 @@
"system_registry": {:hex, :system_registry, "0.6.0", "31642177e6002d3cff2ada3553ed4e9c0a6ca015797d62d7d17c0ab8696185fc", [], [], "hexpm"},
"timex": {:hex, :timex, "3.1.24", "d198ae9783ac807721cca0c5535384ebdf99da4976be8cefb9665a9262a1e9e3", [], [{:combine, "~> 0.7", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
"tzdata": {:hex, :tzdata, "0.1.201605", "0c4184819b9d6adedcc02107b68321c45d8e853def7a32629b7961b9f2e95f33", [], [], "hexpm"},
"udev": {:git, "https://github.com/electricshaman/udev.git", "ea741259f40dab45f16adcef6db49ce3bcb4dd05", []},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [], [], "hexpm"},
"uuid": {:hex, :uuid, "1.1.7", "007afd58273bc0bc7f849c3bdc763e2f8124e83b957e515368c498b641f7ab69", [], [], "hexpm"},
"vmq_commons": {:git, "https://github.com/farmbot-labs/vmq_commons.git", "e780a297d5807a3537723d590036d37abce07cc5", []},

View File

@ -58,6 +58,7 @@
"system_registry": {:hex, :system_registry, "0.6.0", "31642177e6002d3cff2ada3553ed4e9c0a6ca015797d62d7d17c0ab8696185fc", [], [], "hexpm"},
"timex": {:hex, :timex, "3.1.24", "d198ae9783ac807721cca0c5535384ebdf99da4976be8cefb9665a9262a1e9e3", [], [{:combine, "~> 0.7", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
"tzdata": {:hex, :tzdata, "0.1.201605", "0c4184819b9d6adedcc02107b68321c45d8e853def7a32629b7961b9f2e95f33", [], [], "hexpm"},
"udev": {:git, "https://github.com/electricshaman/udev.git", "ea741259f40dab45f16adcef6db49ce3bcb4dd05", []},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [], [], "hexpm"},
"uuid": {:hex, :uuid, "1.1.7", "007afd58273bc0bc7f849c3bdc763e2f8124e83b957e515368c498b641f7ab69", [], [], "hexpm"},
"vmq_commons": {:git, "https://github.com/farmbot-labs/vmq_commons.git", "e780a297d5807a3537723d590036d37abce07cc5", []},

View File

@ -0,0 +1,53 @@
defmodule Farmbot.System.UdevSupervisor do
@moduledoc false
use Supervisor
def start_link do
Supervisor.start_link(__MODULE__, [], name: __MODULE__)
end
def init([]) do
children = [
worker(Farmbot.System.Udev, [])
]
supervise(children, [strategy: :one_for_one])
end
end
defmodule Farmbot.System.Udev do
use GenServer
use Farmbot.Logger
def start_link do
GenServer.start_link(__MODULE__, [], [name: __MODULE__])
end
def init([]) do
# {:ok, udev} = Udev.Monitor.start_link self(), :"#{__MODULE__}-:udev"
{:ok, udev} = Udev.Monitor.start_link self(), :udev
{:ok, %{udev: udev}}
end
def terminate(_reason, state) do
if Process.alive?(state.udev) do
Udev.Monitor.stop(state.udev)
end
end
def handle_info({:udev, %Udev.Device{action: :add, devnode: tty, subsystem: "tty"}}, state) do
Logger.busy 3, "Detected new UART Device: #{tty}"
Application.put_env(:farmbot, :uart_handler, tty: tty)
old_env = Application.get_env(:farmbot, :behaviour)
if old_env[:firmware_handler] == Farmbot.Firmware.StubHandler do
new_env = Keyword.put(old_env, :firmware_handler, Farmbot.Firmware.UartHandler)
Application.put_env(:farmbot, :behaviour, new_env)
GenServer.stop(Farmbot.Firmware, :shutdown)
end
{:noreply, state}
end
def handle_info({:udev, _msg}, state) do
{:noreply, state}
end
end

View File

@ -0,0 +1,71 @@
defmodule Farmbot.Target.Uevent.Supervisor do
@moduledoc false
use Supervisor
def start_link(_,_) do
Supervisor.start_link(__MODULE__, [], [name: __MODULE__])
end
def init([]) do
children = [worker(Farmbot.Target.Uevent, [])]
supervise(children, [strategy: :one_for_one])
end
end
defmodule Farmbot.Target.Uevent do
@moduledoc false
use GenServer
use Farmbot.Logger
def start_link do
GenServer.start_link(__MODULE__, [], [name: __MODULE__])
end
def init([]) do
executable = :code.priv_dir(:nerves_runtime) ++ '/uevent'
port = Port.open({:spawn_executable, executable},
[{:args, []},
{:packet, 2},
:use_stdio,
:binary,
:exit_status])
{:ok, %{port: port}}
end
def handle_info({_, {:data, <<?n, message::binary>>}}, s) do
msg = :erlang.binary_to_term(message)
handle_port(msg, s)
end
defp handle_port({:uevent, _uevent, kv}, s) do
event =
Enum.reduce(kv, %{}, fn (str, acc) ->
[k, v] = String.split(str, "=", parts: 2)
k = String.downcase(k)
Map.put(acc, k, v)
end)
case Map.get(event, "devpath", "") do
"/devices" <> _path -> handle_uevent(event)
_ -> :noop
end
{:noreply, s}
end
defp handle_uevent(%{"action" => "add", "subsystem" => "tty", "devname" => tty}) do
Logger.busy 3, "Detected new UART Device: #{tty}"
Application.put_env(:farmbot, :uart_handler, tty: "/dev/" <> tty)
old_env = Application.get_env(:farmbot, :behaviour)
if old_env[:firmware_handler] == Farmbot.Firmware.StubHandler do
new_env = Keyword.put(old_env, :firmware_handler, Farmbot.Firmware.UartHandler)
Application.put_env(:farmbot, :behaviour, new_env)
if Process.whereis(Farmbot.Firmware) do
GenServer.stop(Farmbot.Firmware, :shutdown)
end
end
end
defp handle_uevent(_action), do: :ok
end