bring back fw flashing

pull/363/head
connor rigby 2017-11-16 13:47:36 -08:00
parent f991a2be2d
commit d36116eb3d
8 changed files with 186 additions and 70 deletions

View File

@ -45,7 +45,7 @@ config :farmbot, :behaviour,
config :farmbot, :farmware,
first_part_farmware_manifest_url: "https://raw.githubusercontent.com/FarmBot-Labs/farmware_manifests/master/manifest.json"
config :farmbot, expected_fw_version: ["5.0.3.F", "5.0.3.R"]
config :farmbot, expected_fw_versions: ["5.0.7.F", "5.0.7.R"]
case target do
"host" ->

View File

@ -46,7 +46,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

@ -24,6 +24,22 @@ defmodule Farmbot.CeleryScript.AST.Node.ConfigUpdate do
defp do_reduce_os([%{args: %{label: key, value: value}} | rest], env) do
Logger.busy 2, "Updating: #{inspect key}: #{value}"
case lookup_os_config(key, value) do
{:ok, {:string, "settings", val}} when val in ["farmduino", "arduino"] ->
Farmbot.System.ConfigStorage.update_config_value(:string, "settings", key, val)
if Application.get_env(:farmbot, :behaviour)[:firmware_handler] == Farmbot.Firmware.UartHandler do
Logger.warn 1, "Updating #{val} firmware."
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)
GenServer.stop(Farmbot.Firmware, :shutdown)
Farmbot.Firmware.UartHandler.Update.maybe_update_firmware(val)
Application.put_env(:farmbot, :behaviour, old_env)
GenServer.stop(Farmbot.Firmware, :shutdown)
end
do_reduce_os(rest, env)
{:ok, {type, group, value}} ->
Farmbot.System.ConfigStorage.update_config_value(type, group, key, value)
do_reduce_os(rest, env)

View File

@ -109,14 +109,17 @@ defmodule Farmbot.Firmware do
handler_mod =
Application.get_env(:farmbot, :behaviour)[:firmware_handler] || raise("No fw handler.")
{:ok, handler} = handler_mod.start_link()
Process.flag(:trap_exit, true)
case handler_mod.start_link() do
{:ok, handler} ->
Process.flag(:trap_exit, true)
{
:producer_consumer,
%State{handler: handler, handler_mod: handler_mod},
subscribe_to: [handler], dispatcher: GenStage.BroadcastDispatcher
}
{:stop, err, state} -> {:stop, err, state}
end
{
:producer_consumer,
%State{handler: handler, handler_mod: handler_mod},
subscribe_to: [handler], dispatcher: GenStage.BroadcastDispatcher
}
end
def handle_info({:EXIT, _, reason}, state) do
@ -269,6 +272,13 @@ defmodule Farmbot.Firmware do
end
defp handle_gcode({:report_software_version, version}, state) do
case String.last(version) do
"F" ->
Farmbot.System.ConfigStorage.update_config_value(:string, "settings", "firmware_hardware", "farmduino")
"R" ->
Farmbot.System.ConfigStorage.update_config_value(:string, "settings", "firmware_hardware", "arduino")
_ -> :ok
end
{:informational_settings, %{firmware_version: version}, state}
end

View File

@ -80,26 +80,25 @@ defmodule Farmbot.Firmware.UartHandler do
def init([]) do
# If in dev environment, it is expected that this be done at compile time.
# If ini target environment, this should be done by `Farmbot.Firmware.AutoDetector`.
# If in target environment, this should be done by `Farmbot.Firmware.AutoDetector`.
tty =
Application.get_env(:farmbot, :uart_handler)[:tty] || raise "Please configure uart handler!"
{:ok, nerves} = UART.start_link()
Process.link(nerves)
case open_tty(nerves, tty) do
:ok -> {:producer, %State{nerves: nerves}, dispatcher: GenStage.BroadcastDispatcher}
case open_tty(tty) do
{:ok, nerves} -> {:producer, %State{nerves: nerves}, dispatcher: GenStage.BroadcastDispatcher}
err -> {:stop, err, :no_state}
end
end
defp open_tty(nerves, tty) do
case UART.open(nerves, tty, speed: 115_200, active: true) do
defp open_tty(tty) do
{:ok, nerves} = UART.start_link()
Process.link(nerves)
case UART.open(nerves, tty, [speed: 115_200, active: true]) do
:ok ->
:ok = configure_uart(nerves, true)
# Flush the buffers so we start fresh
:ok = UART.flush(nerves)
:ok
{:ok, nerves}
err ->
err
@ -117,6 +116,13 @@ defmodule Farmbot.Firmware.UartHandler do
# 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
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, {:error, :eio}, state}
end
def handle_info({:nerves_uart, _, {:error, reason}}, state) do
{:stop, {:error, reason}, state}
end

View File

@ -0,0 +1,124 @@
defmodule Farmbot.Firmware.UartHandler.Update do
@moduledoc false
use Farmbot.Logger
def maybe_update_firmware(hardware \\ nil) do
tty = Application.get_all_env(:farmbot)[:uart_handler][:tty]
hardware = case hardware do
"farmduino" -> "F"
"arduino" -> "R"
nil -> "R"
end
if tty do
do_connect_and_maybe_update(tty, hardware)
end
end
defp do_connect_and_maybe_update(tty, hardware) do
case Nerves.UART.start_link() do
{:ok, uart} ->
opts = [
active: true,
framing: {Nerves.UART.Framing.Line, separator: "\r\n"},
speed: 115200
]
:ok = Nerves.UART.open(uart, tty, [speed: 115200])
:ok = Nerves.UART.configure(uart, opts)
Logger.busy 3, "Waiting for firmware idle report."
do_fw_loop(uart, tty, :idle, hardware)
{:error, reason} ->
Logger.error 1, "Failed to connect to firmware for update: #{inspect reason}"
end
end
defp do_fw_loop(uart, tty, flag, hardware) 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
:idle ->
Logger.busy 3, "Waiting for next idle."
do_fw_loop(uart, tty, :version, hardware)
:version ->
Process.sleep(500)
# tell the FW to report its version.
Nerves.UART.write(uart, "F83")
Logger.busy 3, "Waiting for firmware version report."
do_wait_version(uart, tty, hardware)
end
else
do_fw_loop(uart, tty, flag, hardware)
end
after
15_000 ->
Logger.warn 1, "timeout waiting for firmware idle. Forcing flash."
do_flash(hardware, uart, tty)
end
end
defp do_wait_version(uart, tty, hardware) 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
[_] ->
# IO.puts "got data: #{data}"
do_wait_version(uart, tty, hardware)
["", ver_with_q] -> do_maybe_flash(ver_with_q, uart, tty, hardware)
end
after
15_000 ->
Logger.warn 1, "timeout waiting for firmware version. Forcing flash."
do_flash(hardware, uart, tty)
end
end
defp do_maybe_flash(ver_with_q, uart, tty, hardware) do
current_version = case String.split(ver_with_q, " Q") do
[ver] -> ver
[ver, _] -> ver
end
expected = Application.get_env(:farmbot, :expected_fw_versions)
fw_hw = String.last(current_version)
cond do
fw_hw != hardware ->
Logger.warn 3, "Switching firmware hardware."
do_flash(hardware, uart, tty)
current_version in expected ->
Logger.success 1, "Firmware is already correct version."
true ->
Logger.busy 1, "#{current_version} != #{inspect expected}"
do_flash(fw_hw, uart, tty)
end
end
# Farmduino
defp do_flash("F", uart, tty) do
avrdude("#{:code.priv_dir(:farmbot)}/farmduino-firmware.hex", uart, tty)
end
# Anything else. (should always be "R")
defp do_flash(_, uart, tty) do
avrdude("#{:code.priv_dir(:farmbot)}/arduino-firmware.hex", uart, tty)
end
defp close(uart, _tty) do
if Process.alive?(uart) do
Nerves.UART.close(uart)
Nerves.UART.stop(uart)
Process.sleep(500) # to allow the FD to be closed.
end
end
def avrdude(fw_file, uart, tty) do
close(uart, tty)
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}"
end
end
end

View File

@ -162,8 +162,16 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do
case conn.body_params do
%{"firmware_hardware" => hw} when hw in ["arduino", "farmduino"] ->
ConfigStorage.update_config_value(:string, "settings", "firmware_hardware", hw)
# TODO Flash firmware here.
# If Application.get_env(:farmbot, :uart_handler, :tty) do...
if Application.get_env(:farmbot, :behaviour)[:firmware_handler] == Farmbot.Firmware.UartHandler do
Logger.warn 1, "Updating #{hw} firmware."
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)
Farmbot.Firmware.UartHandler.Update.maybe_update_firmware(hw)
Application.put_env(:farmbot, :behaviour, old_env)
end
redir(conn, "/credentials")
%{"firmware_hardware" => "custom"} ->

View File

@ -15,55 +15,7 @@ defmodule Farmbot.Target.UpdateHandler do
end
def post_update do
maybe_update_firmware()
Farmbot.Firmware.UartHandler.Update.maybe_update_firmware()
:ok
end
defp maybe_update_firmware do
tty = Application.get_all_env(:farmbot)[:uart_handler][:tty]
if tty do
do_connect_and_maybe_update(tty)
end
end
defp do_connect_and_maybe_update(tty, retries \\ 0) do
case Nerves.UART.start_link() do
{:ok, uart} ->
opts = [
active: true,
framing: {Nerves.UART.Framing.Line, separator: "\r\n"}
]
Nerves.UART.open(uart, tty, opts)
do_fw_loop(uart, tty)
{:error, reason} ->
Logger.error 1, "Failed to connect to firmware for update: #{inspect reason}"
end
end
defp do_wait_idle(uart, tty) do
receive do
{:nerves_uart, ^tty, {:error, reason}} ->
Logger.error 1, "Failed to connect to firmware for update: #{inspect reason}"
{:nerves_uart, ^tty, data} ->
if String.contains?(data, "R00") do
# tell the FW to report its version.
Nerves.UART.write("F83")
do_wait_version(uart, tty)
else
do_wait_idle(uart, tty)
end
after
15_000 ->
Logger.warn 1, "timeout waiting for firmware. Forcing flash."
end
end
defp do_wait_version(uart, tty) do
receive do
{:nerves_uart, ^tty, {:error, reason}} ->
Logger.error 1, "Failed to connect to firmware for update: #{inspect reason}"
{:nerves_uart, ^tty, data} ->
end
end
end