From f2b8abd69274f6bba00a85387c20f6b7cf97cbed Mon Sep 17 00:00:00 2001 From: Connor Rigby Date: Thu, 15 Nov 2018 10:24:09 -0800 Subject: [PATCH] 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 tests --- Makefile | 9 +- farmbot_celery_script/.formatter.exs | 2 +- .../lib/farmbot_celery_script/run_time.ex | 44 +- .../run_time/farm_proc.ex | 9 +- .../run_time/instruction_set.ex | 35 +- .../run_time/proc_storage.ex | 2 +- .../run_time/resolver.ex | 9 +- .../farmbot_celery_script/ast/slicer_test.exs | 3 +- .../run_time/farm_proc_test.exs | 22 +- .../run_time/farmware_test.exs | 21 +- .../run_time/resolver_test.exs | 11 +- .../farmbot_celery_script/run_time_test.exs | 12 +- farmbot_core/.formatter.exs | 6 +- farmbot_core/config/config.exs | 17 +- farmbot_core/config/dev.exs | 1 + farmbot_core/config/logger.exs | 6 +- farmbot_core/lib/asset.ex | 41 +- farmbot_core/lib/asset/fbos_config.ex | 6 + farmbot_core/lib/asset/storage_auth.ex | 13 +- farmbot_core/lib/asset/supervisor.ex | 10 +- farmbot_core/lib/asset_monitor.ex | 15 +- .../lib/asset_workers/device_worker.ex | 18 + .../lib/asset_workers/farmware_env_worker.ex | 17 + .../lib/asset_workers/fbos_config_worker.ex | 68 ++ .../lib/asset_workers/peripheral_worker.ex | 8 +- .../lib/asset_workers/pin_binding_worker.ex | 152 +++- .../pin_binding_worker/stub_gpio_handler.ex | 31 + farmbot_core/lib/bot_state/bot_state.ex | 424 +++++------ farmbot_core/lib/bot_state/configuration.ex | 33 - .../lib/bot_state/informational_settings.ex | 23 - farmbot_core/lib/bot_state/job_progress.ex | 27 +- farmbot_core/lib/bot_state/location_data.ex | 8 - farmbot_core/lib/bot_state/mcu_params.ex | 97 --- farmbot_core/lib/bot_state/pin.ex | 4 - farmbot_core/lib/bot_state_ng.ex | 94 +++ .../lib/bot_state_ng/configuration.ex | 64 ++ .../bot_state_ng/informational_settings.ex | 81 +++ .../lib/bot_state_ng/location_data.ex | 72 ++ farmbot_core/lib/bot_state_ng/mcu_params.ex | 292 ++++++++ farmbot_core/lib/bot_state_ng/process_info.ex | 27 + farmbot_core/lib/celery_script/io_layer.ex | 2 +- .../lib/config_storage/config_storage.ex | 6 - farmbot_core/lib/farmbot_core.ex | 7 +- farmbot_core/lib/farmware_runtime.ex | 1 - farmbot_core/lib/firmware/command.ex | 38 - farmbot_core/lib/firmware/completion_logs.ex | 83 --- farmbot_core/lib/firmware/estop_timer.ex | 84 +-- farmbot_core/lib/firmware/firmware.ex | 656 ------------------ .../lib/firmware/firmware_side_effects.ex | 164 +++++ .../lib/firmware/firmware_supervisor.ex | 51 ++ farmbot_core/lib/firmware/gcode/param.ex | 284 -------- farmbot_core/lib/firmware/gcode/parser.ex | 192 ----- farmbot_core/lib/firmware/handler.ex | 86 --- farmbot_core/lib/firmware/supervisor.ex | 24 - farmbot_core/lib/firmware/utils.ex | 39 -- farmbot_core/lib/firmware/vec3.ex | 30 - farmbot_core/lib/log_storage/log.ex | 18 +- farmbot_core/lib/log_storage/logger.ex | 34 +- farmbot_core/lib/log_storage/supervisor.ex | 1 + farmbot_core/lib/registry.ex | 45 -- farmbot_core/mix.exs | 3 +- ...181019180816_create_fbos_configs_table.exs | 2 + .../20171208205940_add_firmware_io_log.exs | 6 +- .../20180620135642_add_log_buffer.exs | 3 +- .../asset_workers/fbos_config_worker_test.exs | 45 ++ farmbot_core/test/bot_state_ng_test.exs | 94 +++ farmbot_core/test/bot_state_test.exs | 42 ++ farmbot_core/test/botstate_test.exs | 12 - farmbot_core/test/firmware/estop_timer.ex | 34 + farmbot_core/test/logger_test.exs | 19 + farmbot_ext/config/farmbot_core.exs | 18 +- farmbot_ext/lib/amqp/bot_state_transport.ex | 46 +- farmbot_ext/lib/amqp/log_transport.ex | 55 +- farmbot_ext/lib/api.ex | 20 +- farmbot_ext/lib/image_uploader.ex | 1 - farmbot_ext/lib/protocols.ex | 16 +- farmbot_ext/test/farmbot_ext_test.exs | 8 - farmbot_firmware/.formatter.exs | 4 + farmbot_firmware/.gitignore | 24 + farmbot_firmware/README.md | 21 + farmbot_firmware/config/config.exs | 1 + farmbot_firmware/lib/farmbot_firmware.ex | 479 +++++++++++++ .../lib/farmbot_firmware/command.ex | 50 ++ .../lib/farmbot_firmware/gcode.ex | 142 ++++ .../lib/farmbot_firmware/gcode/decoder.ex | 185 +++++ .../lib/farmbot_firmware/gcode/encoder.ex | 142 ++++ .../lib/farmbot_firmware/param.ex | 197 ++++++ .../lib/farmbot_firmware/request.ex | 139 ++++ .../lib/farmbot_firmware/side_effects.ex | 30 + .../lib/farmbot_firmware/stub_side_effects.ex | 126 ++++ .../transports/stub_transport.ex | 356 ++++++++++ .../transports/uart_transport.ex | 45 ++ farmbot_firmware/mix.exs | 41 ++ farmbot_firmware/mix.lock | 42 ++ .../test/farmbot_firmware_test.exs | 4 + farmbot_firmware/test/gcode_test.exs | 315 +++++++++ farmbot_firmware/test/test_helper.exs | 1 + farmbot_os/.formatter.exs | 7 +- farmbot_os/config.test.db-journal | Bin 0 -> 8720 bytes farmbot_os/config/config.exs | 125 ++-- farmbot_os/config/host/auth_secret_ci.exs | 35 - .../config/host/auth_secret_template.exs | 9 - farmbot_os/config/host/test.exs | 15 - farmbot_os/config/lagger.exs | 5 + farmbot_os/config/target/rpi3.exs | 3 - farmbot_os/lib/boot_state.ex | 5 +- farmbot_os/lib/celery_script/_if.ex | 74 ++ farmbot_os/lib/celery_script/ast.ex | 2 - farmbot_os/lib/celery_script/calibrate.ex | 22 + farmbot_os/lib/celery_script/dump_info.ex | 46 ++ farmbot_os/lib/celery_script/find_home.ex | 39 ++ farmbot_os/lib/celery_script/home.ex | 28 + farmbot_os/lib/celery_script/io_layer.ex | 175 +++-- farmbot_os/lib/celery_script/io_layer/_if.ex | 69 -- .../lib/celery_script/io_layer/farmware.ex | 47 -- .../lib/celery_script/io_layer/find_home.ex | 41 -- .../celery_script/io_layer/move_absolute.ex | 55 -- .../lib/celery_script/io_layer/read_pin.ex | 84 --- farmbot_os/lib/celery_script/io_layer/sync.ex | 65 -- .../lib/celery_script/io_layer/toggle_pin.ex | 33 - .../lib/celery_script/io_layer/write_pin.ex | 78 --- farmbot_os/lib/celery_script/move_absolute.ex | 18 + farmbot_os/lib/celery_script/move_relative.ex | 37 + farmbot_os/lib/celery_script/read_pin.ex | 16 + farmbot_os/lib/celery_script/send_message.ex | 65 ++ .../lib/celery_script/set_servo_angle.ex | 7 + farmbot_os/lib/celery_script/sync.ex | 32 + farmbot_os/lib/celery_script/toggle_pin.ex | 31 + farmbot_os/lib/celery_script/write_pin.ex | 15 + farmbot_os/lib/celery_script/zero.ex | 22 + farmbot_os/lib/farmbot_os.ex | 2 - farmbot_os/lib/farmware_migration.ex | 100 --- farmbot_os/lib/filesystem.ex | 9 + farmbot_os/lib/init/fs_checkup.ex | 3 +- .../lib/mix/tasks.farmbot/factory_reset.ex | 10 - farmbot_os/lib/platform_supervisor.ex | 2 +- farmbot_os/lib/system.ex | 11 +- farmbot_os/lib/updates/updates.ex | 352 ---------- farmbot_os/mix.exs | 27 +- farmbot_os/mix.lock.host | 16 +- farmbot_os/mix.lock.rpi | 102 +++ farmbot_os/mix.lock.rpi0 | 90 +++ farmbot_os/mix.lock.rpi3 | 24 +- farmbot_os/platform/host/configurator.ex | 16 +- .../target/configurator/captive_portal.ex | 47 +- .../target/configurator/configurator.ex | 34 +- .../platform/target/configurator/router.ex | 235 +++++-- .../target/gpio/elixir_ale_gpio_handler.ex | 45 ++ .../platform/target/gpio/leds_ale_handler.ex | 6 +- .../target/info_workers/disk_usage_worker.ex | 28 +- .../info_workers/memory_usage_worker.ex | 19 +- .../target/info_workers/soc_temp_worker.ex | 22 +- .../target/info_workers/uptime_worker.ex | 17 +- .../platform/target/network/dns_task.ex | 35 +- .../target/network/info_supervisor.ex | 13 +- .../platform/target/network/info_worker.ex | 16 +- farmbot_os/platform/target/network/manager.ex | 75 +- farmbot_os/platform/target/network/network.ex | 53 +- .../target/network/network_not_found_timer.ex | 19 +- .../platform/target/network/scan_result.ex | 31 +- .../platform/target/network/tzdata_task.ex | 20 +- .../platform/target/network/wait_for_time.ex | 25 +- farmbot_os/platform/target/ssh_console.ex | 11 +- farmbot_os/platform/target/uevent.ex | 17 +- 164 files changed, 5636 insertions(+), 3648 deletions(-) create mode 100644 farmbot_core/lib/asset_workers/device_worker.ex create mode 100644 farmbot_core/lib/asset_workers/farmware_env_worker.ex create mode 100644 farmbot_core/lib/asset_workers/fbos_config_worker.ex create mode 100644 farmbot_core/lib/asset_workers/pin_binding_worker/stub_gpio_handler.ex delete mode 100644 farmbot_core/lib/bot_state/configuration.ex delete mode 100644 farmbot_core/lib/bot_state/informational_settings.ex delete mode 100644 farmbot_core/lib/bot_state/location_data.ex delete mode 100644 farmbot_core/lib/bot_state/mcu_params.ex delete mode 100644 farmbot_core/lib/bot_state/pin.ex create mode 100644 farmbot_core/lib/bot_state_ng.ex create mode 100644 farmbot_core/lib/bot_state_ng/configuration.ex create mode 100644 farmbot_core/lib/bot_state_ng/informational_settings.ex create mode 100644 farmbot_core/lib/bot_state_ng/location_data.ex create mode 100644 farmbot_core/lib/bot_state_ng/mcu_params.ex create mode 100644 farmbot_core/lib/bot_state_ng/process_info.ex delete mode 100644 farmbot_core/lib/firmware/command.ex delete mode 100644 farmbot_core/lib/firmware/completion_logs.ex delete mode 100644 farmbot_core/lib/firmware/firmware.ex create mode 100644 farmbot_core/lib/firmware/firmware_side_effects.ex create mode 100644 farmbot_core/lib/firmware/firmware_supervisor.ex delete mode 100644 farmbot_core/lib/firmware/gcode/param.ex delete mode 100644 farmbot_core/lib/firmware/gcode/parser.ex delete mode 100644 farmbot_core/lib/firmware/handler.ex delete mode 100644 farmbot_core/lib/firmware/supervisor.ex delete mode 100644 farmbot_core/lib/firmware/utils.ex delete mode 100644 farmbot_core/lib/firmware/vec3.ex delete mode 100644 farmbot_core/lib/registry.ex create mode 100644 farmbot_core/test/asset_workers/fbos_config_worker_test.exs create mode 100644 farmbot_core/test/bot_state_ng_test.exs create mode 100644 farmbot_core/test/bot_state_test.exs delete mode 100644 farmbot_core/test/botstate_test.exs create mode 100644 farmbot_core/test/firmware/estop_timer.ex create mode 100644 farmbot_core/test/logger_test.exs delete mode 100644 farmbot_ext/lib/image_uploader.ex delete mode 100644 farmbot_ext/test/farmbot_ext_test.exs create mode 100644 farmbot_firmware/.formatter.exs create mode 100644 farmbot_firmware/.gitignore create mode 100644 farmbot_firmware/README.md create mode 100644 farmbot_firmware/config/config.exs create mode 100644 farmbot_firmware/lib/farmbot_firmware.ex create mode 100644 farmbot_firmware/lib/farmbot_firmware/command.ex create mode 100644 farmbot_firmware/lib/farmbot_firmware/gcode.ex create mode 100644 farmbot_firmware/lib/farmbot_firmware/gcode/decoder.ex create mode 100644 farmbot_firmware/lib/farmbot_firmware/gcode/encoder.ex create mode 100644 farmbot_firmware/lib/farmbot_firmware/param.ex create mode 100644 farmbot_firmware/lib/farmbot_firmware/request.ex create mode 100644 farmbot_firmware/lib/farmbot_firmware/side_effects.ex create mode 100644 farmbot_firmware/lib/farmbot_firmware/stub_side_effects.ex create mode 100644 farmbot_firmware/lib/farmbot_firmware/transports/stub_transport.ex create mode 100644 farmbot_firmware/lib/farmbot_firmware/transports/uart_transport.ex create mode 100644 farmbot_firmware/mix.exs create mode 100644 farmbot_firmware/mix.lock create mode 100644 farmbot_firmware/test/farmbot_firmware_test.exs create mode 100644 farmbot_firmware/test/gcode_test.exs create mode 100644 farmbot_firmware/test/test_helper.exs create mode 100644 farmbot_os/config.test.db-journal delete mode 100644 farmbot_os/config/host/auth_secret_ci.exs delete mode 100644 farmbot_os/config/host/auth_secret_template.exs delete mode 100644 farmbot_os/config/host/test.exs create mode 100644 farmbot_os/config/lagger.exs delete mode 100644 farmbot_os/config/target/rpi3.exs create mode 100644 farmbot_os/lib/celery_script/_if.ex delete mode 100644 farmbot_os/lib/celery_script/ast.ex create mode 100644 farmbot_os/lib/celery_script/calibrate.ex create mode 100644 farmbot_os/lib/celery_script/dump_info.ex create mode 100644 farmbot_os/lib/celery_script/find_home.ex create mode 100644 farmbot_os/lib/celery_script/home.ex delete mode 100644 farmbot_os/lib/celery_script/io_layer/_if.ex delete mode 100644 farmbot_os/lib/celery_script/io_layer/farmware.ex delete mode 100644 farmbot_os/lib/celery_script/io_layer/find_home.ex delete mode 100644 farmbot_os/lib/celery_script/io_layer/move_absolute.ex delete mode 100644 farmbot_os/lib/celery_script/io_layer/read_pin.ex delete mode 100644 farmbot_os/lib/celery_script/io_layer/sync.ex delete mode 100644 farmbot_os/lib/celery_script/io_layer/toggle_pin.ex delete mode 100644 farmbot_os/lib/celery_script/io_layer/write_pin.ex create mode 100644 farmbot_os/lib/celery_script/move_absolute.ex create mode 100644 farmbot_os/lib/celery_script/move_relative.ex create mode 100644 farmbot_os/lib/celery_script/read_pin.ex create mode 100644 farmbot_os/lib/celery_script/send_message.ex create mode 100644 farmbot_os/lib/celery_script/set_servo_angle.ex create mode 100644 farmbot_os/lib/celery_script/sync.ex create mode 100644 farmbot_os/lib/celery_script/toggle_pin.ex create mode 100644 farmbot_os/lib/celery_script/write_pin.ex create mode 100644 farmbot_os/lib/celery_script/zero.ex delete mode 100644 farmbot_os/lib/farmware_migration.ex create mode 100644 farmbot_os/lib/filesystem.ex delete mode 100644 farmbot_os/lib/mix/tasks.farmbot/factory_reset.ex delete mode 100644 farmbot_os/lib/updates/updates.ex create mode 100644 farmbot_os/mix.lock.rpi create mode 100644 farmbot_os/mix.lock.rpi0 create mode 100644 farmbot_os/platform/target/gpio/elixir_ale_gpio_handler.ex diff --git a/Makefile b/Makefile index 17c27947..884fe39d 100644 --- a/Makefile +++ b/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 diff --git a/farmbot_celery_script/.formatter.exs b/farmbot_celery_script/.formatter.exs index 964fa581..79fe033f 100644 --- a/farmbot_celery_script/.formatter.exs +++ b/farmbot_celery_script/.formatter.exs @@ -1,3 +1,3 @@ [ - inputs: ["*.{ex,exs}", "{config,priv,lib,test}/**/*.{ex,exs}"], + inputs: ["*.{ex,exs}", "{config,priv,lib,test}/**/*.{ex,exs}"] ] diff --git a/farmbot_celery_script/lib/farmbot_celery_script/run_time.ex b/farmbot_celery_script/lib/farmbot_celery_script/run_time.ex index 41b97032..296c2af5 100644 --- a/farmbot_celery_script/lib/farmbot_celery_script/run_time.ex +++ b/farmbot_celery_script/lib/farmbot_celery_script/run_time.ex @@ -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 diff --git a/farmbot_celery_script/lib/farmbot_celery_script/run_time/farm_proc.ex b/farmbot_celery_script/lib/farmbot_celery_script/run_time/farm_proc.ex index fe3150d4..ba9ff089 100644 --- a/farmbot_celery_script/lib/farmbot_celery_script/run_time/farm_proc.ex +++ b/farmbot_celery_script/lib/farmbot_celery_script/run_time/farm_proc.ex @@ -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 diff --git a/farmbot_celery_script/lib/farmbot_celery_script/run_time/instruction_set.ex b/farmbot_celery_script/lib/farmbot_celery_script/run_time/instruction_set.ex index 39c5db65..f320deab 100644 --- a/farmbot_celery_script/lib/farmbot_celery_script/run_time/instruction_set.ex +++ b/farmbot_celery_script/lib/farmbot_celery_script/run_time/instruction_set.ex @@ -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) diff --git a/farmbot_celery_script/lib/farmbot_celery_script/run_time/proc_storage.ex b/farmbot_celery_script/lib/farmbot_celery_script/run_time/proc_storage.ex index 6666129e..1cb7c2c5 100644 --- a/farmbot_celery_script/lib/farmbot_celery_script/run_time/proc_storage.ex +++ b/farmbot_celery_script/lib/farmbot_celery_script/run_time/proc_storage.ex @@ -2,7 +2,7 @@ defmodule Farmbot.CeleryScript.RunTime.ProcStorage do @moduledoc """ Process wrapper around CircularList """ - + alias Farmbot.CeleryScript.RunTime.FarmProc @opaque proc_storage :: pid diff --git a/farmbot_celery_script/lib/farmbot_celery_script/run_time/resolver.ex b/farmbot_celery_script/lib/farmbot_celery_script/run_time/resolver.ex index 5317c6b8..fdaf659d 100644 --- a/farmbot_celery_script/lib/farmbot_celery_script/run_time/resolver.ex +++ b/farmbot_celery_script/lib/farmbot_celery_script/run_time/resolver.ex @@ -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( diff --git a/farmbot_celery_script/test/farmbot_celery_script/ast/slicer_test.exs b/farmbot_celery_script/test/farmbot_celery_script/ast/slicer_test.exs index 1d29324f..a3124670 100644 --- a/farmbot_celery_script/test/farmbot_celery_script/ast/slicer_test.exs +++ b/farmbot_celery_script/test/farmbot_celery_script/ast/slicer_test.exs @@ -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]" diff --git a/farmbot_celery_script/test/farmbot_celery_script/run_time/farm_proc_test.exs b/farmbot_celery_script/test/farmbot_celery_script/run_time/farm_proc_test.exs index 16c8138a..18357779 100644 --- a/farmbot_celery_script/test/farmbot_celery_script/run_time/farm_proc_test.exs +++ b/farmbot_celery_script/test/farmbot_celery_script/run_time/farm_proc_test.exs @@ -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 diff --git a/farmbot_celery_script/test/farmbot_celery_script/run_time/farmware_test.exs b/farmbot_celery_script/test/farmbot_celery_script/run_time/farmware_test.exs index bc4dc34f..37db0c3a 100644 --- a/farmbot_celery_script/test/farmbot_celery_script/run_time/farmware_test.exs +++ b/farmbot_celery_script/test/farmbot_celery_script/run_time/farmware_test.exs @@ -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 diff --git a/farmbot_celery_script/test/farmbot_celery_script/run_time/resolver_test.exs b/farmbot_celery_script/test/farmbot_celery_script/run_time/resolver_test.exs index cb2fd981..47aa4e4f 100644 --- a/farmbot_celery_script/test/farmbot_celery_script/run_time/resolver_test.exs +++ b/farmbot_celery_script/test/farmbot_celery_script/run_time/resolver_test.exs @@ -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 diff --git a/farmbot_celery_script/test/farmbot_celery_script/run_time_test.exs b/farmbot_celery_script/test/farmbot_celery_script/run_time_test.exs index e78f049a..69907036 100644 --- a/farmbot_celery_script/test/farmbot_celery_script/run_time_test.exs +++ b/farmbot_celery_script/test/farmbot_celery_script/run_time_test.exs @@ -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) diff --git a/farmbot_core/.formatter.exs b/farmbot_core/.formatter.exs index d2245b1a..836bf1c3 100644 --- a/farmbot_core/.formatter.exs +++ b/farmbot_core/.formatter.exs @@ -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"] ] diff --git a/farmbot_core/config/config.exs b/farmbot_core/config/config.exs index 0dde0f44..efae108c 100644 --- a/farmbot_core/config/config.exs +++ b/farmbot_core/config/config.exs @@ -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 diff --git a/farmbot_core/config/dev.exs b/farmbot_core/config/dev.exs index e69de29b..8b137891 100644 --- a/farmbot_core/config/dev.exs +++ b/farmbot_core/config/dev.exs @@ -0,0 +1 @@ + diff --git a/farmbot_core/config/logger.exs b/farmbot_core/config/logger.exs index 619011a9..59afec7b 100644 --- a/farmbot_core/config/logger.exs +++ b/farmbot_core/config/logger.exs @@ -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 diff --git a/farmbot_core/lib/asset.ex b/farmbot_core/lib/asset.ex index 7e17cedc..64c3a776 100644 --- a/farmbot_core/lib/asset.ex +++ b/farmbot_core/lib/asset.ex @@ -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 diff --git a/farmbot_core/lib/asset/fbos_config.ex b/farmbot_core/lib/asset/fbos_config.ex index d4b7e94e..438fbe78 100644 --- a/farmbot_core/lib/asset/fbos_config.ex +++ b/farmbot_core/lib/asset/fbos_config.ex @@ -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, diff --git a/farmbot_core/lib/asset/storage_auth.ex b/farmbot_core/lib/asset/storage_auth.ex index c0bc6db5..29b3d852 100644 --- a/farmbot_core/lib/asset/storage_auth.ex +++ b/farmbot_core/lib/asset/storage_auth.ex @@ -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 diff --git a/farmbot_core/lib/asset/supervisor.ex b/farmbot_core/lib/asset/supervisor.ex index fe27b520..37967b18 100644 --- a/farmbot_core/lib/asset/supervisor.ex +++ b/farmbot_core/lib/asset/supervisor.ex @@ -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 ] diff --git a/farmbot_core/lib/asset_monitor.ex b/farmbot_core/lib/asset_monitor.ex index 05c0ba47..7ac754f2 100644 --- a/farmbot_core/lib/asset_monitor.ex +++ b/farmbot_core/lib/asset_monitor.ex @@ -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 diff --git a/farmbot_core/lib/asset_workers/device_worker.ex b/farmbot_core/lib/asset_workers/device_worker.ex new file mode 100644 index 00000000..27317a2c --- /dev/null +++ b/farmbot_core/lib/asset_workers/device_worker.ex @@ -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 diff --git a/farmbot_core/lib/asset_workers/farmware_env_worker.ex b/farmbot_core/lib/asset_workers/farmware_env_worker.ex new file mode 100644 index 00000000..c316c379 --- /dev/null +++ b/farmbot_core/lib/asset_workers/farmware_env_worker.ex @@ -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 diff --git a/farmbot_core/lib/asset_workers/fbos_config_worker.ex b/farmbot_core/lib/asset_workers/fbos_config_worker.ex new file mode 100644 index 00000000..39945716 --- /dev/null +++ b/farmbot_core/lib/asset_workers/fbos_config_worker.ex @@ -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 diff --git a/farmbot_core/lib/asset_workers/peripheral_worker.ex b/farmbot_core/lib/asset_workers/peripheral_worker.ex index fbc47f62..e50add0f 100644 --- a/farmbot_core/lib/asset_workers/peripheral_worker.ex +++ b/farmbot_core/lib/asset_workers/peripheral_worker.ex @@ -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 diff --git a/farmbot_core/lib/asset_workers/pin_binding_worker.ex b/farmbot_core/lib/asset_workers/pin_binding_worker.ex index bf0b96b5..dc0de88e 100644 --- a/farmbot_core/lib/asset_workers/pin_binding_worker.ex +++ b/farmbot_core/lib/asset_workers/pin_binding_worker.ex @@ -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 diff --git a/farmbot_core/lib/asset_workers/pin_binding_worker/stub_gpio_handler.ex b/farmbot_core/lib/asset_workers/pin_binding_worker/stub_gpio_handler.ex new file mode 100644 index 00000000..aac93869 --- /dev/null +++ b/farmbot_core/lib/asset_workers/pin_binding_worker/stub_gpio_handler.ex @@ -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 diff --git a/farmbot_core/lib/bot_state/bot_state.ex b/farmbot_core/lib/bot_state/bot_state.ex index f623a566..a103b79b 100644 --- a/farmbot_core/lib/bot_state/bot_state.ex +++ b/farmbot_core/lib/bot_state/bot_state.ex @@ -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 diff --git a/farmbot_core/lib/bot_state/configuration.ex b/farmbot_core/lib/bot_state/configuration.ex deleted file mode 100644 index 54700f7a..00000000 --- a/farmbot_core/lib/bot_state/configuration.ex +++ /dev/null @@ -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 diff --git a/farmbot_core/lib/bot_state/informational_settings.ex b/farmbot_core/lib/bot_state/informational_settings.ex deleted file mode 100644 index f9b07328..00000000 --- a/farmbot_core/lib/bot_state/informational_settings.ex +++ /dev/null @@ -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 diff --git a/farmbot_core/lib/bot_state/job_progress.ex b/farmbot_core/lib/bot_state/job_progress.ex index 4fbd9573..61f19f50 100644 --- a/farmbot_core/lib/bot_state/job_progress.ex +++ b/farmbot_core/lib/bot_state/job_progress.ex @@ -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 diff --git a/farmbot_core/lib/bot_state/location_data.ex b/farmbot_core/lib/bot_state/location_data.ex deleted file mode 100644 index 48b837d9..00000000 --- a/farmbot_core/lib/bot_state/location_data.ex +++ /dev/null @@ -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 diff --git a/farmbot_core/lib/bot_state/mcu_params.ex b/farmbot_core/lib/bot_state/mcu_params.ex deleted file mode 100644 index 2ee39358..00000000 --- a/farmbot_core/lib/bot_state/mcu_params.ex +++ /dev/null @@ -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 diff --git a/farmbot_core/lib/bot_state/pin.ex b/farmbot_core/lib/bot_state/pin.ex deleted file mode 100644 index 18a3fc7c..00000000 --- a/farmbot_core/lib/bot_state/pin.ex +++ /dev/null @@ -1,4 +0,0 @@ -defmodule Farmbot.BotState.Pin do - @moduledoc false - defstruct [:mode, :value] -end diff --git a/farmbot_core/lib/bot_state_ng.ex b/farmbot_core/lib/bot_state_ng.ex new file mode 100644 index 00000000..3d8d44b6 --- /dev/null +++ b/farmbot_core/lib/bot_state_ng.ex @@ -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 diff --git a/farmbot_core/lib/bot_state_ng/configuration.ex b/farmbot_core/lib/bot_state_ng/configuration.ex new file mode 100644 index 00000000..5252a2ac --- /dev/null +++ b/farmbot_core/lib/bot_state_ng/configuration.ex @@ -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 diff --git a/farmbot_core/lib/bot_state_ng/informational_settings.ex b/farmbot_core/lib/bot_state_ng/informational_settings.ex new file mode 100644 index 00000000..d4e6860e --- /dev/null +++ b/farmbot_core/lib/bot_state_ng/informational_settings.ex @@ -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 diff --git a/farmbot_core/lib/bot_state_ng/location_data.ex b/farmbot_core/lib/bot_state_ng/location_data.ex new file mode 100644 index 00000000..41e527c7 --- /dev/null +++ b/farmbot_core/lib/bot_state_ng/location_data.ex @@ -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 diff --git a/farmbot_core/lib/bot_state_ng/mcu_params.ex b/farmbot_core/lib/bot_state_ng/mcu_params.ex new file mode 100644 index 00000000..e6be129c --- /dev/null +++ b/farmbot_core/lib/bot_state_ng/mcu_params.ex @@ -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 diff --git a/farmbot_core/lib/bot_state_ng/process_info.ex b/farmbot_core/lib/bot_state_ng/process_info.ex new file mode 100644 index 00000000..1158f9c9 --- /dev/null +++ b/farmbot_core/lib/bot_state_ng/process_info.ex @@ -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 diff --git a/farmbot_core/lib/celery_script/io_layer.ex b/farmbot_core/lib/celery_script/io_layer.ex index 787fcdb1..cfa6ab6b 100644 --- a/farmbot_core/lib/celery_script/io_layer.ex +++ b/farmbot_core/lib/celery_script/io_layer.ex @@ -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. diff --git a/farmbot_core/lib/config_storage/config_storage.ex b/farmbot_core/lib/config_storage/config_storage.ex index dc476afe..fceb57b0 100644 --- a/farmbot_core/lib/config_storage/config_storage.ex +++ b/farmbot_core/lib/config_storage/config_storage.ex @@ -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 diff --git a/farmbot_core/lib/farmbot_core.ex b/farmbot_core/lib/farmbot_core.ex index ff7409ca..b9f6fb59 100644 --- a/farmbot_core/lib/farmbot_core.ex +++ b/farmbot_core/lib/farmbot_core.ex @@ -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 diff --git a/farmbot_core/lib/farmware_runtime.ex b/farmbot_core/lib/farmware_runtime.ex index c641e11c..94b89b8f 100644 --- a/farmbot_core/lib/farmware_runtime.ex +++ b/farmbot_core/lib/farmware_runtime.ex @@ -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] diff --git a/farmbot_core/lib/firmware/command.ex b/farmbot_core/lib/firmware/command.ex deleted file mode 100644 index bdce9fda..00000000 --- a/farmbot_core/lib/firmware/command.ex +++ /dev/null @@ -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 diff --git a/farmbot_core/lib/firmware/completion_logs.ex b/farmbot_core/lib/firmware/completion_logs.ex deleted file mode 100644 index 5ae51f14..00000000 --- a/farmbot_core/lib/firmware/completion_logs.ex +++ /dev/null @@ -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 diff --git a/farmbot_core/lib/firmware/estop_timer.ex b/farmbot_core/lib/firmware/estop_timer.ex index 23293c49..dfa2cdc2 100644 --- a/farmbot_core/lib/firmware/estop_timer.ex +++ b/farmbot_core/lib/firmware/estop_timer.ex @@ -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 diff --git a/farmbot_core/lib/firmware/firmware.ex b/farmbot_core/lib/firmware/firmware.ex deleted file mode 100644 index cd54bf21..00000000 --- a/farmbot_core/lib/firmware/firmware.ex +++ /dev/null @@ -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 diff --git a/farmbot_core/lib/firmware/firmware_side_effects.ex b/farmbot_core/lib/firmware/firmware_side_effects.ex new file mode 100644 index 00000000..b60e9175 --- /dev/null +++ b/farmbot_core/lib/firmware/firmware_side_effects.ex @@ -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 diff --git a/farmbot_core/lib/firmware/firmware_supervisor.ex b/farmbot_core/lib/firmware/firmware_supervisor.ex new file mode 100644 index 00000000..a274ed61 --- /dev/null +++ b/farmbot_core/lib/firmware/firmware_supervisor.ex @@ -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 diff --git a/farmbot_core/lib/firmware/gcode/param.ex b/farmbot_core/lib/firmware/gcode/param.ex deleted file mode 100644 index f57e3bdb..00000000 --- a/farmbot_core/lib/firmware/gcode/param.ex +++ /dev/null @@ -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 diff --git a/farmbot_core/lib/firmware/gcode/parser.ex b/farmbot_core/lib/firmware/gcode/parser.ex deleted file mode 100644 index 94987599..00000000 --- a/farmbot_core/lib/firmware/gcode/parser.ex +++ /dev/null @@ -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") - <> = axis_and_status - - case <> do - "0" -> {q, {:report_calibration, <>, :idle}} - "1" -> {q, {:report_calibration, <>, :home}} - "2" -> {q, {:report_calibration, <>, :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 diff --git a/farmbot_core/lib/firmware/handler.ex b/farmbot_core/lib/firmware/handler.ex deleted file mode 100644 index d3e8e942..00000000 --- a/farmbot_core/lib/firmware/handler.ex +++ /dev/null @@ -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 diff --git a/farmbot_core/lib/firmware/supervisor.ex b/farmbot_core/lib/firmware/supervisor.ex deleted file mode 100644 index ab9c7218..00000000 --- a/farmbot_core/lib/firmware/supervisor.ex +++ /dev/null @@ -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 diff --git a/farmbot_core/lib/firmware/utils.ex b/farmbot_core/lib/firmware/utils.ex deleted file mode 100644 index 73f6ed66..00000000 --- a/farmbot_core/lib/firmware/utils.ex +++ /dev/null @@ -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 diff --git a/farmbot_core/lib/firmware/vec3.ex b/farmbot_core/lib/firmware/vec3.ex deleted file mode 100644 index e1c27329..00000000 --- a/farmbot_core/lib/firmware/vec3.ex +++ /dev/null @@ -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 diff --git a/farmbot_core/lib/log_storage/log.ex b/farmbot_core/lib/log_storage/log.ex index f12ea970..9a8bc413 100644 --- a/farmbot_core/lib/log_storage/log.ex +++ b/farmbot_core/lib/log_storage/log.ex @@ -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 diff --git a/farmbot_core/lib/log_storage/logger.ex b/farmbot_core/lib/log_storage/logger.ex index 32068231..ea142c6c 100644 --- a/farmbot_core/lib/log_storage/logger.ex +++ b/farmbot_core/lib/log_storage/logger.ex @@ -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 diff --git a/farmbot_core/lib/log_storage/supervisor.ex b/farmbot_core/lib/log_storage/supervisor.ex index e0d4533a..0d560732 100644 --- a/farmbot_core/lib/log_storage/supervisor.ex +++ b/farmbot_core/lib/log_storage/supervisor.ex @@ -10,6 +10,7 @@ defmodule Farmbot.Logger.Supervisor do children = [ supervisor(Farmbot.Logger.Repo, []) ] + opts = [strategy: :one_for_all] supervise(children, opts) end diff --git a/farmbot_core/lib/registry.ex b/farmbot_core/lib/registry.ex deleted file mode 100644 index 8d1c47c5..00000000 --- a/farmbot_core/lib/registry.ex +++ /dev/null @@ -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 diff --git a/farmbot_core/mix.exs b/farmbot_core/mix.exs index 418c6fdf..01c9ae7c 100644 --- a/farmbot_core/mix.exs +++ b/farmbot_core/mix.exs @@ -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"}, diff --git a/farmbot_core/priv/asset/migrations/20181019180816_create_fbos_configs_table.exs b/farmbot_core/priv/asset/migrations/20181019180816_create_fbos_configs_table.exs index ea2dedf3..e1c475a1 100644 --- a/farmbot_core/priv/asset/migrations/20181019180816_create_fbos_configs_table.exs +++ b/farmbot_core/priv/asset/migrations/20181019180816_create_fbos_configs_table.exs @@ -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) diff --git a/farmbot_core/priv/config/migrations/20171208205940_add_firmware_io_log.exs b/farmbot_core/priv/config/migrations/20171208205940_add_firmware_io_log.exs index f0c67839..01df1b9f 100644 --- a/farmbot_core/priv/config/migrations/20171208205940_add_firmware_io_log.exs +++ b/farmbot_core/priv/config/migrations/20171208205940_add_firmware_io_log.exs @@ -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 diff --git a/farmbot_core/priv/logger/migrations/20180620135642_add_log_buffer.exs b/farmbot_core/priv/logger/migrations/20180620135642_add_log_buffer.exs index a8cc9e21..bc1fbca2 100644 --- a/farmbot_core/priv/logger/migrations/20180620135642_add_log_buffer.exs +++ b/farmbot_core/priv/logger/migrations/20180620135642_add_log_buffer.exs @@ -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) diff --git a/farmbot_core/test/asset_workers/fbos_config_worker_test.exs b/farmbot_core/test/asset_workers/fbos_config_worker_test.exs new file mode 100644 index 00000000..49f7fe7f --- /dev/null +++ b/farmbot_core/test/asset_workers/fbos_config_worker_test.exs @@ -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 diff --git a/farmbot_core/test/bot_state_ng_test.exs b/farmbot_core/test/bot_state_ng_test.exs new file mode 100644 index 00000000..b705b3fb --- /dev/null +++ b/farmbot_core/test/bot_state_ng_test.exs @@ -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 diff --git a/farmbot_core/test/bot_state_test.exs b/farmbot_core/test/bot_state_test.exs new file mode 100644 index 00000000..d7eafb9a --- /dev/null +++ b/farmbot_core/test/bot_state_test.exs @@ -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 diff --git a/farmbot_core/test/botstate_test.exs b/farmbot_core/test/botstate_test.exs deleted file mode 100644 index 2a07d1c3..00000000 --- a/farmbot_core/test/botstate_test.exs +++ /dev/null @@ -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 diff --git a/farmbot_core/test/firmware/estop_timer.ex b/farmbot_core/test/firmware/estop_timer.ex new file mode 100644 index 00000000..f5ccb5ca --- /dev/null +++ b/farmbot_core/test/firmware/estop_timer.ex @@ -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 diff --git a/farmbot_core/test/logger_test.exs b/farmbot_core/test/logger_test.exs new file mode 100644 index 00000000..60396d6f --- /dev/null +++ b/farmbot_core/test/logger_test.exs @@ -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 diff --git a/farmbot_ext/config/farmbot_core.exs b/farmbot_ext/config/farmbot_core.exs index e84e7b50..0449677c 100644 --- a/farmbot_ext/config/farmbot_core.exs +++ b/farmbot_ext/config/farmbot_core.exs @@ -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") diff --git a/farmbot_ext/lib/amqp/bot_state_transport.ex b/farmbot_ext/lib/amqp/bot_state_transport.ex index 16de196a..85d7e579 100644 --- a/farmbot_ext/lib/amqp/bot_state_transport.ex +++ b/farmbot_ext/lib/amqp/bot_state_transport.ex @@ -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 diff --git a/farmbot_ext/lib/amqp/log_transport.ex b/farmbot_ext/lib/amqp/log_transport.ex index cbaa3e9a..02ce7a4f 100644 --- a/farmbot_ext/lib/amqp/log_transport.ex +++ b/farmbot_ext/lib/amqp/log_transport.ex @@ -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 diff --git a/farmbot_ext/lib/api.ex b/farmbot_ext/lib/api.ex index befe20b4..4d7a86c5 100644 --- a/farmbot_ext/lib/api.ex +++ b/farmbot_ext/lib/api.ex @@ -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 diff --git a/farmbot_ext/lib/image_uploader.ex b/farmbot_ext/lib/image_uploader.ex deleted file mode 100644 index 8b137891..00000000 --- a/farmbot_ext/lib/image_uploader.ex +++ /dev/null @@ -1 +0,0 @@ - diff --git a/farmbot_ext/lib/protocols.ex b/farmbot_ext/lib/protocols.ex index f89995a0..a8c521c6 100644 --- a/farmbot_ext/lib/protocols.ex +++ b/farmbot_ext/lib/protocols.ex @@ -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) diff --git a/farmbot_ext/test/farmbot_ext_test.exs b/farmbot_ext/test/farmbot_ext_test.exs deleted file mode 100644 index 4b3fa769..00000000 --- a/farmbot_ext/test/farmbot_ext_test.exs +++ /dev/null @@ -1,8 +0,0 @@ -defmodule FarmbotExtTest do - use ExUnit.Case - doctest FarmbotExt - - test "greets the world" do - assert FarmbotExt.hello() == :world - end -end diff --git a/farmbot_firmware/.formatter.exs b/farmbot_firmware/.formatter.exs new file mode 100644 index 00000000..d2cda26e --- /dev/null +++ b/farmbot_firmware/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/farmbot_firmware/.gitignore b/farmbot_firmware/.gitignore new file mode 100644 index 00000000..c855abd1 --- /dev/null +++ b/farmbot_firmware/.gitignore @@ -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 + diff --git a/farmbot_firmware/README.md b/farmbot_firmware/README.md new file mode 100644 index 00000000..0441271d --- /dev/null +++ b/farmbot_firmware/README.md @@ -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). + diff --git a/farmbot_firmware/config/config.exs b/farmbot_firmware/config/config.exs new file mode 100644 index 00000000..d2d855e6 --- /dev/null +++ b/farmbot_firmware/config/config.exs @@ -0,0 +1 @@ +use Mix.Config diff --git a/farmbot_firmware/lib/farmbot_firmware.ex b/farmbot_firmware/lib/farmbot_firmware.ex new file mode 100644 index 00000000..de1a1052 --- /dev/null +++ b/farmbot_firmware/lib/farmbot_firmware.ex @@ -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 diff --git a/farmbot_firmware/lib/farmbot_firmware/command.ex b/farmbot_firmware/lib/farmbot_firmware/command.ex new file mode 100644 index 00000000..4ce4c245 --- /dev/null +++ b/farmbot_firmware/lib/farmbot_firmware/command.ex @@ -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 diff --git a/farmbot_firmware/lib/farmbot_firmware/gcode.ex b/farmbot_firmware/lib/farmbot_firmware/gcode.ex new file mode 100644 index 00000000..c1e3f51a --- /dev/null +++ b/farmbot_firmware/lib/farmbot_firmware/gcode.ex @@ -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 diff --git a/farmbot_firmware/lib/farmbot_firmware/gcode/decoder.ex b/farmbot_firmware/lib/farmbot_firmware/gcode/decoder.ex new file mode 100644 index 00000000..d9385275 --- /dev/null +++ b/farmbot_firmware/lib/farmbot_firmware/gcode/decoder.ex @@ -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([<> | 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( + [<>, <> | 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([<> | 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 diff --git a/farmbot_firmware/lib/farmbot_firmware/gcode/encoder.ex b/farmbot_firmware/lib/farmbot_firmware/gcode/encoder.ex new file mode 100644 index 00000000..1cb198ea --- /dev/null +++ b/farmbot_firmware/lib/farmbot_firmware/gcode/encoder.ex @@ -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 diff --git a/farmbot_firmware/lib/farmbot_firmware/param.ex b/farmbot_firmware/lib/farmbot_firmware/param.ex new file mode 100644 index 00000000..d372a7eb --- /dev/null +++ b/farmbot_firmware/lib/farmbot_firmware/param.ex @@ -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 diff --git a/farmbot_firmware/lib/farmbot_firmware/request.ex b/farmbot_firmware/lib/farmbot_firmware/request.ex new file mode 100644 index 00000000..fe900654 --- /dev/null +++ b/farmbot_firmware/lib/farmbot_firmware/request.ex @@ -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 diff --git a/farmbot_firmware/lib/farmbot_firmware/side_effects.ex b/farmbot_firmware/lib/farmbot_firmware/side_effects.ex new file mode 100644 index 00000000..6f2b8985 --- /dev/null +++ b/farmbot_firmware/lib/farmbot_firmware/side_effects.ex @@ -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 diff --git a/farmbot_firmware/lib/farmbot_firmware/stub_side_effects.ex b/farmbot_firmware/lib/farmbot_firmware/stub_side_effects.ex new file mode 100644 index 00000000..8355820c --- /dev/null +++ b/farmbot_firmware/lib/farmbot_firmware/stub_side_effects.ex @@ -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 diff --git a/farmbot_firmware/lib/farmbot_firmware/transports/stub_transport.ex b/farmbot_firmware/lib/farmbot_firmware/transports/stub_transport.ex new file mode 100644 index 00000000..1bb02f6f --- /dev/null +++ b/farmbot_firmware/lib/farmbot_firmware/transports/stub_transport.ex @@ -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 diff --git a/farmbot_firmware/lib/farmbot_firmware/transports/uart_transport.ex b/farmbot_firmware/lib/farmbot_firmware/transports/uart_transport.ex new file mode 100644 index 00000000..2238a7dd --- /dev/null +++ b/farmbot_firmware/lib/farmbot_firmware/transports/uart_transport.ex @@ -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 diff --git a/farmbot_firmware/mix.exs b/farmbot_firmware/mix.exs new file mode 100644 index 00000000..20f98809 --- /dev/null +++ b/farmbot_firmware/mix.exs @@ -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 diff --git a/farmbot_firmware/mix.lock b/farmbot_firmware/mix.lock new file mode 100644 index 00000000..d7e9fa50 --- /dev/null +++ b/farmbot_firmware/mix.lock @@ -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"}, +} diff --git a/farmbot_firmware/test/farmbot_firmware_test.exs b/farmbot_firmware/test/farmbot_firmware_test.exs new file mode 100644 index 00000000..5988e086 --- /dev/null +++ b/farmbot_firmware/test/farmbot_firmware_test.exs @@ -0,0 +1,4 @@ +defmodule Farmbot.FirmwareTest do + use ExUnit.Case + doctest Farmbot.Firmware +end diff --git a/farmbot_firmware/test/gcode_test.exs b/farmbot_firmware/test/gcode_test.exs new file mode 100644 index 00000000..abb7b15d --- /dev/null +++ b/farmbot_firmware/test/gcode_test.exs @@ -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 diff --git a/farmbot_firmware/test/test_helper.exs b/farmbot_firmware/test/test_helper.exs new file mode 100644 index 00000000..869559e7 --- /dev/null +++ b/farmbot_firmware/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/farmbot_os/.formatter.exs b/farmbot_os/.formatter.exs index ca0a0308..77cd7620 100644 --- a/farmbot_os/.formatter.exs +++ b/farmbot_os/.formatter.exs @@ -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"] ] diff --git a/farmbot_os/config.test.db-journal b/farmbot_os/config.test.db-journal new file mode 100644 index 0000000000000000000000000000000000000000..03d7072cb6c9b3fbe9161b3c5671596998f77836 GIT binary patch literal 8720 zcmeHLO>7%Q6yA*;uk&M1NK3a-4qa_k%1VeRN>C*#q`K)gvg+E@u~7q+RvUZlY~$T^ zch>nip-qtB!i5VQxby%QE?j!y!UYKs5*LJk0~aL31qpFMATGQaubrKGV+D?ME&1)6 z_ujrY^S$?G#{S{k$-k6`jqe0On5eXh_ux4XEIj7mn|z)C1Tr~TUKCJzUqH{GNBJ-EujYQs9nE}@-k)BbdN2EV z_S(#R=4kTglUyY71c3yB{~H2__p_3`yez))x^HfEu!H?Q*V{2H$H!jFY+y1fnOdvr zg_^F^3fD`zqK+D<%CoA~RFz`6rmyQ&<>hMecA>hd+|oCn1AEiPs#4SM)|7GuzV%WG zr2auSMoI|vLv6vNOLm4l!Hu40`3FOR)!kh+Z0v8l$1m7c+cSL(g6ow^NiUSaiESQi zVPo5BHn9_xv-izr)5C=DPTSyO&wMNr>21Nf4cm;&sp;CL1x~~1(sddHd%M^RJ!-)s z0nOVC^tw&DJ&d!`f`w^GzI8MRYECw$yrP&M9MXrZEwY^%4KAj%)g-48YsQM2HwfLNFxhk*(d&Nb;piVj#2R zMy;c~#HTr`8lP*H3RSsTsuXIPH7Us#FN(neixpXWgw61gBAQbL3hz8xC*407`pe=H z$qgp?aGdT`0U3z*Qj)x|AO>gH!unO&r)l1QSg>QSip++!dAPL%v!Wy~Ee&=&(%8nf zY4q=Tmg|ra!2}<4L{Aq+FKF;G+y;y5wB7jYs&(>Nq7^1Ga(zh@*d9b4bl?}fVfK8N Wjg66D#`1_L1zTN~Zg-Vd`RE^0rRiq? literal 0 HcmV?d00001 diff --git a/farmbot_os/config/config.exs b/farmbot_os/config/config.exs index a4d12e8c..f0c4e48f 100644 --- a/farmbot_os/config/config.exs +++ b/farmbot_os/config/config.exs @@ -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") diff --git a/farmbot_os/config/host/auth_secret_ci.exs b/farmbot_os/config/host/auth_secret_ci.exs deleted file mode 100644 index 4602805d..00000000 --- a/farmbot_os/config/host/auth_secret_ci.exs +++ /dev/null @@ -1,35 +0,0 @@ -use Mix.Config - -config :farmbot_os, :authorization, - email: "travis@travis.org", - password: "password123", - server: "https://staging.farmbot.io" - -data_path = Path.join(["/", "tmp", "farmbot"]) -config :farmbot_ext, - data_path: data_path - -config :farmbot_core, Farmbot.Config.Repo, - adapter: Sqlite.Ecto2, - loggers: [], - database: Path.join(data_path, "config-#{Mix.env()}.sqlite3") - -config :farmbot_core, Farmbot.Logger.Repo, - adapter: Sqlite.Ecto2, - loggers: [], - database: Path.join(data_path, "logs-#{Mix.env()}.sqlite3") - -config :farmbot_core, Farmbot.Asset.Repo, - adapter: Sqlite.Ecto2, - loggers: [], - database: Path.join(data_path, "repo-#{Mix.env()}.sqlite3") - -config :farmbot_os, - ecto_repos: [Farmbot.Config.Repo, Farmbot.Logger.Repo, Farmbot.Asset.Repo], - platform_children: [ - {Farmbot.Host.Configurator, []} - ] - -config :farmbot_os, :behaviour, - update_handler: Farmbot.Host.UpdateHandler, - system_tasks: Farmbot.Host.SystemTasks diff --git a/farmbot_os/config/host/auth_secret_template.exs b/farmbot_os/config/host/auth_secret_template.exs deleted file mode 100644 index 717ad128..00000000 --- a/farmbot_os/config/host/auth_secret_template.exs +++ /dev/null @@ -1,9 +0,0 @@ -use Mix.Config - -# You should copy this file to config/host/auth_secret.exs -# And make sure to configure the credentials to something that makes sense. - -config :farmbot_os, :authorization, - email: "admin@admin.com", - password: "password123", - server: "http://localhost:3000" \ No newline at end of file diff --git a/farmbot_os/config/host/test.exs b/farmbot_os/config/host/test.exs deleted file mode 100644 index afdfbd02..00000000 --- a/farmbot_os/config/host/test.exs +++ /dev/null @@ -1,15 +0,0 @@ -use Mix.Config - -config :logger_backend_ecto, LoggerBackendEcto.Repo, - adapter: Sqlite.Ecto2, - database: "/tmp/logs.sqlite3" - -cond do - System.get_env("CIRCLECI") -> - Mix.shell.info [:green, "Using circle ci config."] - import_config("auth_secret_ci.exs") - File.exists?("config/host/auth_secret_test.exs") -> - import_config("auth_secret_test.exs") - true -> - Mix.raise("You need to configure your test environment.\r\n") -end diff --git a/farmbot_os/config/lagger.exs b/farmbot_os/config/lagger.exs new file mode 100644 index 00000000..6553c3cf --- /dev/null +++ b/farmbot_os/config/lagger.exs @@ -0,0 +1,5 @@ +use Mix.Config +config :lager, :error_logger_redirect, false +config :lager, :error_logger_whitelist, [] +config :lager, :crash_log, false +config :lager, :handlers, [] diff --git a/farmbot_os/config/target/rpi3.exs b/farmbot_os/config/target/rpi3.exs deleted file mode 100644 index 2e622e90..00000000 --- a/farmbot_os/config/target/rpi3.exs +++ /dev/null @@ -1,3 +0,0 @@ -use Mix.Config -config :farmbot_os, :captive_portal_address, "192.168.24.1" -config :farmbot_os, kernel_modules: ["snd-bcm2835"] diff --git a/farmbot_os/lib/boot_state.ex b/farmbot_os/lib/boot_state.ex index e69b14f3..b7fe377a 100644 --- a/farmbot_os/lib/boot_state.ex +++ b/farmbot_os/lib/boot_state.ex @@ -1,11 +1,10 @@ defmodule Farmbot.BootState do - @data_path Application.get_env(:farmbot_ext, :data_path) - @data_path || Mix.raise("Unconfigured data path.") + @data_path Farmbot.OS.FileSystem.data_path() @state_file Path.join(@data_path, "boot_state") def read do case File.read(@state_file) do - {:error, :enoend} -> write(:NEEDS_CONFIGURATION) + {:error, :enoent} -> write(:NEEDS_CONFIGURATION) {:error, _} = er -> er {:ok, data} -> data |> String.trim() |> String.to_atom() end diff --git a/farmbot_os/lib/celery_script/_if.ex b/farmbot_os/lib/celery_script/_if.ex new file mode 100644 index 00000000..c083232f --- /dev/null +++ b/farmbot_os/lib/celery_script/_if.ex @@ -0,0 +1,74 @@ +defmodule Farmbot.OS.IOLayer.If do + @moduledoc false + + alias Farmbot.{Asset, Firmware} + + def execute(%{lhs: lhs, op: op, rhs: rhs}, _body) do + case eval_lhs(lhs) do + {:ok, left} -> + left + |> eval_if(op, rhs) + + {:error, _} = err -> + err + end + end + + def eval_lhs(axis) when axis in ["x", "y", "z"] do + case Firmware.request({:position_read, []}) do + {:ok, {_, {:report_position, pos}}} -> + Keyword.fetch!(pos, String.to_existing_atom(axis)) + + _ -> + {:error, "Firmware Error reading position"} + end + end + + def eval_lhs(%{kind: :named_pin} = named_pin) do + id = named_pin.args.pin_id + type = named_pin.args.pin_type + + case fetch_resource(type, id) do + {:ok, number} -> eval_lhs("pin#{number}") + {:error, reason} -> {:error, reason} + end + end + + def eval_lhs("pin" <> p) do + p = String.to_integer(p) + + case Firmware.request({:pin_read, [p: p]}) do + {:ok, {_, {:report_pin_value, [p: ^p, v: v]}}} -> {:ok, v} + _ -> {:error, "Firmware error reading pin: #{p}"} + end + end + + defp fetch_resource("Peripheral", id) do + case Asset.get_peripheral(id: id) do + nil -> {:error, "could not find Peripheral #{id}"} + %{pin: p} -> {:ok, p} + end + end + + defp fetch_resource(unknown_type, _) do + {:error, "Unknown resource type: #{unknown_type}"} + end + + defp eval_if(nil, "is_undefined", _), do: {:ok, true} + defp eval_if(_, "is_undefined", _), do: {:ok, false} + + defp eval_if(nil, _, _), + do: {:error, "Could not eval IF because left hand side of if statement is undefined."} + + defp eval_if(lhs, ">", rhs) when lhs > rhs, do: {:ok, true} + defp eval_if(_lhs, ">", _rhs), do: {:ok, false} + + defp eval_if(lhs, "<", rhs) when lhs < rhs, do: {:ok, true} + defp eval_if(_lhs, "<", _rhs), do: {:ok, false} + + defp eval_if(lhs, "==", rhs) when lhs == rhs, do: {:ok, true} + defp eval_if(_lhs, "==", _rhs), do: {:ok, false} + + defp eval_if(lhs, "!=", rhs) when lhs != rhs, do: {:ok, true} + defp eval_if(_lhs, "!=", _rhs), do: {:ok, false} +end diff --git a/farmbot_os/lib/celery_script/ast.ex b/farmbot_os/lib/celery_script/ast.ex deleted file mode 100644 index 0b8712fc..00000000 --- a/farmbot_os/lib/celery_script/ast.ex +++ /dev/null @@ -1,2 +0,0 @@ -require Protocol -Protocol.derive(Jason.Encoder, Farmbot.CeleryScript.AST) diff --git a/farmbot_os/lib/celery_script/calibrate.ex b/farmbot_os/lib/celery_script/calibrate.ex new file mode 100644 index 00000000..12ab2a24 --- /dev/null +++ b/farmbot_os/lib/celery_script/calibrate.ex @@ -0,0 +1,22 @@ +defmodule Farmbot.OS.IOLayer.Calibrate do + @moduledoc false + + alias Farmbot.Firmware + + def execute(%{axis: "all"} = args, body) do + with :ok <- execute(%{args | axis: "z"}, body), + :ok <- execute(%{args | axis: "y"}, body), + :ok <- execute(%{args | axis: "x"}, body) do + :ok + end + end + + def execute(%{axis: axis}, _body) do + command = {:command_movement_calibrate, [String.to_existing_atom(axis)]} + + case Firmware.command(command) do + :ok -> :ok + _ -> {:error, "Firmware Error"} + end + end +end diff --git a/farmbot_os/lib/celery_script/dump_info.ex b/farmbot_os/lib/celery_script/dump_info.ex new file mode 100644 index 00000000..8e032c5f --- /dev/null +++ b/farmbot_os/lib/celery_script/dump_info.ex @@ -0,0 +1,46 @@ +defmodule Farmbot.OS.IOLayer.DumpInfo do + @moduledoc false + + alias Farmbot.{Firmware, Asset, Config, Project} + + def execute(_args, _body) do + conf = Asset.fbos_config() + fw_state = get_fw_state(Process.whereis(Firmware), conf) + network_iface = Config.get_all_network_configs() |> Enum.at(0) + + params = %{ + firmware_state: fw_state, + network_interface: network_iface[:name], + firmware_hardware: fw_state[:firmware_hardware], + fbos_commit: Project.commit(), + fbos_version: Project.version(), + fbos_dmesg_dump: System.cmd("dmesg", []) |> elem(0), + fbos_target: Project.target() + } + + case Farmbot.Asset.new_diagnostic_dump(params) do + {:ok, _} -> :ok + {:error, _} -> {:error, "Failed to create diagnostic dump"} + end + end + + def get_fw_state(nil, _), do: nil + + def get_fw_state(fw, conf) do + %{ + firmware_hardware: conf.firmware_hardware, + firmware_version: firmware_version(fw), + busy: false, + serial_port: conf.firmware_path, + locked: false, + current_command: nil + } + end + + defp firmware_version(fw) do + case Firmware.request(fw, {:software_version_read, []}) do + {:ok, {_, {:report_software_version, [ver]}}} -> ver + _error -> nil + end + end +end diff --git a/farmbot_os/lib/celery_script/find_home.ex b/farmbot_os/lib/celery_script/find_home.ex new file mode 100644 index 00000000..bd243ccc --- /dev/null +++ b/farmbot_os/lib/celery_script/find_home.ex @@ -0,0 +1,39 @@ +defmodule Farmbot.OS.IOLayer.FindHome do + @moduledoc false + + alias Farmbot.Firmware + + def execute(%{axis: "all"} = args, body) do + with :ok <- execute(%{args | axis: "z"}, body), + :ok <- execute(%{args | axis: "y"}, body), + :ok <- execute(%{args | axis: "x"}, body) do + :ok + end + end + + def execute(%{axis: axis}, _body) do + ep_param = :"movement_enable_endpoints_#{axis}" + enc_param = :"encoder_enabled_#{axis}" + + # I'm sorry about these long lines + with {:ok, {_, {:report_paramater_value, [{^ep_param, ep_val}]}}} <- + Firmware.request({:paramater_read, [ep_param]}), + {:ok, {_, {:report_paramater_value, [{^enc_param, enc_val}]}}} <- + Firmware.request({:paramater_read, [enc_param]}) do + command([String.to_existing_atom(axis)], ep_val, enc_val) + else + _ -> {:error, "Firmware Error"} + end + end + + defp command([axis], 0.0, 0.0) do + {:error, "Could not find home on #{axis} axis because endpoints and encoders are disabled."} + end + + defp command(args, _, _) do + case Firmware.command({:command_movement_find_home, args}) do + :ok -> :ok + _ -> {:error, "Firmware Error"} + end + end +end diff --git a/farmbot_os/lib/celery_script/home.ex b/farmbot_os/lib/celery_script/home.ex new file mode 100644 index 00000000..f81a8268 --- /dev/null +++ b/farmbot_os/lib/celery_script/home.ex @@ -0,0 +1,28 @@ +defmodule Farmbot.OS.IOLayer.Home do + @moduledoc false + + alias Farmbot.Firmware + + def execute(%{axis: "all"}, _body) do + command([:x, :y, :z]) + end + + def execute(%{axis: "x"}, _body) do + command([:x]) + end + + def execute(%{axis: "y"}, _body) do + command([:y]) + end + + def execute(%{axis: "z"}, _body) do + command([:z]) + end + + defp command(args) do + case Firmware.command({:command_movement_home, args}) do + :ok -> :ok + _ -> {:error, "Firmware Error"} + end + end +end diff --git a/farmbot_os/lib/celery_script/io_layer.ex b/farmbot_os/lib/celery_script/io_layer.ex index c2012b02..e65edf8c 100644 --- a/farmbot_os/lib/celery_script/io_layer.ex +++ b/farmbot_os/lib/celery_script/io_layer.ex @@ -1,120 +1,107 @@ defmodule Farmbot.OS.IOLayer do + @moduledoc false @behaviour Farmbot.Core.CeleryScript.IOLayer alias Farmbot.OS.IOLayer.{ - Farmware, + Calibrate, + DumpInfo, FindHome, + Home, If, MoveAbsolute, + MoveRelative, ReadPin, + SendMessage, + SetServoAngle, Sync, TogglePin, WritePin, + Zero } - def emergency_lock(_args, _body), do: Farmbot.Firmware.emergency_lock() - - def emergency_unlock(_args, _body), do: Farmbot.Firmware.emergency_unlock() - - def move_relative(%{x: x, y: y, z: z, speed: speed}, []) do - import Farmbot.Core.CeleryScript.Utils - %{x: cur_x, y: cur_y, z: cur_z} = Farmbot.Firmware.get_current_position() - location = new_vec3(cur_x, cur_y, cur_z) - offset = new_vec3(x, y, z) - move_absolute(%{location: location, offset: offset, speed: speed}, []) - end - - def move_absolute(args, body), do: MoveAbsolute.execute(args, body) - - def toggle_pin(args, body), do: TogglePin.execute(args, body) - - def write_pin(args, body), do: WritePin.execute(args, body) - - def read_pin(args, body), do: ReadPin.execute(args, body) - - def set_servo_angle(%{pin_number: pin_number, pin_value: value}, []) do - case Farmbot.Firmware.set_servo_angle(pin_number, value) do - :ok -> :ok - {:error, reason} when is_binary(reason) -> {:error, reason} - end - end - - def home(%{axis: "all"}, []) do - case Farmbot.Firmware.home_all() do - :ok -> :ok - {:error, reason} when is_binary(reason) -> {:error, reason} - end - end - - def home(%{axis: axis}, []) do - case Farmbot.Firmware.home(axis) do - :ok -> :ok - {:error, reason} when is_binary(reason) -> {:error, reason} - end - end - - def find_home(args, body), do: FindHome.execute(args, body) - - def wait(%{milliseconds: millis}, []), do: Process.sleep(millis) - - def zero(_args, _body) do - {:error, "not implemented: zero"} - end - - def calibrate(_args, _body) do - {:error, "not implemented: calibrate"} - end - - def config_update(_args, _body) do - {:error, "config_update depreciated since 6.1.0"} - end - - def set_user_env(_args, pairs) do - IO.puts "not implemented: set_user_env(#{inspect pairs})" - :ok - end - - def execute_script(args, body), do: Farmware.execute(args, body) - - def take_photo(_args, body) do - execute_script(%{package: "take-photo"}, body) - end - + # Reporting commands def read_status(_args, _body) do Farmbot.BotState.fetch() :ok end - def send_message(_args, _body) do - {:error, "not implemented: send_message"} + # send_message needs the firmware to serialize + # position and pins + def send_message(args, body), do: require_firmware(SendMessage, args, body) + + def dump_info(args, body), do: DumpInfo.execute(args, body) + + # Flow Control + def _if(args, body), do: require_firmware(If, args, body) + + def execute(%{sequence_id: id}, _body) do + case Farmbot.Asset.get_sequence(id: id) do + nil -> {:error, "Sequence #{id} not found. Try syncing first."} + %{} = seq -> {:ok, Farmbot.CeleryScript.AST.decode(seq)} + end end + def wait(%{milliseconds: ms}, _body), do: Process.sleep(ms) + + # Emergency control + def emergency_lock(_args, _body) do + if Process.whereis(Farmbot.Firmware) do + _ = Farmbot.Firmware.command({:command_emergency_lock, []}) + end + + :ok + end + + def emergency_unlock(_args, _body) do + if Process.whereis(Farmbot.Firmware) do + _ = Farmbot.Firmware.command({:command_emergency_unlock, []}) + end + + :ok + end + + # Firmware commands + def calibrate(args, body), do: require_firmware(Calibrate, args, body) + def find_home(args, body), do: require_firmware(FindHome, args, body) + def home(args, body), do: require_firmware(Home, args, body) + def move_absolute(args, body), do: require_firmware(MoveAbsolute, args, body) + def move_relative(args, body), do: require_firmware(MoveRelative, args, body) + def read_pin(args, body), do: require_firmware(ReadPin, args, body) + def set_servo_angle(args, body), do: require_firmware(SetServoAngle, args, body) + def toggle_pin(args, body), do: require_firmware(TogglePin, args, body) + def write_pin(args, body), do: require_firmware(WritePin, args, body) + def zero(args, body), do: require_firmware(Zero, args, body) + + # Farmware + def set_user_env(_args, body) do + for %{args: %{label: key, value: value}} <- body do + Farmbot.Asset.new_farmware_env(%{key: key, value: value}) + end + + :ok + end + + def take_photo(_args, _body), do: {:error, "take_photo Stubbed"} + + def execute_script(_args, _body), do: {:error, "execute_script Stubbed"} + + # Sync/Data + def check_updates(_args, _body), do: {:error, "check_updates Stubbed"} def sync(args, body), do: Sync.execute(args, body) - def power_off(_,_), do: Farmbot.System.shutdown("CeleryScript") + # Power/System + def change_ownership(_args, _body), do: {:error, "change_ownership Stubbed"} + def factory_reset(_args, _body), do: Farmbot.System.factory_reset("CeleryScript") + def power_off(_args, _body), do: Farmbot.System.shutdown("CeleryScript") + def reboot(_args, _body), do: Farmbot.System.reboot("CeleryScript") - def reboot(_,_), do: Farmbot.System.reboot("CeleryScript") + # deprecated commands + def config_update(_args, _body), do: {:error, "config_update deprecated"} - def factory_reset(_,_), do: Farmbot.System.factory_reset("CeleryScript") - - def dump_info(_args, _body) do - {:error, "not implemented: dump_info"} - end - - def change_ownership(_args, _body) do - {:error, "not implemented: change_ownership"} - end - - def check_updates(_args, _body) do - {:error, "not implemented: check_updates"} - end - - def _if(args, body), do: If.execute(args, body) - - def execute(%{sequence_id: sid}, _body) do - alias Farmbot.Asset - case Asset.get_sequence_by_id(sid) do - nil -> {:error, "no sequence by id: #{sid}"} - %Asset.Sequence{} = seq -> {:ok, Farmbot.CeleryScript.AST.decode(seq)} + defp require_firmware(module, args, body) do + if Process.whereis(Farmbot.Firmware) do + module.execute(args, body) + else + {:error, "Firmware not initialized"} end end end diff --git a/farmbot_os/lib/celery_script/io_layer/_if.ex b/farmbot_os/lib/celery_script/io_layer/_if.ex deleted file mode 100644 index b2985292..00000000 --- a/farmbot_os/lib/celery_script/io_layer/_if.ex +++ /dev/null @@ -1,69 +0,0 @@ -defmodule Farmbot.OS.IOLayer.If do - alias Farmbot.CeleryScript.AST - alias Farmbot.Asset - alias Asset.{Peripheral, Sensor} - - def execute(%{lhs: lhs, op: op, rhs: rhs}, _body) do - left = eval_lhs(lhs) - cond do - is_number(left) or is_nil(left) -> eval_if(left, op, rhs) - match?({:error, _}, left) -> left - end - end - - defp eval_lhs(axis) when axis in ["x", "y", "z"] do - Farmbot.Firmware.get_current_position() - |> Enum.find(fn({a, _}) -> axis == to_string(a) end) - |> elem(1) - end - - # handles looking up a pin from a peripheral. - defp eval_lhs(%AST{kind: :named_pin} = named_pin) do - id = named_pin.args.pin_id - type = named_pin.args.pin_type - case fetch_resource(type, id) do - {:ok, number} -> - eval_lhs({:pin, number}) - {:error, reason} when is_binary(reason) -> {:error, reason} - end - end - - defp eval_lhs({:pin, pin}) do - case Farmbot.Firmware.get_pin_value(pin) do - %{value: value} -> value - nil -> {:error, "Could not find firmware pin value #{pin}"} - end - end - - defp eval_if(nil, "is_undefined", _), do: {:ok, true} - defp eval_if(_, "is_undefined", _), do: {:ok, false} - defp eval_if(nil, _, _), - do: {:error, "Could not eval IF because left hand side of if statement is undefined."} - - defp eval_if(lhs, ">", rhs) when lhs > rhs, do: {:ok, true} - defp eval_if(_lhs, ">", _rhs), do: {:ok, false} - - defp eval_if(lhs, "<", rhs) when lhs < rhs, do: {:ok, true} - defp eval_if(_lhs, "<", _rhs), do: {:ok, false} - - defp eval_if(lhs, "is", rhs) when lhs == rhs, do: {:ok, true} - defp eval_if(_lhs, "is", _rhs), do: {:ok, false} - - defp eval_if(lhs, "not", rhs) when lhs != rhs, do: {:ok, true} - defp eval_if(_lhs, "not", _rhs), do: {:ok, false} - defp eval_if(_, op, _), do: {:error, "Unknown operator: #{op}"} - - defp fetch_resource("Peripheral", id) do - case Asset.get_peripheral_by_id(id) do - %Peripheral{pin: number} -> {:ok, number} - nil -> {:error, "Could not find Peripheral by id: #{id}"} - end - end - - defp fetch_resource("Sensor", id) do - case Asset.get_sensor_by_id(id) do - %Sensor{pin: number} -> {:ok, number} - nil -> {:error, "Could not find Sensor by id: #{id}"} - end - end -end diff --git a/farmbot_os/lib/celery_script/io_layer/farmware.ex b/farmbot_os/lib/celery_script/io_layer/farmware.ex deleted file mode 100644 index a4f594fb..00000000 --- a/farmbot_os/lib/celery_script/io_layer/farmware.ex +++ /dev/null @@ -1,47 +0,0 @@ -defmodule Farmbot.OS.IOLayer.Farmware do - require Farmbot.Logger - - def first_party(_args, []) do - {:error, "not implemented"} - end - - def install(%{url: _url}, []) do - {:error, "not implemented"} - end - - def update(%{package: _name}, []) do - {:error, "not implemented"} - end - - def remove(%{package: _name}, []) do - {:error, "not implemented"} - end - - def execute(%{package: _name}, []) do - {:error, "not implemented"} - end - - # 1) Check if Farmware is already running - # 1a) Farmware isn't running - start it. - # 1b) Farmware is running - continute. - # 2) check if there is a rpc_request to process. - # 2a) there is a request - queue it - # 2b) there is not a request - continue. - # 3) check if server is still alive - # 3a) The server is still alive - continue - # 3b) the server is not still alive - exit - # def do_execute(fw) do - # case Server.lookup(fw) do - # {:ok, pid} -> pid - # {:error, {:already_started, pid}} -> pid - # end - # |> Server.is_alive?() - # |> case do - # true -> - # case Server.get_request(pid) do - # {:ok, request} -> {:ok, request} - # nil -> {:ok, %Farmbot.CelerScript.AST.new(:rpc_request, %{args: "noop"}, [])} - # end - # end - # end -end diff --git a/farmbot_os/lib/celery_script/io_layer/find_home.ex b/farmbot_os/lib/celery_script/io_layer/find_home.ex deleted file mode 100644 index c5232ad3..00000000 --- a/farmbot_os/lib/celery_script/io_layer/find_home.ex +++ /dev/null @@ -1,41 +0,0 @@ -defmodule Farmbot.OS.IOLayer.FindHome do - require Farmbot.Logger - import Farmbot.Config, only: [get_config_value: 3] - - def execute(%{axis: "all"}, _) do - do_reduce(["z", "y", "x"]) - end - - def execute(%{axis: axis}, _) do - ep = get_config_value(:float, "hardware_params", "movement_enable_endpoints_#{axis}") - ec = get_config_value(:float, "hardware_params", "encoder_enabled_#{axis}") - do_find_home(ep, ec, axis) - end - - defp do_reduce([axis | rest]) do - case execute(%{axis: axis}, []) do - :ok -> do_reduce(rest) - {:error, reason} -> {:error, reason} - end - end - - defp do_reduce([]), do: :ok - - defp do_find_home(ep, ec, axis) - - defp do_find_home(ep, ec, axis) when ((ep == 0) or (ep == nil)) and ((ec == 0) or (ec == nil)) do - {:error, "Could not find home on #{axis} axis because endpoints and encoders are disabled."} - end - - defp do_find_home(ep, ec, axis) when ep == 1 or ec == 1 do - Farmbot.Logger.busy 2, "Finding home on #{axis} axis." - case Farmbot.Firmware.find_home(axis) do - :ok -> :ok - {:error, reason} when is_binary(reason) -> {:error, reason} - end - end - - defp do_find_home(ep, ec, _axis) do - {:error, "Unknown state of endpoints: #{ep} or encoders: #{ec}"} - end -end diff --git a/farmbot_os/lib/celery_script/io_layer/move_absolute.ex b/farmbot_os/lib/celery_script/io_layer/move_absolute.ex deleted file mode 100644 index 392955cc..00000000 --- a/farmbot_os/lib/celery_script/io_layer/move_absolute.ex +++ /dev/null @@ -1,55 +0,0 @@ -defmodule Farmbot.OS.IOLayer.MoveAbsolute do - alias Farmbot.Firmware.Vec3 - import Farmbot.Config, only: [get_config_value: 3] - require Farmbot.Logger - - def execute(%{location: %{x: _, y: _, z: _} = pos_a, - offset: %{x: _, y: _, z: _} = pos_b, - speed: speed}, []) do - pos = vec3_math(pos_a, :+, pos_b) - maybe_log_busy(pos) - speed_x = (speed / 100) * (get_config_value(:float, "hardware_params", "movement_max_spd_x") || 1) - speed_y = (speed / 100) * (get_config_value(:float, "hardware_params", "movement_max_spd_y") || 1) - speed_z = (speed / 100) * (get_config_value(:float, "hardware_params", "movement_max_spd_z") || 1) - case Farmbot.Firmware.move_absolute(pos, speed_x |> round(), speed_y |> round(), speed_z |> round()) do - :ok -> :ok - {:error, reason} when is_binary(reason) -> {:error, reason} - end - end - - def execute(%{location: location, offset: offset, speed: speed}, body) do - with {:ok, location_vec3} <- to_vec3(location), - {:ok, offset_vec3} <- to_vec3(offset) do - execute(%{location: location_vec3, offset: offset_vec3, speed: speed}, body) - end - end - - defp maybe_log_busy(%Vec3{} = pos) do - unless get_config_value(:bool, "settings", "firmware_input_log") do - Farmbot.Logger.busy 1, "Moving to #{inspect pos}" - end - end - - def vec3_math(%{x: xa, y: ya, z: za}, fun, %{x: xb, y: yb, z: zb}) do - res_x = apply(Kernel, fun, [xa || 0, xb || 0]) - res_y = apply(Kernel, fun, [ya || 0, yb || 0]) - res_z = apply(Kernel, fun, [za || 0, zb || 0]) - %Vec3{x: res_x, y: res_y, z: res_z} - end - - def to_vec3(%{x: x, y: y, z: z}), do: {:ok, %{x: x, y: y, z: z}} - - def to_vec3(%{args: %{x: x, y: y, z: z}}), do: {:ok, %{x: x, y: y, z: z}} - - def to_vec3(%{kind: :point, args: %{pointer_id: id}}) do - case Farmbot.Asset.get_point_by_id(id) do - %Farmbot.Asset.Point{x: x, y: y, z: z} -> {:ok, %{x: x, y: y, z: z}} - _ -> {:error, "Could not find Plant by id: #{id}. Try Syncing."} - end - end - - def to_vec3(%{kind: kind} = info) do - Farmbot.Logger.error 1, "Unknown vector type: #{inspect info}" - {:error, "Can't convert #{kind} to vec3."} - end -end diff --git a/farmbot_os/lib/celery_script/io_layer/read_pin.ex b/farmbot_os/lib/celery_script/io_layer/read_pin.ex deleted file mode 100644 index c3047b7a..00000000 --- a/farmbot_os/lib/celery_script/io_layer/read_pin.ex +++ /dev/null @@ -1,84 +0,0 @@ -defmodule Farmbot.OS.IOLayer.ReadPin do - alias Farmbot.CeleryScript.AST - require Farmbot.Logger - alias Farmbot.Asset - alias Asset.{Peripheral, Sensor} - - @digital 0 - @analog 1 - - def execute(%{pin_number: %AST{kind: :named_pin} = named_pin, pin_mode: mode}, _) do - id = named_pin.args.pin_id - type = named_pin.args.pin_type - case fetch_resource(type, id) do - %Peripheral{pin: pin_num, label: name} -> do_read(pin_num, mode, name) - %Sensor{pin: pin_num, label: name} -> do_read(pin_num, mode, name) - {:error, reason} -> {:error, reason} - end - end - - def execute(%{pin_number: pin_num, pin_mode: mode}, _) when is_number(pin_num) do - case fetch_resource(nil, pin_num) do - %Peripheral{pin: pin_num, label: name} -> - do_read(pin_num, mode, name) - %Sensor{pin: pin_num, label: name} -> - do_read(pin_num, mode, name) - {:ok, ^pin_num} -> - do_read(pin_num, mode, "Pin #{pin_num}") - {:error, reason} -> {:error, reason} - end - end - - defp do_read(pin_num, mode, msg) do - case Farmbot.Firmware.read_pin(pin_num, mode) do - :ok -> - case Farmbot.Firmware.get_pin_value(pin_num) do - %{mode: ^mode, value: val} -> - log_success(msg, pin_num, mode, val) - :ok - nil -> {:error, "Firmware didn't report pin value."} - end - {:error, reason} -> {:error, reason} - end - end - - defp log_success(msg, _num, @digital, 1) do - Farmbot.Logger.success 1, "#{msg} value is 1 (digital)" - end - - defp log_success(msg, _num, @digital, 0) do - Farmbot.Logger.success 1, "#{msg} value is 0 (digital)" - end - - defp log_success(msg, _num, @analog, val) do - Farmbot.Logger.success 1, "#{msg} value is #{val} (analog)" - end - - defp fetch_resource("Peripheral", id) do - case Asset.get_peripheral_by_id(id) do - %Peripheral{} = per -> per - nil -> {:error, "Could not find pin by id: #{id}"} - end - end - - defp fetch_resource("Sensor", id) do - case Asset.get_sensor_by_id(id) do - %Sensor{} = sen -> sen - nil -> {:error, "Could not find pin by id: #{id}"} - end - end - - defp fetch_resource(nil, number) do - try_lookup_sensor(number) || - try_lookup_peripheral(number) || - {:ok, number} - end - - defp try_lookup_peripheral(number) do - Asset.get_peripheral_by_number(number) - end - - defp try_lookup_sensor(number) do - Asset.get_sensor_by_number(number) - end -end diff --git a/farmbot_os/lib/celery_script/io_layer/sync.ex b/farmbot_os/lib/celery_script/io_layer/sync.ex deleted file mode 100644 index 18452c6f..00000000 --- a/farmbot_os/lib/celery_script/io_layer/sync.ex +++ /dev/null @@ -1,65 +0,0 @@ -defmodule Farmbot.OS.IOLayer.Sync do - import Farmbot.Config, only: [get_config_value: 3] - import Farmbot.Asset, only: [fragment_sync: 1, full_sync: 2] - require Farmbot.Logger - alias Farmbot.HTTP - alias Farmbot.Asset.{ - Device, - FarmEvent, - # FarmwareEnv, - # FarmwareInstallation, - Peripheral, - PinBinding, - Point, - Regimen, - Sensor, - Sequence, - Tool, - } - - def execute(_, []) do - case get_config_value(:bool, "settings", "needs_http_sync") do - true -> full_sync(1, &http_sync/0) - false -> fragment_sync(1) - end - end - - def http_sync do - Farmbot.Logger.debug 3, "Starting HTTP sync." - {time, results} = :timer.tc(fn() -> - {:ok, pid} = Task.Supervisor.start_link() - [ - Task.Supervisor.async_nolink(pid, fn -> {Device, HTTP.device() |> List.wrap() |> list_to_sync_cmds()} end), - Task.Supervisor.async_nolink(pid, fn -> {FarmEvent, HTTP.farm_events() |> list_to_sync_cmds()} end), - - # These aren't a thing yet. - # Task.Supervisor.async_nolink(pid, fn -> {FarmwareEnv, HTTP.farmware_envs() |> list_to_sync_cmds()} end), - # Task.Supervisor.async_nolink(pid, fn -> {FarmwareInstallation, HTTP.farmware_installations() |> list_to_sync_cmds()} end), - - Task.Supervisor.async_nolink(pid, fn -> {Peripheral, HTTP.peripherals() |> list_to_sync_cmds()} end), - Task.Supervisor.async_nolink(pid, fn -> {PinBinding, HTTP.pin_bindings() |> list_to_sync_cmds()} end), - Task.Supervisor.async_nolink(pid, fn -> {Point, HTTP.points() |> list_to_sync_cmds()} end), - Task.Supervisor.async_nolink(pid, fn -> {Regimen, HTTP.regimens() |> list_to_sync_cmds()} end), - Task.Supervisor.async_nolink(pid, fn -> {Sensor, HTTP.sensors() |> list_to_sync_cmds()} end), - Task.Supervisor.async_nolink(pid, fn -> {Sequence, HTTP.sequences() |> list_to_sync_cmds()} end), - Task.Supervisor.async_nolink(pid, fn -> {Tool, HTTP.tools() |> list_to_sync_cmds()} end), - ] - |> Enum.map(&Task.yield(&1)) - |> Enum.map(fn({:ok, {_kind, list}}) -> list end) - |> List.flatten() - end) - Farmbot.Logger.debug 3, "HTTP requests took: #{time}us." - {:ok, results} - end - - def list_to_sync_cmds(list, acc \\ []) - def list_to_sync_cmds([], results), do: results - def list_to_sync_cmds([data | rest], acc) do - list_to_sync_cmds(rest, [to_sync_cmd(data) | acc]) - end - - def to_sync_cmd(%kind{} = data) do - kind = Module.split(kind) |> List.last() - Farmbot.Asset.new_sync_cmd(data.id, kind, data) - end -end diff --git a/farmbot_os/lib/celery_script/io_layer/toggle_pin.ex b/farmbot_os/lib/celery_script/io_layer/toggle_pin.ex deleted file mode 100644 index c1dcdbb5..00000000 --- a/farmbot_os/lib/celery_script/io_layer/toggle_pin.ex +++ /dev/null @@ -1,33 +0,0 @@ -defmodule Farmbot.OS.IOLayer.TogglePin do - require Farmbot.Logger - - @digital 0 - @analog 1 - - def execute(%{pin_number: num}, []) do - case Farmbot.Firmware.get_pin_value(num) do - %{value: 0, mode: @digital} -> high(num) - %{value: 1, mode: @digital} -> low(num) - %{value: _, mode: @analog} -> unknown(num) - nil -> unknown(num) - {:error, reason} when is_binary(reason) -> {:error, reason} - end - end - - defp unknown(num) do - Farmbot.Logger.warn 2, "Unknown pin value or analog pin. Writing digital low." - low(num) - end - - defp high(num) do - args = %{pin_mode: @digital, pin_number: num, pin_value: 1} - jump(args) - end - - defp low(num) do - args = %{pin_mode: @digital, pin_number: num, pin_value: 0} - jump(args) - end - - defp jump(args), do: Farmbot.OS.IOLayer.write_pin(args, []) -end diff --git a/farmbot_os/lib/celery_script/io_layer/write_pin.ex b/farmbot_os/lib/celery_script/io_layer/write_pin.ex deleted file mode 100644 index 1bb396c4..00000000 --- a/farmbot_os/lib/celery_script/io_layer/write_pin.ex +++ /dev/null @@ -1,78 +0,0 @@ -defmodule Farmbot.OS.IOLayer.WritePin do - alias Farmbot.CeleryScript.AST - alias Farmbot.Asset - alias Asset.Peripheral - require Farmbot.Logger - - @digital 0 - @analog 1 - - def execute(%{pin_number: %AST{kind: :named_pin, args: %{pin_type: "BoxLed3"}}, pin_mode: @digital, pin_value: value}, _body) do - log_success("BoxLed3", "BoxLed3", @digital, value) - Farmbot.Leds.white4(value_to_led(value)) - :ok - end - - def execute(%{pin_number: %AST{kind: :named_pin, args: %{pin_type: "BoxLed4"}}, pin_mode: @digital, pin_value: value}, _body) do - log_success("BoxLed4", "BoxLed4", @digital, value) - Farmbot.Leds.white5(value_to_led(value)) - :ok - end - - def execute(%{pin_number: %AST{kind: :named_pin} = named_pin, pin_mode: mode, pin_value: val}, _body) do - id = named_pin.args.pin_id - type = named_pin.args.pin_type - case fetch_resource(type, id) do - %Peripheral{pin: num, label: name} -> do_write(num, mode, val, name) - {:error, reason} when is_binary(reason) -> {:error, reason} - end - end - - def execute(%{pin_mode: mode, pin_value: value, pin_number: num}, []) do - case fetch_resource(nil, num) do - %Peripheral{pin: num, label: name} -> - do_write(num, mode, value, name) - {:ok, ^num} -> do_write(num, mode, value, "Pin #{num}") - {:error, reason} when is_binary(reason) -> {:error, reason} - end - end - - defp do_write(num, mode, value, msg) do - case Farmbot.Firmware.write_pin(num, mode, value) do - :ok -> - log_success(msg, num, mode, value) - :ok - {:error, reason} when is_binary(reason) -> {:error, reason} - end - end - - defp log_success(msg, _num, @digital, 1) do - Farmbot.Logger.success 1, "#{msg} turned ON" - end - - defp log_success(msg, _num, @digital, 0) do - Farmbot.Logger.success 1, "#{msg} turned OFF" - end - - defp log_success(msg, _num, @analog, val) do - Farmbot.Logger.success 1, "#{msg} set to #{val} (analog)" - end - - defp fetch_resource("Peripheral", id) do - case Asset.get_peripheral_by_id(id) do - %Peripheral{} = per -> per - nil -> {:error, "Could not find pin by id: #{id}"} - end - end - - defp fetch_resource(nil, number) do - try_lookup_peripheral(number) || {:ok, number} - end - - defp try_lookup_peripheral(number) do - Asset.get_peripheral_by_number(number) - end - - defp value_to_led(1), do: :solid - defp value_to_led(_), do: :off -end diff --git a/farmbot_os/lib/celery_script/move_absolute.ex b/farmbot_os/lib/celery_script/move_absolute.ex new file mode 100644 index 00000000..98a66d13 --- /dev/null +++ b/farmbot_os/lib/celery_script/move_absolute.ex @@ -0,0 +1,18 @@ +defmodule Farmbot.OS.IOLayer.MoveAbsolute do + @moduledoc false + + alias Farmbot.Firmware + + def execute(args, _body) do + with %{args: %{x: pos_x, y: pos_y, z: pos_z}} <- args[:location], + %{args: %{x: offset_x, y: offset_y, z: offset_z}} <- args[:offset], + x <- offset_x + pos_x / 1.0, + y <- offset_y + pos_y / 1.0, + z <- offset_z + pos_z / 1.0, + :ok <- Firmware.command({:command_movement, [x: x, y: y, z: z]}) do + :ok + else + _ -> {:error, "Firmware Error"} + end + end +end diff --git a/farmbot_os/lib/celery_script/move_relative.ex b/farmbot_os/lib/celery_script/move_relative.ex new file mode 100644 index 00000000..156deec3 --- /dev/null +++ b/farmbot_os/lib/celery_script/move_relative.ex @@ -0,0 +1,37 @@ +defmodule Farmbot.OS.IOLayer.MoveRelative do + @moduledoc false + + alias Farmbot.Firmware + + def execute(%{x: x, y: y, z: z, speed: s}, _body) do + with {:ok, {_, {:report_paramater_value, [movement_max_spd_x: max_spd_x]}}} <- + Firmware.request({:paramater_read, [:movement_max_spd_x]}), + {:ok, {_, {:report_paramater_value, [movement_max_spd_y: max_spd_y]}}} <- + Firmware.request({:paramater_read, [:movement_max_spd_y]}), + {:ok, {_, {:report_paramater_value, [movement_max_spd_z: max_spd_z]}}} <- + Firmware.request({:paramater_read, [:movement_max_spd_z]}), + {:ok, {_, {:report_position, [x: cur_x, y: cur_y, z: cur_z]}}} <- + Firmware.request({:position_read, []}) do + x_pos = x / 1.0 + cur_x + x_spd = s / 100 * max_spd_x + + y_pos = y / 1.0 + cur_y + y_spd = s / 100 * max_spd_y + + z_pos = z / 1.0 + cur_z + z_spd = s / 100 * max_spd_z + do_move(x_pos, x_spd, y_pos, y_spd, z_pos, z_spd) + else + _ -> {:error, "Firmware Error"} + end + end + + def do_move(x_pos, x_spd, y_pos, y_spd, z_pos, z_spd) do + command_args = [x: x_pos, y: y_pos, z: z_pos, a: x_spd, b: y_spd, c: z_spd] + + case Firmware.command({:command_movement, command_args}) do + :ok -> :ok + _ -> {:error, "Firmware Error"} + end + end +end diff --git a/farmbot_os/lib/celery_script/read_pin.ex b/farmbot_os/lib/celery_script/read_pin.ex new file mode 100644 index 00000000..da00f8de --- /dev/null +++ b/farmbot_os/lib/celery_script/read_pin.ex @@ -0,0 +1,16 @@ +defmodule Farmbot.OS.IOLayer.ReadPin do + @moduledoc false + + alias Farmbot.Firmware + + def execute(%{pin_num: p, pin_mode: m}, _body) + when is_integer(p) + when is_integer(m) do + with {:ok, {_, {:report_pin_value, [p: ^p, v: _]}}} <- + Firmware.request({:pin_read, p: p, m: m}) do + :ok + else + _ -> {:error, "Firmware Error"} + end + end +end diff --git a/farmbot_os/lib/celery_script/send_message.ex b/farmbot_os/lib/celery_script/send_message.ex new file mode 100644 index 00000000..15e69a9a --- /dev/null +++ b/farmbot_os/lib/celery_script/send_message.ex @@ -0,0 +1,65 @@ +defmodule Farmbot.OS.IOLayer.SendMessage do + @moduledoc false + + alias Farmbot.Firmware + @root_regex ~r/{{\s*[\w\.]+\s*}}/ + @extract_reg ~r/[\w\.]+/ + + def execute(%{message: templ, message_type: type}, channels) do + type = String.to_existing_atom(type) + + meta = [ + channels: + Enum.map(channels, fn %{kind: :channel, args: %{channel_name: nm}} -> + nm + end) + ] + + case render(templ) do + {:ok, message} -> + Farmbot.Logger.dispatch_log(__ENV__, type, 1, message, meta) + :ok + + er -> + er + end + end + + def render(templ) do + with {:ok, {_, {:report_position, pos}}} <- Firmware.request({:position_read, []}), + {:ok, pins} <- pins(Enum.to_list(0..69)), + env <- Keyword.merge(pos, pins) do + env = Map.new(env, fn {k, v} -> {to_string(k), to_string(v)} end) + + # Mini Mustache parser + data = + Regex.scan(@root_regex, templ) + |> Map.new(fn [itm] -> + [indx] = Regex.run(@extract_reg, itm) + {itm, env[indx]} + end) + + rendered = + Regex.replace(@root_regex, templ, fn d -> + Map.get(data, d) || "" + end) + + {:ok, rendered} + end + end + + def pins(nums, acc \\ []) + + def pins([p | rest], acc) do + case Firmware.request({:pin_read, [p: p]}) do + {:ok, {_, {:report_pin_value, [p: ^p, v: v]}}} -> + acc = Keyword.put(acc, :"pin#{p}", v) + pins(rest, acc) + + er -> + er + end + end + + def pins([], acc), do: {:ok, acc} +end diff --git a/farmbot_os/lib/celery_script/set_servo_angle.ex b/farmbot_os/lib/celery_script/set_servo_angle.ex new file mode 100644 index 00000000..86336428 --- /dev/null +++ b/farmbot_os/lib/celery_script/set_servo_angle.ex @@ -0,0 +1,7 @@ +defmodule Farmbot.OS.IOLayer.SetServoAngle do + @moduledoc false + + def execute(args, _body) do + {:error, "SetServoAngle Stubbed: #{inspect(args)}"} + end +end diff --git a/farmbot_os/lib/celery_script/sync.ex b/farmbot_os/lib/celery_script/sync.ex new file mode 100644 index 00000000..311837ae --- /dev/null +++ b/farmbot_os/lib/celery_script/sync.ex @@ -0,0 +1,32 @@ +defmodule Farmbot.OS.IOLayer.Sync do + @moduledoc false + require Farmbot.Logger + alias Farmbot.{Asset.Repo, Asset.Sync, API} + alias API.{Reconciler, SyncGroup} + alias Ecto.{Changeset, Multi} + + def execute(_args, _body) do + Farmbot.Logger.busy(3, "Syncing") + sync_changeset = API.get_changeset(Sync) + sync = Changeset.apply_changes(sync_changeset) + multi = Multi.new() + + :ok = Farmbot.BotState.set_sync_status("syncing") + + with {:ok, multi} <- Reconciler.sync_group(multi, sync, SyncGroup.group_1()), + {:ok, multi} <- Reconciler.sync_group(multi, sync, SyncGroup.group_2()), + {:ok, multi} <- Reconciler.sync_group(multi, sync, SyncGroup.group_3()), + {:ok, multi} <- Reconciler.sync_group(multi, sync, SyncGroup.group_4()) do + Multi.insert(multi, :syncs, sync_changeset) + |> Repo.transaction() + + Farmbot.Logger.success(3, "Synced") + :ok = Farmbot.BotState.set_sync_status("synced") + :ok + else + error -> + :ok = Farmbot.BotState.set_sync_status("sync_error") + {:error, inspect(error)} + end + end +end diff --git a/farmbot_os/lib/celery_script/toggle_pin.ex b/farmbot_os/lib/celery_script/toggle_pin.ex new file mode 100644 index 00000000..4571d39b --- /dev/null +++ b/farmbot_os/lib/celery_script/toggle_pin.ex @@ -0,0 +1,31 @@ +defmodule Farmbot.OS.IOLayer.TogglePin do + @moduledoc false + + alias Farmbot.Firmware + @mode_digital 0 + + def execute(%{pin_number: num}, _body) when is_integer(num) do + case Firmware.request({:pin_read, p: num, m: @mode_digital}) do + {:ok, {_, {:report_pin_value, [p: ^num, v: v]}}} -> do_toggle(num, v) + {:error, _} -> {:error, "Firmware Error"} + end + end + + def do_toggle(num, 0) when is_integer(num) do + command(num, 1) + end + + def do_toggle(num, _) when is_integer(num) do + command(num, 0) + end + + def command(num, val) when is_integer(num) do + with :ok <- Firmware.command({:pin_write, [p: num, m: @mode_digital, v: val]}), + {:ok, {_, {:report_pin_value, [p: ^num, v: ^val]}}} <- + Firmware.request({:pin_read, p: num, m: @mode_digital}) do + :ok + else + _ -> {:error, "Ffirmware Error"} + end + end +end diff --git a/farmbot_os/lib/celery_script/write_pin.ex b/farmbot_os/lib/celery_script/write_pin.ex new file mode 100644 index 00000000..f25a18c6 --- /dev/null +++ b/farmbot_os/lib/celery_script/write_pin.ex @@ -0,0 +1,15 @@ +defmodule Farmbot.OS.IOLayer.WritePin do + @moduledoc false + + alias Farmbot.Firmware + + def execute(%{pin_number: p, pin_mode: m, pin_value: v}, _body) when is_integer(p) do + with :ok <- Firmware.command({:pin_write, [p: p, m: m, v: v]}), + {:ok, {_, {:report_pin_value, [p: ^p, v: ^v]}}} <- + Firmware.request({:pin_read, p: p, m: m}) do + :ok + else + _ -> {:error, "Firmware Error"} + end + end +end diff --git a/farmbot_os/lib/celery_script/zero.ex b/farmbot_os/lib/celery_script/zero.ex new file mode 100644 index 00000000..ce67738d --- /dev/null +++ b/farmbot_os/lib/celery_script/zero.ex @@ -0,0 +1,22 @@ +defmodule Farmbot.OS.IOLayer.Zero do + @moduledoc false + + alias Farmbot.Firmware + + def execute(%{axis: "all"} = args, body) do + with :ok <- execute(%{args | axis: "z"}, body), + :ok <- execute(%{args | axis: "y"}, body), + :ok <- execute(%{args | axis: "x"}, body) do + :ok + end + end + + def execute(%{axis: axis}, _body) do + command = {:position_write_zero, [String.to_existing_atom(axis)]} + + case Firmware.command(command) do + :ok -> :ok + _ -> {:error, "Firmware Error"} + end + end +end diff --git a/farmbot_os/lib/farmbot_os.ex b/farmbot_os/lib/farmbot_os.ex index f6517a3d..55d9830d 100644 --- a/farmbot_os/lib/farmbot_os.ex +++ b/farmbot_os/lib/farmbot_os.ex @@ -10,10 +10,8 @@ defmodule Farmbot.OS do {Farmbot.System.Init.Supervisor, []}, {Farmbot.System.CoreStart, []}, {Farmbot.Platform.Supervisor, []}, - {Farmbot.System.UpdateTimer, []}, {Farmbot.System.ExtStart, []}, {Farmbot.EasterEggs, []}, - {Farmbot.FarmwareMigration, []} ] opts = [strategy: :one_for_one, name: __MODULE__] Supervisor.start_link(children, opts) diff --git a/farmbot_os/lib/farmware_migration.ex b/farmbot_os/lib/farmware_migration.ex deleted file mode 100644 index 09762a0c..00000000 --- a/farmbot_os/lib/farmware_migration.ex +++ /dev/null @@ -1,100 +0,0 @@ -defmodule Farmbot.FarmwareMigration do - # TODO(Connor) 2018-08-14 Delete this after 6.5.0 is released - import Farmbot.Config, only: [get_config_value: 3, update_config_value: 4] - require Farmbot.Logger - alias Farmbot.Asset.{FarmwareEnv, FarmwareInstallation} - - @doc false - def child_spec(_) do - %{ - id: __MODULE__, - start: {__MODULE__, :migrate, []}, - type: :worker, - restart: :transient, - shutdown: 500 - } - end - - - @data_path Application.get_env(:farmbot_ext, :data_path) - @farmware_dir Path.join(@data_path, "farmware") - - def migrate do - File.mkdir_p(@farmware_dir) - if get_config_value(:bool, "settings", "firmware_needs_migration") do - Farmbot.Logger.busy 1, "Starting Farmware migration." - migrate_user_env() - migrate_farmware_installations() - update_config_value(:bool, "settings", "firmware_needs_migration", false) - Farmbot.Logger.success 1, "Farmware migrated to the API." - else - Farmbot.Logger.debug 3, "Farmware has already been migrated." - end - :ignore - end - - # takes every key/value pair in user_env and turns it - # into a Farmbot.Asset.FarmwareEnv record. - def migrate_user_env do - full_qry = """ - SELECT sv.value FROM groups g - LEFT JOIN configs c on (c.group_id == g.id) AND (c.key == "user_env") - LEFT JOIN string_values sv on sv.id == c.string_value_id - WHERE g.group_name == "settings" - """ - %{rows: [[value]]} = Ecto.Adapters.SQL.query!(Farmbot.Config.Repo, full_qry, []) - - value - |> Farmbot.JSON.decode!() - |> Enum.map(fn({key, val}) -> - %FarmwareEnv{key: key, value: val} - end) - |> Enum.each(fn(env) -> - case Farmbot.HTTP.new_farmware_env(env) do - {:ok, _} -> - Farmbot.Logger.success 1, "migration task: migrate_user_env '#{env.key}' completed." - {:error, _} -> - Farmbot.Logger.error 1, "migration task: migrate_user_env: '#{env.key}' failed." - end - end) - - update_config_value(:string, "settings", "user_env", nil) - end - - def migrate_farmware_installations do - # Migrate first party stuff. - qry = "SELECT manifests FROM farmware_repositories" - %{rows: rows} = Ecto.Adapters.SQL.query!(Farmbot.Config.Repo, qry, []) - - repos = Enum.map(rows, &Farmbot.JSON.decode/1) - migrated = repos |> Enum.map(&migrate_repo/1) |> List.flatten() - - drop_qry = "DROP TABLE [IF EXISTS] farmware_repositories" - Ecto.Adapters.SQL.query!(Farmbot.Config.Repo, qry, []) - - # Migrate installed farmwares. - @farmware_dir - |> File.ls!() - |> Kernel.--(migrated) - |> Enum.map(&Path.join([@farmware_dir, &1, "manifest.json"])) - |> Enum.map(&File.read!/1) - |> Enum.map(&Farmbot.JSON.decode!/1) - |> Enum.map(&Map.take(&1, ["url", "package"])) - |> Enum.map(&Map.new/1) - |> migrate_repo() - end - - def migrate_repo(list, acc \\ []) - def migrate_repo([], acc), do: Enum.reverse(acc) - def migrate_repo([%{"manifest" => url, "name" => name} | rest], acc) do - first_party? = match?("https://raw.githubusercontent.com/FarmBot-Labs/farmware_manifests/master/" <> _, url) - data = %FarmwareInstallation{url: url, first_party: first_party?} - case Farmbot.HTTP.new_farmware_installation(data) do - {:ok, _} -> - Farmbot.Logger.success 1, "migration task: migrate_farmware '#{name}' completed." - {:error, _} -> - Farmbot.Logger.error 1, "migration task: migrate_farmware: '#{name}' failed." - end - migrate_repo(rest, [name | acc]) - end -end diff --git a/farmbot_os/lib/filesystem.ex b/farmbot_os/lib/filesystem.ex new file mode 100644 index 00000000..b5cc5111 --- /dev/null +++ b/farmbot_os/lib/filesystem.ex @@ -0,0 +1,9 @@ +defmodule Farmbot.OS.FileSystem do + @data_path Application.get_env(:farmbot_os, __MODULE__)[:data_path] + @data_path || Mix.raise(""" + config :farmbot_os, Farmbot.OS.Filesystem, + data_path: "/path/to/folder" + """) + + def data_path, do: @data_path +end \ No newline at end of file diff --git a/farmbot_os/lib/init/fs_checkup.ex b/farmbot_os/lib/init/fs_checkup.ex index c81acf0a..d6de467a 100644 --- a/farmbot_os/lib/init/fs_checkup.ex +++ b/farmbot_os/lib/init/fs_checkup.ex @@ -3,8 +3,7 @@ defmodule Farmbot.System.Init.FSCheckup do use Supervisor require Logger - @data_path Application.get_env(:farmbot_ext, :data_path) - @data_path || Mix.raise("Unconfigured data path.") + @data_path Farmbot.OS.FileSystem.data_path() @ref Farmbot.Project.commit() @version Farmbot.Project.version() diff --git a/farmbot_os/lib/mix/tasks.farmbot/factory_reset.ex b/farmbot_os/lib/mix/tasks.farmbot/factory_reset.ex deleted file mode 100644 index 59931b45..00000000 --- a/farmbot_os/lib/mix/tasks.farmbot/factory_reset.ex +++ /dev/null @@ -1,10 +0,0 @@ -defmodule Mix.Tasks.Farmbot.FactoryReset do - @moduledoc "Helper task for resetting development environment." - use Mix.Config - - def run([]) do - # Application.ensure_all_started(:farmbot_os) - Farmbot.System.factory_reset("Mix task") - :init.stop() - end -end diff --git a/farmbot_os/lib/platform_supervisor.ex b/farmbot_os/lib/platform_supervisor.ex index 807eeda6..0999f076 100644 --- a/farmbot_os/lib/platform_supervisor.ex +++ b/farmbot_os/lib/platform_supervisor.ex @@ -8,7 +8,7 @@ defmodule Farmbot.Platform.Supervisor do end def init([]) do - platform_children = Application.get_env(:farmbot_os, :platform_children) + platform_children = Application.get_env(:farmbot_os, __MODULE__)[:platform_children] Supervisor.init(platform_children, strategy: :one_for_all) end end diff --git a/farmbot_os/lib/system.ex b/farmbot_os/lib/system.ex index 8270a4b7..04b49c58 100644 --- a/farmbot_os/lib/system.ex +++ b/farmbot_os/lib/system.ex @@ -4,17 +4,14 @@ defmodule Farmbot.System do """ require Farmbot.Logger - require Farmbot.Logger - error_msg = """ - Please configure `:system_tasks` and `:data_path`! + Please configure `:system_tasks`! """ - @system_tasks Application.get_env(:farmbot_os, :behaviour)[:system_tasks] + @system_tasks Application.get_env(:farmbot_os, __MODULE__)[:system_tasks] @system_tasks || Mix.raise(error_msg) - @data_path Application.get_env(:farmbot_ext, :data_path) - @data_path || Mix.raise(error_msg) + @data_path Farmbot.OS.FileSystem.data_path() @doc "Restarts the machine." @callback reboot() :: any @@ -34,7 +31,7 @@ defmodule Farmbot.System do defp try_lock_fw do if Process.whereis(Farmbot.Firmware) do Farmbot.Logger.warn 1, "Trying to emergency lock firmware before powerdown" - Farmbot.Firmware.emergency_lock() + Farmbot.Firmware.command({:command_emergency_lock, []}) else Farmbot.Logger.error 1, "Firmware unavailable. Can't emergency_lock" end diff --git a/farmbot_os/lib/updates/updates.ex b/farmbot_os/lib/updates/updates.ex deleted file mode 100644 index 020ea40b..00000000 --- a/farmbot_os/lib/updates/updates.ex +++ /dev/null @@ -1,352 +0,0 @@ -defmodule Farmbot.System.Updates do - @moduledoc "Handles over the air updates." - - use Supervisor - require Farmbot.Logger - alias Farmbot.Config - import Config, only: [get_config_value: 3, update_config_value: 4] - - @data_path Application.get_env(:farmbot_ext, :data_path) - @target Farmbot.Project.target() - @current_version Farmbot.Project.version() - @env Farmbot.Project.env() - - @update_handler Application.get_env(:farmbot_os, :behaviour)[:update_handler] - @update_handler || Mix.raise("Please configure update_handler") - - - @doc "Overwrite os update server field" - def override_update_server(url) do - update_config_value(:string, "settings", "os_update_server_overwrite", url) - end - - defmodule Release do - @moduledoc false - defmodule Asset do - @moduledoc false - defstruct [:name, :browser_download_url] - end - - defstruct [ - tag_name: nil, - target_commitish: nil, - name: nil, - draft: false, - prerelease: true, - body: nil, - assets: [] - ] - - @doc "takes a map with string or atom keys and returns a struct." - def decode_map(%{} = map) do - Map.take(map, keys()) - |> Map.new(fn({key, val}) -> - {String.to_atom(key), val} - end) - |> Map.update(:assets, [], fn(assets) -> - Enum.map(assets, fn(asset) -> - data = Map.take(asset, asset_keys()) - |> Enum.map(fn({key, val}) -> - {String.to_atom(key), val} - end) - struct(Asset, data) - end) - end) - end - - def keys do - [:tag_name, :target_commitish, :name, :draft, :prerelease, :body, :assets] ++ - ["tag_name", "target_commitish", "name", "draft", "prerelease", "body", "assets"] - end - - def asset_keys do - [:name, :browser_download_url] ++ - ["name", "browser_download_url"] - end - end - - defmodule CurrentStuff do - @moduledoc false - import Farmbot.Project - defstruct [ - :token, - :beta_opt_in, - :os_update_server_overwrite, - :currently_on_beta, - :env, - :commit, - :target, - :version - ] - - @doc "Get the current stuff. Fields can be replaced for testing." - def get(replace \\ %{}) do - os_update_server_overwrite = get_config_value(:string, "settings", "os_update_server_overwrite") - beta_opt_in? = is_binary(os_update_server_overwrite) || get_config_value(:bool, "settings", "beta_opt_in") - token_bin = get_config_value(:string, "authorization", "token") - currently_on_beta? = get_config_value(:bool, "settings", "currently_on_beta") - token = if token_bin, do: Farmbot.Jwt.decode!(token_bin), else: nil - opts = %{ - token: token, - beta_opt_in: beta_opt_in?, - currently_on_beta: currently_on_beta?, - os_update_server_overwrite: os_update_server_overwrite, - env: env(), - commit: commit(), - target: target(), - version: version() - } |> Map.merge(Map.new(replace)) - struct(__MODULE__, opts) - end - end - - @doc "Downloads and applies an update file." - def download_and_apply_update({%Version{} = version, dl_url}) do - if @update_handler.requires_reboot?() do - Farmbot.Logger.warn 1, "Can't apply update. An update is already staged. Please reboot and try again." - {:error, :reboot_required} - else - fe_constant = "FBOS_OTA" - dl_fun = Farmbot.BotState.download_progress_fun(fe_constant) - # TODO(Connor): I'd like this to have a version number.. - dl_path = Path.join(@data_path, "ota.fw") - results = http_adapter().download_file(dl_url, dl_path, dl_fun, "", []) - Farmbot.BotState.clear_progress_fun(fe_constant) - case results do - {:ok, path} -> apply_firmware("beta" in (version.pre || []), path, true) - {:error, reason} -> {:error, reason} - end - end - end - - @doc """ - Force check for updates. - Does _NOT_ download or apply update. - """ - def check_updates(release \\ nil, current_stuff \\ nil) - - # All the HTTP Requests happen here. - def check_updates(nil, current_stuff) do - # Get current values. - current_stuff_mut = %{ - token: token, - beta_opt_in: beta_opt_in, - os_update_server_overwrite: server_override, - env: env, - } = current_stuff || CurrentStuff.get() - - cond do - # Don't allow non producion envs to check production env updates. - env != :prod -> {:error, :wrong_env} - # Don't check if the token is nil. - is_nil(token) -> {:error, :no_token} - # Allows the server to be overwrote. - is_binary(server_override) -> - Farmbot.Logger.debug 3, "Update server override: #{server_override}" - get_release_from_url(server_override) - # Beta updates should check twice. - beta_opt_in -> - Farmbot.Logger.debug 3, "Checking for beta updates." - token - |> Map.get(:beta_os_update_server) - |> get_release_from_url() - # Conditions exhausted. We _must_ be on a production release. - true -> - Farmbot.Logger.debug 3, "Checking for production updates." - token - |> Map.get(:os_update_server) - |> get_release_from_url() - end - |> case do - # Beta needs to make two requests: - # check for a later beta update, if no later beta update, - # Check for a later production release. - %Release{} = release when beta_opt_in -> - do_check_production_release = fn() -> - token - |> Map.get(:os_update_server) - |> get_release_from_url() - |> case do - %Release{} = prod_release -> check_updates(prod_release, current_stuff_mut) - err -> err - end - end - check_updates(release, current_stuff_mut) || do_check_production_release.() - # Production release; no beta. Check the release for an asset. - %Release{} = release -> check_updates(release, current_stuff_mut) - err -> err - end - end - - # Check against the release struct. Not HTTP requests from here out. - def check_updates(%Release{} = rel, %CurrentStuff{} = current_stuff) do - %{ - beta_opt_in: beta_opt_in, - currently_on_beta: currently_on_beta, - commit: current_commit, - version: current_version, - } = current_stuff - - release_version = String.trim(rel.tag_name, "v") |> Version.parse!() - is_beta_release? = "beta" in (release_version.pre || []) - version_comp = Version.compare(current_version, release_version) - - release_commit = rel.target_commitish - commits_equal? = current_commit == release_commit - - prerelease = rel.prerelease - cond do - # Don't bother if the release is a draft. Not sure how/if this can happen. - rel.draft -> - Farmbot.Logger.warn 1, "Not checking draft release." - nil - - # Only check prerelease if - # current_version is less than or equal to release_version - # AND - # the commits are not equal. - prerelease and is_beta_release? and beta_opt_in and !commits_equal? -> - # beta release get marked as greater than non beta release, so we need - # to manually check the versions by removing the pre part. - case Version.compare(current_version, %{release_version | pre: []}) do - :lt -> - Farmbot.Logger.debug 3, "Current version (#{current_version}) is less than beta release (#{release_version})" - try_find_dl_url_in_asset(rel.assets, release_version, current_stuff) - :eq -> - if currently_on_beta do - Farmbot.Logger.debug 3, "Current version (#{current_version}) is equal to beta release (#{release_version})" - try_find_dl_url_in_asset(rel.assets, release_version, current_stuff) - else - Farmbot.Logger.debug 3, "Current version (#{current_version}) is greater than equal to latest beta (#{release_version})" - nil - end - - :gt -> - Farmbot.Logger.debug 3, "Current version (#{current_version}) is greater than latest beta release (#{release_version})" - nil - end - - # if the current version is less than the release version. - !prerelease and version_comp == :lt -> - Farmbot.Logger.debug 3, "Current version is less than release." - try_find_dl_url_in_asset(rel.assets, release_version, current_stuff) - - # If the version isn't different, but the commits are different, - # This happens for beta releases. - !prerelease and version_comp == :eq and !commits_equal? -> - Farmbot.Logger.debug 3, "Current version is equal to release, but commits are not equal." - try_find_dl_url_in_asset(rel.assets, release_version, current_stuff) - - # Conditions exhausted. No updates available. - true -> - comparison_str = "version check: current version: #{current_version} #{version_comp} latest release version: #{release_version} \n"<> - "commit check: current commit: #{current_commit} latest release commit: #{release_commit}: (equal: #{commits_equal?})" - - Farmbot.Logger.debug 3, "No updates available: \ntarget: #{Farmbot.Project.target()}: \nprerelease: #{prerelease} \n#{comparison_str}" - nil - end - end - - @doc "Finds a asset url if it exists, nil if not." - @spec try_find_dl_url_in_asset([%Release.Asset{}], Version.t, %CurrentStuff{}) :: {Version.t, String.t} - def try_find_dl_url_in_asset(assets, version, current_stuff) - - def try_find_dl_url_in_asset([%Release.Asset{name: name, browser_download_url: bdurl} | rest], %Version{} = release_version_obj, current_stuff) do - release_version = to_string(release_version_obj) - current_target = to_string(current_stuff.target) - expected_name = "farmbot-#{current_target}-#{release_version}.fw" - if match?(^expected_name, name) do - {release_version_obj, bdurl} - else - Farmbot.Logger.debug 3, "Incorrect asset name for target: #{current_target}: #{name}" - try_find_dl_url_in_asset(rest, release_version_obj, current_stuff) - end - end - - def try_find_dl_url_in_asset([], release_version, current_stuff) do - Farmbot.Logger.warn 2, "No update in assets for #{current_stuff.target()} for #{release_version}" - nil - end - - @doc "HTTP request to fetch a Release." - def get_release_from_url(url) when is_binary(url) do - Farmbot.Logger.debug 3, "Checking for updates: #{url}" - case http_adapter().get(url) do - # This can happen on beta updates, if on an old token - # and a new beta release was published. - {:ok, %{status_code: 404}} -> - Farmbot.Logger.warn 1, "Got a 404 checking for updates: #{url}. Fetching a new token. Try that again" - Farmbot.Bootstrap.AuthTask.force_refresh() - {:error, :token_refresh} - - # Decode the HTTP body as a release. - {:ok, %{status_code: 200, body: body}} -> - case Farmbot.JSON.decode!(body) |> Release.decode_map() do - {:ok, %Release{} = rel} -> rel - _err -> {:error, :bad_release_body} - end - - # Error situations - {:ok, %{status_code: _code, body: body}} -> {:error, body} - err -> err - end - end - - @doc "Apply an OS (fwup) firmware." - def apply_firmware(is_beta?, file_path, reboot) when is_boolean(is_beta?) do - Farmbot.Logger.busy 1, "Applying #{@target} OS update (beta=#{is_beta?})" - before_update() - case @update_handler.apply_firmware(file_path) do - :ok -> - update_config_value(:bool, "settings", "currently_on_beta", is_beta?) - Farmbot.Logger.success 1, "OS Firmware updated!" - if reboot do - Farmbot.Logger.warn 1, "Farmbot going down for OS update." - Farmbot.System.reboot("OS Firmware Update.") - end - {:error, reason} -> - Farmbot.Logger.error 1, "Failed to apply update: #{inspect reason}" - {:error, reason} - end - end - - # Private - - defp maybe_post_update do - case File.read(update_file()) do - {:ok, @current_version} -> :ok - {:ok, old_version} -> - Farmbot.Logger.info 1, "Updating from #{old_version} to #{@current_version}" - @update_handler.post_update() - {:error, :enoent} -> - Farmbot.Logger.info 1, "Updating to #{@current_version}" - {:error, err} -> raise err - end - before_update() - end - - defp before_update, do: File.write!(update_file(), @current_version) - - defp update_file, do: Path.join(@data_path, "update") - - defp http_adapter, do: Farmbot.HTTP - - @doc false - def start_link do - Supervisor.start_link(__MODULE__, [], [name: __MODULE__]) - end - - @doc false - def init([]) do - case @update_handler.setup(@env) do - :ok -> - maybe_post_update() - children = [ - worker(Farmbot.System.UpdateTimer, []) - ] - opts = [strategy: :one_for_one] - supervise(children, opts) - {:error, reason} -> {:stop, reason} - end - end -end diff --git a/farmbot_os/mix.exs b/farmbot_os/mix.exs index 1c428440..65219f1c 100644 --- a/farmbot_os/mix.exs +++ b/farmbot_os/mix.exs @@ -53,42 +53,37 @@ defmodule Farmbot.OS.MixProject do {:shoehorn, "~> 0.4"}, {:logger_backend_sqlite, "~> 2.2"}, {:farmbot_core, path: "../farmbot_core", env: Mix.env()}, - {:farmbot_ext, path: "../farmbot_ext", env: Mix.env()}, + {:farmbot_ext, path: "../farmbot_ext", env: Mix.env()} ] ++ deps(@target) end # Specify target specific dependencies - defp deps("host"), do: [ - {:excoveralls, "~> 0.10", only: [:test]}, - {:dialyxir, "~> 1.0.0-rc.3", only: [:dev], runtime: false}, - {:ex_doc, "~> 0.19", only: [:dev], runtime: false}, - ] + defp deps("host"), + do: [ + {:excoveralls, "~> 0.10", only: [:test]}, + {:dialyxir, "~> 1.0.0-rc.3", only: [:dev], runtime: false}, + {:ex_doc, "~> 0.19", only: [:dev], runtime: false} + ] defp deps(target) do [ # Configurator {:cowboy, "~> 2.5"}, {:plug, "~> 1.6"}, - {:cors_plug, "~> 1.5"}, + # override: true because AMQP + {:ranch, "~> 1.5", override: true}, + {:cors_plug, "~> 2.0"}, {:phoenix_html, "~> 2.12"}, - - # AMQP hacks - {:jsx, "~> 2.9", override: true}, - {:ranch, "~> 1.6", override: true}, - {:ranch_proxy_protocol, "~> 2.1", override: true}, - # End hacks - {:nerves_runtime, "~> 0.8"}, {:nerves_network, "~> 0.3"}, {:nerves_wpa_supplicant, "~> 0.3"}, - {:nerves_firmware, "~> 0.4"}, {:nerves_time, "~> 0.2"}, {:nerves_hub, "~> 0.2"}, {:dhcp_server, "~> 0.6"}, {:mdns, "~> 1.0"}, {:nerves_firmware_ssh, "~> 0.3"}, {:nerves_init_gadget, "~> 0.5", only: :dev}, - {:elixir_ale, "~> 1.1"}, + {:elixir_ale, "~> 1.2"} ] ++ system(target) end diff --git a/farmbot_os/mix.lock.host b/farmbot_os/mix.lock.host index 204806ed..0ae2b54c 100644 --- a/farmbot_os/mix.lock.host +++ b/farmbot_os/mix.lock.host @@ -5,6 +5,8 @@ "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.6.0", "bfd84d90ff966e1f5d4370bdd3943432d8f65f07d3bab48001aebd7030590dcc", [:mix], [], "hexpm"}, "dialyxir": {:hex, :dialyxir, "1.0.0-rc.3", "774306f84973fc3f1e2e8743eeaa5f5d29b117f3916e5de74c075c02f1b8ef55", [:mix], [], "hexpm"}, @@ -12,22 +14,19 @@ "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.1", "407d50ac8fc63dfee9175ccb4548e6c5512b5052afa63eedb9cd452a32a91495", [:mix], [{:hackney, "~> 1.13", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, - "fs": {:hex, :fs, "3.4.0", "6d18575c250b415b3cad559e6f97a4c822516c7bc2c10bfbb2493a8f230f5132", [:rebar3], [], "hexpm"}, - "gen_stage": {:hex, :gen_stage, "0.14.0", "65ae78509f85b59d360690ce3378d5096c3130a0694bab95b0c4ae66f3008fad", [:mix], [], "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"}, "gettext": {:hex, :gettext, "0.16.0", "4a7e90408cef5f1bf57c5a39e2db8c372a906031cc9b1466e963101cb927dafc", [:mix], [], "hexpm"}, "goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], [], "hexpm"}, "hackney": {:hex, :hackney, "1.15.0", "287a5d2304d516f63e56c469511c42b016423bcb167e61b611f6bad47e3ca60e", [: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"}, - "httpoison": {:hex, :httpoison, "1.3.1", "7ac607311f5f706b44e8b3fab736d0737f2f62a31910ccd9afe7227b43edb7f0", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, 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"}, "joken": {:hex, :joken, "1.5.0", "42a0953e80bd933fc98a0874e156771f78bf0e92abe6c3a9c22feb6da28efb0b", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"}, "jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"}, "jsx": {:hex, :jsx, "2.8.2", "7acc7d785b5abe8a6e9adbde926a24e481f29956dd8b4df49e3e4e7bcc92a018", [:mix, :rebar3], [], "hexpm"}, "lager": {:hex, :lager, "3.6.3", "fe78951d174616273f87f0dbc3374d1430b1952e5efc4e1c995592d30a207294", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, repo: "hexpm", optional: false]}], "hexpm"}, - "logger_backend_ecto": {:hex, :logger_backend_ecto, "1.3.0", "6bb1a9d2b0ac1ee04049df94e49ea469f1f0db774d2fa05d673dc796a5ad9ed7", [:mix], [{:sqlite_ecto2, "~> 2.2", [hex: :sqlite_ecto2, repo: "hexpm", optional: true]}], "hexpm"}, "logger_backend_sqlite": {:hex, :logger_backend_sqlite, "2.2.0", "3d3529e7425aede462478896be303acc53c8b195eeef5383fd601b42b809a73e", [:mix], [{:esqlite, "~> 0.2.4", [hex: :esqlite, repo: "hexpm", optional: false]}], "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"}, @@ -48,17 +47,16 @@ "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"}, "rabbit_common": {:hex, :rabbit_common, "3.7.8", "e1410371c5814f85092b6dda85aa25fad945e7a41a9c4e34a59e61bd59a8c3b2", [:make, :rebar3], [{:jsx, "2.8.2", [hex: :jsx, repo: "hexpm", optional: false]}, {:lager, "3.6.3", [hex: :lager, repo: "hexpm", optional: false]}, {:ranch, "1.5.0", [hex: :ranch, repo: "hexpm", optional: false]}, {:ranch_proxy_protocol, "1.5.0", [hex: :ranch_proxy_protocol, repo: "hexpm", optional: false]}, {:recon, "2.3.2", [hex: :recon, repo: "hexpm", optional: false]}], "hexpm"}, - "ranch": {:hex, :ranch, "1.5.0", "f04166f456790fee2ac1aa05a02745cc75783c2bfb26d39faf6aefc9a3d3a58a", [:rebar3], [], "hexpm"}, - "ranch_proxy_protocol": {:hex, :ranch_proxy_protocol, "2.0.0", "623c732025f9d66d123a8ccc1735e5f43d7eb9b20aa09457c9609ef05f7e8ace", [:rebar3], [{:ranch, "1.5.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, + "ranch": {:hex, :ranch, "1.6.2", "6db93c78f411ee033dbb18ba8234c5574883acb9a75af0fb90a9b82ea46afa00", [:rebar3], [], "hexpm"}, + "ranch_proxy_protocol": {:hex, :ranch_proxy_protocol, "2.1.1", "3c4723327166d2d63c0405f4914e2e471c6de362cc844e9b203af5763e7c9d25", [:rebar3], [{:ranch, "1.6.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, "recon": {:hex, :recon, "2.3.2", "4444c879be323b1b133eec5241cb84bd3821ea194c740d75617e106be4744318", [:rebar3], [], "hexpm"}, - "rsa": {:hex, :rsa, "0.0.1", "a63069f88ce342ffdf8448b7cdef4b39ba7dee3c1510644a39385c7e63ba246f", [:mix], [], "hexpm"}, "sbroker": {:hex, :sbroker, "1.0.0", "28ff1b5e58887c5098539f236307b36fe1d3edaa2acff9d6a3d17c2dcafebbd0", [:rebar3], [], "hexpm"}, "shoehorn": {:hex, :shoehorn, "0.4.0", "f3830e22e1c58b502e8c436623804c4eb6ed15f5d0bdbacdeb448cddf4795951", [:mix], [{:distillery, "~> 2.0", [hex: :distillery, repo: "hexpm", optional: false]}], "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"}, "tesla": {:hex, :tesla, "1.2.1", "864783cc27f71dd8c8969163704752476cec0f3a51eb3b06393b3971dc9733ff", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"}, - "timex": {:hex, :timex, "3.4.1", "e63fc1a37453035e534c3febfe9b6b9e18583ec7b37fd9c390efdef97397d70b", [: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"}, + "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"}, "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm"}, diff --git a/farmbot_os/mix.lock.rpi b/farmbot_os/mix.lock.rpi new file mode 100644 index 00000000..99f8f098 --- /dev/null +++ b/farmbot_os/mix.lock.rpi @@ -0,0 +1,102 @@ +%{ + "amqp": {:hex, :amqp, "1.0.3", "06a6d909abc71d82b7c3133ca491899ca18fce857d0697dd060c29de1ef498d8", [:mix], [{:amqp_client, "~> 3.7.3", [hex: :amqp_client, repo: "hexpm", optional: false]}, {:goldrush, "~> 0.1.0", [hex: :goldrush, repo: "hexpm", optional: false]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: false]}, {:lager, "~> 3.5", [hex: :lager, repo: "hexpm", optional: false]}, {:rabbit_common, "~> 3.7.3", [hex: :rabbit_common, repo: "hexpm", optional: false]}, {:ranch, "~> 1.4", [hex: :ranch, repo: "hexpm", optional: false]}, {:ranch_proxy_protocol, "~> 1.4", [hex: :ranch_proxy_protocol, repo: "hexpm", optional: false]}, {:recon, "~> 2.3.2", [hex: :recon, repo: "hexpm", optional: false]}], "hexpm"}, + "amqp_client": {:hex, :amqp_client, "3.7.8", "5ec44ad152aed8519ef557189fa21e779f60578d21bcab36cabe381b451728ee", [:make, :rebar3], [{:rabbit_common, "3.7.8", [hex: :rabbit_common, repo: "hexpm", optional: false]}], "hexpm"}, + "apex": {:hex, :apex, "1.2.0", "2da5035ef5fc59da2c24187a880b1b6d38b6b67c19b89a6265f5986ff0e85b1f", [:mix], [], "hexpm"}, + "artificery": {:hex, :artificery, "0.4.0", "e0b8d3eb9dfe8f42c08a620f90a2aa9cef5dba9fcdfcecad5c2be451df159a77", [:mix], [], "hexpm"}, + "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"}, + "bbmustache": {:hex, :bbmustache, "1.6.1", "9fb63fa60bd53afbf47f02e6d8bd6b2beafc068e02e20975254dc7461fd4f397", [:rebar3], [], "hexpm"}, + "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"}, + "cors_plug": {:hex, :cors_plug, "2.0.0", "238ddb479f92b38f6dc1ae44b8d81f0387f9519101a6da442d543ab70ee0e482", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, + "cowboy": {:hex, :cowboy, "2.6.1", "f2e06f757c337b3b311f9437e6e072b678fcd71545a7b2865bdaa154d078593f", [:rebar3], [{:cowlib, "~> 2.7.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, + "cowlib": {:hex, :cowlib, "2.7.0", "3ef16e77562f9855a2605900cedb15c1462d76fb1be6a32fc3ae91973ee543d2", [: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.6.0", "bfd84d90ff966e1f5d4370bdd3943432d8f65f07d3bab48001aebd7030590dcc", [:mix], [], "hexpm"}, + "dhcp_server": {:hex, :dhcp_server, "0.6.0", "6cc0cf110b8d112455f033ae49eda570e9aeeb42a2fd1c79cc437835ecaa0716", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, + "dialyxir": {:hex, :dialyxir, "1.0.0-rc.3", "774306f84973fc3f1e2e8743eeaa5f5d29b117f3916e5de74c075c02f1b8ef55", [:mix], [], "hexpm"}, + "distillery": {:hex, :distillery, "2.0.10", "e9f1f1d3f4a89996a3e1a555872feed8a3a73e3d10b51886941382d29ca58f99", [:mix], [{:artificery, "~> 0.2", [hex: :artificery, repo: "hexpm", optional: false]}], "hexpm"}, + "dns": {:hex, :dns, "2.1.2", "81c46d39f7934f0e73368355126e4266762cf227ba61d5889635d83b2d64a493", [:mix], [{:socket, "~> 0.3.13", [hex: :socket, 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_ale": {:hex, :elixir_ale, "1.2.1", "07ac2f17a0191b8bd3b0df6b526c7f699a3a4d690c9def573fcb5824eef24d98", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, + "elixir_make": {:hex, :elixir_make, "0.4.2", "332c649d08c18bc1ecc73b1befc68c647136de4f340b548844efc796405743bf", [:mix], [], "hexpm"}, + "esqlite": {:hex, :esqlite, "0.2.4", "3a8a352c190afe2d6b828b252a6fbff65b5cc1124647f38b15bdab3bf6fd4b3e", [:rebar3], [], "hexpm"}, + "farmbot_system_rpi": {:hex, :farmbot_system_rpi, "1.6.1-farmbot.1", "f79b79dad5e36a9e888d97b261864c399f0461f1abd914491e54ab0326c53d34", [:mix], [{:nerves, "~> 1.3", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "1.6.5", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_system_linter, "~> 0.3.0", [hex: :nerves_system_linter, repo: "hexpm", optional: false]}, {:nerves_toolchain_armv6_rpi_linux_gnueabi, "1.1.0", [hex: :nerves_toolchain_armv6_rpi_linux_gnueabi, repo: "hexpm", optional: false]}], "hexpm"}, + "fs": {:hex, :fs, "3.4.0", "6d18575c250b415b3cad559e6f97a4c822516c7bc2c10bfbb2493a8f230f5132", [:rebar3], [], "hexpm"}, + "fwup": {:hex, :fwup, "0.3.0", "2c360815565fcbc945ebbb34b58f156efacb7f8d64766f1cb3426919bb3f41ea", [:mix], [], "hexpm"}, + "gen_stage": {:hex, :gen_stage, "0.14.0", "65ae78509f85b59d360690ce3378d5096c3130a0694bab95b0c4ae66f3008fad", [:mix], [], "hexpm"}, + "gettext": {:hex, :gettext, "0.16.0", "4a7e90408cef5f1bf57c5a39e2db8c372a906031cc9b1466e963101cb927dafc", [:mix], [], "hexpm"}, + "goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], [], "hexpm"}, + "hackney": {:hex, :hackney, "1.15.0", "287a5d2304d516f63e56c469511c42b016423bcb167e61b611f6bad47e3ca60e", [: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"}, + "httpoison": {:hex, :httpoison, "1.3.1", "7ac607311f5f706b44e8b3fab736d0737f2f62a31910ccd9afe7227b43edb7f0", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, 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"}, + "joken": {:hex, :joken, "1.5.0", "42a0953e80bd933fc98a0874e156771f78bf0e92abe6c3a9c22feb6da28efb0b", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"}, + "jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"}, + "jsx": {:hex, :jsx, "2.8.2", "7acc7d785b5abe8a6e9adbde926a24e481f29956dd8b4df49e3e4e7bcc92a018", [:mix, :rebar3], [], "hexpm"}, + "lager": {:hex, :lager, "3.6.3", "fe78951d174616273f87f0dbc3374d1430b1952e5efc4e1c995592d30a207294", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, repo: "hexpm", optional: false]}], "hexpm"}, + "lager_logger": {:hex, :lager_logger, "1.0.5", "2b58be52fe1e0fb82656180fc54e45618aa2dc619090b00e6d3fb4707c6a1fe5", [:mix], [{:lager, ">= 2.1.0", [hex: :lager, repo: "hexpm", optional: false]}], "hexpm"}, + "logger_backend_ecto": {:hex, :logger_backend_ecto, "1.3.0", "6bb1a9d2b0ac1ee04049df94e49ea469f1f0db774d2fa05d673dc796a5ad9ed7", [:mix], [{:sqlite_ecto2, "~> 2.2", [hex: :sqlite_ecto2, repo: "hexpm", optional: true]}], "hexpm"}, + "logger_backend_sqlite": {:hex, :logger_backend_sqlite, "2.2.0", "3d3529e7425aede462478896be303acc53c8b195eeef5383fd601b42b809a73e", [:mix], [{:esqlite, "~> 0.2.4", [hex: :esqlite, repo: "hexpm", optional: false]}], "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"}, + "mdns": {:hex, :mdns, "1.0.2", "c8228dd44d3fdd55e9842cb7111c9145f2eeaa8b7adac75012ee0e250962215e", [:mix], [{:dns, "~> 2.0", [hex: :dns, repo: "hexpm", optional: false]}], "hexpm"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, + "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, + "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, + "muontrap": {:hex, :muontrap, "0.4.0", "f3c48f5e2cbb89b6406d28e488fbd0da1ce0ca00af332860913999befca9688a", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves": {:hex, :nerves, "1.3.4", "9523cc1936f173c99cf15a132c2b24f9c6f1a5cfe3327bbcd518ff7e441327d3", [:mix], [{:distillery, "2.0.10", [hex: :distillery, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, + "nerves_firmware": {:hex, :nerves_firmware, "0.4.0", "ac2fed915a7ca4bb69f567d9b742d77cffc3a6a56420ce65e870c8c34119b935", [:mix], [], "hexpm"}, + "nerves_firmware_ssh": {:hex, :nerves_firmware_ssh, "0.4.0", "494d97e06de0cc7218b0e2b40372f18aae5507c9f3d659a2ba8a8c90239ae51d", [:mix], [{:nerves_runtime, "~> 0.4", [hex: :nerves_runtime, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_hub": {:hex, :nerves_hub, "0.2.1", "135e727260c8bb9b985078d93f09271e59f61e5dd39b336b7dc4d583c5c6c50d", [:mix], [{:fwup, "~> 0.3.0", [hex: :fwup, repo: "hexpm", optional: false]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nerves_hub_cli, "~> 0.5", [hex: :nerves_hub_cli, repo: "hexpm", optional: false]}, {:nerves_runtime, "~> 0.8", [hex: :nerves_runtime, repo: "hexpm", optional: false]}, {:phoenix_channel_client, "~> 0.4", [hex: :phoenix_channel_client, repo: "hexpm", optional: false]}, {:websocket_client, "~> 1.3", [hex: :websocket_client, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_hub_cli": {:hex, :nerves_hub_cli, "0.5.1", "9e00c23678c4f34c05b7c2fda4ad04c79a129336ea8644f8e8ce827d35bedfb5", [:mix], [{:nerves_hub_core, "~> 0.2", [hex: :nerves_hub_core, repo: "hexpm", optional: false]}, {:pbcs, "~> 0.1", [hex: :pbcs, repo: "hexpm", optional: false]}, {:x509, "~> 0.3", [hex: :x509, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_hub_core": {:hex, :nerves_hub_core, "0.2.0", "ee627e0c5fd8c2511cf6e975d914c5993fabf4a1de1febca54d83910a2f476c3", [:mix], [{:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.1 or ~> 1.3", [hex: :tesla, repo: "hexpm", optional: false]}, {:x509, "~> 0.3", [hex: :x509, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_init_gadget": {:hex, :nerves_init_gadget, "0.5.2", "51ea3bc6c07ccdb1823769bb0c69eaa943d5d3da3d8f4e9de3ba02a71f76bf8d", [:mix], [{:mdns, "~> 1.0", [hex: :mdns, repo: "hexpm", optional: false]}, {:nerves_firmware_ssh, "~> 0.2", [hex: :nerves_firmware_ssh, repo: "hexpm", optional: false]}, {:nerves_network, "~> 0.3", [hex: :nerves_network, repo: "hexpm", optional: false]}, {:nerves_runtime, "~> 0.3", [hex: :nerves_runtime, repo: "hexpm", optional: false]}, {:one_dhcpd, "~> 0.1", [hex: :one_dhcpd, repo: "hexpm", optional: false]}, {:ring_logger, "~> 0.4", [hex: :ring_logger, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_leds": {:hex, :nerves_leds, "0.8.0", "193692767dca1a201b09113d242648493b9be0087bab83ebee99c3b0a254f5e1", [:mix], [], "hexpm"}, + "nerves_network": {:hex, :nerves_network, "0.5.3", "526ab769c31f33ecef2a3c8b431ce66f36189aece4cafebc1d604a1509830bc9", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nerves_network_interface, "~> 0.4.4", [hex: :nerves_network_interface, repo: "hexpm", optional: false]}, {:nerves_wpa_supplicant, "~> 0.5", [hex: :nerves_wpa_supplicant, repo: "hexpm", optional: false]}, {:one_dhcpd, "~> 0.2.0", [hex: :one_dhcpd, repo: "hexpm", optional: false]}, {:system_registry, "~> 0.7", [hex: :system_registry, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_network_interface": {:hex, :nerves_network_interface, "0.4.4", "200b1a84bc1a7fdeaf3a1e0e2d4e9b33e240b034e73f39372768d43f8690bae0", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_runtime": {:hex, :nerves_runtime, "0.9.1", "ed0c77f65a30abb0fb25edaa0243850105ce593a78f5beda756dbb48d04f5991", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:system_registry, "~> 0.5", [hex: :system_registry, repo: "hexpm", optional: false]}, {:uboot_env, "~> 0.1", [hex: :uboot_env, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_system_br": {:hex, :nerves_system_br, "1.6.5", "1e51f4a53aa31ff349b5882430ffce8024c43373b103fb40875b8da24a24eff1", [:mix], [], "hexpm"}, + "nerves_system_farmbot_rpi0": {:hex, :nerves_system_farmbot_rpi0, "1.5.0-farmbot.0", "716978a2dc799c06d5ffc04f2a3be0672927b136b805e505af3b57fed0aacd63", [:mix], [{:nerves, "~> 1.3", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "1.5.2", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_system_linter, "~> 0.3.0", [hex: :nerves_system_linter, repo: "hexpm", optional: false]}, {:nerves_toolchain_armv6_rpi_linux_gnueabi, "1.1.0", [hex: :nerves_toolchain_armv6_rpi_linux_gnueabi, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_system_linter": {:hex, :nerves_system_linter, "0.3.0", "84e0f63c8ac196b16b77608bbe7df66dcf352845c4e4fb394bffd2b572025413", [:mix], [], "hexpm"}, + "nerves_time": {:hex, :nerves_time, "0.2.0", "c8ae5cc020cd5e5b9f166f614b3dff30e10b25828715743aa97749cbfe0c5c0a", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:muontrap, "~> 0.4", [hex: :muontrap, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_toolchain_arm_unknown_linux_gnueabihf": {:hex, :nerves_toolchain_arm_unknown_linux_gnueabihf, "1.1.0", "ca466a656f8653346a8551a35743f7c41046f3d53e945723e970cb4a7811e617", [:mix], [{:nerves, "~> 1.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_toolchain_ctng, "~> 1.5.0", [hex: :nerves_toolchain_ctng, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_toolchain_armv6_rpi_linux_gnueabi": {:hex, :nerves_toolchain_armv6_rpi_linux_gnueabi, "1.1.0", "2753102e667d9778047b351618f3dfdc016b81148df58d142fee7630d96a31fe", [:mix], [{:nerves, "~> 1.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_toolchain_ctng, "~> 1.5.0", [hex: :nerves_toolchain_ctng, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_toolchain_ctng": {:hex, :nerves_toolchain_ctng, "1.5.0", "34b8f5664858ff6ce09730b26221441398acd1fa361b8c6d744d9ec18238c16b", [:mix], [{:nerves, "~> 1.0", [hex: :nerves, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_uart": {:hex, :nerves_uart, "1.2.0", "195424116b925cd3bf9d666be036c2a80655e6ca0f8d447e277667a60005c50e", [:mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_wpa_supplicant": {:hex, :nerves_wpa_supplicant, "0.5.1", "5fc2654d9ddbee6fa72f7f6fa90e7b8ee765b0775e57b93ec7ca485c421074cf", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, + "net_logger": {:hex, :net_logger, "0.1.0", "59be302c09cf70dab164810c923ccb9a976eda7270e5a32b93ba8aeb850de1d6", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:uuid, "~> 1.1", [hex: :uuid, repo: "hexpm", optional: false]}], "hexpm"}, + "nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"}, + "one_dhcpd": {:hex, :one_dhcpd, "0.2.0", "18eb8ce7101ad7b79e67f3d7ee7f648f42e02b8fa4c1cb3f24f403bf6860f81d", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, + "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, + "pbcs": {:hex, :pbcs, "0.1.1", "199c7fd4af3351758378355909145a2d187c565555ed16bde30b5055114652ed", [:mix], [], "hexpm"}, + "phoenix_channel_client": {:hex, :phoenix_channel_client, "0.4.0", "dc1da56f539587a6cc32da7b5c17da3d4066411fe6508035142a595cf451a1d2", [:mix], [{:websocket_client, "~> 1.3", [hex: :websocket_client, repo: "hexpm", optional: true]}], "hexpm"}, + "phoenix_html": {:hex, :phoenix_html, "2.12.0", "1fb3c2e48b4b66d75564d8d63df6d53655469216d6b553e7e14ced2b46f97622", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "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"}, + "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, + "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"}, + "rabbit_common": {:hex, :rabbit_common, "3.7.8", "e1410371c5814f85092b6dda85aa25fad945e7a41a9c4e34a59e61bd59a8c3b2", [:make, :rebar3], [{:jsx, "2.8.2", [hex: :jsx, repo: "hexpm", optional: false]}, {:lager, "3.6.3", [hex: :lager, repo: "hexpm", optional: false]}, {:ranch, "1.5.0", [hex: :ranch, repo: "hexpm", optional: false]}, {:ranch_proxy_protocol, "1.5.0", [hex: :ranch_proxy_protocol, repo: "hexpm", optional: false]}, {:recon, "2.3.2", [hex: :recon, repo: "hexpm", optional: false]}], "hexpm"}, + "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"}, + "ranch_proxy_protocol": {:hex, :ranch_proxy_protocol, "2.1.1", "3c4723327166d2d63c0405f4914e2e471c6de362cc844e9b203af5763e7c9d25", [:rebar3], [{:ranch, "1.6.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, + "recon": {:hex, :recon, "2.3.2", "4444c879be323b1b133eec5241cb84bd3821ea194c740d75617e106be4744318", [:rebar3], [], "hexpm"}, + "ring_logger": {:hex, :ring_logger, "0.6.1", "568dbd9eebf26c661427de5204d8a91adaa740993f3ac5aa450257f62b6e7178", [:mix], [], "hexpm"}, + "rsa": {:hex, :rsa, "0.0.1", "a63069f88ce342ffdf8448b7cdef4b39ba7dee3c1510644a39385c7e63ba246f", [:mix], [], "hexpm"}, + "sbroker": {:hex, :sbroker, "1.0.0", "28ff1b5e58887c5098539f236307b36fe1d3edaa2acff9d6a3d17c2dcafebbd0", [:rebar3], [], "hexpm"}, + "shoehorn": {:hex, :shoehorn, "0.4.0", "f3830e22e1c58b502e8c436623804c4eb6ed15f5d0bdbacdeb448cddf4795951", [:mix], [{:distillery, "~> 2.0", [hex: :distillery, repo: "hexpm", optional: false]}], "hexpm"}, + "socket": {:hex, :socket, "0.3.13", "98a2ab20ce17f95fb512c5cadddba32b57273e0d2dba2d2e5f976c5969d0c632", [:mix], [], "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.5.0", "1b23eec4532433a4bdd9c3d77cda988b79a7b4723505a83385ade98b2be35157", [:mix], [{:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:esqlite, "~> 0.2.5", [hex: :esqlite, repo: "hexpm", optional: false]}], "hexpm"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"}, + "system_registry": {:hex, :system_registry, "0.8.1", "7df1f66f0e4fcd0940ecd0473d2787d69d2abd6267d21e8f8ecbab58a14415ce", [:mix], [], "hexpm"}, + "tesla": {:hex, :tesla, "1.2.1", "864783cc27f71dd8c8969163704752476cec0f3a51eb3b06393b3971dc9733ff", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"}, + "timex": {:hex, :timex, "3.4.1", "e63fc1a37453035e534c3febfe9b6b9e18583ec7b37fd9c390efdef97397d70b", [: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"}, + "uboot_env": {:hex, :uboot_env, "0.1.0", "176d277c8461d849614d8b82595060bf03ace6ca59d49c5b53707d90cb9e2f5a", [:mix], [], "hexpm"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, + "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm"}, + "websocket_client": {:hex, :websocket_client, "1.3.0", "2275d7daaa1cdacebf2068891c9844b15f4fdc3de3ec2602420c2fb486db59b6", [:rebar3], [], "hexpm"}, + "x509": {:hex, :x509, "0.5.1", "6a5d00e35ba30da7ce51224253df1e8a1a01545eeba01642009e09e6f5d70c85", [:mix], [], "hexpm"}, +} diff --git a/farmbot_os/mix.lock.rpi0 b/farmbot_os/mix.lock.rpi0 new file mode 100644 index 00000000..a877df07 --- /dev/null +++ b/farmbot_os/mix.lock.rpi0 @@ -0,0 +1,90 @@ +%{ + "amqp": {:hex, :amqp, "1.0.3", "06a6d909abc71d82b7c3133ca491899ca18fce857d0697dd060c29de1ef498d8", [:mix], [{:amqp_client, "~> 3.7.3", [hex: :amqp_client, repo: "hexpm", optional: false]}, {:goldrush, "~> 0.1.0", [hex: :goldrush, repo: "hexpm", optional: false]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: false]}, {:lager, "~> 3.5", [hex: :lager, repo: "hexpm", optional: false]}, {:rabbit_common, "~> 3.7.3", [hex: :rabbit_common, repo: "hexpm", optional: false]}, {:ranch, "~> 1.4", [hex: :ranch, repo: "hexpm", optional: false]}, {:ranch_proxy_protocol, "~> 1.4", [hex: :ranch_proxy_protocol, repo: "hexpm", optional: false]}, {:recon, "~> 2.3.2", [hex: :recon, repo: "hexpm", optional: false]}], "hexpm"}, + "amqp_client": {:hex, :amqp_client, "3.7.8", "5ec44ad152aed8519ef557189fa21e779f60578d21bcab36cabe381b451728ee", [:make, :rebar3], [{:rabbit_common, "3.7.8", [hex: :rabbit_common, repo: "hexpm", optional: false]}], "hexpm"}, + "artificery": {:hex, :artificery, "0.4.0", "e0b8d3eb9dfe8f42c08a620f90a2aa9cef5dba9fcdfcecad5c2be451df159a77", [:mix], [], "hexpm"}, + "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"}, + "cors_plug": {:hex, :cors_plug, "2.0.0", "238ddb479f92b38f6dc1ae44b8d81f0387f9519101a6da442d543ab70ee0e482", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "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.6.0", "bfd84d90ff966e1f5d4370bdd3943432d8f65f07d3bab48001aebd7030590dcc", [:mix], [], "hexpm"}, + "dhcp_server": {:hex, :dhcp_server, "0.6.0", "6cc0cf110b8d112455f033ae49eda570e9aeeb42a2fd1c79cc437835ecaa0716", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, + "dialyxir": {:hex, :dialyxir, "1.0.0-rc.4", "71b42f5ee1b7628f3e3a6565f4617dfb02d127a0499ab3e72750455e986df001", [:mix], [{:erlex, "~> 0.1", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm"}, + "distillery": {:hex, :distillery, "2.0.10", "e9f1f1d3f4a89996a3e1a555872feed8a3a73e3d10b51886941382d29ca58f99", [:mix], [{:artificery, "~> 0.2", [hex: :artificery, repo: "hexpm", optional: false]}], "hexpm"}, + "dns": {:hex, :dns, "2.1.2", "81c46d39f7934f0e73368355126e4266762cf227ba61d5889635d83b2d64a493", [:mix], [{:socket, "~> 0.3.13", [hex: :socket, repo: "hexpm", optional: false]}], "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_ale": {:hex, :elixir_ale, "1.2.0", "02afeced3be0019ca959155d69d3087c8c93fd92a534a02eb77b90898867a146", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "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"}, + "farmbot_system_rpi0": {:hex, :farmbot_system_rpi0, "1.6.1-farmbot.1", "7f1ee2dcc238b806b1f9b22506eb209e267cbaa732c35a4b4e5e472e61169c36", [:mix], [{:nerves, "~> 1.3", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "1.6.5", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_system_linter, "~> 0.3.0", [hex: :nerves_system_linter, repo: "hexpm", optional: false]}, {:nerves_toolchain_armv6_rpi_linux_gnueabi, "1.1.0", [hex: :nerves_toolchain_armv6_rpi_linux_gnueabi, repo: "hexpm", optional: false]}], "hexpm"}, + "fs": {:hex, :fs, "3.4.0", "6d18575c250b415b3cad559e6f97a4c822516c7bc2c10bfbb2493a8f230f5132", [:rebar3], [], "hexpm"}, + "fwup": {:hex, :fwup, "0.3.0", "2c360815565fcbc945ebbb34b58f156efacb7f8d64766f1cb3426919bb3f41ea", [:mix], [], "hexpm"}, + "gen_stage": {:hex, :gen_stage, "0.14.0", "65ae78509f85b59d360690ce3378d5096c3130a0694bab95b0c4ae66f3008fad", [:mix], [], "hexpm"}, + "gettext": {:hex, :gettext, "0.16.0", "4a7e90408cef5f1bf57c5a39e2db8c372a906031cc9b1466e963101cb927dafc", [:mix], [], "hexpm"}, + "goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], [], "hexpm"}, + "hackney": {:hex, :hackney, "1.15.0", "287a5d2304d516f63e56c469511c42b016423bcb167e61b611f6bad47e3ca60e", [: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"}, + "joken": {:hex, :joken, "1.5.0", "42a0953e80bd933fc98a0874e156771f78bf0e92abe6c3a9c22feb6da28efb0b", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"}, + "jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"}, + "jsx": {:hex, :jsx, "2.8.2", "7acc7d785b5abe8a6e9adbde926a24e481f29956dd8b4df49e3e4e7bcc92a018", [:mix, :rebar3], [], "hexpm"}, + "lager": {:hex, :lager, "3.6.3", "fe78951d174616273f87f0dbc3374d1430b1952e5efc4e1c995592d30a207294", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, repo: "hexpm", optional: false]}], "hexpm"}, + "logger_backend_sqlite": {:hex, :logger_backend_sqlite, "2.2.0", "3d3529e7425aede462478896be303acc53c8b195eeef5383fd601b42b809a73e", [:mix], [{:esqlite, "~> 0.2.4", [hex: :esqlite, repo: "hexpm", optional: false]}], "hexpm"}, + "mdns": {:hex, :mdns, "1.0.2", "c8228dd44d3fdd55e9842cb7111c9145f2eeaa8b7adac75012ee0e250962215e", [:mix], [{:dns, "~> 2.0", [hex: :dns, repo: "hexpm", optional: false]}], "hexpm"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, + "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, + "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, + "muontrap": {:hex, :muontrap, "0.4.0", "f3c48f5e2cbb89b6406d28e488fbd0da1ce0ca00af332860913999befca9688a", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves": {:hex, :nerves, "1.3.4", "9523cc1936f173c99cf15a132c2b24f9c6f1a5cfe3327bbcd518ff7e441327d3", [:mix], [{:distillery, "2.0.10", [hex: :distillery, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, + "nerves_firmware": {:hex, :nerves_firmware, "0.4.0", "ac2fed915a7ca4bb69f567d9b742d77cffc3a6a56420ce65e870c8c34119b935", [:mix], [], "hexpm"}, + "nerves_firmware_ssh": {:hex, :nerves_firmware_ssh, "0.4.0", "494d97e06de0cc7218b0e2b40372f18aae5507c9f3d659a2ba8a8c90239ae51d", [:mix], [{:nerves_runtime, "~> 0.4", [hex: :nerves_runtime, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_hub": {:hex, :nerves_hub, "0.2.1", "135e727260c8bb9b985078d93f09271e59f61e5dd39b336b7dc4d583c5c6c50d", [:mix], [{:fwup, "~> 0.3.0", [hex: :fwup, repo: "hexpm", optional: false]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nerves_hub_cli, "~> 0.5", [hex: :nerves_hub_cli, repo: "hexpm", optional: false]}, {:nerves_runtime, "~> 0.8", [hex: :nerves_runtime, repo: "hexpm", optional: false]}, {:phoenix_channel_client, "~> 0.4", [hex: :phoenix_channel_client, repo: "hexpm", optional: false]}, {:websocket_client, "~> 1.3", [hex: :websocket_client, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_hub_cli": {:hex, :nerves_hub_cli, "0.5.1", "9e00c23678c4f34c05b7c2fda4ad04c79a129336ea8644f8e8ce827d35bedfb5", [:mix], [{:nerves_hub_core, "~> 0.2", [hex: :nerves_hub_core, repo: "hexpm", optional: false]}, {:pbcs, "~> 0.1", [hex: :pbcs, repo: "hexpm", optional: false]}, {:x509, "~> 0.3", [hex: :x509, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_hub_core": {:hex, :nerves_hub_core, "0.2.0", "ee627e0c5fd8c2511cf6e975d914c5993fabf4a1de1febca54d83910a2f476c3", [:mix], [{:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.1 or ~> 1.3", [hex: :tesla, repo: "hexpm", optional: false]}, {:x509, "~> 0.3", [hex: :x509, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_init_gadget": {:hex, :nerves_init_gadget, "0.5.2", "51ea3bc6c07ccdb1823769bb0c69eaa943d5d3da3d8f4e9de3ba02a71f76bf8d", [:mix], [{:mdns, "~> 1.0", [hex: :mdns, repo: "hexpm", optional: false]}, {:nerves_firmware_ssh, "~> 0.2", [hex: :nerves_firmware_ssh, repo: "hexpm", optional: false]}, {:nerves_network, "~> 0.3", [hex: :nerves_network, repo: "hexpm", optional: false]}, {:nerves_runtime, "~> 0.3", [hex: :nerves_runtime, repo: "hexpm", optional: false]}, {:one_dhcpd, "~> 0.1", [hex: :one_dhcpd, repo: "hexpm", optional: false]}, {:ring_logger, "~> 0.4", [hex: :ring_logger, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_leds": {:hex, :nerves_leds, "0.8.0", "193692767dca1a201b09113d242648493b9be0087bab83ebee99c3b0a254f5e1", [:mix], [], "hexpm"}, + "nerves_network": {:hex, :nerves_network, "0.5.3", "526ab769c31f33ecef2a3c8b431ce66f36189aece4cafebc1d604a1509830bc9", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nerves_network_interface, "~> 0.4.4", [hex: :nerves_network_interface, repo: "hexpm", optional: false]}, {:nerves_wpa_supplicant, "~> 0.5", [hex: :nerves_wpa_supplicant, repo: "hexpm", optional: false]}, {:one_dhcpd, "~> 0.2.0", [hex: :one_dhcpd, repo: "hexpm", optional: false]}, {:system_registry, "~> 0.7", [hex: :system_registry, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_network_interface": {:hex, :nerves_network_interface, "0.4.4", "200b1a84bc1a7fdeaf3a1e0e2d4e9b33e240b034e73f39372768d43f8690bae0", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_runtime": {:hex, :nerves_runtime, "0.9.1", "ed0c77f65a30abb0fb25edaa0243850105ce593a78f5beda756dbb48d04f5991", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:system_registry, "~> 0.5", [hex: :system_registry, repo: "hexpm", optional: false]}, {:uboot_env, "~> 0.1", [hex: :uboot_env, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_system_br": {:hex, :nerves_system_br, "1.6.5", "1e51f4a53aa31ff349b5882430ffce8024c43373b103fb40875b8da24a24eff1", [:mix], [], "hexpm"}, + "nerves_system_farmbot_rpi0": {:hex, :nerves_system_farmbot_rpi0, "1.5.0-farmbot.0", "716978a2dc799c06d5ffc04f2a3be0672927b136b805e505af3b57fed0aacd63", [:mix], [{:nerves, "~> 1.3", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "1.5.2", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_system_linter, "~> 0.3.0", [hex: :nerves_system_linter, repo: "hexpm", optional: false]}, {:nerves_toolchain_armv6_rpi_linux_gnueabi, "1.1.0", [hex: :nerves_toolchain_armv6_rpi_linux_gnueabi, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_system_linter": {:hex, :nerves_system_linter, "0.3.0", "84e0f63c8ac196b16b77608bbe7df66dcf352845c4e4fb394bffd2b572025413", [:mix], [], "hexpm"}, + "nerves_time": {:hex, :nerves_time, "0.2.0", "c8ae5cc020cd5e5b9f166f614b3dff30e10b25828715743aa97749cbfe0c5c0a", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:muontrap, "~> 0.4", [hex: :muontrap, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_toolchain_armv6_rpi_linux_gnueabi": {:hex, :nerves_toolchain_armv6_rpi_linux_gnueabi, "1.1.0", "2753102e667d9778047b351618f3dfdc016b81148df58d142fee7630d96a31fe", [:mix], [{:nerves, "~> 1.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_toolchain_ctng, "~> 1.5.0", [hex: :nerves_toolchain_ctng, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_toolchain_ctng": {:hex, :nerves_toolchain_ctng, "1.5.0", "34b8f5664858ff6ce09730b26221441398acd1fa361b8c6d744d9ec18238c16b", [:mix], [{:nerves, "~> 1.0", [hex: :nerves, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_uart": {:hex, :nerves_uart, "1.2.0", "195424116b925cd3bf9d666be036c2a80655e6ca0f8d447e277667a60005c50e", [:mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_wpa_supplicant": {:hex, :nerves_wpa_supplicant, "0.5.1", "5fc2654d9ddbee6fa72f7f6fa90e7b8ee765b0775e57b93ec7ca485c421074cf", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, + "nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"}, + "one_dhcpd": {:hex, :one_dhcpd, "0.2.0", "18eb8ce7101ad7b79e67f3d7ee7f648f42e02b8fa4c1cb3f24f403bf6860f81d", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, + "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, + "pbcs": {:hex, :pbcs, "0.1.1", "199c7fd4af3351758378355909145a2d187c565555ed16bde30b5055114652ed", [:mix], [], "hexpm"}, + "phoenix_channel_client": {:hex, :phoenix_channel_client, "0.4.0", "dc1da56f539587a6cc32da7b5c17da3d4066411fe6508035142a595cf451a1d2", [:mix], [{:websocket_client, "~> 1.3", [hex: :websocket_client, repo: "hexpm", optional: true]}], "hexpm"}, + "phoenix_html": {:hex, :phoenix_html, "2.12.0", "1fb3c2e48b4b66d75564d8d63df6d53655469216d6b553e7e14ced2b46f97622", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "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"}, + "rabbit_common": {:hex, :rabbit_common, "3.7.8", "e1410371c5814f85092b6dda85aa25fad945e7a41a9c4e34a59e61bd59a8c3b2", [:make, :rebar3], [{:jsx, "2.8.2", [hex: :jsx, repo: "hexpm", optional: false]}, {:lager, "3.6.3", [hex: :lager, repo: "hexpm", optional: false]}, {:ranch, "1.5.0", [hex: :ranch, repo: "hexpm", optional: false]}, {:ranch_proxy_protocol, "1.5.0", [hex: :ranch_proxy_protocol, repo: "hexpm", optional: false]}, {:recon, "2.3.2", [hex: :recon, repo: "hexpm", optional: false]}], "hexpm"}, + "ranch": {:hex, :ranch, "1.6.2", "6db93c78f411ee033dbb18ba8234c5574883acb9a75af0fb90a9b82ea46afa00", [:rebar3], [], "hexpm"}, + "ranch_proxy_protocol": {:hex, :ranch_proxy_protocol, "2.1.1", "3c4723327166d2d63c0405f4914e2e471c6de362cc844e9b203af5763e7c9d25", [:rebar3], [{:ranch, "1.6.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, + "recon": {:hex, :recon, "2.3.2", "4444c879be323b1b133eec5241cb84bd3821ea194c740d75617e106be4744318", [:rebar3], [], "hexpm"}, + "ring_logger": {:hex, :ring_logger, "0.6.1", "568dbd9eebf26c661427de5204d8a91adaa740993f3ac5aa450257f62b6e7178", [:mix], [], "hexpm"}, + "sbroker": {:hex, :sbroker, "1.0.0", "28ff1b5e58887c5098539f236307b36fe1d3edaa2acff9d6a3d17c2dcafebbd0", [:rebar3], [], "hexpm"}, + "shoehorn": {:hex, :shoehorn, "0.4.0", "f3830e22e1c58b502e8c436623804c4eb6ed15f5d0bdbacdeb448cddf4795951", [:mix], [{:distillery, "~> 2.0", [hex: :distillery, repo: "hexpm", optional: false]}], "hexpm"}, + "socket": {:hex, :socket, "0.3.13", "98a2ab20ce17f95fb512c5cadddba32b57273e0d2dba2d2e5f976c5969d0c632", [:mix], [], "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"}, + "system_registry": {:hex, :system_registry, "0.8.1", "7df1f66f0e4fcd0940ecd0473d2787d69d2abd6267d21e8f8ecbab58a14415ce", [:mix], [], "hexpm"}, + "tesla": {:hex, :tesla, "1.2.1", "864783cc27f71dd8c8969163704752476cec0f3a51eb3b06393b3971dc9733ff", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}], "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"}, + "uboot_env": {:hex, :uboot_env, "0.1.0", "176d277c8461d849614d8b82595060bf03ace6ca59d49c5b53707d90cb9e2f5a", [:mix], [], "hexpm"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, + "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm"}, + "websocket_client": {:hex, :websocket_client, "1.3.0", "2275d7daaa1cdacebf2068891c9844b15f4fdc3de3ec2602420c2fb486db59b6", [:rebar3], [], "hexpm"}, + "x509": {:hex, :x509, "0.5.1", "6a5d00e35ba30da7ce51224253df1e8a1a01545eeba01642009e09e6f5d70c85", [:mix], [], "hexpm"}, +} diff --git a/farmbot_os/mix.lock.rpi3 b/farmbot_os/mix.lock.rpi3 index d5637734..eb88a1b1 100644 --- a/farmbot_os/mix.lock.rpi3 +++ b/farmbot_os/mix.lock.rpi3 @@ -5,19 +5,19 @@ "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"}, - "cors_plug": {:hex, :cors_plug, "1.5.2", "72df63c87e4f94112f458ce9d25800900cc88608c1078f0e4faddf20933eda6e", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, + "cors_plug": {:hex, :cors_plug, "2.0.0", "238ddb479f92b38f6dc1ae44b8d81f0387f9519101a6da442d543ab70ee0e482", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "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.6.0", "bfd84d90ff966e1f5d4370bdd3943432d8f65f07d3bab48001aebd7030590dcc", [:mix], [], "hexpm"}, "dhcp_server": {:hex, :dhcp_server, "0.6.0", "6cc0cf110b8d112455f033ae49eda570e9aeeb42a2fd1c79cc437835ecaa0716", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, - "dialyxir": {:hex, :dialyxir, "1.0.0-rc.3", "774306f84973fc3f1e2e8743eeaa5f5d29b117f3916e5de74c075c02f1b8ef55", [:mix], [], "hexpm"}, + "dialyxir": {:hex, :dialyxir, "1.0.0-rc.4", "71b42f5ee1b7628f3e3a6565f4617dfb02d127a0499ab3e72750455e986df001", [:mix], [{:erlex, "~> 0.1", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm"}, "distillery": {:hex, :distillery, "2.0.10", "e9f1f1d3f4a89996a3e1a555872feed8a3a73e3d10b51886941382d29ca58f99", [:mix], [{:artificery, "~> 0.2", [hex: :artificery, repo: "hexpm", optional: false]}], "hexpm"}, "dns": {:hex, :dns, "2.1.2", "81c46d39f7934f0e73368355126e4266762cf227ba61d5889635d83b2d64a493", [:mix], [{:socket, "~> 0.3.13", [hex: :socket, 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_ale": {:hex, :elixir_ale, "1.1.0", "06e77697fa0bd7aff5f9040d8be8ba7947e5833de2a12d1a25f54332556b4e90", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, + "elixir_ale": {:hex, :elixir_ale, "1.2.0", "02afeced3be0019ca959155d69d3087c8c93fd92a534a02eb77b90898867a146", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "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"}, "farmbot_system_rpi3": {:hex, :farmbot_system_rpi3, "1.6.1-farmbot.1", "421cdd2c383229a776ee6142a493eeba5ab1902d0d888035748454208e28fc28", [:mix], [{:nerves, "~> 1.3", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "1.6.5", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_system_linter, "~> 0.3.0", [hex: :nerves_system_linter, repo: "hexpm", optional: false]}, {:nerves_toolchain_arm_unknown_linux_gnueabihf, "1.1.0", [hex: :nerves_toolchain_arm_unknown_linux_gnueabihf, repo: "hexpm", optional: false]}], "hexpm"}, "fs": {:hex, :fs, "3.4.0", "6d18575c250b415b3cad559e6f97a4c822516c7bc2c10bfbb2493a8f230f5132", [:rebar3], [], "hexpm"}, @@ -26,18 +26,13 @@ "gettext": {:hex, :gettext, "0.16.0", "4a7e90408cef5f1bf57c5a39e2db8c372a906031cc9b1466e963101cb927dafc", [:mix], [], "hexpm"}, "goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], [], "hexpm"}, "hackney": {:hex, :hackney, "1.15.0", "287a5d2304d516f63e56c469511c42b016423bcb167e61b611f6bad47e3ca60e", [: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"}, - "httpoison": {:hex, :httpoison, "1.3.1", "7ac607311f5f706b44e8b3fab736d0737f2f62a31910ccd9afe7227b43edb7f0", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, 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"}, "joken": {:hex, :joken, "1.5.0", "42a0953e80bd933fc98a0874e156771f78bf0e92abe6c3a9c22feb6da28efb0b", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"}, "jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"}, - "jsx": {:hex, :jsx, "2.9.0", "d2f6e5f069c00266cad52fb15d87c428579ea4d7d73a33669e12679e203329dd", [:mix, :rebar3], [], "hexpm"}, + "jsx": {:hex, :jsx, "2.8.2", "7acc7d785b5abe8a6e9adbde926a24e481f29956dd8b4df49e3e4e7bcc92a018", [:mix, :rebar3], [], "hexpm"}, "lager": {:hex, :lager, "3.6.3", "fe78951d174616273f87f0dbc3374d1430b1952e5efc4e1c995592d30a207294", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, repo: "hexpm", optional: false]}], "hexpm"}, - "lager_logger": {:hex, :lager_logger, "1.0.5", "2b58be52fe1e0fb82656180fc54e45618aa2dc619090b00e6d3fb4707c6a1fe5", [:mix], [{:lager, ">= 2.1.0", [hex: :lager, repo: "hexpm", optional: false]}], "hexpm"}, - "logger_backend_ecto": {:hex, :logger_backend_ecto, "1.3.0", "6bb1a9d2b0ac1ee04049df94e49ea469f1f0db774d2fa05d673dc796a5ad9ed7", [:mix], [{:sqlite_ecto2, "~> 2.2", [hex: :sqlite_ecto2, repo: "hexpm", optional: true]}], "hexpm"}, "logger_backend_sqlite": {:hex, :logger_backend_sqlite, "2.2.0", "3d3529e7425aede462478896be303acc53c8b195eeef5383fd601b42b809a73e", [:mix], [{:esqlite, "~> 0.2.4", [hex: :esqlite, repo: "hexpm", optional: false]}], "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"}, "mdns": {:hex, :mdns, "1.0.2", "c8228dd44d3fdd55e9842cb7111c9145f2eeaa8b7adac75012ee0e250962215e", [:mix], [{:dns, "~> 2.0", [hex: :dns, repo: "hexpm", optional: false]}], "hexpm"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, @@ -68,23 +63,24 @@ "pbcs": {:hex, :pbcs, "0.1.1", "199c7fd4af3351758378355909145a2d187c565555ed16bde30b5055114652ed", [:mix], [], "hexpm"}, "phoenix_channel_client": {:hex, :phoenix_channel_client, "0.4.0", "dc1da56f539587a6cc32da7b5c17da3d4066411fe6508035142a595cf451a1d2", [:mix], [{:websocket_client, "~> 1.3", [hex: :websocket_client, repo: "hexpm", optional: true]}], "hexpm"}, "phoenix_html": {:hex, :phoenix_html, "2.12.0", "1fb3c2e48b4b66d75564d8d63df6d53655469216d6b553e7e14ced2b46f97622", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, - "plug": {:hex, :plug, "1.6.4", "35618dd2cc009b69b000f785452f6b370f76d099ece199733fea27bc473f809d", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "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"}, "rabbit_common": {:hex, :rabbit_common, "3.7.8", "e1410371c5814f85092b6dda85aa25fad945e7a41a9c4e34a59e61bd59a8c3b2", [:make, :rebar3], [{:jsx, "2.8.2", [hex: :jsx, repo: "hexpm", optional: false]}, {:lager, "3.6.3", [hex: :lager, repo: "hexpm", optional: false]}, {:ranch, "1.5.0", [hex: :ranch, repo: "hexpm", optional: false]}, {:ranch_proxy_protocol, "1.5.0", [hex: :ranch_proxy_protocol, repo: "hexpm", optional: false]}, {:recon, "2.3.2", [hex: :recon, repo: "hexpm", optional: false]}], "hexpm"}, "ranch": {:hex, :ranch, "1.6.2", "6db93c78f411ee033dbb18ba8234c5574883acb9a75af0fb90a9b82ea46afa00", [:rebar3], [], "hexpm"}, "ranch_proxy_protocol": {:hex, :ranch_proxy_protocol, "2.1.1", "3c4723327166d2d63c0405f4914e2e471c6de362cc844e9b203af5763e7c9d25", [:rebar3], [{:ranch, "1.6.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, "recon": {:hex, :recon, "2.3.2", "4444c879be323b1b133eec5241cb84bd3821ea194c740d75617e106be4744318", [:rebar3], [], "hexpm"}, "ring_logger": {:hex, :ring_logger, "0.6.1", "568dbd9eebf26c661427de5204d8a91adaa740993f3ac5aa450257f62b6e7178", [:mix], [], "hexpm"}, - "rsa": {:hex, :rsa, "0.0.1", "a63069f88ce342ffdf8448b7cdef4b39ba7dee3c1510644a39385c7e63ba246f", [:mix], [], "hexpm"}, "sbroker": {:hex, :sbroker, "1.0.0", "28ff1b5e58887c5098539f236307b36fe1d3edaa2acff9d6a3d17c2dcafebbd0", [:rebar3], [], "hexpm"}, "shoehorn": {:hex, :shoehorn, "0.4.0", "f3830e22e1c58b502e8c436623804c4eb6ed15f5d0bdbacdeb448cddf4795951", [:mix], [{:distillery, "~> 2.0", [hex: :distillery, repo: "hexpm", optional: false]}], "hexpm"}, "socket": {:hex, :socket, "0.3.13", "98a2ab20ce17f95fb512c5cadddba32b57273e0d2dba2d2e5f976c5969d0c632", [:mix], [], "hexpm"}, - "sqlite_ecto2": {:hex, :sqlite_ecto2, "2.2.5", "f111a48188b0640effb7f2952071c4cf285501d3ce090820a7c2fc20af3867e9", [: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"}, + "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"}, "system_registry": {:hex, :system_registry, "0.8.1", "7df1f66f0e4fcd0940ecd0473d2787d69d2abd6267d21e8f8ecbab58a14415ce", [:mix], [], "hexpm"}, "tesla": {:hex, :tesla, "1.2.1", "864783cc27f71dd8c8969163704752476cec0f3a51eb3b06393b3971dc9733ff", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"}, - "timex": {:hex, :timex, "3.4.1", "e63fc1a37453035e534c3febfe9b6b9e18583ec7b37fd9c390efdef97397d70b", [: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"}, + "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"}, "uboot_env": {:hex, :uboot_env, "0.1.0", "176d277c8461d849614d8b82595060bf03ace6ca59d49c5b53707d90cb9e2f5a", [:mix], [], "hexpm"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, diff --git a/farmbot_os/platform/host/configurator.ex b/farmbot_os/platform/host/configurator.ex index 10d228cf..fc4bacc7 100644 --- a/farmbot_os/platform/host/configurator.ex +++ b/farmbot_os/platform/host/configurator.ex @@ -1,13 +1,13 @@ defmodule Farmbot.Host.Configurator do @moduledoc false use Supervisor - + import Farmbot.Config, only: [update_config_value: 4, get_config_value: 3] @doc false def start_link(args) do - Supervisor.start_link(__MODULE__, args, [name: __MODULE__]) + Supervisor.start_link(__MODULE__, args, name: __MODULE__) end defp start_node() do @@ -22,9 +22,9 @@ defmodule Farmbot.Host.Configurator do # Get out authorization data out of the environment. # for host environment this will be configured at compile time. # for target environment it will be configured by `configurator`. - email = Application.get_env(:farmbot_os, :authorization)[:email] || raise error("email") - pass = Application.get_env(:farmbot_os, :authorization)[:password] || raise error("password") - server = Application.get_env(:farmbot_os, :authorization)[:server] || raise error("server") + email = System.get_env("FARMBOT_EMAIL") || raise error("email") + pass = System.get_env("FARMBOT_PASSWORD") || raise error("password") + server = System.get_env("FARMBOT_SERVER") || raise error("server") update_config_value(:string, "authorization", "email", email) # if there is no firmware hardware, default ot farmduino @@ -35,6 +35,7 @@ defmodule Farmbot.Host.Configurator do if get_config_value(:bool, "settings", "first_boot") do update_config_value(:string, "authorization", "password", pass) end + update_config_value(:string, "authorization", "server", server) update_config_value(:string, "authorization", "token", nil) :ignore @@ -42,8 +43,9 @@ defmodule Farmbot.Host.Configurator do defp error(_field) do """ - Your environment is not properly configured! You will need to follow the - directions in `config/host/auth_secret_template.exs` before continuing. + Your environment is not properly configured! + Please export FARMBOT_EMAIL, FARMBOT_PASSWORD and FARMBOT_SERVER + in your environment. """ end end diff --git a/farmbot_os/platform/target/configurator/captive_portal.ex b/farmbot_os/platform/target/configurator/captive_portal.ex index 94f73e87..6cda118d 100644 --- a/farmbot_os/platform/target/configurator/captive_portal.ex +++ b/farmbot_os/platform/target/configurator/captive_portal.ex @@ -35,15 +35,18 @@ defmodule Farmbot.Target.Bootstrap.Configurator.CaptivePortal do gateway: @address, netmask: "255.255.255.0", range: {dhcp_range_begin(@address), dhcp_range_end(@address)}, - domain_servers: [@address], + domain_servers: [@address] ] + {:ok, dhcp_server} = DHCPServer.start_link(@interface, dhcp_opts) dnsmasq = case setup_dnsmasq(@address, @interface) do - {:ok, dnsmasq} -> dnsmasq + {:ok, dnsmasq} -> + dnsmasq + {:error, _} -> - Farmbot.Logger.error 1, "Failed to start DnsMasq" + Farmbot.Logger.error(1, "Failed to start DnsMasq") nil end @@ -54,12 +57,12 @@ defmodule Farmbot.Target.Bootstrap.Configurator.CaptivePortal do end def terminate(_, state) do - Farmbot.Logger.busy 3, "Stopping captive portal GenServer." + Farmbot.Logger.busy(3, "Stopping captive portal GenServer.") - Farmbot.Logger.busy 3, "Stopping mDNS." + Farmbot.Logger.busy(3, "Stopping mDNS.") Mdns.Server.stop() - Farmbot.Logger.busy 3, "Stopping DHCP GenServer." + Farmbot.Logger.busy(3, "Stopping DHCP GenServer.") GenServer.stop(state.dhcp_server, :normal) stop_dnsmasq(state) @@ -83,7 +86,11 @@ defmodule Farmbot.Target.Bootstrap.Configurator.CaptivePortal do defp ensure_interface(interface) do unless interface in Nerves.NetworkInterface.interfaces() do - Farmbot.Logger.debug 2, "Waiting for #{interface}: #{inspect Nerves.NetworkInterface.interfaces()}" + Farmbot.Logger.debug( + 2, + "Waiting for #{interface}: #{inspect(Nerves.NetworkInterface.interfaces())}" + ) + Process.sleep(100) ensure_interface(interface) end @@ -103,15 +110,17 @@ defmodule Farmbot.Target.Bootstrap.Configurator.CaptivePortal do dnsmasq_conf = build_dnsmasq_conf(ip_addr, interface) File.mkdir_p!("/tmp/dnsmasq") :ok = File.write("/tmp/dnsmasq/#{@dnsmasq_conf_file}", dnsmasq_conf) - dnsmasq_cmd = "dnsmasq -k --dhcp-lease " <> - "/tmp/dnsmasq/#{@dnsmasq_pid_file} " <> - "--conf-dir=/tmp/dnsmasq" + + dnsmasq_cmd = + "dnsmasq -k --dhcp-lease " <> + "/tmp/dnsmasq/#{@dnsmasq_pid_file} " <> "--conf-dir=/tmp/dnsmasq" + dnsmasq_port = Port.open({:spawn, dnsmasq_cmd}, [:binary]) get_dnsmasq_info(dnsmasq_port, ip_addr, interface) end defp get_dnsmasq_info(nil, ip_addr, interface) do - Farmbot.Logger.warn 1, "dnsmasq failed to start." + Farmbot.Logger.warn(1, "dnsmasq failed to start.") Process.sleep(1000) setup_dnsmasq(ip_addr, interface) end @@ -120,8 +129,9 @@ defmodule Farmbot.Target.Bootstrap.Configurator.CaptivePortal do case Port.info(dnsmasq_port, :os_pid) do {:os_pid, dnsmasq_os_pid} -> {dnsmasq_port, dnsmasq_os_pid} + nil -> - Farmbot.Logger.warn 1, "dnsmasq not ready yet." + Farmbot.Logger.warn(1, "dnsmasq not ready yet.") Process.sleep(1000) setup_dnsmasq(ip_addr, interface) end @@ -140,19 +150,20 @@ defmodule Farmbot.Target.Bootstrap.Configurator.CaptivePortal do defp stop_dnsmasq(state) do case state.dnsmasq do {dnsmasq_port, dnsmasq_os_pid} -> - Farmbot.Logger.busy 3, "Stopping dnsmasq" - Farmbot.Logger.busy 3, "Killing dnsmasq PID." + Farmbot.Logger.busy(3, "Stopping dnsmasq") + Farmbot.Logger.busy(3, "Killing dnsmasq PID.") :ok = kill(dnsmasq_os_pid) Port.close(dnsmasq_port) - Farmbot.Logger.success 3, "Stopped dnsmasq." + Farmbot.Logger.success(3, "Stopped dnsmasq.") :ok + _ -> - Farmbot.Logger.debug 3, "Dnsmasq not running." + Farmbot.Logger.debug(3, "Dnsmasq not running.") :ok end rescue e -> - Farmbot.Logger.error 3, "Error stopping dnsmasq: #{Exception.message(e)}" + Farmbot.Logger.error(3, "Error stopping dnsmasq: #{Exception.message(e)}") :ok end @@ -160,6 +171,7 @@ defmodule Farmbot.Target.Bootstrap.Configurator.CaptivePortal do defp cmd(cmd_str) do [command | args] = String.split(cmd_str, " ") + System.cmd(command, args, into: IO.stream(:stdio, :line)) |> print_cmd() end @@ -199,5 +211,4 @@ defmodule Farmbot.Target.Bootstrap.Configurator.CaptivePortal do |> Enum.map(&String.to_integer/1) |> List.to_tuple() end - end diff --git a/farmbot_os/platform/target/configurator/configurator.ex b/farmbot_os/platform/target/configurator/configurator.ex index eb167ffd..8fe13354 100644 --- a/farmbot_os/platform/target/configurator/configurator.ex +++ b/farmbot_os/platform/target/configurator/configurator.ex @@ -23,7 +23,7 @@ defmodule Farmbot.Target.Bootstrap.Configurator do """ def start_link(_) do Farmbot.Logger.busy(3, "Configuring Farmbot.") - supervisor = Supervisor.start_link(__MODULE__, [self()], [name: __MODULE__]) + supervisor = Supervisor.start_link(__MODULE__, [self()], name: __MODULE__) case supervisor do {:ok, pid} -> @@ -36,7 +36,7 @@ defmodule Farmbot.Target.Bootstrap.Configurator do defp wait(pid) do if Process.alive?(pid) do - Process.sleep(10) + Process.sleep(1000) wait(pid) else :ignore @@ -45,12 +45,14 @@ defmodule Farmbot.Target.Bootstrap.Configurator do def init(_) do first_boot? = get_config_value(:bool, "settings", "first_boot") - autoconfigure? = Nerves.Runtime.KV.get("farmbot_auto_configure") |> case do - "" -> false - other when is_binary(other) -> true - _ -> false - end + autoconfigure? = + Nerves.Runtime.KV.get("farmbot_auto_configure") + |> case do + "" -> false + other when is_binary(other) -> true + _ -> false + end if first_boot? do maybe_configurate(autoconfigure?) @@ -71,9 +73,11 @@ defmodule Farmbot.Target.Bootstrap.Configurator do import Supervisor.Spec :ets.new(:session, [:named_table, :public, read_concurrency: true]) Config.destroy_all_network_configs() + children = [ worker(Configurator.CaptivePortal, [], restart: :transient), - {Plug.Adapters.Cowboy, scheme: :http, plug: Configurator.Router, options: [port: 80, acceptors: 1]} + {Plug.Adapters.Cowboy, + scheme: :http, plug: Configurator.Router, options: [port: 80, acceptors: 1]} ] opts = [strategy: :one_for_one] @@ -81,16 +85,18 @@ defmodule Farmbot.Target.Bootstrap.Configurator do end defp maybe_configurate(_) do - ifname = Nerves.Runtime.KV.get("farmbot_network_iface") - ssid = Nerves.Runtime.KV.get("farmbot_network_ssid") - psk = Nerves.Runtime.KV.get("farmbot_network_psk") - email = Nerves.Runtime.KV.get("farmbot_email") - server = Nerves.Runtime.KV.get("farmbot_server") + ifname = Nerves.Runtime.KV.get("farmbot_network_iface") + ssid = Nerves.Runtime.KV.get("farmbot_network_ssid") + psk = Nerves.Runtime.KV.get("farmbot_network_psk") + email = Nerves.Runtime.KV.get("farmbot_email") + server = Nerves.Runtime.KV.get("farmbot_server") password = Nerves.Runtime.KV.get("farmbot_password") + Config.input_network_config!(%{ name: ifname, ssid: ssid, - security: "WPA-PSK", psk: psk, + security: "WPA-PSK", + psk: psk, type: if(ssid, do: "wireless", else: "wired"), domain: nil, name_servers: nil, diff --git a/farmbot_os/platform/target/configurator/router.ex b/farmbot_os/platform/target/configurator/router.ex index 596bbf4a..71ed11aa 100644 --- a/farmbot_os/platform/target/configurator/router.ex +++ b/farmbot_os/platform/target/configurator/router.ex @@ -2,8 +2,8 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do @moduledoc "Routes web connections." use Plug.Router - use Plug.Debugger, otp_app: :farmbot_os - plug(Plug.Static, from: {:farmbot_os, "priv/static"}, at: "/") + use Plug.Debugger, otp_app: :farmbot + plug(Plug.Static, from: {:farmbot, "priv/static"}, at: "/") plug(Plug.Logger, log: :debug) plug(Plug.Parsers, parsers: [:urlencoded, :multipart]) plug(:match) @@ -12,18 +12,19 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do require Farmbot.Logger import Phoenix.HTML alias Farmbot.Config - import Config, only: [ - get_config_value: 3, - update_config_value: 4 - ] + + import Config, + only: [ + get_config_value: 3, + update_config_value: 4 + ] defmodule MissingField do defexception [:message, :field, :redir] end @version Farmbot.Project.version() - @log_db Application.get_env(:logger_backend_ecto, LoggerBackendEcto.Repo)[:database] - @log_db || Mix.raise("LoggerBackendEcto probably not configured properly.") + @data_path Application.get_env(:farmbot, :data_path) get "/generate_204" do send_resp(conn, 204, "") @@ -34,69 +35,94 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do end get "/" do - case Farmbot.System.last_shutdown_reason() do - reason when is_binary(reason) -> + last_reset_reason_file = Path.join(@data_path, "last_shutdown_reason") + + case File.read(last_reset_reason_file) do + {:ok, reason} when is_binary(reason) -> if String.contains?(reason, "CeleryScript request.") do - render_page(conn, "index", [version: @version, last_reset_reason: nil]) + render_page(conn, "index", version: @version, last_reset_reason: nil) else - render_page(conn, "index", [version: @version, last_reset_reason: Phoenix.HTML.raw(reason)]) + render_page(conn, "index", + version: @version, + last_reset_reason: Phoenix.HTML.raw(reason) + ) end - _ -> - render_page(conn, "index", [version: @version, last_reset_reason: nil]) + + {:error, _} -> + render_page(conn, "index", version: @version, last_reset_reason: nil) end end get "/view_logs" do all_logs = LoggerBackendSqlite.all_logs() - render_page(conn, "view_logs", [logs: all_logs]) + render_page(conn, "view_logs", logs: all_logs) end get "/logs" do - case File.read(@log_db) do + file = Path.join(@data_path, "debug_logs.sqlite3") + + case File.read(file) do {:ok, data} -> md5 = data |> :erlang.md5() |> Base.encode16() + conn |> put_resp_content_type("application/octet-stream") - |> put_resp_header("Content-Disposition", "inline; filename=\"#{@version}-#{md5}-logs.sqlite3\"") + |> put_resp_header( + "Content-Disposition", + "inline; filename=\"#{@version}-#{md5}-logs.sqlite3\"" + ) |> send_resp(200, data) + {:error, posix} -> send_resp(conn, 404, "Error downloading file: #{posix}") end end - get "/setup", do: redir(conn, "/") + get("/setup", do: redir(conn, "/")) -#NETWORKCONFIG + # NETWORKCONFIG get "/network" do interfaces = Farmbot.Target.Network.get_interfaces() - render_page(conn, "network", [interfaces: interfaces, post_action: "select_interface"]) + render_page(conn, "network", interfaces: interfaces, post_action: "select_interface") end post "select_interface" do {:ok, _, conn} = read_body(conn) interface = conn.body_params["interface"] |> remove_empty_string() + case interface do - nil -> redir(conn, "/network") - <<"w", _ ::binary >> = wireless -> redir(conn, "/config_wireless?ifname=#{wireless}") - wired -> redir(conn, "/config_wired?ifname=#{wired}") + nil -> redir(conn, "/network") + <<"w", _::binary>> = wireless -> redir(conn, "/config_wireless?ifname=#{wireless}") + wired -> redir(conn, "/config_wired?ifname=#{wired}") end end get "/config_wired" do try do - ifname = conn.params["ifname"] || raise(MissingField, field: "ifname", message: "ifname not provided", redir: "/network") - render_page(conn, "config_wired", [ifname: ifname, advanced_network: advanced_network()]) + ifname = + conn.params["ifname"] || + raise(MissingField, field: "ifname", message: "ifname not provided", redir: "/network") + + render_page(conn, "config_wired", ifname: ifname, advanced_network: advanced_network()) rescue e in MissingField -> - Farmbot.Logger.error 1, Exception.message(e) + Farmbot.Logger.error(1, Exception.message(e)) redir(conn, e.redir) end end get "/config_wireless" do try do - ifname = conn.params["ifname"] || raise(MissingField, field: "ifname", message: "ifname not provided", redir: "/network") - opts = [ifname: ifname, ssids: Farmbot.Target.Network.scan(ifname), post_action: "config_wireless_step_1"] + ifname = + conn.params["ifname"] || + raise(MissingField, field: "ifname", message: "ifname not provided", redir: "/network") + + opts = [ + ifname: ifname, + ssids: Farmbot.Target.Network.scan(ifname), + post_action: "config_wireless_step_1" + ] + render_page(conn, "/config_wireless_step_1", opts) rescue e in MissingField -> redir(conn, e.redir) @@ -105,45 +131,84 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do post "config_wireless_step_1" do try do - ifname = conn.params["ifname"] |> remove_empty_string() || raise(MissingField, field: "ifname", message: "ifname not provided", redir: "/network") - ssid = conn.params["ssid"] |> remove_empty_string() + ifname = + conn.params["ifname"] |> remove_empty_string() || + raise(MissingField, field: "ifname", message: "ifname not provided", redir: "/network") + + ssid = conn.params["ssid"] |> remove_empty_string() security = conn.params["security"] |> remove_empty_string() manualssid = conn.params["manualssid"] |> remove_empty_string() - opts = [ssid: ssid, ifname: ifname, security: security, advanced_network: advanced_network(), post_action: "config_network"] + + opts = [ + ssid: ssid, + ifname: ifname, + security: security, + advanced_network: advanced_network(), + post_action: "config_network" + ] + cond do - manualssid != nil -> render_page(conn, "/config_wireless_step_2_custom", Keyword.put(opts, :ssid, manualssid)) - ssid == nil -> raise(MissingField, field: "ssid", message: "ssid not provided", redir: "/config_wireless?ifname=#{ifname}") - security == nil -> raise(MissingField, field: "security", message: "security not provided", redir: "/config_wireless?ifname=#{ifname}") - security == "WPA-PSK" -> render_page(conn, "/config_wireless_step_2_PSK", opts) - security == "WPA-EAP" -> render_page(conn, "/config_wireless_step_2_EAP", opts) - security == "NONE" -> render_page(conn, "/config_wireless_step_2_NONE", opts) - true -> render_page(conn, "/config_wireless_step_2_other", opts) + manualssid != nil -> + render_page( + conn, + "/config_wireless_step_2_custom", + Keyword.put(opts, :ssid, manualssid) + ) + + ssid == nil -> + raise(MissingField, + field: "ssid", + message: "ssid not provided", + redir: "/config_wireless?ifname=#{ifname}" + ) + + security == nil -> + raise(MissingField, + field: "security", + message: "security not provided", + redir: "/config_wireless?ifname=#{ifname}" + ) + + security == "WPA-PSK" -> + render_page(conn, "/config_wireless_step_2_PSK", opts) + + security == "WPA-PSK" -> + render_page(conn, "/config_wireless_step_2_PSK", opts) + + security == "NONE" -> + render_page(conn, "/config_wireless_step_2_NONE", opts) + + true -> + render_page(conn, "/config_wireless_step_2_other", opts) end rescue e in MissingField -> - Farmbot.Logger.error 1, Exception.message(e) + Farmbot.Logger.error(1, Exception.message(e)) redir(conn, e.redir) end end post "/config_network" do try do - ifname = conn.params["ifname"] || raise(MissingField, field: "ifname", message: "ifname not provided", redir: "/network") - ssid = conn.params["ssid"] |> remove_empty_string() - security = conn.params["security"] |> remove_empty_string() - identity = conn.params["identity"] |> remove_empty_string() - password = conn.params["password"] |> remove_empty_string() - psk = conn.params["psk"] |> remove_empty_string() - domain = conn.params["domain"] |> remove_empty_string() - name_servers = conn.params["name_servers"] |> remove_empty_string() - ipv4_method = conn.params["ipv4_method"] |> remove_empty_string() - ipv4_address = conn.params["ipv4_address"] |> remove_empty_string() - ipv4_gateway = conn.params["ipv4_gateway"] |> remove_empty_string() + ifname = + conn.params["ifname"] || + raise(MissingField, field: "ifname", message: "ifname not provided", redir: "/network") + + ssid = conn.params["ssid"] |> remove_empty_string() + security = conn.params["security"] |> remove_empty_string() + psk = conn.params["psk"] |> remove_empty_string() + identity = conn.params["identity"] |> remove_empty_string() + password = conn.params["password"] |> remove_empty_string() + domain = conn.params["domain"] |> remove_empty_string() + name_servers = conn.params["name_servers"] |> remove_empty_string() + ipv4_method = conn.params["ipv4_method"] |> remove_empty_string() + ipv4_address = conn.params["ipv4_address"] |> remove_empty_string() + ipv4_gateway = conn.params["ipv4_gateway"] |> remove_empty_string() ipv4_subnet_mask = conn.params["ipv4_subnet_mask"] |> remove_empty_string() - dns_name = conn.params["dns_name"] |> remove_empty_string() - ntp_server_1 = conn.params["ntp_server_1"] |> remove_empty_string() - ntp_server_2 = conn.params["ntp_server_2"] |> remove_empty_string() + dns_name = conn.params["dns_name"] |> remove_empty_string() + ntp_server_1 = conn.params["ntp_server_1"] |> remove_empty_string() + ntp_server_2 = conn.params["ntp_server_2"] |> remove_empty_string() reg_domain = conn.params["regulatory_domain"] |> remove_empty_string() @@ -167,7 +232,9 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do Config.input_network_config!(%{ name: ifname, - ssid: ssid, security: security, psk: psk, + ssid: ssid, + security: security, + psk: psk, type: if(ssid, do: "wireless", else: "wired"), domain: domain, identity: identity, @@ -179,21 +246,30 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do ipv4_subnet_mask: ipv4_subnet_mask, regulatory_domain: reg_domain, }) + redir(conn, "/firmware") rescue e in MissingField -> - Farmbot.Logger.error 1, Exception.message(e) + Farmbot.Logger.error(1, Exception.message(e)) redir(conn, e.redir) end end -#/NETWORKCONFIG + + # /NETWORKCONFIG get "/credentials" do email = get_config_value(:string, "authorization", "email") || "" + pass = get_config_value(:string, "authorization", "password") || "" server = get_config_value(:string, "authorization", "server") || "" first_boot = get_config_value(:bool, "settings", "first_boot") update_config_value(:string, "authorization", "token", nil) - render_page(conn, "credentials", server: server, email: email, password: "", first_boot: first_boot) + + render_page(conn, "credentials", + server: server, + email: email, + password: pass, + first_boot: first_boot + ) end get "/firmware" do @@ -207,8 +283,9 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do %{"firmware_hardware" => hw} when hw in ["arduino", "farmduino", "farmduino_k14"] -> update_config_value(:string, "settings", "firmware_hardware", hw) - if Application.get_env(:farmbot, :behaviour)[:firmware_handler] == Farmbot.Firmware.UartHandler do - Farmbot.Logger.warn 1, "Updating #{hw} firmware." + if Application.get_env(:farmbot, :behaviour)[:firmware_handler] == + Farmbot.Firmware.UartHandler do + Farmbot.Logger.warn(1, "Updating #{hw} firmware.") # /shrug? Farmbot.Firmware.UartHandler.Update.force_update_firmware(hw) end @@ -233,10 +310,11 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do case conn.body_params do %{"email" => email, "password" => pass, "server" => server} -> if server = test_uri(server) do - IO.puts "server valid: #{server}" + IO.puts("server valid: #{server}") else send_resp(conn, 500, "server field invalid") end + update_config_value(:string, "authorization", "email", email) update_config_value(:string, "authorization", "password", pass) update_config_value(:string, "authorization", "server", server) @@ -252,27 +330,34 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do email = get_config_value(:string, "authorization", "email") pass = get_config_value(:string, "authorization", "password") server = get_config_value(:string, "authorization", "server") - network = !(Enum.empty?(Config.get_all_network_configs())) + network = !Enum.empty?(Config.get_all_network_configs()) + if email && pass && server && network do conn = render_page(conn, "finish") - spawn fn() -> + + spawn(fn -> try do alias Farmbot.Target.Bootstrap.Configurator - Farmbot.Logger.success 2, "Configuration finished." - Process.sleep(2500) # Allow the page to render and send. + Farmbot.Logger.success(2, "Configuration finished.") + # Allow the page to render and send. + Process.sleep(2500) :ok = GenServer.stop(Configurator.CaptivePortal, :normal) # :ok = Supervisor.terminate_child(Configurator, Configurator.CaptivePortal) :ok = Supervisor.stop(Configurator) - Process.sleep(2500) # Good luck. + # Good luck. + Process.sleep(2500) rescue e -> - Farmbot.Logger.warn 1, "Falied to close captive portal. Good luck. " <> - Exception.message(e) + Farmbot.Logger.warn( + 1, + "Falied to close captive portal. Good luck. " <> Exception.message(e) + ) end - end + end) + conn else - Farmbot.Logger.warn 3, "Not configured yet. Restarting configuration." + Farmbot.Logger.warn(3, "Not configured yet. Restarting configuration.") redir(conn, "/") end end @@ -288,14 +373,14 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do defp render_page(conn, page, info \\ []) do page |> template_file() - |> EEx.eval_file(info, [engine: Phoenix.HTML.Engine]) + |> EEx.eval_file(info, engine: Phoenix.HTML.Engine) |> (fn {:safe, contents} -> send_resp(conn, 200, contents) end).() rescue e -> send_resp(conn, 500, "Failed to render page: #{page} inspect: #{Exception.message(e)}") end defp template_file(file) do - "#{:code.priv_dir(:farmbot_os)}/static/templates/#{file}.html.eex" + "#{:code.priv_dir(:farmbot)}/static/templates/#{file}.html.eex" end defp remove_empty_string(""), do: nil @@ -312,11 +397,11 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do defp test_uri(uri) do case URI.parse(uri) do %URI{host: host, port: port, scheme: scheme} - when scheme in ["https", "http"] - and is_binary(host) - and is_integer(port) -> uri + when scheme in ["https", "http"] and is_binary(host) and is_integer(port) -> + uri + _ -> - IO.puts "#{inspect uri} is not valid" + IO.puts("#{inspect(uri)} is not valid") nil end end diff --git a/farmbot_os/platform/target/gpio/elixir_ale_gpio_handler.ex b/farmbot_os/platform/target/gpio/elixir_ale_gpio_handler.ex new file mode 100644 index 00000000..ac1c0194 --- /dev/null +++ b/farmbot_os/platform/target/gpio/elixir_ale_gpio_handler.ex @@ -0,0 +1,45 @@ +defmodule Farmbot.PinBindingWorker.ElixirALEGPIOHandler do + @moduledoc "ElixirALE gpio handler for PinBindings" + @behaviour Farmbot.AssetWorker.Farmbot.Asset.PinBinding + require Logger + use GenServer + alias ElixirALE.GPIO + + @debounce_timeout_ms 1000 + + 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("AleBindingHandler crash: #{inspect(reason)}") + end + + def init([pin_number, fun]) do + Logger.info("AleBindingHandler init") + {:ok, pid} = GPIO.start_link(pin_number, :input) + :ok = GPIO.set_int(pid, :both) + {:ok, %{pin_number: pin_number, pid: pid, fun: fun, debounce: nil}} + end + + def handle_info(:timeout, state) do + Logger.info("AleBindingHandler #{state.pin_number} debounce cleared") + {:noreply, %{state | debounce: nil}} + end + + def handle_info({:gpio_interupt, pin, _}, %{debounce: timer} = state) + when is_reference(timer) do + left = Process.read_timer(timer) + Logger.info("AleBindingHandler #{pin} still debounced for #{left} ms") + {:noreply, state} + end + + def handle_info({:gpio_interupt, _pin, _signal}, state) do + state.fun.() + {:noreply, state, %{state | debounce: debounce_timer()}} + end + + def name(pin_number), do: :"#{__MODULE__}.#{pin_number}" + + defp debounce_timer, do: Process.send_after(self(), :timeout, @debounce_timeout_ms) +end diff --git a/farmbot_os/platform/target/gpio/leds_ale_handler.ex b/farmbot_os/platform/target/gpio/leds_ale_handler.ex index 50beb407..89a815c3 100644 --- a/farmbot_os/platform/target/gpio/leds_ale_handler.ex +++ b/farmbot_os/platform/target/gpio/leds_ale_handler.ex @@ -21,7 +21,7 @@ defmodule Farmbot.Target.Leds.AleHandler do use GenServer def start_link(args) do - GenServer.start_link(__MODULE__, args, [name: __MODULE__]) + GenServer.start_link(__MODULE__, args, name: __MODULE__) end def init([]) do @@ -83,7 +83,9 @@ defmodule Farmbot.Target.Leds.AleHandler do timer = restart_timer(state[color].blink_timer, color, @fast_blink_speed) n = %{state[color] | state: new_led_state, blink_timer: timer, status: :fast_blink} update_color(state, color, n) - _ -> state + + _ -> + state end {:noreply, new_state} diff --git a/farmbot_os/platform/target/info_workers/disk_usage_worker.ex b/farmbot_os/platform/target/info_workers/disk_usage_worker.ex index c97f0a61..733d70ab 100644 --- a/farmbot_os/platform/target/info_workers/disk_usage_worker.ex +++ b/farmbot_os/platform/target/info_workers/disk_usage_worker.ex @@ -1,37 +1,41 @@ defmodule Farmbot.Target.DiskUsageWorker do - use GenServer - @data_path Application.get_env(:farmbot_ext, :data_path) - @data_path || Mix.raise("No data path.") + @moduledoc false - def start_link(_, opts) do - GenServer.start_link(__MODULE__, [], opts) + use GenServer + @data_path Farmbot.OS.FileSystem.data_path() + @default_timeout_ms 60_000 + @error_timeout_ms 5_000 + + def start_link(args) do + GenServer.start_link(__MODULE__, args) end def init([]) do - send(self(), :report_disk_usage) - {:ok, %{}} + {:ok, nil, 0} end - def handle_info(:report_disk_usage, state) do + def handle_info(:timeout, state) do usage = collect_report() if GenServer.whereis(Farmbot.BotState) do Farmbot.BotState.report_disk_usage(usage) - Process.send_after(self(), :report_disk_usage, 60_000) + {:noreply, state, @default_timeout_ms} else - Process.send_after(self(), :report_disk_usage, 5000) + {:noreply, state, @error_timeout_ms} end - {:noreply, state} end def collect_report do {usage_str, 0} = Nerves.Runtime.cmd("df", ["-h", @data_path], :return) - {usage, "%"} = usage_str + + {usage, "%"} = + usage_str |> String.split("\n") |> Enum.at(1) |> String.split(" ") |> Enum.at(-2) |> Integer.parse() + usage end end diff --git a/farmbot_os/platform/target/info_workers/memory_usage_worker.ex b/farmbot_os/platform/target/info_workers/memory_usage_worker.ex index f618f82d..d0dd6a0e 100644 --- a/farmbot_os/platform/target/info_workers/memory_usage_worker.ex +++ b/farmbot_os/platform/target/info_workers/memory_usage_worker.ex @@ -1,24 +1,27 @@ defmodule Farmbot.Target.MemoryUsageWorker do + @moduledoc false + use GenServer - def start_link(_, opts) do - GenServer.start_link(__MODULE__, [], opts) + @default_timeout_ms 60_000 + @error_timeout_ms 5_000 + + def start_link(args) do + GenServer.start_link(__MODULE__, args) end def init([]) do - send(self(), :report_memory_usage) - {:ok, %{}} + {:ok, nil, 0} end - def handle_info(:report_memory_usage, state) do + def handle_info(:timeout, state) do usage = collect_report() if GenServer.whereis(Farmbot.BotState) do Farmbot.BotState.report_memory_usage(usage) - Process.send_after(self(), :report_memory_usage, 60_000) + {:noreply, state, @default_timeout_ms} else - Process.send_after(self(), :report_memory_usage, 5000) + {:noreply, state, @error_timeout_ms} end - {:noreply, state} end def collect_report do diff --git a/farmbot_os/platform/target/info_workers/soc_temp_worker.ex b/farmbot_os/platform/target/info_workers/soc_temp_worker.ex index ce9c94b9..147c4f1c 100644 --- a/farmbot_os/platform/target/info_workers/soc_temp_worker.ex +++ b/farmbot_os/platform/target/info_workers/soc_temp_worker.ex @@ -1,28 +1,34 @@ defmodule Farmbot.Target.SocTempWorker do + @moduledoc false + use GenServer + @default_timeout_ms 60_000 + @error_timeout_ms 5_000 + def start_link(args) do - GenServer.start_link(__MODULE__, args, name: __MODULE__) + GenServer.start_link(__MODULE__, args) end def init([]) do - send(self(), :report_temp) - {:ok, %{}} + {:ok, nil, 0} end def handle_info(:report_temp, state) do {temp_str, 0} = Nerves.Runtime.cmd("vcgencmd", ["measure_temp"], :return) - temp = temp_str + + temp = + temp_str |> String.trim() |> String.split("=") |> List.last() - |> Float.parse + |> Float.parse() |> elem(0) + if GenServer.whereis(Farmbot.BotState) do Farmbot.BotState.report_soc_temp(temp) - Process.send_after(self(), :report_temp, 60_000) + {:noreply, state, @default_timeout_ms} else - Process.send_after(self(), :report_temp, 5000) + {:noreply, state, @error_timeout_ms} end - {:noreply, state} end end diff --git a/farmbot_os/platform/target/info_workers/uptime_worker.ex b/farmbot_os/platform/target/info_workers/uptime_worker.ex index 38e07756..d81799ae 100644 --- a/farmbot_os/platform/target/info_workers/uptime_worker.ex +++ b/farmbot_os/platform/target/info_workers/uptime_worker.ex @@ -1,12 +1,16 @@ defmodule Farmbot.Target.UptimeWorker do + @moduledoc false + use GenServer - def start_link(_, opts) do - GenServer.start_link(__MODULE__, [], opts) + @default_timeout_ms 60_000 + @error_timeout_ms 5_000 + + def start_link(args) do + GenServer.start_link(__MODULE__, args) end def init([]) do - send(self(), :report_uptime) - {:ok, %{}} + {:ok, nil, 0} end def handle_info(:report_uptime, state) do @@ -14,11 +18,10 @@ defmodule Farmbot.Target.UptimeWorker do if GenServer.whereis(Farmbot.BotState) do Farmbot.BotState.report_uptime(usage) - Process.send_after(self(), :report_uptime, 60_000) + {:noreply, state, @default_timeout_ms} else - Process.send_after(self(), :report_uptime, 5000) + {:noreply, state, @error_timeout_ms} end - {:noreply, state} end def collect_report do diff --git a/farmbot_os/platform/target/network/dns_task.ex b/farmbot_os/platform/target/network/dns_task.ex index 636a753e..7db75020 100644 --- a/farmbot_os/platform/target/network/dns_task.ex +++ b/farmbot_os/platform/target/network/dns_task.ex @@ -3,6 +3,7 @@ defmodule Farmbot.Target.Network.DnsTask do use GenServer import Farmbot.Target.Network, only: [test_dns: 0] import Farmbot.Config, only: [get_config_value: 3] + @default_timeout_ms 45_000 def start_link(args) do GenServer.start_link(__MODULE__, args, name: __MODULE__) @@ -11,55 +12,53 @@ defmodule Farmbot.Target.Network.DnsTask do def init([]) do # Block and reset if after 10 tries # resolution doesn't work. - r = block_check(true) - {:ok, %{timer: start_timer(), last_result: r}} + block_check(true) + {:ok, nil, @default_timeout_ms} end - def handle_info(:checkup, state) do + def handle_info(:timeout, state) do # Block and don't reset if after 10 tries # resolution doesn't work. - result = block_check(state.last_result) - {:noreply, %{state | last_result: result, timer: start_timer()}} + block_check() + {:noreply, state, @default_timeout_ms} end defp block_check(last_result, reset \\ false, tries \\ 10) defp block_check(last_result, false, 0) do server = get_config_value(:string, "authorization", "server") - Farmbot.Logger.error 1, "Could not resolve #{server} after 10 tries." + Farmbot.Logger.error(1, "Could not resolve #{server} after 10 tries.") end defp block_check(_last_result, true, 0) do server = get_config_value(:string, "authorization", "server") - Farmbot.Logger.error 1, "Tried 10 times to resolve DNS requests." + Farmbot.Logger.error(1, "Tried 10 times to resolve DNS requests.") + msg = """ FarmBot is unable to make DNS requests to #{server} after 10 tries. It is possible your network has a firewall blocking this url, or your FarmBot is configured incorrectly. """ + Farmbot.System.factory_reset(msg) :error end defp block_check(last_result, reset, tries) do server = get_config_value(:string, "authorization", "server") + case test_dns() do {:ok, _} -> - if last_result == :error do - Logger.success(1, "DNS resolution successful") - end :ok + {:error, :nxdomain} -> Process.sleep(10_000) - Farmbot.Logger.error 1, "Trying to resolve #{server} #{tries - 1} more times." - block_check(last_result, reset, tries - 1) + Farmbot.Logger.error(1, "Trying to resolve #{server} #{tries - 1} more times.") + block_check(reset, tries - 1) + err -> - Farmbot.Logger.error 1, "Failed to resolve #{server}: #{inspect err}" - block_check(last_result, reset, tries) + Farmbot.Logger.error(1, "Failed to resolve #{server}: #{inspect(err)}") + block_check(reset, tries) end end - - defp start_timer do - Process.send_after(self(), :checkup, 45_000) - end end diff --git a/farmbot_os/platform/target/network/info_supervisor.ex b/farmbot_os/platform/target/network/info_supervisor.ex index 4a26d50a..b6b21917 100644 --- a/farmbot_os/platform/target/network/info_supervisor.ex +++ b/farmbot_os/platform/target/network/info_supervisor.ex @@ -3,14 +3,17 @@ defmodule Farmbot.Target.Network.InfoSupervisor do alias Farmbot.Config def start_link(args) do - Supervisor.start_link(__MODULE__, args, [name: __MODULE__]) + Supervisor.start_link(__MODULE__, args, name: __MODULE__) end def init([]) do configs = Config.get_all_network_configs() - children = Enum.map(configs, fn(config) -> - {Farmbot.Target.Network.InfoWorker, [config]} - end) - Supervisor.init(children, [strategy: :one_for_one]) + + children = + Enum.map(configs, fn config -> + {Farmbot.Target.Network.InfoWorker, [config]} + end) + + Supervisor.init(children, strategy: :one_for_one) end end diff --git a/farmbot_os/platform/target/network/info_worker.ex b/farmbot_os/platform/target/network/info_worker.ex index d17447e3..0a2e8b08 100644 --- a/farmbot_os/platform/target/network/info_worker.ex +++ b/farmbot_os/platform/target/network/info_worker.ex @@ -1,26 +1,26 @@ defmodule Farmbot.Target.Network.InfoWorker do + @moduledoc false use GenServer + @default_timeout_ms 60_000 def start_link(args) do GenServer.start_link(__MODULE__, args) end def init([config]) do - send(self(), :report_info) - {:ok, config} + {:ok, config, 0} end - def handle_info(:report_info, %{type: "wireless"} = config) do + def handle_info(:timeout, %{type: "wireless"} = config) do if level = Farmbot.Target.Network.get_level(config.name, config.ssid) do report_wifi_level(level) end - Process.send_after(self(), :report_info, 60_000) - {:noreply, config} + + {:noreply, config, @default_timeout_ms} end - def handle_info(:report_info, config) do - Process.send_after(self(), :report_info, 60_000) - {:noreply, config} + def handle_info(:timeout, config) do + {:noreply, config, :hibernate} end def report_wifi_level(level) do diff --git a/farmbot_os/platform/target/network/manager.ex b/farmbot_os/platform/target/network/manager.ex index b4481830..7aa634ec 100644 --- a/farmbot_os/platform/target/network/manager.ex +++ b/farmbot_os/platform/target/network/manager.ex @@ -30,7 +30,7 @@ defmodule Farmbot.Target.Network.Manager do end def start_link(interface, opts) do - GenServer.start_link(__MODULE__, {interface, opts}, [name: :"#{__MODULE__}-#{interface}"]) + GenServer.start_link(__MODULE__, {interface, opts}, name: :"#{__MODULE__}-#{interface}") end def init({interface, opts} = args) do @@ -55,6 +55,7 @@ defmodule Farmbot.Target.Network.Manager do domain = node() |> to_string() |> String.split("@") |> List.last() |> Kernel.<>(".local") init_mdns(domain) + state = %{ # These won't change mdns_domain: domain, @@ -71,8 +72,9 @@ defmodule Farmbot.Target.Network.Manager do reconnect_timer: nil, # Tests internet connectivity. - dns_timer: nil, + dns_timer: nil } + {:ok, state} end @@ -86,9 +88,10 @@ defmodule Farmbot.Target.Network.Manager do # When assigned an IP address. def handle_info({Nerves.Udhcpc, :bound, %{ipv4_address: ip}}, state) do - Farmbot.Logger.debug 3, "Ip address: #{ip}" + Farmbot.Logger.debug(3, "Ip address: #{ip}") NotFoundTimer.stop() connected = match?({:ok, {:hostent, _, _, :inet, 4, _}}, test_dns()) + if connected do init_mdns(state.mdns_domain) dns_timer = restart_dns_timer(state.dns_timer, 45_000) @@ -99,8 +102,12 @@ defmodule Farmbot.Target.Network.Manager do end end - def handle_info({Nerves.WpaSupplicant, {:INFO, "WPA: 4-Way Handshake failed - pre-shared key may be incorrect"}, _}, state) do - Farmbot.Logger.error 1, "Incorrect PSK." + def handle_info( + {Nerves.WpaSupplicant, + {:INFO, "WPA: 4-Way Handshake failed - pre-shared key may be incorrect"}, _}, + state + ) do + Farmbot.Logger.error(1, "Incorrect PSK.") Farmbot.System.factory_reset("WIFI Authentication failed. (incorrect psk)") {:stop, :normal, state} end @@ -117,18 +124,21 @@ defmodule Farmbot.Target.Network.Manager do reconnect_timer = if state.connected, do: restart_connection_timer(state) maybe_refresh_token() NotFoundTimer.start() - new_state = %{state | - ap_connected: false, - connected: false, - ip_address: nil, - reconnect_timer: reconnect_timer + + new_state = %{ + state + | ap_connected: false, + connected: false, + ip_address: nil, + reconnect_timer: reconnect_timer } + {:noreply, new_state} end def handle_info({Nerves.WpaSupplicant, :"CTRL-EVENT-CONNECTED", _}, state) do # Don't update `connected`. This is not a real test of connectivity. - Farmbot.Logger.success 1, "Connected to access point." + Farmbot.Logger.success(1, "Connected to access point.") NotFoundTimer.stop() {:noreply, %{state | ap_connected: true}} end @@ -138,12 +148,15 @@ defmodule Farmbot.Target.Network.Manager do reconnect_timer = if state.connected, do: restart_connection_timer(state) maybe_refresh_token() NotFoundTimer.start() - new_state = %{state | - ap_connected: false, - connected: false, - ip_address: nil, - reconnect_timer: reconnect_timer + + new_state = %{ + state + | ap_connected: false, + connected: false, + ip_address: nil, + reconnect_timer: reconnect_timer } + {:noreply, new_state} end @@ -161,12 +174,12 @@ defmodule Farmbot.Target.Network.Manager do end def handle_info(:reconnect_timer, %{ap_connected: false} = state) do - Farmbot.Logger.warn 1, "Wireless network not found still. Trying again." + Farmbot.Logger.warn(1, "Wireless network not found still. Trying again.") {:stop, :reconnect_timer, state} end def handle_info(:reconnect_timer, %{ap_connected: true} = state) do - Farmbot.Logger.success 1, "Wireless network reconnected." + Farmbot.Logger.success(1, "Wireless network reconnected.") {:noreply, state} end @@ -178,13 +191,13 @@ defmodule Farmbot.Target.Network.Manager do {:error, err} -> maybe_refresh_token() - Farmbot.Logger.warn 3, "Farmbot was disconnected from the internet: #{inspect err}" + Farmbot.Logger.warn(3, "Farmbot was disconnected from the internet: #{inspect(err)}") {:noreply, %{state | connected: false, dns_timer: restart_dns_timer(nil, 20_000)}} end end def handle_info(:dns_timer, %{ip_address: nil} = state) do - Farmbot.Logger.warn 3, "Farmbot still disconnected from the internet" + Farmbot.Logger.warn(3, "Farmbot still disconnected from the internet") {:noreply, %{state | connected: false, dns_timer: restart_dns_timer(nil, 20_000)}} end @@ -192,16 +205,13 @@ defmodule Farmbot.Target.Network.Manager do case test_dns() do {:ok, {:hostent, _host_name, aliases, :inet, 4, _}} -> # If we weren't previously connected, send a log. - Farmbot.Logger.success 3, "Farmbot was reconnected to the internet: #{inspect aliases}" + Farmbot.Logger.success(3, "Farmbot was reconnected to the internet: #{inspect(aliases)}") maybe_refresh_token() - new_state = %{state | - connected: true, - dns_timer: restart_dns_timer(nil, 45_000), - } + new_state = %{state | connected: true, dns_timer: restart_dns_timer(nil, 45_000)} {:noreply, new_state} {:error, err} -> - Farmbot.Logger.warn 3, "Farmbot was disconnected from the internet: #{inspect err}" + Farmbot.Logger.warn(3, "Farmbot was disconnected from the internet: #{inspect(err)}") maybe_refresh_token() {:noreply, %{state | connected: false, dns_timer: restart_dns_timer(nil, 20_000)}} end @@ -218,6 +228,7 @@ defmodule Farmbot.Target.Network.Manager do # Farmbot.Logger.warn 3, "Cancelling Network timer" Process.cancel_timer(timer) end + nil end @@ -238,7 +249,7 @@ defmodule Farmbot.Target.Network.Manager do if Process.whereis(Farmbot.Bootstrap.AuthTask) do Farmbot.Bootstrap.AuthTask.force_refresh() else - Farmbot.Logger.warn 1, "AuthTask not running yet" + Farmbot.Logger.warn(1, "AuthTask not running yet") end end @@ -275,13 +286,17 @@ defmodule Farmbot.Target.Network.Manager do @tzdata_dir Application.app_dir(:tzdata, "priv") def maybe_hack_tzdata do case Tzdata.Util.data_dir() do - @fb_data_dir -> :ok + @fb_data_dir -> + :ok + _ -> - Farmbot.Logger.debug 3, "Hacking tzdata." + Farmbot.Logger.debug(3, "Hacking tzdata.") objs_to_cp = Path.wildcard(Path.join(@tzdata_dir, "*")) + for obj <- objs_to_cp do - File.cp_r obj, @fb_data_dir + File.cp_r(obj, @fb_data_dir) end + Application.put_env(:tzdata, :data_dir, @fb_data_dir) :ok end diff --git a/farmbot_os/platform/target/network/network.ex b/farmbot_os/platform/target/network/network.ex index eabfa342..732070eb 100644 --- a/farmbot_os/platform/target/network/network.ex +++ b/farmbot_os/platform/target/network/network.ex @@ -13,17 +13,20 @@ defmodule Farmbot.Target.Network do @doc "List available interfaces. Removes unusable entries." def get_interfaces(tries \\ 5) def get_interfaces(0), do: [] + def get_interfaces(tries) do case Nerves.NetworkInterface.interfaces() do ["lo"] -> Process.sleep(100) get_interfaces(tries - 1) + interfaces when is_list(interfaces) -> interfaces - |> List.delete("usb0") # Delete unusable entries if they exist. + # Delete unusable entries if they exist. + |> List.delete("usb0") |> List.delete("lo") |> List.delete("sit0") - |> Map.new(fn(interface) -> + |> Map.new(fn interface -> {:ok, settings} = Nerves.NetworkInterface.status(interface) {interface, settings} end) @@ -37,9 +40,9 @@ defmodule Farmbot.Target.Network do |> ScanResult.sort_results() |> ScanResult.decode_security() |> Enum.filter(&Map.get(&1, :ssid)) - |> Enum.map(&Map.update(&1, :ssid, nil, fn(ssid) -> to_string(ssid) end)) + |> Enum.map(&Map.update(&1, :ssid, nil, fn ssid -> to_string(ssid) end)) |> Enum.reject(&String.contains?(&1.ssid, "\\x00")) - |> Enum.uniq_by(fn(%{ssid: ssid}) -> ssid end) + |> Enum.uniq_by(fn %{ssid: ssid} -> ssid end) end defp wait_for_results(pid) do @@ -53,13 +56,16 @@ defmodule Farmbot.Target.Network do [] -> Process.sleep(500) wait_for_results(pid) - res -> res + + res -> + res end end defp reduce_decode(results, acc \\ []) defp reduce_decode([], acc), do: Enum.reverse(acc) - defp reduce_decode([ [bssid, freq, signal, flags, ssid] | rest], acc) do + + defp reduce_decode([[bssid, freq, signal, flags, ssid] | rest], acc) do decoded = %{ bssid: bssid, frequency: String.to_integer(freq), @@ -67,16 +73,19 @@ defmodule Farmbot.Target.Network do level: String.to_integer(signal), ssid: ssid } + reduce_decode(rest, [decoded | acc]) end - defp reduce_decode([ [bssid, freq, signal, flags] | rest], acc) do - decoded = %{bssid: bssid, + defp reduce_decode([[bssid, freq, signal, flags] | rest], acc) do + decoded = %{ + bssid: bssid, frequency: String.to_integer(freq), flags: flags, level: String.to_integer(signal), ssid: nil } + reduce_decode(rest, [decoded | acc]) end @@ -86,6 +95,7 @@ defmodule Farmbot.Target.Network do def do_scan(iface) do pid = :"Nerves.WpaSupplicant.#{iface}" + if Process.whereis(pid) do Nerves.WpaSupplicant.request(pid, :SCAN) wait_for_results(pid) @@ -96,7 +106,8 @@ defmodule Farmbot.Target.Network do def get_level(ifname, ssid) do r = Farmbot.Target.Network.scan(ifname) - if res = Enum.find(r, &Map.get(&1, :ssid) == ssid) do + + if res = Enum.find(r, &(Map.get(&1, :ssid) == ssid)) do res.level end end @@ -106,7 +117,9 @@ defmodule Farmbot.Target.Network do def test_dns(nil) do case get_config_value(:string, "authorization", "server") do - nil -> test_dns(get_config_value(:string, "settings", "default_dns_name")) + nil -> + test_dns(get_config_value(:string, "settings", "default_dns_name")) + url when is_binary(url) -> %URI{host: hostname} = URI.parse(url) test_dns(hostname) @@ -200,7 +213,7 @@ defmodule Farmbot.Target.Network do end def to_child_spec({interface, opts}) do - worker(NetworkManager, [interface, opts], [restart: :transient]) + worker(NetworkManager, [interface, opts], restart: :transient) end def start_link(args) do @@ -214,10 +227,14 @@ defmodule Farmbot.Target.Network do s2 = Farmbot.Config.get_config_value(:string, "settings", "default_ntp_server_2") Nerves.Time.set_ntp_servers([s1, s2]) maybe_hack_tzdata() - children = config + + children = + config |> Enum.map(&to_network_config/1) |> Enum.map(&to_child_spec/1) - |> Enum.uniq() # Don't know why/if we need this? + # Don't know why/if we need this? + |> Enum.uniq() + children = [{NotFoundTimer, []}] ++ children Supervisor.init(children, strategy: :one_for_one, max_restarts: 20, max_seconds: 1) end @@ -226,13 +243,17 @@ defmodule Farmbot.Target.Network do @tzdata_dir Application.app_dir(:tzdata, "priv") def maybe_hack_tzdata do case Tzdata.Util.data_dir() do - @fb_data_dir -> :ok + @fb_data_dir -> + :ok + _ -> - Farmbot.Logger.debug 3, "Hacking tzdata." + Farmbot.Logger.debug(3, "Hacking tzdata.") objs_to_cp = Path.wildcard(Path.join(@tzdata_dir, "*")) + for obj <- objs_to_cp do - File.cp_r obj, @fb_data_dir + File.cp_r(obj, @fb_data_dir) end + Application.put_env(:tzdata, :data_dir, @fb_data_dir) :ok end diff --git a/farmbot_os/platform/target/network/network_not_found_timer.ex b/farmbot_os/platform/target/network/network_not_found_timer.ex index 6d54e0d4..505137d6 100644 --- a/farmbot_os/platform/target/network/network_not_found_timer.ex +++ b/farmbot_os/platform/target/network/network_not_found_timer.ex @@ -16,7 +16,7 @@ defmodule Farmbot.Target.Network.NotFoundTimer do end def start_link(args) do - GenServer.start_link(__MODULE__, args, [name: __MODULE__]) + GenServer.start_link(__MODULE__, args, name: __MODULE__) end def init([]) do @@ -36,7 +36,7 @@ defmodule Farmbot.Target.Network.NotFoundTimer do minutes = get_config_value(:float, "settings", "network_not_found_timer") || 1 ms = (minutes * 60_000) |> round() timer = Process.send_after(self(), :timer, ms) - Farmbot.Logger.debug 1, "Starting network not found timer: #{minutes} minute(s)" + Farmbot.Logger.debug(1, "Starting network not found timer: #{minutes} minute(s)") {:reply, :ok, %{state | timer: timer}} end @@ -49,17 +49,20 @@ defmodule Farmbot.Target.Network.NotFoundTimer do if state.timer do Process.cancel_timer(state.timer) end + {:reply, :ok, %{state | timer: nil}} end def handle_info(:timer, state) do - delay_minutes = (get_config_value(:float, "settings", "network_not_found_timer") || 1) + delay_minutes = get_config_value(:float, "settings", "network_not_found_timer") || 1 disable_factory_reset? = get_config_value(:bool, "settings", "disable_factory_reset") first_boot? = get_config_value(:bool, "settings", "first_boot") + cond do disable_factory_reset? -> - Farmbot.Logger.warn 1, "Factory reset is disabled. Not resetting." + Farmbot.Logger.warn(1, "Factory reset is disabled. Not resetting.") {:noreply, %{state | timer: nil}} + first_boot? -> msg = """ Network not found after #{delay_minutes} minute(s). @@ -73,11 +76,14 @@ defmodule Farmbot.Target.Network.NotFoundTimer do 5) There is a hardware issue. """ - Farmbot.Logger.error 1, msg + + Farmbot.Logger.error(1, msg) Farmbot.System.factory_reset(msg) {:stop, :normal, %{state | timer: nil}} + true -> - Farmbot.Logger.error 1, "Network not found after timer. Farmbot is disconnected." + Farmbot.Logger.error(1, "Network not found after timer. Farmbot is disconnected.") + msg = """ Network not found after #{delay_minutes} minute(s). This can happen if your wireless access point is no longer available, @@ -86,6 +92,7 @@ defmodule Farmbot.Target.Network.NotFoundTimer do factory reset\" or tune the \"network not found timer\" value in the Farmbot Web Application. """ + Farmbot.System.factory_reset(msg) {:stop, :normal, %{state | timer: nil}} end diff --git a/farmbot_os/platform/target/network/scan_result.ex b/farmbot_os/platform/target/network/scan_result.ex index 5b3b1642..fbadb310 100644 --- a/farmbot_os/platform/target/network/scan_result.ex +++ b/farmbot_os/platform/target/network/scan_result.ex @@ -4,11 +4,16 @@ defmodule Farmbot.Target.Network.ScanResult do alias Farmbot.Target.Network.ScanResult defstruct [ - :ssid, # ssid name - :bssid, # usually macaddress. - :level, # Signal level in dBm. - :flags, # Used to decide security. - :security # This feild is guessed. + # ssid name + :ssid, + # usually macaddress. + :bssid, + # Signal level in dBm. + :level, + # Used to decide security. + :flags, + # This feild is guessed. + :security ] @doc "Decodes a map into a ScanResult, or a list of maps into a list of ScanResult." @@ -20,7 +25,6 @@ defmodule Farmbot.Target.Network.ScanResult do struct(ScanResult, map) end - @doc "Sorts a list of ssids by their level. This does not take noise into account." def sort_results(results) do # Pardon the magic. @@ -39,29 +43,32 @@ defmodule Farmbot.Target.Network.ScanResult do def decode_security_flags(str, state \\ %{in_flag: false, buffer: <<>>, acc: []}) def decode_security_flags(<<>>, %{acc: acc}), do: Enum.reverse(acc) |> decode_security_acc() - def decode_security_flags(<<"[", rest :: binary>>, %{in_flag: false} = state) do + def decode_security_flags(<<"[", rest::binary>>, %{in_flag: false} = state) do decode_security_flags(rest, %{state | in_flag: true, buffer: <<>>}) end - def decode_security_flags(<<"]", rest :: binary>>, %{in_flag: true, buffer: buffer, acc: acc} = state) do + def decode_security_flags( + <<"]", rest::binary>>, + %{in_flag: true, buffer: buffer, acc: acc} = state + ) do decode_security_flags(rest, %{state | in_flag: false, buffer: <<>>, acc: [buffer | acc]}) end - def decode_security_flags(<>, %{in_flag: true} = state) do + def decode_security_flags(<>, %{in_flag: true} = state) do decode_security_flags(rest, %{state | buffer: state.buffer <> char}) end def decode_security_acc(list, security \\ :NONE) - def decode_security_acc([<<"WPA2-EAP", _ :: binary>> | rest], _) do + def decode_security_acc([<<"WPA2-EAP", _::binary>> | rest], _) do decode_security_acc(rest, :"WPA-EAP") end - def decode_security_acc([<<"WPA2-PSK", _ :: binary>> | rest], _) do + def decode_security_acc([<<"WPA2-PSK", _::binary>> | rest], _) do decode_security_acc(rest, :"WPA-PSK") end - def decode_security_acc([<<"WPA-PSK", _ :: binary>> | rest], _) do + def decode_security_acc([<<"WPA-PSK", _::binary>> | rest], _) do decode_security_acc(rest, :"WPA-PSK") end diff --git a/farmbot_os/platform/target/network/tzdata_task.ex b/farmbot_os/platform/target/network/tzdata_task.ex index db1ab5f7..e8681d5d 100644 --- a/farmbot_os/platform/target/network/tzdata_task.ex +++ b/farmbot_os/platform/target/network/tzdata_task.ex @@ -1,29 +1,27 @@ defmodule Farmbot.Target.Network.TzdataTask do use GenServer - @fb_data_dir Path.join(Application.get_env(:farmbot_ext, :data_path), "tmp_downloads") - @timer_ms round(1.2e+6) # 20 minutes + @data_path Farmbot.OS.FileSystem.data_path() + # 20 minutes + @default_timeout_ms round(1.2e+6) def start_link(args) do - GenServer.start_link(__MODULE__, args, [name: __MODULE__]) + GenServer.start_link(__MODULE__, args) end def init([]) do - send self(), :do_checkup - {:ok, nil, :hibernate} + {:ok, nil, 0} end - def handle_info(:do_checkup, _) do - dir = @fb_data_dir + def handle_info(:do_checkup, state) do + dir = Path.join(@data_path, "tmp_downloads") + if File.exists?(dir) do for obj <- File.ls!(dir) do File.rm_rf!(Path.join(dir, obj)) end end - {:noreply, restart_timer(self()), :hibernate} - end - defp restart_timer(pid) do - Process.send_after pid, :do_checkup, @timer_ms + {:noreply, state, @default_timeout_ms} end end diff --git a/farmbot_os/platform/target/network/wait_for_time.ex b/farmbot_os/platform/target/network/wait_for_time.ex index 83395aa8..b71f44c4 100644 --- a/farmbot_os/platform/target/network/wait_for_time.ex +++ b/farmbot_os/platform/target/network/wait_for_time.ex @@ -1,16 +1,20 @@ defmodule Farmbot.Target.Network.WaitForTime do + @moduledoc "Blocks until time is synced." require Farmbot.Logger - nerves_time = case Nerves.Time.FileTime.time() do - {:error, _} -> NaiveDateTime.utc_now() - ndt -> ndt - end + + nerves_time = + case Nerves.Time.FileTime.time() do + {:error, _} -> NaiveDateTime.utc_now() + ndt -> ndt + end + @nerves_time nerves_time - def start_link(_, _) do + def start_link(_args) do :ok = wait_for_time() - Farmbot.Logger.success 3, "Time seems to be set. Moving on." - IO.puts "Check: #{inspect(@nerves_time)}" - IO.puts "Current: #{inspect(NaiveDateTime.utc_now())}" + Farmbot.Logger.success(3, "Time seems to be set. Moving on.") + IO.puts("Check: #{inspect(@nerves_time)}") + IO.puts("Current: #{inspect(NaiveDateTime.utc_now())}") :ignore end @@ -21,10 +25,11 @@ defmodule Farmbot.Target.Network.WaitForTime do defp wait_for_time do case Timex.compare(NaiveDateTime.utc_now(), get_file_time()) do - 1 -> :ok + 1 -> + :ok + _ -> Process.sleep(1000) - # Logger.warn "Waiting for time." wait_for_time() end end diff --git a/farmbot_os/platform/target/ssh_console.ex b/farmbot_os/platform/target/ssh_console.ex index 641a418e..86be8199 100644 --- a/farmbot_os/platform/target/ssh_console.ex +++ b/farmbot_os/platform/target/ssh_console.ex @@ -14,14 +14,15 @@ defmodule Farmbot.Target.SSHConsole do port = get_config_value(:float, "settings", "ssh_port") |> round() authorized_key = get_config_value(:string, "settings", "authorized_ssh_key") decoded_authorized_key = do_decode(authorized_key) + case start_ssh(port, decoded_authorized_key) do {:ok, ssh} -> {:ok, %{ssh: ssh}} + _ -> - Farmbot.Logger.warn 1, "Could not start SSH." + Farmbot.Logger.warn(1, "Could not start SSH.") :ignore end - end def terminate(_, %{ssh: ssh}) do @@ -31,7 +32,9 @@ defmodule Farmbot.Target.SSHConsole do defp start_ssh(port, decoded_authorized_keys) when is_list(decoded_authorized_keys) do # Reuse keys from `nerves_firmware_ssh` so that the user only needs one # config.exs entry. - nerves_keys = Application.get_env(:nerves_firmware_ssh, :authorized_keys, []) |> Enum.join("\n") + nerves_keys = + Application.get_env(:nerves_firmware_ssh, :authorized_keys, []) |> Enum.join("\n") + decoded_nerves_keys = do_decode(nerves_keys) cb_opts = [authorized_keys: decoded_nerves_keys ++ decoded_authorized_keys] @@ -55,7 +58,7 @@ defmodule Farmbot.Target.SSHConsole do :public_key.ssh_decode(authorized_key, :auth_keys) rescue _err -> - Farmbot.Logger.warn 3, "Could not decode ssh keys." + Farmbot.Logger.warn(3, "Could not decode ssh keys.") [] end end diff --git a/farmbot_os/platform/target/uevent.ex b/farmbot_os/platform/target/uevent.ex index d26dd8ff..39930e08 100644 --- a/farmbot_os/platform/target/uevent.ex +++ b/farmbot_os/platform/target/uevent.ex @@ -3,12 +3,12 @@ defmodule Farmbot.Target.Uevent.Supervisor do use Supervisor def start_link(args) do - Supervisor.start_link(__MODULE__, args, [name: __MODULE__]) + Supervisor.start_link(__MODULE__, args, name: __MODULE__) end def init([]) do children = [{Farmbot.Target.Uevent, []}] - Supervisor.init(children, [strategy: :one_for_one]) + Supervisor.init(children, strategy: :one_for_one) end end @@ -19,7 +19,7 @@ defmodule Farmbot.Target.Uevent do require Farmbot.Logger def start_link(args) do - GenServer.start_link(__MODULE__, args, [name: __MODULE__]) + GenServer.start_link(__MODULE__, args, name: __MODULE__) end def init([]) do @@ -30,11 +30,13 @@ defmodule Farmbot.Target.Uevent do def handle_info({:system_registry, :global, new_reg}, %{} = old_reg) do new_ttys = get_in(new_reg, [:state, "subsystems", "tty"]) || [] old_ttys = get_in(old_reg, [:state, "subsystems", "tty"]) || [] + case new_ttys -- old_ttys do - [new] -> new |> List.last() |> maybe_new_tty() + [new] -> new |> List.last() |> maybe_new_tty() [_ | _] -> Farmbot.Logger.warn(3, "Multiple new tty devices detected. Ignoring.") [] -> :ok end + {:noreply, new_reg} end @@ -46,18 +48,21 @@ defmodule Farmbot.Target.Uevent do def maybe_new_tty("ttyACM" <> _ = tty), do: new_tty(tty) def maybe_new_tty("ttyS" <> _), do: :ok def maybe_new_tty("tty" <> _), do: :ok + def maybe_new_tty(unknown) do - Farmbot.Logger.warn 1, "Unknown tty: #{inspect(unknown)}" + Farmbot.Logger.warn(1, "Unknown tty: #{inspect(unknown)}") end def new_tty(tty) do - Farmbot.Logger.busy 3, "Detected new UART Device: #{tty}" + raise("FIXME") + Farmbot.Logger.busy(3, "Detected new UART Device: #{tty}") Application.put_env(:farmbot_core, :uart_handler, tty: "/dev/" <> tty) old_env = Application.get_env(:farmbot_core, :behaviour) if old_env[:firmware_handler] == Farmbot.Firmware.StubHandler do new_env = Keyword.put(old_env, :firmware_handler, Farmbot.Firmware.UartHandler) Application.put_env(:farmbot_core, :behaviour, new_env) + if Process.whereis(Farmbot.Firmware) do GenServer.stop(Farmbot.Firmware, :shutdown) end