Skip to main content

lib/drone/adapters/tello/connection.ex

defmodule Drone.Adapters.Tello.Connection do
  @moduledoc """
  UDP connection handler for the Tello drone.

  Manages a `:gen_udp` socket for sending commands and receiving
  responses from a Tello drone over Wi-Fi.
  """

  @default_drone_ip {192, 168, 10, 1}
  @default_drone_port 8889
  @default_local_port 8889
  @default_timeout 10_000

  def default_drone_ip, do: @default_drone_ip
  def default_drone_port, do: @default_drone_port
  def default_local_port, do: @default_local_port
  def default_timeout, do: @default_timeout

  @spec open(keyword()) :: {:ok, port()} | {:error, term()}
  def open(opts \\ []) do
    local_port = Keyword.get(opts, :local_port, @default_local_port)
    :gen_udp.open(local_port, [:inet, {:active, false}])
  end

  @spec close(port()) :: :ok
  def close(socket) do
    :gen_udp.close(socket)
  end

  @spec send_command(port(), String.t(), keyword()) :: {:ok, term()} | {:error, term()}
  def send_command(socket, command, opts \\ []) do
    ip = Keyword.get(opts, :drone_ip, @default_drone_ip)
    port = Keyword.get(opts, :drone_port, @default_drone_port)
    timeout = Keyword.get(opts, :timeout, @default_timeout)

    case :gen_udp.send(socket, ip, port, command) do
      :ok ->
        receive_response(socket, timeout)

      {:error, reason} ->
        {:error, reason}
    end
  end

  @spec receive_response(port(), non_neg_integer()) :: {:ok, binary()} | {:error, term()}
  def receive_response(socket, timeout) do
    case :gen_udp.recv(socket, 0, timeout) do
      {:ok, {_ip, _port, data}} when is_binary(data) ->
        {:ok, data}

      {:ok, {_ip, _port, data}} when is_list(data) ->
        {:ok, :erlang.list_to_binary(data)}

      {:error, :timeout} ->
        {:error, :timeout}

      {:error, reason} ->
        {:error, reason}
    end
  end
end