Allow flashing firmware from runtime.

pull/412/head
connor rigby 2018-01-10 09:04:59 -08:00
parent 56477fac71
commit 5bf711302d
13 changed files with 109 additions and 110 deletions

View File

@ -4,11 +4,12 @@ use Mix.Config
target = Mix.Project.config()[:target]
env = Mix.env()
config :logger,
config :logger, [
utc_log: true,
# handle_otp_reports: true,
# handle_sasl_reports: true,
backends: []
]
config :elixir, ansi_enabled: true
config :iex, :colors, enabled: true

View File

@ -15,7 +15,6 @@ config :farmbot, data_path: "tmp/"
config :farmbot, :init, [
Farmbot.Host.Bootstrap.Configurator,
Farmbot.Host.TargetConfiguratorTest.Supervisor,
Farmbot.System.Udev.Supervisor,
# Farmbot.System.Debug
]
@ -52,10 +51,10 @@ 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"
config :farmbot, :uart_handler, tty: "/dev/ttyACM1"
config :farmbot, :logger, [
# backends: [Elixir.Logger.Backends.Farmbot]

View File

@ -113,12 +113,12 @@ defmodule Farmbot.Bootstrap.Supervisor do
end
defp actual_init(email, pass, server) do
busy_msg = "Beginning authorization: #{email} - #{server}"
busy_msg = "Beginning Bootstrap authorization: #{email} - #{server}"
Logger.busy(2, busy_msg)
# get a token
case @auth_task.authorize(email, pass, server) do
{:ok, token} ->
success_msg = "Successful authorization: #{email} - #{server}"
success_msg = "Successful Bootstrap authorization: #{email} - #{server}"
Logger.success(2, success_msg)
update_config_value(:bool, "settings", "first_boot", false)
update_config_value(:string, "authorization", "token", token)

View File

@ -95,6 +95,11 @@ defmodule Farmbot.BotState do
GenStage.call(__MODULE__, {:set_sync_status, cmd})
end
@doc "Set the sync status to the previous status."
def reset_sync_status do
GenStage.call(__MODULE__, :reset_sync_status)
end
@doc "Forces a state push over all transports."
def force_state_push do
GenStage.call(__MODULE__, :force_state_push)
@ -181,7 +186,16 @@ defmodule Farmbot.BotState do
end
def handle_call({:set_sync_status, status}, _, state) do
new_info_settings = %{state.informational_settings | sync_status: status}
last = state.informational_settings.sync_status
new_info_settings = %{state.informational_settings | sync_status: status, last_status: last}
new_state = %{state | informational_settings: new_info_settings}
{:reply, :ok, [new_state], new_state}
end
def handle_call(:reset_sync_status, _, state) do
current = state.informational_settings.sync_status
last = state.informational_settings.last_status
new_info_settings = %{state.informational_settings | sync_status: last, last_status: current}
new_state = %{state | informational_settings: new_info_settings}
{:reply, :ok, [new_state], new_state}
end
@ -290,6 +304,7 @@ defmodule Farmbot.BotState do
node_name: nil,
busy: false,
sync_status: :booting,
last_status: nil,
locked: false
},
location_data: %{

View File

@ -78,7 +78,8 @@ defmodule Farmbot.BotState.Transport.AMQP do
end
def terminate(reason, state) do
if reason not in [:normal, :shutdown, :token_refresh] do
ok_reasons = [:normal, :shutdown, :token_refresh]
if reason not in ok_reasons do
Logger.error 1, "AMQP Died: #{inspect reason}"
update_config_value(:bool, "settings", "log_amqp_connected", true)
end
@ -91,7 +92,7 @@ defmodule Farmbot.BotState.Transport.AMQP do
# If the auth task is running, force it to reset.
auth_task = Farmbot.Bootstrap.AuthTask
if Process.whereis(auth_task) && reason != :token_refresh do
if Process.whereis(auth_task) && reason not in ok_reasons do
auth_task.force_refresh()
end
end

View File

@ -16,15 +16,6 @@ defmodule Farmbot.CeleryScript.AST.Node.ConfigUpdate do
defp do_reduce_os([%{args: %{label: key, value: value}} | rest], env) do
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)
hack_file = Path.join(Application.get_env(:farmbot, :data_path), "firmware_flash")
File.write!(hack_file, val)
Farmbot.BotState.set_sync_status(:maintenance)
Logger.warn 1, "Rebooting to flash firmware."
Farmbot.System.reboot("Flash arduino fw.")
# Stop enumeration here.
{:ok, env}
{:ok, {type, group, value}} ->
Farmbot.System.ConfigStorage.update_config_value(type, group, key, value)
Logger.success 3, "Updating: #{inspect key}: #{value}"

View File

@ -143,12 +143,19 @@ defmodule Farmbot.Firmware do
%State{handler: handler, handler_mod: handler_mod},
subscribe_to: [handler], dispatcher: GenStage.BroadcastDispatcher
}
{:stop, err, state} -> {:stop, err, state}
{:error, reason} ->
old = Application.get_all_env(:farmbot)[:behaviour]
new = Keyword.put(old, :firmware_handler, Farmbot.Firmware.StubHandler)
Application.put_env(:farmbot, :behaviour, new)
{:stop, {:handler_init, reason}}
end
end
def terminate(reason, state) do
old = Application.get_all_env(:farmbot)[:behaviour]
new = Keyword.put(old, :firmware_handler, Farmbot.Firmware.StubHandler)
Application.put_env(:farmbot, :behaviour, new)
unless :queue.is_empty(state.queue) do
list = :queue.to_list(state.queue)
for cmd <- list do

View File

@ -7,7 +7,7 @@ defmodule Farmbot.Firmware.UartHandler do
alias Nerves.UART
use Farmbot.Logger
alias Farmbot.System.ConfigStorage
import ConfigStorage, only: [update_config_value: 4]
import ConfigStorage, only: [update_config_value: 4, get_config_value: 3]
alias Farmbot.Firmware
alias Firmware.{UartHandler, Vec3}
import Vec3, only: [fmnt_float: 1]
@ -88,10 +88,13 @@ defmodule Farmbot.Firmware.UartHandler do
defstruct [
nerves: nil,
current_cmd: nil,
tty: nil,
hw: nil
]
end
def init([]) do
Logger.debug 3, "Uart handler init."
# If in dev environment,
# it is expected that this be done at compile time.
# If in target environment,
@ -102,40 +105,29 @@ defmodule Farmbot.Firmware.UartHandler do
# Disable fw input logs after a reset of the
# Fw handler if they were enabled.
update_config_value(:bool, "settings", "firmware_input_log", false)
# This looks up a hack file, flashes fw, and removes hack file.
# Sorry about that.
maybe_flash_fw(tty)
hw = get_config_value(:string, "settings", "firmware_hardware")
gen_stage_opts = [
dispatcher: GenStage.BroadcastDispatcher,
subscribe_to: [ConfigStorage.Dispatcher]
]
case open_tty(tty) do
{:ok, nerves} ->
{:producer_consumer, %State{nerves: nerves}, gen_stage_opts}
{:producer_consumer, %State{nerves: nerves, tty: tty, hw: hw}, gen_stage_opts}
err ->
{:stop, err}
end
end
defp maybe_flash_fw(_tty) do
path_list = [Application.get_env(:farmbot, :data_path), "firmware_flash"]
hack_file = Path.join(path_list)
case File.read(hack_file) do
{:ok, value} when value in ["arduino", "farmduino"] ->
update_config_value(:string, "settings", "firmware_hardware", value)
UartHandler.Update.force_update_firmware(value)
File.rm!(hack_file)
_ -> :ok
end
end
def handle_events(events, _, state) do
state = Enum.reduce(events, state, fn(event, state_acc) ->
handle_config(event, state_acc)
end)
{:noreply, [], state}
case state do
%State{} = state ->
{:noreply, [], state}
_ -> state
end
end
defp handle_config({:config, "settings", key, _val}, state)
@ -147,12 +139,27 @@ defmodule Farmbot.Firmware.UartHandler do
state
end
defp handle_config({:config, "settings", "firmware_hardware", val}, state) do
if val != state.hw do
Logger.info 3, "firmware_hardware updated from #{state.hw} to #{val}"
Farmbot.BotState.set_sync_status(:maintenance)
UART.close(state.nerves)
UartHandler.Update.force_update_firmware(val)
open_tty(state.tty, state.nerves)
Farmbot.BotState.reset_sync_status()
%{state | hw: val}
else
state
end
end
defp handle_config(_, state) do
state
end
defp open_tty(tty) do
{:ok, nerves} = UART.start_link()
defp open_tty(tty, nerves \\ nil) do
Logger.debug 3, "Opening uart device: #{tty}"
nerves = nerves || UART.start_link |> elem(1)
Process.link(nerves)
case UART.open(nerves, tty, [speed: 115_200, active: true]) do
:ok ->
@ -166,12 +173,19 @@ defmodule Farmbot.Firmware.UartHandler do
end
defp loop_until_idle(nerves) do
Logger.debug 3, "Waiting for firmware idle."
receive do
{:nerves_uart, _, {:error, reason}} -> {:stop, reason}
{:nerves_uart, _, {:partial, _}} -> loop_until_idle(nerves)
# {:nerves_uart, _, {_, :idle}} -> {:ok, nerves}
{:nerves_uart, _, {_, {:debug_message, "ARDUINO STARTUP COMPLETE"}}} ->
{:nerves_uart, _, {_, {:debug_message, msg}}} ->
if String.contains?(msg, "STARTUP") do
{:ok, nerves}
else
Logger.debug 1, "Got arduino debug while booting up: #{msg}"
loop_until_idle(nerves)
end
# {:nerves_uart, _, {_, :idle}} -> {:ok, nerves}
{:nerves_uart, _, _msg} ->
# Logger.info 3, "Got message: #{inspect msg}"
loop_until_idle(nerves)
@ -189,9 +203,10 @@ defmodule Farmbot.Firmware.UartHandler do
end
def terminate(reason, state) do
Logger.warn 1, "UART handler died: #{inspect reason}"
if state.nerves do
UART.close(state.nerves)
UART.stop(reason)
UART.stop(:normal)
end
end

View File

@ -135,10 +135,18 @@ defmodule Farmbot.Firmware.UartHandler.Update do
def avrdude(fw_file, uart, tty) do
close(uart)
case System.cmd("avrdude", ~w"-q -q -patmega2560 -cwiring -P#{tty} -b#{@uart_speed} -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}"
Logger.busy 3, "Starting avrdude."
args = ~w"-q -q -patmega2560 -cwiring -P#{tty} -b#{@uart_speed} -D -V -Uflash:w:#{fw_file}:i"
opts = [stderr_to_stdout: true, into: IO.stream(:stdio, :line)]
res = System.cmd("avrdude", args, opts)
Process.sleep(1500) # wait to allow file descriptors to be closed.
case res do
{_, 0} ->
Logger.success 1, "Firmware flashed!"
:ok
{_, err_code} ->
Logger.error 1, "Failed to flash Firmware! #{err_code}"
:error
end
Process.sleep(1500) # to allow the FD to be closed.
end
end

View File

@ -129,7 +129,6 @@ defmodule Farmbot.Mixfile do
{:excoveralls, "~> 0.7", only: :test},
{:mock, "~> 0.2.0", only: :test},
{:faker, "~> 0.9", only: :test},
{:udev, "~> 0.1.0", only: [:dev, :prod]},
]
end

View File

@ -1,6 +1,7 @@
defmodule Farmbot.Host.Bootstrap.Configurator do
@behaviour Farmbot.System.Init
alias Farmbot.System.ConfigStorage
import Farmbot.System.ConfigStorage,
only: [update_config_value: 4, get_config_value: 3]
def start_link(_, opts) do
Supervisor.start_link(__MODULE__, [], opts)
@ -21,12 +22,18 @@ defmodule Farmbot.Host.Bootstrap.Configurator do
email = Application.get_env(:farmbot, :authorization)[:email] || raise error("email")
pass = Application.get_env(:farmbot, :authorization)[:password] || raise error("password")
server = Application.get_env(:farmbot, :authorization)[:server] || raise error("server")
ConfigStorage.update_config_value(:string, "authorization", "email", email)
if ConfigStorage.get_config_value(:bool, "settings", "first_boot") do
ConfigStorage.update_config_value(:string, "authorization", "password", pass)
update_config_value(:string, "authorization", "email", email)
# if there is no firmware hardware, default ot farmduino
unless get_config_value(:string, "settings", "firmware_hardware") do
update_config_value(:string, "settings", "firmware_hardware", "farmduino")
end
ConfigStorage.update_config_value(:string, "authorization", "server", server)
ConfigStorage.update_config_value(:string, "authorization", "token", nil)
if get_config_value(:bool, "settings", "first_boot") do
update_config_value(:string, "authorization", "password", pass)
end
update_config_value(:string, "authorization", "server", server)
update_config_value(:string, "authorization", "token", nil)
:ignore
end

View File

@ -1,54 +0,0 @@
if Farmbot.Project.env() != :test do
defmodule Farmbot.System.Udev.Supervisor 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(), :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
end

View File

@ -1 +1,11 @@
avrdude -v -patmega2560 -cwiring -P/dev/ttyACM0 -b115200 -D -V -Uflash:w:./priv/arduino-firmware.hex:i
if [ -z $1 ]; then
echo "usage: scripts/flash_fw.sh [arduino|farmduino] /dev/ttyACM0"
exit 1
fi
if [ -z $2 ]; then
echo "usage: scripts/flash_fw.sh [arduino|farmduino] /dev/ttyACM0"
exit 1
fi
avrdude -v -patmega2560 -cwiring -P$2 -b115200 -D -V -Uflash:w:./priv/$1-firmware.hex:i