Botstate refactor (#645)
* Refactor PinBindings * Refactor logging to not require Farmbot.Registry * Fix AMQP bot_state_transport and log_transport * Update to use floats everywhere * Refactor Farmbot.Firmware * Write Firmware Tests * dipping into FarmbotOS finally * Cleanup peripheral_worker error message * Implement remaining CeleryScript RPCs * Refactor job progress * Image Upload status notifications updates * Fix compiler warnings and things * Update Firmware submodule * Fix FarmbotEXT testspull/974/head
parent
3b8f8d591f
commit
f2b8abd692
9
Makefile
9
Makefile
|
@ -1,4 +1,4 @@
|
|||
.PHONY: all clean
|
||||
.PHONY: all clean format
|
||||
.DEFAULT_GOAL: all
|
||||
|
||||
MIX_ENV := $(MIX_ENV)
|
||||
|
@ -15,6 +15,7 @@ endif
|
|||
PROJECTS := farmbot_celery_script \
|
||||
farmbot_core \
|
||||
farmbot_ext \
|
||||
farmbot_firmware \
|
||||
farmbot_os
|
||||
|
||||
all: help
|
||||
|
@ -39,3 +40,9 @@ clean: clean_other_branch
|
|||
rm -rf $$project/deps ; \
|
||||
rm -rf $$project/priv/*.so ; \
|
||||
done
|
||||
|
||||
format:
|
||||
@for project in $(PROJECTS) ; do \
|
||||
echo formatting $$project ; \
|
||||
cd $$project && mix format && cd .. ; \
|
||||
done
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
[
|
||||
inputs: ["*.{ex,exs}", "{config,priv,lib,test}/**/*.{ex,exs}"],
|
||||
inputs: ["*.{ex,exs}", "{config,priv,lib,test}/**/*.{ex,exs}"]
|
||||
]
|
||||
|
|
|
@ -26,8 +26,7 @@ defmodule Farmbot.CeleryScript.RunTime do
|
|||
:find_home,
|
||||
:toggle_pin,
|
||||
:zero,
|
||||
:calibrate,
|
||||
|
||||
:calibrate
|
||||
]
|
||||
|
||||
@kinds_aloud_while_locked [
|
||||
|
@ -72,6 +71,7 @@ defmodule Farmbot.CeleryScript.RunTime do
|
|||
|
||||
defmodule Monitor do
|
||||
defstruct [:pid, :ref, :index]
|
||||
|
||||
def new(pid, index) do
|
||||
ref = Process.monitor(pid)
|
||||
%Monitor{ref: ref, pid: pid, index: index}
|
||||
|
@ -235,16 +235,20 @@ defmodule Farmbot.CeleryScript.RunTime do
|
|||
cleanup = fn proc, state ->
|
||||
ProcStorage.delete(state.proc_storage, id)
|
||||
|
||||
state = case Enum.find(state.monitors, fn({_, %{index: index}}) -> index == id end) do
|
||||
{pid, mon} ->
|
||||
Process.demonitor(mon.ref)
|
||||
%{state | monitors: Map.delete(state.monitors, pid)}
|
||||
_ -> state
|
||||
end
|
||||
state =
|
||||
case Enum.find(state.monitors, fn {_, %{index: index}} -> index == id end) do
|
||||
{pid, mon} ->
|
||||
Process.demonitor(mon.ref)
|
||||
%{state | monitors: Map.delete(state.monitors, pid)}
|
||||
|
||||
state = if proc.ref == state.fw_proc,
|
||||
do: %{state | fw_proc: nil},
|
||||
else: state
|
||||
_ ->
|
||||
state
|
||||
end
|
||||
|
||||
state =
|
||||
if proc.ref == state.fw_proc,
|
||||
do: %{state | fw_proc: nil},
|
||||
else: state
|
||||
|
||||
{:reply, proc, state}
|
||||
end
|
||||
|
@ -275,15 +279,17 @@ defmodule Farmbot.CeleryScript.RunTime do
|
|||
end
|
||||
|
||||
mon = state.monitors[pid]
|
||||
state = if mon do
|
||||
case ProcStorage.lookup(state.proc_storage, mon.index) do
|
||||
%FarmProc{status: :crashed} = proc -> cleanup.(proc, mon.index, state)
|
||||
%FarmProc{status: :done} = proc -> cleanup.(proc, mon.index, state)
|
||||
_ -> state
|
||||
|
||||
state =
|
||||
if mon do
|
||||
case ProcStorage.lookup(state.proc_storage, mon.index) do
|
||||
%FarmProc{status: :crashed} = proc -> cleanup.(proc, mon.index, state)
|
||||
%FarmProc{status: :done} = proc -> cleanup.(proc, mon.index, state)
|
||||
_ -> state
|
||||
end
|
||||
else
|
||||
state
|
||||
end
|
||||
else
|
||||
state
|
||||
end
|
||||
|
||||
{:noreply, %{state | monitors: Map.delete(state.monitors, pid)}}
|
||||
end
|
||||
|
|
|
@ -128,8 +128,7 @@ defmodule Farmbot.CeleryScript.RunTime.FarmProc do
|
|||
kind = get_kind(farm_proc, pc_ptr)
|
||||
# TODO Connor 07-31-2018: why do i have to load the module here?
|
||||
available? =
|
||||
Code.ensure_loaded?(InstructionSet) and
|
||||
function_exported?(InstructionSet, kind, 1)
|
||||
Code.ensure_loaded?(InstructionSet) and function_exported?(InstructionSet, kind, 1)
|
||||
|
||||
unless available? do
|
||||
exception(farm_proc, "No implementation for: #{kind}")
|
||||
|
@ -216,8 +215,7 @@ defmodule Farmbot.CeleryScript.RunTime.FarmProc do
|
|||
) do
|
||||
cell = get_cell_by_address(farm_proc, location)
|
||||
|
||||
cell[field] ||
|
||||
exception(farm_proc, "no field called: #{field} at #{inspect(location)}")
|
||||
cell[field] || exception(farm_proc, "no field called: #{field} at #{inspect(location)}")
|
||||
end
|
||||
|
||||
@spec get_cell_attr_as_pointer(FarmProc.t(), Pointer.t(), atom) :: Pointer.t()
|
||||
|
@ -271,7 +269,6 @@ defmodule Farmbot.CeleryScript.RunTime.FarmProc do
|
|||
%FarmProc{} = farm_proc,
|
||||
%Pointer{page_address: page, heap_address: %Address{} = ha}
|
||||
) do
|
||||
get_heap_by_page_index(farm_proc, page)[ha] ||
|
||||
exception(farm_proc, "bad address")
|
||||
get_heap_by_page_index(farm_proc, page)[ha] || exception(farm_proc, "bad address")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -112,19 +112,25 @@ defmodule Farmbot.CeleryScript.RunTime.InstructionSet do
|
|||
heap = get_heap_by_page_index(farm_proc, pc.page_address)
|
||||
data = AST.unslice(heap, pc.heap_address)
|
||||
|
||||
data = case data.args.location do
|
||||
%AST{kind: :identifier} ->
|
||||
location = Resolver.resolve(farm_proc, pc, data.args.location.args.label)
|
||||
%{data | args: %{data.args | location: location}}
|
||||
_ -> data
|
||||
end
|
||||
data =
|
||||
case data.args.location do
|
||||
%AST{kind: :identifier} ->
|
||||
location = Resolver.resolve(farm_proc, pc, data.args.location.args.label)
|
||||
%{data | args: %{data.args | location: location}}
|
||||
|
||||
data = case data.args.offset do
|
||||
%AST{kind: :identifier} ->
|
||||
offset = Resolver.resolve(farm_proc, pc, data.args.offset.args.label)
|
||||
%{data | args: %{data.args | offset: offset}}
|
||||
_ -> data
|
||||
end
|
||||
_ ->
|
||||
data
|
||||
end
|
||||
|
||||
data =
|
||||
case data.args.offset do
|
||||
%AST{kind: :identifier} ->
|
||||
offset = Resolver.resolve(farm_proc, pc, data.args.offset.args.label)
|
||||
%{data | args: %{data.args | offset: offset}}
|
||||
|
||||
_ ->
|
||||
data
|
||||
end
|
||||
|
||||
case farm_proc.io_result do
|
||||
# If we need to lookup a coordinate, do that.
|
||||
|
@ -303,10 +309,12 @@ defmodule Farmbot.CeleryScript.RunTime.InstructionSet do
|
|||
|
||||
{:error, reason} ->
|
||||
crash(farm_proc, reason)
|
||||
|
||||
:ok ->
|
||||
farm_proc
|
||||
|> clear_io_result()
|
||||
|> set_pc_ptr(next_ptr)
|
||||
|
||||
_data ->
|
||||
exception(farm_proc, "Bad execute_script implementation.")
|
||||
end
|
||||
|
@ -327,6 +335,7 @@ defmodule Farmbot.CeleryScript.RunTime.InstructionSet do
|
|||
@spec return(FarmProc.t()) :: FarmProc.t()
|
||||
defp return(%FarmProc{} = farm_proc) do
|
||||
{value, farm_proc} = pop_rs(farm_proc)
|
||||
|
||||
farm_proc
|
||||
|> set_pc_ptr(value)
|
||||
|> set_status(:ok)
|
||||
|
@ -336,6 +345,7 @@ defmodule Farmbot.CeleryScript.RunTime.InstructionSet do
|
|||
defp next(%FarmProc{} = farm_proc) do
|
||||
current_pc = get_pc_ptr(farm_proc)
|
||||
next_ptr = get_next_address(farm_proc, current_pc)
|
||||
|
||||
farm_proc
|
||||
|> set_pc_ptr(next_ptr)
|
||||
|> set_status(:ok)
|
||||
|
@ -349,6 +359,7 @@ defmodule Farmbot.CeleryScript.RunTime.InstructionSet do
|
|||
|
||||
is_null_address? = is_null_address?(addr)
|
||||
return_stack_is_empty? = farm_proc.rs == []
|
||||
|
||||
cond do
|
||||
is_null_address? && return_stack_is_empty? -> set_status(farm_proc, :done)
|
||||
is_null_address? -> return(farm_proc)
|
||||
|
|
|
@ -2,7 +2,7 @@ defmodule Farmbot.CeleryScript.RunTime.ProcStorage do
|
|||
@moduledoc """
|
||||
Process wrapper around CircularList
|
||||
"""
|
||||
|
||||
|
||||
alias Farmbot.CeleryScript.RunTime.FarmProc
|
||||
|
||||
@opaque proc_storage :: pid
|
||||
|
|
|
@ -41,8 +41,7 @@ defmodule Farmbot.CeleryScript.RunTime.Resolver do
|
|||
result = do_resolve(kind, farm_proc, pointer, label)
|
||||
%Address{} = page = pointer.page_address
|
||||
|
||||
%Pointer{} =
|
||||
new_pointer = Pointer.new(page, FarmProc.get_parent(farm_proc, pointer))
|
||||
%Pointer{} = new_pointer = Pointer.new(page, FarmProc.get_parent(farm_proc, pointer))
|
||||
|
||||
if is_nil(result) do
|
||||
search_tree(farm_proc, new_pointer, label)
|
||||
|
@ -52,16 +51,14 @@ defmodule Farmbot.CeleryScript.RunTime.Resolver do
|
|||
else
|
||||
%Address{} = page = pointer.page_address
|
||||
|
||||
%Pointer{} =
|
||||
new_pointer = Pointer.new(page, FarmProc.get_parent(farm_proc, pointer))
|
||||
%Pointer{} = new_pointer = Pointer.new(page, FarmProc.get_parent(farm_proc, pointer))
|
||||
|
||||
search_tree(farm_proc, new_pointer, label)
|
||||
end
|
||||
end
|
||||
|
||||
def do_resolve(:sequence, farm_proc, pointer, label) do
|
||||
locals_ptr =
|
||||
FarmProc.get_cell_attr_as_pointer(farm_proc, pointer, :__locals)
|
||||
locals_ptr = FarmProc.get_cell_attr_as_pointer(farm_proc, pointer, :__locals)
|
||||
|
||||
ast =
|
||||
AST.unslice(
|
||||
|
|
|
@ -179,8 +179,7 @@ defmodule Farmbot.CeleryScript.AST.SlicerTest do
|
|||
|
||||
assert heap[addr(2)][@kind] == :"Elixir.Farmbot.CeleryScript.AST.Node.ROOT[0]"
|
||||
|
||||
assert heap[heap[addr(2)][@body]][@kind] ==
|
||||
:"Elixir.Farmbot.CeleryScript.AST.Node.ROOT[0][0]"
|
||||
assert heap[heap[addr(2)][@body]][@kind] == :"Elixir.Farmbot.CeleryScript.AST.Node.ROOT[0][0]"
|
||||
|
||||
assert heap[heap[addr(2)][@next]][@kind] == :"Elixir.Farmbot.CeleryScript.AST.Node.ROOT[1]"
|
||||
|
||||
|
|
|
@ -64,8 +64,7 @@ defmodule Farmbot.CeleryScript.RunTime.FarmProcTest do
|
|||
step2 = FarmProc.step(step1)
|
||||
assert FarmProc.get_status(step2) == :crashed
|
||||
|
||||
assert FarmProc.get_pc_ptr(step2) ==
|
||||
Pointer.null(FarmProc.get_zero_page(step1))
|
||||
assert FarmProc.get_pc_ptr(step2) == Pointer.null(FarmProc.get_zero_page(step1))
|
||||
end
|
||||
|
||||
test "io functions bad return values raise Farmbot.CeleryScript.RunTime.Error exception" do
|
||||
|
@ -113,9 +112,7 @@ defmodule Farmbot.CeleryScript.RunTime.FarmProcTest do
|
|||
Farmbot.CeleryScript.RunTime.TestSupport.Fixtures.heap()
|
||||
)
|
||||
|
||||
assert FarmProc.is_null_address?(
|
||||
Pointer.null(FarmProc.get_zero_page(farm_proc))
|
||||
)
|
||||
assert FarmProc.is_null_address?(Pointer.null(FarmProc.get_zero_page(farm_proc)))
|
||||
|
||||
assert FarmProc.is_null_address?(Address.null())
|
||||
assert FarmProc.is_null_address?(Pointer.new(addr(0), addr(0)))
|
||||
|
@ -396,8 +393,7 @@ defmodule Farmbot.CeleryScript.RunTime.FarmProcTest do
|
|||
step7 = FarmProc.step(step6)
|
||||
assert FarmProc.get_return_stack(step7) == []
|
||||
|
||||
assert FarmProc.get_pc_ptr(step7) ==
|
||||
Pointer.null(FarmProc.get_zero_page(step7))
|
||||
assert FarmProc.get_pc_ptr(step7) == Pointer.null(FarmProc.get_zero_page(step7))
|
||||
end
|
||||
|
||||
test "raises when trying to step thru a crashed proc" do
|
||||
|
@ -479,30 +475,26 @@ defmodule Farmbot.CeleryScript.RunTime.FarmProcTest do
|
|||
# step into the sequence.
|
||||
next = FarmProc.step(farm_proc)
|
||||
|
||||
assert FarmProc.get_pc_ptr(next) ==
|
||||
Pointer.null(FarmProc.get_zero_page(next))
|
||||
assert FarmProc.get_pc_ptr(next) == Pointer.null(FarmProc.get_zero_page(next))
|
||||
|
||||
assert FarmProc.get_return_stack(next) == []
|
||||
|
||||
# Each following step should still be stopped/paused.
|
||||
next1 = FarmProc.step(next)
|
||||
|
||||
assert FarmProc.get_pc_ptr(next1) ==
|
||||
Pointer.null(FarmProc.get_zero_page(next1))
|
||||
assert FarmProc.get_pc_ptr(next1) == Pointer.null(FarmProc.get_zero_page(next1))
|
||||
|
||||
assert FarmProc.get_return_stack(next1) == []
|
||||
|
||||
next2 = FarmProc.step(next1)
|
||||
|
||||
assert FarmProc.get_pc_ptr(next2) ==
|
||||
Pointer.null(FarmProc.get_zero_page(next2))
|
||||
assert FarmProc.get_pc_ptr(next2) == Pointer.null(FarmProc.get_zero_page(next2))
|
||||
|
||||
assert FarmProc.get_return_stack(next2) == []
|
||||
|
||||
next3 = FarmProc.step(next2)
|
||||
|
||||
assert FarmProc.get_pc_ptr(next3) ==
|
||||
Pointer.null(FarmProc.get_zero_page(next3))
|
||||
assert FarmProc.get_pc_ptr(next3) == Pointer.null(FarmProc.get_zero_page(next3))
|
||||
|
||||
assert FarmProc.get_return_stack(next3) == []
|
||||
end
|
||||
|
|
|
@ -19,8 +19,9 @@ defmodule Farmbot.CeleryScript.RunTime.FarmwareTest do
|
|||
|
||||
test "farmware" do
|
||||
pid = self()
|
||||
{:ok, agent} = Agent.start_link fn() -> 0 end
|
||||
fun = fn(ast) ->
|
||||
{:ok, agent} = Agent.start_link(fn -> 0 end)
|
||||
|
||||
fun = fn ast ->
|
||||
case ast.kind do
|
||||
:execute_script ->
|
||||
case Agent.get_and_update(agent, fn state -> {state, state + 1} end) do
|
||||
|
@ -28,20 +29,24 @@ defmodule Farmbot.CeleryScript.RunTime.FarmwareTest do
|
|||
1 -> {:ok, ast(:wait, %{milliseconds: 456})}
|
||||
2 -> :ok
|
||||
end
|
||||
|
||||
:wait ->
|
||||
send(pid, ast)
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
heap = @rpc_request
|
||||
|> AST.decode()
|
||||
|> AST.slice()
|
||||
heap =
|
||||
@rpc_request
|
||||
|> AST.decode()
|
||||
|> AST.slice()
|
||||
|
||||
%FarmProc{} = step0 = FarmProc.new(fun, addr(-1), heap)
|
||||
complete = Enum.reduce(0..200, step0, fn(_, proc) ->
|
||||
%FarmProc{} = wait_for_io(proc)
|
||||
end)
|
||||
|
||||
complete =
|
||||
Enum.reduce(0..200, step0, fn _, proc ->
|
||||
%FarmProc{} = wait_for_io(proc)
|
||||
end)
|
||||
|
||||
assert complete.status == :done
|
||||
|
||||
|
|
|
@ -26,8 +26,7 @@ defmodule Farmbot.CeleryScript.RunTime.ResolverTest do
|
|||
end
|
||||
|
||||
test "variable resolution" do
|
||||
outer_json =
|
||||
fetch_fixture("fixture/outer_sequence.json") |> AST.Slicer.run()
|
||||
outer_json = fetch_fixture("fixture/outer_sequence.json") |> AST.Slicer.run()
|
||||
|
||||
farm_proc0 = FarmProc.new(io_fun(self()), addr(0), outer_json)
|
||||
|
||||
|
@ -41,7 +40,7 @@ defmodule Farmbot.CeleryScript.RunTime.ResolverTest do
|
|||
assert_received %AST{
|
||||
kind: :move_absolute,
|
||||
args: %{
|
||||
location: %AST{kind: :point, args: %{pointer_id: 456, pointer_type: "Plant"} },
|
||||
location: %AST{kind: :point, args: %{pointer_id: 456, pointer_type: "Plant"}},
|
||||
offset: %AST{kind: :coordinate, args: %{x: 0, y: 0, z: 0}},
|
||||
speed: 100
|
||||
}
|
||||
|
@ -53,17 +52,17 @@ defmodule Farmbot.CeleryScript.RunTime.ResolverTest do
|
|||
location: %AST{kind: :point, args: %{pointer_id: 123, pointer_type: "GenericPointer"}},
|
||||
offset: %AST{kind: :coordinate, args: %{x: 0, y: 0, z: 0}},
|
||||
speed: 100
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
assert_received %AST{
|
||||
kind: :wait,
|
||||
args: %{milliseconds: 1000},
|
||||
args: %{milliseconds: 1000}
|
||||
}
|
||||
|
||||
assert_received %AST{
|
||||
kind: :wait,
|
||||
args: %{milliseconds: 1050},
|
||||
args: %{milliseconds: 1050}
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -88,8 +88,7 @@ defmodule Farmbot.CeleryScript.RunTimeTest do
|
|||
RunTime.rpc_request(farmbot_celery_script, lock_ast, io_fun)
|
||||
assert_receive :emergency_lock
|
||||
|
||||
unlock_ast =
|
||||
ast(:rpc_request, %{label: name}, [ast(:emergency_unlock, %{})])
|
||||
unlock_ast = ast(:rpc_request, %{label: name}, [ast(:emergency_unlock, %{})])
|
||||
|
||||
RunTime.rpc_request(farmbot_celery_script, unlock_ast, io_fun)
|
||||
assert_receive :emergency_unlock
|
||||
|
@ -117,11 +116,9 @@ defmodule Farmbot.CeleryScript.RunTimeTest do
|
|||
label1 = "one"
|
||||
label2 = "two"
|
||||
|
||||
ast1 =
|
||||
ast(:rpc_request, %{label: label1}, [ast(:wait, %{milliseconds: to})])
|
||||
ast1 = ast(:rpc_request, %{label: label1}, [ast(:wait, %{milliseconds: to})])
|
||||
|
||||
ast2 =
|
||||
ast(:rpc_request, %{label: label2}, [ast(:wait, %{milliseconds: to})])
|
||||
ast2 = ast(:rpc_request, %{label: label2}, [ast(:wait, %{milliseconds: to})])
|
||||
|
||||
cb = fn %{kind: :rpc_ok} = rpc_ok -> send(pid, rpc_ok) end
|
||||
spawn_link(RunTime, :rpc_request, [farmbot_celery_script, ast1, cb])
|
||||
|
@ -211,8 +208,7 @@ defmodule Farmbot.CeleryScript.RunTimeTest do
|
|||
{:ok, farmbot_celery_script} = RunTime.start_link(opts, name)
|
||||
ok_ast = ast(:sequence, %{id: 100}, [ast(:wait, %{milliseconds: 100})])
|
||||
|
||||
err_ast =
|
||||
ast(:sequence, %{id: 101}, [ast(:send_message, %{message: "???"})])
|
||||
err_ast = ast(:sequence, %{id: 101}, [ast(:send_message, %{message: "???"})])
|
||||
|
||||
cb = fn results -> send(pid, results) end
|
||||
vm_pid = RunTime.sequence(farmbot_celery_script, ok_ast, 100, cb)
|
||||
|
|
|
@ -3,12 +3,16 @@
|
|||
inputs: [
|
||||
"*.{ex,exs}",
|
||||
"{config,priv,test}/**/*.{ex,exs}",
|
||||
"lib/bot_state/**/*.{ex,exs}",
|
||||
"lib/bot_state_ng/**/*.{ex,exs}",
|
||||
"lib/asset/**/*.{ex,exs}",
|
||||
"lib/asset_workers/**/*.{ex,exs}",
|
||||
"lib/farmware_runtime/**/*.{ex,exs}",
|
||||
"lib/celery_script/**/*.{ex,exs}",
|
||||
"lib/farmware_runtime.ex",
|
||||
"lib/asset*.ex"
|
||||
"lib/firmware/**/*.{ex,exs}",
|
||||
"lib/asset*.ex",
|
||||
"lib/log_storage/**/*.{ex,exs}"
|
||||
],
|
||||
subdirectories: ["priv/*/migrations"]
|
||||
]
|
||||
|
|
|
@ -1,28 +1,27 @@
|
|||
use Mix.Config
|
||||
|
||||
config :farmbot_core, Farmbot.AssetWorker.Farmbot.Asset.FarmEvent,
|
||||
checkup_time_ms: 10_000
|
||||
config :farmbot_core, Farmbot.AssetWorker.Farmbot.Asset.FarmEvent, checkup_time_ms: 10_000
|
||||
|
||||
config :farmbot_core, Farmbot.AssetWorker.Farmbot.Asset.FarmwareInstallation,
|
||||
error_retry_time_ms: 30_000,
|
||||
install_dir: "/tmp/farmware"
|
||||
|
||||
config :farmbot_core, Farmbot.AssetMonitor,
|
||||
checkup_time_ms: 30_000
|
||||
config :farmbot_core, Elixir.Farmbot.AssetWorker.Farmbot.Asset.PinBinding,
|
||||
gpio_handler: Farmbot.PinBindingWorker.StubGPIOHandler,
|
||||
error_retry_time_ms: 30_000
|
||||
|
||||
config :farmbot_core, Farmbot.AssetMonitor, checkup_time_ms: 30_000
|
||||
|
||||
config :farmbot_core,
|
||||
expected_fw_versions: ["6.4.2.F", "6.4.2.R", "6.4.2.G"],
|
||||
default_firmware_io_logs: false,
|
||||
default_server: "https://my.farm.bot",
|
||||
default_currently_on_beta:
|
||||
String.contains?(to_string(:os.cmd('git rev-parse --abbrev-ref HEAD')), "beta"),
|
||||
firmware_io_logs: false,
|
||||
farm_event_debug_log: false
|
||||
String.contains?(to_string(:os.cmd('git rev-parse --abbrev-ref HEAD')), "beta")
|
||||
|
||||
# Configure Farmbot Behaviours.
|
||||
config :farmbot_core, :behaviour,
|
||||
firmware_handler: Farmbot.Firmware.StubHandler,
|
||||
leds_handler: Farmbot.Leds.StubHandler,
|
||||
pin_binding_handler: Farmbot.PinBinding.StubHandler,
|
||||
celery_script_io_layer: Farmbot.Core.CeleryScript.StubIOLayer,
|
||||
json_parser: Farmbot.JSON.JasonParser
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use Mix.Config
|
||||
|
||||
# config :logger,
|
||||
# handle_otp_reports: true,
|
||||
# handle_sasl_reports: true
|
||||
config :logger,
|
||||
handle_otp_reports: true,
|
||||
handle_sasl_reports: true
|
||||
|
|
|
@ -2,11 +2,13 @@ defmodule Farmbot.Asset do
|
|||
alias Farmbot.Asset.{
|
||||
Repo,
|
||||
Device,
|
||||
DiagnosticDump,
|
||||
FarmwareEnv,
|
||||
FarmwareInstallation,
|
||||
FarmEvent,
|
||||
FbosConfig,
|
||||
FirmwareConfig,
|
||||
Peripheral,
|
||||
PinBinding,
|
||||
Regimen,
|
||||
PersistentRegimen,
|
||||
|
@ -106,6 +108,10 @@ defmodule Farmbot.Asset do
|
|||
Repo.get_by!(Sequence, params)
|
||||
end
|
||||
|
||||
def get_sequence(params) do
|
||||
Repo.get_by(Sequence, params)
|
||||
end
|
||||
|
||||
## End Sequence
|
||||
|
||||
## Begin FarmwareInstallation
|
||||
|
@ -116,9 +122,42 @@ defmodule Farmbot.Asset do
|
|||
|> Enum.find(fn %{package: pkg} -> pkg == package end)
|
||||
end
|
||||
|
||||
## End FarmwareInstallation
|
||||
|
||||
## Begin FarmwareEnv
|
||||
|
||||
def list_farmware_env() do
|
||||
Repo.all(FarmwareEnv)
|
||||
end
|
||||
|
||||
## End FarmwareInstallation
|
||||
def new_farmware_env(params) do
|
||||
fwe =
|
||||
if params["key"] || params[:key] do
|
||||
Repo.get_by(FarmwareEnv, key: params["key"] || params[:key])
|
||||
else
|
||||
%FarmwareEnv{}
|
||||
end
|
||||
|
||||
FarmwareEnv.changeset(fwe, params)
|
||||
|> Repo.insert_or_update()
|
||||
end
|
||||
|
||||
## End FarmwareEnv
|
||||
|
||||
## Begin Peripheral
|
||||
|
||||
def get_peripheral(args) do
|
||||
Repo.get_by(Peripheral, args)
|
||||
end
|
||||
|
||||
## End Peripheral
|
||||
|
||||
## Begin DiagnosticDump
|
||||
|
||||
def new_diagnostic_dump(params) do
|
||||
DiagnosticDump.changeset(%DiagnosticDump{}, params)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
## End DiagnosticDump
|
||||
end
|
||||
|
|
|
@ -18,8 +18,10 @@ defmodule Elixir.Farmbot.Asset.FbosConfig do
|
|||
field(:beta_opt_in, :boolean)
|
||||
field(:disable_factory_reset, :boolean)
|
||||
field(:firmware_hardware, :string)
|
||||
field(:firmware_path, :string)
|
||||
field(:firmware_input_log, :boolean)
|
||||
field(:firmware_output_log, :boolean)
|
||||
field(:firmware_debug_log, :boolean)
|
||||
field(:network_not_found_timer, :integer)
|
||||
field(:os_auto_update, :boolean)
|
||||
field(:sequence_body_log, :boolean)
|
||||
|
@ -36,8 +38,10 @@ defmodule Elixir.Farmbot.Asset.FbosConfig do
|
|||
beta_opt_in: fbos_config.beta_opt_in,
|
||||
disable_factory_reset: fbos_config.disable_factory_reset,
|
||||
firmware_hardware: fbos_config.firmware_hardware,
|
||||
firmware_path: fbos_config.firmware_path,
|
||||
firmware_input_log: fbos_config.firmware_input_log,
|
||||
firmware_output_log: fbos_config.firmware_output_log,
|
||||
firmware_debug_log: fbos_config.firmware_debug_log,
|
||||
network_not_found_timer: fbos_config.network_not_found_timer,
|
||||
os_auto_update: fbos_config.os_auto_update,
|
||||
sequence_body_log: fbos_config.sequence_body_log,
|
||||
|
@ -55,8 +59,10 @@ defmodule Elixir.Farmbot.Asset.FbosConfig do
|
|||
:beta_opt_in,
|
||||
:disable_factory_reset,
|
||||
:firmware_hardware,
|
||||
:firmware_path,
|
||||
:firmware_input_log,
|
||||
:firmware_output_log,
|
||||
:firmware_debug_log,
|
||||
:network_not_found_timer,
|
||||
:os_auto_update,
|
||||
:sequence_body_log,
|
||||
|
|
|
@ -2,7 +2,6 @@ defmodule Farmbot.Asset.StorageAuth do
|
|||
use Ecto.Schema
|
||||
use Farmbot.Asset.Schema, path: "/api/storage_auth"
|
||||
|
||||
|
||||
defmodule FormData do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
@ -21,7 +20,15 @@ defmodule Farmbot.Asset.StorageAuth do
|
|||
def changeset(form_data, params \\ %{}) do
|
||||
form_data
|
||||
|> cast(params, [:key, :acl, :policy, :signature, :file, :"Content-Type", :GoogleAccessId])
|
||||
|> validate_required([:key, :acl, :policy, :signature, :file, :"Content-Type", :GoogleAccessId])
|
||||
|> validate_required([
|
||||
:key,
|
||||
:acl,
|
||||
:policy,
|
||||
:signature,
|
||||
:file,
|
||||
:"Content-Type",
|
||||
:GoogleAccessId
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -41,7 +48,7 @@ defmodule Farmbot.Asset.StorageAuth do
|
|||
signature: storage_auth.form_data.signature,
|
||||
file: storage_auth.form_data.file,
|
||||
"Content-Type": storage_auth.form_data."Content-Type",
|
||||
GoogleAccessId: storage_auth.form_data."GoogleAccessId",
|
||||
GoogleAccessId: storage_auth.form_data."GoogleAccessId"
|
||||
},
|
||||
verb: storage_auth.verb,
|
||||
url: storage_auth.url
|
||||
|
|
|
@ -5,11 +5,14 @@ defmodule Farmbot.Asset.Supervisor do
|
|||
|
||||
alias Farmbot.Asset.{
|
||||
Repo,
|
||||
PersistentRegimen,
|
||||
Device,
|
||||
FarmEvent,
|
||||
FarmwareEnv,
|
||||
FarmwareInstallation,
|
||||
FbosConfig,
|
||||
PinBinding,
|
||||
Peripheral,
|
||||
FarmwareInstallation
|
||||
PersistentRegimen
|
||||
}
|
||||
|
||||
def start_link(args) do
|
||||
|
@ -19,11 +22,14 @@ defmodule Farmbot.Asset.Supervisor do
|
|||
def init([]) do
|
||||
children = [
|
||||
Repo,
|
||||
{AssetSupervisor, module: FbosConfig},
|
||||
{AssetSupervisor, module: Device},
|
||||
{AssetSupervisor, module: PersistentRegimen, preload: [:farm_event, :regimen]},
|
||||
{AssetSupervisor, module: FarmEvent},
|
||||
{AssetSupervisor, module: PinBinding},
|
||||
{AssetSupervisor, module: Peripheral},
|
||||
{AssetSupervisor, module: FarmwareInstallation},
|
||||
{AssetSupervisor, module: FarmwareEnv},
|
||||
AssetMonitor
|
||||
]
|
||||
|
||||
|
|
|
@ -4,8 +4,11 @@ defmodule Farmbot.AssetMonitor do
|
|||
|
||||
alias Farmbot.Asset.{
|
||||
Repo,
|
||||
Device,
|
||||
FbosConfig,
|
||||
FarmEvent,
|
||||
FarmwareInstallation,
|
||||
FarmwareEnv,
|
||||
Peripheral,
|
||||
PersistentRegimen,
|
||||
PinBinding
|
||||
|
@ -83,5 +86,15 @@ defmodule Farmbot.AssetMonitor do
|
|||
end)
|
||||
end
|
||||
|
||||
def order, do: [FarmEvent, Peripheral, PersistentRegimen, PinBinding, FarmwareInstallation]
|
||||
def order,
|
||||
do: [
|
||||
Device,
|
||||
FbosConfig,
|
||||
FarmEvent,
|
||||
# Peripheral,
|
||||
PersistentRegimen,
|
||||
PinBinding,
|
||||
FarmwareInstallation,
|
||||
FarmwareEnv
|
||||
]
|
||||
end
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
defimpl Farmbot.AssetWorker, for: Farmbot.Asset.Device do
|
||||
alias Farmbot.Asset.Device
|
||||
use GenServer
|
||||
import Farmbot.Config, only: [update_config_value: 4]
|
||||
|
||||
def start_link(%Device{} = device) do
|
||||
GenServer.start_link(__MODULE__, [%Device{} = device])
|
||||
end
|
||||
|
||||
def init([%Device{} = device]) do
|
||||
{:ok, %Device{} = device, 0}
|
||||
end
|
||||
|
||||
def handle_info(:timeout, %Device{} = device) do
|
||||
update_config_value(:string, "settings", "timezone", device.timezone)
|
||||
{:noreply, device}
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
defimpl Farmbot.AssetWorker, for: Farmbot.Asset.FarmwareEnv do
|
||||
alias Farmbot.Asset.FarmwareEnv
|
||||
use GenServer
|
||||
|
||||
def start_link(%FarmwareEnv{} = env) do
|
||||
GenServer.start_link(__MODULE__, env)
|
||||
end
|
||||
|
||||
def init(%FarmwareEnv{} = env) do
|
||||
{:ok, env, 0}
|
||||
end
|
||||
|
||||
def handle_info(:timeout, %FarmwareEnv{key: key, value: value} = env) do
|
||||
:ok = Farmbot.BotState.set_user_env(key, value)
|
||||
{:noreply, env, :hibernate}
|
||||
end
|
||||
end
|
|
@ -0,0 +1,68 @@
|
|||
defimpl Farmbot.AssetWorker, for: Farmbot.Asset.FbosConfig do
|
||||
use GenServer
|
||||
require Logger
|
||||
alias Farmbot.Asset.FbosConfig
|
||||
import Farmbot.Config, only: [update_config_value: 4]
|
||||
|
||||
def start_link(%FbosConfig{} = fbos_config) do
|
||||
GenServer.start_link(__MODULE__, [%FbosConfig{} = fbos_config])
|
||||
end
|
||||
|
||||
def init([%FbosConfig{} = fbos_config]) do
|
||||
{:ok, %FbosConfig{} = fbos_config, 0}
|
||||
end
|
||||
|
||||
def handle_info(:timeout, %FbosConfig{} = fbos_config) do
|
||||
maybe_reinit_firmware(fbos_config)
|
||||
bool("arduino_debug_messages", fbos_config.arduino_debug_messages)
|
||||
bool("auto_sync", fbos_config.auto_sync)
|
||||
bool("beta_opt_in", fbos_config.beta_opt_in)
|
||||
bool("disable_factory_reset", fbos_config.disable_factory_reset)
|
||||
string("firmware_hardware", fbos_config.firmware_hardware)
|
||||
bool("firmware_input_log", fbos_config.firmware_input_log)
|
||||
bool("firmware_output_log", fbos_config.firmware_output_log)
|
||||
float("network_not_found_timer", fbos_config.network_not_found_timer)
|
||||
bool("os_auto_update", fbos_config.os_auto_update)
|
||||
bool("sequence_body_log", fbos_config.sequence_body_log)
|
||||
bool("sequence_complete_log", fbos_config.sequence_complete_log)
|
||||
bool("sequence_init_log", fbos_config.sequence_init_log)
|
||||
{:noreply, fbos_config}
|
||||
end
|
||||
|
||||
defp bool(key, val) do
|
||||
update_config_value(:bool, "settings", key, val)
|
||||
:ok = Farmbot.BotState.set_config_value(key, val)
|
||||
end
|
||||
|
||||
defp string(key, val) do
|
||||
update_config_value(:string, "settings", key, val)
|
||||
:ok = Farmbot.BotState.set_config_value(key, val)
|
||||
end
|
||||
|
||||
defp float(_key, nil) do
|
||||
:ok
|
||||
end
|
||||
|
||||
defp float(key, val) do
|
||||
update_config_value(:float, "settings", key, val / 1)
|
||||
:ok = Farmbot.BotState.set_config_value(key, val)
|
||||
end
|
||||
|
||||
defp maybe_reinit_firmware(%FbosConfig{firmware_hardware: nil}) do
|
||||
:ok
|
||||
end
|
||||
|
||||
defp maybe_reinit_firmware(%FbosConfig{firmware_path: nil}) do
|
||||
:ok
|
||||
end
|
||||
|
||||
defp maybe_reinit_firmware(%FbosConfig{}) do
|
||||
alias Farmbot.Firmware
|
||||
alias Farmbot.Core.FirmwareSupervisor
|
||||
|
||||
if is_nil(Process.whereis(Firmware)) do
|
||||
Logger.warn("Starting Farmbot firmware")
|
||||
FirmwareSupervisor.reinitialize()
|
||||
end
|
||||
end
|
||||
end
|
|
@ -14,19 +14,19 @@ defimpl Farmbot.AssetWorker, for: Farmbot.Asset.Peripheral do
|
|||
end
|
||||
|
||||
def handle_info(:timeout, peripheral) do
|
||||
# Farmbot.Logger.info 2, "Read peripheral: #{peripheral.label}"
|
||||
Farmbot.Logger.busy(2, "Read peripheral: #{peripheral.label}")
|
||||
CeleryScript.rpc_request(peripheral_to_rpc(peripheral), &handle_ast(&1, self()))
|
||||
{:noreply, peripheral}
|
||||
end
|
||||
|
||||
def handle_cast(%{kind: :rpc_ok}, peripheral) do
|
||||
# Farmbot.Logger.success 2, "Read peripheral: #{peripheral.label} ok"
|
||||
Farmbot.Logger.success(2, "Read peripheral: #{peripheral.label} ok")
|
||||
{:stop, :normal, peripheral}
|
||||
end
|
||||
|
||||
def handle_cast(%{kind: :rpc_error} = rpc, peripheral) do
|
||||
# Farmbot.Logger.error 1, "Read peripheral: #{peripheral.label} error"
|
||||
# IO.inspect(rpc, label: "error")
|
||||
[%{args: %{message: reason}}] = rpc.body
|
||||
Farmbot.Logger.error(1, "Read peripheral: #{peripheral.label} error: #{reason}")
|
||||
{:noreply, peripheral, @retry_ms}
|
||||
end
|
||||
|
||||
|
|
|
@ -1,11 +1,155 @@
|
|||
defimpl Farmbot.AssetWorker, for: Farmbot.Asset.PinBinding do
|
||||
@gpio_handler Application.get_env(:farmbot_core, __MODULE__)[:gpio_handler]
|
||||
@error_retry_time_ms Application.get_env(:farmbot_core, __MODULE__)[:error_retry_time_ms]
|
||||
|
||||
require Logger
|
||||
require Farmbot.Logger
|
||||
import Farmbot.CeleryScript.Utils
|
||||
|
||||
alias Farmbot.{
|
||||
Core.CeleryScript,
|
||||
Asset.PinBinding,
|
||||
Asset.Sequence,
|
||||
Asset
|
||||
}
|
||||
|
||||
@gpio_handler ||
|
||||
Mix.raise("""
|
||||
config :farmbot_core, #{__MODULE__}, gpio_handler: MyModule
|
||||
""")
|
||||
|
||||
@error_retry_time_ms ||
|
||||
Mix.raise("""
|
||||
config :farmbot_core, #{__MODULE__}, error_retry_time_ms: 30_000
|
||||
""")
|
||||
|
||||
@typedoc "Opaque function that should be called upon a trigger"
|
||||
@type trigger_fun :: (pid -> any)
|
||||
|
||||
@typedoc "Integer representing a GPIO on the target platform."
|
||||
@type pin_number :: integer
|
||||
|
||||
@doc """
|
||||
Start a GPIO Handler. Returns the same values as a GenServer start.
|
||||
|
||||
Should call `#{__MODULE__}.trigger/1` when a pin has been triggered.
|
||||
"""
|
||||
@callback start_link(pin_number, trigger_fun) :: GenServer.on_start()
|
||||
|
||||
use GenServer
|
||||
|
||||
def start_link(pin_binding) do
|
||||
GenServer.start_link(__MODULE__, [pin_binding])
|
||||
def start_link(%PinBinding{} = pin_binding) do
|
||||
GenServer.start_link(__MODULE__, [%PinBinding{} = pin_binding])
|
||||
end
|
||||
|
||||
def init([pin_binding]) do
|
||||
{:ok, pin_binding}
|
||||
# This function is opaque and should be considered private.
|
||||
@doc false
|
||||
def trigger(pid) do
|
||||
GenServer.cast(pid, :trigger)
|
||||
end
|
||||
|
||||
def init([%PinBinding{} = pin_binding]) do
|
||||
{:ok, pin_binding, 0}
|
||||
end
|
||||
|
||||
def handle_info(:timeout, %PinBinding{} = pin_binding) do
|
||||
worker_pid = self()
|
||||
|
||||
case @gpio_handler.start_link(pin_binding.pin_num, fn -> trigger(worker_pid) end) do
|
||||
{:ok, pid} when is_pid(pid) ->
|
||||
Process.link(pid)
|
||||
{:noreply, pin_binding}
|
||||
|
||||
{:error, {:already_started, pid}} ->
|
||||
Process.link(pid)
|
||||
{:noreply, pin_binding, :hibernate}
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to start PinBinding GPIO Handler: #{inspect(reason)}")
|
||||
{:noreply, pin_binding, @error_retry_time_ms}
|
||||
|
||||
:ignore ->
|
||||
Logger.info("Failed to start PinBinding GPIO Handler. Not retrying.")
|
||||
{:noreply, pin_binding, :hibernate}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_cast(:trigger, %PinBinding{special_action: nil} = pin_binding) do
|
||||
case Asset.get_sequence(id: pin_binding.sequence_id) do
|
||||
%Sequence{} = seq ->
|
||||
pid = CeleryScript.sequence(seq, &handle_sequence_results(&1, pin_binding))
|
||||
Process.link(pid)
|
||||
{:noreply, pin_binding, :hibernate}
|
||||
|
||||
nil ->
|
||||
Farmbot.Logger.error(1, "Failed to find assosiated Sequence for: #{pin_binding}")
|
||||
{:noreply, pin_binding, :hibernate}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_cast(:trigger, %PinBinding{special_action: "dump_info"} = pin_binding) do
|
||||
ast(:rpc_request, %{label: "pin_binding.#{pin_binding.pin_num}"}, [ast(:dump_info, %{}, [])])
|
||||
|> CeleryScript.rpc_request(&handle_rpc_request(&1, pin_binding))
|
||||
end
|
||||
|
||||
def handle_cast(:trigger, %PinBinding{special_action: "emergency_lock"} = pin_binding) do
|
||||
ast(:rpc_request, %{label: "pin_binding.#{pin_binding.pin_num}"}, [
|
||||
ast(:emergency_lock, %{}, [])
|
||||
])
|
||||
|> CeleryScript.rpc_request(&handle_rpc_request(&1, pin_binding))
|
||||
end
|
||||
|
||||
def handle_cast(:trigger, %PinBinding{special_action: "emergency_unlock"} = pin_binding) do
|
||||
ast(:rpc_request, %{label: "pin_binding.#{pin_binding.pin_num}"}, [
|
||||
ast(:emergency_unlock, %{}, [])
|
||||
])
|
||||
|> CeleryScript.rpc_request(&handle_rpc_request(&1, pin_binding))
|
||||
end
|
||||
|
||||
def handle_cast(:trigger, %PinBinding{special_action: "power_off"} = pin_binding) do
|
||||
ast(:rpc_request, %{label: "pin_binding.#{pin_binding.pin_num}"}, [ast(:power_off, %{}, [])])
|
||||
|> CeleryScript.rpc_request(&handle_rpc_request(&1, pin_binding))
|
||||
end
|
||||
|
||||
def handle_cast(:trigger, %PinBinding{special_action: "read_status"} = pin_binding) do
|
||||
ast(:rpc_request, %{label: "pin_binding.#{pin_binding.pin_num}"}, [ast(:read_status, %{}, [])])
|
||||
|> CeleryScript.rpc_request(&handle_rpc_request(&1, pin_binding))
|
||||
end
|
||||
|
||||
def handle_cast(:trigger, %PinBinding{special_action: "reboot"} = pin_binding) do
|
||||
ast(:rpc_request, %{label: "pin_binding.#{pin_binding.pin_num}"}, [ast(:reboot, %{}, [])])
|
||||
|> CeleryScript.rpc_request(&handle_rpc_request(&1, pin_binding))
|
||||
end
|
||||
|
||||
def handle_cast(:trigger, %PinBinding{special_action: "sync"} = pin_binding) do
|
||||
ast(:rpc_request, %{label: "pin_binding.#{pin_binding.pin_num}"}, [ast(:sync, %{}, [])])
|
||||
|> CeleryScript.rpc_request(&handle_rpc_request(&1, pin_binding))
|
||||
end
|
||||
|
||||
def handle_cast(:trigger, %PinBinding{special_action: "take_photo"} = pin_binding) do
|
||||
ast(:rpc_request, %{label: "pin_binding.#{pin_binding.pin_num}"}, [ast(:take_photo, %{}, [])])
|
||||
|> CeleryScript.rpc_request(&handle_rpc_request(&1, pin_binding))
|
||||
end
|
||||
|
||||
def handle_cast(:trigger, %PinBinding{} = pin_binding) do
|
||||
Farmbot.Logger.error(1, "Unknown PinBinding: #{pin_binding}")
|
||||
{:noreply, pin_binding, :hibernate}
|
||||
end
|
||||
|
||||
def handle_sequence_results({:error, reason}, %PinBinding{} = pin_binding) do
|
||||
Farmbot.Logger.error(1, "PinBinding #{pin_binding} failed to execute sequence: #{reason}")
|
||||
end
|
||||
|
||||
def handle_sequence_results(_, _), do: :ok
|
||||
|
||||
def handle_rpc_request(
|
||||
%{kind: :rpc_error, body: [%{args: %{message: m}}]},
|
||||
%PinBinding{} = pin_binding
|
||||
) do
|
||||
Farmbot.Logger.error(1, "PinBinding: #{pin_binding} failed to execute special action: #{m}")
|
||||
{:noreply, pin_binding, :hibernate}
|
||||
end
|
||||
|
||||
def handle_rpc_request(_, %PinBinding{} = pin_binding),
|
||||
do: {:noreply, pin_binding, :hibernate}
|
||||
end
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
defmodule Farmbot.PinBindingWorker.StubGPIOHandler do
|
||||
@moduledoc "Stub gpio handler for PinBindings"
|
||||
@behaviour Farmbot.AssetWorker.Farmbot.Asset.PinBinding
|
||||
require Logger
|
||||
use GenServer
|
||||
|
||||
def start_link(pin_number, fun) do
|
||||
GenServer.start_link(__MODULE__, [pin_number, fun], name: name(pin_number))
|
||||
end
|
||||
|
||||
def terminate(reason, _state) do
|
||||
Logger.warn("StubBindingHandler crash: #{inspect(reason)}")
|
||||
end
|
||||
|
||||
def debug_trigger(pin_number) do
|
||||
GenServer.call(name(pin_number), :debug_trigger)
|
||||
end
|
||||
|
||||
def init([pin_number, fun]) do
|
||||
Logger.info("StubBindingHandler init")
|
||||
{:ok, %{pin_number: pin_number, fun: fun}}
|
||||
end
|
||||
|
||||
def handle_call(:debug_trigger, _from, state) do
|
||||
Logger.debug("DebugTrigger: #{state.pin_number}")
|
||||
r = state.fun.()
|
||||
{:reply, r, state}
|
||||
end
|
||||
|
||||
def name(pin_number), do: :"#{__MODULE__}.#{pin_number}"
|
||||
end
|
|
@ -1,260 +1,296 @@
|
|||
defmodule Farmbot.BotState do
|
||||
@moduledoc "Central State accumulator."
|
||||
alias Farmbot.BotState
|
||||
alias BotState.{
|
||||
McuParams,
|
||||
LocationData,
|
||||
Configuration,
|
||||
InformationalSettings,
|
||||
Pin
|
||||
}
|
||||
alias Farmbot.BotStateNG
|
||||
use GenServer
|
||||
|
||||
defstruct [
|
||||
mcu_params: struct(McuParams),
|
||||
location_data: struct(LocationData),
|
||||
configuration: struct(Configuration),
|
||||
informational_settings: struct(InformationalSettings),
|
||||
pins: %{},
|
||||
process_info: %{farmwares: %{}},
|
||||
gpio_registry: %{},
|
||||
user_env: %{},
|
||||
jobs: %{}
|
||||
]
|
||||
|
||||
use GenStage
|
||||
|
||||
def download_progress_fun(name) do
|
||||
alias Farmbot.BotState.JobProgress
|
||||
require Farmbot.Logger
|
||||
fn(bytes, total) ->
|
||||
{do_send, prog} = cond do
|
||||
# if the total is complete spit out the bytes,
|
||||
# and put a status of complete.
|
||||
total == :complete ->
|
||||
Farmbot.Logger.success 3, "#{name} complete."
|
||||
{true, %JobProgress.Bytes{bytes: bytes, status: :complete}}
|
||||
|
||||
# if we don't know the total just spit out the bytes.
|
||||
total == nil ->
|
||||
# debug_log "#{name} - #{bytes} bytes."
|
||||
{rem(bytes, 10) == 0, %JobProgress.Bytes{bytes: bytes}}
|
||||
# if the number of bytes == the total bytes,
|
||||
# percentage side is complete.
|
||||
(div(bytes, total)) == 1 ->
|
||||
Farmbot.Logger.success 3, "#{name} complete."
|
||||
{true, %JobProgress.Percent{percent: 100, status: :complete}}
|
||||
# anything else is a percent.
|
||||
true ->
|
||||
percent = ((bytes / total) * 100) |> round()
|
||||
# Logger.busy 3, "#{name} - #{bytes}/#{total} = #{percent}%"
|
||||
{rem(percent, 10) == 0, %JobProgress.Percent{percent: percent}}
|
||||
end
|
||||
if do_send do
|
||||
Farmbot.BotState.set_job_progress(name, prog)
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
@doc "Subscribe to BotState changes"
|
||||
def subscribe(bot_state_server \\ __MODULE__) do
|
||||
GenServer.call(bot_state_server, :subscribe)
|
||||
end
|
||||
|
||||
@doc "Set job progress."
|
||||
def set_job_progress(name, progress) do
|
||||
GenServer.call(__MODULE__, {:set_job_progress, name, progress})
|
||||
def set_job_progress(bot_state_server \\ __MODULE__, name, progress) do
|
||||
GenServer.call(bot_state_server, {:set_job_progress, name, progress})
|
||||
end
|
||||
|
||||
def clear_progress_fun(name) do
|
||||
GenServer.call(__MODULE__, {:clear_progress_fun, name})
|
||||
@doc "Set a configuration value"
|
||||
def set_config_value(bot_state_server \\ __MODULE__, key, value) do
|
||||
GenServer.call(bot_state_server, {:set_config_value, key, value})
|
||||
end
|
||||
|
||||
@doc "Sets user_env value"
|
||||
def set_user_env(bot_state_server \\ __MODULE__, key, value) do
|
||||
GenServer.call(bot_state_server, {:set_user_env, key, value})
|
||||
end
|
||||
|
||||
@doc "Sets the location_data.position"
|
||||
def set_position(bot_state_server \\ __MODULE__, x, y, z) do
|
||||
GenServer.call(bot_state_server, {:set_position, x, y, z})
|
||||
end
|
||||
|
||||
@doc "Sets the location_data.encoders_scaled"
|
||||
def set_encoders_scaled(bot_state_server \\ __MODULE__, x, y, z) do
|
||||
GenServer.call(bot_state_server, {:set_encoders_scaled, x, y, z})
|
||||
end
|
||||
|
||||
@doc "Sets pins.pin.value"
|
||||
def set_pin_value(bot_state_server \\ __MODULE__, pin, value) do
|
||||
GenServer.call(bot_state_server, {:set_pin_value, pin, value})
|
||||
end
|
||||
|
||||
@doc "Sets the location_data.encoders_raw"
|
||||
def set_encoders_raw(bot_state_server \\ __MODULE__, x, y, z) do
|
||||
GenServer.call(bot_state_server, {:set_encoders_raw, x, y, z})
|
||||
end
|
||||
|
||||
@doc "Sets mcu_params[param] = value"
|
||||
def set_firmware_config(bot_state_server \\ __MODULE__, param, value) do
|
||||
GenServer.call(bot_state_server, {:set_firmware_config, param, value})
|
||||
end
|
||||
|
||||
@doc "Sets informational_settings.locked = true"
|
||||
def set_firmware_locked(bot_state_server \\ __MODULE__) do
|
||||
GenServer.call(bot_state_server, {:set_firmware_locked, true})
|
||||
end
|
||||
|
||||
@doc "Sets informational_settings.locked = false"
|
||||
def set_firmware_unlocked(bot_state_server \\ __MODULE__) do
|
||||
GenServer.call(bot_state_server, {:set_firmware_locked, false})
|
||||
end
|
||||
|
||||
def set_sync_status(bot_state_server \\ __MODULE__, s)
|
||||
when s in ["syncing", "synced", "error"] do
|
||||
GenServer.call(bot_state_server, {:set_sync_status, s})
|
||||
end
|
||||
|
||||
@doc "Fetch the current state."
|
||||
def fetch do
|
||||
GenStage.call(__MODULE__, :fetch)
|
||||
def fetch(bot_state_server \\ __MODULE__) do
|
||||
GenServer.call(bot_state_server, :fetch)
|
||||
end
|
||||
|
||||
def report_disk_usage(percent) when is_number(percent) do
|
||||
GenStage.call(__MODULE__, {:report_disk_usage, percent})
|
||||
def report_disk_usage(bot_state_server \\ __MODULE__, percent) do
|
||||
GenServer.call(bot_state_server, {:report_disk_usage, percent})
|
||||
end
|
||||
|
||||
def report_memory_usage(megabytes) when is_number(megabytes) do
|
||||
GenStage.call(__MODULE__, {:report_memory_usage, megabytes})
|
||||
def report_memory_usage(bot_state_server \\ __MODULE__, megabytes) do
|
||||
GenServer.call(bot_state_server, {:report_memory_usage, megabytes})
|
||||
end
|
||||
|
||||
def report_soc_temp(temp_celcius) when is_number(temp_celcius) do
|
||||
GenStage.call(__MODULE__, {:report_soc_temp, temp_celcius})
|
||||
def report_soc_temp(bot_state_server \\ __MODULE__, temp_celcius) do
|
||||
GenServer.call(bot_state_server, {:report_soc_temp, temp_celcius})
|
||||
end
|
||||
|
||||
def report_uptime(seconds) when is_number(seconds) do
|
||||
GenStage.call(__MODULE__, {:report_uptime, seconds})
|
||||
def report_uptime(bot_state_server \\ __MODULE__, seconds) do
|
||||
GenServer.call(bot_state_server, {:report_uptime, seconds})
|
||||
end
|
||||
|
||||
def report_wifi_level(level) when is_number(level) do
|
||||
GenStage.call(__MODULE__, {:report_wifi_level, level})
|
||||
def report_wifi_level(bot_state_server \\ __MODULE__, level) do
|
||||
GenServer.call(bot_state_server, {:report_wifi_level, level})
|
||||
end
|
||||
|
||||
@doc "Put FBOS into maintenance mode."
|
||||
def enter_maintenance_mode do
|
||||
GenStage.call(__MODULE__, :enter_maintenance_mode)
|
||||
def enter_maintenance_mode(bot_state_server \\ __MODULE__) do
|
||||
GenServer.call(bot_state_server, :enter_maintenance_mode)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def start_link(args) do
|
||||
GenStage.start_link(__MODULE__, args, [name: __MODULE__])
|
||||
def start_link(args, opts \\ [name: __MODULE__]) do
|
||||
GenServer.start_link(__MODULE__, args, opts)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def init([]) do
|
||||
Farmbot.Registry.subscribe()
|
||||
send(self(), :get_initial_configuration)
|
||||
send(self(), :get_initial_mcu_params)
|
||||
# send(self(), :get_initial_user_env)
|
||||
{:consumer, struct(BotState), [subscribe_to: [Farmbot.Firmware]]}
|
||||
{:ok, %{tree: BotStateNG.new(), subscribers: []}}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(:subscribe, {pid, _} = _from, state) do
|
||||
Process.link(pid)
|
||||
{:reply, state.tree, %{state | subscribers: Enum.uniq([pid | state.subscribers])}}
|
||||
end
|
||||
|
||||
def handle_call(:fetch, _from, state) do
|
||||
new_state = handle_event({:informational_settings, %{cache_bust: :rand.uniform(1000)}}, state)
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:reply, state, [], new_state}
|
||||
{:reply, state.tree, state}
|
||||
end
|
||||
|
||||
# TODO(Connor) - Fix this to use event system.
|
||||
def handle_call({:set_job_progress, name, progress}, _from, state) do
|
||||
jobs = Map.put(state.jobs, name, progress)
|
||||
new_state = %{state | jobs: jobs}
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:reply, :ok, [], new_state}
|
||||
{reply, state} =
|
||||
BotStateNG.set_job_progress(state.tree, name, Map.from_struct(progress))
|
||||
|> dispatch_and_apply(state)
|
||||
|
||||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
# TODO(Connor) - Fix this to use event system.
|
||||
def handle_call({:clear_progress_fun, name}, _from, state) do
|
||||
jobs = Map.delete(state.jobs, name)
|
||||
new_state = %{state | jobs: jobs}
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:reply, :ok, [], new_state}
|
||||
def handle_call({:set_config_value, key, value}, _from, state) do
|
||||
change = %{configuration: %{key => value}}
|
||||
|
||||
{reply, state} =
|
||||
BotStateNG.changeset(state.tree, change)
|
||||
|> dispatch_and_apply(state)
|
||||
|
||||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
def handle_call({:set_user_env, key, value}, _from, state) do
|
||||
{reply, state} =
|
||||
BotStateNG.set_user_env(state.tree, key, value)
|
||||
|> dispatch_and_apply(state)
|
||||
|
||||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
def handle_call({:set_position, x, y, z}, _from, state) do
|
||||
change = %{location_data: %{position: %{x: x, y: y, z: z}}}
|
||||
|
||||
{reply, state} =
|
||||
BotStateNG.changeset(state.tree, change)
|
||||
|> dispatch_and_apply(state)
|
||||
|
||||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
def handle_call({:set_encoders_scaled, x, y, z}, _from, state) do
|
||||
change = %{location_data: %{scaled_encoders: %{x: x, y: y, z: z}}}
|
||||
|
||||
{reply, state} =
|
||||
BotStateNG.changeset(state.tree, change)
|
||||
|> dispatch_and_apply(state)
|
||||
|
||||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
def handle_call({:set_encoders_raw, x, y, z}, _from, state) do
|
||||
change = %{location_data: %{raw_encoders: %{x: x, y: y, z: z}}}
|
||||
|
||||
{reply, state} =
|
||||
BotStateNG.changeset(state.tree, change)
|
||||
|> dispatch_and_apply(state)
|
||||
|
||||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
def handle_call({:set_pin_value, pin, value}, _from, state) do
|
||||
change = %{pins: %{pin => %{mode: -1, value: value}}}
|
||||
|
||||
{reply, state} =
|
||||
BotStateNG.changeset(state.tree, change)
|
||||
|> dispatch_and_apply(state)
|
||||
|
||||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
def handle_call({:set_firmware_config, param, value}, _from, state) do
|
||||
change = %{mcu_params: %{param => value}}
|
||||
|
||||
{reply, state} =
|
||||
BotStateNG.changeset(state.tree, change)
|
||||
|> dispatch_and_apply(state)
|
||||
|
||||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
def handle_call({:set_firmware_locked, bool}, _from, state) do
|
||||
change = %{informational_settings: %{locked: bool}}
|
||||
|
||||
{reply, state} =
|
||||
BotStateNG.changeset(state.tree, change)
|
||||
|> dispatch_and_apply(state)
|
||||
|
||||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
def handle_call({:set_sync_status, status}, _from, state) do
|
||||
change = %{informational_settings: %{sync_status: status}}
|
||||
|
||||
{reply, state} =
|
||||
BotStateNG.changeset(state.tree, change)
|
||||
|> dispatch_and_apply(state)
|
||||
|
||||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
def handle_call({:report_disk_usage, percent}, _form, state) do
|
||||
event = {:informational_settings, %{disk_usage: percent}}
|
||||
new_state = handle_event(event, state)
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:reply, :ok, [], new_state}
|
||||
change = %{informational_settings: %{disk_usage: percent}}
|
||||
|
||||
{reply, state} =
|
||||
BotStateNG.changeset(state.tree, change)
|
||||
|> dispatch_and_apply(state)
|
||||
|
||||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
def handle_call({:memory_usage, megabytes}, _form, state) do
|
||||
event = {:informational_settings, %{memory_usage: megabytes}}
|
||||
new_state = handle_event(event, state)
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:reply, :ok, [], new_state}
|
||||
change = %{informational_settings: %{memory_usage: megabytes}}
|
||||
|
||||
{reply, state} =
|
||||
BotStateNG.changeset(state.tree, change)
|
||||
|> dispatch_and_apply(state)
|
||||
|
||||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
def handle_call({:report_soc_temp, temp}, _form, state) do
|
||||
event = {:informational_settings, %{soc_temp: temp}}
|
||||
new_state = handle_event(event, state)
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:reply, :ok, [], new_state}
|
||||
change = %{informational_settings: %{soc_temp: temp}}
|
||||
|
||||
{reply, state} =
|
||||
BotStateNG.changeset(state.tree, change)
|
||||
|> dispatch_and_apply(state)
|
||||
|
||||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
def handle_call({:uptime, seconds}, _form, state) do
|
||||
event = {:informational_settings, %{uptime: seconds}}
|
||||
new_state = handle_event(event, state)
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:reply, :ok, [], new_state}
|
||||
change = %{informational_settings: %{uptime: seconds}}
|
||||
|
||||
{reply, state} =
|
||||
BotStateNG.changeset(state.tree, change)
|
||||
|> dispatch_and_apply(state)
|
||||
|
||||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
def handle_call({:report_wifi_level, level}, _form, state) do
|
||||
event = {:informational_settings, %{wifi_level: level}}
|
||||
new_state = handle_event(event, state)
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:reply, :ok, [], new_state}
|
||||
change = %{informational_settings: %{wifi_level: level}}
|
||||
|
||||
{reply, state} =
|
||||
BotStateNG.changeset(state.tree, change)
|
||||
|> dispatch_and_apply(state)
|
||||
|
||||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
def handle_call(:enter_maintenance_mode, _form, state) do
|
||||
event = {:informational_settings, %{sync_status: :maintenance}}
|
||||
new_state = handle_event(event, state)
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:reply, :ok, [], new_state}
|
||||
change = %{informational_settings: %{sync_status: "maintenance"}}
|
||||
|
||||
{reply, state} =
|
||||
BotStateNG.changeset(state.tree, change)
|
||||
|> dispatch_and_apply(state)
|
||||
|
||||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info({Farmbot.Registry, {Farmbot.Config, {"settings", key, val}}}, state) do
|
||||
event = {:settings, %{String.to_atom(key) => val}}
|
||||
new_state = handle_event(event, state)
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:noreply, [], new_state}
|
||||
defp dispatch_and_apply(%Ecto.Changeset{changes: changes}, state) when map_size(changes) == 0 do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
def handle_info({Farmbot.Registry, {_, {:sync_status, status}}}, state) do
|
||||
event = {:informational_settings, %{sync_status: status}}
|
||||
new_state = handle_event(event, state)
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:noreply, [], new_state}
|
||||
defp dispatch_and_apply(%Ecto.Changeset{valid?: true} = change, state) do
|
||||
state = %{state | tree: Ecto.Changeset.apply_changes(change)}
|
||||
|
||||
state =
|
||||
Enum.reduce(state.subscribers, state, fn pid, state ->
|
||||
if Process.alive?(pid) do
|
||||
send(pid, {__MODULE__, change})
|
||||
state
|
||||
else
|
||||
Process.unlink(pid)
|
||||
%{state | subscribers: List.delete(state.subscribers, pid)}
|
||||
end
|
||||
end)
|
||||
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
def handle_info({Farmbot.Registry, _}, state), do: {:noreply, [], state}
|
||||
|
||||
def handle_info(:get_initial_configuration, state) do
|
||||
full_config = Farmbot.Config.get_config_as_map()
|
||||
settings = full_config["settings"]
|
||||
new_state = Enum.reduce(settings, state, fn({key, val}, state) ->
|
||||
event = {:settings, %{String.to_atom(key) => val}}
|
||||
handle_event(event, state)
|
||||
end)
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:noreply, [], new_state}
|
||||
end
|
||||
|
||||
def handle_info(:get_initial_mcu_params, state) do
|
||||
full_config = Farmbot.Config.get_config_as_map()
|
||||
settings = full_config["hardware_params"]
|
||||
new_state = Enum.reduce(settings, state, fn({key, val}, state) ->
|
||||
event = {:mcu_params, %{String.to_atom(key) => val}}
|
||||
handle_event(event, state)
|
||||
end)
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:noreply, [], new_state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_events(events, _from, state) do
|
||||
state = Enum.reduce(events, state, &handle_event(&1, &2))
|
||||
Farmbot.Registry.dispatch(__MODULE__, state)
|
||||
{:noreply, [], state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_event({:informational_settings, data}, state) do
|
||||
new_data = Map.merge(state.informational_settings, data) |> Map.from_struct()
|
||||
new_informational_settings = struct(InformationalSettings, new_data)
|
||||
%{state | informational_settings: new_informational_settings}
|
||||
end
|
||||
|
||||
def handle_event({:mcu_params, data}, state) do
|
||||
new_data = Map.merge(state.mcu_params, data) |> Map.from_struct()
|
||||
new_mcu_params = struct(McuParams, new_data)
|
||||
%{state | mcu_params: new_mcu_params}
|
||||
end
|
||||
|
||||
def handle_event({:location_data, data}, state) do
|
||||
new_data = Map.merge(state.location_data, data) |> Map.from_struct()
|
||||
new_location_data = struct(LocationData, new_data)
|
||||
%{state | location_data: new_location_data}
|
||||
end
|
||||
|
||||
def handle_event({:pins, data}, state) do
|
||||
new_data = Enum.reduce(data, state.pins, fn({number, pin_state}, pins) ->
|
||||
Map.put(pins, number, struct(Pin, pin_state))
|
||||
end)
|
||||
%{state | pins: new_data}
|
||||
end
|
||||
|
||||
def handle_event({:settings, data}, state) do
|
||||
new_data = Map.merge(state.configuration, data) |> Map.from_struct()
|
||||
new_configuration = struct(Configuration, new_data)
|
||||
%{state | configuration: new_configuration}
|
||||
end
|
||||
|
||||
def handle_event(event, state) do
|
||||
IO.inspect event, label: "unhandled event"
|
||||
state
|
||||
defp dispatch_and_apply(%Ecto.Changeset{valid?: false} = change, state) do
|
||||
{{:error, change}, state}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
defmodule Farmbot.BotState.Configuration do
|
||||
@moduledoc false
|
||||
defstruct [
|
||||
timezone: nil,
|
||||
sync_timeout_ms: nil,
|
||||
sequence_init_log: nil,
|
||||
sequence_complete_log: nil,
|
||||
sequence_body_log: nil,
|
||||
os_update_server_overwrite: nil,
|
||||
os_auto_update: nil,
|
||||
network_not_found_timer: nil,
|
||||
log_amqp_connected: nil,
|
||||
ignore_fbos_config: nil,
|
||||
ignore_external_logs: nil,
|
||||
fw_upgrade_migration: nil,
|
||||
first_sync: nil,
|
||||
first_party_farmware_url: nil,
|
||||
first_party_farmware: nil,
|
||||
first_boot: nil,
|
||||
firmware_output_log: nil,
|
||||
firmware_needs_first_sync: nil,
|
||||
firmware_input_log: nil,
|
||||
firmware_hardware: nil,
|
||||
email_on_estop: nil,
|
||||
disable_factory_reset: nil,
|
||||
currently_on_beta: nil,
|
||||
current_repo: nil,
|
||||
beta_opt_in: nil,
|
||||
auto_sync: nil,
|
||||
arduino_debug_messages: nil,
|
||||
api_migrated: nil
|
||||
]
|
||||
end
|
|
@ -1,23 +0,0 @@
|
|||
defmodule Farmbot.BotState.InformationalSettings do
|
||||
@moduledoc false
|
||||
import Farmbot.Project
|
||||
defstruct [
|
||||
target: target(),
|
||||
env: env(),
|
||||
node_name: node(),
|
||||
controller_version: version(),
|
||||
firmware_commit: arduino_commit(),
|
||||
commit: commit(),
|
||||
soc_temp: 0, # degrees celcius
|
||||
wifi_level: nil, # decibels
|
||||
uptime: 0, # seconds
|
||||
memory_usage: 0, # megabytes
|
||||
disk_usage: 0, # percent
|
||||
firmware_version: nil,
|
||||
sync_status: :sync_now,
|
||||
last_status: :sync_now,
|
||||
locked: nil,
|
||||
cache_bust: nil,
|
||||
busy: nil
|
||||
]
|
||||
end
|
|
@ -8,23 +8,26 @@ defmodule Farmbot.BotState.JobProgress do
|
|||
|
||||
defmodule Percent do
|
||||
@moduledoc "Percent job."
|
||||
defstruct [status: :working, percent: 0, unit: :percent]
|
||||
defstruct status: :working, percent: 0, unit: :percent, type: :ota, time: nil
|
||||
|
||||
defimpl Inspect, for: __MODULE__ do
|
||||
def inspect(%{percent: percent}, _) do
|
||||
"#Percent<#{percent}>"
|
||||
end
|
||||
end
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
status: Farmbot.BotState.JobProgress.status,
|
||||
percent: integer,
|
||||
unit: :percent
|
||||
}
|
||||
status: Farmbot.BotState.JobProgress.status(),
|
||||
percent: integer,
|
||||
unit: :percent,
|
||||
type: :image | :ota,
|
||||
time: DateTime.t()
|
||||
}
|
||||
end
|
||||
|
||||
defmodule Bytes do
|
||||
@moduledoc "Bytes job."
|
||||
defstruct [status: :working, bytes: 0, unit: :bytes]
|
||||
defstruct status: :working, bytes: 0, unit: :bytes, type: :ota, time: nil
|
||||
|
||||
defimpl Inspect, for: __MODULE__ do
|
||||
def inspect(%{bytes: bytes}, _) do
|
||||
|
@ -33,11 +36,13 @@ defmodule Farmbot.BotState.JobProgress do
|
|||
end
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
status: Farmbot.BotState.JobProgress.status,
|
||||
bytes: integer,
|
||||
unit: :bytes
|
||||
}
|
||||
status: Farmbot.BotState.JobProgress.status(),
|
||||
bytes: integer,
|
||||
unit: :bytes,
|
||||
type: :image | :ota,
|
||||
time: DateTime.t()
|
||||
}
|
||||
end
|
||||
|
||||
@type t :: Bytes.t | Percent.t
|
||||
@type t :: Bytes.t() | Percent.t()
|
||||
end
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
defmodule Farmbot.BotState.LocationData do
|
||||
@moduledoc false
|
||||
defstruct [
|
||||
scaled_encoders: %{x: -1, y: -1, z: -1},
|
||||
raw_encoders: %{x: -1, y: -1, z: -1},
|
||||
position: %{x: -1, y: -1, z: -1}
|
||||
]
|
||||
end
|
|
@ -1,97 +0,0 @@
|
|||
defmodule Farmbot.BotState.McuParams do
|
||||
@moduledoc false
|
||||
defstruct [
|
||||
:pin_guard_4_time_out,
|
||||
:pin_guard_1_active_state,
|
||||
:encoder_scaling_y,
|
||||
:movement_invert_2_endpoints_x,
|
||||
:movement_min_spd_y,
|
||||
:pin_guard_2_time_out,
|
||||
:movement_timeout_y,
|
||||
:movement_home_at_boot_y,
|
||||
:movement_home_spd_z,
|
||||
:movement_invert_endpoints_z,
|
||||
:pin_guard_1_pin_nr,
|
||||
:movement_invert_endpoints_y,
|
||||
:movement_max_spd_y,
|
||||
:movement_home_up_y,
|
||||
:encoder_missed_steps_decay_z,
|
||||
:movement_home_spd_y,
|
||||
:encoder_use_for_pos_x,
|
||||
:movement_step_per_mm_x,
|
||||
:movement_home_at_boot_z,
|
||||
:movement_steps_acc_dec_z,
|
||||
:pin_guard_5_pin_nr,
|
||||
:movement_invert_motor_z,
|
||||
:movement_max_spd_x,
|
||||
:movement_enable_endpoints_y,
|
||||
:movement_enable_endpoints_z,
|
||||
:param_config_ok,
|
||||
:movement_stop_at_home_x,
|
||||
:movement_axis_nr_steps_y,
|
||||
:pin_guard_1_time_out,
|
||||
:movement_home_at_boot_x,
|
||||
:pin_guard_2_pin_nr,
|
||||
:encoder_scaling_z,
|
||||
:param_e_stop_on_mov_err,
|
||||
:encoder_enabled_x,
|
||||
:pin_guard_2_active_state,
|
||||
:encoder_missed_steps_decay_y,
|
||||
:param_use_eeprom,
|
||||
:movement_home_up_z,
|
||||
:movement_enable_endpoints_x,
|
||||
:movement_step_per_mm_y,
|
||||
:pin_guard_3_pin_nr,
|
||||
:param_mov_nr_retry,
|
||||
:movement_stop_at_home_z,
|
||||
:pin_guard_4_active_state,
|
||||
:movement_steps_acc_dec_y,
|
||||
:movement_home_spd_x,
|
||||
:movement_keep_active_x,
|
||||
:pin_guard_3_time_out,
|
||||
:movement_keep_active_y,
|
||||
:encoder_scaling_x,
|
||||
:param_version,
|
||||
:movement_invert_2_endpoints_z,
|
||||
:encoder_missed_steps_decay_x,
|
||||
:movement_timeout_z,
|
||||
:encoder_missed_steps_max_z,
|
||||
:movement_min_spd_z,
|
||||
:encoder_enabled_y,
|
||||
:encoder_type_y,
|
||||
:movement_home_up_x,
|
||||
:pin_guard_3_active_state,
|
||||
:movement_invert_motor_x,
|
||||
:movement_keep_active_z,
|
||||
:movement_max_spd_z,
|
||||
:movement_secondary_motor_invert_x,
|
||||
:movement_stop_at_max_x,
|
||||
:movement_steps_acc_dec_x,
|
||||
:pin_guard_4_pin_nr,
|
||||
:param_test,
|
||||
:encoder_type_x,
|
||||
:movement_invert_2_endpoints_y,
|
||||
:encoder_invert_y,
|
||||
:movement_axis_nr_steps_x,
|
||||
:movement_stop_at_max_z,
|
||||
:movement_invert_endpoints_x,
|
||||
:encoder_invert_z,
|
||||
:encoder_use_for_pos_z,
|
||||
:pin_guard_5_active_state,
|
||||
:movement_step_per_mm_z,
|
||||
:encoder_enabled_z,
|
||||
:movement_secondary_motor_x,
|
||||
:pin_guard_5_time_out,
|
||||
:movement_min_spd_x,
|
||||
:encoder_type_z,
|
||||
:movement_stop_at_max_y,
|
||||
:encoder_use_for_pos_y,
|
||||
:encoder_missed_steps_max_y,
|
||||
:movement_timeout_x,
|
||||
:movement_stop_at_home_y,
|
||||
:movement_axis_nr_steps_z,
|
||||
:encoder_invert_x,
|
||||
:encoder_missed_steps_max_x,
|
||||
:movement_invert_motor_y,
|
||||
]
|
||||
end
|
|
@ -1,4 +0,0 @@
|
|||
defmodule Farmbot.BotState.Pin do
|
||||
@moduledoc false
|
||||
defstruct [:mode, :value]
|
||||
end
|
|
@ -0,0 +1,94 @@
|
|||
defmodule Farmbot.BotStateNG do
|
||||
alias Farmbot.{
|
||||
BotStateNG,
|
||||
BotStateNG.McuParams,
|
||||
BotStateNG.LocationData,
|
||||
BotStateNG.InformationalSettings,
|
||||
BotStateNG.ProcessInfo,
|
||||
BotStateNG.Configuration
|
||||
}
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
embeds_one(:mcu_params, McuParams, on_replace: :update)
|
||||
embeds_one(:location_data, LocationData, on_replace: :update)
|
||||
embeds_one(:informational_settings, InformationalSettings, on_replace: :update)
|
||||
embeds_one(:process_info, ProcessInfo, on_replace: :update)
|
||||
embeds_one(:configuration, Configuration, on_replace: :update)
|
||||
field(:user_env, {:map, :string}, default: %{})
|
||||
field(:pins, {:map, {:map, :integer}}, default: %{})
|
||||
field(:jobs, {:map, :map}, default: %{})
|
||||
end
|
||||
|
||||
def new do
|
||||
%BotStateNG{}
|
||||
|> changeset(%{})
|
||||
|> put_embed(:mcu_params, McuParams.new())
|
||||
|> put_embed(:location_data, LocationData.new())
|
||||
|> put_embed(:informational_settings, InformationalSettings.new())
|
||||
|> put_embed(:process_info, ProcessInfo.new())
|
||||
|> put_embed(:configuration, Configuration.new())
|
||||
|> apply_changes()
|
||||
end
|
||||
|
||||
def changeset(bot_state, params \\ %{}) do
|
||||
bot_state
|
||||
|> cast(params, [:user_env, :pins, :jobs])
|
||||
|> cast_embed(:mcu_params, [])
|
||||
|> cast_embed(:location_data, [])
|
||||
|> cast_embed(:informational_settings, [])
|
||||
|> cast_embed(:process_info, [])
|
||||
|> cast_embed(:configuration, [])
|
||||
end
|
||||
|
||||
def view(bot_state) do
|
||||
%{
|
||||
mcu_params: McuParams.view(bot_state.mcu_params),
|
||||
location_data: LocationData.view(bot_state.location_data),
|
||||
informational_settings: InformationalSettings.view(bot_state.informational_settings),
|
||||
process_info: ProcessInfo.view(bot_state.process_info),
|
||||
configuration: Configuration.view(bot_state.configuration),
|
||||
user_env: bot_state.user_env,
|
||||
pins: bot_state.pins,
|
||||
jobs: bot_state.jobs
|
||||
}
|
||||
end
|
||||
|
||||
@doc "Add or update a pin to state.pins."
|
||||
def add_or_update_pin(state, number, mode, value) do
|
||||
cs = changeset(state, %{})
|
||||
|
||||
new_pins =
|
||||
cs
|
||||
|> get_field(:pins)
|
||||
|> Map.put(number, %{mode: mode, value: value})
|
||||
|
||||
put_change(cs, :pins, new_pins)
|
||||
end
|
||||
|
||||
def set_user_env(state, key, value) do
|
||||
cs = changeset(state, %{})
|
||||
|
||||
new_user_env =
|
||||
cs
|
||||
|> get_field(:user_env)
|
||||
|> Map.put(key, value)
|
||||
|
||||
put_change(cs, :user_env, new_user_env)
|
||||
end
|
||||
|
||||
def set_job_progress(state, name, progress) do
|
||||
cs = changeset(state, %{})
|
||||
|
||||
new_jobs =
|
||||
cs
|
||||
|> get_field(:jobs)
|
||||
|> Map.put(name, progress)
|
||||
|
||||
put_change(cs, :jobs, new_jobs)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,64 @@
|
|||
defmodule Farmbot.BotStateNG.Configuration do
|
||||
@moduledoc false
|
||||
alias Farmbot.BotStateNG.Configuration
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:arduino_debug_messages, :boolean)
|
||||
field(:auto_sync, :boolean)
|
||||
field(:beta_opt_in, :boolean)
|
||||
field(:disable_factory_reset, :boolean)
|
||||
field(:firmware_hardware, :string)
|
||||
field(:firmware_input_log, :boolean)
|
||||
field(:firmware_output_log, :boolean)
|
||||
field(:network_not_found_timer, :integer)
|
||||
field(:os_auto_update, :boolean)
|
||||
field(:sequence_body_log, :boolean)
|
||||
field(:sequence_complete_log, :boolean)
|
||||
field(:sequence_init_log, :boolean)
|
||||
end
|
||||
|
||||
def new do
|
||||
%Configuration{}
|
||||
|> changeset(%{})
|
||||
|> apply_changes()
|
||||
end
|
||||
|
||||
def view(configuration) do
|
||||
%{
|
||||
arduino_debug_messages: configuration.arduino_debug_messages,
|
||||
auto_sync: configuration.auto_sync,
|
||||
beta_opt_in: configuration.beta_opt_in,
|
||||
disable_factory_reset: configuration.disable_factory_reset,
|
||||
firmware_hardware: configuration.firmware_hardware,
|
||||
firmware_input_log: configuration.firmware_input_log,
|
||||
firmware_output_log: configuration.firmware_output_log,
|
||||
network_not_found_timer: configuration.network_not_found_timer,
|
||||
os_auto_update: configuration.os_auto_update,
|
||||
sequence_body_log: configuration.sequence_body_log,
|
||||
sequence_complete_log: configuration.sequence_complete_log,
|
||||
sequence_init_log: configuration.sequence_init_log
|
||||
}
|
||||
end
|
||||
|
||||
def changeset(configuration, params \\ %{}) do
|
||||
configuration
|
||||
|> cast(params, [
|
||||
:arduino_debug_messages,
|
||||
:auto_sync,
|
||||
:beta_opt_in,
|
||||
:disable_factory_reset,
|
||||
:firmware_hardware,
|
||||
:firmware_input_log,
|
||||
:firmware_output_log,
|
||||
:network_not_found_timer,
|
||||
:os_auto_update,
|
||||
:sequence_body_log,
|
||||
:sequence_complete_log,
|
||||
:sequence_init_log
|
||||
])
|
||||
end
|
||||
end
|
|
@ -0,0 +1,81 @@
|
|||
defmodule Farmbot.BotStateNG.InformationalSettings do
|
||||
@moduledoc false
|
||||
alias Farmbot.BotStateNG.InformationalSettings
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Farmbot.Project
|
||||
|
||||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:target, :string, default: to_string(Project.target()))
|
||||
field(:env, :string, default: to_string(Project.env()))
|
||||
field(:controller_version, :string, default: Project.version())
|
||||
field(:firmware_commit, :string, default: Project.arduino_commit())
|
||||
field(:commit, :string, default: Project.commit())
|
||||
field(:firmware_version, :string)
|
||||
field(:node_name, :string)
|
||||
field(:soc_temp, :integer)
|
||||
field(:wifi_level, :integer)
|
||||
field(:uptime, :integer)
|
||||
field(:memory_usage, :integer)
|
||||
field(:disk_usage, :integer)
|
||||
field(:sync_status, :string, default: "sync_now")
|
||||
field(:locked, :boolean, default: false)
|
||||
field(:last_status, :string)
|
||||
field(:cache_bust, :integer)
|
||||
field(:busy, :boolean)
|
||||
end
|
||||
|
||||
def new do
|
||||
%InformationalSettings{}
|
||||
|> changeset(%{})
|
||||
|> apply_changes()
|
||||
end
|
||||
|
||||
def view(informational_settings) do
|
||||
%{
|
||||
target: informational_settings.target,
|
||||
env: informational_settings.env,
|
||||
controller_version: informational_settings.controller_version,
|
||||
firmware_commit: informational_settings.firmware_commit,
|
||||
commit: informational_settings.commit,
|
||||
firmware_version: informational_settings.firmware_version,
|
||||
node_name: informational_settings.node_name,
|
||||
soc_temp: informational_settings.soc_temp,
|
||||
wifi_level: informational_settings.wifi_level,
|
||||
uptime: informational_settings.uptime,
|
||||
memory_usage: informational_settings.memory_usage,
|
||||
disk_usage: informational_settings.disk_usage,
|
||||
sync_status: informational_settings.sync_status,
|
||||
locked: informational_settings.locked,
|
||||
last_status: informational_settings.last_status,
|
||||
cache_bust: informational_settings.cache_bust,
|
||||
busy: informational_settings.busy
|
||||
}
|
||||
end
|
||||
|
||||
def changeset(informational_settings, params \\ %{}) do
|
||||
informational_settings
|
||||
|> cast(params, [
|
||||
:target,
|
||||
:env,
|
||||
:controller_version,
|
||||
:firmware_commit,
|
||||
:commit,
|
||||
:firmware_version,
|
||||
:node_name,
|
||||
:soc_temp,
|
||||
:wifi_level,
|
||||
:uptime,
|
||||
:memory_usage,
|
||||
:disk_usage,
|
||||
:sync_status,
|
||||
:locked,
|
||||
:last_status,
|
||||
:cache_bust,
|
||||
:busy
|
||||
])
|
||||
end
|
||||
end
|
|
@ -0,0 +1,72 @@
|
|||
defmodule Farmbot.BotStateNG.LocationData do
|
||||
@moduledoc false
|
||||
alias Farmbot.BotStateNG.LocationData
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key false
|
||||
|
||||
defmodule Vec3 do
|
||||
@moduledoc false
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:x, :float)
|
||||
field(:y, :float)
|
||||
field(:z, :float)
|
||||
end
|
||||
|
||||
def new do
|
||||
%__MODULE__{}
|
||||
|> changeset(%{x: -1, y: -1, z: -1})
|
||||
|> apply_changes()
|
||||
end
|
||||
|
||||
def view(vec3) do
|
||||
%{
|
||||
x: vec3.x,
|
||||
y: vec3.y,
|
||||
z: vec3.z
|
||||
}
|
||||
end
|
||||
|
||||
def changeset(vec3, params \\ %{}) do
|
||||
vec3
|
||||
|> cast(params, [:x, :y, :z])
|
||||
end
|
||||
end
|
||||
|
||||
embedded_schema do
|
||||
embeds_one(:scaled_encoders, Vec3, on_replace: :update)
|
||||
embeds_one(:raw_encoders, Vec3, on_replace: :update)
|
||||
embeds_one(:position, Vec3, on_replace: :update)
|
||||
end
|
||||
|
||||
def new do
|
||||
%LocationData{}
|
||||
|> changeset(%{})
|
||||
|> put_embed(:scaled_encoders, Vec3.new(), [])
|
||||
|> put_embed(:raw_encoders, Vec3.new(), [])
|
||||
|> put_embed(:position, Vec3.new(), [])
|
||||
|> apply_changes()
|
||||
end
|
||||
|
||||
def view(location_data) do
|
||||
%{
|
||||
scaled_encoders: Vec3.view(location_data.scaled_encoders),
|
||||
raw_encoders: Vec3.view(location_data.raw_encoders),
|
||||
position: Vec3.view(location_data.position)
|
||||
}
|
||||
end
|
||||
|
||||
def changeset(location_data, params \\ %{}) do
|
||||
location_data
|
||||
|> cast(params, [])
|
||||
|> cast_embed(:scaled_encoders)
|
||||
|> cast_embed(:raw_encoders)
|
||||
|> cast_embed(:position)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,292 @@
|
|||
defmodule Farmbot.BotStateNG.McuParams do
|
||||
@moduledoc false
|
||||
alias Farmbot.BotStateNG.McuParams
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:pin_guard_4_time_out, :float)
|
||||
field(:pin_guard_1_active_state, :float)
|
||||
field(:encoder_scaling_y, :float)
|
||||
field(:movement_invert_2_endpoints_x, :float)
|
||||
field(:movement_min_spd_y, :float)
|
||||
field(:pin_guard_2_time_out, :float)
|
||||
field(:movement_timeout_y, :float)
|
||||
field(:movement_home_at_boot_y, :float)
|
||||
field(:movement_home_spd_z, :float)
|
||||
field(:movement_invert_endpoints_z, :float)
|
||||
field(:pin_guard_1_pin_nr, :float)
|
||||
field(:movement_invert_endpoints_y, :float)
|
||||
field(:movement_max_spd_y, :float)
|
||||
field(:movement_home_up_y, :float)
|
||||
field(:encoder_missed_steps_decay_z, :float)
|
||||
field(:movement_home_spd_y, :float)
|
||||
field(:encoder_use_for_pos_x, :float)
|
||||
field(:movement_step_per_mm_x, :float)
|
||||
field(:movement_home_at_boot_z, :float)
|
||||
field(:movement_steps_acc_dec_z, :float)
|
||||
field(:pin_guard_5_pin_nr, :float)
|
||||
field(:movement_invert_motor_z, :float)
|
||||
field(:movement_max_spd_x, :float)
|
||||
field(:movement_enable_endpoints_y, :float)
|
||||
field(:movement_enable_endpoints_z, :float)
|
||||
field(:movement_stop_at_home_x, :float)
|
||||
field(:movement_axis_nr_steps_y, :float)
|
||||
field(:pin_guard_1_time_out, :float)
|
||||
field(:movement_home_at_boot_x, :float)
|
||||
field(:pin_guard_2_pin_nr, :float)
|
||||
field(:encoder_scaling_z, :float)
|
||||
field(:param_e_stop_on_mov_err, :float)
|
||||
field(:encoder_enabled_x, :float)
|
||||
field(:pin_guard_2_active_state, :float)
|
||||
field(:encoder_missed_steps_decay_y, :float)
|
||||
field(:movement_home_up_z, :float)
|
||||
field(:movement_enable_endpoints_x, :float)
|
||||
field(:movement_step_per_mm_y, :float)
|
||||
field(:pin_guard_3_pin_nr, :float)
|
||||
field(:param_mov_nr_retry, :float)
|
||||
field(:movement_stop_at_home_z, :float)
|
||||
field(:pin_guard_4_active_state, :float)
|
||||
field(:movement_steps_acc_dec_y, :float)
|
||||
field(:movement_home_spd_x, :float)
|
||||
field(:movement_keep_active_x, :float)
|
||||
field(:pin_guard_3_time_out, :float)
|
||||
field(:movement_keep_active_y, :float)
|
||||
field(:encoder_scaling_x, :float)
|
||||
field(:movement_invert_2_endpoints_z, :float)
|
||||
field(:encoder_missed_steps_decay_x, :float)
|
||||
field(:movement_timeout_z, :float)
|
||||
field(:encoder_missed_steps_max_z, :float)
|
||||
field(:movement_min_spd_z, :float)
|
||||
field(:encoder_enabled_y, :float)
|
||||
field(:encoder_type_y, :float)
|
||||
field(:movement_home_up_x, :float)
|
||||
field(:pin_guard_3_active_state, :float)
|
||||
field(:movement_invert_motor_x, :float)
|
||||
field(:movement_keep_active_z, :float)
|
||||
field(:movement_max_spd_z, :float)
|
||||
field(:movement_secondary_motor_invert_x, :float)
|
||||
field(:movement_stop_at_max_x, :float)
|
||||
field(:movement_steps_acc_dec_x, :float)
|
||||
field(:pin_guard_4_pin_nr, :float)
|
||||
field(:encoder_type_x, :float)
|
||||
field(:movement_invert_2_endpoints_y, :float)
|
||||
field(:encoder_invert_y, :float)
|
||||
field(:movement_axis_nr_steps_x, :float)
|
||||
field(:movement_stop_at_max_z, :float)
|
||||
field(:movement_invert_endpoints_x, :float)
|
||||
field(:encoder_invert_z, :float)
|
||||
field(:encoder_use_for_pos_z, :float)
|
||||
field(:pin_guard_5_active_state, :float)
|
||||
field(:movement_step_per_mm_z, :float)
|
||||
field(:encoder_enabled_z, :float)
|
||||
field(:movement_secondary_motor_x, :float)
|
||||
field(:pin_guard_5_time_out, :float)
|
||||
field(:movement_min_spd_x, :float)
|
||||
field(:encoder_type_z, :float)
|
||||
field(:movement_stop_at_max_y, :float)
|
||||
field(:encoder_use_for_pos_y, :float)
|
||||
field(:encoder_missed_steps_max_y, :float)
|
||||
field(:movement_timeout_x, :float)
|
||||
field(:movement_stop_at_home_y, :float)
|
||||
field(:movement_axis_nr_steps_z, :float)
|
||||
field(:encoder_invert_x, :float)
|
||||
field(:encoder_missed_steps_max_x, :float)
|
||||
field(:movement_invert_motor_y, :float)
|
||||
end
|
||||
|
||||
def new() do
|
||||
%McuParams{}
|
||||
|> changeset(%{})
|
||||
|> apply_changes()
|
||||
end
|
||||
|
||||
def view(mcu_params) do
|
||||
%{
|
||||
pin_guard_4_time_out: mcu_params.pin_guard_4_time_out,
|
||||
pin_guard_1_active_state: mcu_params.pin_guard_1_active_state,
|
||||
encoder_scaling_y: mcu_params.encoder_scaling_y,
|
||||
movement_invert_2_endpoints_x: mcu_params.movement_invert_2_endpoints_x,
|
||||
movement_min_spd_y: mcu_params.movement_min_spd_y,
|
||||
pin_guard_2_time_out: mcu_params.pin_guard_2_time_out,
|
||||
movement_timeout_y: mcu_params.movement_timeout_y,
|
||||
movement_home_at_boot_y: mcu_params.movement_home_at_boot_y,
|
||||
movement_home_spd_z: mcu_params.movement_home_spd_z,
|
||||
movement_invert_endpoints_z: mcu_params.movement_invert_endpoints_z,
|
||||
pin_guard_1_pin_nr: mcu_params.pin_guard_1_pin_nr,
|
||||
movement_invert_endpoints_y: mcu_params.movement_invert_endpoints_y,
|
||||
movement_max_spd_y: mcu_params.movement_max_spd_y,
|
||||
movement_home_up_y: mcu_params.movement_home_up_y,
|
||||
encoder_missed_steps_decay_z: mcu_params.encoder_missed_steps_decay_z,
|
||||
movement_home_spd_y: mcu_params.movement_home_spd_y,
|
||||
encoder_use_for_pos_x: mcu_params.encoder_use_for_pos_x,
|
||||
movement_step_per_mm_x: mcu_params.movement_step_per_mm_x,
|
||||
movement_home_at_boot_z: mcu_params.movement_home_at_boot_z,
|
||||
movement_steps_acc_dec_z: mcu_params.movement_steps_acc_dec_z,
|
||||
pin_guard_5_pin_nr: mcu_params.pin_guard_5_pin_nr,
|
||||
movement_invert_motor_z: mcu_params.movement_invert_motor_z,
|
||||
movement_max_spd_x: mcu_params.movement_max_spd_x,
|
||||
movement_enable_endpoints_y: mcu_params.movement_enable_endpoints_y,
|
||||
movement_enable_endpoints_z: mcu_params.movement_enable_endpoints_z,
|
||||
movement_stop_at_home_x: mcu_params.movement_stop_at_home_x,
|
||||
movement_axis_nr_steps_y: mcu_params.movement_axis_nr_steps_y,
|
||||
pin_guard_1_time_out: mcu_params.pin_guard_1_time_out,
|
||||
movement_home_at_boot_x: mcu_params.movement_home_at_boot_x,
|
||||
pin_guard_2_pin_nr: mcu_params.pin_guard_2_pin_nr,
|
||||
encoder_scaling_z: mcu_params.encoder_scaling_z,
|
||||
param_e_stop_on_mov_err: mcu_params.param_e_stop_on_mov_err,
|
||||
encoder_enabled_x: mcu_params.encoder_enabled_x,
|
||||
pin_guard_2_active_state: mcu_params.pin_guard_2_active_state,
|
||||
encoder_missed_steps_decay_y: mcu_params.encoder_missed_steps_decay_y,
|
||||
movement_home_up_z: mcu_params.movement_home_up_z,
|
||||
movement_enable_endpoints_x: mcu_params.movement_enable_endpoints_x,
|
||||
movement_step_per_mm_y: mcu_params.movement_step_per_mm_y,
|
||||
pin_guard_3_pin_nr: mcu_params.pin_guard_3_pin_nr,
|
||||
param_mov_nr_retry: mcu_params.param_mov_nr_retry,
|
||||
movement_stop_at_home_z: mcu_params.movement_stop_at_home_z,
|
||||
pin_guard_4_active_state: mcu_params.pin_guard_4_active_state,
|
||||
movement_steps_acc_dec_y: mcu_params.movement_steps_acc_dec_y,
|
||||
movement_home_spd_x: mcu_params.movement_home_spd_x,
|
||||
movement_keep_active_x: mcu_params.movement_keep_active_x,
|
||||
pin_guard_3_time_out: mcu_params.pin_guard_3_time_out,
|
||||
movement_keep_active_y: mcu_params.movement_keep_active_y,
|
||||
encoder_scaling_x: mcu_params.encoder_scaling_x,
|
||||
movement_invert_2_endpoints_z: mcu_params.movement_invert_2_endpoints_z,
|
||||
encoder_missed_steps_decay_x: mcu_params.encoder_missed_steps_decay_x,
|
||||
movement_timeout_z: mcu_params.movement_timeout_z,
|
||||
encoder_missed_steps_max_z: mcu_params.encoder_missed_steps_max_z,
|
||||
movement_min_spd_z: mcu_params.movement_min_spd_z,
|
||||
encoder_enabled_y: mcu_params.encoder_enabled_y,
|
||||
encoder_type_y: mcu_params.encoder_type_y,
|
||||
movement_home_up_x: mcu_params.movement_home_up_x,
|
||||
pin_guard_3_active_state: mcu_params.pin_guard_3_active_state,
|
||||
movement_invert_motor_x: mcu_params.movement_invert_motor_x,
|
||||
movement_keep_active_z: mcu_params.movement_keep_active_z,
|
||||
movement_max_spd_z: mcu_params.movement_max_spd_z,
|
||||
movement_secondary_motor_invert_x: mcu_params.movement_secondary_motor_invert_x,
|
||||
movement_stop_at_max_x: mcu_params.movement_stop_at_max_x,
|
||||
movement_steps_acc_dec_x: mcu_params.movement_steps_acc_dec_x,
|
||||
pin_guard_4_pin_nr: mcu_params.pin_guard_4_pin_nr,
|
||||
encoder_type_x: mcu_params.encoder_type_x,
|
||||
movement_invert_2_endpoints_y: mcu_params.movement_invert_2_endpoints_y,
|
||||
encoder_invert_y: mcu_params.encoder_invert_y,
|
||||
movement_axis_nr_steps_x: mcu_params.movement_axis_nr_steps_x,
|
||||
movement_stop_at_max_z: mcu_params.movement_stop_at_max_z,
|
||||
movement_invert_endpoints_x: mcu_params.movement_invert_endpoints_x,
|
||||
encoder_invert_z: mcu_params.encoder_invert_z,
|
||||
encoder_use_for_pos_z: mcu_params.encoder_use_for_pos_z,
|
||||
pin_guard_5_active_state: mcu_params.pin_guard_5_active_state,
|
||||
movement_step_per_mm_z: mcu_params.movement_step_per_mm_z,
|
||||
encoder_enabled_z: mcu_params.encoder_enabled_z,
|
||||
movement_secondary_motor_x: mcu_params.movement_secondary_motor_x,
|
||||
pin_guard_5_time_out: mcu_params.pin_guard_5_time_out,
|
||||
movement_min_spd_x: mcu_params.movement_min_spd_x,
|
||||
encoder_type_z: mcu_params.encoder_type_z,
|
||||
movement_stop_at_max_y: mcu_params.movement_stop_at_max_y,
|
||||
encoder_use_for_pos_y: mcu_params.encoder_use_for_pos_y,
|
||||
encoder_missed_steps_max_y: mcu_params.encoder_missed_steps_max_y,
|
||||
movement_timeout_x: mcu_params.movement_timeout_x,
|
||||
movement_stop_at_home_y: mcu_params.movement_stop_at_home_y,
|
||||
movement_axis_nr_steps_z: mcu_params.movement_axis_nr_steps_z,
|
||||
encoder_invert_x: mcu_params.encoder_invert_x,
|
||||
encoder_missed_steps_max_x: mcu_params.encoder_missed_steps_max_x,
|
||||
movement_invert_motor_y: mcu_params.movement_invert_motor_y
|
||||
}
|
||||
end
|
||||
|
||||
def changeset(mcu_params, params \\ %{}) do
|
||||
mcu_params
|
||||
|> cast(params, [
|
||||
:pin_guard_4_time_out,
|
||||
:pin_guard_1_active_state,
|
||||
:encoder_scaling_y,
|
||||
:movement_invert_2_endpoints_x,
|
||||
:movement_min_spd_y,
|
||||
:pin_guard_2_time_out,
|
||||
:movement_timeout_y,
|
||||
:movement_home_at_boot_y,
|
||||
:movement_home_spd_z,
|
||||
:movement_invert_endpoints_z,
|
||||
:pin_guard_1_pin_nr,
|
||||
:movement_invert_endpoints_y,
|
||||
:movement_max_spd_y,
|
||||
:movement_home_up_y,
|
||||
:encoder_missed_steps_decay_z,
|
||||
:movement_home_spd_y,
|
||||
:encoder_use_for_pos_x,
|
||||
:movement_step_per_mm_x,
|
||||
:movement_home_at_boot_z,
|
||||
:movement_steps_acc_dec_z,
|
||||
:pin_guard_5_pin_nr,
|
||||
:movement_invert_motor_z,
|
||||
:movement_max_spd_x,
|
||||
:movement_enable_endpoints_y,
|
||||
:movement_enable_endpoints_z,
|
||||
:movement_stop_at_home_x,
|
||||
:movement_axis_nr_steps_y,
|
||||
:pin_guard_1_time_out,
|
||||
:movement_home_at_boot_x,
|
||||
:pin_guard_2_pin_nr,
|
||||
:encoder_scaling_z,
|
||||
:param_e_stop_on_mov_err,
|
||||
:encoder_enabled_x,
|
||||
:pin_guard_2_active_state,
|
||||
:encoder_missed_steps_decay_y,
|
||||
:movement_home_up_z,
|
||||
:movement_enable_endpoints_x,
|
||||
:movement_step_per_mm_y,
|
||||
:pin_guard_3_pin_nr,
|
||||
:param_mov_nr_retry,
|
||||
:movement_stop_at_home_z,
|
||||
:pin_guard_4_active_state,
|
||||
:movement_steps_acc_dec_y,
|
||||
:movement_home_spd_x,
|
||||
:movement_keep_active_x,
|
||||
:pin_guard_3_time_out,
|
||||
:movement_keep_active_y,
|
||||
:encoder_scaling_x,
|
||||
:movement_invert_2_endpoints_z,
|
||||
:encoder_missed_steps_decay_x,
|
||||
:movement_timeout_z,
|
||||
:encoder_missed_steps_max_z,
|
||||
:movement_min_spd_z,
|
||||
:encoder_enabled_y,
|
||||
:encoder_type_y,
|
||||
:movement_home_up_x,
|
||||
:pin_guard_3_active_state,
|
||||
:movement_invert_motor_x,
|
||||
:movement_keep_active_z,
|
||||
:movement_max_spd_z,
|
||||
:movement_secondary_motor_invert_x,
|
||||
:movement_stop_at_max_x,
|
||||
:movement_steps_acc_dec_x,
|
||||
:pin_guard_4_pin_nr,
|
||||
:encoder_type_x,
|
||||
:movement_invert_2_endpoints_y,
|
||||
:encoder_invert_y,
|
||||
:movement_axis_nr_steps_x,
|
||||
:movement_stop_at_max_z,
|
||||
:movement_invert_endpoints_x,
|
||||
:encoder_invert_z,
|
||||
:encoder_use_for_pos_z,
|
||||
:pin_guard_5_active_state,
|
||||
:movement_step_per_mm_z,
|
||||
:encoder_enabled_z,
|
||||
:movement_secondary_motor_x,
|
||||
:pin_guard_5_time_out,
|
||||
:movement_min_spd_x,
|
||||
:encoder_type_z,
|
||||
:movement_stop_at_max_y,
|
||||
:encoder_use_for_pos_y,
|
||||
:encoder_missed_steps_max_y,
|
||||
:movement_timeout_x,
|
||||
:movement_stop_at_home_y,
|
||||
:movement_axis_nr_steps_z,
|
||||
:encoder_invert_x,
|
||||
:encoder_missed_steps_max_x,
|
||||
:movement_invert_motor_y
|
||||
])
|
||||
end
|
||||
end
|
|
@ -0,0 +1,27 @@
|
|||
defmodule Farmbot.BotStateNG.ProcessInfo do
|
||||
@moduledoc false
|
||||
alias Farmbot.BotStateNG.ProcessInfo
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:farmwares, {:map, {:map, :any}}, default: %{})
|
||||
end
|
||||
|
||||
def new do
|
||||
%ProcessInfo{}
|
||||
|> changeset(%{})
|
||||
|> apply_changes()
|
||||
end
|
||||
|
||||
def view(process_info) do
|
||||
%{farmwares: process_info.farmwares}
|
||||
end
|
||||
|
||||
def changeset(process_info, params \\ %{}) do
|
||||
process_info
|
||||
|> cast(params, [:farmwares])
|
||||
end
|
||||
end
|
|
@ -34,7 +34,7 @@ defmodule Farmbot.Core.CeleryScript.IOLayer do
|
|||
@callback move_absolute(args, body) :: :ok | {:error, String.t()}
|
||||
|
||||
# Complex IO.
|
||||
# @callbcak _if(args, body) :: {:ok, AST.t} | {:error, String.t}
|
||||
# @callbcak _if(args, body) :: {:ok, AST.t()} | {:error, String.t()}
|
||||
@callback execute(args, body) :: {:ok, AST.t()} | {:error, String.t()}
|
||||
|
||||
# Special IO.
|
||||
|
|
|
@ -82,7 +82,6 @@ defmodule Farmbot.Config do
|
|||
|> apply(:"get_#{type}_value", [group_name, key_name])
|
||||
|> Ecto.Changeset.change(value: value)
|
||||
|> Repo.update!()
|
||||
|> dispatch(group_name, key_name)
|
||||
end
|
||||
|
||||
def update_config_value(type, _, _, _) do
|
||||
|
@ -140,9 +139,4 @@ defmodule Farmbot.Config do
|
|||
[group_id] = from(g in Group, where: g.group_name == ^group_name, select: g.id) |> Repo.all()
|
||||
group_id
|
||||
end
|
||||
|
||||
defp dispatch(%{value: value} = val, group, key) do
|
||||
Farmbot.Registry.dispatch(__MODULE__, {group, key, value})
|
||||
val
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,14 +10,13 @@ defmodule Farmbot.Core do
|
|||
|
||||
def init([]) do
|
||||
children = [
|
||||
{Farmbot.Registry, []},
|
||||
{Farmbot.BotState, []},
|
||||
{Farmbot.Logger.Supervisor, []},
|
||||
{Farmbot.Config.Supervisor, []},
|
||||
{Farmbot.Firmware.Supervisor, []},
|
||||
{Farmbot.Asset.Supervisor, []},
|
||||
{Farmbot.BotState, []},
|
||||
{Farmbot.Core.FirmwareSupervisor, []},
|
||||
{Farmbot.Core.CeleryScript.Supervisor, []},
|
||||
]
|
||||
Supervisor.init(children, [strategy: :one_for_one])
|
||||
Supervisor.init(children, [strategy: :one_for_all])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,6 @@ defmodule Farmbot.FarmwareRuntime do
|
|||
import Farmbot.AssetWorker.Farmbot.Asset.FarmwareInstallation, only: [install_dir: 1]
|
||||
|
||||
alias Farmbot.Asset
|
||||
alias Asset.FarmwareInstallation.Manifest
|
||||
alias Farmbot.FarmwareRuntime.PlugWrapper
|
||||
import Farmbot.Config, only: [get_config_value: 3]
|
||||
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
defmodule Farmbot.Firmware.Command do
|
||||
@moduledoc """
|
||||
Structured data to be sent to the Firmware.
|
||||
"""
|
||||
alias Farmbot.Firmware.{Command, Utils}
|
||||
import Utils
|
||||
|
||||
defstruct [:fun, :args, :from, :status]
|
||||
|
||||
@doc "Add a status message to the Command."
|
||||
def add_status(%Command{} = command, status) do
|
||||
%{command | status: (command.status || []) ++ [status]}
|
||||
end
|
||||
|
||||
def add_status(not_command, _), do: not_command
|
||||
|
||||
def format_args(%Farmbot.Firmware.Vec3{x: x, y: y, z: z}) do
|
||||
"#{fmnt_float(x)}, #{fmnt_float(y)}, #{fmnt_float(z)}"
|
||||
end
|
||||
|
||||
def format_args(arg) when is_atom(arg), do: to_string(arg)
|
||||
def format_args(arg) when is_binary(arg), do: arg
|
||||
def format_args(arg), do: inspect(arg)
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Farmbot.Firmware.Command do
|
||||
def inspect(cmd, _) do
|
||||
args = Enum.map(cmd.args, &Farmbot.Firmware.Command.format_args(&1))
|
||||
"#{cmd.fun}(#{Enum.join(args, ", ")})"
|
||||
end
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Farmbot.Firmware.Command do
|
||||
def to_string(cmd) do
|
||||
args = Enum.map(cmd.args, &Farmbot.Firmware.Command.format_args(&1))
|
||||
"#{cmd.fun}(#{Enum.join(args, ", ")})"
|
||||
end
|
||||
end
|
|
@ -1,83 +0,0 @@
|
|||
defmodule Farmbot.Firmware.CompletionLogs do
|
||||
@moduledoc false
|
||||
require Farmbot.Logger
|
||||
import Farmbot.Config, only: [get_config_value: 3]
|
||||
alias Farmbot.Firmware.Command
|
||||
|
||||
def maybe_log_complete(%Command{fun: :move_absolute, args: [pos | _]}, {:error, _reason}) do
|
||||
unless get_config_value(:bool, "settings", "firmware_input_log") do
|
||||
Farmbot.Logger.error 1, "Movement to #{inspect pos} failed."
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_log_complete(%Command{fun: :move_absolute, args: [pos | _]} = current, _reply) do
|
||||
unless get_config_value(:bool, "settings", "firmware_input_log") do
|
||||
if current.status do
|
||||
pos = Enum.reduce(current.status, pos, fn(status, pos) ->
|
||||
case status do
|
||||
{:report_axis_changed_x, new_pos} -> %{pos | x: new_pos}
|
||||
{:report_axis_changed_y, new_pos} -> %{pos | y: new_pos}
|
||||
{:report_axis_changed_z, new_pos} -> %{pos | z: new_pos}
|
||||
_ -> pos
|
||||
end
|
||||
end)
|
||||
Farmbot.Logger.success 1, "Movement to #{inspect pos} complete. (Stopped at end)"
|
||||
else
|
||||
Farmbot.Logger.success 1, "Movement to #{inspect pos} complete."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_log_complete(%Command{fun: :home}, {:error, _reason}) do
|
||||
unless get_config_value(:bool, "settings", "firmware_input_log") do
|
||||
Farmbot.Logger.error 1, "Movement to (0, 0, 0) failed."
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_log_complete(%Command{fun: :home_all}, _reply) do
|
||||
unless get_config_value(:bool, "settings", "firmware_input_log") do
|
||||
Farmbot.Logger.success 1, "Movement to (0, 0, 0) complete."
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_log_complete(_command, {:error, :report_axis_home_complete_x}) do
|
||||
unless get_config_value(:bool, "settings", "firmware_input_log") do
|
||||
Farmbot.Logger.success 2, "X Axis homing complete."
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_log_complete(_command, {:error, :report_axis_home_complete_y}) do
|
||||
unless get_config_value(:bool, "settings", "firmware_input_log") do
|
||||
Farmbot.Logger.success 2, "Y Axis homing complete."
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_log_complete(_command, {:error, :report_axis_home_complete_z}) do
|
||||
unless get_config_value(:bool, "settings", "firmware_input_log") do
|
||||
Farmbot.Logger.success 2, "Z Axis homing complete."
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_log_complete(_command, {:error, :report_axis_timeout_x}) do
|
||||
unless get_config_value(:bool, "settings", "firmware_input_log") do
|
||||
Farmbot.Logger.error 2, "X Axis timeout."
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_log_complete(_command, {:error, :report_axis_timeout_y}) do
|
||||
unless get_config_value(:bool, "settings", "firmware_input_log") do
|
||||
Farmbot.Logger.error 2, "Y Axis timeout."
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_log_complete(_command, {:error, :report_axis_timeout_z}) do
|
||||
unless get_config_value(:bool, "settings", "firmware_input_log") do
|
||||
Farmbot.Logger.error 2, "Z Axis timeout."
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_log_complete(_command, _result) do
|
||||
# IO.puts "#{command} => #{inspect result}"
|
||||
:ok
|
||||
end
|
||||
end
|
|
@ -1,74 +1,58 @@
|
|||
defmodule Farmbot.Firmware.EstopTimer do
|
||||
defmodule Farmbot.Core.FirmwareEstopTimer do
|
||||
@moduledoc """
|
||||
Module responsible for timing emails about E stops.
|
||||
Process that wraps a `Process.send_after/3` call.
|
||||
When `:timeout` is received, a `fatal_email` log message will be
|
||||
dispatched.
|
||||
"""
|
||||
|
||||
use GenServer
|
||||
require Farmbot.Logger
|
||||
|
||||
@msg "Farmbot has been E-Stopped for more than 10 minutes."
|
||||
# Ten minutes.
|
||||
@timer_ms 600_000
|
||||
# fifteen seconds.
|
||||
# @timer_ms 15000
|
||||
|
||||
@doc "Checks if the timer is active."
|
||||
def timer_active? do
|
||||
GenServer.call(__MODULE__, :timer_active?)
|
||||
@ten_minutes_ms 60_0000
|
||||
|
||||
def start_timer(timer_server \\ __MODULE__) do
|
||||
GenServer.call(timer_server, :start_timer)
|
||||
end
|
||||
|
||||
@doc "Starts a new timer if one isn't started."
|
||||
def start_timer do
|
||||
GenServer.call(__MODULE__, :start_timer)
|
||||
def cancel_timer(timer_server \\ __MODULE__) do
|
||||
GenServer.call(timer_server, :cancel_timer)
|
||||
end
|
||||
|
||||
@doc "Cancels a timer if it exists."
|
||||
def cancel_timer do
|
||||
GenServer.call(__MODULE__, :cancel_timer)
|
||||
@doc """
|
||||
optional args:
|
||||
* `timeout_ms` - amount of milliseconds to run timer for
|
||||
* `timeout_function` - function to call instead of logging
|
||||
opts - GenServer.options()
|
||||
"""
|
||||
@spec start_link(Keyword.t(), GenServer.options()) :: GenServer.on_start()
|
||||
def start_link(args, opts \\ [name: __MODULE__]) do
|
||||
GenServer.start_link(__MODULE__, args, opts)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def start_link(args) do
|
||||
GenServer.start_link(__MODULE__, args, [name: __MODULE__])
|
||||
end
|
||||
|
||||
@doc false
|
||||
def init([]) do
|
||||
{:ok, %{timer: nil, already_sent: false}}
|
||||
end
|
||||
|
||||
def handle_call(:timer_active?, _, state) do
|
||||
{:reply, is_timer_active?(state.timer), state}
|
||||
def init(args) do
|
||||
timeout_ms = Keyword.get(args, :timeout_ms, @ten_minutes_ms)
|
||||
timeout_fun = Keyword.get(args, :timeout_function, &do_log/0)
|
||||
state = %{timer: nil, timeout_ms: timeout_ms, timeout_function: timeout_fun}
|
||||
{:ok, state, :hibernate}
|
||||
end
|
||||
|
||||
def handle_call(:start_timer, _from, state) do
|
||||
if is_timer_active?(state.timer) do
|
||||
{:reply, :ok, state}
|
||||
else
|
||||
{:reply, :ok, %{state | timer: do_start_timer(self())}}
|
||||
end
|
||||
timer = Process.send_after(self(), :timeout, state.timeout_ms)
|
||||
{:reply, timer, %{state | timer: timer}}
|
||||
end
|
||||
|
||||
def handle_call(:cancel_timer, _from, state) do
|
||||
if is_timer_active?(state.timer) do
|
||||
Process.cancel_timer(state.timer)
|
||||
end
|
||||
{:reply, :ok, %{state | timer: nil, already_sent: false}}
|
||||
state.timer && Process.cancel_timer(state.timer)
|
||||
{:reply, state.timer, %{state | timer: nil}, :hibernate}
|
||||
end
|
||||
|
||||
def handle_info(:timer, state) do
|
||||
if state.already_sent do
|
||||
{:noreply, %{state | timer: nil}}
|
||||
else
|
||||
Farmbot.Logger.warn 1, @msg, [channels: [:fatal_email]]
|
||||
{:noreply, %{state | timer: nil, already_sent: true}}
|
||||
end
|
||||
def handle_info(:timeout, state) do
|
||||
_ = apply(state.timeout_function, [])
|
||||
{:noreply, %{state | timer: nil}, :hibernate}
|
||||
end
|
||||
|
||||
defp is_timer_active?(timer) do
|
||||
if timer, do: is_number(Process.read_timer(timer)), else: false
|
||||
end
|
||||
|
||||
defp do_start_timer(pid) do
|
||||
Process.send_after(pid, :timer, @timer_ms)
|
||||
end
|
||||
@doc false
|
||||
def do_log, do: Farmbot.Logger.warn(1, @msg, channels: [:fatal_email])
|
||||
end
|
||||
|
|
|
@ -1,656 +0,0 @@
|
|||
defmodule Farmbot.Firmware do
|
||||
@moduledoc "Allows communication with the firmware."
|
||||
|
||||
use GenStage
|
||||
require Farmbot.Logger
|
||||
alias Farmbot.Firmware.{Command, CompletionLogs, Vec3, EstopTimer, Utils}
|
||||
import Utils
|
||||
|
||||
import Farmbot.Config,
|
||||
only: [get_config_value: 3, update_config_value: 4, get_config_as_map: 0]
|
||||
|
||||
import CompletionLogs,
|
||||
only: [maybe_log_complete: 2]
|
||||
|
||||
# If any command takes longer than this, exit.
|
||||
@call_timeout 500_000
|
||||
|
||||
@doc "Move the bot to a position."
|
||||
def move_absolute(%Vec3{} = vec3, x_spd, y_spd, z_spd) do
|
||||
call = {:move_absolute, [vec3, x_spd, y_spd, z_spd]}
|
||||
GenStage.call(__MODULE__, call, @call_timeout)
|
||||
end
|
||||
|
||||
@doc "Calibrate an axis."
|
||||
def calibrate(axis) when is_binary(axis) do
|
||||
axis = String.to_atom(axis)
|
||||
GenStage.call(__MODULE__, {:calibrate, [axis]}, @call_timeout)
|
||||
end
|
||||
|
||||
@doc "Find home on an axis."
|
||||
def find_home(axis) when is_binary(axis) do
|
||||
axis = String.to_atom(axis)
|
||||
GenStage.call(__MODULE__, {:find_home, [axis]}, @call_timeout)
|
||||
end
|
||||
|
||||
@doc "Home every axis."
|
||||
def home_all() do
|
||||
GenStage.call(__MODULE__, {:home_all, []}, @call_timeout)
|
||||
end
|
||||
|
||||
@doc "Home an axis."
|
||||
def home(axis) when is_binary(axis) do
|
||||
axis = String.to_atom(axis)
|
||||
GenStage.call(__MODULE__, {:home, [axis]}, @call_timeout)
|
||||
end
|
||||
|
||||
@doc "Manually set an axis's current position to zero."
|
||||
def zero(axis) when is_binary(axis) do
|
||||
axis = String.to_atom(axis)
|
||||
GenStage.call(__MODULE__, {:zero, [axis]}, @call_timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Update a paramater.
|
||||
For a list of paramaters see `Farmbot.Firmware.Gcode.Param`
|
||||
"""
|
||||
def update_param(param, val) do
|
||||
GenStage.call(__MODULE__, {:update_param, [param, val]}, @call_timeout)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def read_all_params do
|
||||
GenStage.call(__MODULE__, {:read_all_params, []}, @call_timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Read a paramater.
|
||||
For a list of paramaters see `Farmbot.Firmware.Gcode.Param`
|
||||
"""
|
||||
def read_param(param) do
|
||||
GenStage.call(__MODULE__, {:read_param, [param]}, @call_timeout)
|
||||
end
|
||||
|
||||
@doc "Emergency lock Farmbot."
|
||||
def emergency_lock() do
|
||||
GenStage.call(__MODULE__, {:emergency_lock, []}, @call_timeout)
|
||||
end
|
||||
|
||||
@doc "Unlock Farmbot from Emergency state."
|
||||
def emergency_unlock() do
|
||||
GenStage.call(__MODULE__, {:emergency_unlock, []}, @call_timeout)
|
||||
end
|
||||
|
||||
@doc "Set a pin mode (`:input` | `:output` | `:input_pullup`)"
|
||||
def set_pin_mode(pin, mode) do
|
||||
GenStage.call(__MODULE__, {:set_pin_mode, [pin, mode]}, @call_timeout)
|
||||
end
|
||||
|
||||
@doc "Read a pin."
|
||||
def read_pin(pin, mode) do
|
||||
GenStage.call(__MODULE__, {:read_pin, [pin, mode]}, @call_timeout)
|
||||
end
|
||||
|
||||
@doc "Write a pin."
|
||||
def write_pin(pin, mode, value) do
|
||||
GenStage.call(__MODULE__, {:write_pin, [pin, mode, value]}, @call_timeout)
|
||||
end
|
||||
|
||||
@doc "Request version."
|
||||
def request_software_version do
|
||||
GenStage.call(__MODULE__, {:request_software_version, []}, @call_timeout)
|
||||
end
|
||||
|
||||
@doc "Set angle of a servo pin."
|
||||
def set_servo_angle(pin, value) do
|
||||
GenStage.call(__MODULE__, {:set_servo_angle, [pin, value]}, @call_timeout)
|
||||
end
|
||||
|
||||
@doc "Flag for all params reported."
|
||||
def params_reported do
|
||||
GenStage.call(__MODULE__, :params_reported)
|
||||
end
|
||||
|
||||
def get_pin_value(pin_num) do
|
||||
GenStage.call(__MODULE__, {:call, {:get_pin_value, pin_num}})
|
||||
end
|
||||
|
||||
def get_current_position do
|
||||
GenStage.call(__MODULE__, {:call, :get_current_position})
|
||||
end
|
||||
|
||||
@doc "Start the firmware services."
|
||||
def start_link(args) do
|
||||
GenStage.start_link(__MODULE__, args, name: __MODULE__)
|
||||
end
|
||||
|
||||
## GenStage
|
||||
|
||||
defmodule State do
|
||||
@moduledoc false
|
||||
defstruct [
|
||||
handler: nil,
|
||||
handler_mod: nil,
|
||||
idle: false,
|
||||
timer: nil,
|
||||
location_data: %{
|
||||
position: %{x: -1, y: -1, z: -1},
|
||||
scaled_encoders: %{x: -1, y: -1, z: -1},
|
||||
raw_encoders: %{x: -1, y: -1, z: -1},
|
||||
},
|
||||
pins: %{},
|
||||
params: %{},
|
||||
params_reported: false,
|
||||
initialized: false,
|
||||
initializing: false,
|
||||
current: nil,
|
||||
timeout_ms: 150_000,
|
||||
queue: :queue.new(),
|
||||
x_needs_home_on_boot: false,
|
||||
y_needs_home_on_boot: false,
|
||||
z_needs_home_on_boot: false
|
||||
]
|
||||
end
|
||||
|
||||
defp needs_home_on_boot do
|
||||
x = (get_config_value(:float, "hardware_params", "movement_home_at_boot_x") || 0)
|
||||
|> num_to_bool()
|
||||
|
||||
y = (get_config_value(:float, "hardware_params", "movement_home_at_boot_y") || 0)
|
||||
|> num_to_bool()
|
||||
|
||||
z = (get_config_value(:float, "hardware_params", "movement_home_at_boot_z") || 0)
|
||||
|> num_to_bool()
|
||||
|
||||
%{
|
||||
x_needs_home_on_boot: x,
|
||||
y_needs_home_on_boot: y,
|
||||
z_needs_home_on_boot: z,
|
||||
}
|
||||
end
|
||||
|
||||
def init([]) do
|
||||
handler_mod =
|
||||
Application.get_env(:farmbot_core, :behaviour)[:firmware_handler] || raise("No fw handler.")
|
||||
|> IO.inspect(label: "FW Handler")
|
||||
|
||||
case handler_mod.start_link() do
|
||||
{:ok, handler} ->
|
||||
initial = Map.merge(needs_home_on_boot(), %{handler: handler, handler_mod: handler_mod})
|
||||
Process.flag(:trap_exit, true)
|
||||
{
|
||||
:producer_consumer,
|
||||
struct(State, initial),
|
||||
subscribe_to: [handler], dispatcher: GenStage.BroadcastDispatcher
|
||||
}
|
||||
:ignore ->
|
||||
Farmbot.Logger.error 1, "Failed to initialize firmware. Falling back to stub implementation."
|
||||
replace_firmware_handler(Farmbot.Firmware.StubHandler)
|
||||
init([])
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def terminate(reason, state) do
|
||||
unless reason in [:normal, :shutdown] do
|
||||
replace_firmware_handler(Farmbot.Firmware.StubHandler)
|
||||
end
|
||||
|
||||
unless :queue.is_empty(state.queue) do
|
||||
list = :queue.to_list(state.queue)
|
||||
for cmd <- list do
|
||||
:ok = do_reply(%{state | current: cmd}, {:error, "Firmware handler crash"})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info({:EXIT, _pid, :normal}, state) do
|
||||
{:stop, :normal, state}
|
||||
end
|
||||
|
||||
def handle_info({:EXIT, _, reason}, state) do
|
||||
Farmbot.Logger.error 1, "Firmware handler: #{state.handler_mod} died: #{inspect reason}"
|
||||
case state.handler_mod.start_link() do
|
||||
{:ok, handler} ->
|
||||
new_state = %{state | handler: handler}
|
||||
{:noreply, [{:informational_settings, %{busy: false}}], %{new_state | initialized: false, idle: false}}
|
||||
err -> {:stop, err, %{state | handler: false}}
|
||||
end
|
||||
end
|
||||
|
||||
# TODO(Connor): Put some sort of exit strategy here.
|
||||
# If a firmware command keeps timingout/failing, Farmbot OS just keeps trying
|
||||
# it. This can lead to infinate failures.
|
||||
def handle_info({:command_timeout, %Command{} = timeout_command}, state) do
|
||||
case state.current do
|
||||
# Check if this timeout is actually talking about the current command.
|
||||
^timeout_command = current ->
|
||||
Farmbot.Logger.warn 1, "Timed out waiting for Firmware response. Retrying #{inspect current}) "
|
||||
case apply(state.handler_mod, current.fun, [state.handler | current.args]) do
|
||||
:ok ->
|
||||
timer = start_timer(current, state.timeout_ms)
|
||||
{:noreply, [], %{state | current: current, timer: timer}}
|
||||
{:error, reason} = res when is_binary(reason) ->
|
||||
do_reply(state, res)
|
||||
{:noreply, [], %{state | current: nil, queue: :queue.new()}}
|
||||
end
|
||||
|
||||
# If this timeout was not talking about the current command
|
||||
%Command{} = current ->
|
||||
Farmbot.Logger.debug 3, "Got stray timeout for command: #{inspect current}"
|
||||
{:noreply, [], %{state | timer: nil}}
|
||||
|
||||
# If there is no current command, we got a different kind of stray.
|
||||
# This is ok i guess.
|
||||
nil -> {:noreply, [], %{state | timer: nil}}
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
def handle_call({:call, {:get_pin_value, pin_num}}, _from, state) do
|
||||
{:reply, state.pins[pin_num], [], state}
|
||||
end
|
||||
|
||||
def handle_call({:call, :get_current_position}, _from, state) do
|
||||
{:reply, state.location_data.position, [], state}
|
||||
end
|
||||
|
||||
def handle_call(:params_reported, _, state) do
|
||||
{:reply, state.params_reported, [], state}
|
||||
end
|
||||
|
||||
def handle_call({fun, args}, from, state = %{initialized: false})
|
||||
when fun not in [:read_all_params, :update_param, :emergency_unlock, :emergency_lock, :request_software_version] do
|
||||
next_current = struct(Command, from: from, fun: fun, args: args)
|
||||
do_queue_cmd(next_current, state)
|
||||
# {:reply, {:error, "uninitialized"}, [], state}
|
||||
end
|
||||
|
||||
def handle_call({fun, args}, from, state) do
|
||||
next_current = struct(Command, from: from, fun: fun, args: args)
|
||||
current_current = state.current
|
||||
cond do
|
||||
fun == :emergency_lock ->
|
||||
if current_current do
|
||||
do_reply(state, {:error, "emergency_lock"})
|
||||
end
|
||||
do_begin_cmd(next_current, state, [])
|
||||
match?(%Command{}, current_current) ->
|
||||
do_queue_cmd(next_current, state)
|
||||
is_nil(current_current) ->
|
||||
do_begin_cmd(next_current, state, [])
|
||||
end
|
||||
end
|
||||
|
||||
defp do_begin_cmd(%Command{fun: fun, args: args, from: _from} = current, state, dispatch) do
|
||||
case apply(state.handler_mod, fun, [state.handler | args]) do
|
||||
:ok ->
|
||||
timer = start_timer(current, state.timeout_ms)
|
||||
if fun == :emergency_unlock do
|
||||
new_dispatch = [{:informational_settings, %{busy: false, locked: false}} | dispatch]
|
||||
{:noreply, new_dispatch, %{state | current: current, timer: timer}}
|
||||
else
|
||||
{:noreply, dispatch, %{state | current: current, timer: timer}}
|
||||
end
|
||||
{:error, reason} = res when is_binary(reason) ->
|
||||
do_reply(%{state | current: current}, res)
|
||||
{:noreply, dispatch, %{state | current: nil}}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_queue_cmd(%Command{fun: _fun, args: _args, from: _from} = current, state) do
|
||||
# Farmbot.Logger.busy 3, "FW Queuing: #{fun}: #{inspect from}"
|
||||
new_q = :queue.in(current, state.queue)
|
||||
{:noreply, [], %{state | queue: new_q}}
|
||||
end
|
||||
|
||||
def handle_events(gcodes, _from, state) do
|
||||
{diffs, state} = handle_gcodes(gcodes, state)
|
||||
# if after handling the current buffer of gcodes,
|
||||
# Try to start the next command in the queue if it exists.
|
||||
if List.last(gcodes) == :idle && state.current == nil do
|
||||
if state.initialized do
|
||||
case :queue.out(state.queue) do
|
||||
{{:value, next_current}, new_queue} ->
|
||||
do_begin_cmd(next_current, %{state | queue: new_queue, current: next_current}, diffs)
|
||||
{:empty, queue} -> # nothing to do if the queue is empty.
|
||||
{:noreply, diffs, %{state | queue: queue}}
|
||||
end
|
||||
else
|
||||
Farmbot.Logger.warn 1, "Fw not initialized yet"
|
||||
{:noreply, diffs, state}
|
||||
end
|
||||
else
|
||||
{:noreply, diffs, state}
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_gcodes(codes, state, acc \\ [])
|
||||
|
||||
defp handle_gcodes([], state, acc), do: {Enum.reverse(acc), state}
|
||||
|
||||
defp handle_gcodes([code | rest], state, acc) do
|
||||
case handle_gcode(code, state) do
|
||||
{nil, new_state} -> handle_gcodes(rest, new_state, acc)
|
||||
{key, diff, new_state} -> handle_gcodes(rest, new_state, [{key, diff} | acc])
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_gcode({:debug_message, message}, state) do
|
||||
if get_config_value(:bool, "settings", "arduino_debug_messages") do
|
||||
Farmbot.Logger.debug 3, "Arduino debug message: #{message}"
|
||||
end
|
||||
{nil, state}
|
||||
end
|
||||
|
||||
defp handle_gcode(code, state) when code in [:error, :invalid_command] do
|
||||
maybe_cancel_timer(state.timer, state.current)
|
||||
if state.current do
|
||||
Farmbot.Logger.error 1, "Got #{code} while executing `#{inspect state.current}`."
|
||||
do_reply(state, {:error, "Firmware error. See log."})
|
||||
{nil, %{state | current: nil}}
|
||||
else
|
||||
{nil, state}
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_gcode(:report_no_config, state) do
|
||||
Farmbot.Logger.busy 1, "Initializing Firmware."
|
||||
old = get_config_as_map()["hardware_params"]
|
||||
spawn __MODULE__, :do_read_params, [Map.delete(old, "param_version")]
|
||||
{nil, %{state | initialized: false, initializing: true}}
|
||||
end
|
||||
|
||||
defp handle_gcode(:report_params_complete, state) do
|
||||
Farmbot.Logger.success 1, "Firmware Initialized."
|
||||
{nil, %{state | initialized: true, initializing: false}}
|
||||
end
|
||||
|
||||
defp handle_gcode(:idle, %{initialized: false, initializing: false} = state) do
|
||||
Farmbot.Logger.busy 3, "Firmware not initialized yet. Waiting for R88 message."
|
||||
{nil, state}
|
||||
end
|
||||
|
||||
defp handle_gcode(:idle, %{initialized: true, initializing: false, current: nil, z_needs_home_on_boot: true} = state) do
|
||||
Farmbot.Logger.info 2, "Bootup homing Z axis"
|
||||
spawn __MODULE__, :find_home, ["z"]
|
||||
{nil, %{state | z_needs_home_on_boot: false}}
|
||||
end
|
||||
|
||||
defp handle_gcode(:idle, %{initialized: true, initializing: false, current: nil, y_needs_home_on_boot: true} = state) do
|
||||
Farmbot.Logger.info 2, "Bootup homing Y axis"
|
||||
spawn __MODULE__, :find_home, ["y"]
|
||||
{nil, %{state | y_needs_home_on_boot: false}}
|
||||
end
|
||||
|
||||
defp handle_gcode(:idle, %{initialized: true, initializing: false, current: nil, x_needs_home_on_boot: true} = state) do
|
||||
Farmbot.Logger.info 2, "Bootup homing X axis"
|
||||
spawn __MODULE__, :find_home, ["x"]
|
||||
{nil, %{state | x_needs_home_on_boot: false}}
|
||||
end
|
||||
|
||||
defp handle_gcode(:idle, state) do
|
||||
maybe_cancel_timer(state.timer, state.current)
|
||||
if state.current do
|
||||
Farmbot.Logger.warn 1, "Got idle while executing a command."
|
||||
timer = start_timer(state.current, state.timeout_ms)
|
||||
{nil, %{state | timer: timer}}
|
||||
else
|
||||
{:informational_settings, %{busy: false, locked: false}, %{state | idle: true}}
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_gcode({:report_current_position, x, y, z}, state) do
|
||||
position = %{position: %{x: x, y: y, z: z}}
|
||||
new_state = %{state | location_data: Map.merge(state.location_data, position)}
|
||||
{:location_data, position, new_state}
|
||||
end
|
||||
|
||||
defp handle_gcode({:report_encoder_position_scaled, x, y, z}, state) do
|
||||
scaled_encoders = %{scaled_encoders: %{x: x, y: y, z: z}}
|
||||
new_state = %{state | location_data: Map.merge(state.location_data, scaled_encoders)}
|
||||
{:location_data, scaled_encoders, new_state}
|
||||
end
|
||||
|
||||
defp handle_gcode({:report_encoder_position_raw, x, y, z}, state) do
|
||||
raw_encoders = %{raw_encoders: %{x: x, y: y, z: z}}
|
||||
new_state = %{state | location_data: Map.merge(state.location_data, raw_encoders)}
|
||||
{:location_data, raw_encoders, new_state}
|
||||
end
|
||||
|
||||
defp handle_gcode({:report_end_stops, xa, xb, ya, yb, za, zb}, state) do
|
||||
diff = %{end_stops: %{xa: xa, xb: xb, ya: ya, yb: yb, za: za, zb: zb}}
|
||||
{:location_data, diff, state}
|
||||
{nil, state}
|
||||
end
|
||||
|
||||
defp handle_gcode({:report_pin_mode, pin, mode_atom}, state) do
|
||||
# Farmbot.Logger.debug 3, "Got pin mode report: #{pin}: #{mode_atom}"
|
||||
mode = extract_pin_mode(mode_atom)
|
||||
case state.pins[pin] do
|
||||
%{mode: _, value: _} = pin_map ->
|
||||
{:pins, %{pin => %{pin_map | mode: mode}}, %{state | pins: %{state.pins | pin => %{pin_map | mode: mode}}}}
|
||||
nil ->
|
||||
{:pins, %{pin => %{mode: mode, value: -1}}, %{state | pins: Map.put(state.pins, pin, %{mode: mode, value: -1})}}
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_gcode({:report_pin_value, pin, value}, state) do
|
||||
# Farmbot.Logger.debug 3, "Got pin value report: #{pin}: #{value} old: #{inspect state.pins[pin]}"
|
||||
case state.pins[pin] do
|
||||
%{mode: _, value: _} = pin_map ->
|
||||
{:pins, %{pin => %{pin_map | value: value}}, %{state | pins: %{state.pins | pin => %{pin_map | value: value}}}}
|
||||
nil ->
|
||||
{:pins, %{pin => %{mode: nil, value: value}}, %{state | pins: Map.put(state.pins, pin, %{mode: nil, value: value})}}
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_gcode({:report_parameter_value, param, value}, state) when (value == -1) do
|
||||
maybe_update_param_from_report(to_string(param), nil)
|
||||
{:mcu_params, %{param => nil}, %{state | params: Map.put(state.params, param, value)}}
|
||||
end
|
||||
|
||||
defp handle_gcode({:report_parameter_value, param, value}, state) when is_number(value) do
|
||||
maybe_update_param_from_report(to_string(param), value)
|
||||
{:mcu_params, %{param => value}, %{state | params: Map.put(state.params, param, value)}}
|
||||
end
|
||||
|
||||
defp handle_gcode({:report_software_version, version}, state) do
|
||||
hw = get_config_value(:string, "settings", "firmware_hardware")
|
||||
Farmbot.Logger.debug 3, "Firmware reported software version: #{version} current firmware_hardware is: #{hw}"
|
||||
case String.last(version) do
|
||||
"F" ->
|
||||
update_config_value(:string, "settings", "firmware_hardware", "farmduino")
|
||||
"R" ->
|
||||
update_config_value(:string, "settings", "firmware_hardware", "arduino")
|
||||
"G" ->
|
||||
update_config_value(:string, "settings", "firmware_hardware", "farmduino_k14")
|
||||
_ -> :ok
|
||||
end
|
||||
{:informational_settings, %{firmware_version: version}, state}
|
||||
end
|
||||
|
||||
defp handle_gcode(:report_axis_home_complete_x, state) do
|
||||
{nil, state}
|
||||
end
|
||||
|
||||
defp handle_gcode(:report_axis_home_complete_y, state) do
|
||||
{nil, state}
|
||||
end
|
||||
|
||||
defp handle_gcode(:report_axis_home_complete_z, state) do
|
||||
{nil, %{state | timer: nil}}
|
||||
end
|
||||
|
||||
defp handle_gcode(:report_axis_timeout_x, state) do
|
||||
do_reply(state, {:error, "Axis X timeout"})
|
||||
{nil, %{state | timer: nil}}
|
||||
end
|
||||
|
||||
defp handle_gcode(:report_axis_timeout_y, state) do
|
||||
do_reply(state, {:error, "Axis Y timeout"})
|
||||
{nil, %{state | timer: nil}}
|
||||
end
|
||||
|
||||
defp handle_gcode(:report_axis_timeout_z, state) do
|
||||
do_reply(state, {:error, "Axis Z timeout"})
|
||||
{nil, %{state | timer: nil}}
|
||||
end
|
||||
|
||||
defp handle_gcode({:report_axis_changed_x, _new_x} = msg, state) do
|
||||
new_current = Command.add_status(state.current, msg)
|
||||
{nil, %{state | current: new_current}}
|
||||
end
|
||||
|
||||
defp handle_gcode({:report_axis_changed_y, _new_y} = msg, state) do
|
||||
new_current = Command.add_status(state.current, msg)
|
||||
{nil, %{state | current: new_current}}
|
||||
end
|
||||
|
||||
defp handle_gcode({:report_axis_changed_z, _new_z} = msg, state) do
|
||||
new_current = Command.add_status(state.current, msg)
|
||||
{nil, %{state | current: new_current}}
|
||||
end
|
||||
|
||||
defp handle_gcode(:busy, state) do
|
||||
maybe_cancel_timer(state.timer, state.current)
|
||||
timer = if state.current do
|
||||
start_timer(state.current, state.timeout_ms)
|
||||
else
|
||||
nil
|
||||
end
|
||||
{:informational_settings, %{busy: true}, %{state | idle: false, timer: timer}}
|
||||
end
|
||||
|
||||
defp handle_gcode(:done, state) do
|
||||
maybe_cancel_timer(state.timer, state.current)
|
||||
if state.current do
|
||||
do_reply(state, :ok)
|
||||
{:informational_settings, %{busy: false}, %{state | current: nil}}
|
||||
else
|
||||
{:informational_settings, %{busy: false}, state}
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_gcode(:report_emergency_lock, state) do
|
||||
maybe_send_email()
|
||||
state.current && do_reply(state, {:error, :emergency_lock})
|
||||
{:informational_settings, %{locked: true}, %{state | current: nil}}
|
||||
end
|
||||
|
||||
defp handle_gcode({:report_calibration, axis, status}, state) do
|
||||
maybe_cancel_timer(state.timer, state.current)
|
||||
Farmbot.Logger.busy 1, "Axis #{axis} calibration: #{status}"
|
||||
{nil, state}
|
||||
end
|
||||
|
||||
defp handle_gcode({:report_axis_calibration, param, val}, state) do
|
||||
spawn __MODULE__, :report_calibration_callback, [5, param, val]
|
||||
{nil, state}
|
||||
end
|
||||
|
||||
defp handle_gcode(:noop, state) do
|
||||
{nil, state}
|
||||
end
|
||||
|
||||
defp handle_gcode(:received, state) do
|
||||
{nil, state}
|
||||
end
|
||||
|
||||
defp handle_gcode({:echo, _code}, state) do
|
||||
{nil, state}
|
||||
end
|
||||
|
||||
defp handle_gcode(code, state) do
|
||||
Farmbot.Logger.warn(3, "unhandled code: #{inspect(code)}")
|
||||
{nil, state}
|
||||
end
|
||||
|
||||
defp maybe_cancel_timer(nil, _maybe_current_command) do
|
||||
:ok
|
||||
end
|
||||
|
||||
defp maybe_cancel_timer(timer, _maybe_current_command) do
|
||||
Process.read_timer(timer) && Process.cancel_timer(timer)
|
||||
:ok
|
||||
end
|
||||
|
||||
defp start_timer(%Command{} = command, timeout) do
|
||||
Process.send_after(self(), {:command_timeout, command}, timeout)
|
||||
end
|
||||
|
||||
defp maybe_update_param_from_report(param, val) when is_binary(param) do
|
||||
real_val = if val, do: (val / 1), else: nil
|
||||
# Farmbot.Logger.debug 3, "Firmware reported #{param} => #{val || -1}"
|
||||
update_config_value(:float, "hardware_params", to_string(param), real_val)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def do_read_params(old) when is_map(old) do
|
||||
for {key, float_val} <- old do
|
||||
cond do
|
||||
(float_val == -1) -> :ok
|
||||
is_nil(float_val) -> :ok
|
||||
is_number(float_val) ->
|
||||
val = round(float_val)
|
||||
:ok = update_param(:"#{key}", val)
|
||||
end
|
||||
end
|
||||
:ok = update_param(:param_use_eeprom, 0)
|
||||
:ok = update_param(:param_config_ok, 1)
|
||||
read_all_params()
|
||||
:ok = request_software_version()
|
||||
end
|
||||
|
||||
@doc false
|
||||
def report_calibration_callback(tries, param, value)
|
||||
|
||||
def report_calibration_callback(0, _param, _value) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def report_calibration_callback(tries, param, val) do
|
||||
case Farmbot.Firmware.update_param(param, val) do
|
||||
:ok ->
|
||||
str_param = to_string(param)
|
||||
case get_config_value(:float, "hardware_params", str_param) do
|
||||
^val ->
|
||||
Farmbot.Logger.success 1, "Calibrated #{param}: #{val}"
|
||||
# SettingsSync.upload_fw_kv(str_param, val)
|
||||
raise("fixme")
|
||||
:ok
|
||||
_ -> report_calibration_callback(tries - 1, param, val)
|
||||
end
|
||||
{:error, reason} when is_binary(reason) ->
|
||||
Farmbot.Logger.error 1, "Failed to set #{param}: #{val} (#{inspect reason})"
|
||||
report_calibration_callback(tries - 1, param, val)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_reply(state, reply) do
|
||||
maybe_cancel_timer(state.timer, state.current)
|
||||
maybe_log_complete(state.current, reply)
|
||||
case state.current do
|
||||
%Command{fun: :emergency_unlock, from: from} ->
|
||||
# i really don't want this to be here..
|
||||
EstopTimer.cancel_timer()
|
||||
:ok = GenServer.reply from, reply
|
||||
%Command{fun: :emergency_lock, from: from} ->
|
||||
:ok = GenServer.reply from, {:error, "Emergency Lock"}
|
||||
%Command{fun: _fun, from: from} ->
|
||||
# Farmbot.Logger.success 3, "FW Replying: #{fun}: #{inspect from}"
|
||||
:ok = GenServer.reply from, reply
|
||||
nil ->
|
||||
Farmbot.Logger.error 1, "FW Nothing to send reply: #{inspect reply} to!."
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_send_email do
|
||||
if get_config_value(:bool, "settings", "email_on_estop") do
|
||||
if !EstopTimer.timer_active? do
|
||||
EstopTimer.start_timer()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,164 @@
|
|||
defmodule Farmbot.Core.FirmwareSideEffects do
|
||||
@moduledoc "Handles firmware data and syncing it with BotState."
|
||||
@behaviour Farmbot.Firmware.SideEffects
|
||||
alias Farmbot.Core.FirmwareEstopTimer
|
||||
require Logger
|
||||
require Farmbot.Logger
|
||||
|
||||
def handle_position(x: x, y: y, z: z) do
|
||||
:ok = Farmbot.BotState.set_position(x, y, z)
|
||||
end
|
||||
|
||||
def handle_position_change([{_axis, _value}]) do
|
||||
:noop
|
||||
end
|
||||
|
||||
def handle_axis_state([{_axis, _state}]) do
|
||||
:noop
|
||||
end
|
||||
|
||||
def handle_calibration_state([{_axis, _state}]) do
|
||||
:noop
|
||||
end
|
||||
|
||||
def handle_encoders_scaled(x: x, y: y, z: z) do
|
||||
:ok = Farmbot.BotState.set_encoders_scaled(x, y, z)
|
||||
end
|
||||
|
||||
def handle_encoders_raw(x: x, y: y, z: z) do
|
||||
:ok = Farmbot.BotState.set_encoders_raw(x, y, z)
|
||||
end
|
||||
|
||||
def handle_paramater_value([{param, value}]) do
|
||||
:ok = Farmbot.BotState.set_firmware_config(param, value)
|
||||
end
|
||||
|
||||
def handle_pin_value(p: pin, v: value) do
|
||||
:ok = Farmbot.BotState.set_pin_value(pin, value)
|
||||
end
|
||||
|
||||
def handle_software_version([_version]) do
|
||||
:noop
|
||||
end
|
||||
|
||||
def handle_end_stops(_) do
|
||||
:noop
|
||||
end
|
||||
|
||||
def handle_emergency_lock() do
|
||||
_ = FirmwareEstopTimer.start_timer()
|
||||
:ok = Farmbot.BotState.set_firmware_locked()
|
||||
end
|
||||
|
||||
def handle_emergency_unlock() do
|
||||
_ = FirmwareEstopTimer.cancel_timer()
|
||||
:ok = Farmbot.BotState.set_firmware_unlocked()
|
||||
end
|
||||
|
||||
def handle_input_gcode(code) do
|
||||
should_log? = Farmbot.Asset.fbos_config().firmware_input_log
|
||||
should_log? && Farmbot.Logger.debug(3, inspect(code))
|
||||
end
|
||||
|
||||
def handle_output_gcode(code) do
|
||||
should_log? = Farmbot.Asset.fbos_config().firmware_output_log
|
||||
should_log? && Farmbot.Logger.debug(3, inspect(code))
|
||||
end
|
||||
|
||||
def handle_debug_message([message]) do
|
||||
should_log? = Farmbot.Asset.fbos_config().firmware_debug_log
|
||||
should_log? && Farmbot.Logger.debug(3, "Arduino debug message: " <> message)
|
||||
end
|
||||
|
||||
def load_params do
|
||||
conf = Farmbot.Asset.firmware_config()
|
||||
|
||||
Map.take(conf, [
|
||||
:param_e_stop_on_mov_err,
|
||||
:param_mov_nr_retry,
|
||||
:movement_timeout_x,
|
||||
:movement_timeout_y,
|
||||
:movement_timeout_z,
|
||||
:movement_keep_active_x,
|
||||
:movement_keep_active_y,
|
||||
:movement_keep_active_z,
|
||||
:movement_home_at_boot_x,
|
||||
:movement_home_at_boot_y,
|
||||
:movement_home_at_boot_z,
|
||||
:movement_invert_endpoints_x,
|
||||
:movement_invert_endpoints_y,
|
||||
:movement_invert_endpoints_z,
|
||||
:movement_enable_endpoints_x,
|
||||
:movement_enable_endpoints_y,
|
||||
:movement_enable_endpoints_z,
|
||||
:movement_invert_motor_x,
|
||||
:movement_invert_motor_y,
|
||||
:movement_invert_motor_z,
|
||||
:movement_secondary_motor_x,
|
||||
:movement_secondary_motor_invert_x,
|
||||
:movement_steps_acc_dec_x,
|
||||
:movement_steps_acc_dec_y,
|
||||
:movement_steps_acc_dec_z,
|
||||
:movement_stop_at_home_x,
|
||||
:movement_stop_at_home_y,
|
||||
:movement_stop_at_home_z,
|
||||
:movement_home_up_x,
|
||||
:movement_home_up_y,
|
||||
:movement_home_up_z,
|
||||
:movement_step_per_mm_x,
|
||||
:movement_step_per_mm_y,
|
||||
:movement_step_per_mm_z,
|
||||
:movement_min_spd_x,
|
||||
:movement_min_spd_y,
|
||||
:movement_min_spd_z,
|
||||
:movement_home_spd_x,
|
||||
:movement_home_spd_y,
|
||||
:movement_home_spd_z,
|
||||
:movement_max_spd_x,
|
||||
:movement_max_spd_y,
|
||||
:movement_max_spd_z,
|
||||
:encoder_enabled_x,
|
||||
:encoder_enabled_y,
|
||||
:encoder_enabled_z,
|
||||
:encoder_type_x,
|
||||
:encoder_type_y,
|
||||
:encoder_type_z,
|
||||
:encoder_missed_steps_max_x,
|
||||
:encoder_missed_steps_max_y,
|
||||
:encoder_missed_steps_max_z,
|
||||
:encoder_scaling_x,
|
||||
:encoder_scaling_y,
|
||||
:encoder_scaling_z,
|
||||
:encoder_missed_steps_decay_x,
|
||||
:encoder_missed_steps_decay_y,
|
||||
:encoder_missed_steps_decay_z,
|
||||
:encoder_use_for_pos_x,
|
||||
:encoder_use_for_pos_y,
|
||||
:encoder_use_for_pos_z,
|
||||
:encoder_invert_x,
|
||||
:encoder_invert_y,
|
||||
:encoder_invert_z,
|
||||
:movement_axis_nr_steps_x,
|
||||
:movement_axis_nr_steps_y,
|
||||
:movement_axis_nr_steps_z,
|
||||
:movement_stop_at_max_x,
|
||||
:movement_stop_at_max_y,
|
||||
:movement_stop_at_max_z,
|
||||
:pin_guard_1_pin_nr,
|
||||
:pin_guard_1_time_out,
|
||||
:pin_guard_1_active_state,
|
||||
:pin_guard_2_pin_nr,
|
||||
:pin_guard_2_time_out,
|
||||
:pin_guard_2_active_state,
|
||||
:pin_guard_3_pin_nr,
|
||||
:pin_guard_3_time_out,
|
||||
:pin_guard_3_active_state,
|
||||
:pin_guard_4_pin_nr,
|
||||
:pin_guard_4_time_out,
|
||||
:pin_guard_4_active_state,
|
||||
:pin_guard_5_pin_nr,
|
||||
:pin_guard_5_time_out,
|
||||
:pin_guard_5_active_state
|
||||
])
|
||||
end
|
||||
end
|
|
@ -0,0 +1,51 @@
|
|||
defmodule Farmbot.Core.FirmwareSupervisor do
|
||||
use Supervisor
|
||||
alias Farmbot.Asset
|
||||
|
||||
def start_link(args) do
|
||||
Supervisor.start_link(__MODULE__, args, name: __MODULE__)
|
||||
end
|
||||
|
||||
def reinitialize do
|
||||
_ = Supervisor.terminate_child(Farmbot.Core, __MODULE__)
|
||||
Supervisor.restart_child(Farmbot.Core, __MODULE__)
|
||||
end
|
||||
|
||||
def stub do
|
||||
Asset.fbos_config()
|
||||
|> Asset.FbosConfig.changeset(%{firmware_path: "stub"})
|
||||
|> Asset.Repo.insert_or_update()
|
||||
end
|
||||
|
||||
def init([]) do
|
||||
fbos_config = Asset.fbos_config()
|
||||
|
||||
children =
|
||||
firmware_children(fbos_config) ++
|
||||
[
|
||||
Farmbot.Core.FirmwareEstopTimer
|
||||
]
|
||||
|
||||
Supervisor.init(children, strategy: :one_for_all)
|
||||
end
|
||||
|
||||
def firmware_children(%Asset.FbosConfig{firmware_hardware: nil}), do: []
|
||||
|
||||
def firmware_children(%Asset.FbosConfig{firmware_path: "stub"}) do
|
||||
[
|
||||
{Farmbot.Firmware,
|
||||
transport: Farmbot.Firmware.StubTransport, side_effects: Farmbot.Core.FirmwareSideEffects}
|
||||
]
|
||||
end
|
||||
|
||||
def firmware_children(%Asset.FbosConfig{firmware_path: nil}), do: []
|
||||
|
||||
def firmware_children(%Asset.FbosConfig{} = fbos_config) do
|
||||
[
|
||||
{Farmbot.Firmware,
|
||||
device: fbos_config.firmware_path,
|
||||
transport: Farmbot.Firmware.UARTTransport,
|
||||
side_effects: Farmbot.Core.FirmwareSideEffects}
|
||||
]
|
||||
end
|
||||
end
|
|
@ -1,284 +0,0 @@
|
|||
defmodule Farmbot.Firmware.Gcode.Param do
|
||||
@moduledoc "Firmware paramaters."
|
||||
|
||||
@doc "Turn a number into a param, or a param into a number."
|
||||
@spec parse_param(integer | t) :: t | integer
|
||||
def parse_param(0), do: :param_version
|
||||
def parse_param(1), do: :param_test
|
||||
def parse_param(2), do: :param_config_ok
|
||||
def parse_param(3), do: :param_use_eeprom
|
||||
def parse_param(4), do: :param_e_stop_on_mov_err
|
||||
def parse_param(5), do: :param_mov_nr_retry
|
||||
def parse_param(11), do: :movement_timeout_x
|
||||
def parse_param(12), do: :movement_timeout_y
|
||||
def parse_param(13), do: :movement_timeout_z
|
||||
def parse_param(15), do: :movement_keep_active_x
|
||||
def parse_param(16), do: :movement_keep_active_y
|
||||
def parse_param(17), do: :movement_keep_active_z
|
||||
def parse_param(18), do: :movement_home_at_boot_x
|
||||
def parse_param(19), do: :movement_home_at_boot_y
|
||||
def parse_param(20), do: :movement_home_at_boot_z
|
||||
def parse_param(21), do: :movement_invert_endpoints_x
|
||||
def parse_param(22), do: :movement_invert_endpoints_y
|
||||
def parse_param(23), do: :movement_invert_endpoints_z
|
||||
def parse_param(25), do: :movement_enable_endpoints_x
|
||||
def parse_param(26), do: :movement_enable_endpoints_y
|
||||
def parse_param(27), do: :movement_enable_endpoints_z
|
||||
def parse_param(31), do: :movement_invert_motor_x
|
||||
def parse_param(32), do: :movement_invert_motor_y
|
||||
def parse_param(33), do: :movement_invert_motor_z
|
||||
def parse_param(36), do: :movement_secondary_motor_x
|
||||
def parse_param(37), do: :movement_secondary_motor_invert_x
|
||||
def parse_param(41), do: :movement_steps_acc_dec_x
|
||||
def parse_param(42), do: :movement_steps_acc_dec_y
|
||||
def parse_param(43), do: :movement_steps_acc_dec_z
|
||||
def parse_param(45), do: :movement_stop_at_home_x
|
||||
def parse_param(46), do: :movement_stop_at_home_y
|
||||
def parse_param(47), do: :movement_stop_at_home_z
|
||||
def parse_param(51), do: :movement_home_up_x
|
||||
def parse_param(52), do: :movement_home_up_y
|
||||
def parse_param(53), do: :movement_home_up_z
|
||||
def parse_param(55), do: :movement_step_per_mm_x
|
||||
def parse_param(56), do: :movement_step_per_mm_y
|
||||
def parse_param(57), do: :movement_step_per_mm_z
|
||||
def parse_param(61), do: :movement_min_spd_x
|
||||
def parse_param(62), do: :movement_min_spd_y
|
||||
def parse_param(63), do: :movement_min_spd_z
|
||||
def parse_param(65), do: :movement_home_spd_x
|
||||
def parse_param(66), do: :movement_home_spd_y
|
||||
def parse_param(67), do: :movement_home_spd_z
|
||||
def parse_param(71), do: :movement_max_spd_x
|
||||
def parse_param(72), do: :movement_max_spd_y
|
||||
def parse_param(73), do: :movement_max_spd_z
|
||||
def parse_param(75), do: :movement_invert_2_endpoints_x
|
||||
def parse_param(76), do: :movement_invert_2_endpoints_y
|
||||
def parse_param(77), do: :movement_invert_2_endpoints_z
|
||||
def parse_param(101), do: :encoder_enabled_x
|
||||
def parse_param(102), do: :encoder_enabled_y
|
||||
def parse_param(103), do: :encoder_enabled_z
|
||||
def parse_param(105), do: :encoder_type_x
|
||||
def parse_param(106), do: :encoder_type_y
|
||||
def parse_param(107), do: :encoder_type_z
|
||||
def parse_param(111), do: :encoder_missed_steps_max_x
|
||||
def parse_param(112), do: :encoder_missed_steps_max_y
|
||||
def parse_param(113), do: :encoder_missed_steps_max_z
|
||||
def parse_param(115), do: :encoder_scaling_x
|
||||
def parse_param(116), do: :encoder_scaling_y
|
||||
def parse_param(117), do: :encoder_scaling_z
|
||||
def parse_param(121), do: :encoder_missed_steps_decay_x
|
||||
def parse_param(122), do: :encoder_missed_steps_decay_y
|
||||
def parse_param(123), do: :encoder_missed_steps_decay_z
|
||||
def parse_param(125), do: :encoder_use_for_pos_x
|
||||
def parse_param(126), do: :encoder_use_for_pos_y
|
||||
def parse_param(127), do: :encoder_use_for_pos_z
|
||||
def parse_param(131), do: :encoder_invert_x
|
||||
def parse_param(132), do: :encoder_invert_y
|
||||
def parse_param(133), do: :encoder_invert_z
|
||||
def parse_param(141), do: :movement_axis_nr_steps_x
|
||||
def parse_param(142), do: :movement_axis_nr_steps_y
|
||||
def parse_param(143), do: :movement_axis_nr_steps_z
|
||||
def parse_param(145), do: :movement_stop_at_max_x
|
||||
def parse_param(146), do: :movement_stop_at_max_y
|
||||
def parse_param(147), do: :movement_stop_at_max_z
|
||||
def parse_param(201), do: :pin_guard_1_pin_nr
|
||||
def parse_param(202), do: :pin_guard_1_time_out
|
||||
def parse_param(203), do: :pin_guard_1_active_state
|
||||
def parse_param(205), do: :pin_guard_2_pin_nr
|
||||
def parse_param(206), do: :pin_guard_2_time_out
|
||||
def parse_param(207), do: :pin_guard_2_active_state
|
||||
def parse_param(211), do: :pin_guard_3_pin_nr
|
||||
def parse_param(212), do: :pin_guard_3_time_out
|
||||
def parse_param(213), do: :pin_guard_3_active_state
|
||||
def parse_param(215), do: :pin_guard_4_pin_nr
|
||||
def parse_param(216), do: :pin_guard_4_time_out
|
||||
def parse_param(217), do: :pin_guard_4_active_state
|
||||
def parse_param(221), do: :pin_guard_5_pin_nr
|
||||
def parse_param(222), do: :pin_guard_5_time_out
|
||||
def parse_param(223), do: :pin_guard_5_active_state
|
||||
|
||||
def parse_param(:param_version), do: 0
|
||||
def parse_param(:param_test), do: 1
|
||||
def parse_param(:param_config_ok), do: 2
|
||||
def parse_param(:param_use_eeprom), do: 3
|
||||
def parse_param(:param_e_stop_on_mov_err), do: 4
|
||||
def parse_param(:param_mov_nr_retry), do: 5
|
||||
def parse_param(:movement_timeout_x), do: 11
|
||||
def parse_param(:movement_timeout_y), do: 12
|
||||
def parse_param(:movement_timeout_z), do: 13
|
||||
def parse_param(:movement_keep_active_x), do: 15
|
||||
def parse_param(:movement_keep_active_y), do: 16
|
||||
def parse_param(:movement_keep_active_z), do: 17
|
||||
def parse_param(:movement_home_at_boot_x), do: 18
|
||||
def parse_param(:movement_home_at_boot_y), do: 19
|
||||
def parse_param(:movement_home_at_boot_z), do: 20
|
||||
def parse_param(:movement_invert_endpoints_x), do: 21
|
||||
def parse_param(:movement_invert_endpoints_y), do: 22
|
||||
def parse_param(:movement_invert_endpoints_z), do: 23
|
||||
def parse_param(:movement_enable_endpoints_x), do: 25
|
||||
def parse_param(:movement_enable_endpoints_y), do: 26
|
||||
def parse_param(:movement_enable_endpoints_z), do: 27
|
||||
def parse_param(:movement_invert_motor_x), do: 31
|
||||
def parse_param(:movement_invert_motor_y), do: 32
|
||||
def parse_param(:movement_invert_motor_z), do: 33
|
||||
def parse_param(:movement_secondary_motor_x), do: 36
|
||||
def parse_param(:movement_secondary_motor_invert_x), do: 37
|
||||
def parse_param(:movement_steps_acc_dec_x), do: 41
|
||||
def parse_param(:movement_steps_acc_dec_y), do: 42
|
||||
def parse_param(:movement_steps_acc_dec_z), do: 43
|
||||
def parse_param(:movement_stop_at_home_x), do: 45
|
||||
def parse_param(:movement_stop_at_home_y), do: 46
|
||||
def parse_param(:movement_stop_at_home_z), do: 47
|
||||
def parse_param(:movement_home_up_x), do: 51
|
||||
def parse_param(:movement_home_up_y), do: 52
|
||||
def parse_param(:movement_home_up_z), do: 53
|
||||
def parse_param(:movement_step_per_mm_x), do: 55
|
||||
def parse_param(:movement_step_per_mm_y), do: 56
|
||||
def parse_param(:movement_step_per_mm_z), do: 57
|
||||
def parse_param(:movement_min_spd_x), do: 61
|
||||
def parse_param(:movement_min_spd_y), do: 62
|
||||
def parse_param(:movement_min_spd_z), do: 63
|
||||
def parse_param(:movement_home_spd_x), do: 65
|
||||
def parse_param(:movement_home_spd_y), do: 66
|
||||
def parse_param(:movement_home_spd_z), do: 67
|
||||
def parse_param(:movement_max_spd_x), do: 71
|
||||
def parse_param(:movement_max_spd_y), do: 72
|
||||
def parse_param(:movement_max_spd_z), do: 73
|
||||
def parse_param(:movement_invert_2_endpoints_x), do: 75
|
||||
def parse_param(:movement_invert_2_endpoints_y), do: 76
|
||||
def parse_param(:movement_invert_2_endpoints_z), do: 77
|
||||
def parse_param(:encoder_enabled_x), do: 101
|
||||
def parse_param(:encoder_enabled_y), do: 102
|
||||
def parse_param(:encoder_enabled_z), do: 103
|
||||
def parse_param(:encoder_type_x), do: 105
|
||||
def parse_param(:encoder_type_y), do: 106
|
||||
def parse_param(:encoder_type_z), do: 107
|
||||
def parse_param(:encoder_missed_steps_max_x), do: 111
|
||||
def parse_param(:encoder_missed_steps_max_y), do: 112
|
||||
def parse_param(:encoder_missed_steps_max_z), do: 113
|
||||
def parse_param(:encoder_scaling_x), do: 115
|
||||
def parse_param(:encoder_scaling_y), do: 116
|
||||
def parse_param(:encoder_scaling_z), do: 117
|
||||
def parse_param(:encoder_missed_steps_decay_x), do: 121
|
||||
def parse_param(:encoder_missed_steps_decay_y), do: 122
|
||||
def parse_param(:encoder_missed_steps_decay_z), do: 123
|
||||
def parse_param(:encoder_use_for_pos_x), do: 125
|
||||
def parse_param(:encoder_use_for_pos_y), do: 126
|
||||
def parse_param(:encoder_use_for_pos_z), do: 127
|
||||
def parse_param(:encoder_invert_x), do: 131
|
||||
def parse_param(:encoder_invert_y), do: 132
|
||||
def parse_param(:encoder_invert_z), do: 133
|
||||
def parse_param(:movement_axis_nr_steps_x), do: 141
|
||||
def parse_param(:movement_axis_nr_steps_y), do: 142
|
||||
def parse_param(:movement_axis_nr_steps_z), do: 143
|
||||
def parse_param(:movement_stop_at_max_x), do: 145
|
||||
def parse_param(:movement_stop_at_max_y), do: 146
|
||||
def parse_param(:movement_stop_at_max_z), do: 147
|
||||
def parse_param(:pin_guard_1_pin_nr), do: 201
|
||||
def parse_param(:pin_guard_1_time_out), do: 202
|
||||
def parse_param(:pin_guard_1_active_state), do: 203
|
||||
def parse_param(:pin_guard_2_pin_nr), do: 205
|
||||
def parse_param(:pin_guard_2_time_out), do: 206
|
||||
def parse_param(:pin_guard_2_active_state), do: 207
|
||||
def parse_param(:pin_guard_3_pin_nr), do: 211
|
||||
def parse_param(:pin_guard_3_time_out), do: 212
|
||||
def parse_param(:pin_guard_3_active_state), do: 213
|
||||
def parse_param(:pin_guard_4_pin_nr), do: 215
|
||||
def parse_param(:pin_guard_4_time_out), do: 216
|
||||
def parse_param(:pin_guard_4_active_state), do: 217
|
||||
def parse_param(:pin_guard_5_pin_nr), do: 221
|
||||
def parse_param(:pin_guard_5_time_out), do: 222
|
||||
def parse_param(:pin_guard_5_active_state), do: 223
|
||||
|
||||
@typedoc "Human readable param name."
|
||||
@type t :: :param_config_ok |
|
||||
:param_use_eeprom |
|
||||
:param_e_stop_on_mov_err |
|
||||
:param_mov_nr_retry |
|
||||
:movement_timeout_x |
|
||||
:movement_timeout_y |
|
||||
:movement_timeout_z |
|
||||
:movement_keep_active_x |
|
||||
:movement_keep_active_y |
|
||||
:movement_keep_active_z |
|
||||
:movement_home_at_boot_x |
|
||||
:movement_home_at_boot_y |
|
||||
:movement_home_at_boot_z |
|
||||
:movement_invert_endpoints_x |
|
||||
:movement_invert_endpoints_y |
|
||||
:movement_invert_endpoints_z |
|
||||
:movement_enable_endpoints_x |
|
||||
:movement_enable_endpoints_y |
|
||||
:movement_enable_endpoints_z |
|
||||
:movement_invert_motor_x |
|
||||
:movement_invert_motor_y |
|
||||
:movement_invert_motor_z |
|
||||
:movement_secondary_motor_x |
|
||||
:movement_secondary_motor_invert_x |
|
||||
:movement_steps_acc_dec_x |
|
||||
:movement_steps_acc_dec_y |
|
||||
:movement_steps_acc_dec_z |
|
||||
:movement_stop_at_home_x |
|
||||
:movement_stop_at_home_y |
|
||||
:movement_stop_at_home_z |
|
||||
:movement_home_up_x |
|
||||
:movement_home_up_y |
|
||||
:movement_home_up_z |
|
||||
:movement_step_per_mm_x |
|
||||
:movement_step_per_mm_y |
|
||||
:movement_step_per_mm_z |
|
||||
:movement_min_spd_x |
|
||||
:movement_min_spd_y |
|
||||
:movement_min_spd_z |
|
||||
:movement_home_spd_x |
|
||||
:movement_home_spd_y |
|
||||
:movement_home_spd_z |
|
||||
:movement_max_spd_x |
|
||||
:movement_max_spd_y |
|
||||
:movement_max_spd_z |
|
||||
:movement_invert_2_endpoints_x |
|
||||
:movement_invert_2_endpoints_y |
|
||||
:movement_invert_2_endpoints_z |
|
||||
:encoder_enabled_x |
|
||||
:encoder_enabled_y |
|
||||
:encoder_enabled_z |
|
||||
:encoder_type_x |
|
||||
:encoder_type_y |
|
||||
:encoder_type_z |
|
||||
:encoder_missed_steps_max_x |
|
||||
:encoder_missed_steps_max_y |
|
||||
:encoder_missed_steps_max_z |
|
||||
:encoder_scaling_x |
|
||||
:encoder_scaling_y |
|
||||
:encoder_scaling_z |
|
||||
:encoder_missed_steps_decay_x |
|
||||
:encoder_missed_steps_decay_y |
|
||||
:encoder_missed_steps_decay_z |
|
||||
:encoder_use_for_pos_x |
|
||||
:encoder_use_for_pos_y |
|
||||
:encoder_use_for_pos_z |
|
||||
:encoder_invert_x |
|
||||
:encoder_invert_y |
|
||||
:encoder_invert_z |
|
||||
:movement_axis_nr_steps_x |
|
||||
:movement_axis_nr_steps_y |
|
||||
:movement_axis_nr_steps_z |
|
||||
:movement_stop_at_max_x |
|
||||
:movement_stop_at_max_y |
|
||||
:movement_stop_at_max_z |
|
||||
:pin_guard_1_pin_nr |
|
||||
:pin_guard_1_time_out |
|
||||
:pin_guard_1_active_state |
|
||||
:pin_guard_2_pin_nr |
|
||||
:pin_guard_2_time_out |
|
||||
:pin_guard_2_active_state |
|
||||
:pin_guard_3_pin_nr |
|
||||
:pin_guard_3_time_out |
|
||||
:pin_guard_3_active_state |
|
||||
:pin_guard_4_pin_nr |
|
||||
:pin_guard_4_time_out |
|
||||
:pin_guard_4_active_state |
|
||||
:pin_guard_5_pin_nr |
|
||||
:pin_guard_5_time_out |
|
||||
:pin_guard_5_active_state
|
||||
|
||||
end
|
|
@ -1,192 +0,0 @@
|
|||
defmodule Farmbot.Firmware.Gcode.Parser do
|
||||
@moduledoc """
|
||||
Parses [farmbot-arduino-firmware](https://github.com/farmbot/farmbot-arduino-firmware) G-Codes.
|
||||
"""
|
||||
|
||||
import Farmbot.Firmware.Gcode.Param
|
||||
|
||||
@spec parse_code(binary) :: {binary | nil, tuple | atom}
|
||||
|
||||
# Status codes.
|
||||
@doc "Parse a code to an Elixir consumable message."
|
||||
def parse_code("R00 Q" <> tag), do: {tag, :idle}
|
||||
def parse_code("R01 Q" <> tag), do: {tag, :received}
|
||||
def parse_code("R02 Q" <> tag), do: {tag, :done}
|
||||
def parse_code("R03 Q" <> tag), do: {tag, :error}
|
||||
def parse_code("R04 Q" <> tag), do: {tag, :busy}
|
||||
|
||||
def parse_code("R05" <> _r), do: {nil, :noop}
|
||||
def parse_code("R06 " <> r), do: parse_report_calibration(r)
|
||||
def parse_code("R07 " <> _), do: {nil, :noop}
|
||||
def parse_code("R08 " <> echo),
|
||||
do: {:echo, {:echo, String.replace(echo, "\r", "")}}
|
||||
def parse_code("R09 " <> tag), do: {tag, :invalid_command}
|
||||
|
||||
# Report axis homing.
|
||||
def parse_code("R11 " <> tag), do: {tag, :report_axis_home_complete_x}
|
||||
def parse_code("R12 " <> tag), do: {tag, :report_axis_home_complete_y}
|
||||
def parse_code("R13 " <> tag), do: {tag, :report_axis_home_complete_z}
|
||||
|
||||
def parse_code("R15 " <> data) do
|
||||
["X" <> num_str, "Q" <> tag] = String.split(data, " ")
|
||||
{tag, {:report_axis_changed_x, String.to_integer(num_str)}}
|
||||
end
|
||||
|
||||
def parse_code("R16 " <> data) do
|
||||
["Y" <> num_str, "Q" <> tag] = String.split(data, " ")
|
||||
{tag, {:report_axis_changed_y, String.to_integer(num_str)}}
|
||||
end
|
||||
|
||||
def parse_code("R17 " <> data) do
|
||||
["Z" <> num_str, "Q" <> tag] = String.split(data, " ")
|
||||
{tag, {:report_axis_changed_z, String.to_integer(num_str)}}
|
||||
end
|
||||
|
||||
# Param report.
|
||||
def parse_code("R20 Q" <> tag), do: {tag, :report_params_complete}
|
||||
def parse_code("R21 " <> params), do: parse_pvq(params, :report_parameter_value)
|
||||
def parse_code("R23 " <> params), do: parse_report_axis_calibration(params)
|
||||
def parse_code("R31 " <> params), do: parse_pvq(params, :report_status_value)
|
||||
def parse_code("R41 " <> params), do: parse_pvq(params, :report_pin_value)
|
||||
|
||||
#TODO(connor) - remove one of these variants. (With or without Q) at some point.
|
||||
def parse_code("R71 Q" <> tag), do: {tag, :report_axis_timeout_x}
|
||||
def parse_code("R72 Q" <> tag), do: {tag, :report_axis_timeout_y}
|
||||
def parse_code("R73 Q" <> tag), do: {tag, :report_axis_timeout_z}
|
||||
def parse_code("R71"), do: {nil, :report_axis_timeout_x}
|
||||
def parse_code("R72"), do: {nil, :report_axis_timeout_y}
|
||||
def parse_code("R73"), do: {nil, :report_axis_timeout_z}
|
||||
|
||||
# Report Position.
|
||||
def parse_code("R81 " <> params), do: parse_end_stops(params)
|
||||
def parse_code("R82 " <> p), do: report_xyz(p, :report_current_position)
|
||||
def parse_code("R83 " <> v), do: parse_version(v)
|
||||
def parse_code("R84 " <> p), do: report_xyz(p, :report_encoder_position_scaled)
|
||||
def parse_code("R85 " <> p), do: report_xyz(p, :report_encoder_position_raw)
|
||||
def parse_code("R87 Q" <> q), do: {q, :report_emergency_lock}
|
||||
def parse_code("R88 Q" <> q), do: {q, :report_no_config}
|
||||
|
||||
def parse_code("R99 " <> message) do
|
||||
{nil, {:debug_message, message}}
|
||||
end
|
||||
|
||||
def parse_code(code) do
|
||||
{:unhandled_gcode, code}
|
||||
end
|
||||
|
||||
@spec parse_report_calibration(binary)
|
||||
:: {binary, {:report_calibration, binary, :idle | :home | :end}}
|
||||
defp parse_report_calibration(r) do
|
||||
[axis_and_status | [q]] = String.split(r, " Q")
|
||||
<<a::size(8), b::size(8)>> = axis_and_status
|
||||
|
||||
case <<b>> do
|
||||
"0" -> {q, {:report_calibration, <<a>>, :idle}}
|
||||
"1" -> {q, {:report_calibration, <<a>>, :home}}
|
||||
"2" -> {q, {:report_calibration, <<a>>, :end}}
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_report_axis_calibration(params) do
|
||||
["P" <> parm, "V" <> val, "Q" <> tag] = String.split(params, " ")
|
||||
|
||||
if parm in ["141", "142", "143"] do
|
||||
parm_name = :report_axis_calibration
|
||||
result = parse_param(String.to_integer(parm))
|
||||
case Float.parse(val) do
|
||||
{float, _} ->
|
||||
msg = {parm_name, result, float}
|
||||
{tag, msg}
|
||||
:error ->
|
||||
msg = {parm_name, result, String.to_integer(val)}
|
||||
{tag, msg}
|
||||
end
|
||||
else
|
||||
{tag, :noop}
|
||||
end
|
||||
end
|
||||
|
||||
@spec parse_version(binary) :: {binary, {:report_software_version, binary}}
|
||||
defp parse_version(version) do
|
||||
[v | [code]] = String.split(version, " Q")
|
||||
{code, {:report_software_version, v}}
|
||||
end
|
||||
|
||||
@type reporter ::
|
||||
:report_current_position
|
||||
| :report_encoder_position_scaled
|
||||
| :report_encoder_position_raw
|
||||
|
||||
@spec report_xyz(binary, reporter)
|
||||
:: {binary, {reporter, float(), float(), float()}}
|
||||
defp report_xyz(position, reporter) when is_bitstring(position),
|
||||
do: position |> String.split(" ") |> do_parse_pos(reporter)
|
||||
|
||||
@valid_position_reporters [
|
||||
:report_current_position,
|
||||
:report_encoder_position_scaled
|
||||
]
|
||||
defp do_parse_pos(["X" <> x, "Y" <> y, "Z" <> z, "Q" <> tag], reporter)
|
||||
when reporter in @valid_position_reporters
|
||||
do
|
||||
import String, only: [to_float: 1]
|
||||
msg = {reporter, to_float(x), to_float(y), to_float(z)}
|
||||
{tag, msg}
|
||||
end
|
||||
|
||||
defp do_parse_pos(["X" <> x, "Y" <> y, "Z" <> z, "Q" <> tag], reporter) do
|
||||
import String, only: [to_integer: 1]
|
||||
msg = {reporter, to_integer(x), to_integer(y), to_integer(z)}
|
||||
{tag, msg}
|
||||
end
|
||||
|
||||
defp do_parse_pos(l, _) do
|
||||
{:unhandled_gcode, Enum.join(l, " ")}
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec parse_end_stops(binary)
|
||||
:: {binary(), {:report_end_stops, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1}}
|
||||
def parse_end_stops(
|
||||
<<"XA", xa::size(8), 32,
|
||||
"XB", xb::size(8), 32,
|
||||
"YA", ya::size(8), 32,
|
||||
"YB", yb::size(8), 32,
|
||||
"ZA", za::size(8), 32,
|
||||
"ZB", zb::size(8), 32,
|
||||
"Q", tag::binary >>)
|
||||
do
|
||||
r = :report_end_stops
|
||||
msg = {r, xa |> pes, xb |> pes, ya |> pes, yb |> pes, za |> pes, zb |> pes}
|
||||
{tag, msg}
|
||||
end
|
||||
|
||||
# lol
|
||||
@spec pes(48 | 49) :: 0 | 1
|
||||
defp pes(48), do: 0
|
||||
defp pes(49), do: 1
|
||||
|
||||
def parse_pvq(params, :report_parameter_value)
|
||||
when is_bitstring(params),
|
||||
do: params |> String.split(" ") |> do_parse_params
|
||||
|
||||
def parse_pvq(params, human_readable_param_name)
|
||||
when is_bitstring(params) and is_atom(human_readable_param_name),
|
||||
do: params |> String.split(" ") |> do_parse_pvq(human_readable_param_name)
|
||||
|
||||
defp do_parse_pvq([p, v, q], human_readable_param_name) do
|
||||
import String, only: [split: 2, to_integer: 1]
|
||||
[_, rp] = split(p, "P")
|
||||
[_, rv] = split(v, "V")
|
||||
[_, rq] = split(q, "Q")
|
||||
{rq, {human_readable_param_name, to_integer(rp), to_integer(rv)}}
|
||||
end
|
||||
|
||||
defp do_parse_params([p, v, q]) do
|
||||
import String, only: [split: 2, to_integer: 1]
|
||||
[_, rp] = split(p, "P")
|
||||
[_, rv] = split(v, "V")
|
||||
[_, rq] = split(q, "Q")
|
||||
{rq, {:report_parameter_value, parse_param(to_integer(rp)), to_integer(rv)}}
|
||||
end
|
||||
end
|
|
@ -1,86 +0,0 @@
|
|||
defmodule Farmbot.Firmware.Handler do
|
||||
@moduledoc """
|
||||
Any module that implements this behaviour should be a GenStage.
|
||||
|
||||
The implementng stage should communicate with the various Farmbot
|
||||
hardware such as motors and encoders. The `Farmbot.Firmware` module
|
||||
will subscribe_to: the implementing handler. Events should be
|
||||
Gcodes as parsed by `Farmbot.Firmware.Gcode.Parser`.
|
||||
"""
|
||||
|
||||
@typedoc "Pid of a firmware implementation."
|
||||
@type handler :: pid
|
||||
|
||||
@doc "Start a firmware handler."
|
||||
@callback start_link :: {:ok, handler}
|
||||
|
||||
@typedoc false
|
||||
@type fw_ret_val :: :ok | {:error, term}
|
||||
|
||||
@typedoc false
|
||||
@type vec3 :: Farmbot.Firmware.Vec3.t
|
||||
|
||||
@typedoc false
|
||||
@type axis :: Farmbot.Firmware.Vec3.axis
|
||||
|
||||
@typedoc false
|
||||
@type fw_param :: Farmbot.Firmware.Gcode.Param.t
|
||||
|
||||
@typedoc "Speed of a command."
|
||||
@type speed :: number
|
||||
|
||||
@typedoc "Pin"
|
||||
@type pin :: number
|
||||
|
||||
@typedoc "Mode of a pin."
|
||||
@type pin_mode :: :digital | :analog
|
||||
|
||||
@doc "Move to a position."
|
||||
@callback move_absolute(handler, vec3, speed, speed, speed) :: fw_ret_val
|
||||
|
||||
@doc "Calibrate an axis."
|
||||
@callback calibrate(handler, axis) :: fw_ret_val
|
||||
|
||||
@doc "Find home on an axis."
|
||||
@callback find_home(handler, axis) :: fw_ret_val
|
||||
|
||||
@doc "Manually set an axis's current position to zero."
|
||||
@callback zero(handler, axis) :: fw_ret_val
|
||||
|
||||
@doc "Home an axis."
|
||||
@callback home(handler, axis) :: fw_ret_val
|
||||
|
||||
@doc "Home every axis."
|
||||
@callback home_all(handler) :: fw_ret_val
|
||||
|
||||
@doc "Update a paramater."
|
||||
@callback update_param(handler, fw_param, number) :: fw_ret_val
|
||||
|
||||
@doc "Read a paramater."
|
||||
@callback read_param(handler, fw_param) :: fw_ret_val
|
||||
|
||||
@doc "Read all params"
|
||||
@callback read_all_params(handler) :: fw_ret_val
|
||||
|
||||
@doc "Lock the firmware."
|
||||
@callback emergency_lock(handler) :: fw_ret_val
|
||||
|
||||
@doc "Unlock the firmware."
|
||||
@callback emergency_unlock(handler) :: fw_ret_val
|
||||
|
||||
@doc "Read a pin."
|
||||
@callback read_pin(handler, pin, pin_mode) :: fw_ret_val
|
||||
|
||||
@doc "Write a pin."
|
||||
@callback write_pin(handler, pin, pin_mode, number) :: fw_ret_val
|
||||
|
||||
@doc "Set a pin mode (input/output)"
|
||||
@callback set_pin_mode(handler, pin, :input | :input_pullup | :output) :: fw_ret_val
|
||||
|
||||
@doc "Request firmware version."
|
||||
@callback request_software_version(handler) :: fw_ret_val
|
||||
|
||||
@doc "Set angle on a servo pin."
|
||||
@callback set_servo_angle(handler, pin, number) :: fw_ret_val
|
||||
|
||||
end
|
|
@ -1,24 +0,0 @@
|
|||
defmodule Farmbot.Firmware.Supervisor do
|
||||
@moduledoc false
|
||||
use Supervisor
|
||||
|
||||
@doc "Reinitializes the Firmware stack. Warning has MANY SIDE EFFECTS."
|
||||
def reinitialize do
|
||||
Farmbot.Firmware.UartHandler.AutoDetector.start_link([])
|
||||
Supervisor.terminate_child(Farmbot.Core, Farmbot.Firmware.Supervisor)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def start_link(args) do
|
||||
Supervisor.start_link(__MODULE__, args, [name: __MODULE__])
|
||||
end
|
||||
|
||||
def init([]) do
|
||||
children = [
|
||||
{Farmbot.Firmware.EstopTimer, []},
|
||||
{Farmbot.Firmware, []},
|
||||
]
|
||||
|
||||
Supervisor.init(children, [strategy: :one_for_one])
|
||||
end
|
||||
end
|
|
@ -1,39 +0,0 @@
|
|||
defmodule Farmbot.Firmware.Utils do
|
||||
@moduledoc """
|
||||
Helpful utilities for working with Firmware data.
|
||||
"""
|
||||
|
||||
@compile {:inline, [num_to_bool: 1]}
|
||||
@doc "changes a number to a boolean. 1 => true, 0 => false"
|
||||
def num_to_bool(num) when num == 1, do: true
|
||||
def num_to_bool(num) when num == 0, do: false
|
||||
|
||||
@compile {:inline, [fmnt_float: 1]}
|
||||
@doc "Format a float to a binary with two leading decimals."
|
||||
def fmnt_float(num) when is_float(num),
|
||||
do: :erlang.float_to_binary(num, [:compact, {:decimals, 2}])
|
||||
|
||||
def fmnt_float(num) when is_integer(num), do: fmnt_float(num / 1)
|
||||
|
||||
@compile {:inline, [extract_pin_mode: 1]}
|
||||
@doc "Changes `:digital` => 0, and `:analog` => 1"
|
||||
def extract_pin_mode(:digital), do: 0
|
||||
def extract_pin_mode(:analog), do: 1
|
||||
def extract_pin_mode(0), do: 0
|
||||
def extract_pin_mode(1), do: 1
|
||||
|
||||
|
||||
# https://github.com/arduino/Arduino/blob/2bfe164b9a5835e8cb6e194b928538a9093be333/hardware/arduino/avr/cores/arduino/Arduino.h#L43-L45
|
||||
@compile {:inline, [extract_set_pin_mode: 1]}
|
||||
@doc "Changes `set_pin_mode` arg to an integer for the Firmware."
|
||||
def extract_set_pin_mode(:input), do: 0x0
|
||||
def extract_set_pin_mode(:input_pullup), do: 0x2
|
||||
def extract_set_pin_mode(:output), do: 0x1
|
||||
|
||||
@doc "replace the firmware handler at runtime."
|
||||
def replace_firmware_handler(handler) do
|
||||
old = Application.get_all_env(:farmbot_core)[:behaviour]
|
||||
new = Keyword.put(old, :firmware_handler, handler)
|
||||
Application.put_env(:farmbot_core, :behaviour, new)
|
||||
end
|
||||
end
|
|
@ -1,30 +0,0 @@
|
|||
defmodule Farmbot.Firmware.Vec3 do
|
||||
@moduledoc "A three position vector."
|
||||
alias Farmbot.Firmware.Vec3
|
||||
|
||||
defstruct [x: -1.0, y: -1.0, z: -1.0]
|
||||
|
||||
@typedoc "Axis label."
|
||||
@type axis :: :x | :y | :z
|
||||
|
||||
@typedoc @moduledoc
|
||||
@type t :: %__MODULE__{x: number, y: number, z: number}
|
||||
|
||||
def new(x, y, z) do
|
||||
%Vec3{x: x, y: y, z: z}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Farmbot.Firmware.Vec3 do
|
||||
import Farmbot.Firmware.Utils, only: [fmnt_float: 1]
|
||||
def inspect(vec3, _) do
|
||||
"(#{fmnt_float(vec3.x)}, #{fmnt_float(vec3.y)}, #{fmnt_float(vec3.z)})"
|
||||
end
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Farmbot.Firmware.Vec3 do
|
||||
import Farmbot.Firmware.Utils, only: [fmnt_float: 1]
|
||||
def to_string(vec3) do
|
||||
"(#{fmnt_float(vec3.x)}, #{fmnt_float(vec3.y)}, #{fmnt_float(vec3.z)})"
|
||||
end
|
||||
end
|
|
@ -45,6 +45,8 @@ defmodule Farmbot.Log do
|
|||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :binary_id, autogenerate: true}
|
||||
|
||||
schema "logs" do
|
||||
field(:level, LogLevelType)
|
||||
field(:verbosity, :integer)
|
||||
|
@ -62,7 +64,7 @@ defmodule Farmbot.Log do
|
|||
end
|
||||
|
||||
@required_fields [:level, :verbosity, :message]
|
||||
@optional_fields [:meta, :function, :file, :line, :module]
|
||||
@optional_fields [:meta, :function, :file, :line, :module, :id, :inserted_at, :updated_at]
|
||||
|
||||
def changeset(log, params \\ %{}) do
|
||||
log
|
||||
|
@ -88,13 +90,13 @@ defmodule Farmbot.Log do
|
|||
end
|
||||
end
|
||||
|
||||
defp color(:debug), do: IO.ANSI.light_blue()
|
||||
defp color(:info), do: IO.ANSI.cyan()
|
||||
defp color(:busy), do: IO.ANSI.blue()
|
||||
defp color(:debug), do: IO.ANSI.light_blue()
|
||||
defp color(:info), do: IO.ANSI.cyan()
|
||||
defp color(:busy), do: IO.ANSI.blue()
|
||||
defp color(:success), do: IO.ANSI.green()
|
||||
defp color(:warn), do: IO.ANSI.yellow()
|
||||
defp color(:error), do: IO.ANSI.red()
|
||||
defp color(:normal), do: IO.ANSI.normal()
|
||||
defp color(_), do: IO.ANSI.normal()
|
||||
defp color(:warn), do: IO.ANSI.yellow()
|
||||
defp color(:error), do: IO.ANSI.red()
|
||||
defp color(:normal), do: IO.ANSI.normal()
|
||||
defp color(_), do: IO.ANSI.normal()
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,6 +4,7 @@ defmodule Farmbot.Logger do
|
|||
"""
|
||||
|
||||
alias Farmbot.Logger.Repo
|
||||
import Ecto.Query
|
||||
|
||||
@doc "Send a debug message to log endpoints"
|
||||
defmacro debug(verbosity, message, meta \\ []) do
|
||||
|
@ -69,23 +70,21 @@ defmodule Farmbot.Logger do
|
|||
|
||||
@doc "Gets all available logs and deletes them."
|
||||
def handle_all_logs do
|
||||
Repo.all(Farmbot.Log)
|
||||
|> Enum.map(&Repo.delete!(&1))
|
||||
Repo.all(from(l in Farmbot.Log, order_by: l.inserted_at))
|
||||
|> Enum.map(&Repo.delete!/1)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def dispatch_log(%Macro.Env{} = env, level, verbosity, message, meta)
|
||||
when level in [:info, :debug, :busy, :warn, :success, :error, :fun]
|
||||
and is_number(verbosity)
|
||||
and is_binary(message)
|
||||
and is_list(meta)
|
||||
do
|
||||
fun = case env.function do
|
||||
{fun, ar} -> "#{fun}/#{ar}"
|
||||
nil -> "no_function"
|
||||
end
|
||||
when level in [:info, :debug, :busy, :warn, :success, :error, :fun] and is_number(verbosity) and
|
||||
is_binary(message) and is_list(meta) do
|
||||
fun =
|
||||
case env.function do
|
||||
{fun, ar} -> "#{fun}/#{ar}"
|
||||
nil -> "no_function"
|
||||
end
|
||||
|
||||
struct(Farmbot.Log, [
|
||||
struct(Farmbot.Log,
|
||||
level: level,
|
||||
verbosity: verbosity,
|
||||
message: message,
|
||||
|
@ -93,7 +92,8 @@ defmodule Farmbot.Logger do
|
|||
function: fun,
|
||||
file: env.file,
|
||||
line: env.line,
|
||||
module: env.module])
|
||||
module: env.module
|
||||
)
|
||||
|> dispatch_log()
|
||||
end
|
||||
|
||||
|
@ -102,9 +102,6 @@ defmodule Farmbot.Logger do
|
|||
log
|
||||
|> insert_log!()
|
||||
|> elixir_log()
|
||||
|> fn(log) ->
|
||||
Farmbot.Registry.dispatch(__MODULE__, {:log_ready, log.id})
|
||||
end.()
|
||||
end
|
||||
|
||||
defp elixir_log(%Farmbot.Log{} = log) do
|
||||
|
@ -114,9 +111,10 @@ defmodule Farmbot.Logger do
|
|||
function: log.function,
|
||||
file: log.file,
|
||||
line: log.line,
|
||||
module: log.module,
|
||||
module: log.module
|
||||
# time: time
|
||||
]
|
||||
|
||||
level = log.level
|
||||
logger_level = if level in [:info, :debug, :warn, :error], do: level, else: :info
|
||||
Elixir.Logger.bare_log(logger_level, log, logger_meta)
|
||||
|
@ -129,7 +127,7 @@ defmodule Farmbot.Logger do
|
|||
def should_log?(nil, _), do: false
|
||||
|
||||
def should_log?(module, verbosity) when verbosity <= 3 do
|
||||
List.first(Module.split(module)) == "Farmbot"
|
||||
List.first(Module.split(module)) == "Farmbot"
|
||||
end
|
||||
|
||||
def should_log?(_, _), do: false
|
||||
|
|
|
@ -10,6 +10,7 @@ defmodule Farmbot.Logger.Supervisor do
|
|||
children = [
|
||||
supervisor(Farmbot.Logger.Repo, [])
|
||||
]
|
||||
|
||||
opts = [strategy: :one_for_all]
|
||||
supervise(children, opts)
|
||||
end
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
defmodule Farmbot.Registry do
|
||||
@moduledoc "Farmbot System Global Registry"
|
||||
@reg FarmbotRegistry
|
||||
use GenServer
|
||||
|
||||
@doc false
|
||||
def start_link(args) do
|
||||
GenServer.start_link(__MODULE__, args, [name: __MODULE__])
|
||||
end
|
||||
|
||||
@doc "Dispatch a global event from a namespace."
|
||||
def dispatch(namespace, event) do
|
||||
GenServer.call(__MODULE__, {:dispatch, namespace, event})
|
||||
end
|
||||
|
||||
def subscribe(pid \\ self()) do
|
||||
Elixir.Registry.register(@reg, __MODULE__, pid)
|
||||
end
|
||||
|
||||
def drop_pattern(pattern, me, acc \\ []) do
|
||||
receive do
|
||||
{__MODULE__, {^pattern, _}} -> drop_pattern(pattern, me, acc)
|
||||
other -> drop_pattern(pattern, me, [other | acc])
|
||||
after 100 ->
|
||||
for msg <- Enum.reverse(acc) do
|
||||
send(me, msg)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def init([]) do
|
||||
# partitions = System.schedulers_online
|
||||
partitions = 1
|
||||
opts = [keys: :duplicate, partitions: partitions, name: @reg]
|
||||
{:ok, reg} = Elixir.Registry.start_link(opts)
|
||||
{:ok, %{reg: reg}}
|
||||
end
|
||||
|
||||
def handle_call({:dispatch, ns, event}, _from, state) do
|
||||
Elixir.Registry.dispatch(@reg, __MODULE__, fn(entries) ->
|
||||
for {pid, _} <- entries, do: send(pid, {__MODULE__, {ns, event}})
|
||||
end)
|
||||
{:reply, :ok, state}
|
||||
end
|
||||
end
|
|
@ -65,9 +65,8 @@ defmodule FarmbotCore.MixProject do
|
|||
defp deps do
|
||||
[
|
||||
{:farmbot_celery_script, path: "../farmbot_celery_script", env: Mix.env()},
|
||||
{:farmbot_firmware, path: "../farmbot_firmware", env: Mix.env()},
|
||||
{:elixir_make, "~> 0.4", runtime: false},
|
||||
{:nerves_uart, "~> 1.2"},
|
||||
{:gen_stage, "~> 0.14"},
|
||||
{:sqlite_ecto2, "~> 2.3"},
|
||||
{:timex, "~> 3.4"},
|
||||
{:plug_cowboy, "~> 2.0"},
|
||||
|
|
|
@ -10,8 +10,10 @@ defmodule Elixir.Farmbot.Asset.Repo.Migrations.CreateFbosConfigsTable do
|
|||
add(:beta_opt_in, :boolean)
|
||||
add(:disable_factory_reset, :boolean)
|
||||
add(:firmware_hardware, :string)
|
||||
add(:firmware_path, :string)
|
||||
add(:firmware_input_log, :boolean)
|
||||
add(:firmware_output_log, :boolean)
|
||||
add(:firmware_debug_log, :boolean)
|
||||
add(:network_not_found_timer, :integer)
|
||||
add(:os_auto_update, :boolean)
|
||||
add(:sequence_body_log, :boolean)
|
||||
|
|
|
@ -2,10 +2,10 @@ defmodule Farmbot.Config.Repo.Migrations.AddFirmwareIoLog do
|
|||
use Ecto.Migration
|
||||
|
||||
import Farmbot.Config.MigrationHelpers
|
||||
@io_logs Application.get_env(:farmbot_core, :firmware_io_logs, false)
|
||||
@default_firmware_io_logs Application.get_env(:farmbot_core, :default_firmware_io_logs, false)
|
||||
|
||||
def change do
|
||||
create_settings_config("firmware_input_log", :bool, @io_logs)
|
||||
create_settings_config("firmware_output_log", :bool, @io_logs)
|
||||
create_settings_config("firmware_input_log", :bool, @default_firmware_io_logs)
|
||||
create_settings_config("firmware_output_log", :bool, @default_firmware_io_logs)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,8 @@ defmodule Farmbot.Logger.Repo.Migrations.AddLogBuffer do
|
|||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table("logs") do
|
||||
create table("logs", primary_key: false) do
|
||||
add(:id, :binary_id, primary_key: true)
|
||||
add(:message, :text)
|
||||
add(:level, :string)
|
||||
add(:verbosity, :integer)
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
defmodule Farmbot.FbosConfigWorkerTest do
|
||||
use ExUnit.Case
|
||||
alias Farmbot.Asset.FbosConfig
|
||||
|
||||
test "adds configs to bot state and config_storage" do
|
||||
conf =
|
||||
FbosConfig.changeset(%FbosConfig{}, %{
|
||||
arduino_debug_messages: true,
|
||||
auto_sync: false,
|
||||
beta_opt_in: true,
|
||||
disable_factory_reset: false,
|
||||
firmware_hardware: "farmduino_k14",
|
||||
firmware_input_log: false,
|
||||
firmware_output_log: false,
|
||||
id: 145,
|
||||
network_not_found_timer: nil,
|
||||
os_auto_update: false,
|
||||
sequence_body_log: true,
|
||||
sequence_complete_log: true,
|
||||
sequence_init_log: true
|
||||
})
|
||||
|> Farmbot.Asset.Repo.insert!()
|
||||
|
||||
:ok = Farmbot.AssetMonitor.force_checkup()
|
||||
|
||||
# Wait for the timeout to be dispatched
|
||||
Process.sleep(100)
|
||||
|
||||
state_conf = Farmbot.BotState.fetch().configuration
|
||||
assert state_conf.arduino_debug_messages == conf.arduino_debug_messages
|
||||
assert state_conf.auto_sync == conf.auto_sync
|
||||
assert state_conf.beta_opt_in == conf.beta_opt_in
|
||||
assert state_conf.disable_factory_reset == conf.disable_factory_reset
|
||||
assert state_conf.firmware_hardware == conf.firmware_hardware
|
||||
assert state_conf.firmware_input_log == conf.firmware_input_log
|
||||
assert state_conf.firmware_output_log == conf.firmware_output_log
|
||||
assert state_conf.network_not_found_timer == conf.network_not_found_timer
|
||||
assert state_conf.os_auto_update == conf.os_auto_update
|
||||
assert state_conf.sequence_body_log == conf.sequence_body_log
|
||||
assert state_conf.sequence_complete_log == conf.sequence_complete_log
|
||||
assert state_conf.sequence_init_log == conf.sequence_init_log
|
||||
|
||||
# TODO(Connor) assert config_storage
|
||||
end
|
||||
end
|
|
@ -0,0 +1,94 @@
|
|||
defmodule Farmbot.BotStateNGTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Farmbot.BotStateNG
|
||||
|
||||
describe "pins" do
|
||||
test "adds pins to the state" do
|
||||
orig = BotStateNG.new()
|
||||
assert Enum.empty?(orig.pins)
|
||||
|
||||
one_pin =
|
||||
BotStateNG.add_or_update_pin(orig, 10, 1, 2)
|
||||
|> Ecto.Changeset.apply_changes()
|
||||
|
||||
assert one_pin.pins[10] == %{mode: 1, value: 2}
|
||||
|
||||
two_pins =
|
||||
BotStateNG.add_or_update_pin(one_pin, 20, 1, 20)
|
||||
|> Ecto.Changeset.apply_changes()
|
||||
|
||||
assert two_pins.pins[10] == %{mode: 1, value: 2}
|
||||
assert two_pins.pins[20] == %{mode: 1, value: 20}
|
||||
end
|
||||
|
||||
test "updates an existing pin" do
|
||||
orig = BotStateNG.new()
|
||||
assert Enum.empty?(orig.pins)
|
||||
|
||||
one_pin =
|
||||
BotStateNG.add_or_update_pin(orig, 10, 1, 2)
|
||||
|> Ecto.Changeset.apply_changes()
|
||||
|
||||
assert one_pin.pins[10] == %{mode: 1, value: 2}
|
||||
|
||||
one_pin_updated =
|
||||
BotStateNG.add_or_update_pin(one_pin, 10, 1, 50)
|
||||
|> Ecto.Changeset.apply_changes()
|
||||
|
||||
assert one_pin_updated.pins[10] == %{mode: 1, value: 50}
|
||||
end
|
||||
end
|
||||
|
||||
describe "informational_settings" do
|
||||
test "reports soc_temp" do
|
||||
orig = BotStateNG.new()
|
||||
|
||||
mut =
|
||||
BotStateNG.changeset(orig, %{informational_settings: %{soc_temp: 100}})
|
||||
|> Ecto.Changeset.apply_changes()
|
||||
|
||||
assert mut.informational_settings.soc_temp == 100
|
||||
end
|
||||
|
||||
test "reports disk_usage" do
|
||||
orig = BotStateNG.new()
|
||||
|
||||
mut =
|
||||
BotStateNG.changeset(orig, %{informational_settings: %{disk_usage: 100}})
|
||||
|> Ecto.Changeset.apply_changes()
|
||||
|
||||
assert mut.informational_settings.disk_usage == 100
|
||||
end
|
||||
|
||||
test "reports memory_usage" do
|
||||
orig = BotStateNG.new()
|
||||
|
||||
mut =
|
||||
BotStateNG.changeset(orig, %{informational_settings: %{memory_usage: 512}})
|
||||
|> Ecto.Changeset.apply_changes()
|
||||
|
||||
assert mut.informational_settings.memory_usage == 512
|
||||
end
|
||||
|
||||
test "reports uptime" do
|
||||
orig = BotStateNG.new()
|
||||
|
||||
mut =
|
||||
BotStateNG.changeset(orig, %{informational_settings: %{uptime: 5000}})
|
||||
|> Ecto.Changeset.apply_changes()
|
||||
|
||||
assert mut.informational_settings.uptime == 5000
|
||||
end
|
||||
|
||||
test "reports wifi_level" do
|
||||
orig = BotStateNG.new()
|
||||
|
||||
mut =
|
||||
BotStateNG.changeset(orig, %{informational_settings: %{wifi_level: 52}})
|
||||
|> Ecto.Changeset.apply_changes()
|
||||
|
||||
assert mut.informational_settings.wifi_level == 52
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,42 @@
|
|||
defmodule Farmbot.BotStateTest do
|
||||
use ExUnit.Case
|
||||
alias Farmbot.BotState
|
||||
|
||||
describe "bot state pub/sub" do
|
||||
test "subscribes to bot state updates" do
|
||||
{:ok, bot_state_pid} = BotState.start_link([], [])
|
||||
_initial_state = BotState.subscribe(bot_state_pid)
|
||||
:ok = BotState.set_user_env(bot_state_pid, "some_key", "some_val")
|
||||
assert_receive {BotState, %Ecto.Changeset{valid?: true}}
|
||||
end
|
||||
|
||||
test "invalid data doesn't get dispatched" do
|
||||
{:ok, bot_state_pid} = BotState.start_link([], [])
|
||||
_initial_state = BotState.subscribe(bot_state_pid)
|
||||
result = BotState.report_disk_usage(bot_state_pid, "this is invalid")
|
||||
assert match?({:error, %Ecto.Changeset{valid?: false}}, result)
|
||||
refute_receive {BotState, %Ecto.Changeset{valid?: true}}
|
||||
end
|
||||
|
||||
test "subscribing links current process" do
|
||||
# Trap exits so we can assure we can see bot the
|
||||
# BotState processess and the subscriber process crash.
|
||||
Process.flag(:trap_exit, true)
|
||||
|
||||
# two links, BotState and Subscriber
|
||||
{:ok, bot_state_pid} = BotState.start_link([], [])
|
||||
|
||||
fun = fn ->
|
||||
_initial_state = BotState.subscribe(bot_state_pid)
|
||||
exit(:crash)
|
||||
end
|
||||
|
||||
# Spawn the subscriber function
|
||||
fun_pid = spawn_link(fun)
|
||||
|
||||
# Make sure both BotState and Subscriber crashes
|
||||
assert_receive {:EXIT, ^fun_pid, :crash}
|
||||
assert_receive {:EXIT, ^bot_state_pid, :crash}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,12 +0,0 @@
|
|||
defmodule Farmbot.BotStateTest do
|
||||
use ExUnit.Case, async: false
|
||||
alias Farmbot.{BotState, Config}
|
||||
|
||||
test "writing config values goes into state" do
|
||||
Config.update_config_value(:bool, "settings", "log_amqp_connected", true)
|
||||
assert BotState.fetch().configuration.log_amqp_connected
|
||||
|
||||
Config.update_config_value(:bool, "settings", "log_amqp_connected", false)
|
||||
refute BotState.fetch().configuration.log_amqp_connected
|
||||
end
|
||||
end
|
|
@ -0,0 +1,34 @@
|
|||
defmodule Farmbot.Core.FirmwareEstopTimerTest do
|
||||
use ExUnit.Case
|
||||
alias Farmbot.Core.FirmwareEstopTimer
|
||||
|
||||
test "calls a function in X MS" do
|
||||
test_pid = self()
|
||||
timeout_ms = :rand.uniform(20)
|
||||
|
||||
timeout_function = fn ->
|
||||
send(test_pid, :estop_timer_message)
|
||||
end
|
||||
|
||||
args = [timeout_function: timeout_function, timeout_ms: timeout_ms]
|
||||
{:ok, pid} = FirmwareEstopTimer.start_link(args, [])
|
||||
_timer = FirmwareEstopTimer.start_timer(pid)
|
||||
assert_receive :estop_timer_message, timeout_ms + 5
|
||||
end
|
||||
|
||||
test "doesn't call function if canceled" do
|
||||
timeout_ms = :rand.uniform(20)
|
||||
test_pid = self()
|
||||
|
||||
timeout_function = fn ->
|
||||
send(test_pid, :estop_timer_message)
|
||||
flunk("This function should never be called")
|
||||
end
|
||||
|
||||
args = [timeout_function: timeout_function, timeout_ms: timeout_ms]
|
||||
{:ok, pid} = FirmwareEstopTimer.start_link(args, [])
|
||||
timer = FirmwareEstopTimer.start_timer(pid)
|
||||
^timer = FirmwareEstopTimer.cancel_timer(pid)
|
||||
refute_receive :estop_timer_message
|
||||
end
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
defmodule Farmbot.LoggerTest do
|
||||
use ExUnit.Case
|
||||
require Farmbot.Logger
|
||||
|
||||
test "allows handling a log more than once by re-inserting it." do
|
||||
log = Farmbot.Logger.debug(1, "Test log ABC")
|
||||
# Handling a log should delete it from the store.
|
||||
assert Enum.find(Farmbot.Logger.handle_all_logs(), &Kernel.==(Map.fetch!(&1, :id), log.id))
|
||||
# Thus, handling all logs again should mean the log
|
||||
# isn't there any more
|
||||
refute Enum.find(Farmbot.Logger.handle_all_logs(), &Kernel.==(Map.fetch!(&1, :id), log.id))
|
||||
|
||||
# insert the log again
|
||||
assert Farmbot.Logger.insert_log!(log)
|
||||
|
||||
# Make sure the log is available for handling again.
|
||||
assert Enum.find(Farmbot.Logger.handle_all_logs(), &Kernel.==(Map.fetch!(&1, :id), log.id))
|
||||
end
|
||||
end
|
|
@ -1,26 +1,24 @@
|
|||
use Mix.Config
|
||||
config :farmbot_core, Farmbot.AssetWorker.Farmbot.Asset.FarmEvent,
|
||||
checkup_time_ms: 10_000
|
||||
config :farmbot_core, Farmbot.AssetWorker.Farmbot.Asset.FarmEvent, checkup_time_ms: 10_000
|
||||
|
||||
config :farmbot_core, Farmbot.AssetMonitor,
|
||||
checkup_time_ms: 30_000
|
||||
config :farmbot_core, Elixir.Farmbot.AssetWorker.Farmbot.Asset.PinBinding,
|
||||
gpio_handler: Farmbot.PinBindingWorker.StubGPIOHandler,
|
||||
error_retry_time_ms: 30_000
|
||||
|
||||
config :farmbot_core, Farmbot.AssetWorker.Farmbot.Asset.FarmwareInstallation,
|
||||
error_retry_time_ms: 30_000,
|
||||
install_dir: "/tmp/farmware"
|
||||
|
||||
config :farmbot_core, Farmbot.AssetMonitor, checkup_time_ms: 30_000
|
||||
|
||||
config :farmbot_core, :behaviour,
|
||||
firmware_handler: Farmbot.Firmware.StubHandler,
|
||||
leds_handler: Farmbot.Leds.StubHandler,
|
||||
pin_binding_handler: Farmbot.PinBinding.StubHandler,
|
||||
celery_script_io_layer: Farmbot.Core.CeleryScript.StubIOLayer,
|
||||
json_parser: Farmbot.JSON.JasonParser
|
||||
|
||||
config :farmbot_core,
|
||||
ecto_repos: [Farmbot.Config.Repo, Farmbot.Logger.Repo, Farmbot.Asset.Repo],
|
||||
expected_fw_versions: ["6.4.0.F", "6.4.0.R", "6.4.0.G"],
|
||||
default_firmware_io_logs: false,
|
||||
default_server: "https://my.farm.bot",
|
||||
default_currently_on_beta:
|
||||
String.contains?(to_string(:os.cmd('git rev-parse --abbrev-ref HEAD')), "beta"),
|
||||
firmware_io_logs: false,
|
||||
farm_event_debug_log: false
|
||||
String.contains?(to_string(:os.cmd('git rev-parse --abbrev-ref HEAD')), "beta")
|
||||
|
|
|
@ -3,6 +3,9 @@ defmodule Farmbot.AMQP.BotStateTransport do
|
|||
use AMQP
|
||||
require Farmbot.Logger
|
||||
|
||||
# Pushes a state tree every 5 seconds for good luck.
|
||||
@default_force_time_ms 5_000
|
||||
@default_error_retry_ms 100
|
||||
@exchange "amq.topic"
|
||||
|
||||
defstruct [:conn, :chan, :bot, :state_cache]
|
||||
|
@ -19,37 +22,38 @@ defmodule Farmbot.AMQP.BotStateTransport do
|
|||
|
||||
def init([conn, jwt]) do
|
||||
Process.flag(:sensitive, true)
|
||||
Farmbot.Registry.subscribe()
|
||||
initial_bot_state = Farmbot.BotState.subscribe()
|
||||
{:ok, chan} = AMQP.Channel.open(conn)
|
||||
:ok = Basic.qos(chan, global: true)
|
||||
{:ok, struct(State, conn: conn, chan: chan, bot: jwt.bot)}
|
||||
{:ok, struct(State, conn: conn, chan: chan, bot: jwt.bot, state_cache: initial_bot_state), 0}
|
||||
end
|
||||
|
||||
def handle_cast(:force, %{state_cache: bot_state} = state) do
|
||||
push_bot_state(state.chan, state.bot, bot_state)
|
||||
{:noreply, state}
|
||||
def handle_cast(:force, state) do
|
||||
{:noreply, state, 0}
|
||||
end
|
||||
|
||||
def handle_info(
|
||||
{Farmbot.Registry, {Farmbot.BotState, bot_state}},
|
||||
%{state_cache: bot_state} = state
|
||||
) do
|
||||
# IO.puts "no state change"
|
||||
{:noreply, state}
|
||||
def handle_info(:timeout, %{state_cache: bot_state} = state) do
|
||||
case push_bot_state(state.chan, state.bot, bot_state) do
|
||||
:ok ->
|
||||
{:noreply, state, @default_force_time_ms}
|
||||
|
||||
error ->
|
||||
Farmbot.Logger.error(1, "Failed to dispatch BotState: #{inspect(error)}")
|
||||
{:noreply, state, @default_error_retry_ms}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info({Farmbot.Registry, {Farmbot.BotState, bot_state}}, state) do
|
||||
# IO.puts "pushing state"
|
||||
state.state_cache
|
||||
cache = push_bot_state(state.chan, state.bot, bot_state)
|
||||
{:noreply, %{state | state_cache: cache}}
|
||||
def handle_info({Farmbot.BotState, change}, state) do
|
||||
new_state_cache = Ecto.Changeset.apply_changes(change)
|
||||
{:noreply, %{state | state_cache: new_state_cache}, 0}
|
||||
end
|
||||
|
||||
def handle_info({Farmbot.Registry, _}, state), do: {:noreply, state}
|
||||
defp push_bot_state(chan, bot, %Farmbot.BotStateNG{} = bot_state) do
|
||||
json =
|
||||
bot_state
|
||||
|> Farmbot.BotStateNG.view()
|
||||
|> Farmbot.JSON.encode!()
|
||||
|
||||
defp push_bot_state(chan, bot, state) do
|
||||
json = Farmbot.JSON.encode!(state)
|
||||
:ok = AMQP.Basic.publish(chan, @exchange, "bot.#{bot}.status", json)
|
||||
state
|
||||
AMQP.Basic.publish(chan, @exchange, "bot.#{bot}.status", json)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,9 +2,11 @@ defmodule Farmbot.AMQP.LogTransport do
|
|||
use GenServer
|
||||
use AMQP
|
||||
require Farmbot.Logger
|
||||
require Logger
|
||||
import Farmbot.Config, only: [update_config_value: 4]
|
||||
|
||||
@exchange "amq.topic"
|
||||
@checkup_ms 100
|
||||
|
||||
defstruct [:conn, :chan, :bot, :state_cache]
|
||||
alias __MODULE__, as: State
|
||||
|
@ -16,16 +18,11 @@ defmodule Farmbot.AMQP.LogTransport do
|
|||
|
||||
def init([conn, jwt]) do
|
||||
Process.flag(:sensitive, true)
|
||||
Farmbot.Registry.subscribe()
|
||||
initial_bot_state = Farmbot.BotState.subscribe()
|
||||
{:ok, chan} = AMQP.Channel.open(conn)
|
||||
:ok = Basic.qos(chan, global: true)
|
||||
state = struct(State, conn: conn, chan: chan, bot: jwt.bot)
|
||||
|
||||
for l <- Farmbot.Logger.handle_all_logs() do
|
||||
do_handle_log(l, state)
|
||||
end
|
||||
|
||||
{:ok, state}
|
||||
state = struct(State, conn: conn, chan: chan, bot: jwt.bot, state_cache: initial_bot_state)
|
||||
{:ok, state, 0}
|
||||
end
|
||||
|
||||
def terminate(reason, state) do
|
||||
|
@ -41,20 +38,30 @@ defmodule Farmbot.AMQP.LogTransport do
|
|||
if state.chan, do: AMQP.Channel.close(state.chan)
|
||||
end
|
||||
|
||||
def handle_info({Farmbot.Registry, {Farmbot.Logger, {:log_ready, id}}}, state) do
|
||||
if log = Farmbot.Logger.handle_log(id) do
|
||||
do_handle_log(log, state)
|
||||
def handle_info({Farmbot.BotState, change}, state) do
|
||||
new_state_cache = Ecto.Changeset.apply_changes(change)
|
||||
{:noreply, %{state | state_cache: new_state_cache}, @checkup_ms}
|
||||
end
|
||||
|
||||
def handle_info(:timeout, state) do
|
||||
{:noreply, state, {:continue, Farmbot.Logger.handle_all_logs()}}
|
||||
end
|
||||
|
||||
def handle_continue([log | rest], state) do
|
||||
case do_handle_log(log, state) do
|
||||
:ok ->
|
||||
{:noreply, state, {:continue, rest}}
|
||||
|
||||
error ->
|
||||
Logger.error("Logger amqp client failed to upload log: #{inspect(error)}")
|
||||
# Reschedule log to be uploaded again
|
||||
Farmbot.Logger.insert_log!(log)
|
||||
{:noreply, state, @checkup_ms}
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info({Farmbot.Registry, {Farmbot.BotState, bot_state}}, state) do
|
||||
{:noreply, %{state | state_cache: bot_state}}
|
||||
end
|
||||
|
||||
def handle_info({Farmbot.Registry, _}, state) do
|
||||
{:noreply, state}
|
||||
def handle_continue([], state) do
|
||||
{:noreply, state, @checkup_ms}
|
||||
end
|
||||
|
||||
defp do_handle_log(log, state) do
|
||||
|
@ -71,13 +78,15 @@ defmodule Farmbot.AMQP.LogTransport do
|
|||
major_version: log.version.major,
|
||||
minor_version: log.version.minor,
|
||||
patch_version: log.version.patch,
|
||||
# QUESTION(Connor) - Why does this need `.to_unix()`?
|
||||
# ANSWER(Connor) - because the FE needed it.
|
||||
created_at: DateTime.from_naive!(log.inserted_at, "Etc/UTC") |> DateTime.to_unix(),
|
||||
channels: log.meta[:channels] || [],
|
||||
message: log.message
|
||||
}
|
||||
|
||||
log = add_position_to_log(log_without_pos, location_data)
|
||||
push_bot_log(state.chan, state.bot, log)
|
||||
json_log = add_position_to_log(log_without_pos, location_data)
|
||||
push_bot_log(state.chan, state.bot, json_log)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -86,7 +95,7 @@ defmodule Farmbot.AMQP.LogTransport do
|
|||
:ok = AMQP.Basic.publish(chan, @exchange, "bot.#{bot}.logs", json)
|
||||
end
|
||||
|
||||
defp add_position_to_log(%{} = log, %{position: %{} = pos}) do
|
||||
Map.merge(log, pos)
|
||||
defp add_position_to_log(%{} = log, %{position: %{x: x, y: y, z: z}}) do
|
||||
Map.merge(log, %{x: x, y: y, z: z})
|
||||
end
|
||||
end
|
||||
|
|
|
@ -60,13 +60,20 @@ defmodule Farmbot.API do
|
|||
content_length = :filelib.file_size(image_filename)
|
||||
{:ok, pid} = Agent.start_link(fn -> 0 end)
|
||||
|
||||
prog = %Percent{
|
||||
status: :working,
|
||||
percent: 0,
|
||||
time: DateTime.utc_now(),
|
||||
type: :image
|
||||
}
|
||||
|
||||
stream =
|
||||
image_filename
|
||||
|> File.stream!([], @file_chunk)
|
||||
|> Stream.each(fn chunk ->
|
||||
Agent.update(pid, fn sent ->
|
||||
size = sent + byte_size(chunk)
|
||||
prog = put_progress(size, content_length)
|
||||
prog = put_progress(prog, size, content_length)
|
||||
BotState.set_job_progress(image_filename, prog)
|
||||
size
|
||||
end)
|
||||
|
@ -95,17 +102,17 @@ defmodule Farmbot.API do
|
|||
client <- API.client(),
|
||||
body <- %{attachment_url: attachment_url, meta: meta},
|
||||
{:ok, %{status: s}} = r when s > 199 and s < 300 <- API.post(client, "/api/images", body) do
|
||||
Farmbot.BotState.set_job_progress(image_filename, %Percent{percent: 100, status: :complete})
|
||||
Farmbot.BotState.set_job_progress(image_filename, %{prog | status: :complete, percent: 100})
|
||||
r
|
||||
else
|
||||
er ->
|
||||
Farmbot.Logger.error(1, "Failed to upload image")
|
||||
Farmbot.BotState.set_job_progress(image_filename, %Percent{percent: -1, status: :error})
|
||||
Farmbot.BotState.set_job_progress(image_filename, %{prog | percent: -1, status: :error})
|
||||
er
|
||||
end
|
||||
end
|
||||
|
||||
def put_progress(size, max) do
|
||||
def put_progress(prog, size, max) do
|
||||
fraction = size / max
|
||||
completed = trunc(fraction * @progress_steps)
|
||||
percent = trunc(fraction * 100)
|
||||
|
@ -121,8 +128,9 @@ defmodule Farmbot.API do
|
|||
status = if percent == 100, do: :complete, else: :working
|
||||
|
||||
%Percent{
|
||||
status: status,
|
||||
percent: percent
|
||||
prog
|
||||
| status: status,
|
||||
percent: percent
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
# DELETEME
|
||||
require Protocol
|
||||
# Bot State
|
||||
Protocol.derive(Jason.Encoder, Farmbot.BotState)
|
||||
Protocol.derive(Jason.Encoder, Farmbot.BotState.Configuration)
|
||||
Protocol.derive(Jason.Encoder, Farmbot.BotState.InformationalSettings)
|
||||
Protocol.derive(Jason.Encoder, Farmbot.BotState.LocationData)
|
||||
Protocol.derive(Jason.Encoder, Farmbot.BotState.McuParams)
|
||||
Protocol.derive(Jason.Encoder, Farmbot.BotState.Pin)
|
||||
Protocol.derive(Jason.Encoder, Farmbot.BotState.JobProgress.Bytes)
|
||||
Protocol.derive(Jason.Encoder, Farmbot.BotState.JobProgress.Percent)
|
||||
# Protocol.derive(Jason.Encoder, Farmbot.BotState)
|
||||
# Protocol.derive(Jason.Encoder, Farmbot.BotState.Configuration)
|
||||
# Protocol.derive(Jason.Encoder, Farmbot.BotState.InformationalSettings)
|
||||
# Protocol.derive(Jason.Encoder, Farmbot.BotState.LocationData)
|
||||
# Protocol.derive(Jason.Encoder, Farmbot.BotState.McuParams)
|
||||
# Protocol.derive(Jason.Encoder, Farmbot.BotState.Pin)
|
||||
# Protocol.derive(Jason.Encoder, Farmbot.BotState.JobProgress.Bytes)
|
||||
# Protocol.derive(Jason.Encoder, Farmbot.BotState.JobProgress.Percent)
|
||||
|
||||
Protocol.derive(Jason.Encoder, Farmbot.JWT)
|
||||
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
defmodule FarmbotExtTest do
|
||||
use ExUnit.Case
|
||||
doctest FarmbotExt
|
||||
|
||||
test "greets the world" do
|
||||
assert FarmbotExt.hello() == :world
|
||||
end
|
||||
end
|
|
@ -0,0 +1,4 @@
|
|||
# Used by "mix format"
|
||||
[
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||
]
|
|
@ -0,0 +1,24 @@
|
|||
# The directory Mix will write compiled artifacts to.
|
||||
/_build/
|
||||
|
||||
# If you run "mix test --cover", coverage assets end up here.
|
||||
/cover/
|
||||
|
||||
# The directory Mix downloads your dependencies sources to.
|
||||
/deps/
|
||||
|
||||
# Where 3rd-party dependencies like ExDoc output generated docs.
|
||||
/doc/
|
||||
|
||||
# Ignore .fetch files in case you like to edit your project deps locally.
|
||||
/.fetch
|
||||
|
||||
# If the VM crashes, it generates a dump, let's ignore it too.
|
||||
erl_crash.dump
|
||||
|
||||
# Also ignore archive artifacts (built via "mix archive.build").
|
||||
*.ez
|
||||
|
||||
# Ignore package tarball (built via "mix hex.build").
|
||||
farmbot_firmware-*.tar
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# FarmbotFirmware
|
||||
|
||||
**TODO: Add description**
|
||||
|
||||
## Installation
|
||||
|
||||
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
|
||||
by adding `farmbot_firmware` to your list of dependencies in `mix.exs`:
|
||||
|
||||
```elixir
|
||||
def deps do
|
||||
[
|
||||
{:farmbot_firmware, "~> 0.1.0"}
|
||||
]
|
||||
end
|
||||
```
|
||||
|
||||
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
|
||||
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
|
||||
be found at [https://hexdocs.pm/farmbot_firmware](https://hexdocs.pm/farmbot_firmware).
|
||||
|
|
@ -0,0 +1 @@
|
|||
use Mix.Config
|
|
@ -0,0 +1,479 @@
|
|||
defmodule Farmbot.Firmware do
|
||||
@moduledoc """
|
||||
Firmware wrapper for interacting with Farmbot-Arduino-Firmware.
|
||||
This GenServer is expected to be a pretty simple state machine
|
||||
with no side effects to anything in the rest of the Farmbot application.
|
||||
Side effects should be implemented using a callback/pubsub system. This
|
||||
allows for indpendent testing.
|
||||
|
||||
Functionality that is needed to boot the firmware:
|
||||
* paramaters - Keyword list of {param_atom, float}
|
||||
|
||||
Side affects that should be handled
|
||||
* position reports
|
||||
* end stop reports
|
||||
* calibration reports
|
||||
* busy reports
|
||||
|
||||
# State machine
|
||||
The firmware starts in a `:boot` state. It then loads all paramaters
|
||||
writes all paramaters, and goes to idle if all params were loaded successfully.
|
||||
|
||||
State machine flows go as follows:
|
||||
## Boot
|
||||
:boot
|
||||
|> :no_config
|
||||
|> :configuration
|
||||
|> :idle
|
||||
|
||||
## Idle
|
||||
:idle
|
||||
|> :begin
|
||||
|> :busy
|
||||
|> :error | :invalid | :success
|
||||
|
||||
# Constraints and Exceptions
|
||||
Commands will be queued as they received with some exceptions:
|
||||
* if a command is currently executing (state is not `:idle`),
|
||||
proceding commands will be queued in the order they are received.
|
||||
* the `:emergency_lock` and `:emergency_unlock` commands go to the front
|
||||
of the command queue and are started immediately.
|
||||
* if a `report_emergency_lock` message is received at any point during a
|
||||
commands execution, that command is considered an error.
|
||||
(this does not apply to `:boot` state, since `:paramater_write`
|
||||
is accepted while the firmware is locked.)
|
||||
* all reports outside of control flow reports (:begin, :error, :invalid,
|
||||
:success) will be discarded while in `:boot` state. This means while
|
||||
boot, position updates, end stop updates etc are ignored.
|
||||
|
||||
# Transports
|
||||
GCODES should be exchanged in the following format:
|
||||
{tag, {command, args}}
|
||||
* `tag` - binary integer. This is translated to the `Q` paramater.
|
||||
* `command` - either a `RXX`, `FXX`, or `GXX` code.
|
||||
* `args` - a list of arguments to be processed.
|
||||
|
||||
For example a report might look like:
|
||||
{"123", {:report_some_information, [h: 10.00, u: 90.10]}}
|
||||
and a command might look like:
|
||||
{"555", {:fire_laser, [w: 100.00]}}
|
||||
Numbers should be floats when possible. An Exeption to this is `:report_end_stops`
|
||||
where there is only two values: `1` or `0`.
|
||||
|
||||
See the `GCODE` module for more information on available implemented GCODES.
|
||||
a `Transport` should be a process that implements standard `GenServer`
|
||||
behaviour.
|
||||
|
||||
Upon `init/1` the args passed in should be a Keyword list required to configure
|
||||
the transport such as a serial device, etc. `args` will also contain a
|
||||
`:handle_gcode` function that should be called everytime a GCODE is received.
|
||||
|
||||
Keyword.fetch!(args, :handle_gcode).({"999", {:report_software_version, ["Just a test!"]}})
|
||||
|
||||
a transport should also implement a `handle_call` clause like:
|
||||
|
||||
def handle_call({"166", {:paramater_write, [some_param: 100.00]}}, _from, state)
|
||||
|
||||
and reply with `:ok | {:error, term()}`
|
||||
"""
|
||||
use GenServer
|
||||
require Logger
|
||||
|
||||
alias Farmbot.Firmware, as: State
|
||||
alias Farmbot.{Firmware.GCODE, Firmware.Command, Firmware.Request}
|
||||
@error_timeout_ms 2_000
|
||||
|
||||
@type status :: :boot | :no_config | :configuration | :idle | :emergency_lock
|
||||
|
||||
defstruct [
|
||||
:transport,
|
||||
:transport_pid,
|
||||
:side_effects,
|
||||
:status,
|
||||
:tag,
|
||||
:configuration_queue,
|
||||
:command_queue,
|
||||
:caller_pid,
|
||||
:current
|
||||
]
|
||||
|
||||
@type state :: %State{
|
||||
transport: module(),
|
||||
transport_pid: pid(),
|
||||
side_effects: nil | module(),
|
||||
status: status(),
|
||||
tag: GCODE.tag(),
|
||||
configuration_queue: [{GCODE.kind(), GCODE.args()}],
|
||||
command_queue: [{pid(), GCODE.t()}],
|
||||
caller_pid: nil | pid,
|
||||
current: nil | GCODE.t()
|
||||
}
|
||||
|
||||
@doc """
|
||||
Command the firmware to do something. Takes a `{tag, {command, args}}`
|
||||
GCODE. This command will be queued if there is already a command
|
||||
executing. (this does not apply to `:emergency_lock` and `:emergency_unlock`)
|
||||
|
||||
## Response/Control Flow
|
||||
When executed, `command` will block until one of the following respones
|
||||
are received:
|
||||
* `{:report_success, []}` -> `:ok`
|
||||
* `{:report_invalid, []}` -> `{:error, :invalid_command}`
|
||||
* `{:report_error, []}` -> `{:error, :firmware_error}`
|
||||
* `{:report_emergency_lock, []}` -> {:error, :emergency_lock}`
|
||||
|
||||
If the firmware is in any of the following states:
|
||||
* `:boot`
|
||||
* `:no_config`
|
||||
* `:configuration`
|
||||
`command` will fail with `{:error, state}`
|
||||
"""
|
||||
defdelegate command(server \\ __MODULE__, code), to: Command
|
||||
|
||||
@doc """
|
||||
Request data from the firmware.
|
||||
Valid requests are of kind:
|
||||
|
||||
:paramater_read
|
||||
:status_read
|
||||
:pin_read
|
||||
:end_stops_read
|
||||
:position_read
|
||||
:software_version_read
|
||||
|
||||
Will return `{:ok, {tag, {:report_*, args}}}` on success
|
||||
or `{:error, term()}` on error.
|
||||
"""
|
||||
defdelegate request(server \\ __MODULE__, code), to: Request
|
||||
|
||||
@doc """
|
||||
Starting the Firmware server requires at least:
|
||||
* `:transport` - a module implementing the Transport GenServer behaviour.
|
||||
See the `Transports` section of moduledoc.
|
||||
|
||||
Every other arg passed in will be passed directly to the `:transport` module's
|
||||
`init/1` function.
|
||||
"""
|
||||
def start_link(args, opts \\ [name: __MODULE__]) do
|
||||
GenServer.start_link(__MODULE__, args, opts)
|
||||
end
|
||||
|
||||
def init(args) do
|
||||
transport = Keyword.fetch!(args, :transport)
|
||||
side_effects = Keyword.get(args, :side_effects)
|
||||
fw = self()
|
||||
fun = fn {_, _} = code -> GenServer.cast(fw, code) end
|
||||
args = Keyword.put(args, :handle_gcode, fun)
|
||||
|
||||
with {:ok, pid} <- GenServer.start_link(transport, args) do
|
||||
Process.link(pid)
|
||||
Logger.debug("Starting Firmware: #{inspect(args)}")
|
||||
|
||||
state = %State{
|
||||
transport_pid: pid,
|
||||
transport: transport,
|
||||
side_effects: side_effects,
|
||||
status: :boot,
|
||||
command_queue: [],
|
||||
configuration_queue: []
|
||||
}
|
||||
|
||||
{:ok, state}
|
||||
end
|
||||
end
|
||||
|
||||
# @spec handle_info(:timeout, state) :: {:noreply, state}
|
||||
def handle_info(:timeout, %{configuration_queue: [code | rest]} = state) do
|
||||
Logger.debug("Starting next configuration code: #{inspect(code)}")
|
||||
|
||||
case GenServer.call(state.transport_pid, {state.tag, code}) do
|
||||
:ok ->
|
||||
new_state = %{state | current: code, configuration_queue: rest}
|
||||
side_effects(new_state, :handle_output_gcode, [{state.tag, code}])
|
||||
{:noreply, new_state}
|
||||
|
||||
{:error, _} ->
|
||||
{:noreply, state, @error_timeout_ms}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(:timeout, %{command_queue: [{pid, {tag, code}} | rest]} = state) do
|
||||
case GenServer.call(state.transport_pid, {tag, code}) do
|
||||
:ok ->
|
||||
new_state = %{state | tag: tag, current: code, command_queue: rest, caller_pid: pid}
|
||||
side_effects(new_state, :handle_output_gcode, [{state.tag, code}])
|
||||
{:noreply, new_state}
|
||||
|
||||
{:error, _} ->
|
||||
{:noreply, state, @error_timeout_ms}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(:timeout, %{configuration_queue: []} = state) do
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_call({_tag, _code} = gcode, from, state) do
|
||||
handle_command(gcode, from, state)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec handle_command(GCODE.t(), GenServer.from(), state()) :: {:reply, term(), state()}
|
||||
def handle_command(_, _, %{status: s} = state) when s in [:boot, :no_config, :configuration] do
|
||||
{:reply, {:error, s}, state}
|
||||
end
|
||||
|
||||
def handle_command({tag, {:command_emergency_lock, []}} = code, {pid, _ref}, state) do
|
||||
{:reply, {:ok, tag}, %{state | command_queue: [{pid, code} | state.command_queue]}, 0}
|
||||
end
|
||||
|
||||
def handle_command({tag, {:command_emergency_unlock, []}} = code, {pid, _ref}, state) do
|
||||
{:reply, {:ok, tag}, %{state | command_queue: [{pid, code} | state.command_queue]}, 0}
|
||||
end
|
||||
|
||||
def handle_command({tag, {_, _}} = code, {pid, _ref}, state) do
|
||||
new_state = %{state | command_queue: state.command_queue ++ [{pid, code}]}
|
||||
|
||||
case new_state.status do
|
||||
:idle ->
|
||||
{:reply, {:ok, tag}, new_state, 0}
|
||||
|
||||
# Don't do any flow control if state is emergency_lock.
|
||||
# This allows a transport to decide
|
||||
# if a command should be blocked or not.
|
||||
:emergency_lock ->
|
||||
{:reply, {:ok, tag}, new_state, 0}
|
||||
|
||||
_ ->
|
||||
{:reply, {:ok, tag}, new_state}
|
||||
end
|
||||
end
|
||||
|
||||
# Extracts tag
|
||||
def handle_cast({tag, {_, _} = code}, state) do
|
||||
side_effects(state, :handle_input_gcode, [{tag, code}])
|
||||
handle_report(code, %{state | tag: tag})
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec handle_report({GCODE.report_kind(), GCODE.args()}, state) ::
|
||||
{:noreply, state(), 0} | {:noreply, state()}
|
||||
def handle_report({:report_emergency_lock, []} = code, state) do
|
||||
Logger.info("Emergency lock")
|
||||
if state.caller_pid, do: send(state.caller_pid, {state.tag, code})
|
||||
{:noreply, goto(%{state | current: nil, caller_pid: nil}, :emergency_lock), 0}
|
||||
end
|
||||
|
||||
# "ARDUINO STARTUP COMPLETE" => goto(:boot, :no_config)
|
||||
def handle_report({:report_debug_message, ["ARDUINO STARTUP COMPLETE"]}, state) do
|
||||
Logger.info("ARDUINO STARTUP COMPLETE")
|
||||
{:noreply, goto(state, :no_config)}
|
||||
end
|
||||
|
||||
def handle_report({:report_debug_message, msg}, state) do
|
||||
side_effects(state, :handle_debug_message, [msg])
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_report(report, %{status: :boot} = state) do
|
||||
Logger.debug(["still in state: :boot ", inspect(report)])
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
# report_idle => goto(_, :idle)
|
||||
def handle_report({:report_idle, []}, %{status: _} = state) do
|
||||
{:noreply, goto(%{state | caller_pid: nil, current: nil}, :idle), 0}
|
||||
end
|
||||
|
||||
def handle_report({:report_begin, []} = code, state) do
|
||||
if state.caller_pid, do: send(state.caller_pid, {state.tag, code})
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_report({:report_success, []} = code, state) do
|
||||
if state.caller_pid, do: send(state.caller_pid, {state.tag, code})
|
||||
new_state = %{state | current: nil, caller_pid: nil}
|
||||
|
||||
if new_state.status == :emergency_lock do
|
||||
{:noreply, goto(new_state, :idle), 0}
|
||||
else
|
||||
{:noreply, new_state, 0}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_report({:report_busy, []} = code, state) do
|
||||
if state.caller_pid, do: send(state.caller_pid, {state.tag, code})
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_report({:report_error, []} = code, %{status: :configuration} = state) do
|
||||
if state.caller_pid, do: send(state.caller_pid, {state.tag, code})
|
||||
{:stop, {:error, state.current}, state}
|
||||
end
|
||||
|
||||
def handle_report({:report_error, []} = code, state) do
|
||||
if state.caller_pid, do: send(state.caller_pid, {state.tag, code})
|
||||
{:noreply, %{state | caller_pid: nil, current: nil}, 0}
|
||||
end
|
||||
|
||||
def handle_report({:report_invalid, []} = code, %{status: :configuration} = state) do
|
||||
if state.caller_pid, do: send(state.caller_pid, {state.tag, code})
|
||||
{:stop, {:error, state.current}, state}
|
||||
end
|
||||
|
||||
def handle_report({:report_invalid, []} = code, state) do
|
||||
if state.caller_pid, do: send(state.caller_pid, {state.tag, code})
|
||||
{:noreply, %{state | caller_pid: nil, current: nil}, 0}
|
||||
end
|
||||
|
||||
def handle_report({:report_retry, []} = code, %{status: :configuration} = state) do
|
||||
Logger.warn("Retrying configuration command: #{inspect(code)}")
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_report({:report_retry, []} = code, state) do
|
||||
if state.caller_pid, do: send(state.caller_pid, {state.tag, code})
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_report({:report_paramater_value, param} = code, state) do
|
||||
if state.caller_pid, do: send(state.caller_pid, {state.tag, code})
|
||||
side_effects(state, :handle_paramater_value, [param])
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_report({:report_calibration_paramater_value, args} = _code, state) do
|
||||
to_process = [{:paramater_write, args}]
|
||||
|
||||
{:noreply, goto(%{state | tag: state.tag, configuration_queue: to_process}, :configuration),
|
||||
0}
|
||||
end
|
||||
|
||||
# report_no_config => goto(_, :no_config)
|
||||
def handle_report({:report_no_config, []}, %{status: _} = state) do
|
||||
tag = state.tag || "0"
|
||||
loaded_params = side_effects(state, :load_params, []) || []
|
||||
|
||||
param_commands =
|
||||
Enum.reduce(loaded_params, [], fn {param, val}, acc ->
|
||||
if val, do: acc ++ [{:paramater_write, [{param, val}]}], else: acc
|
||||
end)
|
||||
|
||||
to_process =
|
||||
param_commands ++
|
||||
[
|
||||
{:paramater_write, [{:param_config_ok, 1.0}]},
|
||||
{:paramater_read_all, []}
|
||||
]
|
||||
|
||||
to_process =
|
||||
if loaded_params[:movement_home_at_boot_x] == 1,
|
||||
do: to_process ++ [{:command_movement_find_home, [:x]}]
|
||||
|
||||
to_process =
|
||||
if loaded_params[:movement_home_at_boot_y] == 1,
|
||||
do: to_process ++ [{:command_movement_find_home, [:y]}]
|
||||
|
||||
to_process =
|
||||
if loaded_params[:movement_home_at_boot_z] == 1,
|
||||
do: to_process ++ [{:command_movement_find_home, [:z]}]
|
||||
|
||||
{:noreply, goto(%{state | tag: tag, configuration_queue: to_process}, :configuration), 0}
|
||||
end
|
||||
|
||||
# report_paramaters_complete => goto(:configuration, :idle)
|
||||
def handle_report({:report_paramaters_complete, []}, %{status: :configuration} = state) do
|
||||
{:noreply, goto(state, :idle)}
|
||||
end
|
||||
|
||||
def handle_report(_, %{status: :no_config} = state) do
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_report({:report_position, position} = code, state) do
|
||||
if state.caller_pid, do: send(state.caller_pid, {state.tag, code})
|
||||
side_effects(state, :handle_position, [position])
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_report({:report_axis_state, axis_state} = code, state) do
|
||||
if state.caller_pid, do: send(state.caller_pid, {state.tag, code})
|
||||
side_effects(state, :handle_axis_state, [axis_state])
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_report({:report_calibration_state, calibration_state} = code, state) do
|
||||
if state.caller_pid, do: send(state.caller_pid, {state.tag, code})
|
||||
side_effects(state, :handle_calibration_state, [calibration_state])
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_report({:report_position_change, position} = code, state) do
|
||||
if state.caller_pid, do: send(state.caller_pid, {state.tag, code})
|
||||
side_effects(state, :handle_position_change, [position])
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_report({:report_encoders_scaled, encoders} = code, state) do
|
||||
if state.caller_pid, do: send(state.caller_pid, {state.tag, code})
|
||||
side_effects(state, :handle_encoders_scaled, [encoders])
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_report({:report_encoders_raw, encoders} = code, state) do
|
||||
if state.caller_pid, do: send(state.caller_pid, {state.tag, code})
|
||||
side_effects(state, :handle_encoders_raw, [encoders])
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_report({:report_end_stops, end_stops} = code, state) do
|
||||
if state.caller_pid, do: send(state.caller_pid, {state.tag, code})
|
||||
side_effects(state, :handle_end_stops, [end_stops])
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_report({:report_pin_value, value} = code, state) do
|
||||
if state.caller_pid, do: send(state.caller_pid, {state.tag, code})
|
||||
side_effects(state, :handle_pin_value, [value])
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_report({:report_software_version, version} = code, state) do
|
||||
if state.caller_pid, do: send(state.caller_pid, {state.tag, code})
|
||||
side_effects(state, :handle_software_version, [version])
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
# NOOP
|
||||
def handle_report({:report_echo, _}, state), do: {:noreply, state}
|
||||
|
||||
def handle_report({_kind, _args} = code, state) do
|
||||
IO.inspect(code, label: "unknown code for #{state.status}")
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
@spec goto(state(), status()) :: state()
|
||||
defp goto(%{status: old} = state, new) do
|
||||
new_state = %{state | status: new}
|
||||
|
||||
cond do
|
||||
old != new && new == :emergency_lock ->
|
||||
side_effects(new_state, :handle_emergency_lock, [])
|
||||
|
||||
old != new && old == :emergency_lock ->
|
||||
side_effects(new_state, :handle_emergency_unlock, [])
|
||||
|
||||
old == new ->
|
||||
:ok
|
||||
|
||||
true ->
|
||||
Logger.debug("unhandled state change: #{old} => #{new}")
|
||||
end
|
||||
|
||||
new_state
|
||||
end
|
||||
|
||||
@spec side_effects(state, atom, GCODE.args()) :: any()
|
||||
defp side_effects(%{side_effects: nil}, _function, _args), do: nil
|
||||
defp side_effects(%{side_effects: m}, function, args), do: apply(m, function, args)
|
||||
end
|
|
@ -0,0 +1,50 @@
|
|||
defmodule Farmbot.Firmware.Command do
|
||||
@moduledoc false
|
||||
alias Farmbot.{Firmware, Firmware.GCODE}
|
||||
|
||||
@spec command(GenServer.server(), GCODE.t() | {GCODE.kind(), GCODE.args()}) ::
|
||||
:ok | {:error, :invalid_command | :firmware_error | :emergency_lock | Firmware.status()}
|
||||
def command(firmware_server \\ Firmware, code)
|
||||
|
||||
def command(firmware_server, {_tag, {_, _}} = code) do
|
||||
case GenServer.call(firmware_server, code, :infinity) do
|
||||
{:ok, tag} -> wait_for_command_result(tag, code)
|
||||
{:error, status} -> {:error, status}
|
||||
end
|
||||
end
|
||||
|
||||
def command(firmware_server, {_, _} = code) do
|
||||
command(firmware_server, {to_string(:rand.uniform(100)), code})
|
||||
end
|
||||
|
||||
defp wait_for_command_result(tag, code, retries \\ 0, err \\ nil) do
|
||||
receive do
|
||||
{^tag, {:report_begin, []}} ->
|
||||
wait_for_command_result(tag, code, retries, err)
|
||||
|
||||
{^tag, {:report_busy, []}} ->
|
||||
wait_for_command_result(tag, code, retries, err)
|
||||
|
||||
{^tag, {:report_success, []}} ->
|
||||
:ok
|
||||
|
||||
{^tag, {:report_retry, []}} ->
|
||||
wait_for_command_result(tag, code, retries + 1, err)
|
||||
|
||||
{^tag, {:report_position_change, _} = error} ->
|
||||
wait_for_command_result(tag, code, retries, error)
|
||||
|
||||
{^tag, {:report_error, []}} ->
|
||||
if err, do: {:error, err}, else: {:error, :firmware_error}
|
||||
|
||||
{^tag, {:report_invalid, []}} ->
|
||||
{:error, :invalid_command}
|
||||
|
||||
{_, {:report_emergency_lock, []}} ->
|
||||
{:error, :emergency_lock}
|
||||
|
||||
{_tag, _report} ->
|
||||
wait_for_command_result(tag, code, retries, err)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,142 @@
|
|||
defmodule Farmbot.Firmware.GCODE do
|
||||
@moduledoc """
|
||||
Handles encoding and decoding of GCODEs.
|
||||
"""
|
||||
|
||||
alias Farmbot.Firmware.GCODE.{Decoder, Encoder}
|
||||
import Decoder, only: [do_decode: 2]
|
||||
import Encoder, only: [do_encode: 2]
|
||||
|
||||
@typedoc "Tag is a binary integer. example: `\"123\"`"
|
||||
@type tag() :: nil | binary()
|
||||
|
||||
@typedoc "RXX codes. Reports information."
|
||||
@type report_kind ::
|
||||
:report_idle
|
||||
| :report_begin
|
||||
| :report_success
|
||||
| :report_error
|
||||
| :report_busy
|
||||
| :report_axis_state
|
||||
| :report_retry
|
||||
| :report_echo
|
||||
| :report_invalid
|
||||
| :report_home_complete
|
||||
| :report_position
|
||||
| :report_paramaters_complete
|
||||
| :report_paramater_value
|
||||
| :report_calibration_paramater_value
|
||||
| :report_status_value
|
||||
| :report_pin_value
|
||||
| :report_axis_timeout
|
||||
| :report_end_stops
|
||||
| :report_software_version
|
||||
| :report_encoders_scaled
|
||||
| :report_encoders_raw
|
||||
| :report_emergency_lock
|
||||
| :report_no_config
|
||||
| :report_debug_message
|
||||
|
||||
@typedoc "Movement commands"
|
||||
@type command_kind ::
|
||||
:command_movement
|
||||
| :command_movement_home
|
||||
| :command_movement_find_home
|
||||
| :command_movement_calibrate
|
||||
|
||||
@typedoc "Read/Write commands."
|
||||
@type read_write_kind ::
|
||||
:paramater_read_all
|
||||
| :paramater_read
|
||||
| :paramater_write
|
||||
| :status_read
|
||||
| :status_write
|
||||
| :pin_read
|
||||
| :pin_write
|
||||
| :pin_mode_write
|
||||
| :servo_write
|
||||
| :end_stops_read
|
||||
| :position_read
|
||||
| :software_version_read
|
||||
| :position_write_zero
|
||||
|
||||
@type emergency_commands :: :command_emergency_lock | :command_emergency_unlock
|
||||
|
||||
@typedoc "Kind is an atom of the \"name\" of a command. Example: `:write_paramater`"
|
||||
@type kind() :: report_kind | command_kind | read_write_kind | :unknown
|
||||
|
||||
@typedoc "Args is a list of args to a `kind`. example: `[x: 100.00]`"
|
||||
@type args() :: [arg]
|
||||
|
||||
@typedoc "Example: `{:x, 100.00}` or `1` or `\"hello world\"`"
|
||||
@type arg() :: any()
|
||||
|
||||
@typedoc "Constructed GCODE."
|
||||
@type t :: {tag(), {kind(), args}}
|
||||
|
||||
@doc """
|
||||
Shortcut for constructing a new GCODE
|
||||
## Examples
|
||||
iex(1)> Farmbot.Firmware.GCODE.new(:report_idle, [], "100")
|
||||
{"100", {:report_idle, []}}
|
||||
iex(2)> Farmbot.Firmware.GCODE.new(:report_idle, [])
|
||||
{nil, {:report_idle, []}}
|
||||
"""
|
||||
@spec new(kind(), args(), tag()) :: t()
|
||||
def new(kind, args, tag \\ nil) do
|
||||
{tag, {kind, args}}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Takes a string representation of a GCODE, and returns a tuple representation of:
|
||||
`{tag, {kind, args}}`
|
||||
|
||||
## Examples
|
||||
iex(1)> Farmbot.Firmware.GCODE.decode("R00 Q100")
|
||||
{"100", {:report_idle, []}}
|
||||
iex(2)> Farmbot.Firmware.GCODE.decode("R00")
|
||||
{nil, {:report_idle, []}}
|
||||
"""
|
||||
@spec decode(binary()) :: t()
|
||||
def decode(binary_with_q) when is_binary(binary_with_q) do
|
||||
code = String.split(binary_with_q, " ")
|
||||
|
||||
case extract_tag(code) do
|
||||
{tag, [kind | args]} ->
|
||||
{tag, do_decode(kind, args)}
|
||||
|
||||
{tag, []} ->
|
||||
{tag, {:unknown, []}}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Takes a tuple representation of a GCODE and returns a string.
|
||||
|
||||
## Examples
|
||||
iex(1)> Farmbot.Firmware.GCODE.encode({"444", {:report_idle, []}})
|
||||
"R00 Q444"
|
||||
iex(2)> Farmbot.Firmware.GCODE.encode({nil, {:report_idle, []}})
|
||||
"R00"
|
||||
"""
|
||||
@spec encode(t()) :: binary()
|
||||
def encode({nil, {kind, args}}) do
|
||||
do_encode(kind, args)
|
||||
end
|
||||
|
||||
def encode({tag, {kind, args}}) do
|
||||
str = do_encode(kind, args)
|
||||
str <> " Q" <> tag
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec extract_tag([binary()]) :: {tag(), [binary()]}
|
||||
def extract_tag(list) when is_list(list) do
|
||||
with {"Q" <> bin_tag, list} when is_list(list) <- List.pop_at(list, -1) do
|
||||
{bin_tag, list}
|
||||
else
|
||||
# if there was no Q code provided
|
||||
{_, data} when is_list(data) -> {nil, list}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,185 @@
|
|||
defmodule Farmbot.Firmware.GCODE.Decoder do
|
||||
@moduledoc false
|
||||
|
||||
alias Farmbot.Firmware.{GCODE, Param}
|
||||
|
||||
@doc false
|
||||
@spec do_decode(binary(), [binary()]) :: {GCODE.kind(), GCODE.args()}
|
||||
def do_decode("R00", []), do: {:report_idle, []}
|
||||
def do_decode("R01", []), do: {:report_begin, []}
|
||||
def do_decode("R02", []), do: {:report_success, []}
|
||||
def do_decode("R03", []), do: {:report_error, []}
|
||||
def do_decode("R04", []), do: {:report_busy, []}
|
||||
|
||||
def do_decode("R05", xyz), do: {:report_axis_state, decode_axis_state(xyz)}
|
||||
def do_decode("R06", xyz), do: {:report_calibration_state, decode_calibration_state(xyz)}
|
||||
|
||||
def do_decode("R07", []), do: {:report_retry, []}
|
||||
def do_decode("R08", args), do: {:report_echo, decode_echo(Enum.join(args, " "))}
|
||||
def do_decode("R09", []), do: {:report_invalid, []}
|
||||
|
||||
def do_decode("R11", []), do: {:report_home_complete, [:x]}
|
||||
def do_decode("R12", []), do: {:report_home_complete, [:y]}
|
||||
def do_decode("R13", []), do: {:report_home_complete, [:z]}
|
||||
|
||||
def do_decode("R15", x), do: {:report_position_change, decode_floats(x)}
|
||||
def do_decode("R16", y), do: {:report_position_change, decode_floats(y)}
|
||||
def do_decode("R17", z), do: {:report_position_change, decode_floats(z)}
|
||||
|
||||
def do_decode("R20", []), do: {:report_paramaters_complete, []}
|
||||
|
||||
def do_decode("R21", pv), do: {:report_paramater_value, decode_pv(pv)}
|
||||
def do_decode("R23", pv), do: {:report_calibration_paramater_value, decode_pv(pv)}
|
||||
def do_decode("R41", pv), do: {:report_pin_value, decode_ints(pv)}
|
||||
|
||||
def do_decode("R71", []), do: {:report_axis_timeout, [:x]}
|
||||
def do_decode("R72", []), do: {:report_axis_timeout, [:y]}
|
||||
def do_decode("R73", []), do: {:report_axis_timeout, [:z]}
|
||||
|
||||
def do_decode("R81", xxyyzz), do: {:report_end_stops, decode_end_stops(xxyyzz)}
|
||||
def do_decode("R82", xyzs), do: {:report_position, decode_floats(xyzs)}
|
||||
|
||||
def do_decode("R83", [version]), do: {:report_software_version, [version]}
|
||||
|
||||
def do_decode("R84", xyz), do: {:report_encoders_scaled, decode_floats(xyz)}
|
||||
def do_decode("R85", xyz), do: {:report_encoders_raw, decode_floats(xyz)}
|
||||
|
||||
def do_decode("R87", []), do: {:report_emergency_lock, []}
|
||||
def do_decode("R88", []), do: {:report_no_config, []}
|
||||
def do_decode("R99", debug), do: {:report_debug_message, [Enum.join(debug, " ")]}
|
||||
|
||||
def do_decode("G00", xyzs), do: {:command_movement, decode_floats(xyzs)}
|
||||
def do_decode("G28", []), do: {:comand_movement_home, [:x, :y, :z]}
|
||||
|
||||
def do_decode("F11", []), do: {:command_movement_find_home, [:x]}
|
||||
def do_decode("F12", []), do: {:command_movement_find_home, [:y]}
|
||||
def do_decode("F13", []), do: {:command_movement_find_home, [:z]}
|
||||
|
||||
def do_decode("F14", []), do: {:command_movement_calibrate, [:x]}
|
||||
def do_decode("F15", []), do: {:command_movement_calibrate, [:y]}
|
||||
def do_decode("F16", []), do: {:command_movement_calibrate, [:z]}
|
||||
|
||||
def do_decode("F20", []), do: {:paramater_read_all, []}
|
||||
def do_decode("F21", [param_id]), do: {:paramater_read, [Param.decode(param_id)]}
|
||||
def do_decode("F22", pv), do: {:paramater_write, decode_pv(pv)}
|
||||
def do_decode("F23", pv), do: {:calibration_paramater_write, decode_pv(pv)}
|
||||
|
||||
def do_decode("F41", pvm), do: {:pin_write, decode_ints(pvm)}
|
||||
def do_decode("F42", pv), do: {:pin_read, decode_ints(pv)}
|
||||
def do_decode("F43", pm), do: {:pin_mode_write, decode_ints(pm)}
|
||||
|
||||
def do_decode("F61", pv), do: {:servo_write, decode_ints(pv)}
|
||||
|
||||
def do_decode("F81", []), do: {:end_stops_read, []}
|
||||
def do_decode("F82", []), do: {:position_read, []}
|
||||
def do_decode("F83", []), do: {:software_version_read, []}
|
||||
def do_decode("F84", xyzs), do: {:position_write_zero, decode_ints(xyzs)}
|
||||
|
||||
def do_decode("F09", _), do: {:command_emergency_unlock, []}
|
||||
def do_decode("E", _), do: {:command_emergency_lock, []}
|
||||
|
||||
def do_decode(kind, args) do
|
||||
{:unknown, [kind | args]}
|
||||
end
|
||||
|
||||
defp decode_floats(list, acc \\ [])
|
||||
|
||||
defp decode_floats([<<arg::binary-1, val::binary>> | rest], acc) do
|
||||
arg =
|
||||
arg
|
||||
|> String.downcase()
|
||||
|> String.to_existing_atom()
|
||||
|
||||
case Float.parse(val) do
|
||||
{num, ""} ->
|
||||
decode_floats(rest, Keyword.put(acc, arg, num))
|
||||
|
||||
_ ->
|
||||
case Integer.parse(val) do
|
||||
{num, ""} -> decode_floats(rest, Keyword.put(acc, arg, num / 1))
|
||||
_ -> decode_floats(rest, acc)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This is sort of order dependent and not exactly correct.
|
||||
# It should ensure the order is [x: _, y: _, z: _]
|
||||
defp decode_floats([], acc), do: Enum.reverse(acc)
|
||||
|
||||
defp decode_axis_state(list) do
|
||||
args = decode_floats(list)
|
||||
|
||||
Enum.map(args, fn {axis, value} ->
|
||||
case value do
|
||||
0.0 -> {axis, :idle}
|
||||
1.0 -> {axis, :begin}
|
||||
2.0 -> {axis, :accelerate}
|
||||
3.0 -> {axis, :cruise}
|
||||
4.0 -> {axis, :decelerate}
|
||||
5.0 -> {axis, :stop}
|
||||
6.0 -> {axis, :crawl}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp decode_calibration_state(list) do
|
||||
args = decode_floats(list)
|
||||
|
||||
Enum.map(args, fn {axis, value} ->
|
||||
case value do
|
||||
0.0 -> {axis, :idle}
|
||||
1.0 -> {axis, :home}
|
||||
2.0 -> {axis, :end}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@spec decode_end_stops([binary()], Keyword.t()) :: Keyword.t()
|
||||
defp decode_end_stops(list, acc \\ [])
|
||||
|
||||
defp decode_end_stops(
|
||||
[<<arg::binary-1, "A", val0::binary>>, <<arg::binary-1, "B", val1::binary>> | rest],
|
||||
acc
|
||||
) do
|
||||
dc = String.downcase(arg)
|
||||
|
||||
acc =
|
||||
acc ++
|
||||
[
|
||||
{:"#{dc}a", String.to_integer(val0)},
|
||||
{:"#{dc}b", String.to_integer(val1)}
|
||||
]
|
||||
|
||||
decode_end_stops(rest, acc)
|
||||
end
|
||||
|
||||
defp decode_end_stops([], acc), do: acc
|
||||
|
||||
defp decode_pv(["P" <> param_id, "V" <> value]) do
|
||||
param = Param.decode(String.to_integer(param_id))
|
||||
{value, ""} = Float.parse(value)
|
||||
[{param, value}]
|
||||
end
|
||||
|
||||
defp decode_ints(pvm, acc \\ [])
|
||||
|
||||
defp decode_ints([<<arg::binary-1, val::binary>> | rest], acc) do
|
||||
arg =
|
||||
arg
|
||||
|> String.downcase()
|
||||
|> String.to_existing_atom()
|
||||
|
||||
case Integer.parse(val) do
|
||||
{num, ""} -> decode_ints(rest, Keyword.put(acc, arg, num))
|
||||
_ -> decode_ints(rest, acc)
|
||||
end
|
||||
end
|
||||
|
||||
defp decode_ints([], acc), do: Enum.reverse(acc)
|
||||
|
||||
@spec decode_echo(binary()) :: [binary()]
|
||||
defp decode_echo(str) when is_binary(str) do
|
||||
[_, echo | _] = String.split(str, "*", parts: 3)
|
||||
[String.trim(echo)]
|
||||
end
|
||||
end
|
|
@ -0,0 +1,142 @@
|
|||
defmodule Farmbot.Firmware.GCODE.Encoder do
|
||||
@moduledoc false
|
||||
|
||||
alias Farmbot.Firmware.{GCODE, Param}
|
||||
|
||||
@doc false
|
||||
@spec do_encode(GCODE.kind(), GCODE.args()) :: binary()
|
||||
def do_encode(:report_idle, []), do: "R00"
|
||||
def do_encode(:report_begin, []), do: "R01"
|
||||
def do_encode(:report_success, []), do: "R02"
|
||||
def do_encode(:report_error, []), do: "R03"
|
||||
def do_encode(:report_busy, []), do: "R04"
|
||||
|
||||
def do_encode(:report_axis_state, xyz), do: "R05 " <> encode_axis_state(xyz)
|
||||
def do_encode(:report_calibration_state, xyz), do: "R06 " <> encode_calibration_state(xyz)
|
||||
|
||||
def do_encode(:report_retry, []), do: "R07"
|
||||
def do_encode(:report_echo, [echo]), do: "R08 * #{echo} *"
|
||||
def do_encode(:report_invalid, []), do: "R09"
|
||||
|
||||
def do_encode(:report_home_complete, [:x]), do: "R11"
|
||||
def do_encode(:report_home_complete, [:y]), do: "R12"
|
||||
def do_encode(:report_home_complete, [:z]), do: "R13"
|
||||
|
||||
def do_encode(:report_position_change, [x: _] = arg), do: "R15 " <> encode_floats(arg)
|
||||
def do_encode(:report_position_change, [y: _] = arg), do: "R16 " <> encode_floats(arg)
|
||||
def do_encode(:report_position_change, [z: _] = arg), do: "R16 " <> encode_floats(arg)
|
||||
|
||||
def do_encode(:report_paramaters_complete, []), do: "R20"
|
||||
|
||||
def do_encode(:report_parmater_value, pv), do: "R21 " <> encode_pv(pv)
|
||||
def do_encode(:report_calibration_paramater_value, pv), do: "R23 " <> encode_pv(pv)
|
||||
def do_encode(:report_pin_value, pv), do: "R41 " <> encode_ints(pv)
|
||||
|
||||
def do_encode(:report_axis_timeout, [:x]), do: "R71"
|
||||
def do_encode(:report_axis_timeout, [:y]), do: "R72"
|
||||
def do_encode(:report_axis_timeout, [:z]), do: "R73"
|
||||
|
||||
def do_encode(:report_end_stops, xxyyzz), do: "R81 " <> encode_end_stops(xxyyzz)
|
||||
def do_encode(:report_position, xyzs), do: "R82 " <> encode_floats(xyzs)
|
||||
|
||||
def do_encode(:report_software_version, [version]), do: "R83 " <> version
|
||||
|
||||
def do_encode(:report_encoders_scaled, xyz), do: "R84 " <> encode_floats(xyz)
|
||||
def do_encode(:report_encoders_raw, xyz), do: "R85 " <> encode_floats(xyz)
|
||||
|
||||
def do_encode(:report_emergency_lock, []), do: "R87"
|
||||
def do_encode(:report_no_config, []), do: "R88"
|
||||
def do_encode(:report_debug_message, [message]), do: "R99 " <> message
|
||||
|
||||
def do_encode(:command_movement, xyzs), do: "G00 " <> encode_floats(xyzs)
|
||||
def do_encode(:command_movement_home, [:x, :y, :z]), do: "G28"
|
||||
def do_encode(:command_movement_home, [:x]), do: "G00 " <> encode_floats(x: 0.0)
|
||||
def do_encode(:command_movement_home, [:y]), do: "G00 " <> encode_floats(y: 0.0)
|
||||
def do_encode(:command_movement_home, [:z]), do: "G00 " <> encode_floats(z: 0.0)
|
||||
|
||||
def do_encode(:command_movement_find_home, [:x]), do: "F11"
|
||||
def do_encode(:command_movement_find_home, [:y]), do: "F12"
|
||||
def do_encode(:command_movement_find_home, [:z]), do: "F13"
|
||||
|
||||
def do_encode(:command_movement_calibrate, [:x]), do: "F14"
|
||||
def do_encode(:command_movement_calibrate, [:y]), do: "F15"
|
||||
def do_encode(:command_movement_calibrate, [:z]), do: "F16"
|
||||
|
||||
def do_encode(:paramater_read_all, []), do: "F20"
|
||||
def do_encode(:paramater_read, [paramater]), do: "F21 P#{Param.encode(paramater)}"
|
||||
|
||||
def do_encode(:paramater_write, pv), do: "F22 " <> encode_pv(pv)
|
||||
def do_encode(:calibration_paramater_write, pv), do: "F23 " <> encode_pv(pv)
|
||||
|
||||
def do_encode(:pin_write, pv), do: "F41 " <> encode_ints(pv)
|
||||
def do_encode(:pin_read, p), do: "F42 " <> encode_ints(p)
|
||||
def do_encode(:pin_mode_write, pm), do: "F43 " <> encode_ints(pm)
|
||||
|
||||
def do_encode(:servo_write, pv), do: "F61 " <> encode_ints(pv)
|
||||
def do_encode(:end_stops_read, []), do: "F81"
|
||||
def do_encode(:position_read, []), do: "F82"
|
||||
def do_encode(:software_version_read, []), do: "F83"
|
||||
def do_encode(:position_write_zero, [:x, :y, :z]), do: "F84 X1 Y1 Z1"
|
||||
def do_encode(:position_write_zero, [:x]), do: "F84 X1"
|
||||
def do_encode(:position_write_zero, [:y]), do: "F84 Y1"
|
||||
def do_encode(:position_write_zero, [:z]), do: "F84 Z1"
|
||||
|
||||
def do_encode(:command_emergency_unlock, _), do: "F09"
|
||||
def do_encode(:command_emergency_lock, _), do: "E"
|
||||
|
||||
@spec encode_floats([{Param.t(), float()}]) :: binary()
|
||||
defp encode_floats(args) do
|
||||
Enum.map(args, fn {param, value} ->
|
||||
binary_float = :erlang.float_to_binary(value, decimals: 2)
|
||||
String.upcase(to_string(param)) <> binary_float
|
||||
end)
|
||||
|> Enum.join(" ")
|
||||
end
|
||||
|
||||
defp encode_axis_state([{axis, :idle}]),
|
||||
do: String.upcase(to_string(axis)) <> "0"
|
||||
|
||||
defp encode_axis_state([{axis, :begin}]),
|
||||
do: String.upcase(to_string(axis)) <> "1"
|
||||
|
||||
defp encode_axis_state([{axis, :accelerate}]),
|
||||
do: String.upcase(to_string(axis)) <> "2"
|
||||
|
||||
defp encode_axis_state([{axis, :cruise}]),
|
||||
do: String.upcase(to_string(axis)) <> "3"
|
||||
|
||||
defp encode_axis_state([{axis, :decelerate}]),
|
||||
do: String.upcase(to_string(axis)) <> "4"
|
||||
|
||||
defp encode_axis_state([{axis, :stop}]),
|
||||
do: String.upcase(to_string(axis)) <> "5"
|
||||
|
||||
defp encode_axis_state([{axis, :crawl}]),
|
||||
do: String.upcase(to_string(axis)) <> "6"
|
||||
|
||||
defp encode_calibration_state([{axis, :idle}]),
|
||||
do: String.upcase(to_string(axis)) <> "0"
|
||||
|
||||
defp encode_calibration_state([{axis, :home}]),
|
||||
do: String.upcase(to_string(axis)) <> "1"
|
||||
|
||||
defp encode_calibration_state([{axis, :end}]),
|
||||
do: String.upcase(to_string(axis)) <> "2"
|
||||
|
||||
defp encode_end_stops(xa: xa, xb: xb, ya: ya, yb: yb, za: za, zb: zb) do
|
||||
"XA#{xa} XB#{xb} YA#{ya} YB#{yb} ZA#{za} ZB#{zb}"
|
||||
end
|
||||
|
||||
defp encode_pv([{param, value}]) do
|
||||
param_id = Param.encode(param)
|
||||
binary_float = :erlang.float_to_binary(value, decimals: 2)
|
||||
"P#{param_id} V#{binary_float}"
|
||||
end
|
||||
|
||||
defp encode_ints(args) do
|
||||
Enum.map(args, fn {key, val} ->
|
||||
String.upcase(to_string(key)) <> to_string(val)
|
||||
end)
|
||||
|> Enum.join(" ")
|
||||
end
|
||||
end
|
|
@ -0,0 +1,197 @@
|
|||
defmodule Farmbot.Firmware.Param do
|
||||
@moduledoc "decodes/encodes integer id to name and vice versa"
|
||||
require Logger
|
||||
|
||||
@type t() :: atom()
|
||||
|
||||
@doc "Decodes an integer paramater id to a atom paramater name"
|
||||
def decode(paramater_id)
|
||||
def decode(2), do: :param_config_ok
|
||||
def decode(3), do: :param_use_eeprom
|
||||
def decode(4), do: :param_e_stop_on_mov_err
|
||||
def decode(5), do: :param_mov_nr_retry
|
||||
def decode(11), do: :movement_timeout_x
|
||||
def decode(12), do: :movement_timeout_y
|
||||
def decode(13), do: :movement_timeout_z
|
||||
def decode(15), do: :movement_keep_active_x
|
||||
def decode(16), do: :movement_keep_active_y
|
||||
def decode(17), do: :movement_keep_active_z
|
||||
def decode(18), do: :movement_home_at_boot_x
|
||||
def decode(19), do: :movement_home_at_boot_y
|
||||
def decode(20), do: :movement_home_at_boot_z
|
||||
def decode(21), do: :movement_invert_endpoints_x
|
||||
def decode(22), do: :movement_invert_endpoints_y
|
||||
def decode(23), do: :movement_invert_endpoints_z
|
||||
def decode(25), do: :movement_enable_endpoints_x
|
||||
def decode(26), do: :movement_enable_endpoints_y
|
||||
def decode(27), do: :movement_enable_endpoints_z
|
||||
def decode(31), do: :movement_invert_motor_x
|
||||
def decode(32), do: :movement_invert_motor_y
|
||||
def decode(33), do: :movement_invert_motor_z
|
||||
def decode(36), do: :movement_secondary_motor_x
|
||||
def decode(37), do: :movement_secondary_motor_invert_x
|
||||
def decode(41), do: :movement_steps_acc_dec_x
|
||||
def decode(42), do: :movement_steps_acc_dec_y
|
||||
def decode(43), do: :movement_steps_acc_dec_z
|
||||
def decode(45), do: :movement_stop_at_home_x
|
||||
def decode(46), do: :movement_stop_at_home_y
|
||||
def decode(47), do: :movement_stop_at_home_z
|
||||
def decode(51), do: :movement_home_up_x
|
||||
def decode(52), do: :movement_home_up_y
|
||||
def decode(53), do: :movement_home_up_z
|
||||
def decode(55), do: :movement_step_per_mm_x
|
||||
def decode(56), do: :movement_step_per_mm_y
|
||||
def decode(57), do: :movement_step_per_mm_z
|
||||
def decode(61), do: :movement_min_spd_x
|
||||
def decode(62), do: :movement_min_spd_y
|
||||
def decode(63), do: :movement_min_spd_z
|
||||
def decode(65), do: :movement_home_spd_x
|
||||
def decode(66), do: :movement_home_spd_y
|
||||
def decode(67), do: :movement_home_spd_z
|
||||
def decode(71), do: :movement_max_spd_x
|
||||
def decode(72), do: :movement_max_spd_y
|
||||
def decode(73), do: :movement_max_spd_z
|
||||
def decode(75), do: :movement_invert_2_endpoints_x
|
||||
def decode(76), do: :movement_invert_2_endpoints_y
|
||||
def decode(77), do: :movement_invert_2_endpoints_z
|
||||
def decode(101), do: :encoder_enabled_x
|
||||
def decode(102), do: :encoder_enabled_y
|
||||
def decode(103), do: :encoder_enabled_z
|
||||
def decode(105), do: :encoder_type_x
|
||||
def decode(106), do: :encoder_type_y
|
||||
def decode(107), do: :encoder_type_z
|
||||
def decode(111), do: :encoder_missed_steps_max_x
|
||||
def decode(112), do: :encoder_missed_steps_max_y
|
||||
def decode(113), do: :encoder_missed_steps_max_z
|
||||
def decode(115), do: :encoder_scaling_x
|
||||
def decode(116), do: :encoder_scaling_y
|
||||
def decode(117), do: :encoder_scaling_z
|
||||
def decode(121), do: :encoder_missed_steps_decay_x
|
||||
def decode(122), do: :encoder_missed_steps_decay_y
|
||||
def decode(123), do: :encoder_missed_steps_decay_z
|
||||
def decode(125), do: :encoder_use_for_pos_x
|
||||
def decode(126), do: :encoder_use_for_pos_y
|
||||
def decode(127), do: :encoder_use_for_pos_z
|
||||
def decode(131), do: :encoder_invert_x
|
||||
def decode(132), do: :encoder_invert_y
|
||||
def decode(133), do: :encoder_invert_z
|
||||
def decode(141), do: :movement_axis_nr_steps_x
|
||||
def decode(142), do: :movement_axis_nr_steps_y
|
||||
def decode(143), do: :movement_axis_nr_steps_z
|
||||
def decode(145), do: :movement_stop_at_max_x
|
||||
def decode(146), do: :movement_stop_at_max_y
|
||||
def decode(147), do: :movement_stop_at_max_z
|
||||
def decode(201), do: :pin_guard_1_pin_nr
|
||||
def decode(202), do: :pin_guard_1_time_out
|
||||
def decode(203), do: :pin_guard_1_active_state
|
||||
def decode(205), do: :pin_guard_2_pin_nr
|
||||
def decode(206), do: :pin_guard_2_time_out
|
||||
def decode(207), do: :pin_guard_2_active_state
|
||||
def decode(211), do: :pin_guard_3_pin_nr
|
||||
def decode(212), do: :pin_guard_3_time_out
|
||||
def decode(213), do: :pin_guard_3_active_state
|
||||
def decode(215), do: :pin_guard_4_pin_nr
|
||||
def decode(216), do: :pin_guard_4_time_out
|
||||
def decode(217), do: :pin_guard_4_active_state
|
||||
def decode(221), do: :pin_guard_5_pin_nr
|
||||
def decode(222), do: :pin_guard_5_time_out
|
||||
def decode(223), do: :pin_guard_5_active_state
|
||||
|
||||
def decode(unknown) when is_integer(unknown) do
|
||||
Logger.error("unknown firmware paramater: #{unknown}")
|
||||
:unknown_paramater
|
||||
end
|
||||
|
||||
@doc "Encodes an atom paramater name to an integer paramater id."
|
||||
def encode(paramater)
|
||||
def encode(:param_config_ok), do: 2
|
||||
def encode(:param_use_eeprom), do: 3
|
||||
def encode(:param_e_stop_on_mov_err), do: 4
|
||||
def encode(:param_mov_nr_retry), do: 5
|
||||
def encode(:movement_timeout_x), do: 11
|
||||
def encode(:movement_timeout_y), do: 12
|
||||
def encode(:movement_timeout_z), do: 13
|
||||
def encode(:movement_keep_active_x), do: 15
|
||||
def encode(:movement_keep_active_y), do: 16
|
||||
def encode(:movement_keep_active_z), do: 17
|
||||
def encode(:movement_home_at_boot_x), do: 18
|
||||
def encode(:movement_home_at_boot_y), do: 19
|
||||
def encode(:movement_home_at_boot_z), do: 20
|
||||
def encode(:movement_invert_endpoints_x), do: 21
|
||||
def encode(:movement_invert_endpoints_y), do: 22
|
||||
def encode(:movement_invert_endpoints_z), do: 23
|
||||
def encode(:movement_enable_endpoints_x), do: 25
|
||||
def encode(:movement_enable_endpoints_y), do: 26
|
||||
def encode(:movement_enable_endpoints_z), do: 27
|
||||
def encode(:movement_invert_motor_x), do: 31
|
||||
def encode(:movement_invert_motor_y), do: 32
|
||||
def encode(:movement_invert_motor_z), do: 33
|
||||
def encode(:movement_secondary_motor_x), do: 36
|
||||
def encode(:movement_secondary_motor_invert_x), do: 37
|
||||
def encode(:movement_steps_acc_dec_x), do: 41
|
||||
def encode(:movement_steps_acc_dec_y), do: 42
|
||||
def encode(:movement_steps_acc_dec_z), do: 43
|
||||
def encode(:movement_stop_at_home_x), do: 45
|
||||
def encode(:movement_stop_at_home_y), do: 46
|
||||
def encode(:movement_stop_at_home_z), do: 47
|
||||
def encode(:movement_home_up_x), do: 51
|
||||
def encode(:movement_home_up_y), do: 52
|
||||
def encode(:movement_home_up_z), do: 53
|
||||
def encode(:movement_step_per_mm_x), do: 55
|
||||
def encode(:movement_step_per_mm_y), do: 56
|
||||
def encode(:movement_step_per_mm_z), do: 57
|
||||
def encode(:movement_min_spd_x), do: 61
|
||||
def encode(:movement_min_spd_y), do: 62
|
||||
def encode(:movement_min_spd_z), do: 63
|
||||
def encode(:movement_home_spd_x), do: 65
|
||||
def encode(:movement_home_spd_y), do: 66
|
||||
def encode(:movement_home_spd_z), do: 67
|
||||
def encode(:movement_max_spd_x), do: 71
|
||||
def encode(:movement_max_spd_y), do: 72
|
||||
def encode(:movement_max_spd_z), do: 73
|
||||
def encode(:movement_invert_2_endpoints_x), do: 75
|
||||
def encode(:movement_invert_2_endpoints_y), do: 76
|
||||
def encode(:movement_invert_2_endpoints_z), do: 77
|
||||
def encode(:encoder_enabled_x), do: 101
|
||||
def encode(:encoder_enabled_y), do: 102
|
||||
def encode(:encoder_enabled_z), do: 103
|
||||
def encode(:encoder_type_x), do: 105
|
||||
def encode(:encoder_type_y), do: 106
|
||||
def encode(:encoder_type_z), do: 107
|
||||
def encode(:encoder_missed_steps_max_x), do: 111
|
||||
def encode(:encoder_missed_steps_max_y), do: 112
|
||||
def encode(:encoder_missed_steps_max_z), do: 113
|
||||
def encode(:encoder_scaling_x), do: 115
|
||||
def encode(:encoder_scaling_y), do: 116
|
||||
def encode(:encoder_scaling_z), do: 117
|
||||
def encode(:encoder_missed_steps_decay_x), do: 121
|
||||
def encode(:encoder_missed_steps_decay_y), do: 122
|
||||
def encode(:encoder_missed_steps_decay_z), do: 123
|
||||
def encode(:encoder_use_for_pos_x), do: 125
|
||||
def encode(:encoder_use_for_pos_y), do: 126
|
||||
def encode(:encoder_use_for_pos_z), do: 127
|
||||
def encode(:encoder_invert_x), do: 131
|
||||
def encode(:encoder_invert_y), do: 132
|
||||
def encode(:encoder_invert_z), do: 133
|
||||
def encode(:movement_axis_nr_steps_x), do: 141
|
||||
def encode(:movement_axis_nr_steps_y), do: 142
|
||||
def encode(:movement_axis_nr_steps_z), do: 143
|
||||
def encode(:movement_stop_at_max_x), do: 145
|
||||
def encode(:movement_stop_at_max_y), do: 146
|
||||
def encode(:movement_stop_at_max_z), do: 147
|
||||
def encode(:pin_guard_1_pin_nr), do: 201
|
||||
def encode(:pin_guard_1_time_out), do: 202
|
||||
def encode(:pin_guard_1_active_state), do: 203
|
||||
def encode(:pin_guard_2_pin_nr), do: 205
|
||||
def encode(:pin_guard_2_time_out), do: 206
|
||||
def encode(:pin_guard_2_active_state), do: 207
|
||||
def encode(:pin_guard_3_pin_nr), do: 211
|
||||
def encode(:pin_guard_3_time_out), do: 212
|
||||
def encode(:pin_guard_3_active_state), do: 213
|
||||
def encode(:pin_guard_4_pin_nr), do: 215
|
||||
def encode(:pin_guard_4_time_out), do: 216
|
||||
def encode(:pin_guard_4_active_state), do: 217
|
||||
def encode(:pin_guard_5_pin_nr), do: 221
|
||||
def encode(:pin_guard_5_time_out), do: 222
|
||||
def encode(:pin_guard_5_active_state), do: 223
|
||||
end
|
|
@ -0,0 +1,139 @@
|
|||
defmodule Farmbot.Firmware.Request do
|
||||
@moduledoc false
|
||||
alias Farmbot.{Firmware, Firmware.GCODE}
|
||||
|
||||
@spec request(GenServer.server(), GCODE.t()) ::
|
||||
{:ok, GCODE.t()} | {:error, :invalid_command | :firmware_error | Firmware.status()}
|
||||
def request(firmware_server \\ Firmware, code)
|
||||
|
||||
def request(firmware_server, {_tag, {kind, _}} = code) do
|
||||
if kind not in [
|
||||
:paramater_read,
|
||||
:status_read,
|
||||
:pin_read,
|
||||
:end_stops_read,
|
||||
:position_read,
|
||||
:software_version_read
|
||||
] do
|
||||
raise ArgumentError, "#{kind} is not a valid request."
|
||||
end
|
||||
|
||||
case GenServer.call(firmware_server, code, :infinity) do
|
||||
{:ok, tag} -> wait_for_request_result(tag, code)
|
||||
{:error, status} -> {:error, status}
|
||||
end
|
||||
end
|
||||
|
||||
def request(firmware_server, {_, _} = code) do
|
||||
request(firmware_server, {to_string(:rand.uniform(100)), code})
|
||||
end
|
||||
|
||||
# This is a bit weird but let me explain:
|
||||
# if this function `receive`s
|
||||
# * report_error
|
||||
# * report_invalid
|
||||
# * report_emergency_lock
|
||||
# it needs to return an error.
|
||||
# If this function `receive`s
|
||||
# * report_success
|
||||
# when no valid data has been collected from `wait_for_request_result_process`
|
||||
# it needs to return an error.
|
||||
# If this function `receive`s
|
||||
# * report_success
|
||||
# when valid data has been collected from `wait_for_request_result_process`
|
||||
# it will return that data.
|
||||
# If this function returns no data for 5 seconds, it needs to error.
|
||||
defp wait_for_request_result(tag, code, result \\ nil) do
|
||||
receive do
|
||||
{^tag, {:report_begin, []}} ->
|
||||
wait_for_request_result(tag, code, result)
|
||||
|
||||
{^tag, {:report_busy, []}} ->
|
||||
wait_for_request_result(tag, code, result)
|
||||
|
||||
{^tag, {:report_success, []}} ->
|
||||
if result,
|
||||
do: {:ok, {tag, result}},
|
||||
else: wait_for_request_result(tag, code, result)
|
||||
|
||||
{^tag, {:report_error, []}} ->
|
||||
{:error, :firmware_error}
|
||||
|
||||
{^tag, {:report_invalid, []}} ->
|
||||
{:error, :invalid_command}
|
||||
|
||||
{_, {:report_emergency_lock, []}} ->
|
||||
{:error, :emergency_lock}
|
||||
|
||||
{_tag, report} ->
|
||||
wait_for_request_result_process(report, tag, code, result)
|
||||
after
|
||||
5_000 ->
|
||||
if result, do: {:ok, {tag, result}}, else: {:error, {:timeout, result}}
|
||||
end
|
||||
end
|
||||
|
||||
# {:paramater_read, [param]} => {:report_paramater_value, [{param, val}]}
|
||||
defp wait_for_request_result_process(
|
||||
{:report_paramater_value, _} = report,
|
||||
tag,
|
||||
{_, {:paramater_read, _}} = code,
|
||||
_
|
||||
) do
|
||||
wait_for_request_result(tag, code, report)
|
||||
end
|
||||
|
||||
# {:status_read, [status]} => {:report_status_value, [{status, value}]}
|
||||
defp wait_for_request_result_process(
|
||||
{:report_status_value, _} = report,
|
||||
tag,
|
||||
{_, {:status_read, _}} = code,
|
||||
_
|
||||
) do
|
||||
wait_for_request_result(tag, code, report)
|
||||
end
|
||||
|
||||
# {:pin_read, [pin]} => {:report_pin_value, [{pin, value}]}
|
||||
defp wait_for_request_result_process(
|
||||
{:report_pin_value, _} = report,
|
||||
tag,
|
||||
{_, {:pin_read, _}} = code,
|
||||
_
|
||||
) do
|
||||
wait_for_request_result(tag, code, report)
|
||||
end
|
||||
|
||||
# {:end_stops_read, []} => {:position_end_stops, end_stops}
|
||||
defp wait_for_request_result_process(
|
||||
{:report_end_stops, _} = report,
|
||||
tag,
|
||||
{_, {:end_stops_read, []}} = code,
|
||||
_
|
||||
) do
|
||||
wait_for_request_result(tag, code, report)
|
||||
end
|
||||
|
||||
# {:position_read, []} => {:position_report, [x: x, y: y, z: z]}
|
||||
defp wait_for_request_result_process(
|
||||
{:report_position, _} = report,
|
||||
tag,
|
||||
{_, {:position_read, []}} = code,
|
||||
_
|
||||
) do
|
||||
wait_for_request_result(tag, code, report)
|
||||
end
|
||||
|
||||
# {:software_version_read, []} => {:report_software_version, [version]}
|
||||
defp wait_for_request_result_process(
|
||||
{:report_software_version, _} = report,
|
||||
tag,
|
||||
{_, {:software_version_read, _}} = code,
|
||||
_
|
||||
) do
|
||||
wait_for_request_result(tag, code, report)
|
||||
end
|
||||
|
||||
defp wait_for_request_result_process(_report, tag, code, result) do
|
||||
wait_for_request_result(tag, code, result)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,30 @@
|
|||
defmodule Farmbot.Firmware.SideEffects do
|
||||
alias Farmbot.Firmware.{GCODE, Param}
|
||||
|
||||
@type axis :: :x | :y | :z
|
||||
|
||||
@doc "While in state `:boot`, the firmware needs to load its params."
|
||||
@callback load_params :: [{Param.t(), float() | nil}]
|
||||
|
||||
@callback handle_position(x: float(), y: float(), z: float()) :: any()
|
||||
@callback handle_position_change([{axis(), float()}]) :: any()
|
||||
@callback handle_encoders_scaled(x: float(), y: float(), z: float()) :: any()
|
||||
@callback handle_encoders_raw(x: float(), y: float(), z: float()) :: any()
|
||||
@callback handle_paramater_value([{Param.t(), float()}]) :: any()
|
||||
@callback handle_end_stops(xa: 0 | 1, xb: 0 | 1, ya: 0 | 1, yb: 0 | 1, za: 0 | 1, zb: 0 | 1) ::
|
||||
any()
|
||||
@callback handle_emergency_lock() :: any()
|
||||
@callback handle_emergency_unlock() :: any()
|
||||
@callback handle_pin_value(p: integer(), v: integer()) :: any()
|
||||
@callback handle_software_version([String.t()]) :: any()
|
||||
|
||||
@type axis_state :: :stop | :idle | :begin | :crawl | :decelerate | :accelerate
|
||||
@callback handle_axis_state([{axis(), axis_state}]) :: any()
|
||||
|
||||
@type calibration_state :: :idle | :home | :end
|
||||
@callback handle_calibration_state([{axis(), calibration_state()}]) :: any()
|
||||
|
||||
@callback handle_input_gcode(GCODE.t()) :: any()
|
||||
@callback handle_output_gcode(GCODE.t()) :: any()
|
||||
@callback handle_debug_message([String.t()]) :: any()
|
||||
end
|
|
@ -0,0 +1,126 @@
|
|||
defmodule Farmbot.Firmware.StubSideEffects do
|
||||
@behaviour Farmbot.Firmware.SideEffects
|
||||
|
||||
def load_params do
|
||||
[
|
||||
movement_home_spd_z: 200.0,
|
||||
movement_step_per_mm_x: 5.0,
|
||||
movement_min_spd_z: 200.0,
|
||||
movement_home_at_boot_z: 1.0,
|
||||
pin_guard_5_active_state: 1.0,
|
||||
encoder_missed_steps_decay_z: 5.0,
|
||||
movement_step_per_mm_y: 5.0,
|
||||
pin_guard_1_active_state: 1.0,
|
||||
movement_max_spd_z: 400.0,
|
||||
movement_invert_2_endpoints_y: 0.0,
|
||||
movement_home_up_y: 0.0,
|
||||
pin_guard_5_pin_nr: 0.0,
|
||||
encoder_use_for_pos_y: 1.0,
|
||||
encoder_enabled_z: 1.0,
|
||||
encoder_use_for_pos_z: 1.0,
|
||||
movement_home_up_x: 0.0,
|
||||
encoder_missed_steps_max_z: 5.0,
|
||||
pin_guard_3_active_state: 1.0,
|
||||
movement_keep_active_y: 0.0,
|
||||
movement_timeout_z: 120.0,
|
||||
encoder_invert_x: 0.0,
|
||||
movement_home_spd_y: 400.0,
|
||||
param_e_stop_on_mov_err: 0.0,
|
||||
pin_guard_4_pin_nr: 0.0,
|
||||
movement_axis_nr_steps_z: 7050.0,
|
||||
movement_steps_acc_dec_x: 100.0,
|
||||
movement_invert_motor_z: 0.0,
|
||||
encoder_scaling_x: 5556.0,
|
||||
movement_home_spd_x: 400.0,
|
||||
movement_keep_active_x: 0.0,
|
||||
movement_enable_endpoints_z: 0.0,
|
||||
movement_invert_endpoints_y: 0.0,
|
||||
encoder_missed_steps_max_x: 5.0,
|
||||
movement_stop_at_max_x: 1.0,
|
||||
pin_guard_4_active_state: 1.0,
|
||||
movement_secondary_motor_x: 1.0,
|
||||
encoder_invert_y: 0.0,
|
||||
movement_axis_nr_steps_y: 1686.0,
|
||||
movement_invert_2_endpoints_z: 0.0,
|
||||
movement_timeout_x: 120.0,
|
||||
encoder_missed_steps_max_y: 5.0,
|
||||
movement_stop_at_home_x: 1.0,
|
||||
pin_guard_4_time_out: 60.0,
|
||||
movement_secondary_motor_invert_x: 1.0,
|
||||
movement_invert_endpoints_z: 0.0,
|
||||
movement_steps_acc_dec_y: 100.0,
|
||||
encoder_invert_z: 0.0,
|
||||
movement_home_at_boot_y: 1.0,
|
||||
encoder_scaling_y: 5556.0,
|
||||
movement_invert_2_endpoints_x: 0.0,
|
||||
movement_steps_acc_dec_z: 300.0,
|
||||
encoder_type_z: 0.0,
|
||||
encoder_type_y: 0.0,
|
||||
encoder_use_for_pos_x: 1.0,
|
||||
movement_enable_endpoints_y: 0.0,
|
||||
movement_invert_endpoints_x: 0.0,
|
||||
pin_guard_2_active_state: 1.0,
|
||||
movement_invert_motor_x: 0.0,
|
||||
movement_keep_active_z: 1.0,
|
||||
movement_stop_at_max_z: 1.0,
|
||||
pin_guard_5_time_out: 60.0,
|
||||
movement_min_spd_x: 250.0,
|
||||
movement_timeout_y: 120.0,
|
||||
encoder_missed_steps_decay_y: 5.0,
|
||||
movement_max_spd_x: 800.0,
|
||||
encoder_enabled_x: 1.0,
|
||||
pin_guard_1_pin_nr: 0.0,
|
||||
movement_home_at_boot_x: 1.0,
|
||||
movement_min_spd_y: 350.0,
|
||||
movement_invert_motor_y: 0.0,
|
||||
param_mov_nr_retry: 3.0,
|
||||
pin_guard_2_pin_nr: 0.0,
|
||||
movement_home_up_z: 1.0,
|
||||
movement_axis_nr_steps_x: 1342.0,
|
||||
encoder_enabled_y: 1.0,
|
||||
movement_stop_at_max_y: 1.0,
|
||||
movement_stop_at_home_z: 1.0,
|
||||
movement_step_per_mm_z: 25.0,
|
||||
pin_guard_3_time_out: 60.0,
|
||||
encoder_type_x: 0.0,
|
||||
pin_guard_1_time_out: 60.0,
|
||||
movement_enable_endpoints_x: 0.0,
|
||||
movement_max_spd_y: 800.0,
|
||||
pin_guard_3_pin_nr: 0.0,
|
||||
movement_stop_at_home_y: 1.0,
|
||||
pin_guard_2_time_out: 60.0,
|
||||
encoder_scaling_z: 5556.0,
|
||||
encoder_missed_steps_decay_x: 5.0
|
||||
]
|
||||
end
|
||||
|
||||
def handle_position(_), do: :noop
|
||||
|
||||
def handle_position_change(_), do: :noop
|
||||
|
||||
def handle_axis_state(_), do: :noop
|
||||
|
||||
def handle_calibration_state(_), do: :noop
|
||||
|
||||
def handle_encoders_scaled(_), do: :noop
|
||||
|
||||
def handle_encoders_raw(_), do: :noop
|
||||
|
||||
def handle_paramater_value(_), do: :noop
|
||||
|
||||
def handle_end_stops(_), do: :noop
|
||||
|
||||
def handle_emergency_lock(), do: :noop
|
||||
|
||||
def handle_emergency_unlock(), do: :noop
|
||||
|
||||
def handle_pin_value(_), do: :noop
|
||||
|
||||
def handle_software_version(_), do: :noop
|
||||
|
||||
def handle_input_gcode(_), do: :noop
|
||||
|
||||
def handle_output_gcode(_), do: :noop
|
||||
|
||||
def handle_debug_message(_), do: :noop
|
||||
end
|
|
@ -0,0 +1,356 @@
|
|||
defmodule Farmbot.Firmware.StubTransport do
|
||||
@moduledoc "Stub for transporting GCODES. Simulates the _real_ Firmware."
|
||||
use GenServer
|
||||
|
||||
alias Farmbot.Firmware.StubTransport, as: State
|
||||
alias Farmbot.Firmware.{GCODE, Param}
|
||||
require Logger
|
||||
|
||||
defstruct status: :boot,
|
||||
handle_gcode: nil,
|
||||
position: [x: 0.0, y: 0.0, z: 0.0],
|
||||
encoders_scaled: [x: 0.0, y: 0.0, z: 0.0],
|
||||
encoders_raw: [x: 0.0, y: 0.0, z: 0.0],
|
||||
pins: %{},
|
||||
params: []
|
||||
|
||||
@type t :: %State{
|
||||
status: Farmbot.Firmware.status(),
|
||||
handle_gcode: (Farmbot.Firmware.GCODE.t() -> :ok),
|
||||
position: [x: float(), y: float(), z: float()],
|
||||
encoders_scaled: [x: float(), y: float(), z: float()],
|
||||
encoders_raw: [x: float(), y: float(), z: float()],
|
||||
pins: %{},
|
||||
params: [{Param.t(), float() | nil}]
|
||||
}
|
||||
|
||||
def init(args) do
|
||||
handle_gcode = Keyword.fetch!(args, :handle_gcode)
|
||||
{:ok, %State{status: :boot, handle_gcode: handle_gcode}, 0}
|
||||
end
|
||||
|
||||
def handle_info(:timeout, %{status: :boot} = state) do
|
||||
state.handle_gcode.(GCODE.new(:report_debug_message, ["ARDUINO STARTUP COMPLETE"]))
|
||||
{:noreply, goto(state, :no_config), 0}
|
||||
end
|
||||
|
||||
def handle_info(:timeout, %{status: :no_config} = state) do
|
||||
state.handle_gcode.(GCODE.new(:report_no_config, []))
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info(:timeout, %{status: :emergency_lock} = state) do
|
||||
resp_codes = [
|
||||
GCODE.new(:report_emergency_lock, [])
|
||||
]
|
||||
|
||||
{:noreply, state, {:continue, resp_codes}}
|
||||
end
|
||||
|
||||
def handle_info(:timeout, %{status: :idle} = state) do
|
||||
resp_codes = [
|
||||
GCODE.new(:report_position, state.position),
|
||||
GCODE.new(:report_encoders_scaled, state.encoders_scaled),
|
||||
GCODE.new(:report_encoders_raw, state.encoders_raw),
|
||||
GCODE.new(:report_idle, [])
|
||||
]
|
||||
|
||||
{:noreply, state, {:continue, resp_codes}}
|
||||
end
|
||||
|
||||
def handle_call({tag, {:command_emergency_lock, _}} = code, _from, state) do
|
||||
resp_codes = [
|
||||
GCODE.new(:report_echo, [GCODE.encode(code)]),
|
||||
GCODE.new(:report_begin, [], tag),
|
||||
GCODE.new(:report_emergency_lock, [], tag)
|
||||
]
|
||||
|
||||
{:reply, :ok, %{state | status: :emergency_lock}, {:continue, resp_codes}}
|
||||
end
|
||||
|
||||
def handle_call({tag, {:command_emergency_unlock, _}} = code, _from, state) do
|
||||
resp_codes = [
|
||||
GCODE.new(:report_echo, [GCODE.encode(code)]),
|
||||
GCODE.new(:report_begin, [], tag),
|
||||
GCODE.new(:report_success, [], tag)
|
||||
]
|
||||
|
||||
{:reply, :ok, %{state | status: :idle}, {:continue, resp_codes}}
|
||||
end
|
||||
|
||||
def handle_call(
|
||||
{tag, {:paramater_write, [{:param_config_ok = param, 1.0 = value}]}} = code,
|
||||
_from,
|
||||
state
|
||||
) do
|
||||
new_state = %{state | params: Keyword.put(state.params, param, value)}
|
||||
|
||||
resp_codes = [
|
||||
GCODE.new(:report_echo, [GCODE.encode(code)]),
|
||||
GCODE.new(:report_begin, [], tag),
|
||||
GCODE.new(:report_success, [], tag)
|
||||
]
|
||||
|
||||
{:reply, :ok, goto(new_state, :idle), {:continue, resp_codes}}
|
||||
end
|
||||
|
||||
def handle_call({tag, {:paramater_write, [{param, value}]}} = code, _from, state) do
|
||||
new_state = %{state | params: Keyword.put(state.params, param, value)}
|
||||
|
||||
resp_codes = [
|
||||
GCODE.new(:report_echo, [GCODE.encode(code)]),
|
||||
GCODE.new(:report_begin, [], tag),
|
||||
GCODE.new(:report_success, [], tag)
|
||||
]
|
||||
|
||||
{:reply, :ok, new_state, {:continue, resp_codes}}
|
||||
end
|
||||
|
||||
def handle_call({tag, {:paramater_read_all, []}} = code, _from, state) do
|
||||
resp_codes =
|
||||
[
|
||||
GCODE.new(:report_echo, [GCODE.encode(code)]),
|
||||
GCODE.new(:report_begin, [], tag),
|
||||
Enum.map(state.params, fn {p, v} ->
|
||||
GCODE.new(:report_paramater_value, [{p, v}])
|
||||
end),
|
||||
GCODE.new(:report_success, [], tag)
|
||||
]
|
||||
|> List.flatten()
|
||||
|
||||
{:reply, :ok, state, {:continue, resp_codes}}
|
||||
end
|
||||
|
||||
def handle_call({tag, {:paramater_read, [param]}} = code, _from, state) do
|
||||
resp_codes = [
|
||||
GCODE.new(:report_echo, [GCODE.encode(code)]),
|
||||
GCODE.new(:report_begin, [], tag),
|
||||
GCODE.new(:report_paramater_value, [{param, state.params[param] || -1.0}]),
|
||||
GCODE.new(:report_success, [], tag)
|
||||
]
|
||||
|
||||
{:reply, :ok, state, {:continue, resp_codes}}
|
||||
end
|
||||
|
||||
def handle_call({tag, {:position_write_zero, [:x, :y, :z]}} = code, _from, state) do
|
||||
position = [
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0
|
||||
]
|
||||
|
||||
state = %{state | position: position}
|
||||
|
||||
resp_codes = [
|
||||
GCODE.new(:report_echo, [GCODE.encode(code)]),
|
||||
GCODE.new(:report_begin, [], tag),
|
||||
GCODE.new(:report_busy, [], tag),
|
||||
GCODE.new(:report_position, state.position),
|
||||
GCODE.new(:report_success, [], tag)
|
||||
]
|
||||
|
||||
{:reply, :ok, state, {:continue, resp_codes}}
|
||||
end
|
||||
|
||||
def handle_call({tag, {:position_write_zero, [axis]}} = code, _from, state) do
|
||||
position = Keyword.put(state.position, axis, 0.0) |> ensure_order()
|
||||
state = %{state | position: position}
|
||||
|
||||
resp_codes = [
|
||||
GCODE.new(:report_echo, [GCODE.encode(code)]),
|
||||
GCODE.new(:report_begin, [], tag),
|
||||
GCODE.new(:report_position, state.position),
|
||||
GCODE.new(:report_success, [], tag)
|
||||
]
|
||||
|
||||
{:reply, :ok, state, {:continue, resp_codes}}
|
||||
end
|
||||
|
||||
def handle_call({tag, {:command_movement_calibrate, [axis]}} = code, _from, state) do
|
||||
position = [x: 0.0, y: 0.0, z: 0.0]
|
||||
state = %{state | position: position}
|
||||
param_nr_steps = :"movement_axis_nr_steps_#{axis}"
|
||||
param_nr_steps_val = Keyword.get(state.params, :param_nr_steps, 10_0000.00)
|
||||
|
||||
param_endpoints = :"movement_invert_endpoints_#{axis}"
|
||||
param_endpoints_val = Keyword.get(state.params, :param_endpoints, 1.0)
|
||||
|
||||
resp_codes = [
|
||||
GCODE.new(:report_echo, [GCODE.encode(code)]),
|
||||
GCODE.new(:report_begin, [], tag),
|
||||
GCODE.new(:report_busy, [], tag),
|
||||
GCODE.new(:report_calibration_state, [:idle]),
|
||||
GCODE.new(:report_calibration_state, [:home]),
|
||||
GCODE.new(:report_calibration_state, [:end]),
|
||||
GCODE.new(:report_calibration_paramater_value, [{param_nr_steps, param_nr_steps_val}]),
|
||||
GCODE.new(:report_calibration_paramater_value, [{param_endpoints, param_endpoints_val}]),
|
||||
GCODE.new(:report_position, state.position),
|
||||
GCODE.new(:report_success, [], tag)
|
||||
]
|
||||
|
||||
{:reply, :ok, state, {:continue, resp_codes}}
|
||||
end
|
||||
|
||||
# Everything under this clause should be blocked if emergency_locked
|
||||
def handle_call({_tag, {_, _}} = code, _from, %{status: :emergency_lock} = state) do
|
||||
Logger.error("Stub Transport emergency lock")
|
||||
|
||||
resp_codes = [
|
||||
GCODE.new(:report_echo, [GCODE.encode(code)]),
|
||||
GCODE.new(:report_emergency_lock, [])
|
||||
]
|
||||
|
||||
{:reply, :ok, state, {:continue, resp_codes}}
|
||||
end
|
||||
|
||||
def handle_call({tag, {:pin_read, args}} = code, _from, state) do
|
||||
p = Keyword.fetch!(args, :p)
|
||||
m = Keyword.get(args, :m, state.pins[p][:m] || 0)
|
||||
|
||||
state =
|
||||
case Map.get(state.pins, p) do
|
||||
nil -> %{state | pins: Map.put(state.pins, p, m: m, v: 0)}
|
||||
[m: ^m, v: v] -> %{state | pins: Map.put(state.pins, p, m: m, v: v)}
|
||||
_ -> state
|
||||
end
|
||||
|
||||
resp_codes = [
|
||||
GCODE.new(:report_echo, [GCODE.encode(code)]),
|
||||
GCODE.new(:report_begin, [], tag),
|
||||
GCODE.new(:report_pin_value, p: p, v: Map.get(state.pins, p)[:v]),
|
||||
GCODE.new(:report_success, [], tag)
|
||||
]
|
||||
|
||||
{:reply, :ok, state, {:continue, resp_codes}}
|
||||
end
|
||||
|
||||
def handle_call({tag, {:pin_write, args}} = code, _from, state) do
|
||||
p = Keyword.fetch!(args, :p)
|
||||
m = Keyword.get(args, :m, state.pins[p][:m] || 0)
|
||||
v = Keyword.fetch!(args, :v)
|
||||
state = %{state | pins: Map.put(state.pins, p, m: m, v: v)}
|
||||
|
||||
resp_codes = [
|
||||
GCODE.new(:report_echo, [GCODE.encode(code)]),
|
||||
GCODE.new(:report_begin, [], tag),
|
||||
GCODE.new(:report_success, [], tag)
|
||||
]
|
||||
|
||||
{:reply, :ok, state, {:continue, resp_codes}}
|
||||
end
|
||||
|
||||
def handle_call({tag, {:position_read, _}} = code, _from, state) do
|
||||
resp_codes = [
|
||||
GCODE.new(:report_echo, [GCODE.encode(code)]),
|
||||
GCODE.new(:report_begin, [], tag),
|
||||
GCODE.new(:report_position, state.position),
|
||||
GCODE.new(:report_success, [], tag)
|
||||
]
|
||||
|
||||
{:reply, :ok, state, {:continue, resp_codes}}
|
||||
end
|
||||
|
||||
def handle_call({tag, {:command_movement, args}} = code, _from, state) do
|
||||
position = [
|
||||
x: args[:x] || state.position[:x],
|
||||
y: args[:y] || state.position[:y],
|
||||
z: args[:z] || state.position[:z]
|
||||
]
|
||||
|
||||
state = %{state | position: position}
|
||||
|
||||
resp_codes = [
|
||||
GCODE.new(:report_echo, [GCODE.encode(code)]),
|
||||
GCODE.new(:report_begin, [], tag),
|
||||
GCODE.new(:report_busy, [], tag),
|
||||
GCODE.new(:report_position, state.position),
|
||||
GCODE.new(:report_success, [], tag)
|
||||
]
|
||||
|
||||
{:reply, :ok, state, {:continue, resp_codes}}
|
||||
end
|
||||
|
||||
def handle_call({tag, {:command_movement_home, [:x, :y, :z]}} = code, _from, state) do
|
||||
position = [
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0
|
||||
]
|
||||
|
||||
state = %{state | position: position}
|
||||
|
||||
resp_codes = [
|
||||
GCODE.new(:report_echo, [GCODE.encode(code)]),
|
||||
GCODE.new(:report_begin, [], tag),
|
||||
GCODE.new(:report_busy, [], tag),
|
||||
GCODE.new(:report_position, state.position),
|
||||
GCODE.new(:report_success, [], tag)
|
||||
]
|
||||
|
||||
{:reply, :ok, state, {:continue, resp_codes}}
|
||||
end
|
||||
|
||||
def handle_call({tag, {:command_movement_home, [axis]}} = code, _from, state) do
|
||||
position = Keyword.put(state.position, axis, 0.0) |> ensure_order()
|
||||
state = %{state | position: position}
|
||||
|
||||
resp_codes = [
|
||||
GCODE.new(:report_echo, [GCODE.encode(code)]),
|
||||
GCODE.new(:report_begin, [], tag),
|
||||
GCODE.new(:report_busy, [], tag),
|
||||
GCODE.new(:report_position, state.position),
|
||||
GCODE.new(:report_success, [], tag)
|
||||
]
|
||||
|
||||
{:reply, :ok, state, {:continue, resp_codes}}
|
||||
end
|
||||
|
||||
def handle_call({tag, {:command_movement_find_home, [axis]}} = code, _from, state) do
|
||||
position = Keyword.put(state.position, axis, 0.0) |> ensure_order()
|
||||
state = %{state | position: position}
|
||||
|
||||
resp_codes = [
|
||||
GCODE.new(:report_echo, [GCODE.encode(code)]),
|
||||
GCODE.new(:report_begin, [], tag),
|
||||
GCODE.new(:report_busy, [], tag),
|
||||
GCODE.new(:report_position, state.position),
|
||||
GCODE.new(:report_success, [], tag)
|
||||
]
|
||||
|
||||
{:reply, :ok, state, {:continue, resp_codes}}
|
||||
end
|
||||
|
||||
def handle_call({tag, {_, _}} = code, _from, state) do
|
||||
Logger.error("STUB HANDLER: unknown code: #{inspect(code)} for state: #{state.status}")
|
||||
|
||||
resp_codes = [
|
||||
GCODE.new(:report_echo, [GCODE.encode(code)]),
|
||||
GCODE.new(:report_invalid, [], tag)
|
||||
]
|
||||
|
||||
{:reply, :ok, state, {:continue, resp_codes}}
|
||||
end
|
||||
|
||||
def handle_continue([code | rest], state) do
|
||||
state.handle_gcode.(code)
|
||||
{:noreply, state, {:continue, rest}}
|
||||
end
|
||||
|
||||
def handle_continue([], %{status: :idle} = state) do
|
||||
{:noreply, state, 5_000}
|
||||
end
|
||||
|
||||
def handle_continue([], %{status: _} = state) do
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
defp goto(%{status: _old} = state, status), do: %{state | status: status}
|
||||
|
||||
defp ensure_order(pos) do
|
||||
[
|
||||
x: Keyword.fetch!(pos, :x),
|
||||
y: Keyword.fetch!(pos, :y),
|
||||
z: Keyword.fetch!(pos, :z)
|
||||
]
|
||||
end
|
||||
end
|
|
@ -0,0 +1,45 @@
|
|||
defmodule Farmbot.Firmware.UARTTransport do
|
||||
@moduledoc """
|
||||
Handles sending/receiving GCODEs over UART.
|
||||
This is the mechanism that official Farmbot's communicate with
|
||||
official Farmbot-Arduino-Firmware's over.
|
||||
"""
|
||||
alias Farmbot.Firmware.GCODE
|
||||
use GenServer
|
||||
|
||||
def init(args) do
|
||||
device = Keyword.fetch!(args, :device)
|
||||
handle_gcode = Keyword.fetch!(args, :handle_gcode)
|
||||
{:ok, uart} = Nerves.UART.start_link()
|
||||
{:ok, %{uart: uart, device: device, open: false, handle_gcode: handle_gcode}, 0}
|
||||
end
|
||||
|
||||
def terminate(_, state) do
|
||||
Nerves.UART.stop(state.uart)
|
||||
end
|
||||
|
||||
def handle_info(:timeout, %{open: false} = state) do
|
||||
opts = [active: true, speed: 115_200, framing: {Nerves.UART.Framing.Line, separator: "\r\n"}]
|
||||
|
||||
case Nerves.UART.open(state.uart, state.device, opts) do
|
||||
:ok -> {:noreply, %{state | open: true}}
|
||||
{:error, reason} -> {:stop, {:uart_error, reason}, state}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info({:nerves_uart, _, {:error, reason}}, state) do
|
||||
{:stop, {:uart_error, reason}, state}
|
||||
end
|
||||
|
||||
def handle_info({:nerves_uart, _, data}, state) when is_binary(data) do
|
||||
code = GCODE.decode(String.trim(data))
|
||||
state.handle_gcode.(code)
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_call(code, _from, state) do
|
||||
str = GCODE.encode(code)
|
||||
r = Nerves.UART.write(state.uart, str)
|
||||
{:reply, r, state}
|
||||
end
|
||||
end
|
|
@ -0,0 +1,41 @@
|
|||
defmodule Farmbot.Firmware.MixProject do
|
||||
use Mix.Project
|
||||
@version Path.join([__DIR__, "..", "VERSION"]) |> File.read!() |> String.trim()
|
||||
@elixir_version Path.join([__DIR__, "..", "ELIXIR_VERSION"]) |> File.read!() |> String.trim()
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :farmbot_firmware,
|
||||
version: @version,
|
||||
elixir: @elixir_version,
|
||||
start_permanent: Mix.env() == :prod,
|
||||
test_coverage: [tool: ExCoveralls],
|
||||
preferred_cli_env: [
|
||||
test: :test,
|
||||
coveralls: :test,
|
||||
"coveralls.circle": :test,
|
||||
"coveralls.detail": :test,
|
||||
"coveralls.post": :test,
|
||||
"coveralls.html": :test
|
||||
],
|
||||
deps: deps()
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help compile.app" to learn about applications.
|
||||
def application do
|
||||
[
|
||||
extra_applications: [:logger]
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
{:nerves_uart, "~> 1.2"},
|
||||
{:excoveralls, "~> 0.10", only: [:test]},
|
||||
{:dialyxir, "~> 1.0.0-rc.3", only: [:dev], runtime: false},
|
||||
{:ex_doc, "~> 0.19", only: [:docs], runtime: false}
|
||||
]
|
||||
end
|
||||
end
|
|
@ -0,0 +1,42 @@
|
|||
%{
|
||||
"certifi": {:hex, :certifi, "2.4.2", "75424ff0f3baaccfd34b1214184b6ef616d89e420b258bb0a5ea7d7bc628f7f0", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"},
|
||||
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
|
||||
"cowboy": {:hex, :cowboy, "2.5.0", "4ef3ae066ee10fe01ea3272edc8f024347a0d3eb95f6fbb9aed556dacbfc1337", [:rebar3], [{:cowlib, "~> 2.6.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.6.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"cowlib": {:hex, :cowlib, "2.6.0", "8aa629f81a0fc189f261dc98a42243fa842625feea3c7ec56c48f4ccdb55490f", [:rebar3], [], "hexpm"},
|
||||
"db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"},
|
||||
"dialyxir": {:hex, :dialyxir, "1.0.0-rc.4", "71b42f5ee1b7628f3e3a6565f4617dfb02d127a0499ab3e72750455e986df001", [:mix], [{:erlex, "~> 0.1", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"earmark": {:hex, :earmark, "1.2.6", "b6da42b3831458d3ecc57314dff3051b080b9b2be88c2e5aa41cd642a5b044ed", [:mix], [], "hexpm"},
|
||||
"ecto": {:hex, :ecto, "2.2.9", "031d55df9bb430cb118e6f3026a87408d9ce9638737bda3871e5d727a3594aae", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"elixir_make": {:hex, :elixir_make, "0.4.2", "332c649d08c18bc1ecc73b1befc68c647136de4f340b548844efc796405743bf", [:mix], [], "hexpm"},
|
||||
"erlex": {:hex, :erlex, "0.1.6", "c01c889363168d3fdd23f4211647d8a34c0f9a21ec726762312e08e083f3d47e", [:mix], [], "hexpm"},
|
||||
"esqlite": {:hex, :esqlite, "0.2.4", "3a8a352c190afe2d6b828b252a6fbff65b5cc1124647f38b15bdab3bf6fd4b3e", [:rebar3], [], "hexpm"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"excoveralls": {:hex, :excoveralls, "0.10.2", "fb4abd5b8a1b9d52d35e1162e7e2ea8bfb84b47ae07c38d39aa8ce64be0b0794", [:mix], [{:hackney, "~> 1.13", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"gen_stage": {:hex, :gen_stage, "0.14.1", "9d46723fda072d4f4bb31a102560013f7960f5d80ea44dcb96fd6304ed61e7a4", [:mix], [], "hexpm"},
|
||||
"gettext": {:hex, :gettext, "0.16.0", "4a7e90408cef5f1bf57c5a39e2db8c372a906031cc9b1466e963101cb927dafc", [:mix], [], "hexpm"},
|
||||
"hackney": {:hex, :hackney, "1.14.3", "b5f6f5dcc4f1fba340762738759209e21914516df6be440d85772542d4a5e412", [:rebar3], [{:certifi, "2.4.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"makeup": {:hex, :makeup, "0.5.5", "9e08dfc45280c5684d771ad58159f718a7b5788596099bdfb0284597d368a882", [:mix], [{:nimble_parsec, "~> 0.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"makeup_elixir": {:hex, :makeup_elixir, "0.10.0", "0f09c2ddf352887a956d84f8f7e702111122ca32fbbc84c2f0569b8b65cbf7fa", [:mix], [{:makeup, "~> 0.5.5", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
|
||||
"mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"},
|
||||
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
|
||||
"nerves_uart": {:hex, :nerves_uart, "1.2.0", "195424116b925cd3bf9d666be036c2a80655e6ca0f8d447e277667a60005c50e", [:mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"},
|
||||
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
|
||||
"plug": {:hex, :plug, "1.7.1", "8516d565fb84a6a8b2ca722e74e2cd25ca0fc9d64f364ec9dbec09d33eb78ccd", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"plug_cowboy": {:hex, :plug_cowboy, "2.0.0", "ab0c92728f2ba43c544cce85f0f220d8d30fc0c90eaa1e6203683ab039655062", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
|
||||
"poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"},
|
||||
"ranch": {:hex, :ranch, "1.6.2", "6db93c78f411ee033dbb18ba8234c5574883acb9a75af0fb90a9b82ea46afa00", [:rebar3], [], "hexpm"},
|
||||
"sbroker": {:hex, :sbroker, "1.0.0", "28ff1b5e58887c5098539f236307b36fe1d3edaa2acff9d6a3d17c2dcafebbd0", [:rebar3], [], "hexpm"},
|
||||
"sqlite_ecto2": {:hex, :sqlite_ecto2, "2.3.1", "fe58926854c3962c4c8710bd1070dd4ba3717ba77250387794cb7a65f77006aa", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "2.2.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: false]}, {:sqlitex, "~> 1.4", [hex: :sqlitex, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"sqlitex": {:hex, :sqlitex, "1.4.3", "a50f12d6aeb25f4ebb128453386c09bbba8f5abd3c7713dc5eaa92f359926ac5", [:mix], [{:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:esqlite, "~> 0.2.4", [hex: :esqlite, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
|
||||
"timex": {:hex, :timex, "3.4.2", "d74649c93ad0e12ce5b17cf5e11fbd1fb1b24a3d114643e86dba194b64439547", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"tzdata": {:hex, :tzdata, "0.5.19", "7962a3997bf06303b7d1772988ede22260f3dae1bf897408ebdac2b4435f4e6a", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
defmodule Farmbot.FirmwareTest do
|
||||
use ExUnit.Case
|
||||
doctest Farmbot.Firmware
|
||||
end
|
|
@ -0,0 +1,315 @@
|
|||
defmodule Farmbot.Firmware.GCODETest do
|
||||
use ExUnit.Case
|
||||
alias Farmbot.Firmware.GCODE
|
||||
doctest GCODE
|
||||
|
||||
test "extracts q codes" do
|
||||
assert {"0", ["ABC"]} = GCODE.extract_tag(["ABC", "Q0"])
|
||||
|
||||
assert {"123", ["Y00", "H1", "I1", "K9", "L199"]} =
|
||||
GCODE.extract_tag(["Y00", "H1", "I1", "K9", "L199", "Q123"])
|
||||
|
||||
assert {"abc", ["J700"]} = GCODE.extract_tag(["J700", "Qabc"])
|
||||
assert {nil, ["H100"]} = GCODE.extract_tag(["H100"])
|
||||
end
|
||||
|
||||
describe "receive codes" do
|
||||
test "idle" do
|
||||
assert {nil, {:report_idle, []}} = GCODE.decode("R00")
|
||||
assert {"100", {:report_idle, []}} = GCODE.decode("R00 Q100")
|
||||
|
||||
assert "R00" = GCODE.encode({nil, {:report_idle, []}})
|
||||
assert "R00 Q100" = GCODE.encode({"100", {:report_idle, []}})
|
||||
end
|
||||
|
||||
test "begin" do
|
||||
assert {nil, {:report_begin, []}} = GCODE.decode("R01")
|
||||
assert {"100", {:report_begin, []}} = GCODE.decode("R01 Q100")
|
||||
|
||||
assert "R01" = GCODE.encode({nil, {:report_begin, []}})
|
||||
assert "R01 Q100" = GCODE.encode({"100", {:report_begin, []}})
|
||||
end
|
||||
|
||||
test "success" do
|
||||
assert {nil, {:report_success, []}} = GCODE.decode("R02")
|
||||
assert {"100", {:report_success, []}} = GCODE.decode("R02 Q100")
|
||||
|
||||
assert "R02" = GCODE.encode({nil, {:report_success, []}})
|
||||
assert "R02 Q100" = GCODE.encode({"100", {:report_success, []}})
|
||||
end
|
||||
|
||||
test "error" do
|
||||
assert {nil, {:report_error, []}} = GCODE.decode("R03")
|
||||
assert {"100", {:report_error, []}} = GCODE.decode("R03 Q100")
|
||||
|
||||
assert "R03" = GCODE.encode({nil, {:report_error, []}})
|
||||
assert "R03 Q100" = GCODE.encode({"100", {:report_error, []}})
|
||||
end
|
||||
|
||||
test "busy" do
|
||||
assert {nil, {:report_busy, []}} = GCODE.decode("R04")
|
||||
assert {"100", {:report_busy, []}} = GCODE.decode("R04 Q100")
|
||||
|
||||
assert "R04" = GCODE.encode({nil, {:report_busy, []}})
|
||||
assert "R04 Q100" = GCODE.encode({"100", {:report_busy, []}})
|
||||
end
|
||||
|
||||
test "axis state" do
|
||||
assert {nil, {:report_axis_state, [x: :idle]}} = GCODE.decode("R05 X0")
|
||||
assert {nil, {:report_axis_state, [x: :begin]}} = GCODE.decode("R05 X1")
|
||||
assert {nil, {:report_axis_state, [x: :accelerate]}} = GCODE.decode("R05 X2")
|
||||
assert {nil, {:report_axis_state, [x: :cruise]}} = GCODE.decode("R05 X3")
|
||||
assert {nil, {:report_axis_state, [x: :decelerate]}} = GCODE.decode("R05 X4")
|
||||
assert {nil, {:report_axis_state, [x: :stop]}} = GCODE.decode("R05 X5")
|
||||
assert {nil, {:report_axis_state, [x: :crawl]}} = GCODE.decode("R05 X6")
|
||||
|
||||
assert {"12", {:report_axis_state, [x: :idle]}} = GCODE.decode("R05 X0 Q12")
|
||||
assert {"12", {:report_axis_state, [x: :begin]}} = GCODE.decode("R05 X1 Q12")
|
||||
assert {"12", {:report_axis_state, [x: :accelerate]}} = GCODE.decode("R05 X2 Q12")
|
||||
assert {"12", {:report_axis_state, [x: :cruise]}} = GCODE.decode("R05 X3 Q12")
|
||||
assert {"12", {:report_axis_state, [x: :decelerate]}} = GCODE.decode("R05 X4 Q12")
|
||||
assert {"12", {:report_axis_state, [x: :stop]}} = GCODE.decode("R05 X5 Q12")
|
||||
assert {"12", {:report_axis_state, [x: :crawl]}} = GCODE.decode("R05 X6 Q12")
|
||||
|
||||
assert "R05 X0" = GCODE.encode({nil, {:report_axis_state, [x: :idle]}})
|
||||
assert "R05 X1" = GCODE.encode({nil, {:report_axis_state, [x: :begin]}})
|
||||
assert "R05 X2" = GCODE.encode({nil, {:report_axis_state, [x: :accelerate]}})
|
||||
assert "R05 X3" = GCODE.encode({nil, {:report_axis_state, [x: :cruise]}})
|
||||
assert "R05 X4" = GCODE.encode({nil, {:report_axis_state, [x: :decelerate]}})
|
||||
assert "R05 X5" = GCODE.encode({nil, {:report_axis_state, [x: :stop]}})
|
||||
assert "R05 X6" = GCODE.encode({nil, {:report_axis_state, [x: :crawl]}})
|
||||
|
||||
assert "R05 X0 Q12" = GCODE.encode({"12", {:report_axis_state, [x: :idle]}})
|
||||
assert "R05 X1 Q12" = GCODE.encode({"12", {:report_axis_state, [x: :begin]}})
|
||||
assert "R05 X2 Q12" = GCODE.encode({"12", {:report_axis_state, [x: :accelerate]}})
|
||||
assert "R05 X3 Q12" = GCODE.encode({"12", {:report_axis_state, [x: :cruise]}})
|
||||
assert "R05 X4 Q12" = GCODE.encode({"12", {:report_axis_state, [x: :decelerate]}})
|
||||
assert "R05 X5 Q12" = GCODE.encode({"12", {:report_axis_state, [x: :stop]}})
|
||||
assert "R05 X6 Q12" = GCODE.encode({"12", {:report_axis_state, [x: :crawl]}})
|
||||
|
||||
assert {nil, {:report_axis_state, [y: :idle]}} = GCODE.decode("R05 Y0")
|
||||
assert {nil, {:report_axis_state, [y: :begin]}} = GCODE.decode("R05 Y1")
|
||||
assert {nil, {:report_axis_state, [y: :accelerate]}} = GCODE.decode("R05 Y2")
|
||||
assert {nil, {:report_axis_state, [y: :cruise]}} = GCODE.decode("R05 Y3")
|
||||
assert {nil, {:report_axis_state, [y: :decelerate]}} = GCODE.decode("R05 Y4")
|
||||
assert {nil, {:report_axis_state, [y: :stop]}} = GCODE.decode("R05 Y5")
|
||||
assert {nil, {:report_axis_state, [y: :crawl]}} = GCODE.decode("R05 Y6")
|
||||
|
||||
assert {"13", {:report_axis_state, [y: :idle]}} = GCODE.decode("R05 Y0 Q13")
|
||||
assert {"13", {:report_axis_state, [y: :begin]}} = GCODE.decode("R05 Y1 Q13")
|
||||
assert {"13", {:report_axis_state, [y: :accelerate]}} = GCODE.decode("R05 Y2 Q13")
|
||||
assert {"13", {:report_axis_state, [y: :cruise]}} = GCODE.decode("R05 Y3 Q13")
|
||||
assert {"13", {:report_axis_state, [y: :decelerate]}} = GCODE.decode("R05 Y4 Q13")
|
||||
assert {"13", {:report_axis_state, [y: :stop]}} = GCODE.decode("R05 Y5 Q13")
|
||||
assert {"13", {:report_axis_state, [y: :crawl]}} = GCODE.decode("R05 Y6 Q13")
|
||||
|
||||
assert "R05 Y0" = GCODE.encode({nil, {:report_axis_state, [y: :idle]}})
|
||||
assert "R05 Y1" = GCODE.encode({nil, {:report_axis_state, [y: :begin]}})
|
||||
assert "R05 Y2" = GCODE.encode({nil, {:report_axis_state, [y: :accelerate]}})
|
||||
assert "R05 Y3" = GCODE.encode({nil, {:report_axis_state, [y: :cruise]}})
|
||||
assert "R05 Y4" = GCODE.encode({nil, {:report_axis_state, [y: :decelerate]}})
|
||||
assert "R05 Y5" = GCODE.encode({nil, {:report_axis_state, [y: :stop]}})
|
||||
assert "R05 Y6" = GCODE.encode({nil, {:report_axis_state, [y: :crawl]}})
|
||||
|
||||
assert "R05 Y0 Q13" = GCODE.encode({"13", {:report_axis_state, [y: :idle]}})
|
||||
assert "R05 Y1 Q13" = GCODE.encode({"13", {:report_axis_state, [y: :begin]}})
|
||||
assert "R05 Y2 Q13" = GCODE.encode({"13", {:report_axis_state, [y: :accelerate]}})
|
||||
assert "R05 Y3 Q13" = GCODE.encode({"13", {:report_axis_state, [y: :cruise]}})
|
||||
assert "R05 Y4 Q13" = GCODE.encode({"13", {:report_axis_state, [y: :decelerate]}})
|
||||
assert "R05 Y5 Q13" = GCODE.encode({"13", {:report_axis_state, [y: :stop]}})
|
||||
assert "R05 Y6 Q13" = GCODE.encode({"13", {:report_axis_state, [y: :crawl]}})
|
||||
|
||||
assert {nil, {:report_axis_state, [z: :idle]}} = GCODE.decode("R05 Z0")
|
||||
assert {nil, {:report_axis_state, [z: :begin]}} = GCODE.decode("R05 Z1")
|
||||
assert {nil, {:report_axis_state, [z: :accelerate]}} = GCODE.decode("R05 Z2")
|
||||
assert {nil, {:report_axis_state, [z: :cruise]}} = GCODE.decode("R05 Z3")
|
||||
assert {nil, {:report_axis_state, [z: :decelerate]}} = GCODE.decode("R05 Z4")
|
||||
assert {nil, {:report_axis_state, [z: :stop]}} = GCODE.decode("R05 Z5")
|
||||
assert {nil, {:report_axis_state, [z: :crawl]}} = GCODE.decode("R05 Z6")
|
||||
|
||||
assert {"14", {:report_axis_state, [z: :idle]}} = GCODE.decode("R05 Z0 Q14")
|
||||
assert {"14", {:report_axis_state, [z: :begin]}} = GCODE.decode("R05 Z1 Q14")
|
||||
assert {"14", {:report_axis_state, [z: :accelerate]}} = GCODE.decode("R05 Z2 Q14")
|
||||
assert {"14", {:report_axis_state, [z: :cruise]}} = GCODE.decode("R05 Z3 Q14")
|
||||
assert {"14", {:report_axis_state, [z: :decelerate]}} = GCODE.decode("R05 Z4 Q14")
|
||||
assert {"14", {:report_axis_state, [z: :stop]}} = GCODE.decode("R05 Z5 Q14")
|
||||
assert {"14", {:report_axis_state, [z: :crawl]}} = GCODE.decode("R05 Z6 Q14")
|
||||
|
||||
assert "R05 Z0" = GCODE.encode({nil, {:report_axis_state, [z: :idle]}})
|
||||
assert "R05 Z1" = GCODE.encode({nil, {:report_axis_state, [z: :begin]}})
|
||||
assert "R05 Z2" = GCODE.encode({nil, {:report_axis_state, [z: :accelerate]}})
|
||||
assert "R05 Z3" = GCODE.encode({nil, {:report_axis_state, [z: :cruise]}})
|
||||
assert "R05 Z4" = GCODE.encode({nil, {:report_axis_state, [z: :decelerate]}})
|
||||
assert "R05 Z5" = GCODE.encode({nil, {:report_axis_state, [z: :stop]}})
|
||||
assert "R05 Z6" = GCODE.encode({nil, {:report_axis_state, [z: :crawl]}})
|
||||
|
||||
assert "R05 Z0 Q14" = GCODE.encode({"14", {:report_axis_state, [z: :idle]}})
|
||||
assert "R05 Z1 Q14" = GCODE.encode({"14", {:report_axis_state, [z: :begin]}})
|
||||
assert "R05 Z2 Q14" = GCODE.encode({"14", {:report_axis_state, [z: :accelerate]}})
|
||||
assert "R05 Z3 Q14" = GCODE.encode({"14", {:report_axis_state, [z: :cruise]}})
|
||||
assert "R05 Z4 Q14" = GCODE.encode({"14", {:report_axis_state, [z: :decelerate]}})
|
||||
assert "R05 Z5 Q14" = GCODE.encode({"14", {:report_axis_state, [z: :stop]}})
|
||||
assert "R05 Z6 Q14" = GCODE.encode({"14", {:report_axis_state, [z: :crawl]}})
|
||||
end
|
||||
|
||||
test "calibration" do
|
||||
assert {nil, {:report_calibration_state, [x: :idle]}} = GCODE.decode("R06 X0")
|
||||
assert {nil, {:report_calibration_state, [x: :home]}} = GCODE.decode("R06 X1")
|
||||
assert {nil, {:report_calibration_state, [x: :end]}} = GCODE.decode("R06 X2")
|
||||
|
||||
assert {"1", {:report_calibration_state, [x: :idle]}} = GCODE.decode("R06 X0 Q1")
|
||||
assert {"1", {:report_calibration_state, [x: :home]}} = GCODE.decode("R06 X1 Q1")
|
||||
assert {"1", {:report_calibration_state, [x: :end]}} = GCODE.decode("R06 X2 Q1")
|
||||
|
||||
assert "R06 X0" = GCODE.encode({nil, {:report_calibration_state, [x: :idle]}})
|
||||
assert "R06 X1" = GCODE.encode({nil, {:report_calibration_state, [x: :home]}})
|
||||
assert "R06 X2" = GCODE.encode({nil, {:report_calibration_state, [x: :end]}})
|
||||
|
||||
assert "R06 X0 Q1" = GCODE.encode({"1", {:report_calibration_state, [x: :idle]}})
|
||||
assert "R06 X1 Q1" = GCODE.encode({"1", {:report_calibration_state, [x: :home]}})
|
||||
assert "R06 X2 Q1" = GCODE.encode({"1", {:report_calibration_state, [x: :end]}})
|
||||
|
||||
assert {nil, {:report_calibration_state, [y: :idle]}} = GCODE.decode("R06 Y0")
|
||||
assert {nil, {:report_calibration_state, [y: :home]}} = GCODE.decode("R06 Y1")
|
||||
assert {nil, {:report_calibration_state, [y: :end]}} = GCODE.decode("R06 Y2")
|
||||
|
||||
assert {"1", {:report_calibration_state, [y: :idle]}} = GCODE.decode("R06 Y0 Q1")
|
||||
assert {"1", {:report_calibration_state, [y: :home]}} = GCODE.decode("R06 Y1 Q1")
|
||||
assert {"1", {:report_calibration_state, [y: :end]}} = GCODE.decode("R06 Y2 Q1")
|
||||
|
||||
assert "R06 Y0" = GCODE.encode({nil, {:report_calibration_state, [y: :idle]}})
|
||||
assert "R06 Y1" = GCODE.encode({nil, {:report_calibration_state, [y: :home]}})
|
||||
assert "R06 Y2" = GCODE.encode({nil, {:report_calibration_state, [y: :end]}})
|
||||
|
||||
assert "R06 Y0 Q1" = GCODE.encode({"1", {:report_calibration_state, [y: :idle]}})
|
||||
assert "R06 Y1 Q1" = GCODE.encode({"1", {:report_calibration_state, [y: :home]}})
|
||||
assert "R06 Y2 Q1" = GCODE.encode({"1", {:report_calibration_state, [y: :end]}})
|
||||
|
||||
assert {nil, {:report_calibration_state, [z: :idle]}} = GCODE.decode("R06 Z0")
|
||||
assert {nil, {:report_calibration_state, [z: :home]}} = GCODE.decode("R06 Z1")
|
||||
assert {nil, {:report_calibration_state, [z: :end]}} = GCODE.decode("R06 Z2")
|
||||
|
||||
assert {"1", {:report_calibration_state, [z: :idle]}} = GCODE.decode("R06 Z0 Q1")
|
||||
assert {"1", {:report_calibration_state, [z: :home]}} = GCODE.decode("R06 Z1 Q1")
|
||||
assert {"1", {:report_calibration_state, [z: :end]}} = GCODE.decode("R06 Z2 Q1")
|
||||
|
||||
assert "R06 Z0" = GCODE.encode({nil, {:report_calibration_state, [z: :idle]}})
|
||||
assert "R06 Z1" = GCODE.encode({nil, {:report_calibration_state, [z: :home]}})
|
||||
assert "R06 Z2" = GCODE.encode({nil, {:report_calibration_state, [z: :end]}})
|
||||
|
||||
assert "R06 Z0 Q1" = GCODE.encode({"1", {:report_calibration_state, [z: :idle]}})
|
||||
assert "R06 Z1 Q1" = GCODE.encode({"1", {:report_calibration_state, [z: :home]}})
|
||||
assert "R06 Z2 Q1" = GCODE.encode({"1", {:report_calibration_state, [z: :end]}})
|
||||
end
|
||||
|
||||
test "retry" do
|
||||
assert {nil, {:report_retry, []}} = GCODE.decode("R07")
|
||||
assert {"100", {:report_retry, []}} = GCODE.decode("R07 Q100")
|
||||
|
||||
assert "R07" = GCODE.encode({nil, {:report_retry, []}})
|
||||
assert "R07 Q100" = GCODE.encode({"100", {:report_retry, []}})
|
||||
end
|
||||
|
||||
test "echo" do
|
||||
assert {nil, {:report_echo, ["ABC"]}} = GCODE.decode("R08 * ABC *")
|
||||
assert "R08 * ABC *" = GCODE.encode({nil, {:report_echo, ["ABC"]}})
|
||||
end
|
||||
|
||||
test "invalid" do
|
||||
assert {nil, {:report_invalid, []}} = GCODE.decode("R09")
|
||||
assert {"50", {:report_invalid, []}} = GCODE.decode("R09 Q50")
|
||||
|
||||
assert "R09" = GCODE.encode({nil, {:report_invalid, []}})
|
||||
assert "R09 Q50" = GCODE.encode({"50", {:report_invalid, []}})
|
||||
end
|
||||
|
||||
test "home complete" do
|
||||
assert {nil, {:report_home_complete, [:x]}} = GCODE.decode("R11")
|
||||
assert {"22", {:report_home_complete, [:x]}} = GCODE.decode("R11 Q22")
|
||||
|
||||
assert {nil, {:report_home_complete, [:y]}} = GCODE.decode("R12")
|
||||
assert {"22", {:report_home_complete, [:y]}} = GCODE.decode("R12 Q22")
|
||||
|
||||
assert {nil, {:report_home_complete, [:z]}} = GCODE.decode("R13")
|
||||
assert {"22", {:report_home_complete, [:z]}} = GCODE.decode("R13 Q22")
|
||||
end
|
||||
|
||||
test "position change" do
|
||||
assert {nil, {:report_position_change, [{:x, 200.0}]}} = GCODE.decode("R15 X200")
|
||||
assert {"33", {:report_position_change, [{:x, 200.0}]}} = GCODE.decode("R15 X200 Q33")
|
||||
|
||||
assert {nil, {:report_position_change, [{:y, 200.0}]}} = GCODE.decode("R16 Y200")
|
||||
assert {"33", {:report_position_change, [{:y, 200.0}]}} = GCODE.decode("R17 Y200 Q33")
|
||||
|
||||
assert {nil, {:report_position_change, [{:z, 200.0}]}} = GCODE.decode("R15 Z200")
|
||||
assert {"33", {:report_position_change, [{:z, 200.0}]}} = GCODE.decode("R15 Z200 Q33")
|
||||
end
|
||||
|
||||
test "paramater report complete" do
|
||||
assert {nil, {:report_paramaters_complete, []}} = GCODE.decode("R20")
|
||||
assert {"66", {:report_paramaters_complete, []}} = GCODE.decode("R20 Q66")
|
||||
end
|
||||
|
||||
test "axis timeout" do
|
||||
assert {nil, {:report_axis_timeout, [:x]}} = GCODE.decode("R71")
|
||||
assert {"22", {:report_axis_timeout, [:x]}} = GCODE.decode("R71 Q22")
|
||||
|
||||
assert {nil, {:report_axis_timeout, [:y]}} = GCODE.decode("R72")
|
||||
assert {"22", {:report_axis_timeout, [:y]}} = GCODE.decode("R72 Q22")
|
||||
|
||||
assert {nil, {:report_axis_timeout, [:z]}} = GCODE.decode("R73")
|
||||
assert {"22", {:report_axis_timeout, [:z]}} = GCODE.decode("R73 Q22")
|
||||
end
|
||||
|
||||
test "end stops" do
|
||||
assert {nil, {:report_end_stops, [xa: 1, xb: 0, ya: 0, yb: 1, za: 1, zb: 0]}} =
|
||||
GCODE.decode("R81 XA1 XB0 YA0 YB1 ZA1 ZB0")
|
||||
end
|
||||
|
||||
test "position" do
|
||||
assert {nil, {:report_position, [{:x, 100.0}, {:y, 200.0}, {:z, 400.0}]}} =
|
||||
GCODE.decode("R82 X100 Y200 Z400")
|
||||
|
||||
assert {"1", {:report_position, [{:x, 100.0}, {:y, 200.0}, {:z, 400.0}]}} =
|
||||
GCODE.decode("R82 X100 Y200 Z400 Q1")
|
||||
|
||||
assert {nil, {:report_position, [{:x, 100.0}, {:z, 12.0}]}} = GCODE.decode("R82 X100 Z12")
|
||||
assert {nil, {:report_position, [{:z, 5.0}]}} = GCODE.decode("R82 Z5")
|
||||
end
|
||||
|
||||
test "version" do
|
||||
assert {nil, {:report_software_version, ["6.5.0.G"]}} = GCODE.decode("R83 6.5.0.G")
|
||||
assert {"900", {:report_software_version, ["6.5.0.G"]}} = GCODE.decode("R83 6.5.0.G Q900")
|
||||
|
||||
assert "R83 6.5.0.G" = GCODE.encode({nil, {:report_software_version, ["6.5.0.G"]}})
|
||||
assert "R83 6.5.0.G Q900" = GCODE.encode({"900", {:report_software_version, ["6.5.0.G"]}})
|
||||
end
|
||||
|
||||
test "encoders" do
|
||||
assert {nil, {:report_encoders_scaled, [{:x, 100.0}, {:y, 200.0}, {:z, 400.0}]}} =
|
||||
GCODE.decode("R84 X100 Y200 Z400")
|
||||
|
||||
assert {"1", {:report_encoders_scaled, [{:x, 100.0}, {:y, 200.0}, {:z, 400.0}]}} =
|
||||
GCODE.decode("R84 X100 Y200 Z400 Q1")
|
||||
|
||||
assert {nil, {:report_encoders_raw, [{:x, 100.0}, {:y, 200.0}, {:z, 400.0}]}} =
|
||||
GCODE.decode("R85 X100 Y200 Z400")
|
||||
|
||||
assert {"1", {:report_encoders_raw, [{:x, 100.0}, {:y, 200.0}, {:z, 400.0}]}} =
|
||||
GCODE.decode("R85 X100 Y200 Z400 Q1")
|
||||
end
|
||||
|
||||
test "emergency lock" do
|
||||
assert {nil, {:report_emergency_lock, []}} = GCODE.decode("R87")
|
||||
assert {"999", {:report_emergency_lock, []}} = GCODE.decode("R87 Q999")
|
||||
|
||||
assert "R87" = GCODE.encode({nil, {:report_emergency_lock, []}})
|
||||
assert "R87 Q999" = GCODE.encode({"999", {:report_emergency_lock, []}})
|
||||
end
|
||||
|
||||
test "debug message" do
|
||||
assert {nil, {:report_debug_message, ["Hello, World!"]}} = GCODE.decode("R99 Hello, World!")
|
||||
assert "R99 Hello, World!" = GCODE.encode({nil, {:report_debug_message, ["Hello, World!"]}})
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
ExUnit.start()
|
|
@ -1,5 +1,10 @@
|
|||
[
|
||||
import_deps: [:ecto],
|
||||
inputs: ["*.{ex,exs}", "{config,priv,test}/**/*.{ex,exs}"],
|
||||
inputs: [
|
||||
"*.{ex,exs}",
|
||||
"{config,priv,test}/**/*.{ex,exs}",
|
||||
"lib/celery_script/**/*.{ex,exs}",
|
||||
"platform/**/*.{ex,exs}"
|
||||
],
|
||||
subdirectories: ["priv/*/migrations"]
|
||||
]
|
||||
|
|
Binary file not shown.
|
@ -1,28 +1,14 @@
|
|||
# This file is responsible for configuring your application
|
||||
# and its dependencies with the aid of the Mix.Config module.
|
||||
#
|
||||
# This configuration file is loaded before any dependency and
|
||||
# is restricted to this project.
|
||||
use Mix.Config
|
||||
|
||||
# Mix configs.
|
||||
target = Mix.Project.config()[:target]
|
||||
env = Mix.env()
|
||||
config :farmbot_core, Farmbot.AssetWorker.Farmbot.Asset.FarmEvent, checkup_time_ms: 10_000
|
||||
|
||||
config :logger, [
|
||||
utc_log: true,
|
||||
handle_otp_reports: true,
|
||||
handle_sasl_reports: true,
|
||||
backends: [:console]
|
||||
]
|
||||
config :farmbot_core, Farmbot.AssetWorker.Farmbot.Asset.FarmwareInstallation,
|
||||
error_retry_time_ms: 30_000,
|
||||
install_dir: "/tmp/farmware"
|
||||
|
||||
# Randomly picked 300 megabytes.
|
||||
# 3964928 bytes == ~4 megabytes in sqlite3
|
||||
# 9266 logs = ~4 megabytes
|
||||
# 4 logs * 75 = 300 megabytes
|
||||
# 9266 logs * 75 = 694950 logs
|
||||
# This will trim 175000 logs (25%) every time it gets to the max logs.
|
||||
config :logger_backend_ecto, max_logs: 700000
|
||||
config :farmbot_core, Elixir.Farmbot.AssetWorker.Farmbot.Asset.PinBinding,
|
||||
gpio_handler: Farmbot.PinBindingWorker.StubGPIOHandler,
|
||||
error_retry_time_ms: 30_000
|
||||
|
||||
# Customize non-Elixir parts of the firmware. See
|
||||
# https://hexdocs.pm/nerves/advanced-configuration.html for details.
|
||||
|
@ -30,74 +16,51 @@ config :nerves, :firmware,
|
|||
rootfs_overlay: "rootfs_overlay",
|
||||
provisioning: :nerves_hub
|
||||
|
||||
# Use shoehorn to start the main application. See the shoehorn
|
||||
# docs for separating out critical OTP applications such as those
|
||||
# involved with firmware updates.
|
||||
config :shoehorn,
|
||||
init: [:nerves_runtime, :nerves_init_gadget],
|
||||
handler: Farmbot.OS.ShoehornHandler,
|
||||
app: Mix.Project.config()[:app]
|
||||
|
||||
# Stop lager redirecting :error_logger messages
|
||||
config :lager, :error_logger_redirect, false
|
||||
|
||||
# Stop lager removing Logger's :error_logger handler
|
||||
config :lager, :error_logger_whitelist, []
|
||||
|
||||
# Stop lager writing a crash log
|
||||
config :lager, :crash_log, false
|
||||
|
||||
# Use LagerLogger as lager's only handler.
|
||||
config :lager, :handlers, []
|
||||
|
||||
config :ssl, protocol_version: :"tlsv1.2"
|
||||
|
||||
# Disable tzdata autoupdates because it tries to dl the update file
|
||||
# Before we have network or ntp.
|
||||
config :tzdata, :autoupdate, :disabled
|
||||
config :tesla, Tesla.Middleware.Logger,
|
||||
format: "$method $url ====> $status / time=$time",
|
||||
debug: false
|
||||
|
||||
config :farmbot_core, :behaviour,
|
||||
firmware_handler: Farmbot.Firmware.StubHandler,
|
||||
leds_handler: Farmbot.Leds.StubHandler,
|
||||
pin_binding_handler: Farmbot.PinBinding.StubHandler,
|
||||
celery_script_io_layer: Farmbot.OS.IOLayer,
|
||||
json_parser: Farmbot.JSON.JasonParser
|
||||
config :farmbot_core, Farmbot.AssetMonitor, checkup_time_ms: 30_000
|
||||
|
||||
config :farmbot_core,
|
||||
expected_fw_versions: ["6.4.0.F", "6.4.0.R", "6.4.0.G"],
|
||||
default_firmware_io_logs: false,
|
||||
default_server: "https://my.farm.bot",
|
||||
default_currently_on_beta: String.contains?(to_string(:os.cmd('git rev-parse --abbrev-ref HEAD')), "beta"),
|
||||
firmware_io_logs: false,
|
||||
farm_event_debug_log: false
|
||||
default_currently_on_beta:
|
||||
String.contains?(to_string(:os.cmd('git rev-parse --abbrev-ref HEAD')), "beta")
|
||||
|
||||
config :farmbot_ext, :behaviour,
|
||||
authorization: Farmbot.Bootstrap.Authorization
|
||||
# Configure Farmbot Behaviours.
|
||||
config :farmbot_core, :behaviour,
|
||||
leds_handler: Farmbot.Leds.StubHandler,
|
||||
celery_script_io_layer: Farmbot.OS.IOLayer,
|
||||
json_parser: Farmbot.JSON.JasonParser
|
||||
|
||||
config :farmbot_ext, :behaviour, authorization: Farmbot.Bootstrap.Authorization
|
||||
config :ecto, json_library: Farmbot.JSON
|
||||
|
||||
config :farmbot_os,
|
||||
ecto_repos: [Farmbot.Config.Repo, Farmbot.Logger.Repo, Farmbot.Asset.Repo]
|
||||
|
||||
config :farmbot_os, :builtins,
|
||||
sequence: [
|
||||
emergency_lock: -1,
|
||||
emergency_unlock: -2,
|
||||
sync: -3,
|
||||
reboot: -4,
|
||||
power_off: -5
|
||||
],
|
||||
pin_binding: [
|
||||
emergency_lock: -1,
|
||||
emergency_unlock: -2,
|
||||
config :farmbot_core, Farmbot.Config.Repo,
|
||||
adapter: Sqlite.Ecto2,
|
||||
loggers: [],
|
||||
database: "config.#{Mix.env()}.db",
|
||||
priv: "../farmbot_core/priv/config"
|
||||
|
||||
config :farmbot_core, Farmbot.Logger.Repo,
|
||||
adapter: Sqlite.Ecto2,
|
||||
loggers: [],
|
||||
database: "logger.#{Mix.env()}.db",
|
||||
priv: "../farmbot_core/priv/logger"
|
||||
|
||||
config :farmbot_core, Farmbot.Asset.Repo,
|
||||
adapter: Sqlite.Ecto2,
|
||||
loggers: [],
|
||||
database: "asset.#{Mix.env()}.db",
|
||||
priv: "../farmbot_core/priv/asset"
|
||||
|
||||
config :farmbot_os, Farmbot.OS.FileSystem, data_path: "/tmp/farmbot"
|
||||
config :farmbot_os, Farmbot.System, system_tasks: Farmbot.Host.SystemTasks
|
||||
|
||||
config :farmbot_os, Farmbot.Platform.Supervisor,
|
||||
platform_children: [
|
||||
Farmbot.Host.Configurator
|
||||
]
|
||||
|
||||
case target do
|
||||
"host" ->
|
||||
import_config("host/#{env}.exs")
|
||||
|
||||
_ ->
|
||||
import_config("target/#{env}.exs")
|
||||
if File.exists?("config/target/#{target}.exs"),
|
||||
do: import_config("target/#{target}.exs")
|
||||
end
|
||||
import_config("lagger.exs")
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue