398 lines
12 KiB
Elixir
398 lines
12 KiB
Elixir
defmodule FarmbotCeleryScript.SysCalls do
|
|
@moduledoc """
|
|
Behaviour for abstracting CeleryScript functionality.
|
|
"""
|
|
alias FarmbotCeleryScript.{AST, RuntimeError}
|
|
|
|
@sys_calls Application.get_env(:farmbot_celery_script, __MODULE__)[:sys_calls]
|
|
@sys_calls ||
|
|
Mix.raise("""
|
|
config :farmbot_celery_script, FarmbotCeleryScript.SysCalls, [
|
|
sys_calls: SomeModuleThatImplementsTheBehaviour
|
|
]
|
|
""")
|
|
|
|
@type error :: {:error, String.t()}
|
|
@type ok_or_error :: :ok | error
|
|
|
|
# "x", "y", or "z"
|
|
@type axis :: String.t()
|
|
@type package :: String.t()
|
|
@type resource_id :: integer()
|
|
|
|
@callback calibrate(axis) :: ok_or_error
|
|
@callback change_ownership(
|
|
email :: String.t(),
|
|
secret :: binary(),
|
|
server :: String.t()
|
|
) ::
|
|
ok_or_error
|
|
@callback check_update() :: ok_or_error
|
|
@callback coordinate(x :: number, y :: number, z :: number) ::
|
|
%{x: number(), y: number(), z: number()} | error
|
|
@callback dump_info() :: ok_or_error
|
|
@callback emergency_lock() :: ok_or_error
|
|
@callback emergency_unlock() :: ok_or_error
|
|
@callback execute_script(package, args :: map()) :: ok_or_error
|
|
@callback update_farmware(package) :: ok_or_error
|
|
@callback factory_reset(package :: String.t()) :: ok_or_error
|
|
@callback find_home(axis) :: ok_or_error
|
|
@callback firmware_reboot() :: ok_or_error
|
|
@callback flash_firmware(package) :: ok_or_error
|
|
@callback get_current_x() :: number() | error()
|
|
@callback get_current_y() :: number() | error()
|
|
@callback get_current_z() :: number() | error()
|
|
|
|
@callback get_cached_x() :: number() | error()
|
|
@callback get_cached_y() :: number() | error()
|
|
@callback get_cached_z() :: number() | error()
|
|
|
|
@callback get_sequence(resource_id) :: FarmbotCeleryScript.AST.t() | error()
|
|
@callback get_toolslot_for_tool(resource_id) ::
|
|
%{x: number(), y: number(), z: number()} | error()
|
|
@callback home(axis, speed :: number()) :: ok_or_error
|
|
@callback install_first_party_farmware() :: ok_or_error
|
|
@callback move_absolute(
|
|
x :: number(),
|
|
y :: number(),
|
|
z :: number(),
|
|
speed :: number()
|
|
) ::
|
|
ok_or_error
|
|
# ?
|
|
@callback named_pin(named_pin_type :: String.t(), resource_id) ::
|
|
map() | integer | error()
|
|
@callback nothing() :: any()
|
|
@callback point(point_type :: String.t(), resource_id) :: number() | error()
|
|
@callback power_off() :: ok_or_error
|
|
@callback read_pin(pin_num :: number(), pin_mode :: number()) ::
|
|
number | error()
|
|
@callback read_cached_pin(pin_num :: number()) :: number | error()
|
|
@callback toggle_pin(pin_num :: number()) :: ok_or_error
|
|
@callback read_status() :: ok_or_error
|
|
@callback reboot() :: ok_or_error
|
|
@callback resource_update(String.t(), resource_id, map()) :: ok_or_error
|
|
@callback send_message(type :: String.t(), message :: String.t(), [atom]) ::
|
|
ok_or_error
|
|
@callback set_servo_angle(pin :: number(), value :: number()) :: ok_or_error
|
|
@callback set_pin_io_mode(pin :: number(), mode :: number()) :: ok_or_error
|
|
@callback set_user_env(env_name :: String.t(), env_value :: String.t()) ::
|
|
ok_or_error
|
|
@callback sync() :: ok_or_error
|
|
@callback wait(millis :: number()) :: ok_or_error
|
|
@callback write_pin(
|
|
pin_num :: number(),
|
|
pin_mode :: number(),
|
|
pin_value :: number
|
|
) ::
|
|
ok_or_error
|
|
@callback zero(axis) :: ok_or_error
|
|
|
|
@callback log(message :: String.t(), force? :: boolean()) :: any()
|
|
@callback sequence_init_log(message :: String.t()) :: any()
|
|
@callback sequence_complete_log(message :: String.t()) :: any()
|
|
@callback eval_assertion(comment :: String.t(), expression :: String.t()) ::
|
|
true | false | error()
|
|
|
|
@callback get_point_group(String.t() | resource_id) :: %{
|
|
required(:point_ids) => [resource_id]
|
|
}
|
|
|
|
def get_point_group(sys_calls \\ @sys_calls, point_group_id) do
|
|
point_group_or_error(sys_calls, :get_point_group, [point_group_id])
|
|
end
|
|
|
|
def format_lhs(sys_calls \\ @sys_calls, lhs)
|
|
|
|
def format_lhs(_sys_calls, "x"), do: "current X position"
|
|
def format_lhs(_sys_calls, "y"), do: "current Y position"
|
|
def format_lhs(_sys_calls, "z"), do: "current z position"
|
|
def format_lhs(_sys_calls, "pin" <> num), do: "Pin #{num} value"
|
|
|
|
def format_lhs(sys_calls, %{
|
|
kind: :named_pin,
|
|
args: %{pin_type: type, pin_id: pin_id}
|
|
}) do
|
|
case named_pin(sys_calls, type, pin_id) do
|
|
%{label: label} -> label
|
|
{:error, _reason} -> "unknown left hand side"
|
|
end
|
|
end
|
|
|
|
def eval_assertion(sys_calls \\ @sys_calls, comment, expression)
|
|
when is_binary(expression) do
|
|
case sys_calls.eval_assertion(comment, expression) do
|
|
true ->
|
|
true
|
|
|
|
false ->
|
|
false
|
|
|
|
{:error, reason} = error when is_binary(reason) ->
|
|
or_error(sys_calls, :eval_assertion, [comment, expression], error)
|
|
end
|
|
end
|
|
|
|
def log_assertion(sys_calls \\ @sys_calls, passed?, type, message) do
|
|
if function_exported?(sys_calls, :log_assertion, 3) do
|
|
apply(sys_calls, :log_assertion, [passed?, type, message])
|
|
end
|
|
end
|
|
|
|
# TODO: Connor, due to default arg here, things get weird
|
|
def log(message, force? \\ false) when is_binary(message) do
|
|
apply(@sys_calls, :log, [message, force?])
|
|
end
|
|
|
|
def sequence_init_log(sys_calls \\ @sys_calls, message)
|
|
when is_binary(message) do
|
|
apply(sys_calls, :sequence_init_log, [message])
|
|
end
|
|
|
|
def sequence_complete_log(sys_calls \\ @sys_calls, message)
|
|
when is_binary(message) do
|
|
apply(sys_calls, :sequence_complete_log, [message])
|
|
end
|
|
|
|
def calibrate(sys_calls \\ @sys_calls, axis) when axis in ["x", "y", "z"] do
|
|
ok_or_error(sys_calls, :calibrate, [axis])
|
|
end
|
|
|
|
def change_ownership(sys_calls \\ @sys_calls, email, secret, server) do
|
|
ok_or_error(sys_calls, :change_ownership, [email, secret, server])
|
|
end
|
|
|
|
def check_update(sys_calls \\ @sys_calls) do
|
|
ok_or_error(sys_calls, :check_update, [])
|
|
end
|
|
|
|
def coordinate(sys_calls \\ @sys_calls, x, y, z)
|
|
when is_number(x)
|
|
when is_number(y)
|
|
when is_number(z) do
|
|
coord_or_error(sys_calls, :coordinate, [x, y, z])
|
|
end
|
|
|
|
def dump_info(sys_calls \\ @sys_calls) do
|
|
ok_or_error(sys_calls, :dump_info, [])
|
|
end
|
|
|
|
def emergency_lock(sys_calls \\ @sys_calls) do
|
|
ok_or_error(sys_calls, :emergency_lock, [])
|
|
end
|
|
|
|
def emergency_unlock(sys_calls \\ @sys_calls) do
|
|
ok_or_error(sys_calls, :emergency_unlock, [])
|
|
end
|
|
|
|
def execute_script(sys_calls \\ @sys_calls, package, %{} = env)
|
|
when is_binary(package) do
|
|
ok_or_error(sys_calls, :execute_script, [package, env])
|
|
end
|
|
|
|
def update_farmware(sys_calls \\ @sys_calls, package)
|
|
when is_binary(package) do
|
|
ok_or_error(sys_calls, :update_farmware, [package])
|
|
end
|
|
|
|
def factory_reset(sys_calls \\ @sys_calls, package) do
|
|
ok_or_error(sys_calls, :factory_reset, [package])
|
|
end
|
|
|
|
def find_home(sys_calls \\ @sys_calls, axis) when axis in ["x", "y", "z"] do
|
|
ok_or_error(sys_calls, :find_home, [axis])
|
|
end
|
|
|
|
def firmware_reboot(sys_calls \\ @sys_calls) do
|
|
ok_or_error(sys_calls, :firmware_reboot, [])
|
|
end
|
|
|
|
def flash_firmware(sys_calls \\ @sys_calls, package) do
|
|
ok_or_error(sys_calls, :flash_firmware, [package])
|
|
end
|
|
|
|
def get_current_x(sys_calls \\ @sys_calls) do
|
|
number_or_error(sys_calls, :get_current_x, [])
|
|
end
|
|
|
|
def get_current_y(sys_calls \\ @sys_calls) do
|
|
number_or_error(sys_calls, :get_current_y, [])
|
|
end
|
|
|
|
def get_current_z(sys_calls \\ @sys_calls) do
|
|
number_or_error(sys_calls, :get_current_z, [])
|
|
end
|
|
|
|
def get_cached_x(sys_calls \\ @sys_calls) do
|
|
number_or_nil_or_error(sys_calls, :get_cached_x, [])
|
|
end
|
|
|
|
def get_cached_y(sys_calls \\ @sys_calls) do
|
|
number_or_nil_or_error(sys_calls, :get_cached_y, [])
|
|
end
|
|
|
|
def get_cached_z(sys_calls \\ @sys_calls) do
|
|
number_or_nil_or_error(sys_calls, :get_cached_z, [])
|
|
end
|
|
|
|
def get_sequence(sys_calls \\ @sys_calls, sequence_id) do
|
|
case sys_calls.get_sequence(sequence_id) do
|
|
%AST{} = ast -> ast
|
|
error -> or_error(sys_calls, :get_sequence, [sequence_id], error)
|
|
end
|
|
end
|
|
|
|
def get_toolslot_for_tool(sys_calls \\ @sys_calls, id) do
|
|
coord_or_error(sys_calls, :get_toolslot_for_tool, [id])
|
|
end
|
|
|
|
def home(sys_calls \\ @sys_calls, axis, speed)
|
|
when axis in ["x", "y", "z"]
|
|
when is_number(speed) do
|
|
ok_or_error(sys_calls, :home, [axis, speed])
|
|
end
|
|
|
|
def install_first_party_farmware(sys_calls \\ @sys_calls) do
|
|
ok_or_error(sys_calls, :install_first_party_farmware, [])
|
|
end
|
|
|
|
def move_absolute(sys_calls \\ @sys_calls, x, y, z, speed)
|
|
when is_number(x)
|
|
when is_number(y)
|
|
when is_number(z) do
|
|
ok_or_error(sys_calls, :move_absolute, [x, y, z, speed])
|
|
end
|
|
|
|
def named_pin(sys_calls \\ @sys_calls, type, id) do
|
|
case sys_calls.named_pin(type, id) do
|
|
%{} = data -> %{} = data
|
|
number when is_integer(number) -> number
|
|
error -> or_error(sys_calls, :named_pin, [type, id], error)
|
|
end
|
|
end
|
|
|
|
def nothing(sys_calls \\ @sys_calls) do
|
|
sys_calls.nothing()
|
|
end
|
|
|
|
def point(sys_calls \\ @sys_calls, type, id) do
|
|
coord_or_error(sys_calls, :point, [type, id])
|
|
end
|
|
|
|
def power_off(sys_calls \\ @sys_calls) do
|
|
ok_or_error(sys_calls, :power_off, [])
|
|
end
|
|
|
|
def read_pin(sys_calls \\ @sys_calls, pin_num, pin_mode) do
|
|
number_or_error(sys_calls, :read_pin, [pin_num, pin_mode])
|
|
end
|
|
|
|
def read_cached_pin(sys_calls \\ @sys_calls, pin_num) do
|
|
number_or_nil_or_error(sys_calls, :read_cached_pin, [pin_num])
|
|
end
|
|
|
|
def toggle_pin(sys_calls \\ @sys_calls, pun_num) do
|
|
ok_or_error(sys_calls, :toggle_pin, [pun_num])
|
|
end
|
|
|
|
def read_status(sys_calls \\ @sys_calls) do
|
|
ok_or_error(sys_calls, :read_status, [])
|
|
end
|
|
|
|
def reboot(sys_calls \\ @sys_calls) do
|
|
ok_or_error(sys_calls, :reboot, [])
|
|
end
|
|
|
|
def resource_update(sys_calls \\ @sys_calls, kind, id, params) do
|
|
ok_or_error(sys_calls, :resource_update, [kind, id, params])
|
|
end
|
|
|
|
def send_message(sys_calls \\ @sys_calls, kind, msg, channels) do
|
|
ok_or_error(sys_calls, :send_message, [kind, msg, channels])
|
|
end
|
|
|
|
def set_servo_angle(sys_calls \\ @sys_calls, pin_num, pin_value) do
|
|
ok_or_error(sys_calls, :set_servo_angle, [pin_num, pin_value])
|
|
end
|
|
|
|
def set_pin_io_mode(sys_calls \\ @sys_calls, pin_number, pin_io_mode) do
|
|
ok_or_error(sys_calls, :set_pin_io_mode, [pin_number, pin_io_mode])
|
|
end
|
|
|
|
def set_user_env(sys_calls \\ @sys_calls, key, value) do
|
|
ok_or_error(sys_calls, :set_user_env, [key, value])
|
|
end
|
|
|
|
def sync(sys_calls \\ @sys_calls) do
|
|
ok_or_error(sys_calls, :sync, [])
|
|
end
|
|
|
|
def wait(sys_calls \\ @sys_calls, millis) do
|
|
ok_or_error(sys_calls, :wait, [millis])
|
|
end
|
|
|
|
def write_pin(sys_calls \\ @sys_calls, pin_number, pin_mode, pin_value) do
|
|
ok_or_error(sys_calls, :write_pin, [pin_number, pin_mode, pin_value])
|
|
end
|
|
|
|
def zero(sys_calls \\ @sys_calls, axis) when axis in ["x", "y", "z"] do
|
|
ok_or_error(sys_calls, :zero, [axis])
|
|
end
|
|
|
|
defp ok_or_error(sys_calls, fun, args) do
|
|
case apply(sys_calls, fun, args) do
|
|
:ok -> :ok
|
|
error -> or_error(sys_calls, fun, args, error)
|
|
end
|
|
end
|
|
|
|
defp number_or_error(sys_calls, fun, args) do
|
|
case apply(sys_calls, fun, args) do
|
|
result when is_number(result) -> result
|
|
error -> or_error(sys_calls, fun, args, error)
|
|
end
|
|
end
|
|
|
|
defp number_or_nil_or_error(sys_calls, fun, args) do
|
|
case apply(sys_calls, fun, args) do
|
|
result when is_number(result) -> result
|
|
nil -> nil
|
|
error -> or_error(sys_calls, fun, args, error)
|
|
end
|
|
end
|
|
|
|
defp coord_or_error(sys_calls, fun, args) do
|
|
case apply(sys_calls, fun, args) do
|
|
%{x: x, y: y, z: z} = coord
|
|
when is_number(x)
|
|
when is_number(y)
|
|
when is_number(z) ->
|
|
coord
|
|
|
|
error ->
|
|
or_error(sys_calls, fun, args, error)
|
|
end
|
|
end
|
|
|
|
defp point_group_or_error(sys_calls, fun, args) do
|
|
case apply(sys_calls, fun, args) do
|
|
%{point_ids: ids} = point_group when is_list(ids) -> point_group
|
|
error -> or_error(sys_calls, fun, args, error)
|
|
end
|
|
end
|
|
|
|
defp or_error(_sys_calls, _fun, _args, {:error, reason})
|
|
when is_binary(reason) do
|
|
{:error, reason}
|
|
end
|
|
|
|
defp or_error(sys_calls, fun, args, bad_val) do
|
|
raise RuntimeError,
|
|
message: """
|
|
Bad return value: #{inspect(bad_val)}
|
|
called as:
|
|
#{sys_calls}.#{fun}(#{Enum.join(Enum.map(args, &inspect/1), ",")})
|
|
"""
|
|
end
|
|
end
|