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
@type option() :: {:port, String.t()} | {:baud_rate, pos_integer()} | {:control_table, module()} | {:timeout, pos_integer()} | {:name, GenServer.name()}
@type register_name() :: atom()
@type servo_id() :: 0..254
Functions
@spec action(GenServer.server()) :: :ok
Triggers all buffered reg_write commands.
Typically sent to all servos using the broadcast ID.
Returns a specification to start this module under a supervisor.
See Supervisor.
@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)
@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
@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
@spec recovery(GenServer.server(), servo_id()) :: {:ok, Feetech.Error.status_info()} | {:error, Feetech.Error.error()}
Resets a servo's control table to factory defaults.
@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
@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).
@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 toFeetech.ControlTable.STS3215:timeout- Response timeout in ms, defaults to 100:name- GenServer name for registration
Examples
{:ok, pid} = Feetech.start_link(port: "/dev/ttyUSB0")
@spec stop(GenServer.server()) :: :ok
Closes the UART connection and stops the driver.
@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)
@spec sync_read_raw(GenServer.server(), [servo_id()], register_name()) :: {:ok, [integer()]} | {:error, Feetech.Error.error()}
Reads raw values from multiple servos.
@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}
])
@spec sync_write_raw(GenServer.server(), register_name(), [{servo_id(), integer()}]) :: :ok | {:error, Feetech.Error.error()}
Writes raw values to multiple servos simultaneously.
@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)
@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)