294 lines
8.0 KiB
Elixir
294 lines
8.0 KiB
Elixir
defmodule Command do
|
|
@moduledoc """
|
|
BotCommands.
|
|
"""
|
|
require Logger
|
|
alias Farmbot.BotState
|
|
alias Farmbot.Scheduler
|
|
alias Farmbot.Serial.Gcode.Parser, as: GcodeParser
|
|
alias Farmbot.Serial.Gcode.Handler, as: GcodeHandler
|
|
|
|
@type command_output :: :done | :timeout | :error
|
|
@doc """
|
|
EMERGENCY STOP.
|
|
This will happen in a pretty specific order.
|
|
first: write an "E" to the serial line directly.
|
|
second: stop any running sequences.
|
|
third: Probably write another E.
|
|
fourth: pause running regimens and other stuff that might do serial stuff
|
|
on a timer.
|
|
"""
|
|
@spec e_stop :: {:error, atom} | :ok
|
|
def e_stop do
|
|
# The index of the lock "e_stop". should be an integer or nil
|
|
e_stop(BotState.get_lock("e_stop"))
|
|
end
|
|
|
|
@spec e_stop(integer) :: {:error, :already_locked}
|
|
def e_stop(integer) when is_integer(integer), do: {:error, :already_locked}
|
|
|
|
@spec e_stop(nil) :: :ok
|
|
def e_stop(nil) do
|
|
BotState.add_lock("e_stop")
|
|
# Log somethingwarn("E Stopping", type: :toast)
|
|
Farmbot.Serial.Handler.e_stop
|
|
Scheduler.e_stop_lock
|
|
Logger.error ">> is emergency stopped!",
|
|
channels: [:toast]
|
|
:ok
|
|
end
|
|
|
|
@doc """
|
|
resume from an e stop
|
|
This is way to complex
|
|
"""
|
|
@spec resume() :: :ok | {:error, atom}
|
|
def resume do
|
|
# The index of the lock "e_stop". should be an integer or nil
|
|
resume(BotState.get_lock("e_stop"))
|
|
end
|
|
|
|
@spec resume(integer | nil) :: :ok | {:error, atom}
|
|
def resume(integer) when is_integer(integer) do
|
|
Farmbot.Serial.Handler.resume
|
|
params = BotState.get_all_mcu_params
|
|
# The firmware takes forever to become ready again.
|
|
Process.sleep(2000)
|
|
case Enum.partition(params, fn({param, value}) ->
|
|
param_int = GcodeParser.parse_param(param)
|
|
Command.update_param(param_int, value)
|
|
end)
|
|
do
|
|
{_, []} ->
|
|
Logger.debug ">> is back up and running!",
|
|
type: :success, channels: [:toast]
|
|
BotState.remove_lock("e_stop")
|
|
Scheduler.e_stop_unlock
|
|
:ok
|
|
{_, failed} ->
|
|
Logger.error ">> encountered errors resuming " <>
|
|
"from emergency stop mode! #{inspect failed}"
|
|
{:error, :prams}
|
|
end
|
|
end
|
|
|
|
def resume(nil) do
|
|
{:error, :not_locked}
|
|
end
|
|
|
|
@doc """
|
|
Home All
|
|
"""
|
|
@spec home_all(number | nil) :: command_output
|
|
def home_all(speed \\ nil) do
|
|
Logger.debug(">> is going home.")
|
|
Command.move_absolute(0, 0, 0, speed || BotState.get_config(:steps_per_mm))
|
|
end
|
|
|
|
@doc """
|
|
Home x
|
|
"""
|
|
@spec home_x() :: command_output
|
|
def home_x() do
|
|
Logger.debug ">> is homing X"
|
|
GcodeHandler.block_send("F11")
|
|
end
|
|
|
|
@doc """
|
|
Home y
|
|
"""
|
|
@spec home_y() :: command_output
|
|
def home_y() do
|
|
Logger.debug ">> is homing Y"
|
|
GcodeHandler.block_send("F12")
|
|
end
|
|
|
|
@doc """
|
|
Home z
|
|
"""
|
|
@spec home_z() :: command_output
|
|
def home_z() do
|
|
Logger.debug ">> is homing Z"
|
|
GcodeHandler.block_send("F13")
|
|
end
|
|
|
|
@doc """
|
|
Calibrates an axis. May be used for other calibration things later.
|
|
"""
|
|
@lint false # Dont lint this.
|
|
@spec calibrate(String.t) :: command_output
|
|
def calibrate("x") do
|
|
GcodeHandler.block_send("F14")
|
|
|> logmsg("X Axis Calibration")
|
|
end
|
|
|
|
def calibrate("y") do
|
|
GcodeHandler.block_send("F15")
|
|
|> logmsg("Y Axis Calibration")
|
|
end
|
|
|
|
def calibrate("z") do
|
|
GcodeHandler.block_send("F16")
|
|
|> logmsg("Z Axis Calibration")
|
|
end
|
|
|
|
@doc """
|
|
Writes a pin high or low
|
|
"""
|
|
@spec write_pin(number, number, number) :: command_output
|
|
@lint false # Dont lint this.
|
|
def write_pin(pin, value, mode)
|
|
when is_integer(pin) and is_integer(value) and is_integer(mode) do
|
|
BotState.set_pin_mode(pin, mode)
|
|
BotState.set_pin_value(pin, value)
|
|
GcodeHandler.block_send("F41 P#{pin} V#{value} M#{mode}")
|
|
|> logmsg("pin write")
|
|
end
|
|
|
|
@doc """
|
|
Moves to (x,y,z) point.
|
|
"""
|
|
@spec move_absolute(number, number, number, number | nil) :: command_output
|
|
@lint false # Dont lint this.
|
|
def move_absolute(x ,y ,z ,s \\ nil)
|
|
@lint false # Dont lint this.
|
|
def move_absolute(x, y, z, s) do
|
|
Logger.debug ">> is moving to X#{x} Y#{y} Z#{z}."
|
|
GcodeHandler.block_send(
|
|
"G00 X#{x} Y#{y} Z#{z} S#{s || BotState.get_config(:steps_per_mm)}")
|
|
|> logmsg("movement")
|
|
end
|
|
|
|
@doc """
|
|
Gets the current position
|
|
then pipes into move_absolute
|
|
* {:x, `speed`, `amount`}
|
|
* {:y, `speed`, `amount`}
|
|
* {:z, `speed`, `amount`}
|
|
* %{x: `amount`, y: `amount`, z: `amount` ,s: `speed`}
|
|
"""
|
|
@spec move_relative(
|
|
{:x, number | nil, number} |
|
|
{:y, number | nil, number} |
|
|
{:z, number | nil, number} |
|
|
%{x: number, y: number, z: number, speed: number | nil}) :: command_output
|
|
def move_relative({:x, s, move_by}) when is_integer move_by do
|
|
[x,y,z] = BotState.get_current_pos
|
|
move_absolute(x + move_by,y,z,s)
|
|
end
|
|
|
|
def move_relative({:y, s, move_by}) when is_integer move_by do
|
|
[x,y,z] = BotState.get_current_pos
|
|
move_absolute(x,y + move_by,z,s)
|
|
end
|
|
|
|
def move_relative({:z, s, move_by}) when is_integer move_by do
|
|
[x,y,z] = BotState.get_current_pos
|
|
move_absolute(x,y,z + move_by,s)
|
|
end
|
|
|
|
# This is a funky one. Only used in sequences right now.
|
|
def move_relative(%{x: x_move_by, y: y_move_by, z: z_move_by, speed: speed})
|
|
when is_integer(x_move_by) and
|
|
is_integer(y_move_by) and
|
|
is_integer(z_move_by)
|
|
do
|
|
[x,y,z] = BotState.get_current_pos
|
|
move_absolute(x + x_move_by,y + y_move_by,z + z_move_by, speed)
|
|
end
|
|
|
|
@doc """
|
|
Used when bootstrapping the bot.
|
|
Reads all the params.
|
|
TODO: Make these not magic numbers.
|
|
"""
|
|
@spec read_all_params(list(number)) :: :ok | :fail
|
|
def read_all_params(params \\ [0,11,12,13,21,22,23,
|
|
31,32,33,41,42,43,51,
|
|
52,53,61,62,63,71,72,73, 101,102,103])
|
|
when is_list(params) do
|
|
case Enum.partition(params, fn param ->
|
|
GenServer.cast(GcodeHandler, {:send, "F21 P#{param}", self()})
|
|
:done == GcodeHandler.block(2500)
|
|
end) do
|
|
{_, []} -> :ok
|
|
{_, failed_params} -> read_all_params(failed_params)
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Reads a pin value.
|
|
mode can be 0 (digital) or 1 (analog)
|
|
"""
|
|
@spec read_pin(number, 0 | 1) :: command_output
|
|
@lint false # Dont lint this.
|
|
def read_pin(pin, mode \\ 0) when is_integer(pin) do
|
|
BotState.set_pin_mode(pin, mode)
|
|
GcodeHandler.block_send("F42 P#{pin} M#{mode}")
|
|
|> logmsg("read_pin")
|
|
end
|
|
|
|
@doc """
|
|
gets the current value, and then toggles it.
|
|
"""
|
|
@spec toggle_pin(number) :: command_output
|
|
def toggle_pin(pin) when is_integer(pin) do
|
|
pin_map = BotState.get_pin(pin)
|
|
case pin_map do
|
|
%{mode: 0, value: 1} ->
|
|
write_pin(pin, 0, 0)
|
|
%{mode: 0, value: 0} ->
|
|
write_pin(pin, 1, 0)
|
|
nil ->
|
|
read_pin(pin, 0)
|
|
toggle_pin(pin)
|
|
_ -> :fail
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Reads a param. Needs the integer version of said param.
|
|
"""
|
|
@spec read_param(number) :: command_output
|
|
def read_param(param) when is_integer param do
|
|
case GcodeHandler.block_send "F21 P#{param}" do
|
|
:timeout ->
|
|
Process.sleep(100)
|
|
read_param(param)
|
|
whatever -> whatever
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Update a param. Param needs to be an integer.
|
|
"""
|
|
@spec update_param(number | nil, number) :: command_output
|
|
def update_param(param, value) when is_integer param do
|
|
case GcodeHandler.block_send "F22 P#{param} V#{value}" do
|
|
:timeout ->
|
|
Process.sleep(10)
|
|
update_param(param, value)
|
|
_ ->
|
|
Process.sleep(100)
|
|
Command.read_param(param)
|
|
end
|
|
end
|
|
|
|
def update_param(nil, _value) do
|
|
{:error, "unknown param"}
|
|
end
|
|
|
|
@spec logmsg(command_output, String.t) :: command_output
|
|
defp logmsg(:done, command) when is_bitstring(command) do
|
|
Logger.debug(">> completed #{command}.")
|
|
:done
|
|
end
|
|
|
|
defp logmsg(other, command) when is_bitstring(command) do
|
|
Logger.error ">> encountered an error " <>
|
|
"executing #{command}: #{inspect other}",
|
|
channels: [:toast]
|
|
other
|
|
end
|
|
end
|