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

View File

@ -1,2 +1,2 @@
erlang 20.0
erlang 20.1
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]
# 30 minutes.
# @refresh_time 1.8e+6 |> round()
@refresh_time 5_000
@refresh_time 1.8e+6 |> round()
# @refresh_time 5_000
@doc false
def start_link() do

View File

@ -188,10 +188,18 @@ defmodule Farmbot.BotState.Transport.AMQP do
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
def handle_celery_script(payload, _state) 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
end
end

View File

@ -7,32 +7,32 @@ defmodule Farmbot.Firmware do
@doc "Move the bot to a position."
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
@doc "Calibrate an axis."
def calibrate(axis) do
GenStage.call(__MODULE__, {:calibrate, [axis]}, :infinity)
GenStage.call(__MODULE__, {:calibrate, [axis]}, 120_000)
end
@doc "Find home on an axis."
def find_home(axis) do
GenStage.call(__MODULE__, {:find_home, [axis]}, :infinity)
GenStage.call(__MODULE__, {:find_home, [axis]}, 120_000)
end
@doc "Home every axis."
def home_all() do
GenStage.call(__MODULE__, {:home_all, []}, :infinity)
GenStage.call(__MODULE__, {:home_all, []}, 120_000)
end
@doc "Home an axis."
def home(axis) do
GenStage.call(__MODULE__, {:home, [axis]}, :infinity)
GenStage.call(__MODULE__, {:home, [axis]}, 120_000)
end
@doc "Manually set an axis's current position to zero."
def zero(axis) do
GenStage.call(__MODULE__, {:zero, [axis]}, :infinity)
GenStage.call(__MODULE__, {:zero, [axis]}, 120_000)
end
@doc """
@ -40,12 +40,12 @@ defmodule Farmbot.Firmware do
For a list of paramaters see `Farmbot.Firmware.Gcode.Param`
"""
def update_param(param, val) do
GenStage.call(__MODULE__, {:update_param, [param, val]}, :infinity)
GenStage.call(__MODULE__, {:update_param, [param, val]}, 120_000)
end
@doc false
def read_all_params do
GenStage.call(__MODULE__, {:read_all_params, []}, :infinity)
GenStage.call(__MODULE__, {:read_all_params, []}, 120_000)
end
@doc """
@ -53,42 +53,42 @@ defmodule Farmbot.Firmware do
For a list of paramaters see `Farmbot.Firmware.Gcode.Param`
"""
def read_param(param) do
GenStage.call(__MODULE__, {:read_param, [param]}, :infinity)
GenStage.call(__MODULE__, {:read_param, [param]}, 120_000)
end
@doc "Emergency lock Farmbot."
def emergency_lock() do
GenStage.call(__MODULE__, {:emergency_lock, []}, :infinity)
GenStage.call(__MODULE__, {:emergency_lock, []}, 120_000)
end
@doc "Unlock Farmbot from Emergency state."
def emergency_unlock() do
GenStage.call(__MODULE__, {:emergency_unlock, []}, :infinity)
GenStage.call(__MODULE__, {:emergency_unlock, []}, 120_000)
end
@doc "Set a pin mode (:input | :output)"
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
@doc "Read a pin."
def read_pin(pin, mode) do
GenStage.call(__MODULE__, {:read_pin, [pin, mode]}, :infinity)
GenStage.call(__MODULE__, {:read_pin, [pin, mode]}, 120_000)
end
@doc "Write a pin."
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
@doc "Request version."
def request_software_version do
GenStage.call(__MODULE__, {:request_software_version, []}, :infinity)
GenStage.call(__MODULE__, {:request_software_version, []}, 120_000)
end
@doc "Set angle of a servo pin."
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
@doc "Start the firmware services."
@ -152,19 +152,18 @@ defmodule Farmbot.Firmware do
def handle_info(:timeout, state) do
case state.current do
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}) "
case apply(state.handler_mod, fun, [state.handler | args]) do
:ok ->
timer = Process.send_after(self(), :timeout, state.timeout_ms)
{:noreply, [], %{state | current: current, timer: timer}}
{:error, _} = res ->
GenStage.reply(from, res)
do_reply(state, res)
{:noreply, [], %{state | current: nil, queue: :queue.new()}}
end
{:noreply, [], %{state | timer: nil}}
end
end
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
def handle_call({fun, args}, from, state) do
current = struct(Current, from: from, fun: fun, args: args)
if :queue.is_empty(state.queue) do
do_begin_cmd(current, state, [])
next_current = struct(Current, from: from, fun: fun, args: args)
current_current = state.current
if current_current do
do_queue_cmd(next_current, state)
else
do_queue_cmd(current, state)
do_begin_cmd(next_current, state, [])
end
end
defp do_begin_cmd(%Current{fun: fun, args: args, from: _from} = current, state, dispatch) do
# Logger.debug 3, "Firmware command: #{fun}#{inspect(args)}"
defp do_begin_cmd(%Current{fun: fun, args: args, from: from} = current, state, dispatch) do
Logger.busy 3, "FW Starting: #{fun}: #{inspect from}"
case apply(state.handler_mod, fun, [state.handler | args]) do
:ok ->
@ -189,22 +189,26 @@ defmodule Farmbot.Firmware do
timer = Process.send_after(self(), :timeout, state.timeout_ms)
{:noreply, dispatch, %{state | current: current, timer: timer}}
{:error, _} = res ->
{:reply, res, dispatch, %{state | current: nil, queue: :queue.new()}}
do_reply(%{state | current: current}, res)
{:noreply, dispatch, %{state | current: nil}}
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)
{:noreply, [], %{state | queue: new_q}}
end
def handle_events(gcodes, _from, state) do
{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
{{:value, current}, new_queue} ->
do_begin_cmd(current, %{state | queue: new_queue, current: current}, diffs)
{:empty, queue} ->
{{:value, next_current}, new_queue} ->
do_begin_cmd(next_current, %{state | queue: new_queue, current: next_current}, diffs)
{:empty, queue} -> # nothing to do if the queue is empty.
{:noreply, diffs, %{state | queue: queue}}
end
else
@ -242,7 +246,7 @@ defmodule Farmbot.Firmware do
end
end)
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}}
else
{nil, state}
@ -322,13 +326,13 @@ defmodule Farmbot.Firmware do
Farmbot.BotState.set_busy(false)
if state.current do
# 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."
timer = Process.send_after(self(), :timeout, state.timeout_ms)
{nil, %{state | timer: timer}}
else
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}}
end
else
@ -379,7 +383,7 @@ defmodule Farmbot.Firmware do
defp handle_gcode(:done, state) do
maybe_cancel_timer(state.timer)
if state.current do
GenStage.reply(state.current.from, :ok)
do_reply(state, :ok)
{nil, %{state | current: nil}}
else
{nil, state}
@ -389,7 +393,7 @@ defmodule Farmbot.Firmware do
defp handle_gcode(:report_emergency_lock, state) do
Farmbot.System.GPIO.Leds.led_status_err
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}}
else
{:informational_settings, %{locked: true}, state}
@ -471,4 +475,15 @@ defmodule Farmbot.Firmware do
report_calibration_callback(tries - 1, param, val)
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

View File

@ -14,8 +14,8 @@ defmodule Farmbot.Firmware.StubHandler do
GenStage.start_link(__MODULE__, [])
end
def move_absolute(handler, pos, x_speed, y_speed, z_speed) do
GenStage.call(handler, {:move_absolute, pos, x_speed, y_speed, z_speed})
def move_absolute(handler, pos, x_spd, y_spd, z_spd) do
GenStage.call(handler, {:move_absolute, pos, x_spd, y_spd, z_spd}, 120_000)
end
def calibrate(handler, axis) do
@ -116,6 +116,7 @@ defmodule Farmbot.Firmware.StubHandler do
end
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},
{:report_encoder_position_scaled, pos.x, pos.y, pos.z},
{:report_encoder_position_raw, pos.x, pos.y, pos.z}, :done]

View File

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