Add Lua helpers for updating and getting resources
parent
a242cce830
commit
209ded96cb
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 = """
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue