farmbot_os/lib/farmbot/firmware/uart_handler/update.ex

187 lines
5.6 KiB
Elixir

defmodule Farmbot.Firmware.UartHandler.Update do
@moduledoc false
use Farmbot.Logger
@uart_speed 115_200
alias Circuits.UART
def maybe_update_firmware(hardware \\ nil) do
tty = Application.get_all_env(:farmbot)[:uart_handler][:tty]
hardware = case hardware do
"farmduino" -> "F"
"arduino" -> "R"
"farmduino_k14" -> "G"
nil -> "R"
end
if tty do
do_connect_and_maybe_update(tty, hardware)
end
end
def force_update_firmware(hardware \\ nil) do
tty = Application.get_all_env(:farmbot)[:uart_handler][:tty]
hardware = case hardware do
"farmduino" -> "F"
"arduino" -> "R"
"farmduino_k14" -> "G"
nil -> "R"
end
do_flash(hardware, nil, tty)
end
defp do_connect_and_maybe_update(tty, hardware) do
case UART.start_link() do
{:ok, uart} ->
opts = [
active: true,
framing: {UART.Framing.Line, separator: "\r\n"},
speed: @uart_speed
]
:ok = UART.open(uart, tty, [speed: @uart_speed])
:ok = 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
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}"
{: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.
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}"
{: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)}/eeprom_clear.ino.hex", uart, tty)
Process.sleep(1000)
avrdude("#{:code.priv_dir(:farmbot)}/farmduino.hex", uart, tty)
end
defp do_flash("G", uart, tty) do
avrdude("#{:code.priv_dir(:farmbot)}/eeprom_clear.ino.hex", uart, tty)
Process.sleep(1000)
avrdude("#{:code.priv_dir(:farmbot)}/farmduino_k14.hex", uart, tty)
end
# Anything else. (should always be "R")
defp do_flash(_, uart, tty) do
avrdude("#{:code.priv_dir(:farmbot)}/eeprom_clear.ino.hex", uart, tty)
Process.sleep(1000)
avrdude("#{:code.priv_dir(:farmbot)}/arduino_firmware.hex", uart, tty)
end
defp close(nil) do
Logger.info 3, "No uart process."
:ok
end
defp close(uart) do
if Process.alive?(uart) do
close = UART.close(uart)
stop = UART.stop(uart)
Logger.info 3, "CLOSE: #{inspect close} STOP: #{stop}"
Process.sleep(2000) # to allow the FD to be closed.
end
end
def avrdude(fw_file, uart, tty) do
close(uart)
args = ~w"-patmega2560 -cwiring -P#{tty} -b#{@uart_speed} -D -V -Uflash:w:#{fw_file}:i"
Logger.busy 3, "Starting avrdude: #{inspect(args)}"
reset = reset_init()
opts = [stderr_to_stdout: true, into: IO.stream(:stdio, :line)]
do_reset(reset)
res = System.cmd("avrdude", args, opts)
reset_close(reset)
Process.sleep(1500) # wait to allow file descriptors to be closed.
case res do
{_, 0} ->
Logger.success 1, "Firmware flashed! #{fw_file}"
:ok
{_, err_code} ->
Logger.error 1, "Failed to flash Firmware! #{fw_file} #{err_code}"
Farmbot.Firmware.Utils.replace_firmware_handler(Farmbot.Firmware.StubHandler)
:error
end
end
if Farmbot.Project.target() in [:rpi0, :rpi] do
@reset_pin 19
defp reset_init do
{:ok, gpio} = Circuits.GPIO.open(@reset_pin, :output)
gpio
end
defp do_reset(gpio) do
Circuits.GPIO.write(gpio, 1)
Circuits.GPIO.write(gpio, 0)
end
defp reset_close(gpio), do: Circuits.GPIO.close(gpio)
else
defp reset_init(), do: nil
defp do_reset(nil), do: :ok
defp reset_close(nil), do: :ok
end
end