Feetech (feetech v0.2.2)

Copy Markdown View Source

Driver for Feetech TTL-based serial bus servos.

This module provides a GenServer-based interface for communicating with Feetech servos using their proprietary serial protocol.

Basic Usage

# Start the driver (defaults to STS3215 control table)
{:ok, pid} = Feetech.start_link(port: "/dev/ttyUSB0")

# Check if servo is responding
{:ok, status} = Feetech.ping(pid, 1)

# Read current position (radians)
{:ok, position} = Feetech.read(pid, 1, :present_position)

# Move to position (radians)
:ok = Feetech.write(pid, 1, :goal_position, 1.57)

# Read raw position (steps)
{:ok, steps} = Feetech.read_raw(pid, 1, :present_position)

Operating Modes

Servos support multiple operating modes:

  • :position - Standard position control (default)
  • :velocity - Continuous rotation with speed control
  • :step - Stepper mode for multi-turn positioning

Change modes by writing to the :mode register.

Bulk Operations

For controlling multiple servos simultaneously:

# Write to multiple servos at once
Feetech.sync_write(pid, :goal_position, [
  {1, 1.57},
  {2, 0.0},
  {3, -1.57}
])

# Read from multiple servos
{:ok, positions} = Feetech.sync_read(pid, [1, 2, 3], :present_position)

Buffered Writes

For synchronized movement across multiple servos:

# Buffer commands (servos don't move yet)
Feetech.reg_write(pid, 1, :goal_position, 1.57)
Feetech.reg_write(pid, 2, :goal_position, 0.0)

# Execute all buffered commands simultaneously
Feetech.action(pid)

Summary

Functions

Triggers all buffered reg_write commands.

Returns a specification to start this module under a supervisor.

Pings a servo to check if it's responding.

Reads a register value with unit conversion.

Reads a register value as a raw integer (no conversion).

Resets a servo's control table to factory defaults.

Writes a buffered command that executes on action/1.

Resets a servo's state (rotation count for multi-turn mode).

Starts the Feetech driver.

Closes the UART connection and stops the driver.

Reads converted values from multiple servos.

Reads raw values from multiple servos.

Writes converted values to multiple servos simultaneously.

Writes raw values to multiple servos simultaneously.

Writes a converted value to a register.

Writes a raw integer value to a register (no conversion).

Types

option()

@type option() ::
  {:port, String.t()}
  | {:baud_rate, pos_integer()}
  | {:control_table, module()}
  | {:timeout, pos_integer()}
  | {:name, GenServer.name()}

register_name()

@type register_name() :: atom()

servo_id()

@type servo_id() :: 0..254

Functions

action(server)

@spec action(GenServer.server()) :: :ok

Triggers all buffered reg_write commands.

Typically sent to all servos using the broadcast ID.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

ping(server, id)

@spec ping(GenServer.server(), servo_id()) ::
  {:ok, Feetech.Error.status_info()} | {:error, Feetech.Error.error()}

Pings a servo to check if it's responding.

Returns the parsed status from the servo.

Examples

{:ok, %{errors: [], torque_enabled: true}} = Feetech.ping(pid, 1)

read(server, id, register)

@spec read(GenServer.server(), servo_id(), register_name()) ::
  {:ok, term()} | {:error, Feetech.Error.error()}

Reads a register value with unit conversion.

The value is converted according to the control table specification (e.g., steps to radians for position).

Examples

{:ok, 1.57} = Feetech.read(pid, 1, :present_position)  # radians
{:ok, true} = Feetech.read(pid, 1, :torque_enable)     # boolean

read_raw(server, id, register)

@spec read_raw(GenServer.server(), servo_id(), register_name()) ::
  {:ok, integer()} | {:error, Feetech.Error.error()}

Reads a register value as a raw integer (no conversion).

Examples

{:ok, 2048} = Feetech.read_raw(pid, 1, :present_position)  # steps

recovery(server, id)

@spec recovery(GenServer.server(), servo_id()) ::
  {:ok, Feetech.Error.status_info()} | {:error, Feetech.Error.error()}

Resets a servo's control table to factory defaults.

reg_write(server, id, register, value)

@spec reg_write(GenServer.server(), servo_id(), register_name(), term()) ::
  :ok | {:ok, Feetech.Error.status_info()} | {:error, Feetech.Error.error()}

Writes a buffered command that executes on action/1.

Use this to synchronize movement across multiple servos.

Examples

:ok = Feetech.reg_write(pid, 1, :goal_position, 1.57)
:ok = Feetech.reg_write(pid, 2, :goal_position, 0.0)
:ok = Feetech.action(pid)  # Both servos move simultaneously

reset(server, id)

@spec reset(GenServer.server(), servo_id()) ::
  {:ok, Feetech.Error.status_info()} | {:error, Feetech.Error.error()}

Resets a servo's state (rotation count for multi-turn mode).

start_link(opts)

@spec start_link([option()]) :: GenServer.on_start()

Starts the Feetech driver.

Options

  • :port - Serial port path (required), e.g., "/dev/ttyUSB0"
  • :baud_rate - Baud rate, defaults to 1,000,000
  • :control_table - Control table module, defaults to Feetech.ControlTable.STS3215
  • :timeout - Response timeout in ms, defaults to 100
  • :name - GenServer name for registration

Examples

{:ok, pid} = Feetech.start_link(port: "/dev/ttyUSB0")

stop(server)

@spec stop(GenServer.server()) :: :ok

Closes the UART connection and stops the driver.

sync_read(server, ids, register)

@spec sync_read(GenServer.server(), [servo_id()], register_name()) ::
  {:ok, [term()]} | {:error, Feetech.Error.error()}

Reads converted values from multiple servos.

Returns values in the same order as the ID list.

Examples

{:ok, [1.57, 0.0, -1.57]} = Feetech.sync_read(pid, [1, 2, 3], :present_position)

sync_read_raw(server, ids, register)

@spec sync_read_raw(GenServer.server(), [servo_id()], register_name()) ::
  {:ok, [integer()]} | {:error, Feetech.Error.error()}

Reads raw values from multiple servos.

sync_write(server, register, servo_values)

@spec sync_write(GenServer.server(), register_name(), [{servo_id(), term()}]) ::
  :ok | {:error, Feetech.Error.error()}

Writes converted values to multiple servos simultaneously.

Examples

:ok = Feetech.sync_write(pid, :goal_position, [
  {1, 1.57},
  {2, 0.0},
  {3, -1.57}
])

sync_write_raw(server, register, servo_values)

@spec sync_write_raw(GenServer.server(), register_name(), [{servo_id(), integer()}]) ::
  :ok | {:error, Feetech.Error.error()}

Writes raw values to multiple servos simultaneously.

write(server, id, register, value, opts \\ [])

@spec write(GenServer.server(), servo_id(), register_name(), term(), keyword()) ::
  :ok | {:ok, Feetech.Error.status_info()} | {:error, Feetech.Error.error()}

Writes a converted value to a register.

The value is converted from user units (e.g., radians) to raw units according to the control table specification.

By default, this is a fire-and-forget operation. Use await_response: true to wait for acknowledgement.

Options

  • :await_response - Wait for servo response (default: false)

Examples

:ok = Feetech.write(pid, 1, :goal_position, 1.57)
{:ok, status} = Feetech.write(pid, 1, :goal_position, 1.57, await_response: true)

write_raw(server, id, register, value, opts \\ [])

@spec write_raw(GenServer.server(), servo_id(), register_name(), integer(), keyword()) ::
  :ok | {:ok, Feetech.Error.status_info()} | {:error, Feetech.Error.error()}

Writes a raw integer value to a register (no conversion).

Examples

:ok = Feetech.write_raw(pid, 1, :goal_position, 2048)