farmbot_os/farmbot_firmware/lib/farmbot_firmware/gcode.ex

146 lines
4.0 KiB
Elixir

defmodule FarmbotFirmware.GCODE do
@moduledoc """
Handles encoding and decoding of GCODEs.
"""
alias FarmbotFirmware.GCODE.{Decoder, Encoder}
import Decoder, only: [do_decode: 2]
import Encoder, only: [do_encode: 2]
@typedoc "Tag is a binary integer. example: `\"123\"`"
@type tag() :: nil | binary()
@typedoc "RXX codes. Reports information."
@type report_kind ::
:report_idle
| :report_begin
| :report_success
| :report_error
| :report_busy
| :report_axis_state
| :report_retry
| :report_echo
| :report_invalid
| :report_home_complete
| :report_position
| :report_load
| :report_position_change
| :report_parameters_complete
| :report_parameter_value
| :report_calibration_parameter_value
| :report_status_value
| :report_pin_value
| :report_axis_timeout
| :report_end_stops
| :report_software_version
| :report_encoders_scaled
| :report_encoders_raw
| :report_emergency_lock
| :report_no_config
| :report_debug_message
@typedoc "Movement commands"
@type command_kind ::
:command_movement
| :command_movement_home
| :command_movement_find_home
| :command_movement_calibrate
@typedoc "Read/Write commands."
@type read_write_kind ::
:parameter_read_all
| :parameter_read
| :parameter_write
| :status_read
| :status_write
| :pin_read
| :pin_write
| :pin_mode_write
| :servo_write
| :end_stops_read
| :position_read
| :software_version_read
| :position_write_zero
@type emergency_commands ::
:command_emergency_lock | :command_emergency_unlock
@typedoc "Kind is an atom of the \"name\" of a command. Example: `:write_parameter`"
@type kind() :: report_kind | command_kind | read_write_kind | :unknown
@typedoc "Args is a list of args to a `kind`. example: `[x: 100.00]`"
@type args() :: [arg]
@typedoc "Example: `{:x, 100.00}` or `1` or `\"hello world\"`"
@type arg() :: any()
@typedoc "Constructed GCODE."
@type t :: {tag(), {kind(), args}}
@doc """
Shortcut for constructing a new GCODE
## Examples
iex(1)> FarmbotFirmware.GCODE.new(:report_idle, [], "100")
{"100", {:report_idle, []}}
iex(2)> FarmbotFirmware.GCODE.new(:report_idle, [])
{nil, {:report_idle, []}}
"""
@spec new(kind(), args(), tag()) :: t()
def new(kind, args, tag \\ nil) do
{tag, {kind, args}}
end
@doc """
Takes a string representation of a GCODE, and returns a tuple representation of:
`{tag, {kind, args}}`
## Examples
iex(1)> FarmbotFirmware.GCODE.decode("R00 Q100")
{"100", {:report_idle, []}}
iex(2)> FarmbotFirmware.GCODE.decode("R00")
{nil, {:report_idle, []}}
"""
@spec decode(binary()) :: t()
def decode(binary_with_q) when is_binary(binary_with_q) do
code = String.split(binary_with_q, " ")
case extract_tag(code) do
{tag, [kind | args]} ->
{tag, do_decode(kind, args)}
{tag, []} ->
{tag, {:unknown, []}}
end
end
@doc """
Takes a tuple representation of a GCODE and returns a string.
## Examples
iex(1)> FarmbotFirmware.GCODE.encode({"444", {:report_idle, []}})
"R00 Q444"
iex(2)> FarmbotFirmware.GCODE.encode({nil, {:report_idle, []}})
"R00"
"""
@spec encode(t()) :: binary()
def encode({nil, {kind, args}}) do
do_encode(kind, args)
end
def encode({tag, {kind, args}}) do
str = do_encode(kind, args)
str <> " Q" <> tag
end
@doc false
@spec extract_tag([binary()]) :: {tag(), [binary()]}
def extract_tag(list) when is_list(list) do
with {"Q" <> bin_tag, list} when is_list(list) <- List.pop_at(list, -1) do
{bin_tag, list}
else
# if there was no Q code provided
{_, data} when is_list(data) -> {nil, list}
end
end
end