From 194bcbdffb1251776bd53496c73ee7f762e3dc3a Mon Sep 17 00:00:00 2001 From: connor rigby Date: Mon, 21 Aug 2017 13:31:32 -0700 Subject: [PATCH] start implementing fw again --- .gitignore | 1 + config/config.exs | 6 +- lib/farmbot/bot_state/configuration.ex | 6 - .../bot_state/informational_settings.ex | 16 +- lib/farmbot/bot_state/location_data.ex | 38 +- lib/farmbot/bot_state/supervisor.ex | 4 +- lib/farmbot/bot_state/transport/gen_mqtt.ex | 1 + lib/farmbot/firmware.ex | 75 +++ .../{serial => firmware}/gcode/parser.ex | 0 lib/farmbot/firmware/handler.ex | 3 + lib/farmbot/firmware/supervisor.ex | 12 +- lib/farmbot/firmware/uart_handler.ex | 79 +++ lib/farmbot/lib/maths.ex | 30 -- lib/farmbot/serial/handler.ex | 498 ------------------ 14 files changed, 223 insertions(+), 546 deletions(-) create mode 100644 lib/farmbot/firmware.ex rename lib/farmbot/{serial => firmware}/gcode/parser.ex (100%) create mode 100644 lib/farmbot/firmware/handler.ex create mode 100644 lib/farmbot/firmware/uart_handler.ex delete mode 100644 lib/farmbot/lib/maths.ex delete mode 100644 lib/farmbot/serial/handler.ex diff --git a/.gitignore b/.gitignore index 8fb5c913..0d2b9fc7 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,4 @@ fwup-key.priv .env # secret config stuffs for dev environment. config/auth_secret.exs +flash_fw.sh diff --git a/config/config.exs b/config/config.exs index d7d663c1..322e2b04 100644 --- a/config/config.exs +++ b/config/config.exs @@ -29,14 +29,18 @@ config :fs, path: "/tmp/images" config :farmbot, :init, [ ] # Transports. -config :farmbot, :transport, [ ] +config :farmbot, :transport, [ ] # Configure Farmbot Behaviours. config :farmbot, :behaviour, [ authorization: Farmbot.Bootstrap.Authorization, + firmware: Farmbot.Firmware.UartHandler ] +config :farmbot, :uart_handler, [ + tty: "/dev/ttyACM0" +] # import config specific to our nerves_target IO.puts "using #{target} - #{env} configuration." diff --git a/lib/farmbot/bot_state/configuration.ex b/lib/farmbot/bot_state/configuration.ex index 8e2c37ec..ea8b4ef5 100644 --- a/lib/farmbot/bot_state/configuration.ex +++ b/lib/farmbot/bot_state/configuration.ex @@ -3,17 +3,11 @@ defmodule Farmbot.BotState.Configuration do defstruct [ os_auto_update: false, - steps_per_mm_x: 25, - steps_per_mm_y: 25, - steps_per_mm_z: 25 ] @typedoc "Config data" @type t :: %__MODULE__{ os_auto_update: boolean, - steps_per_mm_x: number, - steps_per_mm_y: number, - steps_per_mm_z: number } use Farmbot.BotState.Lib.Partition diff --git a/lib/farmbot/bot_state/informational_settings.ex b/lib/farmbot/bot_state/informational_settings.ex index a9b44dc0..46f24787 100644 --- a/lib/farmbot/bot_state/informational_settings.ex +++ b/lib/farmbot/bot_state/informational_settings.ex @@ -11,7 +11,7 @@ defmodule Farmbot.BotState.InformationalSettings do :sync_now, :synced, :syncing, - :unknown + :unknown, ] @typedoc "Status of the sync bar" @@ -39,7 +39,8 @@ defmodule Farmbot.BotState.InformationalSettings do firmware_version: :disconnected, throttled: false, private_ip: :disconnected, - sync_status: SyncStatus.status(:sync_now) + sync_status: SyncStatus.status(:sync_now), + busy: true ] @typedoc "Information Settings." @@ -48,8 +49,17 @@ defmodule Farmbot.BotState.InformationalSettings do firmware_version: :disconnected | Version.version, throttled: boolean, private_ip: :disconnected | Version.version, - sync_status: SyncStatus.t + sync_status: SyncStatus.t, + busy: boolean } use Farmbot.BotState.Lib.Partition + + def set_busy(part, busy) do + GenServer.call(part, {:set_busy, busy}) + end + + def partition_call({:set_busy, busy}, _, state) do + {:reply, :ok, %{state | busy: busy}} + end end diff --git a/lib/farmbot/bot_state/location_data.ex b/lib/farmbot/bot_state/location_data.ex index 735d819a..aa33df72 100644 --- a/lib/farmbot/bot_state/location_data.ex +++ b/lib/farmbot/bot_state/location_data.ex @@ -25,15 +25,49 @@ defmodule Farmbot.BotState.LocationData do defstruct [ position: Vec3.new(-1, -1, -1), scaled_encoders: Vec3.new(-1, -1, -1), - raw_encoders: Vec3.new(-1, -1, -1) + raw_encoders: Vec3.new(-1, -1, -1), + end_stops: "-1-1-1-1-1-1" ] @typedoc "Data about the bot's position." @type t :: %__MODULE__{ position: Vec3.t, scaled_encoders: Vec3.t, - raw_encoders: Vec3.t + raw_encoders: Vec3.t, + end_stops: binary } use Farmbot.BotState.Lib.Partition + + def report_current_position(part, x, y, z) do + GenServer.call(part, {:report_current_position, Vec3.new(x,y,z)}) + end + + def report_encoder_position_scaled(part, x, y, z) do + GenServer.call(part, {:report_encoder_position_scaled, Vec3.new(x,y,z)}) + end + + def report_encoder_position_raw(part, x, y, z) do + GenServer.call(part, {:report_encoder_position_raw, Vec3.new(x,y,z)}) + end + + def report_end_stops(part, xa, xb, ya, yb, za, zb) do + GenServer.call(part, {:report_end_stops, "#{xa}#{xb}#{ya}#{yb}#{za}#{zb}"}) + end + + def partition_call({:report_current_position, pos}, _, state) do + {:reply, :ok, %{state | position: pos}} + end + + def partition_call({:report_encoder_position_scaled, pos}, _, state) do + {:reply, :ok, %{state | scaled_encoders: pos}} + end + + def partition_call({:report_encoder_position_raw, pos}, _, state) do + {:reply, :ok, %{state | raw_encoders: pos}} + end + + def partition_call({:report_end_stops, stops}, _, state) do + {:reply, :ok, %{state | end_stops: stops}} + end end diff --git a/lib/farmbot/bot_state/supervisor.ex b/lib/farmbot/bot_state/supervisor.ex index b14401b7..f850d774 100644 --- a/lib/farmbot/bot_state/supervisor.ex +++ b/lib/farmbot/bot_state/supervisor.ex @@ -26,10 +26,10 @@ defmodule Farmbot.BotState.Supervisor do worker(McuParams, [BotState, [name: McuParams]]), # Transport part. - supervisor(Transport.Supervisor, [token, BotState, [name: Transport.Supervisor]]) + supervisor(Transport.Supervisor, [token, BotState, [name: Transport.Supervisor]]), # Firmware part. - # supervisor(FW.Supervisor, [name: FW.Supervisor]) + supervisor(FW.Supervisor, [BotState, InformationalSettings, Configuration, LocationData, McuParams, [name: FW.Supervisor]]) ] # We set one_for_all here, since all of these link to `BotState` supervise(children, [strategy: :one_for_all]) diff --git a/lib/farmbot/bot_state/transport/gen_mqtt.ex b/lib/farmbot/bot_state/transport/gen_mqtt.ex index 67ec9edb..fe1a64ad 100644 --- a/lib/farmbot/bot_state/transport/gen_mqtt.ex +++ b/lib/farmbot/bot_state/transport/gen_mqtt.ex @@ -60,6 +60,7 @@ defmodule Farmbot.BotState.Transport.GenMqtt do def handle_info({:bot_state, bs}, state) do json = Poison.encode!(bs) GenMQTT.publish(self(), status_topic(state.bot), json, 0, false) + {:noreply, state} end def handle_cast(_, %{connected: false} = state), do: {:noreply, state} diff --git a/lib/farmbot/firmware.ex b/lib/farmbot/firmware.ex new file mode 100644 index 00000000..64bbab01 --- /dev/null +++ b/lib/farmbot/firmware.ex @@ -0,0 +1,75 @@ +defmodule Farmbot.Firmware do + @moduledoc "Allows communication with the firmware." + + use GenServer + require Logger + + alias Farmbot.BotState + alias Farmbot.BotState.{ + InformationalSettings, + LocationData + } + + def handle_gcode(firmware, code), do: GenServer.call(firmware, {:handle_gcode, code}) + + def start_link(bot_state, informational_settings, configuration, location_data, mcu_params, handler_mod, opts) do + GenServer.start_link(__MODULE__, [bot_state, informational_settings, configuration, location_data, mcu_params, handler_mod], opts) + end + + defmodule State do + defstruct [ + :bot_state, + :informational_settings, + :configuration, + :location_data, + :mcu_params, + :handler_mod, + :handler + ] + end + + def init([bot_state, informational_settings, configuration, location_data, mcu_params, handler_mod]) do + {:ok, handler} = handler_mod.start_link(self(), name: handler_mod) + Process.link(handler) + s = %State{ + bot_state: bot_state, + informational_settings: informational_settings, + configuration: configuration, + location_data: location_data, + mcu_params: mcu_params, + handler_mod: handler_mod, + handler: handler + } + {:ok, s} + end + + def handle_call({:handle_gcode, :idle}, _, state) do + reply = InformationalSettings.set_busy(state.informational_settings, false) + {:reply, reply, state} + end + + def handle_call({:handle_gcode, {:report_current_position, x, y, z}}, _, state) do + reply = LocationData.report_current_position(state.location_data, x, y, z) + {:reply, reply, state} + end + + def handle_call({:handle_gcode, {:report_encoder_position_scaled, x, y, z}}, _, state) do + reply = LocationData.report_encoder_position_scaled(state.location_data, x, y, z) + {:reply, reply, state} + end + + def handle_call({:handle_gcode, {:report_encoder_position_raw, x, y, z}}, _, state) do + reply = LocationData.report_encoder_position_raw(state.location_data, x, y, z) + {:reply, reply, state} + end + + def handle_call({:handle_gcode, {:report_end_stops, xa, xb, ya, yb, za, zb}}, _, state) do + reply = LocationData.report_end_stops(state.location_data, xa, xb, ya, yb, za, zb) + {:reply, reply, state} + end + + def handle_call({:handle_gcode, code}, _, state) do + Logger.warn "Got misc gcode: #{inspect code}" + {:reply, {:error, :unhandled}, state} + end +end diff --git a/lib/farmbot/serial/gcode/parser.ex b/lib/farmbot/firmware/gcode/parser.ex similarity index 100% rename from lib/farmbot/serial/gcode/parser.ex rename to lib/farmbot/firmware/gcode/parser.ex diff --git a/lib/farmbot/firmware/handler.ex b/lib/farmbot/firmware/handler.ex new file mode 100644 index 00000000..a11dda4c --- /dev/null +++ b/lib/farmbot/firmware/handler.ex @@ -0,0 +1,3 @@ +defmodule Farmbot.Firmware.Handler do + @moduledoc "Handler for a firmware." +end diff --git a/lib/farmbot/firmware/supervisor.ex b/lib/farmbot/firmware/supervisor.ex index d06e6328..010e1e55 100644 --- a/lib/farmbot/firmware/supervisor.ex +++ b/lib/farmbot/firmware/supervisor.ex @@ -2,15 +2,19 @@ defmodule Farmbot.Firmware.Supervisor do @moduledoc "Supervises the firmware handler." use Supervisor + @error_msg "Please configure a Firmware handler." + @doc "Start the Firmware Supervisor." - def start_link(args, opts \\ []) do - Supervisor.start_link(__MODULE__, args, opts) + def start_link(bot_state, informational_settings, configuration, location_data, mcu_params, opts \\ []) do + Supervisor.start_link(__MODULE__, [bot_state, informational_settings, configuration, location_data, mcu_params], opts) end - def init(args) do + def init([bot_state, informational_settings, configuration, location_data, mcu_params]) do + handler_mod = Application.get_env(:farmbot, :behaviour)[:firmware] || raise @error_msg children = [ - + worker(Farmbot.Firmware, [bot_state, informational_settings, configuration, location_data, mcu_params, handler_mod, [name: Farmbot.Firmware]]) ] opts = [strategy: :one_for_one] + supervise(children, opts) end end diff --git a/lib/farmbot/firmware/uart_handler.ex b/lib/farmbot/firmware/uart_handler.ex new file mode 100644 index 00000000..da7e5ac6 --- /dev/null +++ b/lib/farmbot/firmware/uart_handler.ex @@ -0,0 +1,79 @@ +defmodule Farmbot.Firmware.UartHandler do + @moduledoc """ + Handles communication between farmbot and uart devices + """ + + use GenServer + use Farmbot.DebugLog + alias Nerves.UART + alias Farmbot.Serial.Gcode.Parser + require Logger + + @default_timeout_ms 15_000 + @max_timeouts 10 + + @doc """ + Writes a string to the uart line + """ + def write(handler, string) do + GenServer.call(handler, {:write, string, @default_timeout_ms}, :infinity) + end + + @doc "Starts a UART GenServer" + def start_link(firmware, opts) do + GenServer.start_link(__MODULE__, firmware, opts) + end + + ## Private + + defmodule State do + @moduledoc false + defstruct [ + :firmware, + :nerves + ] + end + + def init(firmware) do + tty = Application.get_env(:farmbot, :uart_handler)[:tty] || raise "Please configure uart handler!" + {:ok, nerves} = UART.start_link() + Process.link(nerves) + case open_tty(nerves, tty) do + :ok -> {:ok, %State{firmware: firmware, nerves: nerves}} + err -> {:stop, err, :no_state} + end + end + + defp open_tty(nerves, tty) do + case UART.open(nerves, tty, speed: 115_200, active: true) do + :ok -> + :ok = configure_uart(nerves, true) + # Flush the buffers so we start fresh + :ok = UART.flush(nerves) + :ok + err -> err + end + end + + defp configure_uart(nerves, active) do + UART.configure(nerves, + framing: {UART.Framing.Line, separator: "\r\n"}, + active: active, + rx_framing_timeout: 500) + end + + def handle_info({:nerves_uart, _, {:error, reason}}, state) do + {:stop, {:error, reason}, state} + end + + def handle_info({:nerves_uart, _, bin}, state) do + case Parser.parse_code(bin) do + {:unhandled_gcode, code_str} -> + Logger.warn "Got unhandled code: #{code_str}" + {_q, gcode} -> + _reply = Farmbot.Firmware.handle_gcode(state.firmware, gcode) + end + {:noreply, state} + end + +end diff --git a/lib/farmbot/lib/maths.ex b/lib/farmbot/lib/maths.ex deleted file mode 100644 index 88d06baf..00000000 --- a/lib/farmbot/lib/maths.ex +++ /dev/null @@ -1,30 +0,0 @@ -defmodule Farmbot.Lib.Maths do - @moduledoc """ - Math related functions. - """ - - @doc """ - Converts millimeters ot steps. - """ - @spec mm_to_steps(integer, integer) :: integer - def mm_to_steps(mm, spm), do: mm * spm - - @doc """ - Converts steps to mm. - """ - # NOTE(Connor), we will round here. This WILL come up at some point im sure. - # if we never allow sending of steps we should always be able to - # divide back by spm. - @spec steps_to_mm(integer, integer) :: integer - - def steps_to_mm(steps, spm) when is_binary(spm) do - case Integer.parse(spm) do - {int, _} -> steps_to_mm(steps, int) - _ -> - raise "Can not converts steps (#{steps}) to millimeters!" <> - " #{inspect spm} must be an integer!" - end - end - - def steps_to_mm(steps, spm), do: Kernel.div(steps, spm) -end diff --git a/lib/farmbot/serial/handler.ex b/lib/farmbot/serial/handler.ex deleted file mode 100644 index dfa0afac..00000000 --- a/lib/farmbot/serial/handler.ex +++ /dev/null @@ -1,498 +0,0 @@ -defmodule Farmbot.Serial.Handler do - @moduledoc """ - Handles communication between farmbot and uart devices - """ - - alias Farmbot.BotState - alias Farmbot.Lib.Maths - alias Farmbot.Serial.Gcode.Parser - alias Nerves.UART - require Logger - use GenServer - - use Farmbot.DebugLog - - @typedoc """ - Handler pid or name - """ - @type handler :: GenServer.server - - @typedoc """ - Nerves.UART pid or name - """ - @type nerves :: GenServer.server - - @typedoc """ - Status of the arduino - """ - @type status :: :busy | :done - - @typedoc """ - State for this GenServer - """ - @type state :: %{ - nerves: nerves, - tty: binary, - current: current, - timeouts: integer, - status: status, - initialized: boolean - } - - @typedoc """ - The current message being handled - """ - @type current :: %{ - timer: reference, - reply: tuple, - status: status, - from: {pid, reference}, - q: binary - } | nil - - @default_timeout_ms 15_000 - @max_timeouts 10 - - @doc "Starts a UART GenServer" - def start_link(tty, opts) when is_binary(tty) do - GenServer.start_link(__MODULE__, tty, opts) - end - - @doc """ - Blocks the calling process until serial is ready. - """ - def wait_for_available(handler) do - GenServer.call(handler, :wait_for_available, 12_000) - end - - @doc """ - Checks if we have a handler available - """ - def available?(handler) do - GenServer.call(handler, :available?) - end - - @doc """ - Writes a string to the uart line - """ - def write(handler, string, timeout \\ @default_timeout_ms) do - if available?(handler) do - GenServer.call(handler, {:write, string, timeout}, :infinity) - else - {:error, :unavailable} - end - end - - @doc """ - Send the E stop command to the arduino. - """ - def emergency_lock(handler) do - if available?(handler) do - GenServer.call(handler, :emergency_lock) - else - {:error, :unavailable} - end - end - - @doc """ - Tell the arduino its fine now. - """ - def emergency_unlock(handler) do - # We check for aliveness here, not availableness. - if is_alive?(handler) do - GenServer.call(handler, :emergency_unlock) - else - {:error, :unavailable} - end - end - - defp is_alive?(handler) do - Process.alive?(handler) - end - - ## Private - - def init(tty) do - {:ok, nerves} = UART.start_link() - Process.link(nerves) - case open_tty(nerves, tty) do - :ok -> - state = %{ - nerves: nerves, - tty: tty, - current: nil, - timeouts: 0, - status: :busy, - waiting: [], - initialized: false - } - {:ok, state} - err -> - debug_log "could not open tty: #{inspect err}" - {:stop, err, :no_state} - end - end - - @spec open_tty(nerves, binary) :: :ok - defp open_tty(nerves, tty) do - # Open the tty - case UART.open(nerves, tty, speed: 115_200, active: true) do - :ok -> - :ok = configure_uart(nerves, true) - # Flush the buffers so we start fresh - :ok = UART.flush(nerves) - :ok - err -> err - end - end - - defp configure_uart(nerves, active) do - debug_log "reconfigureing uart: #{active}" - UART.configure(nerves, - framing: {UART.Framing.Line, separator: "\r\n"}, - active: active, - rx_framing_timeout: 500) - end - - def handle_call(:wait_for_available, from, state) do - case state.status do - :idle -> {:reply, :ok, state} - :locked -> {:reply, {:error, :locked}, state} - _ -> - Process.send_after(self(), {:waiting_timeout, from}, 10_000) - {:noreply, %{state | waiting: [from | state.waiting]}} - end - end - - def handle_call(:emergency_lock, _, state) do - UART.write(state.nerves, "E") - status = handle_locked(state.current) - next = %{state | - current: nil, - timeouts: 0, - status: status, - initialized: false - } - {:reply, :ok, next} - end - - def handle_call(:emergency_unlock, _, state) do - UART.write(state.nerves, "F09") - next = %{state | status: :idle, initialized: false} - {:reply, :ok, next} - end - - def handle_call(:available?, _, state), do: {:reply, state.initialized, state} - - def handle_call({:write, str, timeout}, from, %{status: :idle} = state) - when is_binary(str) do - handshake = generate_handshake() - writeme = "#{str} #{handshake}" - - # :ok = configure_uart(state.nerves, false) - - debug_log "writing: #{writeme}" - :ok = UART.write(state.nerves, writeme) - - echo_ok = recieve_echo(state.nerves, writeme, "") - - case echo_ok do - :ok -> - debug_log "timing this out in #{timeout} ms." - timer = Process.send_after(self(), :timeout, timeout) - current = %{ - status: nil, - reply: nil, - from: from, - q: handshake, - timer: timer, - callback: nil - } - {:noreply, %{state | current: current}} - {:error, reason} -> - Logger.error "Farmbot.BotState.set_busy(false)" - {:reply, {:error, reason}, %{state | current: nil}} - end - - end - - def handle_call({:write, _str, _timeout}, _from, %{status: status} = state) do - debug_log "Serial Handler status: #{status}" - {:reply, {:error, :bad_status}, state} - end - - def handle_info(:timeout, state) do - current = state.current - if current do - if current.reply do - debug_log "ignoring timeout. This transaction seems fine." - {:noreply, %{state | current: %{current | timer: nil}}} - else - debug_log "timing out current: #{inspect current}" - GenServer.reply(current.from, {:error, :timeout}) - check_timeouts(state) - end - else - debug_log "got stray timeout." - {:noreply, %{state | current: nil}} - end - end - - def handle_info({:waiting_timeout, from}, state) do - if from in state.waiting do - debug_log("Arduino is taking too long. Timing out waiting process: #{inspect from}") - GenServer.reply(from, {:error, :timeout}) - {:noreply, %{state | waiting: List.delete(state.waiting, from)}} - else - {:noreply, state} - end - end - - def handle_info({:nerves_uart, tty, {:error, error}}, state) do - Logger.error "#{tty} handler exiting!: #{error}" - {:stop, error, state} - end - - def handle_info({:nerves_uart, _tty, {:partial, _}}, s), do: {:noreply, s} - - def handle_info({:nerves_uart, _, str}, %{initialized: false} = state) do - if String.contains?(str, "R00") do - Logger.info "Initializing Firmware!" - fn -> - Logger.error "GenServer.cast(state.context.hardware, {:serial_ready, state.context})" - end.() - {:noreply, %{state | initialized: true, status: :idle}} - else - debug_log "Serial not initialized yet: #{str}" - {:noreply, state} - end - end - - def handle_info({:nerves_uart, _, str}, state) when is_binary(str) do - debug_log "Reading: #{str}" - case str |> Parser.parse_code |> do_handle(state.current) do - :locked -> {:noreply, %{state | current: nil, status: :locked}} - {:callback, str, current} -> - debug_log "setting callback: #{str}" - :ok = UART.write(state.nerves, str) - {:noreply, %{state | current: current}} - current -> - - next = %{state | - current: current, status: current[:status] || :idle, timeouts: 0 - } - if next.status == :idle do - waiting = do_resolve_waiting(state.waiting) - {:noreply, %{next | waiting: waiting}} - else - {:noreply, next} - end - end - end - - def terminate(_, :no_state), do: :ok - def terminate(_reason, state) do - UART.close(state.nerves) - UART.stop(state.nerves) - end - - # This function should be called after every write and makes a couple assumptions. - defp recieve_echo(_nerves, writeme, acc) do - debug_log "Waiting for echo: sent: #{writeme} have: #{acc}" - # this could return {:error, reason} - receive do - {:nerves_uart, _, bin} when is_binary(bin) -> parse_echo(acc <> bin, writeme) - {:nerves_uart, _, {:error, reason}} -> {:error, reason} - end - end - - defp parse_echo(echo, writeme) do - debug_log "Parsing echo: #{echo}" - case echo do - # R08 means valid command + whatever you wrote. - "R08 " <> echo -> - if echo |> String.trim() |> String.contains?(writeme), do: :ok, else: {:error, :bad_echo} - # R09 means invalid command. - << "R09", _ :: binary >> -> {:error, :invalid} - # R87 is E stop - << "R87", _ :: binary >> -> {:error, :emergency_lock} - other -> - debug_log "Got an unhandled echo. Expecting: #{writeme} but got: #{echo}" - {:error, "unhandled echo: #{other}"} - end - end - - defp do_resolve_waiting(list) do - from = List.last(list) - if from do - GenServer.reply(from, :ok) - end - List.delete(list, from) - end - - defp do_handle({_qcode, parsed}, current) when is_map(current) do - if current.timer do - Process.cancel_timer(current.timer) - end - results = handle_gcode(parsed) - debug_log "Handling results: #{inspect results}" - case results do - {:status, :done} -> handle_done(current) - {:status, :busy} -> handle_busy(current) - {:status, :locked} -> handle_locked(current) - {:status, status} -> %{current | status: status} - {:callback, str} -> %{current | callback: str} - {:reply, reply} -> %{current | reply: reply} - thing -> handle_other(thing, current) - end - end - - defp do_handle({_qcode, parsed}, nil) do - handle_gcode(parsed) - nil - end - - defp handle_locked(_current) do - Logger.error "Farmbot.BotState.lock_bot(ctx)" - :locked - end - - defp handle_busy(current) do - debug_log "refreshing timer." - Process.cancel_timer(current.timer) - Logger.error "Farmbot.BotState.set_busy(context, true)" - timer = Process.send_after(self(), :timeout, @default_timeout_ms) - %{current | status: :busy, timer: timer} - end - - defp handle_other(thing, current) do - unless is_nil(thing) do - debug_log "Unexpected thing: #{inspect thing}" - end - current - end - - defp handle_done(current) do - debug_log "replying to #{inspect current.from} with: #{inspect current.reply}" - Logger.error "Farmbot.BotState.set_busy(context, false)" - if current.callback do - Process.cancel_timer(current.timer) - handshake = generate_handshake() - writeme = "#{current.callback} #{handshake}" - timer = Process.send_after(self(), :timeout, @default_timeout_ms) - current = %{ - status: nil, - reply: nil, - from: current.from, - q: handshake, - timer: timer, - callback: nil - } - {:callback, writeme, current} - else - GenServer.reply(current.from, current.reply || "R02") - nil - end - end - - defp generate_handshake, do: "Q#{:rand.uniform(99)}" - - defp handle_gcode(:report_emergency_lock), do: {:status, :locked} - defp handle_gcode(:busy), do: {:status, :busy} - defp handle_gcode(:done), do: {:status, :done} - defp handle_gcode(:received), do: {:status, :received} - defp handle_gcode(:error), do: {:reply, :error} - defp handle_gcode(:report_params_complete), do: {:reply, :report_params_complete} - defp handle_gcode(:noop), do: nil - - defp handle_gcode(:idle) do - Logger.error ":ok = Farmbot.BotState.set_busy(ctx, false)" - {:status, :idle} - end - - defp handle_gcode({:debug_message, message}) do - debug_log "R99 #{message}" - nil - end - - defp handle_gcode({:report_pin_value, pin, value} = reply) - when is_integer(pin) and is_integer(value) do - Logger.error "BotState.set_pin_value(ctx, pin, value)" - {:reply, reply} - end - - defp handle_gcode({:report_current_position, x_steps, y_steps, z_steps} = reply) do - # thing_x = spm(:x, ctx) - # thing_y = spm(:y, ctx) - # thing_z = spm(:z, ctx) - # r = BotState.set_pos(ctx, - # Maths.steps_to_mm(x_steps, thing_x), - # Maths.steps_to_mm(y_steps, thing_y), - # Maths.steps_to_mm(z_steps, thing_z)) - Logger.error "Report position not implemented" - # debug_log "Position report reply: #{inspect r}" - {:reply, reply} - end - - defp handle_gcode({:report_parameter_value, param, value} = reply) - when is_atom(param) and is_integer(value) do - unless value == -1 do - Logger.error "BotState.set_param(ctx, param, value)" - end - {:reply, reply} - end - - defp handle_gcode({:report_axis_calibration, param, value}) do - p = Parser.parse_param(param) - Logger.error "BotState.set_param(ctx, param, value)" - {:callback, "F22 P#{p} V#{value}"} - end - - defp handle_gcode({:report_calibration, axis, status} = reply) do - Logger.info ">> Calibration message: #{axis}: #{status}" - {:reply, reply} - end - - defp handle_gcode( - {:report_end_stops, x1, x2, y1, y2, z1, z2} = reply) - do - Logger.error "BotState.set_end_stops({x1, x2, y1, y2, z1, z2})" - {:reply, reply} - end - - defp handle_gcode({:report_encoder_position_scaled, x, y, z} = reply) do - Logger.error "BotState.set_scaled_encoders(ctx, x, y, z)" - {:reply, reply} - end - - defp handle_gcode({:report_encoder_position_raw, x, y, z} = reply) do - Logger.error "BotState.set_raw_encoders(ctx, x, y, z)" - {:reply, reply} - end - - defp handle_gcode({:report_software_version, version} = reply) do - Logger.error "BotState.set_fw_version(ctx, version)" - {:reply, reply} - end - - defp handle_gcode({:unhandled_gcode, code}) do - Logger.warn ">> got an misc gcode #{code}" - {:reply, code} - end - - defp handle_gcode(parsed) do - Logger.warn "Unhandled message: #{inspect parsed}" - {:reply, parsed} - end - - defp check_timeouts(state) do - if state.timeouts > @max_timeouts do - debug_log "reached max timeouts!" - :ok = UART.close(state.nerves) - Process.sleep(5000) - {:stop, :max_timeouts, state} - else - {:noreply, %{state | current: nil, timeouts: state.timeouts + 1}} - end - end -end