defmodule Drone.Command do @moduledoc """ Command struct and helpers for drone operations. Every command sent through the ex_drone pipeline is represented as a `Drone.Command` struct. This provides a unified representation regardless of which adapter handles the command. """ @type direction :: :up | :down | :left | :right | :forward | :back @type rotation :: :cw | :ccw @type flip_direction :: :left | :right | :forward | :back @type query_type :: :battery | :height | :speed | :time | :wifi | :sdk_version | :serial_number @type command_type :: :sdk_mode | :takeoff | :land | :emergency | :move | :rotate | :flip | :hover | :speed | :stop | :query @type t :: %__MODULE__{ type: command_type(), args: keyword(), raw: String.t() | nil } @enforce_keys [:type] defstruct [:type, :args, :raw] @doc """ Creates a new command struct. """ @spec new(command_type(), keyword()) :: t() def new(type, args \\ []) do %__MODULE__{type: type, args: args} end @doc """ Creates an SDK mode activation command. """ @spec sdk_mode() :: t() def sdk_mode, do: new(:sdk_mode) @doc """ Creates a takeoff command. """ @spec takeoff() :: t() def takeoff, do: new(:takeoff) @doc """ Creates a land command. """ @spec land() :: t() def land, do: new(:land) @doc """ Creates an emergency stop command. """ @spec emergency() :: t() def emergency, do: new(:emergency) @doc """ Creates a movement command. Direction must be one of: `:up`, `:down`, `:left`, `:right`, `:forward`, `:back`. Distance must be between 20 and 500 cm. """ @spec move(direction(), pos_integer()) :: t() def move(direction, distance) when direction in [:up, :down, :left, :right, :forward, :back] do new(:move, direction: direction, distance: distance) end @doc """ Creates a rotation command. Direction must be `:cw` (clockwise) or `:ccw` (counter-clockwise). Degrees must be between 1 and 3600. """ @spec rotate(rotation(), pos_integer()) :: t() def rotate(direction, degrees) when direction in [:cw, :ccw] do new(:rotate, direction: direction, degrees: degrees) end @doc """ Creates a flip command. Direction must be one of: `:left`, `:right`, `:forward`, `:back`. """ @spec flip(flip_direction()) :: t() def flip(direction) when direction in [:left, :right, :forward, :back] do new(:flip, direction: direction) end @doc """ Creates a hover command. Seconds must be a positive integer. """ @spec hover(pos_integer()) :: t() def hover(seconds) do new(:hover, seconds: seconds) end @doc """ Creates a speed setting command. Speed must be between 10 and 100 cm/s. """ @spec speed(pos_integer()) :: t() def speed(speed) do new(:speed, speed: speed) end @doc """ Creates a stop command (hover in place). """ @spec stop() :: t() def stop, do: new(:stop) @doc """ Creates a query command. Query type must be one of: `:battery`, `:height`, `:speed`, `:time`, `:wifi`, `:sdk_version`, `:serial_number`. """ @spec query(query_type()) :: t() def query(type) when type in [:battery, :height, :speed, :time, :wifi, :sdk_version, :serial_number] do new(:query, type: type) end @doc """ Checks if a command is an emergency command. """ @spec emergency?(t()) :: boolean() def emergency?(%__MODULE__{type: :emergency}), do: true def emergency?(_), do: false @doc """ Checks if a command is a movement command. """ @spec movement?(t()) :: boolean() def movement?(%__MODULE__{type: type}) when type in [:move, :rotate, :flip], do: true def movement?(_), do: false @doc """ Checks if a command is a query command. """ @spec query?(t()) :: boolean() def query?(%__MODULE__{type: :query}), do: true def query?(_), do: false @doc """ Checks if a command requires the drone to be flying. """ @spec requires_flying?(t()) :: boolean() def requires_flying?(%__MODULE__{type: type}) when type in [:move, :rotate, :flip, :hover, :stop], do: true def requires_flying?(%__MODULE__{type: :land}), do: true def requires_flying?(_), do: false @doc """ Checks if a command is a safe (retryable) command. """ @spec safe_to_retry?(t()) :: boolean() def safe_to_retry?(%__MODULE__{type: :query}), do: true def safe_to_retry?(%__MODULE__{type: :sdk_mode}), do: true def safe_to_retry?(_), do: false @doc """ Returns all valid command types. """ @spec types() :: [command_type()] def types do [:sdk_mode, :takeoff, :land, :emergency, :move, :rotate, :flip, :hover, :speed, :stop, :query] end end