commit
c2b92722a8
|
@ -2,7 +2,7 @@ version: 2.0
|
|||
defaults: &defaults
|
||||
working_directory: /nerves/build
|
||||
docker:
|
||||
- image: nervesproject/nerves_system_br:latest
|
||||
- image: nervesproject/nerves_system_br:1.11.3
|
||||
|
||||
install_elixir: &install_elixir
|
||||
run:
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
# Changelog
|
||||
|
||||
# 10.0.0
|
||||
|
||||
* Deprecate `resource_update` RPC
|
||||
* Introduce `update_resource` RPC, which allows users to modify variables from the sequence editor.
|
||||
* Genesis v1.5 and Express v1.0 firmware updates.
|
||||
* Fix a bug where FBOS would not honor an "AUTO UPDATE" value of "false".
|
||||
|
||||
# 9.2.2
|
||||
|
||||
* Fix firmware locking error ("Can't perform X in Y state")
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"assertion_block": "8.0.0",
|
||||
"backscheduled_regimens": "6.4.0",
|
||||
"change_ownership": "6.3.0",
|
||||
"criteria_groups": "9.2.2",
|
||||
"diagnostic_dumps": "6.4.4",
|
||||
"endstop_invert": "6.4.1",
|
||||
"express_k10": "8.0.0",
|
||||
|
@ -20,6 +21,7 @@
|
|||
"ota_update_hour": "8.2.3",
|
||||
"rpi_led_control": "6.4.4",
|
||||
"sensors": "6.3.0",
|
||||
"update_resource": "10.0.0",
|
||||
"use_update_channel": "6.4.12",
|
||||
"variables": "8.0.0"
|
||||
}
|
||||
|
|
|
@ -28,3 +28,14 @@ This release uses an improved Farmware API:
|
|||
# v9
|
||||
|
||||
FarmBot OS v8+ uses an improved Farmware API. See the [Farmware developer documentation](https://developer.farm.bot/docs/farmware) for more information.
|
||||
|
||||
# v10
|
||||
|
||||
FarmBot OS v10 features an improved *Mark As* step. If you have previously added *Mark As* steps to sequences, you will need to update them before they can be executed by FarmBot:
|
||||
* Open any sequences with a caution icon next to the name.
|
||||
* Click the `CONVERT` button in each old *Mark As* step.
|
||||
* Save the sequence.
|
||||
* If you have auto-sync disabled, press `SYNC NOW` once all sequences have been updated.
|
||||
* Verify that any events using the updated sequences are running as expected.
|
||||
|
||||
FarmBot OS auto-update was disabled prior to this release.
|
||||
|
|
|
@ -22,13 +22,14 @@
|
|||
{
|
||||
"name": "ALLOWED_MESSAGE_TYPES",
|
||||
"allowed_values": [
|
||||
"success",
|
||||
"assertion",
|
||||
"busy",
|
||||
"warn",
|
||||
"debug",
|
||||
"error",
|
||||
"info",
|
||||
"fun",
|
||||
"debug"
|
||||
"info",
|
||||
"success",
|
||||
"warn"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -55,6 +56,15 @@
|
|||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ALLOWED_ASSERTION_TYPES",
|
||||
"allowed_values": [
|
||||
"abort",
|
||||
"recover",
|
||||
"abort_recover",
|
||||
"continue"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "AllowedPinTypes",
|
||||
"allowed_values": [
|
||||
|
@ -89,6 +99,7 @@
|
|||
"name": "LegalSequenceKind",
|
||||
"allowed_values": [
|
||||
"_if",
|
||||
"assertion",
|
||||
"calibrate",
|
||||
"change_ownership",
|
||||
"check_updates",
|
||||
|
@ -109,7 +120,6 @@
|
|||
"read_status",
|
||||
"reboot",
|
||||
"remove_farmware",
|
||||
"resource_update",
|
||||
"send_message",
|
||||
"set_servo_angle",
|
||||
"set_user_env",
|
||||
|
@ -117,6 +127,7 @@
|
|||
"take_photo",
|
||||
"toggle_pin",
|
||||
"update_farmware",
|
||||
"update_resource",
|
||||
"wait",
|
||||
"write_pin",
|
||||
"zero"
|
||||
|
@ -206,7 +217,8 @@
|
|||
"planned",
|
||||
"planted",
|
||||
"harvested",
|
||||
"sprouted"
|
||||
"sprouted",
|
||||
"removed"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -214,24 +226,18 @@
|
|||
"allowed_values": [
|
||||
"GenericPointer",
|
||||
"ToolSlot",
|
||||
"Plant"
|
||||
"Plant",
|
||||
"Weed"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "resource_type",
|
||||
"allowed_values": [
|
||||
"Device",
|
||||
"FarmEvent",
|
||||
"Image",
|
||||
"Log",
|
||||
"Peripheral",
|
||||
"Plant",
|
||||
"Point",
|
||||
"Regimen",
|
||||
"Sequence",
|
||||
"Tool",
|
||||
"Plant",
|
||||
"ToolSlot",
|
||||
"User",
|
||||
"Weed",
|
||||
"GenericPointer"
|
||||
]
|
||||
},
|
||||
|
@ -261,11 +267,13 @@
|
|||
"z",
|
||||
"pin_type",
|
||||
"pointer_id",
|
||||
"point_group_id",
|
||||
"pointer_type",
|
||||
"pin_mode",
|
||||
"sequence_id",
|
||||
"lhs",
|
||||
"op",
|
||||
"priority",
|
||||
"channel_name",
|
||||
"message_type",
|
||||
"tool_id",
|
||||
|
@ -273,19 +281,22 @@
|
|||
"axis",
|
||||
"message",
|
||||
"speed",
|
||||
"resource_type"
|
||||
"resource_type",
|
||||
"assertion_type",
|
||||
"lua",
|
||||
"resource"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "LegalKindString",
|
||||
"allowed_values": [
|
||||
"Assertion",
|
||||
"If",
|
||||
"Calibrate",
|
||||
"ChangeOwnership",
|
||||
"Channel",
|
||||
"CheckUpdates",
|
||||
"Coordinate",
|
||||
"DumpInfo",
|
||||
"EmergencyLock",
|
||||
"EmergencyUnlock",
|
||||
"ExecuteScript",
|
||||
|
@ -331,7 +342,10 @@
|
|||
"MoveAbsolute",
|
||||
"WritePin",
|
||||
"ReadPin",
|
||||
"ResourceUpdate"
|
||||
"ResourceUpdate",
|
||||
"Resource",
|
||||
"UpdateResource",
|
||||
"PointGroup"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
@ -394,6 +408,10 @@
|
|||
{
|
||||
"tag": "identifier",
|
||||
"name": "identifier"
|
||||
},
|
||||
{
|
||||
"tag": "point_group",
|
||||
"name": "point_group"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -625,6 +643,15 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "point_group_id",
|
||||
"allowed_values": [
|
||||
{
|
||||
"tag": "integer",
|
||||
"name": "Integer"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pointer_type",
|
||||
"allowed_values": [
|
||||
|
@ -674,6 +701,15 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "priority",
|
||||
"allowed_values": [
|
||||
{
|
||||
"tag": "integer",
|
||||
"name": "Integer"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "channel_name",
|
||||
"allowed_values": [
|
||||
|
@ -745,9 +781,53 @@
|
|||
"name": "resource_type"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "assertion_type",
|
||||
"allowed_values": [
|
||||
{
|
||||
"tag": "ALLOWED_ASSERTION_TYPES",
|
||||
"name": "ALLOWED_ASSERTION_TYPES"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "lua",
|
||||
"allowed_values": [
|
||||
{
|
||||
"tag": "string",
|
||||
"name": "String"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "resource",
|
||||
"allowed_values": [
|
||||
{
|
||||
"tag": "identifier",
|
||||
"name": "identifier"
|
||||
},
|
||||
{
|
||||
"tag": "resource",
|
||||
"name": "resource"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"allowed_args": [
|
||||
"assertion_type",
|
||||
"_then",
|
||||
"lua"
|
||||
],
|
||||
"allowed_body_types": [],
|
||||
"name": "assertion",
|
||||
"tags": [
|
||||
"*"
|
||||
],
|
||||
"docs": ""
|
||||
},
|
||||
{
|
||||
"allowed_args": [
|
||||
"lhs",
|
||||
|
@ -1151,9 +1231,11 @@
|
|||
},
|
||||
{
|
||||
"allowed_args": [
|
||||
"label"
|
||||
"label",
|
||||
"priority"
|
||||
],
|
||||
"allowed_body_types": [
|
||||
"assertion",
|
||||
"calibrate",
|
||||
"change_ownership",
|
||||
"check_updates",
|
||||
|
@ -1175,7 +1257,7 @@
|
|||
"read_status",
|
||||
"reboot",
|
||||
"remove_farmware",
|
||||
"resource_update",
|
||||
"update_resource",
|
||||
"send_message",
|
||||
"set_servo_angle",
|
||||
"set_user_env",
|
||||
|
@ -1225,6 +1307,7 @@
|
|||
"locals"
|
||||
],
|
||||
"allowed_body_types": [
|
||||
"assertion",
|
||||
"calibrate",
|
||||
"change_ownership",
|
||||
"check_updates",
|
||||
|
@ -1246,7 +1329,7 @@
|
|||
"read_status",
|
||||
"reboot",
|
||||
"remove_farmware",
|
||||
"resource_update",
|
||||
"update_resource",
|
||||
"send_message",
|
||||
"set_servo_angle",
|
||||
"set_user_env",
|
||||
|
@ -1459,6 +1542,45 @@
|
|||
"network_user"
|
||||
],
|
||||
"docs": ""
|
||||
},
|
||||
{
|
||||
"allowed_args": [
|
||||
"resource_type",
|
||||
"resource_id"
|
||||
],
|
||||
"allowed_body_types": [],
|
||||
"name": "resource",
|
||||
"tags": [
|
||||
"network_user"
|
||||
],
|
||||
"docs": ""
|
||||
},
|
||||
{
|
||||
"allowed_args": [
|
||||
"resource"
|
||||
],
|
||||
"allowed_body_types": [
|
||||
"pair"
|
||||
],
|
||||
"name": "update_resource",
|
||||
"tags": [
|
||||
"function",
|
||||
"api_writer",
|
||||
"network_user"
|
||||
],
|
||||
"docs": ""
|
||||
},
|
||||
{
|
||||
"allowed_args": [
|
||||
"point_group_id"
|
||||
],
|
||||
"allowed_body_types": [],
|
||||
"name": "point_group",
|
||||
"tags": [
|
||||
"data",
|
||||
"list_like"
|
||||
],
|
||||
"docs": ""
|
||||
}
|
||||
]
|
||||
}
|
|
@ -11,17 +11,10 @@ defmodule FarmbotCeleryScript.Compiler do
|
|||
Compiler.IdentifierSanitizer
|
||||
}
|
||||
|
||||
@doc "Sets debug mode for the compiler"
|
||||
def debug_mode(bool \\ true) do
|
||||
old = Application.get_env(:farmbot_celery_script, __MODULE__, [])
|
||||
new = Keyword.put(old, :debug, bool)
|
||||
Application.put_env(:farmbot_celery_script, __MODULE__, new)
|
||||
bool
|
||||
end
|
||||
|
||||
@doc "Returns current debug mode value"
|
||||
def debug_mode?() do
|
||||
Application.get_env(:farmbot_celery_script, __MODULE__)[:debug] || false
|
||||
# Set this to `true` when debuging.
|
||||
false
|
||||
end
|
||||
|
||||
@valid_entry_points [:sequence, :rpc_request]
|
||||
|
@ -94,27 +87,28 @@ defmodule FarmbotCeleryScript.Compiler do
|
|||
defdelegate assertion(ast, env), to: Compiler.Assertion
|
||||
defdelegate calibrate(ast, env), to: Compiler.AxisControl
|
||||
defdelegate coordinate(ast, env), to: Compiler.DataControl
|
||||
defdelegate execute(ast, env), to: Compiler.Execute
|
||||
defdelegate execute_script(ast, env), to: Compiler.Farmware
|
||||
defdelegate execute(ast, env), to: Compiler.Execute
|
||||
defdelegate find_home(ast, env), to: Compiler.AxisControl
|
||||
defdelegate home(ast, env), to: Compiler.AxisControl
|
||||
defdelegate unquote(:_if)(ast, env), to: Compiler.If
|
||||
defdelegate install_first_party_farmware(ast, env), to: Compiler.Farmware
|
||||
defdelegate move_absolute(ast, env), to: Compiler.AxisControl
|
||||
defdelegate move_relative(ast, env), to: Compiler.AxisControl
|
||||
defdelegate named_pin(ast, env), to: Compiler.DataControl
|
||||
defdelegate point(ast, env), to: Compiler.DataControl
|
||||
defdelegate read_pin(ast, env), to: Compiler.PinControl
|
||||
defdelegate resource_update(ast, env), to: Compiler.DataControl
|
||||
defdelegate resource(ast, env), to: Compiler.DataControl
|
||||
defdelegate rpc_request(ast, env), to: Compiler.RPCRequest
|
||||
defdelegate sequence(ast, env), to: Compiler.Sequence
|
||||
defdelegate set_pin_io_mode(ast, env), to: Compiler.PinControl
|
||||
defdelegate set_servo_angle(ast, env), to: Compiler.PinControl
|
||||
defdelegate set_user_env(ast, env), to: Compiler.Farmware
|
||||
defdelegate take_photo(ast, env), to: Compiler.Farmware
|
||||
defdelegate tool(ast, env), to: Compiler.DataControl
|
||||
defdelegate toggle_pin(ast, env), to: Compiler.PinControl
|
||||
defdelegate tool(ast, env), to: Compiler.DataControl
|
||||
defdelegate unquote(:_if)(ast, env), to: Compiler.If
|
||||
defdelegate update_farmware(ast, env), to: Compiler.Farmware
|
||||
defdelegate update_resource(ast, env), to: Compiler.UpdateResource
|
||||
defdelegate variable_declaration(ast, env), to: Compiler.VariableDeclaration
|
||||
defdelegate write_pin(ast, env), to: Compiler.PinControl
|
||||
defdelegate zero(ast, env), to: Compiler.AxisControl
|
||||
|
@ -280,13 +274,13 @@ defmodule FarmbotCeleryScript.Compiler do
|
|||
end
|
||||
|
||||
defp print_compiled_code(compiled) do
|
||||
IO.puts("========")
|
||||
IO.puts("=== START ===")
|
||||
|
||||
compiled
|
||||
|> Macro.to_string()
|
||||
|> Code.format_string!()
|
||||
|> IO.puts()
|
||||
|
||||
IO.puts("========\n\n")
|
||||
IO.puts("=== END ===\n\n")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,19 @@
|
|||
defmodule FarmbotCeleryScript.Compiler.DataControl do
|
||||
alias FarmbotCeleryScript.Compiler
|
||||
|
||||
def resource(ast, _env) do
|
||||
IO.puts("======")
|
||||
IO.inspect(ast)
|
||||
# %FarmbotCeleryScript.AST{
|
||||
# args: %{resource_id: 0, resource_type: "Device"},
|
||||
# body: [],
|
||||
# comment: nil,
|
||||
# kind: :resource,
|
||||
# meta: nil
|
||||
# }
|
||||
raise "TODO: Pull resource from DB?"
|
||||
end
|
||||
|
||||
# compiles coordinate
|
||||
# Coordinate should return a vec3
|
||||
def coordinate(%{args: %{x: x, y: y, z: z}}, env) do
|
||||
|
@ -40,35 +53,4 @@ defmodule FarmbotCeleryScript.Compiler.DataControl do
|
|||
)
|
||||
end
|
||||
end
|
||||
|
||||
def resource_update(
|
||||
%{
|
||||
args: %{
|
||||
resource_type: kind,
|
||||
resource_id: id,
|
||||
label: label,
|
||||
value: value
|
||||
},
|
||||
body: body
|
||||
},
|
||||
env
|
||||
) do
|
||||
initial = %{label => value}
|
||||
# Technically now body isn't supported by this node.
|
||||
extra =
|
||||
Map.new(body, fn %{args: %{label: label, data_value: value}} ->
|
||||
{label, value}
|
||||
end)
|
||||
|
||||
# Make sure the initial stuff higher most priority
|
||||
params = Map.merge(extra, initial)
|
||||
|
||||
quote do
|
||||
FarmbotCeleryScript.SysCalls.resource_update(
|
||||
unquote(Compiler.compile_ast(kind, env)),
|
||||
unquote(Compiler.compile_ast(id, env)),
|
||||
unquote(Macro.escape(params))
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
defmodule FarmbotCeleryScript.Compiler.Sequence do
|
||||
import FarmbotCeleryScript.Compiler.Utils
|
||||
alias FarmbotCeleryScript.Compiler.IdentifierSanitizer
|
||||
|
||||
@iterables [:point_group, :every_point]
|
||||
|
||||
|
@ -29,13 +30,7 @@ defmodule FarmbotCeleryScript.Compiler.Sequence do
|
|||
def compile_sequence_iterable(
|
||||
iterable_ast,
|
||||
%{
|
||||
args:
|
||||
%{
|
||||
locals:
|
||||
%{
|
||||
body: params
|
||||
} = locals
|
||||
} = sequence_args,
|
||||
args: %{locals: %{body: _} = locals} = sequence_args,
|
||||
meta: sequence_meta
|
||||
} = sequence_ast,
|
||||
env
|
||||
|
@ -43,31 +38,6 @@ defmodule FarmbotCeleryScript.Compiler.Sequence do
|
|||
sequence_name =
|
||||
sequence_meta[:sequence_name] || sequence_args[:sequence_name]
|
||||
|
||||
# remove the iterable from the parameter applications,
|
||||
# since it will be injected after this.
|
||||
_params =
|
||||
Enum.reduce(params, [], 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 = iterable_ast.args.data_value
|
||||
# check if it's a point_group first, then fall back to every_point
|
||||
|
@ -142,6 +112,37 @@ defmodule FarmbotCeleryScript.Compiler.Sequence do
|
|||
end
|
||||
end
|
||||
|
||||
def create_better_params(body, env) do
|
||||
parameter_declarations =
|
||||
Enum.reduce(env, %{}, fn
|
||||
{key, value}, map ->
|
||||
encoded_label = "#{key}"
|
||||
|
||||
if String.starts_with?(encoded_label, "unsafe_") do
|
||||
Map.put(map, IdentifierSanitizer.to_string(encoded_label), value)
|
||||
else
|
||||
map
|
||||
end
|
||||
end)
|
||||
|
||||
Enum.reduce(body, parameter_declarations, fn ast, map ->
|
||||
case ast do
|
||||
%{kind: :parameter_application} ->
|
||||
args = Map.fetch!(ast, :args)
|
||||
label = Map.fetch!(args, :label)
|
||||
Map.put(map, label, Map.fetch!(args, :data_value))
|
||||
|
||||
%{kind: :variable_declaration} ->
|
||||
args = Map.fetch!(ast, :args)
|
||||
label = Map.fetch!(args, :label)
|
||||
Map.put(map, label, Map.fetch!(args, :data_value))
|
||||
|
||||
%{kind: :parameter_declaration} ->
|
||||
map
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def compile_sequence(
|
||||
%{args: %{locals: %{body: params}} = args, body: block, meta: meta},
|
||||
env
|
||||
|
@ -150,6 +151,9 @@ defmodule FarmbotCeleryScript.Compiler.Sequence do
|
|||
# The `params` side gets turned into
|
||||
# a keyword list. These `params` are passed in from a previous sequence.
|
||||
# The `body` side declares variables in _this_ scope.
|
||||
# === DON'T USE THIS IN NEW CODE.
|
||||
# SCHEDULED FOR DEPRECATION.
|
||||
# USE `better_params` INSTEAD.
|
||||
{params_fetch, body} =
|
||||
Enum.reduce(params, {[], []}, fn ast, {params, body} = _acc ->
|
||||
case ast do
|
||||
|
@ -173,6 +177,8 @@ defmodule FarmbotCeleryScript.Compiler.Sequence do
|
|||
|
||||
steps = add_sequence_init_and_complete_logs(steps, sequence_name)
|
||||
|
||||
better_params = create_better_params(params, env)
|
||||
|
||||
[
|
||||
quote location: :keep do
|
||||
fn params ->
|
||||
|
@ -183,7 +189,7 @@ defmodule FarmbotCeleryScript.Compiler.Sequence do
|
|||
# parent = Keyword.fetch!(params, :parent)
|
||||
unquote_splicing(params_fetch)
|
||||
unquote_splicing(assignments)
|
||||
|
||||
better_params = unquote(better_params)
|
||||
# Unquote the remaining sequence steps.
|
||||
unquote(steps)
|
||||
end
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
defmodule FarmbotCeleryScript.Compiler.UpdateResource do
|
||||
alias FarmbotCeleryScript.{AST, DotProps}
|
||||
|
||||
def update_resource(%AST{args: args, body: body}, _env) do
|
||||
quote location: :keep do
|
||||
me = unquote(__MODULE__)
|
||||
variable = unquote(Map.fetch!(args, :resource))
|
||||
update = unquote(unpair(body, %{}))
|
||||
|
||||
# Go easy on the API...
|
||||
case variable do
|
||||
%AST{kind: :identifier} ->
|
||||
args = Map.fetch!(variable, :args)
|
||||
label = Map.fetch!(args, :label)
|
||||
resource = Map.fetch!(better_params, label)
|
||||
me.do_update(resource, update)
|
||||
|
||||
%AST{kind: :point} ->
|
||||
me.do_update(variable.args, update)
|
||||
|
||||
%AST{kind: :resource} ->
|
||||
me.do_update(variable.args, update)
|
||||
|
||||
res ->
|
||||
raise "Resource error. Please notfiy support: #{inspect(res)}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def do_update(%{pointer_id: id, pointer_type: kind}, update_params) do
|
||||
FarmbotCeleryScript.SysCalls.update_resource(kind, id, update_params)
|
||||
end
|
||||
|
||||
def do_update(%{resource_id: id, resource_type: kind}, update_params) do
|
||||
FarmbotCeleryScript.SysCalls.update_resource(kind, id, update_params)
|
||||
end
|
||||
|
||||
def do_update(%{args: %{pointer_id: id, pointer_type: k}}, update_params) do
|
||||
FarmbotCeleryScript.SysCalls.update_resource(k, id, update_params)
|
||||
end
|
||||
|
||||
def do_update(other, update) do
|
||||
raise String.trim("""
|
||||
MARK AS can only be used to mark resources like plants and devices.
|
||||
It cannot be used on things like coordinates.
|
||||
Ensure that your sequences and farm events us MARK AS on plants and not
|
||||
coordinates (#{inspect(other)} / #{inspect(update)})
|
||||
""")
|
||||
end
|
||||
|
||||
defp unpair([pair | rest], acc) do
|
||||
key = Map.fetch!(pair.args, :label)
|
||||
val = Map.fetch!(pair.args, :value)
|
||||
next_acc = Map.merge(acc, DotProps.create(key, val))
|
||||
unpair(rest, next_acc)
|
||||
end
|
||||
|
||||
defp unpair([], acc) do
|
||||
acc
|
||||
end
|
||||
end
|
|
@ -0,0 +1,22 @@
|
|||
defmodule FarmbotCeleryScript.DotProps do
|
||||
@dot "."
|
||||
@doc ~S"""
|
||||
Takes a "dotted" key and val.
|
||||
Returns deeply nested hash.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create("foo.bar.baz", 321)
|
||||
%{"foo" => %{"bar" => %{"baz" => 321}}}
|
||||
|
||||
iex> create("foo", "bar")
|
||||
%{"foo" => "bar"}
|
||||
"""
|
||||
def create(dotted, val) do
|
||||
[key | list] = dotted |> String.split(@dot) |> Enum.reverse()
|
||||
|
||||
Enum.reduce(list, %{key => val}, fn next_key, acc ->
|
||||
%{next_key => acc}
|
||||
end)
|
||||
end
|
||||
end
|
|
@ -57,7 +57,6 @@ defmodule FarmbotCeleryScript.SysCalls do
|
|||
speed :: number()
|
||||
) ::
|
||||
ok_or_error
|
||||
# ?
|
||||
@callback named_pin(named_pin_type :: String.t(), resource_id) ::
|
||||
map() | integer | error()
|
||||
@callback nothing() :: any()
|
||||
|
@ -69,7 +68,6 @@ defmodule FarmbotCeleryScript.SysCalls do
|
|||
@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
|
||||
|
@ -95,6 +93,8 @@ defmodule FarmbotCeleryScript.SysCalls do
|
|||
@callback find_points_via_group(String.t() | resource_id) :: %{
|
||||
required(:point_ids) => [resource_id]
|
||||
}
|
||||
@callback update_resource(kind :: String.t(), resource_id, params :: map()) ::
|
||||
ok_or_error
|
||||
|
||||
def find_points_via_group(sys_calls \\ @sys_calls, point_group_id) do
|
||||
point_group_or_error(sys_calls, :find_points_via_group, [point_group_id])
|
||||
|
@ -297,10 +297,6 @@ defmodule FarmbotCeleryScript.SysCalls 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
|
||||
|
@ -333,6 +329,10 @@ defmodule FarmbotCeleryScript.SysCalls do
|
|||
ok_or_error(sys_calls, :zero, [axis])
|
||||
end
|
||||
|
||||
def update_resource(sys_calls \\ @sys_calls, kind, id, params) do
|
||||
ok_or_error(sys_calls, :update_resource, [kind, id, params])
|
||||
end
|
||||
|
||||
defp ok_or_error(sys_calls, fun, args) do
|
||||
case apply(sys_calls, fun, args) do
|
||||
:ok -> :ok
|
||||
|
|
|
@ -119,10 +119,6 @@ defmodule FarmbotCeleryScript.SysCalls.Stubs do
|
|||
@impl true
|
||||
def reboot(), do: error(:reboot, [])
|
||||
|
||||
@impl true
|
||||
def resource_update(kind, resource_id, data),
|
||||
do: error(:resource_update, [kind, resource_id, data])
|
||||
|
||||
@impl true
|
||||
def send_message(type, message, channels),
|
||||
do: error(:send_message, [type, message, channels])
|
||||
|
@ -147,6 +143,10 @@ defmodule FarmbotCeleryScript.SysCalls.Stubs do
|
|||
def write_pin(pin_num, pin_mode, pin_value),
|
||||
do: error(:write_pin, [pin_num, pin_mode, pin_value])
|
||||
|
||||
@impl true
|
||||
def update_resource(kind, id, params),
|
||||
do: error(:update_resource, [kind, id, params])
|
||||
|
||||
@impl true
|
||||
def zero(axis), do: error(:zero, [axis])
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
defmodule FarmbotCeleryScript.DotPropsTest do
|
||||
use ExUnit.Case
|
||||
doctest FarmbotCeleryScript.DotProps, import: true
|
||||
end
|
|
@ -47,8 +47,8 @@ defmodule FarmbotCeleryScript.CompilerGroupsTest do
|
|||
canary_actual = :crypto.hash(:sha, Macro.to_string(result))
|
||||
|
||||
canary_expected =
|
||||
<<157, 69, 5, 38, 188, 78, 10, 183, 154, 99, 151, 193, 214, 208, 187, 130,
|
||||
183, 73, 13, 48>>
|
||||
<<136, 140, 48, 226, 216, 155, 178, 103, 244, 88, 225, 146, 130, 216, 125,
|
||||
72, 113, 195, 65, 1>>
|
||||
|
||||
# READ THE NOTE ABOVE IF THIS TEST FAILS!!!
|
||||
assert canary_expected == canary_actual
|
||||
|
|
|
@ -79,7 +79,7 @@ defmodule FarmbotCeleryScript.CompilerTest do
|
|||
end
|
||||
|
||||
test "identifier sanitization" do
|
||||
label = "System.cmd(\"rm\", [\"-rf /*\"])"
|
||||
label = "System.cmd(\"echo\", [\"lol\"])"
|
||||
value_ast = AST.Factory.new("coordinate", x: 1, y: 1, z: 1)
|
||||
identifier_ast = AST.Factory.new("identifier", label: label)
|
||||
|
||||
|
@ -120,11 +120,19 @@ defmodule FarmbotCeleryScript.CompilerTest do
|
|||
[
|
||||
fn params ->
|
||||
_ = inspect(params)
|
||||
unsafe_U3lzdGVtLmNtZCgiZWNobyIsIFsibG9sIl0p = FarmbotCeleryScript.SysCalls.coordinate(1, 1, 1)
|
||||
|
||||
#{var_name} =
|
||||
FarmbotCeleryScript.SysCalls.coordinate(1, 1, 1)
|
||||
better_params = %{
|
||||
"System.cmd(\\"echo\\", [\\"lol\\"])" => %FarmbotCeleryScript.AST{
|
||||
args: %{x: 1, y: 1, z: 1},
|
||||
body: [],
|
||||
comment: nil,
|
||||
kind: :coordinate,
|
||||
meta: nil
|
||||
}
|
||||
}
|
||||
|
||||
[fn -> #{var_name} end]
|
||||
[fn -> unsafe_U3lzdGVtLmNtZCgiZWNobyIsIFsibG9sIl0p end]
|
||||
end
|
||||
]
|
||||
""")
|
||||
|
@ -355,6 +363,114 @@ defmodule FarmbotCeleryScript.CompilerTest do
|
|||
""")
|
||||
end
|
||||
|
||||
test "`update_resource`: " do
|
||||
compiled =
|
||||
"test/fixtures/mark_variable_removed.json"
|
||||
|> File.read!()
|
||||
|> Jason.decode!()
|
||||
|> AST.decode()
|
||||
|> compile()
|
||||
|
||||
assert compiled ==
|
||||
strip_nl("""
|
||||
[
|
||||
fn params ->
|
||||
_ = inspect(params)
|
||||
|
||||
unsafe_cGFyZW50 =
|
||||
Keyword.get(params, :unsafe_cGFyZW50, FarmbotCeleryScript.SysCalls.coordinate(1, 2, 3))
|
||||
|
||||
better_params = %{}
|
||||
|
||||
[
|
||||
fn ->
|
||||
me = FarmbotCeleryScript.Compiler.UpdateResource
|
||||
|
||||
variable = %FarmbotCeleryScript.AST{
|
||||
args: %{label: "parent"},
|
||||
body: [],
|
||||
comment: nil,
|
||||
kind: :identifier,
|
||||
meta: nil
|
||||
}
|
||||
|
||||
update = %{"plant_stage" => "removed"}
|
||||
|
||||
case(variable) do
|
||||
%AST{kind: :identifier} ->
|
||||
args = Map.fetch!(variable, :args)
|
||||
label = Map.fetch!(args, :label)
|
||||
resource = Map.fetch!(better_params, label)
|
||||
me.do_update(resource, update)
|
||||
|
||||
%AST{kind: :point} ->
|
||||
me.do_update(variable.args(), update)
|
||||
|
||||
%AST{kind: :resource} ->
|
||||
me.do_update(variable.args(), update)
|
||||
|
||||
res ->
|
||||
raise("Resource error. Please notfiy support: \#{inspect(res)}")
|
||||
end
|
||||
end
|
||||
]
|
||||
end
|
||||
]
|
||||
""")
|
||||
end
|
||||
|
||||
test "`update_resource`: Multiple fields of `resource` type." do
|
||||
compiled =
|
||||
"test/fixtures/update_resource_multi.json"
|
||||
|> File.read!()
|
||||
|> Jason.decode!()
|
||||
|> AST.decode()
|
||||
|> compile()
|
||||
|
||||
assert compiled ==
|
||||
strip_nl("""
|
||||
[
|
||||
fn params ->
|
||||
_ = inspect(params)
|
||||
better_params = %{}
|
||||
|
||||
[
|
||||
fn ->
|
||||
me = FarmbotCeleryScript.Compiler.UpdateResource
|
||||
|
||||
variable = %FarmbotCeleryScript.AST{
|
||||
args: %{resource_id: 23, resource_type: "Plant"},
|
||||
body: [],
|
||||
comment: nil,
|
||||
kind: :resource,
|
||||
meta: nil
|
||||
}
|
||||
|
||||
update = %{"plant_stage" => "planted", "r" => 23}
|
||||
|
||||
case(variable) do
|
||||
%AST{kind: :identifier} ->
|
||||
args = Map.fetch!(variable, :args)
|
||||
label = Map.fetch!(args, :label)
|
||||
resource = Map.fetch!(better_params, label)
|
||||
me.do_update(resource, update)
|
||||
|
||||
%AST{kind: :point} ->
|
||||
me.do_update(variable.args(), update)
|
||||
|
||||
%AST{kind: :resource} ->
|
||||
me.do_update(variable.args(), update)
|
||||
|
||||
res ->
|
||||
raise("Resource error. Please notfiy support: \#{inspect(res)}")
|
||||
end
|
||||
end
|
||||
]
|
||||
end
|
||||
]
|
||||
""")
|
||||
end
|
||||
|
||||
defp compile(ast) do
|
||||
ast
|
||||
|> Compiler.compile_ast([])
|
||||
|
|
|
@ -4,7 +4,7 @@ defmodule FarmbotCeleryScript.Corpus.NodeTest do
|
|||
|
||||
test "inspect" do
|
||||
a =
|
||||
"Sequence(version, locals) [calibrate, change_ownership, check_updates, emergency_lock, emergency_unlock, execute, execute_script, factory_reset, find_home, flash_firmware, home, install_farmware, install_first_party_farmware, _if, move_absolute, move_relative, power_off, read_pin, read_status, reboot, remove_farmware, resource_update, send_message, set_servo_angle, set_user_env, sync, take_photo, toggle_pin, update_farmware, wait, write_pin, zero]"
|
||||
"Sequence(version, locals) [assertion, calibrate, change_ownership, check_updates, emergency_lock, emergency_unlock, execute, execute_script, factory_reset, find_home, flash_firmware, home, install_farmware, install_first_party_farmware, _if, move_absolute, move_relative, power_off, read_pin, read_status, reboot, remove_farmware, update_resource, send_message, set_servo_angle, set_user_env, sync, take_photo, toggle_pin, update_farmware, wait, write_pin, zero]"
|
||||
|
||||
b = inspect(Corpus.sequence())
|
||||
assert a == b
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"args": {
|
||||
"locals": {
|
||||
"args": {},
|
||||
"body": [
|
||||
{
|
||||
"args": {
|
||||
"default_value": {
|
||||
"args": {
|
||||
"x": 1,
|
||||
"y": 2,
|
||||
"z": 3
|
||||
},
|
||||
"kind": "coordinate"
|
||||
},
|
||||
"label": "parent"
|
||||
},
|
||||
"kind": "parameter_declaration"
|
||||
}
|
||||
],
|
||||
"kind": "scope_declaration"
|
||||
},
|
||||
"version": -999
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"args": {
|
||||
"resource": {
|
||||
"args": {
|
||||
"label": "parent"
|
||||
},
|
||||
"kind": "identifier"
|
||||
}
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"args": {
|
||||
"label": "meta.my_prop",
|
||||
"value": "whatever"
|
||||
},
|
||||
"kind": "pair"
|
||||
}
|
||||
],
|
||||
"kind": "update_resource"
|
||||
}
|
||||
],
|
||||
"kind": "sequence"
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"args": {
|
||||
"locals": {
|
||||
"args": {},
|
||||
"body": [
|
||||
{
|
||||
"args": {
|
||||
"default_value": {
|
||||
"args": {
|
||||
"x": 1,
|
||||
"y": 2,
|
||||
"z": 3
|
||||
},
|
||||
"kind": "coordinate"
|
||||
},
|
||||
"label": "parent"
|
||||
},
|
||||
"kind": "parameter_declaration"
|
||||
}
|
||||
],
|
||||
"kind": "scope_declaration"
|
||||
},
|
||||
"version": -999
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"args": {
|
||||
"resource": {
|
||||
"args": {
|
||||
"label": "parent"
|
||||
},
|
||||
"kind": "identifier"
|
||||
}
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"args": {
|
||||
"label": "plant_stage",
|
||||
"value": "removed"
|
||||
},
|
||||
"kind": "pair"
|
||||
}
|
||||
],
|
||||
"kind": "update_resource"
|
||||
}
|
||||
],
|
||||
"kind": "sequence"
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"args": {
|
||||
"locals": {
|
||||
"args": {},
|
||||
"body": [],
|
||||
"kind": "scope_declaration"
|
||||
},
|
||||
"version": -999
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"args": {
|
||||
"resource": {
|
||||
"args": {
|
||||
"resource_id": 0,
|
||||
"resource_type": "Device"
|
||||
},
|
||||
"kind": "resource"
|
||||
}
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"args": {
|
||||
"label": "mounted_tool_id",
|
||||
"value": 12161
|
||||
},
|
||||
"kind": "pair"
|
||||
}
|
||||
],
|
||||
"kind": "update_resource"
|
||||
}
|
||||
],
|
||||
"kind": "sequence"
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"args": {
|
||||
"locals": {
|
||||
"args": {},
|
||||
"body": [],
|
||||
"kind": "scope_declaration"
|
||||
},
|
||||
"version": -999
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"args": {
|
||||
"resource": {
|
||||
"args": {
|
||||
"resource_id": 23,
|
||||
"resource_type": "Plant"
|
||||
},
|
||||
"kind": "resource"
|
||||
}
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"args": {
|
||||
"label": "plant_stage",
|
||||
"value": "planted"
|
||||
},
|
||||
"kind": "pair"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"label": "r",
|
||||
"value": 23
|
||||
},
|
||||
"kind": "pair"
|
||||
}
|
||||
],
|
||||
"kind": "update_resource"
|
||||
}
|
||||
],
|
||||
"kind": "sequence"
|
||||
}
|
|
@ -46,14 +46,7 @@ config :farmbot_core, FarmbotCore.EctoMigrator,
|
|||
"beta"
|
||||
)
|
||||
|
||||
config :farmbot_core, FarmbotCore.FirmwareTTYDetector, expected_names: []
|
||||
|
||||
config :farmbot_core, FarmbotCore.FirmwareOpenTask, attempt_threshold: 5
|
||||
|
||||
config :farmbot_firmware, FarmbotFirmware, reset: FarmbotFirmware.NullReset
|
||||
|
||||
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FbosConfig,
|
||||
firmware_flash_attempt_threshold: 5
|
||||
config :farmbot_firmware, FarmbotFirmware, reset: FarmbotCore.FirmwareResetter
|
||||
|
||||
import_config "ecto.exs"
|
||||
import_config "logger.exs"
|
||||
|
|
|
@ -2,8 +2,3 @@ use Mix.Config
|
|||
|
||||
config :farmbot_celery_script, FarmbotCeleryScript.SysCalls,
|
||||
sys_calls: FarmbotCeleryScript.SysCalls.Stubs
|
||||
|
||||
config :farmbot_core, FarmbotCore.FirmwareOpenTask, attempt_threshold: 5
|
||||
|
||||
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FbosConfig,
|
||||
firmware_flash_attempt_threshold: 5
|
||||
|
|
|
@ -27,7 +27,10 @@ defmodule FarmbotCore do
|
|||
FarmbotCore.FirmwareOpenTask,
|
||||
FarmbotCore.FirmwareEstopTimer,
|
||||
# Also error handling for a transport not starting ?
|
||||
{FarmbotFirmware, transport: FarmbotFirmware.StubTransport, side_effects: FarmbotCore.FirmwareSideEffects},
|
||||
{FarmbotFirmware,
|
||||
transport: FarmbotFirmware.StubTransport,
|
||||
side_effects: FarmbotCore.FirmwareSideEffects,
|
||||
reset: FarmbotCore.FirmwareResetter},
|
||||
FarmbotCeleryScript.Scheduler
|
||||
]
|
||||
config = (Application.get_env(:farmbot_ext, __MODULE__) || [])
|
||||
|
|
|
@ -6,15 +6,15 @@ defmodule FarmbotCore.Asset do
|
|||
"""
|
||||
|
||||
alias FarmbotCore.Asset.{
|
||||
Repo,
|
||||
CriteriaRetriever,
|
||||
Device,
|
||||
DeviceCert,
|
||||
FarmwareEnv,
|
||||
FirstPartyFarmware,
|
||||
FarmwareInstallation,
|
||||
FarmEvent,
|
||||
FarmwareEnv,
|
||||
FarmwareInstallation,
|
||||
FbosConfig,
|
||||
FirmwareConfig,
|
||||
FirstPartyFarmware,
|
||||
Peripheral,
|
||||
PinBinding,
|
||||
Point,
|
||||
|
@ -22,11 +22,11 @@ defmodule FarmbotCore.Asset do
|
|||
PublicKey,
|
||||
Regimen,
|
||||
RegimenInstance,
|
||||
Sequence,
|
||||
Repo,
|
||||
Sensor,
|
||||
SensorReading,
|
||||
Sequence,
|
||||
Tool,
|
||||
CriteriaRetriever
|
||||
}
|
||||
|
||||
alias FarmbotCore.AssetSupervisor
|
||||
|
@ -251,8 +251,24 @@ defmodule FarmbotCore.Asset do
|
|||
end
|
||||
|
||||
def update_point(point, params) do
|
||||
point
|
||||
|> Point.changeset(params)
|
||||
# TODO: RC 8 MAY 2020 - We need to hard refresh the point.
|
||||
# The CSVM appears to be caching resources. This leads
|
||||
# to problems when a user runs a sequence that has two
|
||||
# MARK AS steps.
|
||||
# NOTE: Updating the `meta` attribute is a _replace_ action
|
||||
# by default, not a merge action.
|
||||
# MORE NOTES: Mixed keys (symbol vs. string) will crash this FN.
|
||||
# Let's just stringify everything...
|
||||
new_meta = params[:meta] || params["meta"] || %{}
|
||||
old_meta = point.meta || %{}
|
||||
updated_meta = Map.merge(old_meta, new_meta)
|
||||
clean_params = params
|
||||
|> Map.merge(%{meta: updated_meta})
|
||||
|> Enum.map(fn {k, v} -> {"#{k}", v} end)
|
||||
|> Map.new()
|
||||
|
||||
Repo.get_by(Point, id: point.id)
|
||||
|> Point.changeset(clean_params)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
|
|
|
@ -132,15 +132,14 @@ defmodule FarmbotCore.Asset.CriteriaRetriever do
|
|||
end
|
||||
|
||||
defp stage_1_day_field({pg, accum}) do
|
||||
day_criteria = pg.criteria["day"] || %{}
|
||||
days = day_criteria["days_ago"] || 0
|
||||
day_criteria = pg.criteria["day"] || pg.criteria[:day] || %{}
|
||||
days = day_criteria["days_ago"] || day_criteria[:days_ago] || 0
|
||||
op = day_criteria["op"] || day_criteria[:op] || "<"
|
||||
time = Timex.shift(Timex.now(), days: -1 * days)
|
||||
|
||||
if days == 0 do
|
||||
{ pg, accum }
|
||||
else
|
||||
|
||||
op = day_criteria["op"] || "<"
|
||||
time = Timex.shift(Timex.now(), days: -1 * days)
|
||||
|
||||
inverted_op = if op == ">" do "<" else ">" end
|
||||
|
||||
{ pg, accum ++ [{"created_at", inverted_op, time}] }
|
||||
|
|
|
@ -37,6 +37,7 @@ defmodule FarmbotCore.Asset.Point do
|
|||
meta: point.meta,
|
||||
name: point.name,
|
||||
plant_stage: point.plant_stage,
|
||||
created_at: point.created_at,
|
||||
planted_at: point.planted_at,
|
||||
pointer_type: point.pointer_type,
|
||||
radius: point.radius,
|
||||
|
|
|
@ -11,16 +11,9 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FbosConfig do
|
|||
alias FarmbotCore.{Asset.FbosConfig, BotState, Config}
|
||||
import FarmbotFirmware.PackageUtils, only: [package_to_string: 1]
|
||||
|
||||
@firmware_flash_attempt_threshold Application.get_env(:farmbot_core, __MODULE__)[:firmware_flash_attempt_threshold]
|
||||
@firmware_flash_attempt_threshold Application.get_env(:farmbot_core, __MODULE__)[:firmware_flash_attempt_threshold] || 5
|
||||
@firmware_flash_timeout Application.get_env(:farmbot_core, __MODULE__)[:firmware_flash_timeout] || 5000
|
||||
@disable_firmware_io_logs_timeout Application.get_env(:farmbot_core, __MODULE__)[:disable_firmware_io_logs_timeout] || 300000
|
||||
@firmware_flash_attempt_threshold || Mix.raise """
|
||||
Firmware open attempt threshold not configured:
|
||||
|
||||
config :farmbot_core, #{__MODULE__}, [
|
||||
firmware_flash_attempt_threshold: :infinity
|
||||
]
|
||||
"""
|
||||
|
||||
@impl FarmbotCore.AssetWorker
|
||||
def preload(%FbosConfig{}), do: []
|
||||
|
|
|
@ -9,14 +9,7 @@ defmodule FarmbotCore.FirmwareOpenTask do
|
|||
require FarmbotCore.Logger
|
||||
alias FarmbotFirmware.{UARTTransport, StubTransport}
|
||||
alias FarmbotCore.{Asset, Config}
|
||||
@attempt_threshold Application.get_env(:farmbot_core, __MODULE__)[:attempt_threshold]
|
||||
@attempt_threshold || Mix.raise """
|
||||
Firmware open attempt threshold not configured:
|
||||
|
||||
config :farmbot_core, FarmbotCore.FirmwareOpenTask, [
|
||||
attempt_threshold: 10
|
||||
]
|
||||
"""
|
||||
@attempt_threshold Application.get_env(:farmbot_core, __MODULE__)[:attempt_threshold] || 5
|
||||
|
||||
@doc false
|
||||
def start_link(args, opts \\ [name: __MODULE__]) do
|
||||
|
@ -25,7 +18,10 @@ defmodule FarmbotCore.FirmwareOpenTask do
|
|||
|
||||
@doc false
|
||||
def swap_transport(tty) do
|
||||
Application.put_env(:farmbot_firmware, FarmbotFirmware, transport: UARTTransport, device: tty)
|
||||
Application.put_env(:farmbot_firmware, FarmbotFirmware,
|
||||
transport: UARTTransport,
|
||||
device: tty,
|
||||
reset: FarmbotCore.FirmwareResetter)
|
||||
# Swap transport on FW module.
|
||||
# Close tranpsort if it is open currently.
|
||||
_ = FarmbotFirmware.close_transport()
|
||||
|
@ -33,7 +29,9 @@ defmodule FarmbotCore.FirmwareOpenTask do
|
|||
end
|
||||
|
||||
def unswap_transport() do
|
||||
Application.put_env(:farmbot_firmware, FarmbotFirmware, transport: StubTransport)
|
||||
Application.put_env(:farmbot_firmware, FarmbotFirmware,
|
||||
transport: StubTransport,
|
||||
reset: FarmbotCore.FirmwareResetter)
|
||||
# Swap transport on FW module.
|
||||
# Close tranpsort if it is open currently.
|
||||
_ = FarmbotFirmware.close_transport()
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
defmodule FarmbotCore.FirmwareResetter do
|
||||
if Code.ensure_compiled?(Circuits.GPIO) do
|
||||
@gpio Circuits.GPIO
|
||||
else
|
||||
@gpio nil
|
||||
end
|
||||
alias FarmbotCore.Asset
|
||||
require FarmbotCore.Logger
|
||||
|
||||
def reset(package \\ nil) do
|
||||
pkg = package || Asset.fbos_config(:firmware_hardware)
|
||||
FarmbotCore.Logger.debug(3, "Attempting to retrieve #{pkg} reset function.")
|
||||
{:ok, fun} = find_reset_fun(pkg)
|
||||
fun.()
|
||||
end
|
||||
|
||||
def find_reset_fun("express_k10") do
|
||||
FarmbotCore.Logger.debug(3, "Using special express reset function")
|
||||
{:ok, fn -> express_reset_fun() end}
|
||||
end
|
||||
|
||||
def find_reset_fun(_) do
|
||||
FarmbotCore.Logger.debug(3, "Using default reset function")
|
||||
{:ok, fn -> :ok end}
|
||||
end
|
||||
|
||||
def express_reset_fun() do
|
||||
try do
|
||||
gpio_module = @gpio
|
||||
FarmbotCore.Logger.debug(3, "Begin MCU reset")
|
||||
{:ok, gpio} = gpio_module.open(19, :output)
|
||||
:ok = gpio_module.write(gpio, 0)
|
||||
:ok = gpio_module.write(gpio, 1)
|
||||
Process.sleep(1000)
|
||||
:ok = gpio_module.write(gpio, 0)
|
||||
FarmbotCore.Logger.debug(3, "Finish MCU Reset")
|
||||
:ok
|
||||
rescue
|
||||
ex ->
|
||||
message = Exception.message(ex)
|
||||
msg = "Express reset failed #{message}"
|
||||
FarmbotCore.Logger.error(3, msg)
|
||||
{:error, msg}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,17 +3,14 @@ defmodule FarmbotCore.FirmwareTTYDetector do
|
|||
require Logger
|
||||
alias Circuits.UART
|
||||
|
||||
@expected_names Application.get_env(:farmbot_core, __MODULE__)[:expected_names]
|
||||
@expected_names ||
|
||||
Mix.raise("""
|
||||
Please configure `expected_names` for TTYDetector.
|
||||
|
||||
config :farmbot_core, FarmbotCore.FirmwareTTYDetector,
|
||||
expected_names: ["ttyS0", "ttyNotReal"]
|
||||
""")
|
||||
|
||||
@error_retry_ms 5_000
|
||||
|
||||
if System.get_env("FARMBOT_TTY") do
|
||||
@expected_names ["ttyUSB0", "ttyAMA0", "ttyACM0", System.get_env("FARMBOT_TTY")]
|
||||
else
|
||||
@expected_names ["ttyUSB0", "ttyAMA0", "ttyACM0"]
|
||||
end
|
||||
|
||||
@doc "Gets the detected TTY"
|
||||
def tty(server \\ __MODULE__) do
|
||||
GenServer.call(server, :tty)
|
||||
|
@ -53,7 +50,6 @@ defmodule FarmbotCore.FirmwareTTYDetector do
|
|||
if farmbot_tty?(name) do
|
||||
{:noreply, name}
|
||||
else
|
||||
# Logger.warn("#{name} is not an expected Farmbot Firmware TTY")
|
||||
{:noreply, state, {:continue, rest}}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -124,8 +124,6 @@ defmodule FarmbotCore.Asset.CriteriaRetrieverTest do
|
|||
expect(Timex, :now, fn -> @now end)
|
||||
pg = point_group_with_fake_points()
|
||||
|
||||
# This one is _almost_ a perfect match,
|
||||
# but the meta field is a miss.
|
||||
point!(%{
|
||||
id: 888,
|
||||
created_at: @five_days_ago,
|
||||
|
@ -525,6 +523,37 @@ defmodule FarmbotCore.Asset.CriteriaRetrieverTest do
|
|||
assert Enum.count(ids) == 1
|
||||
end
|
||||
|
||||
test "edge case: Retrieves by `day` criteria only" do
|
||||
Repo.delete_all(PointGroup)
|
||||
Repo.delete_all(Point)
|
||||
days_ago4 = Timex.shift(@now, days: -4)
|
||||
days_ago2 = Timex.shift(@now, days: -2)
|
||||
expect(Timex, :now, fn -> @now end)
|
||||
|
||||
point!(%{id: 1, pointer_type: "Plant", created_at: days_ago4})
|
||||
p2 = point!(%{id: 2, pointer_type: "Plant", created_at: days_ago2})
|
||||
|
||||
pg1 = %PointGroup{
|
||||
id: 212,
|
||||
created_at: Timex.shift(@now, hours: -1),
|
||||
updated_at: Timex.shift(@now, hours: -1),
|
||||
name: "Less than 2 days ago",
|
||||
point_ids: [],
|
||||
sort_type: "yx_descending",
|
||||
criteria: %{
|
||||
day: %{"op" => "<", "days_ago" => 3},
|
||||
string_eq: %{},
|
||||
number_eq: %{},
|
||||
number_lt: %{},
|
||||
number_gt: %{}
|
||||
}
|
||||
}
|
||||
|
||||
ids = CriteriaRetriever.run(pg1) |> Enum.map(fn p -> p.id end)
|
||||
assert Enum.count(ids) == 1
|
||||
assert Enum.member?(ids, p2.id)
|
||||
end
|
||||
|
||||
test "edge case: Filter by slot direction" do
|
||||
Repo.delete_all(PointGroup)
|
||||
Repo.delete_all(Point)
|
||||
|
|
|
@ -2,8 +2,6 @@ use Mix.Config
|
|||
|
||||
config :logger, handle_otp_reports: true, handle_sasl_reports: true
|
||||
|
||||
config :farmbot_firmware, FarmbotFirmware, reset: FarmbotFirmware.NullReset
|
||||
|
||||
# TODO(Rick) We probably don't need to use this anymore now that Mox is a thing.
|
||||
config :farmbot_celery_script, FarmbotCeleryScript.SysCalls,
|
||||
sys_calls: FarmbotCeleryScript.SysCalls.Stubs
|
||||
|
|
|
@ -39,8 +39,6 @@ config :farmbot_core, FarmbotCore.EctoMigrator,
|
|||
default_currently_on_beta:
|
||||
String.contains?(to_string(:os.cmd('git rev-parse --abbrev-ref HEAD')), "beta")
|
||||
|
||||
config :farmbot_core, FarmbotCore.FirmwareTTYDetector, expected_names: []
|
||||
|
||||
config :farmbot_core, FarmbotCore.FirmwareOpenTask, attempt_threshold: 0
|
||||
|
||||
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FbosConfig,
|
||||
|
|
|
@ -6,8 +6,9 @@ defmodule FarmbotExt.API.DirtyWorker do
|
|||
import API.View, only: [render: 2]
|
||||
|
||||
require Logger
|
||||
require FarmbotCore.Logger
|
||||
use GenServer
|
||||
@timeout 10000
|
||||
@timeout 500
|
||||
|
||||
# these resources can't be accessed by `id`.
|
||||
@singular [
|
||||
|
@ -34,84 +35,69 @@ defmodule FarmbotExt.API.DirtyWorker do
|
|||
|
||||
@impl GenServer
|
||||
def init(args) do
|
||||
# Logger.disable(self())
|
||||
module = Keyword.fetch!(args, :module)
|
||||
timeout = Keyword.get(args, :timeout, @timeout)
|
||||
{:ok, %{module: module, timeout: timeout}, timeout}
|
||||
Process.send_after(self(), :do_work, @timeout)
|
||||
{:ok, %{module: module}}
|
||||
end
|
||||
|
||||
@impl GenServer
|
||||
def handle_info(:timeout, %{module: module} = state) do
|
||||
dirty = Private.list_dirty(module)
|
||||
local = Private.list_local(module)
|
||||
{:noreply, state, {:continue, Enum.uniq(dirty ++ local)}}
|
||||
def handle_info(:do_work, %{module: module} = state) do
|
||||
Process.sleep(@timeout)
|
||||
list = Enum.uniq(Private.list_dirty(module) ++ Private.list_local(module))
|
||||
|
||||
unless has_race_condition?(module, list) do
|
||||
Enum.map(list, fn dirty -> work(dirty, module) end)
|
||||
end
|
||||
|
||||
Process.send_after(self(), :do_work, @timeout)
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
@impl GenServer
|
||||
def handle_continue([], state) do
|
||||
{:noreply, state, state.timeout}
|
||||
end
|
||||
def work(dirty, module) do
|
||||
# Go easy on the API
|
||||
Process.sleep(333)
|
||||
|
||||
def handle_continue([dirty | rest], %{module: module} = state) do
|
||||
# Logger.info("[#{module} #{dirty.local_id} #{inspect(self())}] Handling dirty data")
|
||||
|
||||
case http_request(dirty, state) do
|
||||
case http_request(dirty, module) do
|
||||
# Valid data
|
||||
{:ok, %{status: s, body: body}} when s > 199 and s < 300 ->
|
||||
# Logger.debug(
|
||||
# "[#{module} #{dirty.local_id} #{inspect(self())}] HTTP request complete: #{s} ok"
|
||||
# )
|
||||
|
||||
dirty |> module.changeset(body) |> handle_changeset(rest, state)
|
||||
dirty |> module.changeset(body) |> handle_changeset(module)
|
||||
|
||||
# Invalid data
|
||||
{:ok, %{status: s, body: %{} = body}} when s > 399 and s < 500 ->
|
||||
# Logger.debug(
|
||||
# "[#{module} #{dirty.local_id} #{inspect(self())}] HTTP request complete: #{s} error+body"
|
||||
# )
|
||||
|
||||
FarmbotCore.Logger.error(2, "HTTP Error #{s}. #{inspect(body)}")
|
||||
changeset = module.changeset(dirty)
|
||||
|
||||
Enum.reduce(body, changeset, fn {key, val}, changeset ->
|
||||
Ecto.Changeset.add_error(changeset, key, val)
|
||||
end)
|
||||
|> handle_changeset(rest, state)
|
||||
|> handle_changeset(module)
|
||||
|
||||
# Invalid data, but the API didn't say why
|
||||
{:ok, %{status: s, body: _body}} when s > 399 and s < 500 ->
|
||||
# Logger.debug(
|
||||
# "[#{module} #{dirty.local_id} #{inspect(self())}] HTTP request complete: #{s} error"
|
||||
# )
|
||||
FarmbotCore.Logger.error(2, "HTTP Error #{s}. #{inspect(dirty)}")
|
||||
|
||||
module.changeset(dirty)
|
||||
|> Map.put(:valid?, false)
|
||||
|> handle_changeset(rest, state)
|
||||
|> handle_changeset(module)
|
||||
|
||||
# HTTP Error. (500, network error, timeout etc.)
|
||||
error ->
|
||||
Logger.error(
|
||||
"[#{module} #{dirty.local_id} #{inspect(self())}] HTTP Error: #{state.module} #{
|
||||
FarmbotCore.Logger.error(
|
||||
2,
|
||||
"[#{module} #{dirty.local_id} #{inspect(self())}] HTTP Error: #{module} #{
|
||||
inspect(error)
|
||||
}"
|
||||
)
|
||||
|
||||
{:noreply, state, @timeout}
|
||||
end
|
||||
end
|
||||
|
||||
# If the changeset was valid, update the record.
|
||||
def handle_changeset(%{valid?: true} = changeset, rest, state) do
|
||||
# Logger.info("Successfully synced: #{state.module}")
|
||||
|
||||
Repo.update!(changeset)
|
||||
|> Private.mark_clean!()
|
||||
|
||||
{:noreply, state, {:continue, rest}}
|
||||
def handle_changeset(%{valid?: true} = changeset, _module) do
|
||||
Private.mark_clean!(Repo.update!(changeset))
|
||||
:ok
|
||||
end
|
||||
|
||||
# If the changeset was invalid, delete the record.
|
||||
# TODO(Connor) - Update the dirty field here, upload to rollbar?
|
||||
def handle_changeset(%{valid?: false, data: data} = changeset, rest, state) do
|
||||
def handle_changeset(%{valid?: false, data: data} = changeset, module) do
|
||||
message =
|
||||
Enum.map(changeset.errors, fn
|
||||
{key, {msg, _meta}} when is_binary(key) -> "\t#{key}: #{msg}"
|
||||
|
@ -119,29 +105,64 @@ defmodule FarmbotExt.API.DirtyWorker do
|
|||
end)
|
||||
|> Enum.join("\n")
|
||||
|
||||
Logger.error("Failed to sync: #{state.module} \n #{message}")
|
||||
FarmbotCore.Logger.error(3, "Failed to sync: #{module} \n #{message}")
|
||||
_ = Repo.delete!(data)
|
||||
{:noreply, state, {:continue, rest}}
|
||||
:ok
|
||||
end
|
||||
|
||||
defp http_request(%{id: nil} = dirty, state) do
|
||||
# Logger.debug("#{state.module} clean request (post)")
|
||||
path = state.module.path()
|
||||
data = render(state.module, dirty)
|
||||
defp http_request(%{id: nil} = dirty, module) do
|
||||
path = module.path()
|
||||
data = render(module, dirty)
|
||||
API.post(API.client(), path, data)
|
||||
end
|
||||
|
||||
defp http_request(dirty, %{module: module} = state) when module in @singular do
|
||||
# Logger.debug("#{state.module} dirty request (patch)")
|
||||
path = path = state.module.path()
|
||||
data = render(state.module, dirty)
|
||||
defp http_request(dirty, module) when module in @singular do
|
||||
path = path = module.path()
|
||||
data = render(module, dirty)
|
||||
API.patch(API.client(), path, data)
|
||||
end
|
||||
|
||||
defp http_request(dirty, state) do
|
||||
# Logger.debug("#{state.module} dirty request (patch)")
|
||||
path = Path.join(state.module.path(), to_string(dirty.id))
|
||||
data = render(state.module, dirty)
|
||||
defp http_request(dirty, module) do
|
||||
path = Path.join(module.path(), to_string(dirty.id))
|
||||
data = render(module, dirty)
|
||||
API.patch(API.client(), path, data)
|
||||
end
|
||||
|
||||
# This is a fix for a race condtion. The root cause is unknown
|
||||
# as of 18 May 2020. The problem is that records are marked
|
||||
# diry _before_ the dirty data is saved. That means that FBOS
|
||||
# knows a record has changed, but for a brief moment, it only
|
||||
# has the old copy of the record (not the changes).
|
||||
# Because of this race condtion,
|
||||
# The race condition:
|
||||
#
|
||||
# * Is nondeterministic
|
||||
# * Happens frequently when running many MARK AS steps in one go.
|
||||
# * Happens frequently when Erlang VM only has one thread
|
||||
# * Ie: `iex --erl '+S 1 +A 1' -S mix`
|
||||
# * Happens frequently when @timeout is decreased to `1`.
|
||||
#
|
||||
# This function PREVENTS CORRUPTION OF API DATA. It can be
|
||||
# removed once the root cause of the data race is determined.
|
||||
# - RC 18 May 2020
|
||||
def has_race_condition?(module, list) do
|
||||
Enum.find(list, fn item ->
|
||||
if item.id do
|
||||
if item == Repo.get_by(module, id: item.id) do
|
||||
# This item is OK - no race condition.
|
||||
false
|
||||
else
|
||||
# There was a race condtion. We probably can't trust
|
||||
# any of the data in this list. We need to wait and
|
||||
# try again later.
|
||||
Process.sleep(@timeout * 3)
|
||||
true
|
||||
end
|
||||
else
|
||||
# This item only exists on the FBOS side.
|
||||
# It will never be affected by the data race condtion.
|
||||
false
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,12 +21,12 @@ timeout = System.get_env("EXUNIT_TIMEOUT") || "5000"
|
|||
System.put_env("LOG_SILENCE", "true")
|
||||
|
||||
ExUnit.start(assert_receive_timeout: String.to_integer(timeout))
|
||||
# Use this to stub out calls to `state.reset.reset()` in firmware.
|
||||
defmodule StubReset do
|
||||
def reset(), do: :ok
|
||||
end
|
||||
|
||||
defmodule Helpers do
|
||||
# Maybe I don't need this?
|
||||
# Maybe I could use `start_supervised`?
|
||||
# https://hexdocs.pm/ex_unit/ExUnit.Callbacks.html#start_supervised/2
|
||||
|
||||
@wait_time 180
|
||||
# Base case: We have a pid
|
||||
def wait_for(pid) when is_pid(pid), do: check_on_mbox(pid)
|
||||
|
|
|
@ -107,8 +107,7 @@ defmodule FarmbotFirmware do
|
|||
:command_queue,
|
||||
:caller_pid,
|
||||
:current,
|
||||
:reset,
|
||||
:reset_pid
|
||||
:reset
|
||||
]
|
||||
|
||||
@type state :: %State{
|
||||
|
@ -123,8 +122,7 @@ defmodule FarmbotFirmware do
|
|||
command_queue: [{pid(), GCODE.t()}],
|
||||
caller_pid: nil | pid,
|
||||
current: nil | GCODE.t(),
|
||||
reset: module(),
|
||||
reset_pid: nil | pid()
|
||||
reset: module()
|
||||
}
|
||||
|
||||
@doc """
|
||||
|
@ -202,16 +200,7 @@ defmodule FarmbotFirmware do
|
|||
args = Keyword.merge(args, global)
|
||||
transport = Keyword.fetch!(args, :transport)
|
||||
side_effects = Keyword.get(args, :side_effects)
|
||||
# This is probably the cause of
|
||||
# https://github.com/FarmBot/farmbot_os/issues/1111
|
||||
# FarmbotFirmware.NullReset (RPi3? Safe default?)
|
||||
# -OR-
|
||||
# FarmbotOS.Platform.Target.FirmwareReset.GPIO (RPi0, RPi)
|
||||
# -OR-
|
||||
# Use Application.get_env to find target?
|
||||
# probably?
|
||||
reset = Keyword.get(args, :reset) || FarmbotFirmware.NullReset
|
||||
|
||||
reset = Keyword.fetch!(args, :reset)
|
||||
# Add an anon function that transport implementations should call.
|
||||
fw = self()
|
||||
fun = fn {_, _} = code -> GenServer.cast(fw, code) end
|
||||
|
@ -225,7 +214,6 @@ defmodule FarmbotFirmware do
|
|||
side_effects: side_effects,
|
||||
status: :transport_boot,
|
||||
reset: reset,
|
||||
reset_pid: nil,
|
||||
command_queue: [],
|
||||
configuration_queue: []
|
||||
}
|
||||
|
@ -242,24 +230,6 @@ defmodule FarmbotFirmware do
|
|||
GenServer.stop(state.transport_pid)
|
||||
end
|
||||
|
||||
def handle_info(:timeout, %{status: :transport_boot, reset_pid: nil} = state) do
|
||||
case GenServer.start_link(state.reset, state.transport_args,
|
||||
name: state.reset
|
||||
) do
|
||||
{:ok, pid} ->
|
||||
{:noreply, %{state | reset_pid: pid}}
|
||||
|
||||
# TODO(Rick): I have no idea what's going on here.
|
||||
{:error, {:already_started, pid}} ->
|
||||
{:noreply, %{state | reset_pid: pid}}
|
||||
|
||||
error ->
|
||||
Logger.error("Error starting Firmware Reset: #{inspect(error)}")
|
||||
Process.send_after(self(), :timeout, @transport_init_error_retry_ms)
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
# This will be the first message received right after `init/1`
|
||||
# It should try to open a transport every `transport_init_error_retry_ms`
|
||||
# until success.
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
defmodule FarmbotFirmware.NullReset do
|
||||
@moduledoc """
|
||||
Does nothing in reference to resetting the firmware port
|
||||
"""
|
||||
@behaviour FarmbotFirmware.Reset
|
||||
use GenServer
|
||||
|
||||
@impl FarmbotFirmware.Reset
|
||||
def reset(), do: :ok
|
||||
|
||||
@impl GenServer
|
||||
def init(_args) do
|
||||
{:ok, %{}}
|
||||
end
|
||||
end
|
|
@ -553,6 +553,6 @@ defmodule FarmbotFirmware.Param do
|
|||
def format_bool(val) when val == 1, do: true
|
||||
def format_bool(val) when val == 0, do: false
|
||||
|
||||
def format_high_low_inverted(val) when val == 0, do: "HIGH"
|
||||
def format_high_low_inverted(val) when val == 1, do: "LOW"
|
||||
def format_high_low_inverted(val) when val == 0, do: "ON"
|
||||
def format_high_low_inverted(val) when val == 1, do: "OFF"
|
||||
end
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
defmodule FarmbotFirmware.Reset do
|
||||
@moduledoc """
|
||||
Behaviour to reset the UART connection into
|
||||
bootloader mode for firmware upgrades.
|
||||
"""
|
||||
|
||||
@callback reset :: :ok | {:error, Stirng.t()}
|
||||
end
|
|
@ -13,7 +13,7 @@ defmodule FarmbotFirmware.UARTTransport do
|
|||
def init(args) do
|
||||
device = Keyword.fetch!(args, :device)
|
||||
handle_gcode = Keyword.fetch!(args, :handle_gcode)
|
||||
reset = Keyword.get(args, :reset)
|
||||
reset = Keyword.fetch!(args, :reset)
|
||||
{:ok, uart} = UartDefaultAdapter.start_link()
|
||||
|
||||
{:ok,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -6,11 +6,16 @@ defmodule FarmbotFirmware.CommandTest do
|
|||
import ExUnit.CaptureLog
|
||||
@subject FarmbotFirmware.Command
|
||||
|
||||
@tag :capture_log
|
||||
test "command() runs RPCs" do
|
||||
arg = [transport: FarmbotFirmware.StubTransport]
|
||||
def fake_pid() do
|
||||
arg = [transport: FarmbotFirmware.StubTransport, reset: StubReset]
|
||||
{:ok, pid} = FarmbotFirmware.start_link(arg, [])
|
||||
send(pid, :timeout)
|
||||
pid
|
||||
end
|
||||
|
||||
@tag :capture_log
|
||||
test "command() runs RPCs" do
|
||||
pid = fake_pid()
|
||||
|
||||
assert {:error, :emergency_lock} ==
|
||||
FarmbotFirmware.command(pid, {:command_emergency_lock, []})
|
||||
|
@ -22,9 +27,7 @@ defmodule FarmbotFirmware.CommandTest do
|
|||
|
||||
@tag :capture_log
|
||||
test "command() refuses to run RPCs in :boot state" do
|
||||
arg = [transport: FarmbotFirmware.StubTransport]
|
||||
{:ok, pid} = FarmbotFirmware.start_link(arg, [])
|
||||
send(pid, :timeout)
|
||||
pid = fake_pid()
|
||||
{:error, message} = @subject.command(pid, {:a, {:b, :c}})
|
||||
assert "Can't send command when in :boot state" == message
|
||||
end
|
||||
|
|
|
@ -14,7 +14,7 @@ defmodule FarmbotFirmware.UARTTransportTest do
|
|||
init_args = [
|
||||
device: :FAKE_DEVICE,
|
||||
handle_gcode: :FAKE_GCODE_HANDLER,
|
||||
reset: :FAKE_RESETER
|
||||
reset: StubReset
|
||||
]
|
||||
|
||||
{:ok, state, 0} = UARTTransport.init(init_args)
|
||||
|
|
|
@ -11,7 +11,7 @@ defmodule FarmbotFirmwareTest do
|
|||
end
|
||||
|
||||
def firmware_server do
|
||||
arg = [transport: FarmbotFirmware.StubTransport]
|
||||
arg = [transport: FarmbotFirmware.StubTransport, reset: StubReset]
|
||||
{:ok, pid} = FarmbotFirmware.start_link(arg, [])
|
||||
send(pid, :timeout)
|
||||
try_command(pid, {nil, {:command_emergency_lock, []}})
|
||||
|
|
|
@ -16,19 +16,19 @@ defmodule FarmbotFirmware.ParamTest do
|
|||
|
||||
t(:pin_guard_5_time_out, 12, {"pin guard 5 timeout", "(seconds)", "12"})
|
||||
t(:pin_guard_5_pin_nr, 12, {"pin guard 5 pin number", nil, "12"})
|
||||
t(:pin_guard_5_active_state, 0, {"pin guard 5 safe state", nil, "HIGH"})
|
||||
t(:pin_guard_5_active_state, 0, {"pin guard 5 safe state", nil, "ON"})
|
||||
t(:pin_guard_4_time_out, 12, {"pin guard 4 timeout", "(seconds)", "12"})
|
||||
t(:pin_guard_4_pin_nr, 12, {"pin guard 4 pin number", nil, "12"})
|
||||
t(:pin_guard_4_active_state, 0, {"pin guard 4 safe state", nil, "HIGH"})
|
||||
t(:pin_guard_4_active_state, 0, {"pin guard 4 safe state", nil, "ON"})
|
||||
t(:pin_guard_3_time_out, 1.0, {"pin guard 3 timeout", "(seconds)", "1"})
|
||||
t(:pin_guard_3_pin_nr, 1.0, {"pin guard 3 pin number", nil, "1"})
|
||||
t(:pin_guard_3_active_state, 0, {"pin guard 3 safe state", nil, "HIGH"})
|
||||
t(:pin_guard_3_active_state, 0, {"pin guard 3 safe state", nil, "ON"})
|
||||
t(:pin_guard_2_time_out, 1.0, {"pin guard 2 timeout", "(seconds)", "1"})
|
||||
t(:pin_guard_2_pin_nr, 1.0, {"pin guard 2 pin number", nil, "1"})
|
||||
t(:pin_guard_2_active_state, 0, {"pin guard 2 safe state", nil, "HIGH"})
|
||||
t(:pin_guard_2_active_state, 0, {"pin guard 2 safe state", nil, "ON"})
|
||||
t(:pin_guard_1_time_out, 1.0, {"pin guard 1 timeout", "(seconds)", "1"})
|
||||
t(:pin_guard_1_pin_nr, 1.0, {"pin guard 1 pin number", nil, "1"})
|
||||
t(:pin_guard_1_active_state, 0, {"pin guard 1 safe state", nil, "HIGH"})
|
||||
t(:pin_guard_1_active_state, 0, {"pin guard 1 safe state", nil, "ON"})
|
||||
t(:param_use_eeprom, 1, {"use eeprom", nil, true})
|
||||
t(:param_test, 1, {"param_test", nil, true})
|
||||
t(:param_mov_nr_retry, 1.0, {"max retries", nil, "1"})
|
||||
|
|
|
@ -80,8 +80,6 @@ config :farmbot, FarmbotOS.Platform.Supervisor,
|
|||
FarmbotOS.Platform.Host.Configurator
|
||||
]
|
||||
|
||||
config :farmbot_firmware, FarmbotFirmware, reset: FarmbotFirmware.NullReset
|
||||
|
||||
config :logger,
|
||||
handle_sasl_reports: false,
|
||||
handle_otp_reports: false,
|
||||
|
|
|
@ -36,15 +36,7 @@ config :farmbot,
|
|||
FarmbotCore.Asset.Repo
|
||||
]
|
||||
|
||||
config :farmbot_core, FarmbotCore.FirmwareTTYDetector,
|
||||
expected_names: [
|
||||
System.get_env("FARMBOT_TTY")
|
||||
]
|
||||
|
||||
config :farmbot_core, FarmbotCore.FirmwareOpenTask, attempt_threshold: 5
|
||||
|
||||
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FbosConfig,
|
||||
firmware_flash_attempt_threshold: 5
|
||||
|
||||
config :logger,
|
||||
backends: [:console]
|
||||
|
|
|
@ -35,8 +35,6 @@ config :farmbot, FarmbotOS.Configurator,
|
|||
data_layer: FarmbotOS.Configurator.ConfigDataLayer,
|
||||
network_layer: FarmbotOS.Configurator.FakeNetworkLayer
|
||||
|
||||
config :farmbot_core, FarmbotCore.FirmwareTTYDetector, expected_names: []
|
||||
|
||||
config :farmbot_core, FarmbotCore.FirmwareOpenTask, attempt_threshold: 0
|
||||
|
||||
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FbosConfig,
|
||||
|
|
|
@ -116,9 +116,6 @@ config :farmbot, FarmbotOS.System,
|
|||
|
||||
config :farmbot_core, FarmbotCore.FirmwareOpenTask, attempt_threshold: 5
|
||||
|
||||
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FbosConfig,
|
||||
firmware_flash_attempt_threshold: 5
|
||||
|
||||
config :logger, backends: [RingLogger]
|
||||
|
||||
config :logger, RingLogger,
|
||||
|
|
|
@ -1,16 +1,6 @@
|
|||
use Mix.Config
|
||||
|
||||
config :farmbot_core, FarmbotCore.FirmwareTTYDetector,
|
||||
expected_names: ["ttyUSB0", "ttyAMA0"]
|
||||
|
||||
config :farmbot_firmware, FarmbotFirmware,
|
||||
reset: FarmbotOS.Platform.Target.FirmwareReset.GPIO
|
||||
|
||||
config :farmbot, FarmbotOS.Init.Supervisor,
|
||||
init_children: [
|
||||
FarmbotOS.Platform.Target.RTCWorker
|
||||
]
|
||||
|
||||
# :farmbot_firmware, FarmbotFirmware changes too much.
|
||||
# Needed one that would stay stable, so I duplicated it here:
|
||||
config :farmbot, FarmbotOS.SysCalls.FlashFirmware, gpio: Circuits.GPIO
|
||||
|
|
|
@ -1,17 +1,7 @@
|
|||
use Mix.Config
|
||||
|
||||
config :farmbot_core, FarmbotCore.FirmwareTTYDetector,
|
||||
expected_names: ["ttyUSB0", "ttyAMA0"]
|
||||
|
||||
config :farmbot_core, FarmbotCore.FirmwareOpenTask, attempt_threshold: 50
|
||||
|
||||
config :farmbot_firmware, FarmbotFirmware,
|
||||
reset: FarmbotOS.Platform.Target.FirmwareReset.GPIO
|
||||
|
||||
# :farmbot_firmware, FarmbotFirmware changes too much.
|
||||
# Needed one that would stay stable, so I duplicated it here:
|
||||
config :farmbot, FarmbotOS.SysCalls.FlashFirmware, gpio: Circuits.GPIO
|
||||
|
||||
config :farmbot, FarmbotOS.Init.Supervisor,
|
||||
init_children: [
|
||||
FarmbotOS.Platform.Target.RTCWorker
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
use Mix.Config
|
||||
|
||||
config :farmbot_core, FarmbotCore.FirmwareTTYDetector,
|
||||
expected_names: ["ttyUSB0", "ttyACM0"]
|
||||
|
||||
config :farmbot_firmware, FarmbotFirmware, reset: FarmbotFirmware.NullReset
|
||||
config :farmbot_firmware, FarmbotFirmware, reset: FarmbotCore.FirmwareResetter
|
||||
|
||||
config :farmbot, FarmbotOS.Init.Supervisor,
|
||||
init_children: [
|
||||
|
|
|
@ -84,7 +84,7 @@ defmodule FarmbotOS.SysCalls do
|
|||
defdelegate set_servo_angle(pin, angle), to: PinControl
|
||||
|
||||
@impl true
|
||||
defdelegate resource_update(kind, id, params), to: ResourceUpdate
|
||||
defdelegate update_resource(kind, id, params), to: ResourceUpdate
|
||||
|
||||
@impl true
|
||||
defdelegate get_current_x(), to: Movement
|
||||
|
@ -184,6 +184,7 @@ defmodule FarmbotOS.SysCalls do
|
|||
|
||||
@impl true
|
||||
def firmware_reboot do
|
||||
FarmbotCore.Logger.info(1, "Restarting firmware...")
|
||||
GenServer.stop(FarmbotFirmware, :reboot)
|
||||
end
|
||||
|
||||
|
@ -204,12 +205,14 @@ defmodule FarmbotOS.SysCalls do
|
|||
@impl true
|
||||
def emergency_lock do
|
||||
_ = FarmbotFirmware.command({:command_emergency_lock, []})
|
||||
FarmbotCore.Logger.error(1, "E-stopped")
|
||||
:ok
|
||||
end
|
||||
|
||||
@impl true
|
||||
def emergency_unlock do
|
||||
_ = FarmbotFirmware.command({:command_emergency_unlock, []})
|
||||
FarmbotCore.Logger.busy(1, "Unlocked")
|
||||
:ok
|
||||
end
|
||||
|
||||
|
|
|
@ -1,29 +1,10 @@
|
|||
Application.get_env(:farmbot, FarmbotOS.SysCalls.FlashFirmware, [])[:gpio]
|
||||
|
||||
defmodule FarmbotOS.SysCalls.FlashFirmware do
|
||||
@moduledoc false
|
||||
|
||||
alias FarmbotCore.{Asset, Asset.Private}
|
||||
alias FarmbotCore.{Asset, Asset.Private, FirmwareResetter}
|
||||
alias FarmbotFirmware
|
||||
alias FarmbotCore.FirmwareTTYDetector
|
||||
|
||||
defmodule Stub do
|
||||
require FarmbotCore.Logger
|
||||
|
||||
def fail do
|
||||
m = "No express function found. Please notify support."
|
||||
FarmbotCore.Logger.error(3, m)
|
||||
{:error, m}
|
||||
end
|
||||
|
||||
def open(_, _), do: fail()
|
||||
def write(_, _), do: fail()
|
||||
end
|
||||
|
||||
# This only matter for express.
|
||||
# When it's an express, use Circuits.GPIO.
|
||||
@gpio Application.get_env(:farmbot, __MODULE__, [])[:gpio] || Stub
|
||||
|
||||
import FarmbotFirmware.PackageUtils,
|
||||
only: [find_hex_file: 1, package_to_string: 1]
|
||||
|
||||
|
@ -40,11 +21,11 @@ defmodule FarmbotOS.SysCalls.FlashFirmware do
|
|||
{:ok, tty} <- find_tty(),
|
||||
_ <-
|
||||
FarmbotCore.Logger.debug(3, "found tty: #{tty} for firmware flash"),
|
||||
{:ok, fun} <- find_reset_fun(package),
|
||||
{:ok, fun} <- FirmwareResetter.find_reset_fun(package),
|
||||
_ <-
|
||||
FarmbotCore.Logger.debug(
|
||||
3,
|
||||
"closing firmware transport before flash"
|
||||
"Closing the firmware transport before flash"
|
||||
),
|
||||
:ok <- FarmbotFirmware.close_transport(),
|
||||
_ <- FarmbotCore.Logger.debug(3, "starting firmware flash"),
|
||||
|
@ -64,14 +45,17 @@ defmodule FarmbotOS.SysCalls.FlashFirmware do
|
|||
end
|
||||
|
||||
def finish_flashing({_result, 0}) do
|
||||
FarmbotCore.Logger.success(2, "Firmware flashed successfully!")
|
||||
FarmbotCore.Logger.success(
|
||||
1,
|
||||
"Firmware flashed successfully. Unlock FarmBot to finish initialization."
|
||||
)
|
||||
end
|
||||
|
||||
def finish_flashing(result) do
|
||||
FarmbotCore.Logger.debug(2, "AVR flash returned #{inspect(result)}")
|
||||
end
|
||||
|
||||
defp find_tty() do
|
||||
def find_tty() do
|
||||
case FirmwareTTYDetector.tty() do
|
||||
nil ->
|
||||
{:error,
|
||||
|
@ -83,32 +67,4 @@ defmodule FarmbotOS.SysCalls.FlashFirmware do
|
|||
{:ok, tty}
|
||||
end
|
||||
end
|
||||
|
||||
defp find_reset_fun("express_k10") do
|
||||
FarmbotCore.Logger.debug(3, "Using special express reset function")
|
||||
{:ok, fn -> express_reset_fun() end}
|
||||
end
|
||||
|
||||
defp find_reset_fun(_) do
|
||||
FarmbotCore.Logger.debug(3, "Using default reset function")
|
||||
{:ok, &FarmbotFirmware.NullReset.reset/0}
|
||||
end
|
||||
|
||||
def express_reset_fun() do
|
||||
try do
|
||||
FarmbotCore.Logger.debug(3, "Begin MCU reset")
|
||||
{:ok, gpio} = @gpio.open(19, :output)
|
||||
:ok = @gpio.write(gpio, 0)
|
||||
:ok = @gpio.write(gpio, 1)
|
||||
Process.sleep(1000)
|
||||
:ok = @gpio.write(gpio, 0)
|
||||
FarmbotCore.Logger.debug(3, "Finish MCU Reset")
|
||||
:ok
|
||||
rescue
|
||||
ex ->
|
||||
message = Exception.message(ex)
|
||||
Logger.error("Could not flash express firmware: #{message}")
|
||||
:express_reset_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -98,6 +98,12 @@ defmodule FarmbotOS.SysCalls.Movement do
|
|||
# TODO(Rick): Figure out source of Error: {:ok, "ok"} logs.
|
||||
def handle_movement_error({:ok, _}), do: :ok
|
||||
|
||||
def handle_movement_error(:emergency_lock) do
|
||||
msg = "Cannot execute commands while E-stopped"
|
||||
FarmbotCore.Logger.busy(1, msg)
|
||||
{:error, msg}
|
||||
end
|
||||
|
||||
def handle_movement_error(reason) do
|
||||
msg = "Movement failed. #{inspect(reason)}"
|
||||
FarmbotCore.Logger.error(1, msg)
|
||||
|
|
|
@ -8,8 +8,21 @@ defmodule FarmbotOS.SysCalls.PointLookup do
|
|||
|
||||
def point(kind, id) do
|
||||
case Asset.get_point(id: id) do
|
||||
nil -> {:error, "#{kind} not found"}
|
||||
%{name: name, x: x, y: y, z: z} -> %{name: name, x: x, y: y, z: z}
|
||||
nil ->
|
||||
{:error, "#{kind || "point"} #{id} not found"}
|
||||
|
||||
%{name: name, x: x, y: y, z: z, pointer_type: type} ->
|
||||
%{
|
||||
name: name,
|
||||
resource_type: type,
|
||||
resource_id: id,
|
||||
x: x,
|
||||
y: y,
|
||||
z: z
|
||||
}
|
||||
|
||||
other ->
|
||||
Logger.debug("Point error: Please notify support #{inspect(other)}")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ defmodule FarmbotOS.SysCalls.ResourceUpdate do
|
|||
@moduledoc false
|
||||
|
||||
require Logger
|
||||
require FarmbotCore.Logger
|
||||
|
||||
alias FarmbotCore.{
|
||||
Asset,
|
||||
|
@ -10,9 +11,39 @@ defmodule FarmbotOS.SysCalls.ResourceUpdate do
|
|||
|
||||
alias FarmbotOS.SysCalls.SendMessage
|
||||
|
||||
@point_kinds ~w(Plant GenericPointer)
|
||||
@point_kinds ~w(Plant GenericPointer ToolSlot Weed)
|
||||
@friendly_names %{
|
||||
"gantry_mounted" => "`gantry mounted` property",
|
||||
"mounted_tool_id" => "mounted tool ID",
|
||||
"openfarm_slug" => "Openfarm slug",
|
||||
"ota_hour" => "OTA hour",
|
||||
"plant_stage" => "plant stage",
|
||||
"planted_at" => "planted at time",
|
||||
"pullout_direction" => "pullout direction",
|
||||
"tool_id" => "tool ID",
|
||||
"tz_offset_hrs" => "timezone offset hours",
|
||||
"x" => "X axis",
|
||||
"y" => "Y axis",
|
||||
"z" => "Z axis",
|
||||
"Device" => "device",
|
||||
"Plant" => "plant",
|
||||
"GenericPointer" => "map point",
|
||||
"ToolSlot" => "tool slot",
|
||||
"Weed" => "weed"
|
||||
}
|
||||
|
||||
def notify_user_of_updates(kind, params, id \\ nil) do
|
||||
Enum.map(params, fn {k, v} ->
|
||||
name = @friendly_names[kind] || kind
|
||||
property = @friendly_names["#{k}"] || k
|
||||
msg = "Setting #{name} #{id} #{property} to #{inspect(v)}"
|
||||
FarmbotCore.Logger.info(3, msg)
|
||||
end)
|
||||
end
|
||||
|
||||
def update_resource("Device" = kind, _, params) do
|
||||
notify_user_of_updates(kind, params)
|
||||
|
||||
def resource_update("Device", 0, params) do
|
||||
params
|
||||
|> do_handlebars()
|
||||
|> Asset.update_device!()
|
||||
|
@ -21,12 +52,13 @@ defmodule FarmbotOS.SysCalls.ResourceUpdate do
|
|||
:ok
|
||||
end
|
||||
|
||||
def resource_update(kind, id, params) when kind in @point_kinds do
|
||||
def update_resource(kind, id, params) when kind in @point_kinds do
|
||||
notify_user_of_updates(kind, params, id)
|
||||
params = do_handlebars(params)
|
||||
point_resource_update(kind, id, params)
|
||||
point_update_resource(kind, id, params)
|
||||
end
|
||||
|
||||
def resource_update(kind, id, _params) do
|
||||
def update_resource(kind, id, _params) do
|
||||
{:error,
|
||||
"""
|
||||
Unknown resource: #{kind}.#{id}
|
||||
|
@ -34,18 +66,26 @@ defmodule FarmbotOS.SysCalls.ResourceUpdate do
|
|||
end
|
||||
|
||||
@doc false
|
||||
def point_resource_update(type, id, params) do
|
||||
with %{} = point <- Asset.get_point(pointer_type: type, id: id),
|
||||
def point_update_resource(type, id, params) do
|
||||
with %{} = point <- Asset.get_point(id: id),
|
||||
{:ok, point} <- Asset.update_point(point, params) do
|
||||
_ = Private.mark_dirty!(point)
|
||||
:ok
|
||||
else
|
||||
nil ->
|
||||
{:error,
|
||||
"#{type}.#{id} is not currently synced, so it could not be updated"}
|
||||
msg = "#{type}.#{id} is not currently synced. Please re-sync."
|
||||
FarmbotCore.Logger.error(3, msg)
|
||||
{:error, msg}
|
||||
|
||||
{:error, _changeset} ->
|
||||
{:error, "Failed to update #{type}.#{id}"}
|
||||
msg =
|
||||
"Failed update (#{type}.#{id}): Ensure the data is properly formatted"
|
||||
|
||||
FarmbotCore.Logger.error(3, msg)
|
||||
{:error, msg}
|
||||
|
||||
err ->
|
||||
{:error, "Unknown error. Please notify support. #{inspect(err)}"}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -59,7 +99,7 @@ defmodule FarmbotOS.SysCalls.ResourceUpdate do
|
|||
|
||||
_ ->
|
||||
Logger.warn(
|
||||
"failed to render #{key} => #{value} for resource_update"
|
||||
"failed to render #{key} => #{value} for update_resource"
|
||||
)
|
||||
|
||||
{key, value}
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
defmodule FarmbotOS.Platform.Target.FirmwareReset.GPIO do
|
||||
@moduledoc """
|
||||
Uses GPIO pin 19 to reset the firmware.
|
||||
"""
|
||||
@behaviour FarmbotFirmware.Reset
|
||||
|
||||
use GenServer
|
||||
require Logger
|
||||
|
||||
@impl FarmbotFirmware.Reset
|
||||
def reset(server \\ __MODULE__) do
|
||||
Logger.debug("calling gpio reset/0")
|
||||
GenServer.call(server, :reset)
|
||||
end
|
||||
|
||||
@impl GenServer
|
||||
def init(_args) do
|
||||
Logger.debug("initializing gpio thing for firmware reset")
|
||||
{:ok, gpio} = Circuits.GPIO.open(19, :output)
|
||||
{:ok, %{gpio: gpio}}
|
||||
end
|
||||
|
||||
@impl GenServer
|
||||
def handle_call(:reset, _from, state) do
|
||||
Logger.warn("doing firmware gpio reset")
|
||||
|
||||
with :ok <- Circuits.GPIO.write(state.gpio, 1),
|
||||
:ok <- Circuits.GPIO.write(state.gpio, 0) do
|
||||
{:reply, :ok, state}
|
||||
else
|
||||
error -> {:reply, error, state}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -542,7 +542,7 @@ defmodule FarmbotOS.Platform.Target.NervesHubClient do
|
|||
"ota_hour = #{ota_hour || "null"} timezone = #{timezone || "null"}"
|
||||
)
|
||||
|
||||
true
|
||||
!!auto_update
|
||||
end
|
||||
|
||||
result && !currently_downloading?()
|
||||
|
|
|
@ -17,7 +17,7 @@ defmodule FarmbotOS.SysCalls.PointLookupTest do
|
|||
|
||||
test "failure cases" do
|
||||
err1 = PointLookup.point("GenericPointer", 24)
|
||||
assert {:error, "GenericPointer not found"} == err1
|
||||
assert {:error, "GenericPointer 24 not found"} == err1
|
||||
|
||||
err2 = PointLookup.get_toolslot_for_tool(24)
|
||||
assert {:error, "Could not find point for tool by id: 24"} == err2
|
||||
|
@ -33,7 +33,9 @@ defmodule FarmbotOS.SysCalls.PointLookupTest do
|
|||
name: "test suite III",
|
||||
x: 1.2,
|
||||
y: 3.4,
|
||||
z: 5.6
|
||||
z: 5.6,
|
||||
resource_id: 555,
|
||||
resource_type: "GenericPointer"
|
||||
}
|
||||
|
||||
p = point(expected)
|
||||
|
|
|
@ -14,14 +14,14 @@ defmodule FarmbotOS.SysCalls.ResourceUpdateTest do
|
|||
end)
|
||||
end
|
||||
|
||||
test "resource_update/3 - Device" do
|
||||
test "update_resource/3 - Device" do
|
||||
fake_coords!()
|
||||
params = %{name: "X is {{ x }}"}
|
||||
assert :ok == ResourceUpdate.resource_update("Device", 0, params)
|
||||
assert :ok == ResourceUpdate.update_resource("Device", 0, params)
|
||||
assert "X is 1.2" == FarmbotCore.Asset.device().name
|
||||
end
|
||||
|
||||
test "resource_update/3 - Point" do
|
||||
test "update_resource/3 - Point" do
|
||||
Repo.delete_all(Point)
|
||||
|
||||
%Point{id: 555, pointer_type: "Plant"}
|
||||
|
@ -29,17 +29,17 @@ defmodule FarmbotOS.SysCalls.ResourceUpdateTest do
|
|||
|> Repo.insert!()
|
||||
|
||||
params = %{name: "Updated to {{ x }}"}
|
||||
assert :ok == ResourceUpdate.resource_update("Plant", 555, params)
|
||||
assert :ok == ResourceUpdate.update_resource("Plant", 555, params)
|
||||
next_plant = PointLookup.point("Plant", 555)
|
||||
assert "Updated to " == next_plant.name
|
||||
assert String.contains?(next_plant.name, "Updated to ")
|
||||
|
||||
bad_result1 = ResourceUpdate.resource_update("Plant", 0, params)
|
||||
error = "Plant.0 is not currently synced, so it could not be updated"
|
||||
bad_result1 = ResourceUpdate.update_resource("Plant", 0, params)
|
||||
error = "Plant.0 is not currently synced. Please re-sync."
|
||||
assert {:error, error} == bad_result1
|
||||
end
|
||||
|
||||
test "resource_update/3 - unknown" do
|
||||
{:error, error} = ResourceUpdate.resource_update("Foo", 0, nil)
|
||||
test "update_resource/3 - unknown" do
|
||||
{:error, error} = ResourceUpdate.update_resource("Foo", 0, nil)
|
||||
assert error == "Unknown resource: Foo.0\n"
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue