Skip to content

Commit

Permalink
Merge pull request #1469 from FarmBot/staging
Browse files Browse the repository at this point in the history
v14.8.1 Release
  • Loading branch information
RickCarlino authored Jan 21, 2022
2 parents c60ae18 + a2e714a commit 7940544
Show file tree
Hide file tree
Showing 13 changed files with 160 additions and 153 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

# 14.8.1

* Add `api()` helper to simplify API access in Lua.
* Remove legacy logs relating to device updates.
* Ability to execute raw CeleryScript from Lua.

# 14.8.0

* Upgrade Elixir, Erlang and system deps.
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
14.8.0
14.8.1
16 changes: 16 additions & 0 deletions lib/celery.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,20 @@ defmodule FarmbotOS.Celery do
when k in @entrypoints do
StepRunner.begin(caller, tag, ast)
end

@doc "Lua VM calls CSVM"
def execute_from_lua([input_ast], lua) do
input_ast
|> FarmbotOS.Lua.Util.lua_to_elixir()
|> AST.decode()
|> execute(make_ref(), self())

receive do
{:csvm_done, _, :ok} ->
{[true, nil], lua}

other ->
{[false, inspect(other)], lua}
end
end
end
75 changes: 2 additions & 73 deletions lib/core/asset_workers/device_worker.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
defimpl FarmbotOS.AssetWorker, for: FarmbotOS.Asset.Device do
alias FarmbotOS.{Asset, Asset.Device}
alias FarmbotOS.Celery.AST
alias FarmbotOS.Asset.Device
use GenServer
require FarmbotOS.Logger

Expand All @@ -13,88 +12,18 @@ defimpl FarmbotOS.AssetWorker, for: FarmbotOS.Asset.Device do
end

def init(%Device{} = device) do
send(self(), :check_factory_reset)
{:ok, %Device{} = device, 0}
end

def handle_info(:timeout, %Device{} = device) do
{:noreply, device}
end

def handle_info(:check_factory_reset, %Device{needs_reset: true} = state) do
ast =
AST.Factory.new()
|> AST.Factory.rpc_request("RESET_DEVICE_NOW")
|> AST.Factory.factory_reset("farmbot_os")

:ok = FarmbotOS.Celery.execute(ast, make_ref())

{:noreply, state}
end

def handle_info(:check_factory_reset, state) do
{:noreply, state}
end

def handle_info({:csvm_done, _ref, _}, state) do
{:noreply, state}
end

def handle_cast({:new_data, new_device}, old_device) do
_ = log_changes(new_device, old_device)
send(self(), :check_factory_reset)
def handle_cast({:new_data, new_device}, _old_dev) do
{:noreply, new_device}
end

def log_changes(new_device, old_device) do
interesting_params = [
:ota_hour,
:mounted_tool_id
]

new_interesting_device =
Map.take(new_device, interesting_params) |> MapSet.new()

old_interesting_device =
Map.take(old_device, interesting_params) |> MapSet.new()

difference =
MapSet.difference(new_interesting_device, old_interesting_device)

Enum.each(difference, fn
{:ota_hour, nil} ->
FarmbotOS.Logger.success(
1,
"Farmbot will apply updates as soon as possible"
)

{:ota_hour, hour} ->
FarmbotOS.Logger.success(
1,
"Farmbot will apply updates during the hour of #{hour}:00"
)

{:mounted_tool_id, nil} ->
if old_device.mounted_tool_id do
if tool = Asset.get_tool(id: old_device.mounted_tool_id) do
FarmbotOS.Logger.info(2, "Dismounted the #{tool.name}")
else
FarmbotOS.Logger.info(2, "Dismounted unknown tool")
end
else
# no previously mounted tool
:ok
end

{:mounted_tool_id, id} ->
if tool = Asset.get_tool(id: id) do
FarmbotOS.Logger.info(2, "Mounted the #{tool.name}")
else
FarmbotOS.Logger.info(2, "Mounted unknown tool")
end

{_key, _value} ->
:noop
end)
end
end
2 changes: 0 additions & 2 deletions lib/ext/mqtt/rpc_handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ defmodule FarmbotOS.MQTT.RPCHandler do
end

def handle_info({:csvm_done, ref, :ok}, state) do
Logger.info("CeleryScript OK")

case state.rpc_requests[ref] do
%{label: label, timer: timer} ->
FarmbotOS.Time.cancel_timer(timer)
Expand Down
56 changes: 23 additions & 33 deletions lib/os/lua.ex
Original file line number Diff line number Diff line change
Expand Up @@ -97,32 +97,29 @@ defmodule FarmbotOS.Lua do

def builtins() do
%{
base64: [
{:decode, &DataManipulation.b64_decode/2},
{:encode, &DataManipulation.b64_encode/2}
],
json: [
{:decode, &DataManipulation.json_decode/2},
{:encode, &DataManipulation.json_encode/2}
],
uart: [
{:open, &FarmbotOS.Firmware.LuaUART.open/2},
{:list, &FarmbotOS.Firmware.LuaUART.list/2}
],
# This flag can be compared agaist the last e-stop timestamp
# to abort script execution (if E-Stop was called at any
# point during Lua execution).
__LUA_START_TIME: FarmbotOS.Time.system_time_ms(),
__SERVER_PATH:
FarmbotOS.Config.get_config_value(:string, "authorization", "server"),
auth_token: &Info.auth_token/2,
api: &DataManipulation.api/2,
base64: [
{:decode, &DataManipulation.b64_decode/2},
{:encode, &DataManipulation.b64_encode/2}
],
calibrate_camera: execute_script("camera-calibration"),
check_position: &Firmware.check_position/2,
coordinate: &Firmware.coordinate/2,
cs_eval: &FarmbotOS.Celery.execute_from_lua/2,
current_hour: &Info.current_hour/2,
current_minute: &Info.current_minute/2,
current_month: &Info.current_month/2,
current_second: &Info.current_second/2,
detect_weeds: execute_script("plant-detection"),
emergency_lock: &Firmware.emergency_lock/2,
emergency_unlock: &Firmware.emergency_unlock/2,
soft_stop: &Firmware.soft_stop/2,
env: &DataManipulation.env/2,
fbos_version: &Info.fbos_version/2,
find_axis_length: &Firmware.calibrate/2,
Expand All @@ -132,45 +129,38 @@ defmodule FarmbotOS.Lua do
get_device: &DataManipulation.get_device/2,
get_fbos_config: &DataManipulation.get_fbos_config/2,
get_firmware_config: &DataManipulation.get_firmware_config/2,
get_job_progress: &Info.get_job_progress/2,
get_position: &Firmware.get_position/2,
go_to_home: &Firmware.go_to_home/2,
http: &DataManipulation.http/2,
inspect: &DataManipulation.json_encode/2,
json: [
{:decode, &DataManipulation.json_decode/2},
{:encode, &DataManipulation.json_encode/2}
],
measure_soil_height: execute_script("Measure Soil Height"),
move_absolute: &Firmware.move_absolute/2,
new_sensor_reading: &DataManipulation.new_sensor_reading/2,
photo_grid: &DataManipulation.photo_grid/2,
read_pin: &Firmware.read_pin/2,
read_status: &Info.read_status/2,
send_message: &Info.send_message/2,
set_job_progress: &Info.set_job_progress/2,
set_pin_io_mode: &Firmware.set_pin_io_mode/2,
soft_stop: &Firmware.soft_stop/2,
soil_height: &DataManipulation.soil_height/2,
take_photo_raw: &DataManipulation.take_photo_raw/2,
take_photo: execute_script("take-photo"),
calibrate_camera: execute_script("camera-calibration"),
detect_weeds: execute_script("plant-detection"),
measure_soil_height: execute_script("Measure Soil Height"),
uart: [
{:open, &FarmbotOS.Firmware.LuaUART.open/2},
{:list, &FarmbotOS.Firmware.LuaUART.list/2}
],
update_device: &DataManipulation.update_device/2,
update_fbos_config: &DataManipulation.update_fbos_config/2,
update_firmware_config: &DataManipulation.update_firmware_config/2,
wait: &Wait.wait/2,
watch_pin: &PinWatcher.new/2,
write_pin: &Firmware.write_pin/2,
get_job_progress: fn [name], lua ->
job = Map.get(FarmbotOS.BotState.fetch().jobs, name)
{[job], lua}
end,
set_job_progress: fn [name, args], lua ->
map = FarmbotOS.Lua.Util.lua_to_elixir(args)

job = %FarmbotOS.BotState.JobProgress.Percent{
type: Map.get(map, "type") || "unknown",
status: Map.get(map, "status") || "working",
percent: Map.get(map, "percent") || 0
}

FarmbotOS.BotState.set_job_progress(name, job)
{[], lua}
end
write_pin: &Firmware.write_pin/2
}
end
end
17 changes: 12 additions & 5 deletions lib/os/lua/data_manipulation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -216,11 +216,18 @@ defmodule FarmbotOS.Lua.DataManipulation do
end
end

def photo_grid(_, lua) do
lua_code = File.read!("#{:code.priv_dir(:farmbot)}/lua/photo_grid.lua")

with {:ok, result} <- Lua.raw_eval(lua, lua_code) do
{result, lua}
def photo_grid(args, lua), do: lua_extension(args, lua, "photo_grid")
def api(args, lua), do: lua_extension(args, lua, "api")

defp lua_extension(args, lua, filename) do
lua_code = File.read!("#{:code.priv_dir(:farmbot)}/lua/#{filename}.lua")

with {:ok, [result]} <- Lua.raw_eval(lua, lua_code) do
if is_function(result) do
{result.(args), lua}
else
{[result], lua}
end
else
error ->
{[nil, "ERROR: #{inspect(error)}"], lua}
Expand Down
18 changes: 18 additions & 0 deletions lib/os/lua/info.ex
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,24 @@ defmodule FarmbotOS.Lua.Info do
{[token], lua}
end

def get_job_progress([name], lua) do
job = Map.get(FarmbotOS.BotState.fetch().jobs, name)
{[job], lua}
end

def set_job_progress([name, args], lua) do
map = FarmbotOS.Lua.Util.lua_to_elixir(args)

job = %FarmbotOS.BotState.JobProgress.Percent{
type: Map.get(map, "type") || "unknown",
status: Map.get(map, "status") || "working",
percent: Map.get(map, "percent") || 0
}

FarmbotOS.BotState.set_job_progress(name, job)
{[], lua}
end

defp do_send_message(kind, message, channels, lua) do
result = SysCallGlue.send_message(kind, "#{message}", channels)

Expand Down
37 changes: 37 additions & 0 deletions priv/lua/api.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
function merge(t1, t2)
for k, v in ipairs(t2) do table.insert(t1, v) end
return t1
end

return function(input)
params = {
headers = {
Authorization = ("bearer " .. auth_token()),
Accept = "application/json"
}
}
params.method = input.method or "GET"
if input.url then
params.url = __SERVER_PATH .. input.url
else
send_message("error", "Missing URL in HTTP request")
return
end

if input.body then params.body = json.encode(input.body) end

if input.headers then merge(params.headers, input.headers) end

local result, error = http(params)
if error then
send_message("error", "NETWORK ERROR: " .. inspect(error))
return
else
if result.status > 299 then
send_message("error", "HTTP ERROR: " .. inspect(result))
return
else
return json.decode(result.body)
end
end
end
37 changes: 5 additions & 32 deletions test/asset_workers/device_worker_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,9 @@ defmodule FarmbotOS.DeviceWorkerTest do
use Mimic

alias Farmbot.TestSupport.AssetFixtures
alias FarmbotOS.Celery.SysCallGlue.Stubs
alias FarmbotOS.Asset.Device
alias FarmbotOS.AssetWorker

@im_so_sorry 300

setup :set_mimic_global
setup :verify_on_exit!

Expand All @@ -18,36 +15,12 @@ defmodule FarmbotOS.DeviceWorkerTest do
dev
end

test "triggering of factory reset during init" do
expect(Stubs, :factory_reset, fn _ ->
:ok
end)

dev = fresh_device()
{:ok, _pid} = AssetWorker.start_link(dev, [])
Process.sleep(@im_so_sorry)
end

test "DO trigger factory reset during update" do
dev = fresh_device(false)
{:ok, pid} = AssetWorker.start_link(dev, [])

expect(Stubs, :factory_reset, 1, fn _pkg ->
:ok
end)

GenServer.cast(pid, {:new_data, %{dev | needs_reset: true}})
Process.sleep(@im_so_sorry)
end

test "DO NOT trigger factory reset during update" do
test "initializes and runs noops" do
dev = fresh_device(false)
{:ok, _} = AssetWorker.start_link(dev, [])

stub(Stubs, :factory_reset, fn _pkg ->
nooo = "SHOULD NOT HAPPEN!"
flunk(nooo)
raise nooo
end)
# Test noop functions:
worker = FarmbotOS.AssetWorker.FarmbotOS.Asset.Device
{:noreply, %{}} = worker.handle_info({:csvm_done, make_ref(), :ok}, %{})
{:noreply, %{}} = worker.handle_cast({:new_data, %{}}, %{})
end
end
Loading

0 comments on commit 7940544

Please sign in to comment.