Allow flashing firmware from runtime.
parent
56477fac71
commit
5bf711302d
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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: %{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
1
mix.exs
1
mix.exs
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue