Implement `every_point` and `point_group` Celery nodes

pull/1035/head
Connor Rigby 2019-09-23 17:24:29 -07:00 committed by Connor Rigby
parent d3e51996b0
commit 1ff2a7d153
9 changed files with 254 additions and 31 deletions

View File

@ -0,0 +1,44 @@
{
"args": {
"locals": {
"args": {},
"body": [],
"comment": null,
"kind": "scope_declaration",
"meta": null
}
},
"body": [
{
"args": {
"sequence_id": 19012
},
"body": [
{
"args": {
"data_value": {
"args": {
"every_point_type": "Plant"
},
"body": null,
"comment": null,
"kind": "every_point",
"meta": null
},
"label": "parent"
},
"body": null,
"comment": null,
"kind": "parameter_application",
"meta": null
}
],
"comment": null,
"kind": "execute",
"meta": null
}
],
"comment": null,
"kind": "sequence",
"meta": null
}

View File

@ -0,0 +1,44 @@
{
"args": {
"locals": {
"args": {},
"body": [],
"comment": null,
"kind": "scope_declaration",
"meta": null
}
},
"body": [
{
"args": {
"sequence_id": 19012
},
"body": [
{
"args": {
"data_value": {
"args": {
"resource_id": 31
},
"body": null,
"comment": null,
"kind": "point_group",
"meta": null
},
"label": "parent"
},
"body": null,
"comment": null,
"kind": "parameter_application",
"meta": null
}
],
"comment": null,
"kind": "execute",
"meta": null
}
],
"comment": null,
"kind": "sequence",
"meta": null
}

View File

@ -387,18 +387,92 @@ defmodule FarmbotCeleryScript.Compiler do
end
end
# Compiles an `execute` block.
compile :execute, %{sequence_id: id}, parameter_applications do
quote location: :keep do
# We have to lookup the sequence by it's id.
case FarmbotCeleryScript.SysCalls.get_sequence(unquote(id)) do
%FarmbotCeleryScript.AST{} = ast ->
# compile the ast
env = unquote(compile_params_to_function_args(parameter_applications))
FarmbotCeleryScript.Compiler.compile(ast, env)
@iterables [:point_group, :every_point]
error ->
error
# Compiles an `execute` block.
# This one is actually pretty complex and is split into two parts.
# TODO(Connor) refactor this into it's own module, or at least two
# different functions.
compile :execute, %{sequence_id: id}, parameter_applications do
# if there is an iterable AST here,
# we need to compile _many_ sequences, not just one.
loop_parameter_appl_ast =
Enum.find_value(parameter_applications, fn
# check if this parameter_application is a iterable type
%{kind: :parameter_application, args: %{data_value: %{kind: kind}}} = iterable
when kind in @iterables ->
iterable
_other ->
false
end)
# if there was an iterable, inject it's value for every instance of that item
if loop_parameter_appl_ast do
# remove the iterable from the parameter applications,
# since it will be injected after this.
parameter_applications =
Enum.reduce(parameter_applications, [], fn
# Remove point_group from parameter appls
%{kind: :parameter_application, args: %{data_value: %{kind: :point_group}}}, acc -> acc
# Remove every_point from parameter appls
%{kind: :parameter_application, args: %{data_value: %{kind: :every_point}}}, acc -> acc
# Everything else gets added back
ast, acc -> acc ++ [ast]
end)
# will be a point_group or every_point node
group_ast = loop_parameter_appl_ast.args.data_value
# check if it's a point_group first, then fall back to every_point
point_group_arg = group_ast.args[:resource_id] || group_ast.args[:every_point_type]
# lookup all point_groups related to this value
case FarmbotCeleryScript.SysCalls.get_point_group(point_group_arg) do
{:error, reason} ->
quote location: :keep, do: Macro.escape({:error, unquote(reason)})
%{} = point_group ->
# Map over all the points returned by `get_point_group/1`
Enum.map(point_group.point_ids, fn point_id ->
# check if it's an every_point node first, if not fall back go generic pointer
pointer_type = group_ast.args[:every_point_type] || "GenericPointer"
# compile a `execute` ast, injecting the appropriate `point` ast with
# the matching `label`
compile_ast(%FarmbotCeleryScript.AST{
kind: :execute,
args: %{sequence_id: id},
body: [
# this is the injection. This parameter_application was removed
%FarmbotCeleryScript.AST{
kind: :parameter_application,
args: %{
# inject the replacement with the same label
label: loop_parameter_appl_ast.args.label,
data_value: %FarmbotCeleryScript.AST{
kind: :point,
args: %{pointer_type: pointer_type, pointer_id: point_id}
}
}
}
# add all other parmeter_applications back in the case of variables, etc
| parameter_applications
]
})
end)
end
else
quote location: :keep do
# We have to lookup the sequence by it's id.
case FarmbotCeleryScript.SysCalls.get_sequence(unquote(id)) do
%FarmbotCeleryScript.AST{} = ast ->
# compile the ast
env = unquote(compile_params_to_function_args(parameter_applications))
FarmbotCeleryScript.Compiler.compile(ast, env)
error ->
error
end
end
end
end
@ -1051,6 +1125,6 @@ defmodule FarmbotCeleryScript.Compiler do
# compiled
# |> Macro.to_string()
# |> Code.format_string!()
# |> Logger.debug()
# |> IO.puts()
# end
end

View File

@ -77,6 +77,12 @@ defmodule FarmbotCeleryScript.SysCalls do
@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"
@ -335,6 +341,13 @@ defmodule FarmbotCeleryScript.SysCalls do
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

View File

@ -97,6 +97,9 @@ defmodule FarmbotCeleryScript.SysCalls.Stubs do
@impl true
def point(point_type, resource_id), do: error(:point, [point_type, resource_id])
@impl true
def get_point_group(id_or_type), do: error(:get_point_group, [id_or_type])
@impl true
def power_off(), do: error(:power_off, [])

View File

@ -19,6 +19,7 @@ defmodule FarmbotCore.Asset do
Peripheral,
PinBinding,
Point,
PointGroup,
PublicKey,
Regimen,
RegimenInstance,
@ -228,8 +229,21 @@ defmodule FarmbotCore.Asset do
|> Repo.update()
end
@doc "Returns all points matching Point.pointer_type"
def get_all_points_by_type(type) do
Repo.all(from p in Point, where: p.pointer_type == ^type)
end
## End Point
## Begin PointGroup
def get_point_group(params) do
Repo.get_by(PointGroup, params)
end
## End PointGroup
## Begin PublicKey
def get_public_key(id) do

View File

@ -21,7 +21,8 @@ defmodule FarmbotOS.SysCalls do
SetPinIOMode,
PinControl,
ResourceUpdate,
Movement
Movement,
PointLookup
}
alias FarmbotOS.Lua
@ -121,6 +122,15 @@ defmodule FarmbotOS.SysCalls do
@impl true
defdelegate home(axis, speed), to: Movement
@impl true
defdelegate point(kind, id), to: PointLookup
@impl true
defdelegate get_point_group(type_or_id), to: PointLookup
@impl true
defdelegate get_toolslot_for_tool(id), to: PointLookup
@impl true
def log(message, force?) do
if force? || FarmbotCore.Asset.fbos_config(:sequence_body_log) do
@ -182,14 +192,6 @@ defmodule FarmbotOS.SysCalls do
end
end
@impl true
def point(kind, id) do
case Asset.get_point(id: id) do
nil -> {:error, "#{kind} not found"}
%{x: x, y: y, z: z} -> %{x: x, y: y, z: z}
end
end
@impl true
def emergency_lock do
_ = FarmbotFirmware.command({:command_emergency_lock, []})
@ -244,16 +246,6 @@ defmodule FarmbotOS.SysCalls do
end
end
@impl true
def get_toolslot_for_tool(id) do
with %{id: ^id} <- Asset.get_tool(id: id),
%{x: x, y: y, z: z} <- Asset.get_point(tool_id: id) do
%{x: x, y: y, z: z}
else
nil -> {:error, "Could not find point for tool by id: #{id}"}
end
end
@impl true
def sync() do
FarmbotCore.Logger.busy(3, "Syncing")

View File

@ -0,0 +1,34 @@
defmodule FarmbotOS.SysCalls.PointLookup do
alias FarmbotCore.Asset
def point(kind, id) do
case Asset.get_point(id: id) do
nil -> {:error, "#{kind} not found"}
%{x: x, y: y, z: z} -> %{x: x, y: y, z: z}
end
end
def get_toolslot_for_tool(id) do
with %{id: ^id} <- Asset.get_tool(id: id),
%{x: x, y: y, z: z} <- Asset.get_point(tool_id: id) do
%{x: x, y: y, z: z}
else
nil -> {:error, "Could not find point for tool by id: #{id}"}
end
end
def get_point_group(id) when is_number(id) do
case Asset.get_point_group(id: id) do
nil -> {:error, "Could not find PointGroup.#{id}"}
%{point_ids: _} = group -> group
end
end
def get_point_group(type) when is_binary(type) do
points = Asset.get_all_points_by_type(type)
Enum.reduce(points, %{point_ids: []}, fn
%{id: id}, acc -> %{acc | point_ids: [id | acc.point_ids]}
end)
end
end

View File

@ -86,6 +86,11 @@ defmodule Farmbot.TestSupport.CeleryScript.TestSysCalls do
call({:point, [type, id]})
end
@impl true
def get_point_group(type_or_id) do
call({:get_point_group, [type_or_id]})
end
@impl true
def move_absolute(x, y, z, speed) do
call({:move_absolute, [x, y, z, speed]})