[BROKEN] begin allowing http adapter to be replaced.
parent
9ecbb78f11
commit
f78ce3773c
|
@ -54,3 +54,4 @@ tmp
|
|||
*.sqlite3-wal
|
||||
*.img
|
||||
run-qemu.sh
|
||||
auth_secret.exs
|
||||
|
|
|
@ -11,7 +11,6 @@ config :iex, :colors, enabled: true
|
|||
|
||||
config :ssl, protocol_version: :"tlsv1.2"
|
||||
|
||||
|
||||
# This is usually in the `priv` dir of :tzdata, but our fs is read only.
|
||||
config :tzdata, :data_dir, "/tmp"
|
||||
config :tzdata, :autoupdate, :disabled
|
||||
|
|
|
@ -41,7 +41,7 @@ defmodule Farmbot do
|
|||
def start(type, start_opts)
|
||||
|
||||
def start(_, start_opts) do
|
||||
Logger.info(">> Booting Farmbot OS version: #{@version} - #{@commit}")
|
||||
Logger.debug("Booting Farmbot OS version: #{@version} - #{@commit}")
|
||||
name = Keyword.get(start_opts, :name, __MODULE__)
|
||||
|
||||
case Supervisor.start_link(__MODULE__, [], name: name) do
|
||||
|
|
|
@ -53,7 +53,7 @@ defmodule Farmbot.BotState.Transport.GenMQTT do
|
|||
|
||||
def on_connect(state) do
|
||||
GenMQTT.subscribe(self(), [{bot_topic(state.device), 0}])
|
||||
Logger.info(">> Connected!")
|
||||
Logger.info("Connected!")
|
||||
|
||||
if state.cache do
|
||||
GenMQTT.publish(self(), status_topic(state.device), state.cache, 0, false)
|
||||
|
|
|
@ -4,33 +4,23 @@ defmodule Farmbot.Firmware do
|
|||
use GenStage
|
||||
require Logger
|
||||
|
||||
defmodule 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`.
|
||||
"""
|
||||
|
||||
@doc "Start a firmware handler."
|
||||
@callback start_link :: GenServer.on_start()
|
||||
|
||||
@doc "Write a gcode."
|
||||
@callback write(Farmbot.Firmware.Gcode.t()) :: :ok | {:error, term}
|
||||
end
|
||||
|
||||
@handler Application.get_env(:farmbot, :behaviour)[:firmware_handler] || raise("No fw handler.")
|
||||
|
||||
defdelegate move_absolute(vec3), to: @handler
|
||||
defdelegate calibrate(axis), to: @handler
|
||||
defdelegate update_param(param, val), to: @handler
|
||||
defdelegate read_param(param), to: @handler
|
||||
defdelegate emergency_lock(), to: @handler
|
||||
defdelegate emergency_unlock(), to: @handler
|
||||
defdelegate find_home(axis), to: @handler
|
||||
defdelegate read_pin(pin, mode), to: @handler
|
||||
defdelegate write_pin(pin, mode, value), to: @handler
|
||||
|
||||
@doc "Start the firmware services."
|
||||
def start_link do
|
||||
GenStage.start_link(__MODULE__, [], name: __MODULE__)
|
||||
end
|
||||
|
||||
@doc "Writes a Gcode to a the running hand:ler"
|
||||
def write(code), do: @handler.write(code)
|
||||
|
||||
## GenStage
|
||||
|
||||
defmodule State do
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
defmodule Farmbot.Firmware.Gcode do
|
||||
@moduledoc """
|
||||
Gcode is the itermediate representation
|
||||
of commands to the underlying hardware.
|
||||
"""
|
||||
@typedoc "Code representation."
|
||||
@type t :: term
|
||||
end
|
|
@ -0,0 +1,270 @@
|
|||
defmodule Farmbot.Firmware.Gcode.Param do
|
||||
@moduledoc "Firmware paramaters."
|
||||
|
||||
@typedoc "Human readable name of a paramater."
|
||||
@type t :: atom
|
||||
|
||||
@doc ~S"""
|
||||
Parses farmbot_arduino_firmware params.
|
||||
If we want the name of param "0"\n
|
||||
Example:
|
||||
iex> Gcode.parse_param("0")
|
||||
:param_version
|
||||
|
||||
Example:
|
||||
iex> Gcode.parse_param(0)
|
||||
:param_version
|
||||
|
||||
If we want the integer of param :param_version\n
|
||||
Example:
|
||||
iex> Gcode.parse_param(:param_version)
|
||||
0
|
||||
|
||||
Example:
|
||||
iex> Gcode.parse_param("param_version")
|
||||
0
|
||||
"""
|
||||
@spec parse_param(binary | integer) :: t | nil
|
||||
def parse_param("0"), do: :param_version
|
||||
|
||||
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_speed_x
|
||||
def parse_param("66"), do: :movement_home_speed_y
|
||||
def parse_param("67"), do: :movement_home_speed_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("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_pin_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_pin_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_pin_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_pin_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) when is_integer(param), do: parse_param("#{param}")
|
||||
|
||||
@spec parse_param(t) :: integer | nil
|
||||
def parse_param(:param_version), do: 0
|
||||
|
||||
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_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_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_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_speed_x), do: 65
|
||||
def parse_param(:movement_home_speed_y), do: 66
|
||||
def parse_param(:movement_home_speed_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(: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_pin_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_pin_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_pin_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_pin_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
|
||||
|
||||
def parse_param(param_string) when is_bitstring(param_string),
|
||||
do: param_string |> String.to_atom() |> parse_param
|
||||
|
||||
def parse_param(_), do: nil
|
||||
end
|
|
@ -4,6 +4,7 @@ defmodule Farmbot.Firmware.Gcode.Parser do
|
|||
"""
|
||||
|
||||
require Logger
|
||||
import Farmbot.Firmware.Gcode.Param
|
||||
|
||||
@spec parse_code(binary) :: {binary, tuple}
|
||||
|
||||
|
@ -164,280 +165,4 @@ defmodule Farmbot.Firmware.Gcode.Parser do
|
|||
[_, rq] = String.split(q, "Q")
|
||||
{rq, {:report_parameter_value, parse_param(rp), String.to_integer(rv)}}
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Parses farmbot_arduino_firmware params.
|
||||
If we want the name of param "0"\n
|
||||
Example:
|
||||
iex> Gcode.parse_param("0")
|
||||
:param_version
|
||||
|
||||
Example:
|
||||
iex> Gcode.parse_param(0)
|
||||
:param_version
|
||||
|
||||
If we want the integer of param :param_version\n
|
||||
Example:
|
||||
iex> Gcode.parse_param(:param_version)
|
||||
0
|
||||
|
||||
Example:
|
||||
iex> Gcode.parse_param("param_version")
|
||||
0
|
||||
"""
|
||||
@spec parse_param(binary | integer) :: atom | nil
|
||||
def parse_param("0"), do: :param_version
|
||||
|
||||
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_speed_x
|
||||
def parse_param("66"), do: :movement_home_speed_y
|
||||
def parse_param("67"), do: :movement_home_speed_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("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_pin_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_pin_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_pin_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_pin_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) when is_integer(param), do: parse_param("#{param}")
|
||||
|
||||
@spec parse_param(atom) :: integer | nil
|
||||
def parse_param(:param_version), do: 0
|
||||
|
||||
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_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_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_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_speed_x), do: 65
|
||||
def parse_param(:movement_home_speed_y), do: 66
|
||||
def parse_param(:movement_home_speed_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(: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_pin_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_pin_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_pin_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_pin_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
|
||||
|
||||
def parse_param(param_string) when is_bitstring(param_string),
|
||||
do: param_string |> String.to_atom() |> parse_param
|
||||
|
||||
# derp.
|
||||
if Mix.env() == :dev do
|
||||
def parse_param(uhh) do
|
||||
Logger.error(
|
||||
"Unrecognized param needs implementation " <> "#{inspect(uhh)}",
|
||||
rollbar: false
|
||||
)
|
||||
|
||||
nil
|
||||
end
|
||||
else
|
||||
def parse_param(_), do: nil
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
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`.
|
||||
"""
|
||||
|
||||
@doc "Start a firmware handler."
|
||||
@callback start_link :: GenServer.on_start()
|
||||
|
||||
@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 "Pin"
|
||||
@type pin :: number
|
||||
|
||||
@typedoc "Mode of a pin."
|
||||
@type pin_mode :: :digital | :analog
|
||||
|
||||
@doc "Move to a position."
|
||||
@callback move_absolute(vec3) :: fw_ret_val
|
||||
|
||||
@doc "Calibrate an axis."
|
||||
@callback calibrate(axis) :: fw_ret_val
|
||||
|
||||
@doc "Update a paramater."
|
||||
@callback update_param(fw_param, number) :: fw_ret_val
|
||||
|
||||
@callback read_param(fw_param) :: {:ok, number} | {:error, term}
|
||||
|
||||
@doc "Lock the firmware."
|
||||
@callback emergency_lock() :: fw_ret_val
|
||||
|
||||
@doc "Unlock the firmware."
|
||||
@callback emergency_unlock() :: fw_ret_val
|
||||
|
||||
@doc "Find home on an axis."
|
||||
@callback find_home(axis) :: fw_ret_val
|
||||
|
||||
@doc "Read a pin."
|
||||
@callback read_pin(pin, pin_mode) :: {:ok, number} | {:error, term}
|
||||
|
||||
@doc "Write a pin."
|
||||
@callback write_pin(pin, pin_mode, number) :: fw_ret_val
|
||||
end
|
|
@ -0,0 +1,11 @@
|
|||
defmodule Farmbot.Firmware.Vec3 do
|
||||
@moduledoc "A three position vector."
|
||||
|
||||
defstruct [:x, :y, :z]
|
||||
|
||||
@typedoc "Axis label."
|
||||
@type axis :: :x | :y | :z
|
||||
|
||||
@typedoc @moduledoc
|
||||
@type t :: %__MODULE__{x: number, y: number, z: number}
|
||||
end
|
|
@ -0,0 +1,2 @@
|
|||
defmodule Farmbot.HTTP.Adapter do
|
||||
end
|
|
@ -1,491 +1,26 @@
|
|||
defmodule Farmbot.HTTP do
|
||||
@moduledoc """
|
||||
Farmbot HTTP adapter for accessing the world and farmbot web api easily.
|
||||
"""
|
||||
@moduledoc "Wraps an HTTP Adapter."
|
||||
use GenServer
|
||||
alias HTTPoison
|
||||
alias HTTPoison.{AsyncResponse, AsyncStatus, AsyncHeaders, AsyncChunk, AsyncEnd}
|
||||
alias Farmbot.HTTP.{Response, Helpers, Error}
|
||||
import Helpers
|
||||
require Logger
|
||||
|
||||
@version Mix.Project.config()[:version]
|
||||
@target Mix.Project.config()[:target]
|
||||
@redirect_status_codes [301, 302, 303, 307, 308]
|
||||
@adapter Application.get_env(:farmbot, :behaviour)[:http_adapter] || raise("No http adapter.")
|
||||
|
||||
@doc """
|
||||
Make an http request. Will not raise.
|
||||
* `method` - can be any http verb
|
||||
* `url` - fully formatted url or an api slug.
|
||||
* `body` - body can be any of:
|
||||
* binary
|
||||
* `{:multipart, [{binary_key, binary_value}]}`
|
||||
* headers - `[{binary_key, binary_value}]`
|
||||
* opts - Keyword opts to be passed to adapter (hackney/httpoison)
|
||||
* `file` - option to be passed if the output should be saved to a file.
|
||||
"""
|
||||
def request(http, method, url, body \\ "", headers \\ [], opts \\ [])
|
||||
@doc "Make an HTTP Request."
|
||||
def request(method, url, body \\ "", headers \\ [], opts \\ [])
|
||||
|
||||
def request(http, method, url, body, headers, opts) do
|
||||
GenServer.call(http, {:req, method, url, body, headers, opts}, :infinity)
|
||||
@doc "HTTP GET request."
|
||||
def get(url, headers \\ [], opts \\ [])
|
||||
|
||||
@doc "HTTP POST request."
|
||||
def post(url, headers \\ [], opts \\ [])
|
||||
|
||||
@doc "Start HTTP Services."
|
||||
def start_link do
|
||||
GenServer.start_link(__MODULE__, [], name: __MODULE__)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `request/6` - but raises a `Farmbot.HTTP.Error` exception.
|
||||
"""
|
||||
def request!(http, method, url, body \\ "", headers \\ [], opts \\ [])
|
||||
|
||||
def request!(http, method, url, body, headers, opts) do
|
||||
case request(http, method, url, body, headers, opts) do
|
||||
{:ok, %Response{status_code: code} = resp} when is_2xx(code) -> resp
|
||||
{:ok, %Response{} = resp} -> raise Error, resp
|
||||
{:error, error} when is_binary(error) or is_atom(error) -> raise Error, "#{error}"
|
||||
{:error, error} -> raise Error, inspect(error)
|
||||
end
|
||||
end
|
||||
|
||||
methods = [:get, :post, :delete, :patch, :put]
|
||||
|
||||
for verb <- methods do
|
||||
@doc """
|
||||
HTTP #{verb} request.
|
||||
"""
|
||||
def unquote(verb)(http, url, body \\ "", headers \\ [], opts \\ [])
|
||||
|
||||
def unquote(verb)(http, url, body, headers, opts) do
|
||||
request(http, unquote(verb), url, body, headers, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as #{verb}/5 but raises Farmbot.HTTP.Error exception.
|
||||
"""
|
||||
fun_name = "#{verb}!" |> String.to_atom()
|
||||
def unquote(fun_name)(http, url, body \\ "", headers \\ [], opts \\ [])
|
||||
|
||||
def unquote(fun_name)(http, url, body, headers, opts) do
|
||||
request!(http, unquote(verb), url, body, headers, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def download_file(http, url, path, progress_callback \\ nil, payload \\ "", headers \\ [])
|
||||
|
||||
def download_file(http, url, path, progress_callback, payload, headers) do
|
||||
case get(http, url, payload, headers, file: path, progress_callback: progress_callback) do
|
||||
{:ok, %Response{status_code: code}} when is_2xx(code) -> {:ok, path}
|
||||
{:ok, %Response{} = resp} -> {:error, resp}
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
def download_file!(http, url, path, progress_callback \\ nil, payload \\ "", headers \\ [])
|
||||
|
||||
def download_file!(http, url, path, progress_callback, payload, headers) do
|
||||
case download_file(http, url, path, progress_callback, payload, headers) do
|
||||
{:ok, path} -> path
|
||||
{:error, reason} -> raise Error, reason
|
||||
end
|
||||
end
|
||||
|
||||
def upload_file(http, path, meta \\ nil) do
|
||||
if File.exists?(path) do
|
||||
http
|
||||
|> get("/api/storage_auth")
|
||||
|> do_multipart_request(http, meta || %{x: -1, y: -1, z: -1}, path)
|
||||
else
|
||||
{:error, "#{path} not found"}
|
||||
end
|
||||
end
|
||||
|
||||
def upload_file!(http, path, meta \\ %{}) do
|
||||
case upload_file(http, path, meta) do
|
||||
:ok -> :ok
|
||||
{:error, reason} -> raise Error, reason
|
||||
end
|
||||
end
|
||||
|
||||
defp do_multipart_request({:ok, %Response{status_code: code, body: bin_body}}, http, meta, path)
|
||||
when is_2xx(code) do
|
||||
with {:ok, body} <- Poison.decode(bin_body),
|
||||
{:ok, file} <- File.read(path) do
|
||||
url = "https:" <> body["url"]
|
||||
form_data = body["form_data"]
|
||||
attachment_url = url <> form_data["key"]
|
||||
|
||||
mp =
|
||||
Enum.map(form_data, fn {key, val} ->
|
||||
if key == "file", do: {"file", file}, else: {key, val}
|
||||
end)
|
||||
|
||||
http
|
||||
|> post(url, {:multipart, mp})
|
||||
|> finish_upload(http, attachment_url, meta)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_multipart_request({:ok, %Response{} = response}, _http, _meta, _path),
|
||||
do: {:error, response}
|
||||
|
||||
defp do_multipart_request({:error, reason}, _http, _meta, _path), do: {:error, reason}
|
||||
|
||||
defp finish_upload({:ok, %Response{status_code: code}}, http, atch_url, meta) when is_2xx(code) do
|
||||
with {:ok, body} <- Poison.encode(%{"attachment_url" => atch_url, "meta" => meta}) do
|
||||
case post(http, "/api/images", body) do
|
||||
{:ok, %Response{status_code: code}} when is_2xx(code) ->
|
||||
# debug_log("#{atch_url} should exist shortly.")
|
||||
:ok
|
||||
|
||||
{:ok, %Response{} = response} ->
|
||||
{:error, response}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp finish_upload({:ok, %Response{} = resp}, _http, _url, _meta), do: {:error, resp}
|
||||
defp finish_upload({:error, reason}, _http, _url, _meta), do: {:error, reason}
|
||||
|
||||
# GenServer
|
||||
|
||||
defmodule State do
|
||||
defstruct [:token, :requests]
|
||||
|
||||
defimpl Inspect, for: __MODULE__ do
|
||||
def inspect(_state, _), do: "#HTTPState<>"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Buffer do
|
||||
defstruct [
|
||||
:data,
|
||||
:headers,
|
||||
:status_code,
|
||||
:request,
|
||||
:from,
|
||||
:id,
|
||||
:file,
|
||||
:timeout,
|
||||
:progress_callback,
|
||||
:file_size
|
||||
]
|
||||
end
|
||||
|
||||
def start_link(token, opts) do
|
||||
GenServer.start_link(__MODULE__, token, opts)
|
||||
end
|
||||
|
||||
def init(token) do
|
||||
state = %State{token: token, requests: %{}}
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
def handle_call({:req, method, url, body, headers, opts}, from, state) do
|
||||
{file, opts} = maybe_open_file(opts)
|
||||
opts = fb_opts(opts)
|
||||
headers = fb_headers(headers)
|
||||
# debug_log "#{inspect Tuple.delete_at(from, 0)} Request start (#{url})"
|
||||
# Pattern match the url.
|
||||
case url do
|
||||
"/api" <> _ -> do_api_request({method, url, body, headers, opts, from}, state)
|
||||
_ -> do_normal_request({method, url, body, headers, opts, from}, file, state)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info({:timeout, ref}, state) do
|
||||
case state.requests[ref] do
|
||||
%Buffer{} = buffer ->
|
||||
GenServer.reply(buffer.from, {:error, :timeout})
|
||||
{:noreply, %{state | requests: Map.delete(state.requests, ref)}}
|
||||
|
||||
nil ->
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(%AsyncStatus{code: code, id: ref}, state) do
|
||||
case state.requests[ref] do
|
||||
%Buffer{} = buffer ->
|
||||
# debug_log "#{inspect Tuple.delete_at(buffer.from, 0)} Got Status."
|
||||
HTTPoison.stream_next(%AsyncResponse{id: ref})
|
||||
{:noreply, %{state | requests: %{state.requests | ref => %{buffer | status_code: code}}}}
|
||||
|
||||
nil ->
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(%AsyncHeaders{headers: headers, id: ref}, state) do
|
||||
case state.requests[ref] do
|
||||
%Buffer{} = buffer ->
|
||||
# debug_log("#{inspect Tuple.delete_at(buffer.from, 0)} Got headers")
|
||||
file_size =
|
||||
Enum.find_value(headers, fn {header, val} ->
|
||||
case header do
|
||||
"Content-Length" -> val
|
||||
"content_length" -> val
|
||||
_header -> nil
|
||||
end
|
||||
end)
|
||||
|
||||
HTTPoison.stream_next(%AsyncResponse{id: ref})
|
||||
|
||||
{
|
||||
:noreply,
|
||||
%{
|
||||
state
|
||||
| requests: %{
|
||||
state.requests
|
||||
| ref => %{buffer | headers: headers, file_size: file_size}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nil ->
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(%AsyncChunk{chunk: chunk, id: ref}, state) do
|
||||
case state.requests[ref] do
|
||||
%Buffer{} = buffer ->
|
||||
if buffer.timeout, do: Process.cancel_timer(buffer.timeout)
|
||||
timeout = Process.send_after(self(), {:timeout, ref}, 30000)
|
||||
maybe_log_progress(buffer)
|
||||
maybe_stream_to_file(buffer.file, buffer.status_code, chunk)
|
||||
HTTPoison.stream_next(%AsyncResponse{id: ref})
|
||||
|
||||
{
|
||||
:noreply,
|
||||
%{
|
||||
state
|
||||
| requests: %{
|
||||
state.requests
|
||||
| ref => %{buffer | data: buffer.data <> chunk, timeout: timeout}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nil ->
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(%AsyncEnd{id: ref}, state) do
|
||||
case state.requests[ref] do
|
||||
%Buffer{} = buffer ->
|
||||
# debug_log "#{inspect Tuple.delete_at(buffer.from, 0)} Request finish."
|
||||
finish_request(buffer, state)
|
||||
|
||||
nil ->
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
def terminate({:error, reason}, state), do: terminate(reason, state)
|
||||
|
||||
def terminate(reason, state) do
|
||||
for {_ref, buffer} <- state.requests do
|
||||
maybe_close_file(buffer.file)
|
||||
GenServer.reply(buffer.from, {:error, reason})
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_open_file(opts) do
|
||||
{file, opts} = Keyword.pop(opts, :file)
|
||||
|
||||
case file do
|
||||
filename when is_binary(filename) ->
|
||||
# debug_log "Opening file: #{filename}"
|
||||
File.rm(file)
|
||||
:ok = File.touch(filename)
|
||||
{:ok, fd} = :file.open(filename, [:write, :raw])
|
||||
{fd, Keyword.merge(opts, stream_to: self(), async: :once)}
|
||||
|
||||
_ ->
|
||||
{nil, opts}
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_stream_to_file(nil, _, _data), do: :ok
|
||||
defp maybe_stream_to_file(_, code, _data) when code in @redirect_status_codes, do: :ok
|
||||
|
||||
defp maybe_stream_to_file(fd, _code, data) when is_binary(data) do
|
||||
# debug_log "[#{inspect self()}] writing data to file."
|
||||
:ok = :file.write(fd, data)
|
||||
end
|
||||
|
||||
defp maybe_close_file(nil), do: :ok
|
||||
defp maybe_close_file(fd), do: :file.close(fd)
|
||||
|
||||
defp maybe_log_progress(%Buffer{file: file, progress_callback: pcb})
|
||||
when is_nil(file) or is_nil(pcb) do
|
||||
# debug_log "File (#{inspect file}) or progress callback: #{inspect pcb} are nil"
|
||||
:ok
|
||||
end
|
||||
|
||||
defp maybe_log_progress(%Buffer{file: _file, file_size: fs} = buffer) do
|
||||
downloaded_bytes = byte_size(buffer.data)
|
||||
|
||||
case fs do
|
||||
numstr when is_binary(numstr) ->
|
||||
total_bytes = numstr |> String.to_integer()
|
||||
buffer.progress_callback.(downloaded_bytes, total_bytes)
|
||||
|
||||
other when other in [:complete] or is_nil(other) ->
|
||||
buffer.progress_callback.(downloaded_bytes, other)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_api_request({method, url, body, headers, opts, from}, %{token: tkn} = state) do
|
||||
headers =
|
||||
headers
|
||||
|> add_header({"Authorization", "Bearer " <> tkn})
|
||||
|> add_header({"Content-Type", "application/json"})
|
||||
|
||||
opts = opts |> Keyword.put(:timeout, :infinity)
|
||||
# TODO Fix this.
|
||||
url = "https:" <> Farmbot.Jwt.decode!(tkn).iss <> url
|
||||
do_normal_request({method, url, body, headers, opts, from}, nil, state)
|
||||
end
|
||||
|
||||
defp do_normal_request({method, url, body, headers, opts, from}, file, state) do
|
||||
case HTTPoison.request(method, url, body, headers, opts) do
|
||||
{:ok, %HTTPoison.Response{status_code: code, headers: resp_headers}}
|
||||
when code in @redirect_status_codes ->
|
||||
redir =
|
||||
Enum.find_value(resp_headers, fn {header, val} ->
|
||||
if header == "Location", do: val, else: false
|
||||
end)
|
||||
|
||||
if redir do
|
||||
# debug_log "redirect"
|
||||
do_normal_request({method, redir, body, headers, opts, from}, file, state)
|
||||
else
|
||||
# debug_log("Failed to redirect: #{inspect resp}")
|
||||
GenServer.reply(from, {:error, :no_server_for_redirect})
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
{:ok, %HTTPoison.Response{body: body, headers: headers, status_code: code}} ->
|
||||
GenServer.reply(from, {:ok, %Response{body: body, headers: headers, status_code: code}})
|
||||
{:noreply, state}
|
||||
|
||||
{:ok, %AsyncResponse{id: ref}} ->
|
||||
timeout = Process.send_after(self(), {:timeout, ref}, 30000)
|
||||
|
||||
req = %Buffer{
|
||||
id: ref,
|
||||
from: from,
|
||||
timeout: timeout,
|
||||
file: file,
|
||||
data: "",
|
||||
headers: nil,
|
||||
status_code: nil,
|
||||
progress_callback: Keyword.fetch!(opts, :progress_callback),
|
||||
request: {method, url, body, headers, opts}
|
||||
}
|
||||
|
||||
{:noreply, %{state | requests: Map.put(state.requests, ref, req)}}
|
||||
|
||||
{:error, %HTTPoison.Error{reason: reason}} ->
|
||||
GenServer.reply(from, {:error, reason})
|
||||
{:noreply, state}
|
||||
|
||||
{:error, reason} ->
|
||||
GenServer.reply(from, {:error, reason})
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_redirect_request(%Buffer{} = buffer, redir, state) do
|
||||
{method, _url, body, headers, opts} = buffer.request
|
||||
|
||||
case HTTPoison.request(method, redir, body, headers, opts) do
|
||||
{:ok, %AsyncResponse{id: ref}} ->
|
||||
req = %Buffer{
|
||||
buffer
|
||||
| id: ref,
|
||||
from: buffer.from,
|
||||
file: buffer.file,
|
||||
data: "",
|
||||
headers: nil,
|
||||
status_code: nil,
|
||||
request: {method, redir, body, headers, opts}
|
||||
}
|
||||
|
||||
state = %{state | requests: Map.delete(state.requests, buffer.id)}
|
||||
state = %{state | requests: Map.put(state.requests, ref, req)}
|
||||
{:noreply, state}
|
||||
|
||||
{:error, %HTTPoison.Error{reason: reason}} ->
|
||||
GenServer.reply(buffer.from, {:error, reason})
|
||||
{:noreply, state}
|
||||
|
||||
{:error, reason} ->
|
||||
GenServer.reply(buffer.from, {:error, reason})
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
defp finish_request(%Buffer{status_code: status_code} = buffer, state)
|
||||
when status_code in @redirect_status_codes do
|
||||
# debug_log "#{inspect Tuple.delete_at(buffer.from, 0)} Trying to redirect: (#{status_code})"
|
||||
redir =
|
||||
Enum.find_value(buffer.headers, fn {header, val} ->
|
||||
case header do
|
||||
"Location" -> val
|
||||
"location" -> val
|
||||
_ -> false
|
||||
end
|
||||
end)
|
||||
|
||||
if redir do
|
||||
do_redirect_request(buffer, redir, state)
|
||||
else
|
||||
# debug_log("Failed to redirect: #{inspect buffer}")
|
||||
GenServer.reply(buffer.from, {:error, :no_server_for_redirect})
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
defp finish_request(%Buffer{} = buffer, state) do
|
||||
# debug_log "Request finish."
|
||||
response = %Response{
|
||||
status_code: buffer.status_code,
|
||||
body: buffer.data,
|
||||
headers: buffer.headers
|
||||
}
|
||||
|
||||
if buffer.timeout, do: Process.cancel_timer(buffer.timeout)
|
||||
maybe_close_file(buffer.file)
|
||||
|
||||
case buffer.file_size do
|
||||
nil -> maybe_log_progress(%{buffer | file_size: :complete})
|
||||
_num -> maybe_log_progress(%{buffer | file_size: "#{byte_size(buffer.data)}"})
|
||||
end
|
||||
|
||||
GenServer.reply(buffer.from, {:ok, response})
|
||||
{:noreply, %{state | requests: Map.delete(state.requests, buffer.id)}}
|
||||
end
|
||||
|
||||
defp fb_headers(headers) do
|
||||
headers |> add_header({"User-Agent", "FarmbotOS/#{@version} (#{@target}) #{@target} ()"})
|
||||
end
|
||||
|
||||
defp add_header(headers, new), do: [new | headers]
|
||||
|
||||
defp fb_opts(opts) do
|
||||
Keyword.merge(
|
||||
opts,
|
||||
ssl: [{:versions, [:"tlsv1.2"]}],
|
||||
hackney: [:insecure, pool: :farmbot_http_pool],
|
||||
recv_timeout: :infinity,
|
||||
timeout: :infinity
|
||||
)
|
||||
|
||||
# stream_to: self(),
|
||||
# follow_redirect: false,
|
||||
# async: :once
|
||||
def init([]) do
|
||||
{:ok, adapter} = @adapter.start_link()
|
||||
Process.link(adapter)
|
||||
{:ok, %{adapter: adapter}}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,493 @@
|
|||
defmodule Farmbot.HTTP.HTTPoisonAdapter do
|
||||
@moduledoc """
|
||||
Farmbot HTTP adapter for accessing the world and farmbot web api easily.
|
||||
"""
|
||||
|
||||
use GenServer
|
||||
alias HTTPoison
|
||||
alias HTTPoison.{AsyncResponse, AsyncStatus, AsyncHeaders, AsyncChunk, AsyncEnd}
|
||||
alias Farmbot.HTTP.{Response, Helpers, Error}
|
||||
import Helpers
|
||||
require Logger
|
||||
|
||||
@behaviour Farmbot.HTTP.Adapter
|
||||
|
||||
@version Mix.Project.config()[:version]
|
||||
@target Mix.Project.config()[:target]
|
||||
@redirect_status_codes [301, 302, 303, 307, 308]
|
||||
|
||||
@doc """
|
||||
Make an http request. Will not raise.
|
||||
* `method` - can be any http verb
|
||||
* `url` - fully formatted url or an api slug.
|
||||
* `body` - body can be any of:
|
||||
* binary
|
||||
* `{:multipart, [{binary_key, binary_value}]}`
|
||||
* headers - `[{binary_key, binary_value}]`
|
||||
* opts - Keyword opts to be passed to adapter (hackney/httpoison)
|
||||
* `file` - option to be passed if the output should be saved to a file.
|
||||
"""
|
||||
def request(http, method, url, body \\ "", headers \\ [], opts \\ [])
|
||||
|
||||
def request(http, method, url, body, headers, opts) do
|
||||
GenServer.call(http, {:req, method, url, body, headers, opts}, :infinity)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `request/6` - but raises a `Farmbot.HTTP.Error` exception.
|
||||
"""
|
||||
def request!(http, method, url, body \\ "", headers \\ [], opts \\ [])
|
||||
|
||||
def request!(http, method, url, body, headers, opts) do
|
||||
case request(http, method, url, body, headers, opts) do
|
||||
{:ok, %Response{status_code: code} = resp} when is_2xx(code) -> resp
|
||||
{:ok, %Response{} = resp} -> raise Error, resp
|
||||
{:error, error} when is_binary(error) or is_atom(error) -> raise Error, "#{error}"
|
||||
{:error, error} -> raise Error, inspect(error)
|
||||
end
|
||||
end
|
||||
|
||||
methods = [:get, :post, :delete, :patch, :put]
|
||||
|
||||
for verb <- methods do
|
||||
@doc """
|
||||
HTTP #{verb} request.
|
||||
"""
|
||||
def unquote(verb)(http, url, body \\ "", headers \\ [], opts \\ [])
|
||||
|
||||
def unquote(verb)(http, url, body, headers, opts) do
|
||||
request(http, unquote(verb), url, body, headers, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as #{verb}/5 but raises Farmbot.HTTP.Error exception.
|
||||
"""
|
||||
fun_name = "#{verb}!" |> String.to_atom()
|
||||
def unquote(fun_name)(http, url, body \\ "", headers \\ [], opts \\ [])
|
||||
|
||||
def unquote(fun_name)(http, url, body, headers, opts) do
|
||||
request!(http, unquote(verb), url, body, headers, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def download_file(http, url, path, progress_callback \\ nil, payload \\ "", headers \\ [])
|
||||
|
||||
def download_file(http, url, path, progress_callback, payload, headers) do
|
||||
case get(http, url, payload, headers, file: path, progress_callback: progress_callback) do
|
||||
{:ok, %Response{status_code: code}} when is_2xx(code) -> {:ok, path}
|
||||
{:ok, %Response{} = resp} -> {:error, resp}
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
def download_file!(http, url, path, progress_callback \\ nil, payload \\ "", headers \\ [])
|
||||
|
||||
def download_file!(http, url, path, progress_callback, payload, headers) do
|
||||
case download_file(http, url, path, progress_callback, payload, headers) do
|
||||
{:ok, path} -> path
|
||||
{:error, reason} -> raise Error, reason
|
||||
end
|
||||
end
|
||||
|
||||
def upload_file(http, path, meta \\ nil) do
|
||||
if File.exists?(path) do
|
||||
http
|
||||
|> get("/api/storage_auth")
|
||||
|> do_multipart_request(http, meta || %{x: -1, y: -1, z: -1}, path)
|
||||
else
|
||||
{:error, "#{path} not found"}
|
||||
end
|
||||
end
|
||||
|
||||
def upload_file!(http, path, meta \\ %{}) do
|
||||
case upload_file(http, path, meta) do
|
||||
:ok -> :ok
|
||||
{:error, reason} -> raise Error, reason
|
||||
end
|
||||
end
|
||||
|
||||
defp do_multipart_request({:ok, %Response{status_code: code, body: bin_body}}, http, meta, path)
|
||||
when is_2xx(code) do
|
||||
with {:ok, body} <- Poison.decode(bin_body),
|
||||
{:ok, file} <- File.read(path) do
|
||||
url = "https:" <> body["url"]
|
||||
form_data = body["form_data"]
|
||||
attachment_url = url <> form_data["key"]
|
||||
|
||||
mp =
|
||||
Enum.map(form_data, fn {key, val} ->
|
||||
if key == "file", do: {"file", file}, else: {key, val}
|
||||
end)
|
||||
|
||||
http
|
||||
|> post(url, {:multipart, mp})
|
||||
|> finish_upload(http, attachment_url, meta)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_multipart_request({:ok, %Response{} = response}, _http, _meta, _path),
|
||||
do: {:error, response}
|
||||
|
||||
defp do_multipart_request({:error, reason}, _http, _meta, _path), do: {:error, reason}
|
||||
|
||||
defp finish_upload({:ok, %Response{status_code: code}}, http, atch_url, meta) when is_2xx(code) do
|
||||
with {:ok, body} <- Poison.encode(%{"attachment_url" => atch_url, "meta" => meta}) do
|
||||
case post(http, "/api/images", body) do
|
||||
{:ok, %Response{status_code: code}} when is_2xx(code) ->
|
||||
# debug_log("#{atch_url} should exist shortly.")
|
||||
:ok
|
||||
|
||||
{:ok, %Response{} = response} ->
|
||||
{:error, response}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp finish_upload({:ok, %Response{} = resp}, _http, _url, _meta), do: {:error, resp}
|
||||
defp finish_upload({:error, reason}, _http, _url, _meta), do: {:error, reason}
|
||||
|
||||
# GenServer
|
||||
|
||||
defmodule State do
|
||||
defstruct [:requests]
|
||||
|
||||
end
|
||||
|
||||
defmodule Buffer do
|
||||
defstruct [
|
||||
:data,
|
||||
:headers,
|
||||
:status_code,
|
||||
:request,
|
||||
:from,
|
||||
:id,
|
||||
:file,
|
||||
:timeout,
|
||||
:progress_callback,
|
||||
:file_size
|
||||
]
|
||||
end
|
||||
|
||||
def start_link() do
|
||||
GenServer.start_link(__MODULE__, [], [])
|
||||
end
|
||||
|
||||
def init(token) do
|
||||
state = %State{requests: %{}}
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
def handle_call({:req, method, url, body, headers, opts}, from, state) do
|
||||
{file, opts} = maybe_open_file(opts)
|
||||
opts = fb_opts(opts)
|
||||
headers = fb_headers(headers)
|
||||
# debug_log "#{inspect Tuple.delete_at(from, 0)} Request start (#{url})"
|
||||
# Pattern match the url.
|
||||
case url do
|
||||
"/api" <> _ -> do_api_request({method, url, body, headers, opts, from}, state)
|
||||
_ -> do_normal_request({method, url, body, headers, opts, from}, file, state)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info({:timeout, ref}, state) do
|
||||
case state.requests[ref] do
|
||||
%Buffer{} = buffer ->
|
||||
GenServer.reply(buffer.from, {:error, :timeout})
|
||||
{:noreply, %{state | requests: Map.delete(state.requests, ref)}}
|
||||
|
||||
nil ->
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(%AsyncStatus{code: code, id: ref}, state) do
|
||||
case state.requests[ref] do
|
||||
%Buffer{} = buffer ->
|
||||
# debug_log "#{inspect Tuple.delete_at(buffer.from, 0)} Got Status."
|
||||
HTTPoison.stream_next(%AsyncResponse{id: ref})
|
||||
{:noreply, %{state | requests: %{state.requests | ref => %{buffer | status_code: code}}}}
|
||||
|
||||
nil ->
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(%AsyncHeaders{headers: headers, id: ref}, state) do
|
||||
case state.requests[ref] do
|
||||
%Buffer{} = buffer ->
|
||||
# debug_log("#{inspect Tuple.delete_at(buffer.from, 0)} Got headers")
|
||||
file_size =
|
||||
Enum.find_value(headers, fn {header, val} ->
|
||||
case header do
|
||||
"Content-Length" -> val
|
||||
"content_length" -> val
|
||||
_header -> nil
|
||||
end
|
||||
end)
|
||||
|
||||
HTTPoison.stream_next(%AsyncResponse{id: ref})
|
||||
|
||||
{
|
||||
:noreply,
|
||||
%{
|
||||
state
|
||||
| requests: %{
|
||||
state.requests
|
||||
| ref => %{buffer | headers: headers, file_size: file_size}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nil ->
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(%AsyncChunk{chunk: chunk, id: ref}, state) do
|
||||
case state.requests[ref] do
|
||||
%Buffer{} = buffer ->
|
||||
if buffer.timeout, do: Process.cancel_timer(buffer.timeout)
|
||||
timeout = Process.send_after(self(), {:timeout, ref}, 30000)
|
||||
maybe_log_progress(buffer)
|
||||
maybe_stream_to_file(buffer.file, buffer.status_code, chunk)
|
||||
HTTPoison.stream_next(%AsyncResponse{id: ref})
|
||||
|
||||
{
|
||||
:noreply,
|
||||
%{
|
||||
state
|
||||
| requests: %{
|
||||
state.requests
|
||||
| ref => %{buffer | data: buffer.data <> chunk, timeout: timeout}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nil ->
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(%AsyncEnd{id: ref}, state) do
|
||||
case state.requests[ref] do
|
||||
%Buffer{} = buffer ->
|
||||
# debug_log "#{inspect Tuple.delete_at(buffer.from, 0)} Request finish."
|
||||
finish_request(buffer, state)
|
||||
|
||||
nil ->
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
def terminate({:error, reason}, state), do: terminate(reason, state)
|
||||
|
||||
def terminate(reason, state) do
|
||||
for {_ref, buffer} <- state.requests do
|
||||
maybe_close_file(buffer.file)
|
||||
GenServer.reply(buffer.from, {:error, reason})
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_open_file(opts) do
|
||||
{file, opts} = Keyword.pop(opts, :file)
|
||||
|
||||
case file do
|
||||
filename when is_binary(filename) ->
|
||||
# debug_log "Opening file: #{filename}"
|
||||
File.rm(file)
|
||||
:ok = File.touch(filename)
|
||||
{:ok, fd} = :file.open(filename, [:write, :raw])
|
||||
{fd, Keyword.merge(opts, stream_to: self(), async: :once)}
|
||||
|
||||
_ ->
|
||||
{nil, opts}
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_stream_to_file(nil, _, _data), do: :ok
|
||||
defp maybe_stream_to_file(_, code, _data) when code in @redirect_status_codes, do: :ok
|
||||
|
||||
defp maybe_stream_to_file(fd, _code, data) when is_binary(data) do
|
||||
# debug_log "[#{inspect self()}] writing data to file."
|
||||
:ok = :file.write(fd, data)
|
||||
end
|
||||
|
||||
defp maybe_close_file(nil), do: :ok
|
||||
defp maybe_close_file(fd), do: :file.close(fd)
|
||||
|
||||
defp maybe_log_progress(%Buffer{file: file, progress_callback: pcb})
|
||||
when is_nil(file) or is_nil(pcb) do
|
||||
# debug_log "File (#{inspect file}) or progress callback: #{inspect pcb} are nil"
|
||||
:ok
|
||||
end
|
||||
|
||||
defp maybe_log_progress(%Buffer{file: _file, file_size: fs} = buffer) do
|
||||
downloaded_bytes = byte_size(buffer.data)
|
||||
|
||||
case fs do
|
||||
numstr when is_binary(numstr) ->
|
||||
total_bytes = numstr |> String.to_integer()
|
||||
buffer.progress_callback.(downloaded_bytes, total_bytes)
|
||||
|
||||
other when other in [:complete] or is_nil(other) ->
|
||||
buffer.progress_callback.(downloaded_bytes, other)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_api_request({method, url, body, headers, opts, from}, state) do
|
||||
#TODO Get token and url out of config storage.
|
||||
token = ""
|
||||
url = ""
|
||||
|
||||
headers =
|
||||
headers
|
||||
|> add_header({"Authorization", "Bearer " <> tkn})
|
||||
|> add_header({"Content-Type", "application/json"})
|
||||
|
||||
opts = opts |> Keyword.put(:timeout, :infinity)
|
||||
do_normal_request({method, url, body, headers, opts, from}, nil, state)
|
||||
end
|
||||
|
||||
defp do_normal_request({method, url, body, headers, opts, from}, file, state) do
|
||||
case HTTPoison.request(method, url, body, headers, opts) do
|
||||
{:ok, %HTTPoison.Response{status_code: code, headers: resp_headers}}
|
||||
when code in @redirect_status_codes ->
|
||||
redir =
|
||||
Enum.find_value(resp_headers, fn {header, val} ->
|
||||
if header == "Location", do: val, else: false
|
||||
end)
|
||||
|
||||
if redir do
|
||||
# debug_log "redirect"
|
||||
do_normal_request({method, redir, body, headers, opts, from}, file, state)
|
||||
else
|
||||
# debug_log("Failed to redirect: #{inspect resp}")
|
||||
GenServer.reply(from, {:error, :no_server_for_redirect})
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
{:ok, %HTTPoison.Response{body: body, headers: headers, status_code: code}} ->
|
||||
GenServer.reply(from, {:ok, %Response{body: body, headers: headers, status_code: code}})
|
||||
{:noreply, state}
|
||||
|
||||
{:ok, %AsyncResponse{id: ref}} ->
|
||||
timeout = Process.send_after(self(), {:timeout, ref}, 30000)
|
||||
|
||||
req = %Buffer{
|
||||
id: ref,
|
||||
from: from,
|
||||
timeout: timeout,
|
||||
file: file,
|
||||
data: "",
|
||||
headers: nil,
|
||||
status_code: nil,
|
||||
progress_callback: Keyword.fetch!(opts, :progress_callback),
|
||||
request: {method, url, body, headers, opts}
|
||||
}
|
||||
|
||||
{:noreply, %{state | requests: Map.put(state.requests, ref, req)}}
|
||||
|
||||
{:error, %HTTPoison.Error{reason: reason}} ->
|
||||
GenServer.reply(from, {:error, reason})
|
||||
{:noreply, state}
|
||||
|
||||
{:error, reason} ->
|
||||
GenServer.reply(from, {:error, reason})
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_redirect_request(%Buffer{} = buffer, redir, state) do
|
||||
{method, _url, body, headers, opts} = buffer.request
|
||||
|
||||
case HTTPoison.request(method, redir, body, headers, opts) do
|
||||
{:ok, %AsyncResponse{id: ref}} ->
|
||||
req = %Buffer{
|
||||
buffer
|
||||
| id: ref,
|
||||
from: buffer.from,
|
||||
file: buffer.file,
|
||||
data: "",
|
||||
headers: nil,
|
||||
status_code: nil,
|
||||
request: {method, redir, body, headers, opts}
|
||||
}
|
||||
|
||||
state = %{state | requests: Map.delete(state.requests, buffer.id)}
|
||||
state = %{state | requests: Map.put(state.requests, ref, req)}
|
||||
{:noreply, state}
|
||||
|
||||
{:error, %HTTPoison.Error{reason: reason}} ->
|
||||
GenServer.reply(buffer.from, {:error, reason})
|
||||
{:noreply, state}
|
||||
|
||||
{:error, reason} ->
|
||||
GenServer.reply(buffer.from, {:error, reason})
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
defp finish_request(%Buffer{status_code: status_code} = buffer, state)
|
||||
when status_code in @redirect_status_codes do
|
||||
# debug_log "#{inspect Tuple.delete_at(buffer.from, 0)} Trying to redirect: (#{status_code})"
|
||||
redir =
|
||||
Enum.find_value(buffer.headers, fn {header, val} ->
|
||||
case header do
|
||||
"Location" -> val
|
||||
"location" -> val
|
||||
_ -> false
|
||||
end
|
||||
end)
|
||||
|
||||
if redir do
|
||||
do_redirect_request(buffer, redir, state)
|
||||
else
|
||||
# debug_log("Failed to redirect: #{inspect buffer}")
|
||||
GenServer.reply(buffer.from, {:error, :no_server_for_redirect})
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
defp finish_request(%Buffer{} = buffer, state) do
|
||||
# debug_log "Request finish."
|
||||
response = %Response{
|
||||
status_code: buffer.status_code,
|
||||
body: buffer.data,
|
||||
headers: buffer.headers
|
||||
}
|
||||
|
||||
if buffer.timeout, do: Process.cancel_timer(buffer.timeout)
|
||||
maybe_close_file(buffer.file)
|
||||
|
||||
case buffer.file_size do
|
||||
nil -> maybe_log_progress(%{buffer | file_size: :complete})
|
||||
_num -> maybe_log_progress(%{buffer | file_size: "#{byte_size(buffer.data)}"})
|
||||
end
|
||||
|
||||
GenServer.reply(buffer.from, {:ok, response})
|
||||
{:noreply, %{state | requests: Map.delete(state.requests, buffer.id)}}
|
||||
end
|
||||
|
||||
defp fb_headers(headers) do
|
||||
headers |> add_header({"User-Agent", "FarmbotOS/#{@version} (#{@target}) #{@target} ()"})
|
||||
end
|
||||
|
||||
defp add_header(headers, new), do: [new | headers]
|
||||
|
||||
defp fb_opts(opts) do
|
||||
Keyword.merge(
|
||||
opts,
|
||||
ssl: [{:versions, [:"tlsv1.2"]}],
|
||||
hackney: [:insecure, pool: :farmbot_http_pool],
|
||||
recv_timeout: :infinity,
|
||||
timeout: :infinity
|
||||
)
|
||||
|
||||
# stream_to: self(),
|
||||
# follow_redirect: false,
|
||||
# async: :once
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@ defmodule Farmbot.HTTP.Supervisor do
|
|||
|
||||
def init(token) do
|
||||
children = [
|
||||
worker(Farmbot.HTTP, [token, [name: Farmbot.HTTP]])
|
||||
worker(Farmbot.HTTP)
|
||||
]
|
||||
|
||||
opts = [strategy: :one_for_all]
|
||||
|
|
Loading…
Reference in New Issue