farmbot_os/farmbot_core/lib/firmware/stub_handler.ex

261 lines
7.9 KiB
Elixir

defmodule Farmbot.Firmware.StubHandler do
@moduledoc "Stubs out firmware functionality when you don't have an arduino."
use GenStage
require Farmbot.Logger
@behaviour Farmbot.Firmware.Handler
alias Farmbot.Firmware.Vec3
## Firmware Handler Behaviour.
def start_link do
Farmbot.Logger.warn(3, "Firmware is being stubbed.")
GenStage.start_link(__MODULE__, [])
end
def move_absolute(handler, pos, x_spd, y_spd, z_spd) do
GenStage.call(handler, {:move_absolute, pos, x_spd, y_spd, z_spd}, 120_000)
end
def calibrate(handler, axis) do
GenStage.call(handler, {:calibrate, axis})
end
def find_home(handler, axis) do
GenStage.call(handler, {:find_home, axis})
end
def home_all(handler) do
GenStage.call(handler, :home_all)
end
def home(handler, axis) do
GenStage.call(handler, {:home, axis})
end
def zero(handler, axis) do
GenStage.call(handler, {:zero, axis})
end
def update_param(handler, param, val) do
GenStage.call(handler, {:update_param, param, val})
end
def read_param(handler, param) do
GenStage.call(handler, {:read_param, param})
end
def read_all_params(handler) do
GenStage.call(handler, :read_all_params)
end
def emergency_lock(handler) do
GenStage.call(handler, :emergency_lock)
end
def emergency_unlock(handler) do
GenStage.call(handler, :emergency_unlock)
end
def set_pin_mode(handler, pin, mode) do
GenStage.call(handler, {:set_pin_mode, pin, mode})
end
def read_pin(handler, pin, pin_mode) do
GenStage.call(handler, {:read_pin, pin, pin_mode})
end
def write_pin(handler, pin, pin_mode, value) do
GenStage.call(handler, {:write_pin, pin, pin_mode, value})
end
def request_software_version(handler) do
GenStage.call(handler, :request_software_version)
end
def set_servo_angle(handler, pin, number) do
GenStage.call(handler, {:set_servo_angle, pin, number})
end
## GenStage Behaviour
defmodule State do
@moduledoc false
defstruct pos: nil,
fw_params: %{},
locked?: false
end
defp do_idle(pid) do
Process.send_after(pid, :idle_timer, 100)
end
defp do_wait_for_config_ok(pid) do
Process.send_after(pid, :config_ok_timer, 100)
end
def init([]) do
state = %State{pos: struct(Vec3)}
do_wait_for_config_ok(self())
{:producer, state, dispatcher: GenStage.BroadcastDispatcher}
end
def handle_demand(_amnt, state) do
{:noreply, [], state}
end
def handle_info(:idle_timer, state) do
do_idle(self())
dispatch = if state.locked? do
[:report_emergency_lock]
else
[
{:report_current_position, state.pos.x, state.pos.y, state.pos.z},
{:report_encoder_position_scaled, state.pos.x, state.pos.y, state.pos.z},
{:report_encoder_position_raw, state.pos.x, state.pos.y, state.pos.z},
:idle,
]
end
{:noreply, dispatch, state}
end
def handle_info(:config_ok_timer, state) do
send(self(), :idle_timer)
{:noreply, [:idle, :report_no_config, :idle], state}
end
def handle_call(cmd, _from, %{locked?: true} = state) when cmd not in [:emergency_unlock, :update_param] do
{:reply, :ok, [:error], state}
end
def handle_call({:move_absolute, pos, _x_speed, _y_speed, _z_speed}, _from, state) do
response = build_resp [{:report_current_position, pos.x, pos.y, pos.z},
{:report_encoder_position_scaled, pos.x, pos.y, pos.z},
{:report_encoder_position_raw, pos.x, pos.y, pos.z}, :done]
{:reply, build_reply(:ok), response, %{state | pos: pos}}
end
def handle_call({:calibrate, _axis}, _from, state) do
{:reply, :ok, [:done], state}
end
def handle_call({:find_home, axis}, _from, state) do
state = %{state | pos: %{state.pos | axis => 0}}
response = build_resp [
:"report_axis_home_complete_#{axis}",
{:report_current_position, state.pos.x, state.pos.y, state.pos.z},
{:report_encoder_position_scaled, state.pos.x, state.pos.y, state.pos.z},
{:report_encoder_position_raw, state.pos.x, state.pos.y, state.pos.z},
:done
]
{:reply, build_reply(:ok), response, state}
end
def handle_call({:home, axis}, _from, state) do
state = %{state | pos: %{state.pos | axis => 0}}
response = build_resp [
:"report_axis_home_complete_#{axis}",
{:report_current_position, state.pos.x, state.pos.y, state.pos.z},
{:report_encoder_position_scaled, state.pos.x, state.pos.y, state.pos.z},
{:report_encoder_position_raw, state.pos.x, state.pos.y, state.pos.z},
:done
]
{:reply, build_reply(:ok), response, state}
end
def handle_call(:home_all, _from, state) do
state = %{state | pos: %{state.pos | x: 0, y: 0, z: 0}}
response = build_resp [
:report_axis_home_complete_x,
:report_axis_home_complete_y,
:report_axis_home_complete_z,
{:report_current_position, state.pos.x, state.pos.y, state.pos.z},
{:report_encoder_position_scaled, state.pos.x, state.pos.y, state.pos.z},
{:report_encoder_position_raw, state.pos.x, state.pos.y, state.pos.z},
:done
]
{:reply, build_reply(:ok), response, state}
end
def handle_call({:read_pin, pin, mode}, _from, state) do
response = build_resp [
{:report_pin_mode, pin, mode},
{:report_pin_value, pin, 1}, :done
]
{:reply, build_reply(:ok), response, state}
end
def handle_call({:write_pin, pin, mode, value}, _from, state) do
response = build_resp [
{:report_pin_mode, pin, mode},
{:report_pin_value, pin, value}, :done]
{:reply, build_reply(:ok), response, state}
end
def handle_call({:set_pin_mode, pin, mode}, _from, state) do
response = [{:report_pin_mode, pin, mode}, :done]
{:reply, build_reply(:ok), response, state}
end
def handle_call({:zero, axis}, _from, state) do
state = %{state | pos: %{state.pos | axis => 0}}
response = build_resp [
{:report_current_position, state.pos.x, state.pos.y, state.pos.z},
{:report_encoder_position_scaled, state.pos.x, state.pos.y, state.pos.z},
{:report_encoder_position_raw, state.pos.x, state.pos.y, state.pos.z},
:done
]
{:reply, build_reply(:ok), response, state}
end
def handle_call({:update_param, param, val}, _from, state) do
response = build_resp [{:report_parameter_value, param, val}, :done]
{:reply, build_reply(:ok), response, %{state | fw_params: Map.put(state.fw_params, param, val)}}
end
def handle_call({:read_param, param}, _from, state) do
res = state.fw_params[param]
response = build_resp [{:report_parameter_value, param, res}, :done]
{:reply, build_reply(:ok), response, state}
end
def handle_call(:read_all_params, _from, state) do
response = build_resp [:report_params_complete, :done]
{:reply, build_reply(:ok), response, state}
end
def handle_call(:emergency_lock, _from, state) do
response = build_resp [:report_emergency_lock, :done]
{:reply, build_reply(:ok), response, %{state | locked?: true}}
end
def handle_call(:emergency_unlock, _from, state) do
response = build_resp [:done, :idle]
{:reply, build_reply(:ok), response, %{state | locked?: false}}
end
def handle_call(:request_software_version, _, state) do
response = build_resp [{:report_software_version, "STUBFW"}, :done]
{:reply, build_reply(:ok), response, state}
end
def handle_call({:set_servo_angle, _pin, _angle}, _, state) do
response = build_resp [:done]
{:reply, build_reply(:ok), response, state}
end
# Define functions based on production or not.
# This will make every command error with the message "Firmware Disconnected"
# on production, but work fine on development.
case Mix.env() do
:prod ->
defp build_resp(_), do: [:done]
defp build_reply(_), do: {:error, "Firmware Disconnected."}
_env ->
defp build_resp(list), do: list
defp build_reply(reply), do: reply
end
end