Add Lua helpers for updating and getting resources

pull/974/head
Connor Rigby 2019-08-23 10:31:13 -07:00
parent a242cce830
commit 209ded96cb
No known key found for this signature in database
GPG Key ID: 29A88B24B70456E0
11 changed files with 277 additions and 99 deletions

View File

@ -11,12 +11,6 @@ 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
@ -28,6 +22,8 @@ else
return false;
end
return get_position("y") <= 20;
-- get_pins()
-- Returns a table containing current pin data
@ -36,10 +32,106 @@ if pins[9] == 1.0 then
return true;
end
return get_pin(10) == 0;
-- send_message(type, message, channels)
-- Sends a message to farmbot's logger
send_message("info", "hello, world", ["toast"])
send_message("info", "hello, world", ["toast"]);
-- calibrate(axis)
-- calibrate an axis
calibrate("x");
calibrate("y");
calibrate("z");
-- emergency_lock()
-- emergency_unlock()
-- lock and unlock farmbot's firmware.
send_message("error", "locking the firmware!");
emergancy_lock();
emergency_unlock();
-- find_home(axis)
-- Find home on an axis.
find_home("x");
find_home("y");
find_home("z");
-- home(axis)
-- Go to home on an axis.
home("x");
home("y");
home("z");
-- coordinate(x, y, z)
-- create a vec3
move_to = coordinate(1.0, 0, 0);
-- move_absolute(x, y, z)
-- Move in a line to a position
move_absolute(1.0, 0, 0);
move_absolute(coordinate(1.0, 20, 30));
-- check_position(vec3, tolerance)
-- Check a position against Farmbot's current
-- position within a error threshold
move_absolute(1.0, 0, 0);
return check_position({x = 1.0, y = 0; z = 0}, 0.50);
move_absolute(20, 100, 100);
return check_position(coordinate(20, 100, 100), 1);
-- read_status(arg0)
-- Get a field on farmbot's current state
status = read_status();
return status.informational_settings.wifi_level >= 5;
return read_status("location_data", "raw_encoders") >= 1900;
-- version()
-- return Farmbot's current version
return version() == "8.1.2";
-- get_device(field)
-- return the device settings
return get_device().timezone == "America/los_angeles";
return get_device("name") == "Test Farmbot";
-- update_device(table)
-- update device settings
update_device({name = "Test Farmbot"});
-- get_fbos_config(field)
-- return the current fbos_config
return get_fbos_config("auto_sync");
return get_fbos_config().os_auto_update;
-- update_fbos_config(table)
-- update the current fbos_config
update_fbos_config({auto_sync = true, os_auto_update = false});
-- get_firmware_config(field)
-- return current firmware_config data
return get_firmware_config().encoder_enabled_x == 1.0;
return get_firmware_config("encoder_enabled_z");
-- update_firmware_config(table)
-- update current firmware_config data
update_firmware_config({encoder_enabled_z = 1.0});
```
## Expression contract

View File

@ -187,11 +187,16 @@ defmodule FarmbotCeleryScript.Compiler do
quote location: :keep do
comment_header = unquote(comment_header)
assertion_type = unquote(assertion_type)
case FarmbotCeleryScript.SysCalls.eval_assertion(unquote(compile_ast(expression))) do
case FarmbotCeleryScript.SysCalls.eval_assertion(
unquote(comment),
unquote(compile_ast(expression))
) do
{:error, reason} ->
FarmbotCeleryScript.SysCalls.log_assertion(
false,
assertion_type,
"#{comment_header}failed to evaluate, aborting"
)
@ -200,34 +205,43 @@ defmodule FarmbotCeleryScript.Compiler do
true ->
FarmbotCeleryScript.SysCalls.log_assertion(
true,
assertion_type,
"#{comment_header}passed, continuing execution"
)
:ok
false when unquote(assertion_type) == "continue" ->
false when assertion_type == "continue" ->
FarmbotCeleryScript.SysCalls.log_assertion(
false,
assertion_type,
"#{comment_header}failed, continuing execution"
)
:ok
false when unquote(assertion_type) == "abort" ->
FarmbotCeleryScript.SysCalls.log_assertion(false, "#{comment_header}failed, aborting")
{:error, "Assertion failed (aborting)"}
false when unquote(assertion_type) == "recover" ->
false when assertion_type == "abort" ->
FarmbotCeleryScript.SysCalls.log_assertion(
false,
assertion_type,
"#{comment_header}failed, aborting"
)
{:error, "Assertion failed (aborting)"}
false when assertion_type == "recover" ->
FarmbotCeleryScript.SysCalls.log_assertion(
false,
assertion_type,
"#{comment_header}failed, recovering and continuing"
)
unquote(compile_block(then_ast))
false when unquote(assertion_type) == "abort_recover" ->
false when assertion_type == "abort_recover" ->
FarmbotCeleryScript.SysCalls.log_assertion(
false,
assertion_type,
"#{comment_header}failed, recovering and aborting"
)

View File

@ -67,24 +67,25 @@ defmodule FarmbotCeleryScript.SysCalls do
@callback log(message :: String.t()) :: any()
@callback sequence_init_log(message :: String.t()) :: any()
@callback sequence_complete_log(message :: String.t()) :: any()
@callback eval_assertion(expression :: String.t()) :: true | false | error()
@callback eval_assertion(comment :: String.t(), 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
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} when is_binary(reason) ->
or_error(sys_calls, :eval_assertion, [expression], reason)
{: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?, message) do
if function_exported?(sys_calls, :log_assertion, 2) do
apply(sys_calls, :log_assertion, [passed?, message])
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

View File

@ -130,7 +130,7 @@ defmodule FarmbotCeleryScript.SysCalls.Stubs do
def zero(axis), do: error(:zero, [axis])
@impl true
def eval_assertion(expression), do: error(:eval_assertion, [expression])
def eval_assertion(comment, expression), do: error(:eval_assertion, [comment, expression])
defp error(fun, _args) do
msg = """

View File

@ -96,7 +96,10 @@ defmodule FarmbotExt.AMQP.LogChannel do
# ANSWER(Connor) - because the FE needed it.
created_at: DateTime.from_naive!(log.inserted_at, "Etc/UTC") |> DateTime.to_unix(),
channels: log.meta[:channels] || log.meta["channels"] || [],
assertion_passed: log.meta[:assertion_passed],
meta: %{
assertion_passed: log.meta[:assertion_passed],
assertion_type: log.meta[:assertion_type]
},
message: log.message
}

View File

@ -1,21 +1,24 @@
defmodule FarmbotOS.Lua do
@type t() :: tuple()
@type table() :: [{any, any}]
require FarmbotCore.Logger
alias FarmbotOS.Lua.Ext.{
Data,
Firmware,
Info
}
def log_assertion(passed?, message) do
# FarmbotCore.Logger.dispatch_log(__ENV__, :assertion, 2, message, [assertion_passed: passed?])
FarmbotCore.Logger.dispatch_log(__ENV__, :info, 2, message, assertion_passed: passed?)
def log_assertion(passed?, type, message) do
meta = [assertion_passed: passed?, assertion_type: type]
FarmbotCore.Logger.dispatch_log(__ENV__, :assertion, 2, message, meta)
end
@doc """
Evaluates some Lua code. The code should
return a boolean value.
"""
def eval_assertion(str) when is_binary(str) do
def eval_assertion(comment, str) when is_binary(str) do
init()
|> eval(str)
|> case do
@ -35,6 +38,17 @@ defmodule FarmbotOS.Lua do
{:error, "lua runtime error evaluating expression"}
{:error, {:badmatch, {:error, [{line, :luerl_parse, parse_error}], _}}} ->
FarmbotCore.Logger.error(
1,
"""
Failed to parse expression:
`#{comment}.lua:#{line}`
#{IO.iodata_to_binary(parse_error)}
""",
channels: [:toast]
)
{:error, "failed to parse expression (line:#{line}): #{IO.iodata_to_binary(parse_error)}"}
error ->
@ -59,6 +73,14 @@ defmodule FarmbotOS.Lua do
|> set_table([:read_status], &Info.read_status/2)
|> set_table([:send_message], &Info.send_message/2)
|> set_table([:version], &Info.version/2)
|> set_table([:update_device], &Data.update_device/2)
|> set_table([:get_device], &Data.get_device/2)
|> set_table([:update_fbos_config], &Data.update_fbos_config/2)
|> set_table([:get_fbos_config], &Data.get_fbos_config/2)
|> set_table([:update_firmware_config], &Data.update_firmware_config/2)
|> set_table([:get_firmware_config], &Data.get_firmware_config/2)
|> set_table([:new_farmware_env], &Data.new_farmware_env/2)
|> set_table([:new_sensor_reading], &Data.new_sensor_reading/2)
end
@spec set_table(t(), Path.t(), any()) :: t()

View File

@ -0,0 +1,73 @@
defmodule FarmbotOS.Lua.Ext.Data do
@moduledoc """
Extensions for manipulating data from Lua
"""
import FarmbotOS.Lua.Util
alias FarmbotCore.{
Asset,
Asset.Device,
Asset.FbosConfig,
Asset.FirmwareConfig
}
def update_device([table], lua) do
params = Map.new(table)
_ = Asset.update_device!(params)
{[true], lua}
end
def get_device([field], lua) do
device = Asset.device() |> Device.render()
{[device[String.to_atom(field)]], lua}
end
def get_device(_, lua) do
device = Asset.device() |> Device.render()
{[map_to_table(device)], lua}
end
def update_fbos_config([table], lua) do
params = Map.new(table)
_ = Asset.update_fbos_config!(params)
{[true], lua}
end
def get_fbos_config([field], lua) do
fbos_config = Asset.fbos_config() |> FbosConfig.render()
{[fbos_config[String.to_atom(field)]], lua}
end
def get_fbos_config(_, lua) do
fbos_config = Asset.fbos_config() |> FbosConfig.render()
{[map_to_table(fbos_config)], lua}
end
def update_firmware_config([table], lua) do
params = Map.new(table)
_ = Asset.update_firmware_config!(params)
{[true], lua}
end
def get_firmware_config([field], lua) do
firmware_config = Asset.firmware_config() |> FirmwareConfig.render()
{[firmware_config[String.to_atom(field)]], lua}
end
def get_firmware_config(_, lua) do
firmware_config = Asset.firmware_config() |> FirmwareConfig.render()
{[map_to_table(firmware_config)], lua}
end
def new_farmware_env([table], lua) do
params = Map.new(table)
_ = Asset.new_farmware_env(params)
{[true], lua}
end
def new_sensor_reading([table], lua) do
params = Map.new(table)
_ = Asset.new_sensor_reading!(params)
{[true], lua}
end
end

View File

@ -1,4 +1,7 @@
defmodule FarmbotOS.Lua.Ext.Info do
@moduledoc """
Lua extensions for gathering information about Farmbot
"""
alias FarmbotCeleryScript.SysCalls
@doc """
@ -22,76 +25,24 @@ defmodule FarmbotOS.Lua.Ext.Info do
do_send_message(kind, message, channels, lua)
end
def read_status(_, lua) do
bot_state = FarmbotCore.BotState.fetch()
@doc "Returns data about the bot's state"
def read_status([], lua) do
bot_state = FarmbotCore.BotState.fetch() |> FarmbotCore.BotStateNG.view()
configuration_table = [
{"arduino_debug_messages", bot_state.configuration.arduino_debug_messages},
{"auto_sync", bot_state.configuration.auto_sync},
{"beta_opt_in", bot_state.configuration.beta_opt_in},
{"disable_factory_reset", bot_state.configuration.disable_factory_reset},
{"firmware_hardware", bot_state.configuration.firmware_hardware},
{"firmware_input_log", bot_state.configuration.firmware_input_log},
{"firmware_output_log", bot_state.configuration.firmware_output_log},
{"network_not_found_timer", bot_state.configuration.network_not_found_timer},
{"os_auto_update", bot_state.configuration.os_auto_update},
{"sequence_body_log", bot_state.configuration.sequence_body_log},
{"sequence_complete_log", bot_state.configuration.sequence_complete_log},
{"sequence_init_log", bot_state.configuration.sequence_init_log}
]
{[map_to_table(bot_state)], lua}
end
informational_settings_table = [
{"busy", bot_state.informational_settings.busy},
{"cache_bust", bot_state.informational_settings.cache_bust},
{"commit", bot_state.informational_settings.commit},
{"controller_version", bot_state.informational_settings.controller_version},
{"disk_usage", bot_state.informational_settings.disk_usage},
{"env", bot_state.informational_settings.env},
{"firmware_commit", bot_state.informational_settings.firmware_commit},
{"firmware_version", bot_state.informational_settings.firmware_version},
{"idle", bot_state.informational_settings.idle},
{"last_status", bot_state.informational_settings.last_status},
{"locked", bot_state.informational_settings.locked},
{"memory_usage", bot_state.informational_settings.memory_usage},
{"node_name", bot_state.informational_settings.node_name},
{"soc_temp", bot_state.informational_settings.soc_temp},
{"sync_status", bot_state.informational_settings.sync_status},
{"target", bot_state.informational_settings.target},
{"throttled", bot_state.informational_settings.throttled},
{"update_available", bot_state.informational_settings.update_available},
{"uptime", bot_state.informational_settings.uptime},
{"wifi_level", bot_state.informational_settings.wifi_level},
{"wifi_level_percent", bot_state.informational_settings.wifi_level_percent}
]
def read_status(path, lua) do
bot_state = FarmbotCore.BotState.fetch() |> FarmbotCore.BotStateNG.view()
path = List.flatten(path) |> Enum.map(&String.to_atom(&1))
location_data_table = [
{"position",
[
{"x", bot_state.location_data.position.x},
{"y", bot_state.location_data.position.y},
{"z", bot_state.location_data.position.z}
]},
{"raw_encoders",
[
{"x", bot_state.location_data.raw_encoders.x},
{"y", bot_state.location_data.raw_encoders.y},
{"z", bot_state.location_data.raw_encoders.z}
]},
{"scaled_encoders",
[
{"x", bot_state.location_data.scaled_encoders.x},
{"y", bot_state.location_data.scaled_encoders.y},
{"z", bot_state.location_data.scaled_encoders.z}
]}
]
case get_in(bot_state, path) do
%{} = map ->
{[map_to_table(map)], lua}
final_table = [
{"configuration", configuration_table},
{"informational_settings", informational_settings_table},
{"location_data", location_data_table}
]
{[final_table, nil], lua}
other ->
{[other], lua}
end
end
@doc "Returns the current version of farmbot."
@ -108,4 +59,14 @@ defmodule FarmbotOS.Lua.Ext.Info do
{[nil, reason], lua}
end
end
defp map_to_table(map) do
Enum.map(map, fn
{key, %{} = value} ->
{to_string(key), map_to_table(value)}
{key, value} ->
{to_string(key), value}
end)
end
end

View File

@ -0,0 +1,12 @@
defmodule FarmbotOS.Lua.Util do
@doc "Convert an Elixir map to a Lua table"
def map_to_table(map) do
Enum.map(map, fn
{key, %{} = value} ->
{to_string(key), map_to_table(value)}
{key, value} ->
{to_string(key), value}
end)
end
end

View File

@ -61,8 +61,8 @@ defmodule FarmbotOS.SysCalls do
defdelegate set_pin_io_mode(pin, mode), to: SetPinIOMode
@impl true
defdelegate eval_assertion(expression), to: Lua
defdelegate log_assertion(passed?, message), to: Lua
defdelegate eval_assertion(comment, expression), to: Lua
defdelegate log_assertion(passed?, type, message), to: Lua
@impl true
def log(message) do

View File

@ -242,8 +242,8 @@ defmodule Farmbot.TestSupport.CeleryScript.TestSysCalls do
end
@impl true
def eval_assertion(expression) do
call({:eval_assertion, [expression]})
def eval_assertion(comment, expression) do
call({:eval_assertion, [comment, expression]})
end
defp call(data) do