Add ability to plug/unplug arduino without disaster
parent
5bc7955488
commit
2eddf23878
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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, [
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}"
|
||||
|
|
5
mix.exs
5
mix.exs
|
@ -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
|
||||
|
||||
|
|
|
@ -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", []},
|
||||
|
|
|
@ -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", []},
|
||||
|
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue