Implement new AST: `assert`.
This is a new AST that will allow executing a simple expression and conditionally pass/fail and cleanup when it completes.pull/974/head
parent
a04ddf0159
commit
7e1ceaf7be
|
@ -56,3 +56,4 @@ nerves-hub
|
||||||
*.pem
|
*.pem
|
||||||
*.db
|
*.db
|
||||||
*.db-journal
|
*.db-journal
|
||||||
|
*.lua
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
# CeleryScript IF `expression` field.
|
||||||
|
|
||||||
|
The CeleryScript `if` block takes a possible left hand side value of
|
||||||
|
`expression` which allows an arbitrary string to be evaluated. This
|
||||||
|
expression is evaluated against a lua 5.2 interpreter.
|
||||||
|
|
||||||
|
## Lua API
|
||||||
|
The following functions are available for usage along with [Lua's
|
||||||
|
standard library](https://www.lua.org/manual/5.2/).
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- Comments are ignored by the interpreter
|
||||||
|
|
||||||
|
-- help(function_name)
|
||||||
|
-- Returns docs for a function
|
||||||
|
|
||||||
|
print(help("send_message"));
|
||||||
|
print(help("get_position"));
|
||||||
|
|
||||||
|
-- get_position()
|
||||||
|
-- Returns a table containing the current position data
|
||||||
|
|
||||||
|
position = get_position();
|
||||||
|
if position.x <= 20.55 then
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
print("current position: (", position.x, ",", position.y, "," position.z, ")");
|
||||||
|
return false;
|
||||||
|
end
|
||||||
|
|
||||||
|
-- get_pins()
|
||||||
|
-- Returns a table containing current pin data
|
||||||
|
|
||||||
|
pins = get_pins();
|
||||||
|
if pins[9] == 1.0 then
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
|
||||||
|
-- send_message(type, message, channels)
|
||||||
|
-- Sends a message to farmbot's logger
|
||||||
|
|
||||||
|
send_message("info", "hello, world", ["toast"])
|
||||||
|
```
|
||||||
|
|
||||||
|
## Expression contract
|
||||||
|
Expressions are expected to be evaluated in a certain way. The evaluation will fail
|
||||||
|
if this contract is not met. An expression should return one of the following values:
|
||||||
|
* `true`
|
||||||
|
* `false`
|
||||||
|
* `("error", "string reason signaling an error happened")`
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
Check if the x position is within a range of 5 and 10
|
||||||
|
|
||||||
|
```lua
|
||||||
|
position = get_position();
|
||||||
|
return position.x >= 5 and position.x <= 10;
|
||||||
|
```
|
||||||
|
|
||||||
|
Check is a pin is a toggled, with error checking
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- All farmbot functions will return a tuple containing an error
|
||||||
|
-- if something bad happens
|
||||||
|
|
||||||
|
position, positionErr = get_position();
|
||||||
|
pins, pinErr = get_pins();
|
||||||
|
if positionErr or pinErr then
|
||||||
|
return "error", positionErr or pinErr;
|
||||||
|
else
|
||||||
|
return pins[9] == 1.0
|
||||||
|
end
|
||||||
|
```
|
|
@ -166,8 +166,35 @@ defmodule FarmbotCeleryScript.Compiler do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# `Assert` is a internal node useful for self testing.
|
||||||
|
compile :assertion, %{lua: expression, op: op, _then: then_ast} do
|
||||||
|
quote location: :keep do
|
||||||
|
case FarmbotCeleryScript.SysCalls.eval_assertion(unquote(compile_ast(expression))) do
|
||||||
|
{:error, reason} ->
|
||||||
|
{:error, reason}
|
||||||
|
|
||||||
|
true ->
|
||||||
|
:ok
|
||||||
|
|
||||||
|
false when unquote(op) == "abort" ->
|
||||||
|
FarmbotCeleryScript.SysCalls.log("Assertion failed (aborting)")
|
||||||
|
{:error, "Assertion failed (aborting)"}
|
||||||
|
|
||||||
|
false when unquote(op) == "recover" ->
|
||||||
|
FarmbotCeleryScript.SysCalls.log("Assertion failed (recovering)")
|
||||||
|
unquote(compile_block(then_ast))
|
||||||
|
|
||||||
|
false when unquote(op) == "abort_recover" ->
|
||||||
|
FarmbotCeleryScript.SysCalls.log("Assertion failed (recovering then aborting)")
|
||||||
|
unquote(compile_block([then_ast, %AST{kind: :abort, args: %{}}]))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Compiles an if statement.
|
# Compiles an if statement.
|
||||||
compile :_if, %{_then: then_ast, _else: else_ast, lhs: lhs, op: op, rhs: rhs} do
|
compile :_if, %{_then: then_ast, _else: else_ast, lhs: lhs, op: op, rhs: rhs} do
|
||||||
|
rhs = compile_ast(rhs)
|
||||||
|
|
||||||
# Turns the left hand side arg into
|
# Turns the left hand side arg into
|
||||||
# a number. x, y, z, and pin{number} are special that need to be
|
# a number. x, y, z, and pin{number} are special that need to be
|
||||||
# evaluated before evaluating the if statement.
|
# evaluated before evaluating the if statement.
|
||||||
|
@ -188,6 +215,10 @@ defmodule FarmbotCeleryScript.Compiler do
|
||||||
quote [location: :keep],
|
quote [location: :keep],
|
||||||
do: FarmbotCeleryScript.SysCalls.read_pin(unquote(String.to_integer(pin)), nil)
|
do: FarmbotCeleryScript.SysCalls.read_pin(unquote(String.to_integer(pin)), nil)
|
||||||
|
|
||||||
|
"expression" ->
|
||||||
|
quote [location: :keep],
|
||||||
|
do: FarmbotCeleryScript.SysCalls.eval_assertion(rhs)
|
||||||
|
|
||||||
# Named pin has two intents here
|
# Named pin has two intents here
|
||||||
# in this case we want to read the named pin.
|
# in this case we want to read the named pin.
|
||||||
%AST{kind: :named_pin} = ast ->
|
%AST{kind: :named_pin} = ast ->
|
||||||
|
@ -198,8 +229,6 @@ defmodule FarmbotCeleryScript.Compiler do
|
||||||
compile_ast(ast)
|
compile_ast(ast)
|
||||||
end
|
end
|
||||||
|
|
||||||
rhs = compile_ast(rhs)
|
|
||||||
|
|
||||||
# Turn the `op` arg into Elixir code
|
# Turn the `op` arg into Elixir code
|
||||||
if_eval =
|
if_eval =
|
||||||
case op do
|
case op do
|
||||||
|
@ -238,6 +267,11 @@ defmodule FarmbotCeleryScript.Compiler do
|
||||||
quote location: :keep do
|
quote location: :keep do
|
||||||
unquote(lhs) > unquote(rhs)
|
unquote(lhs) > unquote(rhs)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
quote location: :keep do
|
||||||
|
unquote(lhs)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Finally, compile the entire if statement.
|
# Finally, compile the entire if statement.
|
||||||
|
@ -336,6 +370,12 @@ defmodule FarmbotCeleryScript.Compiler do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
compile :abort do
|
||||||
|
quote location: :keep do
|
||||||
|
Macro.escape({:error, "aborted"})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Compiles move_absolute
|
# Compiles move_absolute
|
||||||
compile :move_absolute, %{location: location, offset: offset, speed: speed} do
|
compile :move_absolute, %{location: location, offset: offset, speed: speed} do
|
||||||
quote location: :keep do
|
quote location: :keep do
|
||||||
|
|
|
@ -136,7 +136,7 @@ defmodule FarmbotCeleryScript.Scheduler do
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info(:checkup, %{next: nil} = state) do
|
def handle_info(:checkup, %{next: nil} = state) do
|
||||||
Logger.debug("Scheduling next checkup with no next")
|
# Logger.debug("Scheduling next checkup with no next")
|
||||||
|
|
||||||
state
|
state
|
||||||
|> schedule_next_checkup()
|
|> schedule_next_checkup()
|
||||||
|
|
|
@ -67,6 +67,20 @@ defmodule FarmbotCeleryScript.SysCalls do
|
||||||
@callback log(message :: String.t()) :: any()
|
@callback log(message :: String.t()) :: any()
|
||||||
@callback sequence_init_log(message :: String.t()) :: any()
|
@callback sequence_init_log(message :: String.t()) :: any()
|
||||||
@callback sequence_complete_log(message :: String.t()) :: any()
|
@callback sequence_complete_log(message :: String.t()) :: any()
|
||||||
|
@callback eval_assertion(expression :: String.t()) :: true | false | error()
|
||||||
|
|
||||||
|
def eval_assertion(sys_calls \\ @sys_calls, expression) when is_binary(expression) do
|
||||||
|
case sys_calls.eval_assertion(expression) do
|
||||||
|
true ->
|
||||||
|
true
|
||||||
|
|
||||||
|
false ->
|
||||||
|
false
|
||||||
|
|
||||||
|
{:error, reason} when is_binary(reason) ->
|
||||||
|
or_error(sys_calls, :eval_assertion, [expression], reason)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def log(sys_calls \\ @sys_calls, message) when is_binary(message) do
|
def log(sys_calls \\ @sys_calls, message) when is_binary(message) do
|
||||||
apply(sys_calls, :log, [message])
|
apply(sys_calls, :log, [message])
|
||||||
|
|
|
@ -129,6 +129,9 @@ defmodule FarmbotCeleryScript.SysCalls.Stubs do
|
||||||
@impl true
|
@impl true
|
||||||
def zero(axis), do: error(:zero, [axis])
|
def zero(axis), do: error(:zero, [axis])
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def eval_assertion(expression), do: error(:eval_assertion, [expression])
|
||||||
|
|
||||||
defp error(fun, _args) do
|
defp error(fun, _args) do
|
||||||
msg = """
|
msg = """
|
||||||
CeleryScript syscall stubbed: #{fun}
|
CeleryScript syscall stubbed: #{fun}
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
defmodule FarmbotOS.Lua do
|
||||||
|
@type t() :: tuple()
|
||||||
|
@type table() :: [{any, any}]
|
||||||
|
alias FarmbotOS.Lua.CeleryScript
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Evaluates some Lua code. The code should
|
||||||
|
return a boolean value.
|
||||||
|
"""
|
||||||
|
def eval_assertion(str) when is_binary(str) do
|
||||||
|
init()
|
||||||
|
|> set_table([:get_position], &CeleryScript.get_position/2)
|
||||||
|
|> set_table([:get_pins], &CeleryScript.get_pins/2)
|
||||||
|
|> set_table([:send_message], &CeleryScript.send_message/2)
|
||||||
|
|> set_table([:help], &CeleryScript.help/2)
|
||||||
|
|> set_table([:version], &CeleryScript.version/2)
|
||||||
|
|> eval(str)
|
||||||
|
|> case do
|
||||||
|
{:ok, [true | _]} ->
|
||||||
|
true
|
||||||
|
|
||||||
|
{:ok, [false | _]} ->
|
||||||
|
false
|
||||||
|
|
||||||
|
{:ok, [_, reason]} when is_binary(reason) ->
|
||||||
|
{:error, reason}
|
||||||
|
|
||||||
|
{:ok, _data} ->
|
||||||
|
{:error, "bad return value from expression evaluation"}
|
||||||
|
|
||||||
|
{:error, {:lua_error, _error, _lua}} ->
|
||||||
|
{:error, "lua runtime error evaluating expression"}
|
||||||
|
|
||||||
|
{:error, {:badmatch, {:error, [{line, :luerl_parse, parse_error}], _}}} ->
|
||||||
|
{:error, "failed to parse expression (line:#{line}): #{IO.iodata_to_binary(parse_error)}"}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec init() :: t()
|
||||||
|
def init do
|
||||||
|
:luerl.init()
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec set_table(t(), Path.t(), any()) :: t()
|
||||||
|
def set_table(lua, path, value) do
|
||||||
|
:luerl.set_table(path, value, lua)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec eval(t(), String.t()) :: {:ok, any()} | {:error, any()}
|
||||||
|
def eval(lua, hook) when is_binary(hook) do
|
||||||
|
:luerl.eval(hook, lua)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,147 @@
|
||||||
|
defmodule FarmbotOS.Lua.CeleryScript do
|
||||||
|
alias FarmbotCeleryScript.SysCalls
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns a table containing position data
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
print("x", farmbot.get_position().x);
|
||||||
|
print("y", farmbot.get_position()["y"]);
|
||||||
|
position = farmbot.get_position();
|
||||||
|
print("z", position.z);
|
||||||
|
"""
|
||||||
|
def get_position(["x"], lua) do
|
||||||
|
case SysCalls.get_current_x() do
|
||||||
|
x when is_number(x) ->
|
||||||
|
{[x, nil], lua}
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
{[nil, reason], lua}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_position(["y"], lua) do
|
||||||
|
case SysCalls.get_current_y() do
|
||||||
|
y when is_number(y) ->
|
||||||
|
{[y, nil], lua}
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
{[nil, reason], lua}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_position(["z"], lua) do
|
||||||
|
case SysCalls.get_current_z() do
|
||||||
|
z when is_number(z) ->
|
||||||
|
{[z, nil], lua}
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
{[nil, reason], lua}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_position(_args, lua) do
|
||||||
|
with x when is_number(x) <- SysCalls.get_current_x(),
|
||||||
|
y when is_number(y) <- SysCalls.get_current_y(),
|
||||||
|
z when is_number(z) <- SysCalls.get_current_z() do
|
||||||
|
{[[{"x", x}, {"y", y}, {"z", z}], nil], lua}
|
||||||
|
else
|
||||||
|
{:error, reason} ->
|
||||||
|
{[nil, reason], lua}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns a table with pins data
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
print("pin9", farmbot.get_pin()["9"]);
|
||||||
|
"""
|
||||||
|
def get_pins(_args, lua) do
|
||||||
|
case do_get_pins(Enum.to_list(0..69)) do
|
||||||
|
{:ok, contents} ->
|
||||||
|
{[contents, nil], lua}
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
{[nil, reason], lua}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
# Example Usage
|
||||||
|
|
||||||
|
## With channels
|
||||||
|
|
||||||
|
farmbot.send_message("info", "hello, world", ["email", "toast"])
|
||||||
|
|
||||||
|
## No channels
|
||||||
|
|
||||||
|
farmbot.send_message("info", "hello, world")
|
||||||
|
|
||||||
|
"""
|
||||||
|
def send_message([kind, message], lua) do
|
||||||
|
do_send_message(kind, message, [], lua)
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_message([kind, message | channels], lua) do
|
||||||
|
channels = Enum.map(channels, &String.to_atom/1)
|
||||||
|
do_send_message(kind, message, channels, lua)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns help docs about a function.
|
||||||
|
"""
|
||||||
|
def help([function_name], lua) do
|
||||||
|
function_name = String.to_atom(function_name)
|
||||||
|
|
||||||
|
case Code.fetch_docs(__MODULE__) do
|
||||||
|
{:docs_v1, _, _, _, _, _, docs} ->
|
||||||
|
docs =
|
||||||
|
Enum.find_value(docs, fn
|
||||||
|
{{:function, ^function_name, _arity}, _, _, %{"en" => docs}, _} ->
|
||||||
|
IO.iodata_to_binary(docs)
|
||||||
|
|
||||||
|
_other ->
|
||||||
|
false
|
||||||
|
end)
|
||||||
|
|
||||||
|
if docs,
|
||||||
|
do: {[docs, nil], lua},
|
||||||
|
else: {[nil, "docs not found"], lua}
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
{[nil, reason], lua}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "Returns the current version of farmbot."
|
||||||
|
def version(_args, lua) do
|
||||||
|
{[FarmbotCore.Project.version(), nil], lua}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_send_message(kind, message, channels, lua) do
|
||||||
|
case SysCalls.send_message(kind, message, channels) do
|
||||||
|
:ok ->
|
||||||
|
{[true, nil], lua}
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
{[nil, reason], lua}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_get_pins(nums, acc \\ [])
|
||||||
|
|
||||||
|
defp do_get_pins([p | rest], acc) do
|
||||||
|
case FarmbotFirmware.request({:pin_read, [p: p]}) do
|
||||||
|
{:ok, {_, {:report_pin_value, [p: ^p, v: v]}}} ->
|
||||||
|
do_get_pins(rest, [{to_string(p), v} | acc])
|
||||||
|
|
||||||
|
er ->
|
||||||
|
er
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_get_pins([], acc), do: {:ok, Enum.reverse(acc)}
|
||||||
|
end
|
|
@ -23,6 +23,8 @@ defmodule FarmbotOS.SysCalls do
|
||||||
SetPinIOMode
|
SetPinIOMode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
alias FarmbotOS.Lua
|
||||||
|
|
||||||
alias FarmbotCore.{Asset, Asset.Repo, Asset.Private, Asset.Sync, BotState, Leds}
|
alias FarmbotCore.{Asset, Asset.Repo, Asset.Private, Asset.Sync, BotState, Leds}
|
||||||
alias FarmbotExt.{API, API.Reconciler, API.SyncGroup}
|
alias FarmbotExt.{API, API.Reconciler, API.SyncGroup}
|
||||||
|
|
||||||
|
@ -58,6 +60,9 @@ defmodule FarmbotOS.SysCalls do
|
||||||
@impl true
|
@impl true
|
||||||
defdelegate set_pin_io_mode(pin, mode), to: SetPinIOMode
|
defdelegate set_pin_io_mode(pin, mode), to: SetPinIOMode
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
defdelegate eval_assertion(expression), to: Lua
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def log(message) do
|
def log(message) do
|
||||||
if FarmbotCore.Asset.fbos_config(:sequence_body_log) do
|
if FarmbotCore.Asset.fbos_config(:sequence_body_log) do
|
||||||
|
|
|
@ -82,6 +82,7 @@ defmodule FarmbotOS.MixProject do
|
||||||
{:nerves_hub_cli, "~> 0.7", runtime: false},
|
{:nerves_hub_cli, "~> 0.7", runtime: false},
|
||||||
{:shoehorn, "~> 0.6"},
|
{:shoehorn, "~> 0.6"},
|
||||||
{:ring_logger, "~> 0.8"},
|
{:ring_logger, "~> 0.8"},
|
||||||
|
{:luerl, github: "rvirding/luerl"},
|
||||||
|
|
||||||
# Host/test only dependencies.
|
# Host/test only dependencies.
|
||||||
{:excoveralls, "~> 0.10", only: [:test], targets: [:host]},
|
{:excoveralls, "~> 0.10", only: [:test], targets: [:host]},
|
||||||
|
|
|
@ -33,6 +33,8 @@
|
||||||
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
|
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
"jsx": {:hex, :jsx, "2.9.0", "d2f6e5f069c00266cad52fb15d87c428579ea4d7d73a33669e12679e203329dd", [:mix, :rebar3], [], "hexpm"},
|
"jsx": {:hex, :jsx, "2.9.0", "d2f6e5f069c00266cad52fb15d87c428579ea4d7d73a33669e12679e203329dd", [:mix, :rebar3], [], "hexpm"},
|
||||||
"lager": {:hex, :lager, "3.6.5", "831910109f3fcb503debf658ca0538836b348c58bfbf349a6d48228096ce9040", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, repo: "hexpm", optional: false]}], "hexpm"},
|
"lager": {:hex, :lager, "3.6.5", "831910109f3fcb503debf658ca0538836b348c58bfbf349a6d48228096ce9040", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
"luer": {:git, "https://github.com/rvirding/luerl.git", "ce4e1b5a66a2a37efe2f8cd16e365ad9845b5015", []},
|
||||||
|
"luerl": {:git, "https://github.com/rvirding/luerl.git", "ce4e1b5a66a2a37efe2f8cd16e365ad9845b5015", []},
|
||||||
"makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
|
"makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
|
"makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"mdns_lite": {:hex, :mdns_lite, "0.1.0", "efad834847576ab7641d1016754ec6f512d765788ae3718e1ac6a648c85eab12", [:mix], [{:dns, "~> 2.1", [hex: :dns, repo: "hexpm", optional: false]}], "hexpm"},
|
"mdns_lite": {:hex, :mdns_lite, "0.1.0", "efad834847576ab7641d1016754ec6f512d765788ae3718e1ac6a648c85eab12", [:mix], [{:dns, "~> 2.1", [hex: :dns, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
|
|
@ -241,6 +241,11 @@ defmodule Farmbot.TestSupport.CeleryScript.TestSysCalls do
|
||||||
call({:zero, [axis]})
|
call({:zero, [axis]})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def eval_assertion(expression) do
|
||||||
|
call({:eval_assertion, [expression]})
|
||||||
|
end
|
||||||
|
|
||||||
defp call(data) do
|
defp call(data) do
|
||||||
{handler, kind, args} = GenServer.call(__MODULE__, data, :infinity)
|
{handler, kind, args} = GenServer.call(__MODULE__, data, :infinity)
|
||||||
handler.(kind, args)
|
handler.(kind, args)
|
||||||
|
|
Loading…
Reference in New Issue