diff --git a/farmbot_firmware/lib/farmbot_firmware.ex b/farmbot_firmware/lib/farmbot_firmware.ex index b4acbfda..1ca7ce01 100644 --- a/farmbot_firmware/lib/farmbot_firmware.ex +++ b/farmbot_firmware/lib/farmbot_firmware.ex @@ -77,29 +77,6 @@ defmodule FarmbotFirmware do def handle_call({"166", {:parameter_write, [some_param: 100.00]}}, _from, state) and reply with `:ok | {:error, term()}` - - # VCR - - This server can save all the input and output gcodes to a text file for - further external analysis or playback later. - - ## Using VCR mode - - The server can be started in VCR mode by doing: - - FarmbotFirmware.start_link([transport: FarmbotFirmware.StubTransport, vcr_path: "/tmp/vcr.txt"], []) - - or can be started at runtime: - - FarmbotFirmware.enter_vcr_mode(firmware_server, "/tmp/vcr.txt") - - in either case the VCR recording needs to be stopped: - - FarmbotFirmware.exit_vcr_mode(firmware_server) - - VCRs can later be played back: - - FarmbotFirmware.VCR.playback!("/tmp/vcr.txt") """ use GenServer require Logger @@ -129,7 +106,6 @@ defmodule FarmbotFirmware do :command_queue, :caller_pid, :current, - :vcr_fd, :reset, :reset_pid ] @@ -146,7 +122,6 @@ defmodule FarmbotFirmware do command_queue: [{pid(), GCODE.t()}], caller_pid: nil | pid, current: nil | GCODE.t(), - vcr_fd: nil | File.io_device(), reset: module(), reset_pid: nil | pid() } @@ -209,22 +184,6 @@ defmodule FarmbotFirmware do GenServer.call(server, :reset) end - @doc """ - Sets the Firmware server to record input and output GCODES - to a pair of text files. - """ - def enter_vcr_mode(server \\ __MODULE__, tape_path) do - GenServer.call(server, {:enter_vcr_mode, tape_path}) - end - - @doc """ - Sets the Firmware server to stop recording input and output - GCODES. - """ - def exit_vcr_mode(server \\ __MODULE__) do - GenServer.cast(server, :exit_vcr_mode) - end - @doc """ Starting the Firmware server requires at least: * `:transport` - a module implementing the Transport GenServer behaviour. @@ -252,18 +211,6 @@ defmodule FarmbotFirmware do # probably? reset = Keyword.get(args, :reset) || FarmbotFirmware.NullReset - vcr_fd = - case Keyword.get(args, :vcr_path) do - nil -> - nil - - tape_path -> - {:ok, vcr_fd} = - File.open(tape_path, [:binary, :append, :exclusive, :write]) - - vcr_fd - end - # Add an anon function that transport implementations should call. fw = self() fun = fn {_, _} = code -> GenServer.cast(fw, code) end @@ -279,8 +226,7 @@ defmodule FarmbotFirmware do reset: reset, reset_pid: nil, command_queue: [], - configuration_queue: [], - vcr_fd: vcr_fd + configuration_queue: [] } send(self(), :timeout) @@ -358,7 +304,6 @@ defmodule FarmbotFirmware do } _ = side_effects(new_state, :handle_output_gcode, [{state.tag, code}]) - _ = vcr_write(state, :out, {state.tag, code}) {:noreply, new_state} @@ -374,7 +319,6 @@ defmodule FarmbotFirmware do :ok -> new_state = %{state | current: code, configuration_queue: rest} _ = side_effects(new_state, :handle_output_gcode, [{state.tag, code}]) - _ = vcr_write(state, :out, {state.tag, code}) {:noreply, new_state} error -> @@ -411,7 +355,6 @@ defmodule FarmbotFirmware do } _ = side_effects(new_state, :handle_output_gcode, [{state.tag, code}]) - _ = vcr_write(state, :out, {state.tag, code}) for {pid, _code} <- rest, do: send(pid, {state.tag, {:report_busy, []}}) {:noreply, new_state} @@ -482,17 +425,7 @@ defmodule FarmbotFirmware do _from, %{status: s} = state ) do - {:reply, {:error, s}, state} - end - - def handle_call({:enter_vcr_mode, tape_path}, _from, state) do - with {:ok, vcr_fd} <- - File.open(tape_path, [:binary, :append, :exclusive, :write]) do - {:reply, :ok, %{state | vcr_fd: vcr_fd}} - else - error -> - {:reply, error, state} - end + {:reply, {:error, "Can't open transport in #{inspect(s)} state"}, state} end def handle_call({tag, {kind, args}}, from, state) do @@ -565,388 +498,10 @@ defmodule FarmbotFirmware do end end - def handle_cast(:exit_vcr_mode, state) do - state.vcr_fd && File.close(state.vcr_fd) - {:noreply, %{state | vcr_fd: nil}} - end - # Extracts tag def handle_cast({tag, {_, _} = code}, state) do _ = side_effects(state, :handle_input_gcode, [{tag, code}]) - _ = vcr_write(state, :in, {tag, code}) - handle_report(code, %{state | tag: tag}) - end - - @doc false - @spec handle_report({GCODE.report_kind(), GCODE.args()}, state) :: - {:noreply, state()} - def handle_report({:report_emergency_lock, []} = code, state) do - Logger.info("Emergency lock") - if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) - for {pid, _code} <- state.command_queue, do: send(pid, code) - - send(self(), :timeout) - {:noreply, goto(%{state | current: nil, caller_pid: nil}, :emergency_lock)} - end - - # "ARDUINO STARTUP COMPLETE" => goto(:boot, :no_config) - def handle_report( - {:unknown, [_, "ARDUINO", "STARTUP", "COMPLETE"]}, - %{status: :boot} = state - ) do - Logger.info("ARDUINO STARTUP COMPLETE (text) transport=#{state.transport}") - handle_report({:report_no_config, []}, state) - end - - def handle_report( - {:report_idle, []}, - %{status: :boot} = state - ) do - Logger.info("ARDUINO STARTUP COMPLETE (idle) transport=#{state.transport}") - handle_report({:report_no_config, []}, state) - end - - def handle_report( - {:report_debug_message, ["ARDUINO STARTUP COMPLETE"]}, - %{status: :boot} = state - ) do - Logger.info("ARDUINO STARTUP COMPLETE (r99) transport=#{state.transport}") - handle_report({:report_no_config, []}, state) - end - - # report_no_config => goto(_, :no_config) - def handle_report({:report_no_config, []}, %{status: _} = state) do - Logger.warn(":report_no_config received") - tag = state.tag || "0" - loaded_params = side_effects(state, :load_params, []) || [] - - param_commands = - Enum.reduce(loaded_params, [], fn {param, val}, acc -> - if val, do: acc ++ [{:parameter_write, [{param, val}]}], else: acc - end) - - to_process = - [{:software_version_read, []} | param_commands] ++ - [ - {:parameter_write, [{:param_use_eeprom, 0.0}]}, - {:parameter_write, [{:param_config_ok, 1.0}]}, - {:parameter_read_all, []} - ] - - to_process = - if loaded_params[:movement_home_at_boot_z] == 1, - do: to_process ++ [{:command_movement_find_home, [:z]}], - else: to_process - - to_process = - if loaded_params[:movement_home_at_boot_y] == 1, - do: to_process ++ [{:command_movement_find_home, [:y]}], - else: to_process - - to_process = - if loaded_params[:movement_home_at_boot_x] == 1, - do: to_process ++ [{:command_movement_find_home, [:x]}], - else: to_process - - send(self(), :timeout) - - {:noreply, - goto(%{state | tag: tag, configuration_queue: to_process}, :configuration)} - end - - def handle_report({:report_debug_message, msg}, state) do - side_effects(state, :handle_debug_message, [msg]) - {:noreply, state} - end - - def handle_report(report, %{status: :boot} = state) do - Logger.debug(["still in state: :boot ", inspect(report)]) - {:noreply, state} - end - - # an idle report while there is a current command running - # should not count. - def handle_report({:report_idle, []}, %{current: c} = state) - when is_tuple(c) do - if state.caller_pid, - do: send(state.caller_pid, {state.tag, {:report_busy, []}}) - - for {pid, _code} <- state.command_queue, - do: send(pid, {state.tag, {:report_busy, []}}) - - {:noreply, state} - end - - # report_idle => goto(_, :idle) - def handle_report({:report_idle, []}, %{status: _} = state) do - for {pid, _code} <- state.command_queue, - do: send(pid, {state.tag, {:report_busy, []}}) - - side_effects(state, :handle_busy, [false]) - side_effects(state, :handle_idle, [true]) - send(self(), :timeout) - {:noreply, goto(%{state | caller_pid: nil, current: nil}, :idle)} - end - - def handle_report({:report_begin, []} = code, state) do - if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) - - for {pid, _code} <- state.command_queue, - do: send(pid, {state.tag, {:report_busy, []}}) - - {:noreply, goto(state, :begin)} - end - - def handle_report({:report_success, []} = code, state) do - if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) - - for {pid, _code} <- state.command_queue, - do: send(pid, {state.tag, {:report_busy, []}}) - - new_state = %{state | current: nil, caller_pid: nil} - side_effects(state, :handle_busy, [false]) - send(self(), :timeout) - {:noreply, goto(new_state, :idle)} - end - - def handle_report({:report_busy, []} = code, state) do - if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) - - for {pid, _code} <- state.command_queue, - do: send(pid, {state.tag, {:report_busy, []}}) - - side_effects(state, :handle_busy, [true]) - {:noreply, goto(state, :busy)} - end - - def handle_report( - {:report_error, _} = code, - %{status: :configuration} = state - ) do - if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) - - for {pid, _code} <- state.command_queue, - do: send(pid, {state.tag, {:report_busy, []}}) - - side_effects(state, :handle_busy, [false]) - {:stop, {:error, state.current}, state} - end - - def handle_report({:report_error, _} = code, state) do - if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) - - for {pid, _code} <- state.command_queue, - do: send(pid, {state.tag, {:report_busy, []}}) - - side_effects(state, :handle_busy, [false]) - send(self(), :timeout) - {:noreply, %{state | caller_pid: nil, current: nil}} - end - - def handle_report( - {:report_invalid, []} = code, - %{status: :configuration} = state - ) do - if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) - - for {pid, _code} <- state.command_queue, - do: send(pid, {state.tag, {:report_busy, []}}) - - {:stop, {:error, state.current}, state} - end - - def handle_report({:report_invalid, []} = code, state) do - if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) - - for {pid, _code} <- state.command_queue, - do: send(pid, {state.tag, {:report_busy, []}}) - - send(self(), :timeout) - {:noreply, %{state | caller_pid: nil, current: nil}} - end - - def handle_report( - {:report_retry, []} = code, - %{status: :configuration} = state - ) do - Logger.warn("Retrying configuration command: #{inspect(code)}") - {:noreply, state} - end - - def handle_report({:report_retry, []} = code, state) do - if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) - - for {pid, _code} <- state.command_queue, - do: send(pid, {state.tag, {:report_busy, []}}) - - {:noreply, state} - end - - def handle_report({:report_parameter_value, param} = code, state) do - if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) - - for {pid, _code} <- state.command_queue, - do: send(pid, {state.tag, {:report_busy, []}}) - - side_effects(state, :handle_parameter_value, [param]) - {:noreply, state} - end - - def handle_report({:report_calibration_parameter_value, param} = _code, state) do - to_process = [{:parameter_write, param}] - side_effects(state, :handle_parameter_value, [param]) - side_effects(state, :handle_parameter_calibration_value, [param]) - send(self(), :timeout) - - {:noreply, - goto( - %{state | tag: state.tag, configuration_queue: to_process}, - :configuration - )} - end - - # report_parameters_complete => goto(:configuration, :idle) - def handle_report( - {:report_parameters_complete, []}, - %{status: status} = state - ) - when status in [:begin, :configuration] do - {:noreply, goto(state, :idle)} - end - - def handle_report(_, %{status: :no_config} = state) do - {:noreply, state} - end - - def handle_report({:report_position, position} = code, state) do - if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) - - for {pid, _code} <- state.command_queue, - do: send(pid, {state.tag, {:report_busy, []}}) - - side_effects(state, :handle_position, [position]) - {:noreply, state} - end - - def handle_report({:report_load, load} = code, state) do - if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) - - for {pid, _code} <- state.command_queue, - do: send(pid, {state.tag, {:report_busy, []}}) - - side_effects(state, :handle_load, [load]) - {:noreply, state} - end - - def handle_report({:report_axis_state, axis_state} = code, state) do - if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) - - for {pid, _code} <- state.command_queue, - do: send(pid, {state.tag, {:report_busy, []}}) - - side_effects(state, :handle_axis_state, [axis_state]) - {:noreply, state} - end - - def handle_report({:report_axis_timeout, [axis]} = code, state) do - if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) - - for {pid, _code} <- state.command_queue, - do: send(pid, {state.tag, {:report_busy, []}}) - - side_effects(state, :handle_axis_timeout, [axis]) - {:noreply, state} - end - - def handle_report( - {:report_calibration_state, calibration_state} = code, - state - ) do - if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) - - for {pid, _code} <- state.command_queue, - do: send(pid, {state.tag, {:report_busy, []}}) - - side_effects(state, :handle_calibration_state, [calibration_state]) - {:noreply, state} - end - - def handle_report({:report_home_complete, axis} = code, state) do - if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) - - for {pid, _code} <- state.command_queue, - do: send(pid, {state.tag, {:report_busy, []}}) - - side_effects(state, :handle_home_complete, axis) - {:noreply, state} - end - - def handle_report({:report_position_change, position} = code, state) do - if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) - - for {pid, _code} <- state.command_queue, - do: send(pid, {state.tag, {:report_busy, []}}) - - side_effects(state, :handle_position_change, [position]) - {:noreply, state} - end - - def handle_report({:report_encoders_scaled, encoders} = code, state) do - if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) - - for {pid, _code} <- state.command_queue, - do: send(pid, {state.tag, {:report_busy, []}}) - - side_effects(state, :handle_encoders_scaled, [encoders]) - {:noreply, state} - end - - def handle_report({:report_encoders_raw, encoders} = code, state) do - if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) - - for {pid, _code} <- state.command_queue, - do: send(pid, {state.tag, {:report_busy, []}}) - - side_effects(state, :handle_encoders_raw, [encoders]) - {:noreply, state} - end - - def handle_report({:report_end_stops, end_stops} = code, state) do - if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) - - for {pid, _code} <- state.command_queue, - do: send(pid, {state.tag, {:report_busy, []}}) - - side_effects(state, :handle_end_stops, [end_stops]) - {:noreply, state} - end - - def handle_report({:report_pin_value, value} = code, state) do - if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) - - for {pid, _code} <- state.command_queue, - do: send(pid, {state.tag, {:report_busy, []}}) - - side_effects(state, :handle_pin_value, [value]) - {:noreply, state} - end - - def handle_report({:report_software_version, version} = code, state) do - if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) - - for {pid, _code} <- state.command_queue, - do: send(pid, {state.tag, {:report_busy, []}}) - - side_effects(state, :handle_software_version, [version]) - {:noreply, state} - end - - # NOOP - def handle_report({:report_echo, _}, state), do: {:noreply, state} - - def handle_report({_kind, _args} = code, state) do - Logger.warn("unknown code for #{state.status}: #{inspect(code)}") - {:noreply, state} + FarmbotFirmware.ReportHandler.handle_report(code, %{state | tag: tag}) end @spec goto(state(), status()) :: state() @@ -994,35 +549,7 @@ defmodule FarmbotFirmware do end @spec side_effects(state, atom, GCODE.args()) :: any() - defp side_effects(%{side_effects: nil}, _function, _args), do: nil + defp side_effects(%{side_effects: nil}, _func, _args), do: nil - defp side_effects(%{side_effects: m}, function, args), - do: apply(m, function, args) - - @spec vcr_write(state, :in | :out, GCODE.t()) :: :ok - defp vcr_write(%{vcr_fd: nil}, _direction, _code), do: :ok - - defp vcr_write(state, :in, code), do: vcr_write(state, "<", code) - - defp vcr_write(state, :out, code), do: vcr_write(state, "\n>", code) - - defp vcr_write(state, direction, code) do - data = GCODE.encode(code) - time = :os.system_time(:second) - - current_data = - if state.current do - GCODE.encode({state.tag, state.current}) - else - "nil" - end - - state_data = - "#{state.status} | #{current_data} | #{inspect(state.caller_pid)}" - - IO.write( - state.vcr_fd, - direction <> " #{time} " <> data <> " state=" <> state_data <> "\n" - ) - end + defp side_effects(%{side_effects: m}, func, args), do: apply(m, func, args) end diff --git a/farmbot_firmware/lib/farmbot_firmware/report_handler.ex b/farmbot_firmware/lib/farmbot_firmware/report_handler.ex new file mode 100644 index 00000000..b6074f87 --- /dev/null +++ b/farmbot_firmware/lib/farmbot_firmware/report_handler.ex @@ -0,0 +1,375 @@ +defmodule FarmbotFirmware.ReportHandler do + require Logger + + @doc false + @spec handle_report({GCODE.report_kind(), GCODE.args()}, state) :: + {:noreply, state()} + def handle_report({:report_emergency_lock, []} = code, state) do + Logger.info("Emergency lock") + if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) + for {pid, _code} <- state.command_queue, do: send(pid, code) + + send(self(), :timeout) + {:noreply, goto(%{state | current: nil, caller_pid: nil}, :emergency_lock)} + end + + # "ARDUINO STARTUP COMPLETE" => goto(:boot, :no_config) + def handle_report( + {:unknown, [_, "ARDUINO", "STARTUP", "COMPLETE"]}, + %{status: :boot} = state + ) do + Logger.info("ARDUINO STARTUP COMPLETE (text) transport=#{state.transport}") + handle_report({:report_no_config, []}, state) + end + + def handle_report( + {:report_idle, []}, + %{status: :boot} = state + ) do + Logger.info("ARDUINO STARTUP COMPLETE (idle) transport=#{state.transport}") + handle_report({:report_no_config, []}, state) + end + + def handle_report( + {:report_debug_message, ["ARDUINO STARTUP COMPLETE"]}, + %{status: :boot} = state + ) do + Logger.info("ARDUINO STARTUP COMPLETE (r99) transport=#{state.transport}") + handle_report({:report_no_config, []}, state) + end + + # report_no_config => goto(_, :no_config) + def handle_report({:report_no_config, []}, %{status: _} = state) do + Logger.warn(":report_no_config received") + tag = state.tag || "0" + loaded_params = side_effects(state, :load_params, []) || [] + + param_commands = + Enum.reduce(loaded_params, [], fn {param, val}, acc -> + if val, do: acc ++ [{:parameter_write, [{param, val}]}], else: acc + end) + + to_process = + [{:software_version_read, []} | param_commands] ++ + [ + {:parameter_write, [{:param_use_eeprom, 0.0}]}, + {:parameter_write, [{:param_config_ok, 1.0}]}, + {:parameter_read_all, []} + ] + + to_process = + if loaded_params[:movement_home_at_boot_z] == 1, + do: to_process ++ [{:command_movement_find_home, [:z]}], + else: to_process + + to_process = + if loaded_params[:movement_home_at_boot_y] == 1, + do: to_process ++ [{:command_movement_find_home, [:y]}], + else: to_process + + to_process = + if loaded_params[:movement_home_at_boot_x] == 1, + do: to_process ++ [{:command_movement_find_home, [:x]}], + else: to_process + + send(self(), :timeout) + + {:noreply, + goto(%{state | tag: tag, configuration_queue: to_process}, :configuration)} + end + + def handle_report({:report_debug_message, msg}, state) do + side_effects(state, :handle_debug_message, [msg]) + {:noreply, state} + end + + def handle_report(report, %{status: :boot} = state) do + Logger.debug(["still in state: :boot ", inspect(report)]) + {:noreply, state} + end + + # an idle report while there is a current command running + # should not count. + def handle_report({:report_idle, []}, %{current: c} = state) + when is_tuple(c) do + if state.caller_pid, + do: send(state.caller_pid, {state.tag, {:report_busy, []}}) + + for {pid, _code} <- state.command_queue, + do: send(pid, {state.tag, {:report_busy, []}}) + + {:noreply, state} + end + + # report_idle => goto(_, :idle) + def handle_report({:report_idle, []}, %{status: _} = state) do + for {pid, _code} <- state.command_queue, + do: send(pid, {state.tag, {:report_busy, []}}) + + side_effects(state, :handle_busy, [false]) + side_effects(state, :handle_idle, [true]) + send(self(), :timeout) + {:noreply, goto(%{state | caller_pid: nil, current: nil}, :idle)} + end + + def handle_report({:report_begin, []} = code, state) do + if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) + + for {pid, _code} <- state.command_queue, + do: send(pid, {state.tag, {:report_busy, []}}) + + {:noreply, goto(state, :begin)} + end + + def handle_report({:report_success, []} = code, state) do + if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) + + for {pid, _code} <- state.command_queue, + do: send(pid, {state.tag, {:report_busy, []}}) + + new_state = %{state | current: nil, caller_pid: nil} + side_effects(state, :handle_busy, [false]) + send(self(), :timeout) + {:noreply, goto(new_state, :idle)} + end + + def handle_report({:report_busy, []} = code, state) do + if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) + + for {pid, _code} <- state.command_queue, + do: send(pid, {state.tag, {:report_busy, []}}) + + side_effects(state, :handle_busy, [true]) + {:noreply, goto(state, :busy)} + end + + def handle_report( + {:report_error, _} = code, + %{status: :configuration} = state + ) do + if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) + + for {pid, _code} <- state.command_queue, + do: send(pid, {state.tag, {:report_busy, []}}) + + side_effects(state, :handle_busy, [false]) + {:stop, {:error, state.current}, state} + end + + def handle_report({:report_error, _} = code, state) do + if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) + + for {pid, _code} <- state.command_queue, + do: send(pid, {state.tag, {:report_busy, []}}) + + side_effects(state, :handle_busy, [false]) + send(self(), :timeout) + {:noreply, %{state | caller_pid: nil, current: nil}} + end + + def handle_report( + {:report_invalid, []} = code, + %{status: :configuration} = state + ) do + if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) + + for {pid, _code} <- state.command_queue, + do: send(pid, {state.tag, {:report_busy, []}}) + + {:stop, {:error, state.current}, state} + end + + def handle_report({:report_invalid, []} = code, state) do + if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) + + for {pid, _code} <- state.command_queue, + do: send(pid, {state.tag, {:report_busy, []}}) + + send(self(), :timeout) + {:noreply, %{state | caller_pid: nil, current: nil}} + end + + def handle_report( + {:report_retry, []} = code, + %{status: :configuration} = state + ) do + Logger.warn("Retrying configuration command: #{inspect(code)}") + {:noreply, state} + end + + def handle_report({:report_retry, []} = code, state) do + if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) + + for {pid, _code} <- state.command_queue, + do: send(pid, {state.tag, {:report_busy, []}}) + + {:noreply, state} + end + + def handle_report({:report_parameter_value, param} = code, state) do + if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) + + for {pid, _code} <- state.command_queue, + do: send(pid, {state.tag, {:report_busy, []}}) + + side_effects(state, :handle_parameter_value, [param]) + {:noreply, state} + end + + def handle_report({:report_calibration_parameter_value, param} = _code, state) do + to_process = [{:parameter_write, param}] + side_effects(state, :handle_parameter_value, [param]) + side_effects(state, :handle_parameter_calibration_value, [param]) + send(self(), :timeout) + + {:noreply, + goto( + %{state | tag: state.tag, configuration_queue: to_process}, + :configuration + )} + end + + # report_parameters_complete => goto(:configuration, :idle) + def handle_report( + {:report_parameters_complete, []}, + %{status: status} = state + ) + when status in [:begin, :configuration] do + {:noreply, goto(state, :idle)} + end + + def handle_report(_, %{status: :no_config} = state) do + {:noreply, state} + end + + def handle_report({:report_position, position} = code, state) do + if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) + + for {pid, _code} <- state.command_queue, + do: send(pid, {state.tag, {:report_busy, []}}) + + side_effects(state, :handle_position, [position]) + {:noreply, state} + end + + def handle_report({:report_load, load} = code, state) do + if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) + + for {pid, _code} <- state.command_queue, + do: send(pid, {state.tag, {:report_busy, []}}) + + side_effects(state, :handle_load, [load]) + {:noreply, state} + end + + def handle_report({:report_axis_state, axis_state} = code, state) do + if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) + + for {pid, _code} <- state.command_queue, + do: send(pid, {state.tag, {:report_busy, []}}) + + side_effects(state, :handle_axis_state, [axis_state]) + {:noreply, state} + end + + def handle_report({:report_axis_timeout, [axis]} = code, state) do + if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) + + for {pid, _code} <- state.command_queue, + do: send(pid, {state.tag, {:report_busy, []}}) + + side_effects(state, :handle_axis_timeout, [axis]) + {:noreply, state} + end + + def handle_report( + {:report_calibration_state, calibration_state} = code, + state + ) do + if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) + + for {pid, _code} <- state.command_queue, + do: send(pid, {state.tag, {:report_busy, []}}) + + side_effects(state, :handle_calibration_state, [calibration_state]) + {:noreply, state} + end + + def handle_report({:report_home_complete, axis} = code, state) do + if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) + + for {pid, _code} <- state.command_queue, + do: send(pid, {state.tag, {:report_busy, []}}) + + side_effects(state, :handle_home_complete, axis) + {:noreply, state} + end + + def handle_report({:report_position_change, position} = code, state) do + if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) + + for {pid, _code} <- state.command_queue, + do: send(pid, {state.tag, {:report_busy, []}}) + + side_effects(state, :handle_position_change, [position]) + {:noreply, state} + end + + def handle_report({:report_encoders_scaled, encoders} = code, state) do + if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) + + for {pid, _code} <- state.command_queue, + do: send(pid, {state.tag, {:report_busy, []}}) + + side_effects(state, :handle_encoders_scaled, [encoders]) + {:noreply, state} + end + + def handle_report({:report_encoders_raw, encoders} = code, state) do + if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) + + for {pid, _code} <- state.command_queue, + do: send(pid, {state.tag, {:report_busy, []}}) + + side_effects(state, :handle_encoders_raw, [encoders]) + {:noreply, state} + end + + def handle_report({:report_end_stops, end_stops} = code, state) do + if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) + + for {pid, _code} <- state.command_queue, + do: send(pid, {state.tag, {:report_busy, []}}) + + side_effects(state, :handle_end_stops, [end_stops]) + {:noreply, state} + end + + def handle_report({:report_pin_value, value} = code, state) do + if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) + + for {pid, _code} <- state.command_queue, + do: send(pid, {state.tag, {:report_busy, []}}) + + side_effects(state, :handle_pin_value, [value]) + {:noreply, state} + end + + def handle_report({:report_software_version, version} = code, state) do + if state.caller_pid, do: send(state.caller_pid, {state.tag, code}) + + for {pid, _code} <- state.command_queue, + do: send(pid, {state.tag, {:report_busy, []}}) + + side_effects(state, :handle_software_version, [version]) + {:noreply, state} + end + + # NOOP + def handle_report({:report_echo, _}, state), do: {:noreply, state} + + def handle_report({_kind, _args} = code, state) do + Logger.warn("unknown code for #{state.status}: #{inspect(code)}") + {:noreply, state} + end +end