farmbot_os/farmbot_core/lib/firmware/uart_handler/uart_handler.ex

396 lines
11 KiB
Elixir
Raw Normal View History

2017-08-21 14:31:32 -06:00
defmodule Farmbot.Firmware.UartHandler do
@moduledoc """
Handles communication between farmbot and uart devices
"""
2017-09-29 17:55:10 -06:00
use GenStage
alias Nerves.UART
require Farmbot.Logger
import Farmbot.Config, only: [update_config_value: 4, get_config_value: 3]
alias Farmbot.Firmware
alias Firmware.Utils
2018-04-19 09:19:41 -06:00
import Utils
@behaviour Firmware.Handler
2017-08-21 14:31:32 -06:00
2017-10-24 07:21:23 -06:00
def start_link do
GenStage.start_link(__MODULE__, [])
2017-08-21 14:31:32 -06:00
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})
2017-10-24 07:21:23 -06:00
end
def calibrate(handler, axis) do
2017-12-15 10:29:26 -07:00
GenStage.call(handler, {:calibrate, axis})
2017-10-24 07:21:23 -06:00
end
def find_home(handler, axis) do
GenStage.call(handler, {:find_home, axis})
2017-10-24 07:21:23 -06:00
end
def home(handler, axis) do
GenStage.call(handler, {:home, axis})
end
def home_all(handler) do
GenStage.call(handler, :home_all)
2017-11-05 03:07:30 -07:00
end
2017-11-08 13:55:16 -07:00
def zero(handler, axis) do
GenStage.call(handler, {:zero, axis})
2017-10-24 07:21:23 -06:00
end
def update_param(handler, param, val) do
GenStage.call(handler, {:update_param, param, val})
end
def read_param(handler, param) do
GenStage.call(handler, {:read_param, param})
end
def read_all_params(handler) do
GenStage.call(handler, :read_all_params)
end
2017-10-24 07:21:23 -06:00
def emergency_lock(handler) do
GenStage.call(handler, :emergency_lock)
end
def emergency_unlock(handler) do
GenStage.call(handler, :emergency_unlock)
end
2017-11-09 18:30:26 -07:00
def set_pin_mode(handler, pin, mode) do
GenStage.call(handler, {:set_pin_mode, pin, mode})
end
2017-10-24 07:21:23 -06:00
def read_pin(handler, pin, pin_mode) do
GenStage.call(handler, {:read_pin, pin, pin_mode})
end
def write_pin(handler, pin, pin_mode, value) do
GenStage.call(handler, {:write_pin, pin, pin_mode, value})
2017-08-21 14:31:32 -06:00
end
2017-11-09 18:10:47 -07:00
def request_software_version(handler) do
GenStage.call(handler, :request_software_version)
end
def set_servo_angle(handler, pin, number) do
GenStage.call(handler, {:set_servo_angle, pin, number})
end
2017-08-21 14:31:32 -06:00
## Private
defmodule State do
@moduledoc false
defstruct [
config_busy: false,
nerves: nil,
current_cmd: nil,
2018-01-10 10:04:59 -07:00
tty: nil,
hw: nil
2017-08-21 14:31:32 -06:00
]
end
2017-09-29 17:55:10 -06:00
def init([]) do
Farmbot.Logger.debug 3, "Uart handler init."
# If in dev environment,
# it is expected that this be done at compile time.
# If in target environment,
# this should be done by `Farmbot.Firmware.AutoDetector`.
error_msg = "Please configure uart handler!"
tty = Application.get_env(:farmbot_core, :uart_handler)[:tty] || raise error_msg
# Disable fw input logs after a reset of the
# Fw handler if they were enabled.
update_config_value(:bool, "settings", "firmware_input_log", false)
2018-01-10 10:04:59 -07:00
hw = get_config_value(:string, "settings", "firmware_hardware")
gen_stage_opts = [
dispatcher: GenStage.BroadcastDispatcher,
]
2017-11-16 14:47:36 -07:00
case open_tty(tty) do
{:ok, nerves} ->
{:producer, %State{nerves: nerves, tty: tty, hw: hw}, gen_stage_opts}
{:error, reason} ->
Farmbot.Logger.error 1, "Uart handler failed to initialize: #{inspect reason}"
:ignore
2017-08-21 14:31:32 -06:00
end
end
def handle_events(events, _, state) do
state = Enum.reduce(events, state, fn(event, state_acc) ->
handle_config(event, state_acc)
end)
2018-01-10 10:04:59 -07:00
case state do
%State{config_busy: true} = state ->
{:noreply, [:busy], state}
2018-01-10 10:04:59 -07:00
%State{} = state ->
{:noreply, [], state}
_ -> state
end
end
defp handle_config({:config, "settings", key, _val}, state)
when key in ["firmware_input_log", "firmware_output_log"]
do
# Restart the framing to pick up new changes.
UART.configure state.nerves, [framing: UART.Framing.None, active: false]
configure_uart(state.nerves, true)
%{state | config_busy: true}
end
defp handle_config({:config, "settings", "firmware_hardware", _val}, _state) do
raise("FIXME")
2018-01-10 10:04:59 -07:00
end
defp handle_config(_, state) do
state
end
2018-01-10 10:04:59 -07:00
defp open_tty(tty, nerves \\ nil) do
Farmbot.Logger.debug 3, "Opening uart device: #{tty}"
2018-01-10 10:04:59 -07:00
nerves = nerves || UART.start_link |> elem(1)
2017-11-16 14:47:36 -07:00
Process.link(nerves)
case UART.open(nerves, tty, [speed: 115_200, active: true]) do
2017-08-21 14:31:32 -06:00
:ok ->
:ok = configure_uart(nerves, true)
# Flush the buffers so we start fresh
:ok = UART.flush(nerves)
loop_until_idle(nerves)
2017-10-11 16:53:00 -06:00
err ->
err
2017-08-21 14:31:32 -06:00
end
end
defp loop_until_idle(nerves, idle_count \\ 0)
defp loop_until_idle(nerves, 2) do
Farmbot.Logger.success 3, "Got two idles. UART is up."
Process.sleep(1500)
{:ok, nerves}
end
defp loop_until_idle(nerves, idle_count)
when is_pid(nerves) and is_number(idle_count)
do
Farmbot.Logger.debug 3, "Waiting for firmware idle."
receive do
{:circuits_uart, _, {:error, reason}} -> {:stop, reason}
{:circuits_uart, _, {:partial, _}} -> loop_until_idle(nerves, idle_count)
{:circuits_uart, _, {_, :idle}} -> loop_until_idle(nerves, idle_count + 1)
{:circuits_uart, _, {_, {:debug_message, msg}}} ->
2018-01-10 10:04:59 -07:00
if String.contains?(msg, "STARTUP") do
Farmbot.Logger.success 3, "Got #{msg}. UART is up."
UART.write(nerves, "F22 P2 V0")
{:ok, nerves}
2018-01-10 10:04:59 -07:00
else
Farmbot.Logger.debug 3, "Got arduino debug while booting up: #{msg}"
loop_until_idle(nerves, idle_count)
2018-01-10 10:04:59 -07:00
end
{:circuits_uart, _, _msg} -> loop_until_idle(nerves, idle_count)
after
10_000 -> {:stop, "Firmware didn't send any info for 10 seconds."}
end
end
2017-08-21 14:31:32 -06:00
defp configure_uart(nerves, active) do
2017-10-11 16:53:00 -06:00
UART.configure(
nerves,
2017-12-04 16:38:43 -07:00
framing: {Farmbot.Firmware.UartHandler.Framing, separator: "\r\n"},
2017-08-21 14:31:32 -06:00
active: active,
2017-10-11 16:53:00 -06:00
rx_framing_timeout: 500
)
2017-08-21 14:31:32 -06:00
end
def terminate(reason, state) do
Farmbot.Logger.warn 1, "UART handler died: #{inspect reason}"
if state.nerves do
UART.close(state.nerves)
2018-08-14 15:30:10 -06:00
UART.stop(state.nerves)
end
end
2017-10-04 20:46:51 -06:00
# if there is an error, we assume something bad has happened, and we probably
# Are better off crashing here, and being restarted.
def handle_info({:circuits_uart, _, {:error, :eio}}, state) do
Farmbot.Logger.error 1, "UART device removed."
old_env = Application.get_env(:farmbot_core, :behaviour)
new_env = Keyword.put(old_env, :firmware_handler, Firmware.StubHandler)
Application.put_env(:farmbot_core, :behaviour, new_env)
2017-11-16 14:47:36 -07:00
{:stop, {:error, :eio}, state}
end
def handle_info({:circuits_uart, _, {:error, reason}}, state) do
2017-08-21 14:31:32 -06:00
{:stop, {:error, reason}, state}
end
2017-10-04 20:46:51 -06:00
# Unhandled gcodes just get ignored.
def handle_info({:circuits_uart, _, {:unhandled_gcode, code_str}}, state) do
Farmbot.Logger.debug 3, "Got unhandled gcode: #{code_str}"
2017-09-30 23:08:53 -06:00
{:noreply, [], state}
end
def handle_info({:circuits_uart, _, {_, {:report_software_version, v}}}, state) do
expected = Application.get_env(:farmbot_core, :expected_fw_versions)
if v in expected do
{:noreply, [{:report_software_version, v}], state}
else
err = "Firmware version #{v} is not in expected versions: #{inspect expected}"
Farmbot.Logger.error 1, err
old_env = Application.get_env(:farmbot_core, :behaviour)
new_env = Keyword.put(old_env, :firmware_handler, Firmware.StubHandler)
Application.put_env(:farmbot_core, :behaviour, new_env)
{:stop, :normal, state}
end
end
def handle_info({:circuits_uart, _, {:echo, _}}, %{current_cmd: nil} = state) do
{:noreply, [], state}
end
def handle_info({:circuits_uart, _, {:echo, {:echo, "*F43" <> _}}}, state) do
{:noreply, [], state}
end
def handle_info({:circuits_uart, _, {:echo, {:echo, code}}}, state) do
distance = String.jaro_distance(state.current_cmd, code)
if distance > 0.85 do
:ok
else
err = "Echo #{code} does not match #{state.current_cmd} (#{distance})"
Farmbot.Logger.error 3, err
end
{:noreply, [], %{state | current_cmd: nil}}
end
def handle_info({:circuits_uart, _, {_q, :done}}, state) do
{:noreply, [:done], %{state | current_cmd: nil, config_busy: false}}
end
def handle_info({:circuits_uart, _, {_q, gcode}}, state) do
2017-11-08 13:55:16 -07:00
{:noreply, [gcode], state}
2017-08-21 14:31:32 -06:00
end
def handle_info({:circuits_uart, _, bin}, state) when is_binary(bin) do
Farmbot.Logger.warn(3, "Unparsed Gcode: #{bin}")
2017-10-11 10:47:52 -06:00
{:noreply, [], state}
end
defp do_write(bin, state, dispatch \\ []) do
# Farmbot.Logger.debug 3, "writing: #{bin}"
case UART.write(state.nerves, bin) do
:ok -> {:reply, :ok, dispatch, %{state | current_cmd: bin}}
err -> {:reply, err, [], %{state | current_cmd: nil}}
end
end
def handle_call({:move_absolute, pos, x_speed, y_speed, z_speed}, _from, state) do
cmd = "X#{fmnt_float(pos.x)} "
<> "Y#{fmnt_float(pos.y)} "
<> "Z#{fmnt_float(pos.z)} "
<> "A#{fmnt_float(x_speed)} "
<> "B#{fmnt_float(y_speed)} "
<> "C#{fmnt_float(z_speed)}"
wrote = "G00 #{cmd}"
do_write(wrote, state)
2017-11-08 13:55:16 -07:00
end
def handle_call({:calibrate, axis}, _from, state) do
2017-11-08 13:55:16 -07:00
num = case axis |> to_string() do
"x" -> 14
"y" -> 15
"z" -> 16
end
do_write("F#{num}", state)
2017-11-08 13:55:16 -07:00
end
def handle_call({:find_home, axis}, _from, state) do
2017-11-08 13:55:16 -07:00
cmd = case axis |> to_string() do
"x" -> "11"
"y" -> "12"
"z" -> "13"
2017-11-08 13:55:16 -07:00
end
do_write("F#{cmd}", state)
2017-11-08 13:55:16 -07:00
end
def handle_call(:home_all, _from, state) do
do_write("G28", state)
end
def handle_call({:home, axis}, _from, state) do
2017-11-08 13:55:16 -07:00
cmd = case axis |> to_string() do
"x" -> "X0"
"y" -> "Y0"
"z" -> "Z0"
2017-11-08 13:55:16 -07:00
end
do_write("G00 #{cmd}", state)
2017-11-08 13:55:16 -07:00
end
def handle_call({:zero, axis}, _from, state) do
axis_format = case axis |> to_string() do
"x" -> "X"
"y" -> "Y"
"z" -> "Z"
end
2017-11-21 13:31:01 -07:00
do_write("F84 #{axis_format}1", state)
2017-11-08 13:55:16 -07:00
end
def handle_call(:emergency_lock, _from, state) do
r = UART.write(state.nerves, "E")
{:reply, r, [], state}
end
def handle_call(:emergency_unlock, _from, state) do
do_write("F09", state)
end
def handle_call({:read_param, param}, _from, state) do
num = Farmbot.Firmware.Gcode.Param.parse_param(param)
do_write("F21 P#{num}", state)
end
def handle_call({:update_param, param, val}, _from, state) do
num = Farmbot.Firmware.Gcode.Param.parse_param(param)
do_write("F22 P#{num} V#{fmnt_float(val)}", state)
end
def handle_call(:read_all_params, _from, state) do
do_write("F20", state)
2017-11-08 13:55:16 -07:00
end
2018-04-19 09:51:52 -06:00
def handle_call({:set_pin_mode, pin, mode_atom}, _from, state) do
encoded_mode = extract_set_pin_mode(mode_atom)
2017-11-09 18:38:44 -07:00
do_write("F43 P#{pin} M#{encoded_mode}", state, [])
2017-11-09 18:30:26 -07:00
end
2017-11-08 13:55:16 -07:00
def handle_call({:read_pin, pin, mode}, _from, state) do
2017-11-09 18:30:26 -07:00
encoded_mode = extract_pin_mode(mode)
dispatch = [{:report_pin_mode, pin, mode}]
do_write("F42 P#{pin} M#{encoded_mode}", state, dispatch)
2017-11-08 13:55:16 -07:00
end
def handle_call({:write_pin, pin, mode, value}, _from, state) do
2017-11-09 18:30:26 -07:00
encoded_mode = extract_pin_mode(mode)
dispatch = [{:report_pin_mode, pin, mode}, {:report_pin_value, pin, value}]
do_write("F41 P#{pin} V#{value} M#{encoded_mode}", state, dispatch)
2017-11-08 12:10:28 -07:00
end
2017-11-09 18:10:47 -07:00
def handle_call(:request_software_version, _from, state) do
do_write("F83", state)
end
def handle_call({:set_servo_angle, pin, angle}, _, state) do
do_write("F61 P#{pin} V#{angle}", state)
end
2017-11-08 12:10:28 -07:00
def handle_call(_call, _from, state) do
{:reply, {:error, :bad_call}, [], state}
end
2017-09-29 17:55:10 -06:00
def handle_demand(_amnt, state) do
2017-11-08 13:55:16 -07:00
{:noreply, [], state}
2017-09-29 16:05:48 -06:00
end
2017-08-21 14:31:32 -06:00
end