Fix firmware command queuing. (needs log cleanup)

pull/392/head
connor rigby 2018-01-02 11:32:11 -08:00
parent 99fbfd4256
commit cea71b2305
7 changed files with 75 additions and 48 deletions

View File

@ -60,6 +60,7 @@ jobs:
- run: - run:
command: mix firmware command: mix firmware
environment: environment:
MIX_ENV: dev
MIX_TARGET: rpi3 MIX_TARGET: rpi3
- save_cache: - save_cache:
key: v3-dependency-cache-{{ checksum "mix.lock.rpi3" }} key: v3-dependency-cache-{{ checksum "mix.lock.rpi3" }}
@ -69,8 +70,9 @@ jobs:
- ~/.mix - ~/.mix
- ~/.nerves - ~/.nerves
- run: - run:
command: mix firmware.slack -channels C58DCU4A3 command: mix firmware.slack --channels C58DCU4A3
environment: environment:
MIX_ENV: dev
MIX_TARGET: rpi3 MIX_TARGET: rpi3
workflows: workflows:

View File

@ -1,2 +1,2 @@
erlang 20.0 erlang 20.1
elixir 1.5.2 elixir 1.5.2

View File

@ -6,8 +6,8 @@ defmodule Farmbot.Bootstrap.AuthTask do
import ConfigStorage, only: [update_config_value: 4, get_config_value: 3] import ConfigStorage, only: [update_config_value: 4, get_config_value: 3]
# 30 minutes. # 30 minutes.
# @refresh_time 1.8e+6 |> round() @refresh_time 1.8e+6 |> round()
@refresh_time 5_000 # @refresh_time 5_000
@doc false @doc false
def start_link() do def start_link() do

View File

@ -188,10 +188,18 @@ defmodule Farmbot.BotState.Transport.AMQP do
end end
end end
def handle_info({:DOWN, _, :process, pid, reason}, state) do
Logger.warn 3, "Sequence: #{inspect pid} died: #{inspect reason}"
{:noreply, [], state}
end
@doc false @doc false
def handle_celery_script(payload, _state) do def handle_celery_script(payload, _state) do
case AST.decode(payload) do case AST.decode(payload) do
{:ok, ast} -> spawn CeleryScript, :execute, [ast] {:ok, ast} ->
pid = spawn(CeleryScript, :execute, [ast])
Logger.busy 3, "Sequence starting: #{inspect pid}"
Process.monitor(pid)
_ -> :ok _ -> :ok
end end
end end

View File

@ -7,32 +7,32 @@ defmodule Farmbot.Firmware do
@doc "Move the bot to a position." @doc "Move the bot to a position."
def move_absolute(%Vec3{} = vec3, x_speed, y_speed, z_speed) do def move_absolute(%Vec3{} = vec3, x_speed, y_speed, z_speed) do
GenStage.call(__MODULE__, {:move_absolute, [vec3, x_speed, y_speed, z_speed]}, :infinity) GenStage.call(__MODULE__, {:move_absolute, [vec3, x_speed, y_speed, z_speed]}, 120_000)
end end
@doc "Calibrate an axis." @doc "Calibrate an axis."
def calibrate(axis) do def calibrate(axis) do
GenStage.call(__MODULE__, {:calibrate, [axis]}, :infinity) GenStage.call(__MODULE__, {:calibrate, [axis]}, 120_000)
end end
@doc "Find home on an axis." @doc "Find home on an axis."
def find_home(axis) do def find_home(axis) do
GenStage.call(__MODULE__, {:find_home, [axis]}, :infinity) GenStage.call(__MODULE__, {:find_home, [axis]}, 120_000)
end end
@doc "Home every axis." @doc "Home every axis."
def home_all() do def home_all() do
GenStage.call(__MODULE__, {:home_all, []}, :infinity) GenStage.call(__MODULE__, {:home_all, []}, 120_000)
end end
@doc "Home an axis." @doc "Home an axis."
def home(axis) do def home(axis) do
GenStage.call(__MODULE__, {:home, [axis]}, :infinity) GenStage.call(__MODULE__, {:home, [axis]}, 120_000)
end end
@doc "Manually set an axis's current position to zero." @doc "Manually set an axis's current position to zero."
def zero(axis) do def zero(axis) do
GenStage.call(__MODULE__, {:zero, [axis]}, :infinity) GenStage.call(__MODULE__, {:zero, [axis]}, 120_000)
end end
@doc """ @doc """
@ -40,12 +40,12 @@ defmodule Farmbot.Firmware do
For a list of paramaters see `Farmbot.Firmware.Gcode.Param` For a list of paramaters see `Farmbot.Firmware.Gcode.Param`
""" """
def update_param(param, val) do def update_param(param, val) do
GenStage.call(__MODULE__, {:update_param, [param, val]}, :infinity) GenStage.call(__MODULE__, {:update_param, [param, val]}, 120_000)
end end
@doc false @doc false
def read_all_params do def read_all_params do
GenStage.call(__MODULE__, {:read_all_params, []}, :infinity) GenStage.call(__MODULE__, {:read_all_params, []}, 120_000)
end end
@doc """ @doc """
@ -53,42 +53,42 @@ defmodule Farmbot.Firmware do
For a list of paramaters see `Farmbot.Firmware.Gcode.Param` For a list of paramaters see `Farmbot.Firmware.Gcode.Param`
""" """
def read_param(param) do def read_param(param) do
GenStage.call(__MODULE__, {:read_param, [param]}, :infinity) GenStage.call(__MODULE__, {:read_param, [param]}, 120_000)
end end
@doc "Emergency lock Farmbot." @doc "Emergency lock Farmbot."
def emergency_lock() do def emergency_lock() do
GenStage.call(__MODULE__, {:emergency_lock, []}, :infinity) GenStage.call(__MODULE__, {:emergency_lock, []}, 120_000)
end end
@doc "Unlock Farmbot from Emergency state." @doc "Unlock Farmbot from Emergency state."
def emergency_unlock() do def emergency_unlock() do
GenStage.call(__MODULE__, {:emergency_unlock, []}, :infinity) GenStage.call(__MODULE__, {:emergency_unlock, []}, 120_000)
end end
@doc "Set a pin mode (:input | :output)" @doc "Set a pin mode (:input | :output)"
def set_pin_mode(pin, mode) do def set_pin_mode(pin, mode) do
GenStage.call(__MODULE__, {:set_pin_mode, [pin, mode]}, :infinity) GenStage.call(__MODULE__, {:set_pin_mode, [pin, mode]}, 120_000)
end end
@doc "Read a pin." @doc "Read a pin."
def read_pin(pin, mode) do def read_pin(pin, mode) do
GenStage.call(__MODULE__, {:read_pin, [pin, mode]}, :infinity) GenStage.call(__MODULE__, {:read_pin, [pin, mode]}, 120_000)
end end
@doc "Write a pin." @doc "Write a pin."
def write_pin(pin, mode, value) do def write_pin(pin, mode, value) do
GenStage.call(__MODULE__, {:write_pin, [pin, mode, value]}, :infinity) GenStage.call(__MODULE__, {:write_pin, [pin, mode, value]}, 120_000)
end end
@doc "Request version." @doc "Request version."
def request_software_version do def request_software_version do
GenStage.call(__MODULE__, {:request_software_version, []}, :infinity) GenStage.call(__MODULE__, {:request_software_version, []}, 120_000)
end end
@doc "Set angle of a servo pin." @doc "Set angle of a servo pin."
def set_servo_angle(pin, value) do def set_servo_angle(pin, value) do
GenStage.call(__MODULE__, {:set_servo_angle, [pin, value]}, :infinity) GenStage.call(__MODULE__, {:set_servo_angle, [pin, value]}, 120_000)
end end
@doc "Start the firmware services." @doc "Start the firmware services."
@ -152,19 +152,18 @@ defmodule Farmbot.Firmware do
def handle_info(:timeout, state) do def handle_info(:timeout, state) do
case state.current do case state.current do
nil -> {:noreply, [], %{state | timer: nil}} nil -> {:noreply, [], %{state | timer: nil}}
%Current{fun: fun, args: args, from: from} = current -> %Current{fun: fun, args: args, from: _from} = current ->
Logger.warn 1, "Got Firmware timeout. Retrying #{fun}(#{inspect args}) " Logger.warn 1, "Got Firmware timeout. Retrying #{fun}(#{inspect args}) "
case apply(state.handler_mod, fun, [state.handler | args]) do case apply(state.handler_mod, fun, [state.handler | args]) do
:ok -> :ok ->
timer = Process.send_after(self(), :timeout, state.timeout_ms) timer = Process.send_after(self(), :timeout, state.timeout_ms)
{:noreply, [], %{state | current: current, timer: timer}} {:noreply, [], %{state | current: current, timer: timer}}
{:error, _} = res -> {:error, _} = res ->
GenStage.reply(from, res) do_reply(state, res)
{:noreply, [], %{state | current: nil, queue: :queue.new()}} {:noreply, [], %{state | current: nil, queue: :queue.new()}}
end end
{:noreply, [], %{state | timer: nil}} {:noreply, [], %{state | timer: nil}}
end end
end end
def handle_call({fun, _}, _from, state = %{initialized: false}) when fun not in [:read_all_params, :update_param, :emergency_unlock, :emergency_lock] do def handle_call({fun, _}, _from, state = %{initialized: false}) when fun not in [:read_all_params, :update_param, :emergency_unlock, :emergency_lock] do
@ -172,16 +171,17 @@ defmodule Farmbot.Firmware do
end end
def handle_call({fun, args}, from, state) do def handle_call({fun, args}, from, state) do
current = struct(Current, from: from, fun: fun, args: args) next_current = struct(Current, from: from, fun: fun, args: args)
if :queue.is_empty(state.queue) do current_current = state.current
do_begin_cmd(current, state, []) if current_current do
do_queue_cmd(next_current, state)
else else
do_queue_cmd(current, state) do_begin_cmd(next_current, state, [])
end end
end end
defp do_begin_cmd(%Current{fun: fun, args: args, from: _from} = current, state, dispatch) do defp do_begin_cmd(%Current{fun: fun, args: args, from: from} = current, state, dispatch) do
# Logger.debug 3, "Firmware command: #{fun}#{inspect(args)}" Logger.busy 3, "FW Starting: #{fun}: #{inspect from}"
case apply(state.handler_mod, fun, [state.handler | args]) do case apply(state.handler_mod, fun, [state.handler | args]) do
:ok -> :ok ->
@ -189,22 +189,26 @@ defmodule Farmbot.Firmware do
timer = Process.send_after(self(), :timeout, state.timeout_ms) timer = Process.send_after(self(), :timeout, state.timeout_ms)
{:noreply, dispatch, %{state | current: current, timer: timer}} {:noreply, dispatch, %{state | current: current, timer: timer}}
{:error, _} = res -> {:error, _} = res ->
{:reply, res, dispatch, %{state | current: nil, queue: :queue.new()}} do_reply(%{state | current: current}, res)
{:noreply, dispatch, %{state | current: nil}}
end end
end end
defp do_queue_cmd(%Current{fun: _fun, args: _args, from: _from} = current, state) do defp do_queue_cmd(%Current{fun: fun, args: _args, from: from} = current, state) do
Logger.busy 3, "FW Queuing: #{fun}: #{inspect from}"
new_q = :queue.in(current, state.queue) new_q = :queue.in(current, state.queue)
{:noreply, [], %{state | queue: new_q}} {:noreply, [], %{state | queue: new_q}}
end end
def handle_events(gcodes, _from, state) do def handle_events(gcodes, _from, state) do
{diffs, state} = handle_gcodes(gcodes, state) {diffs, state} = handle_gcodes(gcodes, state)
if state.current == nil do # if after handling the current buffer of gcodes,
# Try to start the next command in the queue if it exists.
if List.last(gcodes) == :idle && state.current == nil do
case :queue.out(state.queue) do case :queue.out(state.queue) do
{{:value, current}, new_queue} -> {{:value, next_current}, new_queue} ->
do_begin_cmd(current, %{state | queue: new_queue, current: current}, diffs) do_begin_cmd(next_current, %{state | queue: new_queue, current: next_current}, diffs)
{:empty, queue} -> {:empty, queue} -> # nothing to do if the queue is empty.
{:noreply, diffs, %{state | queue: queue}} {:noreply, diffs, %{state | queue: queue}}
end end
else else
@ -242,7 +246,7 @@ defmodule Farmbot.Firmware do
end end
end) end)
Logger.error 1, "Failed to execute #{state.current.fun} #{inspect formatted_args}" Logger.error 1, "Failed to execute #{state.current.fun} #{inspect formatted_args}"
GenStage.reply(state.current.from, {:error, :firmware_error}) do_reply(state, {:error, :firmware_error})
{nil, %{state | current: nil}} {nil, %{state | current: nil}}
else else
{nil, state} {nil, state}
@ -322,13 +326,13 @@ defmodule Farmbot.Firmware do
Farmbot.BotState.set_busy(false) Farmbot.BotState.set_busy(false)
if state.current do if state.current do
# This might be a bug in the FW # This might be a bug in the FW
if state.current.command in [:home, :home_all] do if state.current.fun in [:home, :home_all] do
Logger.warn 1, "Got idle during home. Ignoring. This might be bad." Logger.warn 1, "Got idle during home. Ignoring. This might be bad."
timer = Process.send_after(self(), :timeout, state.timeout_ms) timer = Process.send_after(self(), :timeout, state.timeout_ms)
{nil, %{state | timer: timer}} {nil, %{state | timer: timer}}
else else
Logger.warn 1, "Got idle while executing a command." Logger.warn 1, "Got idle while executing a command."
GenStage.reply(state.current.from, {:error, :timeout}) do_reply(state, {:error, :timeout})
{:informational_settings, %{busy: false, locked: false}, %{state | current: nil, idle: true}} {:informational_settings, %{busy: false, locked: false}, %{state | current: nil, idle: true}}
end end
else else
@ -379,7 +383,7 @@ defmodule Farmbot.Firmware do
defp handle_gcode(:done, state) do defp handle_gcode(:done, state) do
maybe_cancel_timer(state.timer) maybe_cancel_timer(state.timer)
if state.current do if state.current do
GenStage.reply(state.current.from, :ok) do_reply(state, :ok)
{nil, %{state | current: nil}} {nil, %{state | current: nil}}
else else
{nil, state} {nil, state}
@ -389,7 +393,7 @@ defmodule Farmbot.Firmware do
defp handle_gcode(:report_emergency_lock, state) do defp handle_gcode(:report_emergency_lock, state) do
Farmbot.System.GPIO.Leds.led_status_err Farmbot.System.GPIO.Leds.led_status_err
if state.current do if state.current do
GenStage.reply(state.current.from, {:error, :emergency_lock}) do_reply(state, {:error, :emergency_lock})
{:informational_settings, %{locked: true}, %{state | current: nil}} {:informational_settings, %{locked: true}, %{state | current: nil}}
else else
{:informational_settings, %{locked: true}, state} {:informational_settings, %{locked: true}, state}
@ -471,4 +475,15 @@ defmodule Farmbot.Firmware do
report_calibration_callback(tries - 1, param, val) report_calibration_callback(tries - 1, param, val)
end end
end end
defp do_reply(state, reply) do
case state.current do
%Current{fun: fun, from: from} ->
Logger.success 3, "FW Replying: #{fun}: #{inspect from}"
:ok = GenServer.reply from, reply
nil ->
Logger.error 1, "FW Nothing to send reply: #{inspect reply} to!."
:error
end
end
end end

View File

@ -14,8 +14,8 @@ defmodule Farmbot.Firmware.StubHandler do
GenStage.start_link(__MODULE__, []) GenStage.start_link(__MODULE__, [])
end end
def move_absolute(handler, pos, x_speed, y_speed, z_speed) do def move_absolute(handler, pos, x_spd, y_spd, z_spd) do
GenStage.call(handler, {:move_absolute, pos, x_speed, y_speed, z_speed}) GenStage.call(handler, {:move_absolute, pos, x_spd, y_spd, z_spd}, 120_000)
end end
def calibrate(handler, axis) do def calibrate(handler, axis) do
@ -116,6 +116,7 @@ defmodule Farmbot.Firmware.StubHandler do
end end
def handle_call({:move_absolute, pos, _x_speed, _y_speed, _z_speed}, _from, state) do def handle_call({:move_absolute, pos, _x_speed, _y_speed, _z_speed}, _from, state) do
Process.sleep(5000)
response = build_resp [{:report_current_position, pos.x, pos.y, pos.z}, response = build_resp [{:report_current_position, pos.x, pos.y, pos.z},
{:report_encoder_position_scaled, pos.x, pos.y, pos.z}, {:report_encoder_position_scaled, pos.x, pos.y, pos.z},
{:report_encoder_position_raw, pos.x, pos.y, pos.z}, :done] {:report_encoder_position_raw, pos.x, pos.y, pos.z}, :done]

View File

@ -88,7 +88,7 @@ defmodule Farmbot.Repo do
# Copy configs # Copy configs
[current, _] = repos [current, _] = repos
copy_configs(current) :ok = copy_configs(current)
{:ok, %{repos: repos, needs_hard_sync: needs_hard_sync, timer: start_timer()}} {:ok, %{repos: repos, needs_hard_sync: needs_hard_sync, timer: start_timer()}}
end end
@ -118,11 +118,11 @@ defmodule Farmbot.Repo do
def handle_call(:flip, _, %{repos: [repo_a, repo_b], needs_hard_sync: true} = state) do def handle_call(:flip, _, %{repos: [repo_a, repo_b], needs_hard_sync: true} = state) do
maybe_cancel_timer(state.timer) maybe_cancel_timer(state.timer)
destroy_all_sync_cmds() destroy_all_sync_cmds()
Logger.busy(1, "Syncing.") Logger.busy(1, "Syncing (hard sync)")
BotState.set_sync_status(:syncing) BotState.set_sync_status(:syncing)
do_sync_both(repo_a, repo_b) do_sync_both(repo_a, repo_b)
BotState.set_sync_status(:synced) BotState.set_sync_status(:synced)
copy_configs(repo_b) :ok = copy_configs(repo_b)
flip_repos_in_cs() flip_repos_in_cs()
Logger.success(1, "Sync complete.") Logger.success(1, "Sync complete.")
{:reply, :ok, %{state | repos: [repo_b, repo_a], needs_hard_sync: false, timer: start_timer()}} {:reply, :ok, %{state | repos: [repo_b, repo_a], needs_hard_sync: false, timer: start_timer()}}
@ -140,7 +140,7 @@ defmodule Farmbot.Repo do
flip_repos_in_cs() flip_repos_in_cs()
BotState.set_sync_status(:synced) BotState.set_sync_status(:synced)
copy_configs(repo_b) :ok = copy_configs(repo_b)
destroy_all_sync_cmds() destroy_all_sync_cmds()
Logger.success(1, "Sync complete.") Logger.success(1, "Sync complete.")
{:reply, repo_b, %{state | repos: [repo_b, repo_a], timer: start_timer()}} {:reply, repo_b, %{state | repos: [repo_b, repo_a], timer: start_timer()}}
@ -187,11 +187,12 @@ defmodule Farmbot.Repo do
repo.all(Peripheral) repo.all(Peripheral)
|> Enum.all?(fn %{mode: mode, pin: pin} -> |> Enum.all?(fn %{mode: mode, pin: pin} ->
mode = if mode == 0, do: :digital, else: :analog mode = if mode == 0, do: :digital, else: :analog
# Logger.busy 3, "Reading peripheral (#{pin} - #{mode})" Logger.busy 3, "Reading peripheral (#{pin} - #{mode})"
Farmbot.Firmware.read_pin(pin, mode) Farmbot.Firmware.read_pin(pin, mode)
end) end)
Farmbot.FarmEvent.Manager.register_events repo.all(Farmbot.Repo.FarmEvent) Farmbot.FarmEvent.Manager.register_events repo.all(Farmbot.Repo.FarmEvent)
:ok
end end
defp destroy_all_sync_cmds do defp destroy_all_sync_cmds do