A controller that manages a Feetech servo bus.
This controller wraps the Feetech GenServer and provides a shared ETS table
for actuators to write commands to. The controller runs a fixed-rate control
loop that batches all pending commands into efficient bulk writes (sync_write)
and reads positions via bulk reads (sync_read).
On each loop tick, the controller:
- Reads all pending commands from the ETS table
- Batches them into
sync_writeoperations for the serial bus - Clears the command fields
- Reads all servo positions via
sync_read - Updates the ETS table with current positions
- Publishes
JointStatemessages for positions that exceed deadband
Configuration
The controller is typically defined in the robot DSL:
controllers do
controller :feetech, {BB.Servo.Feetech.Controller,
port: "/dev/ttyUSB0",
baud_rate: 1_000_000,
control_table: Feetech.ControlTable.STS3215,
loop_interval_ms: 10
}
endOptions
:port- (required) The serial port path, e.g.,"/dev/ttyUSB0":baud_rate- Baud rate in bps (default: 1_000_000):control_table- The servo control table to use (default:Feetech.ControlTable.STS3215):loop_interval_ms- Control loop interval in ms (default: 10, i.e. 100Hz):status_poll_interval_ms- Status polling interval in ms (default: 1000, set to 0 to disable):disarm_action- Action to take when robot is disarmed (default::disable_torque):disable_torque- Disable torque on all servos (safe default):hold- Keep torque enabled (servos hold position)
ETS Table Structure
Each registered servo has a row in the ETS table:
{servo_id, actuator_path, position_deadband,
last_position_raw, present_position, present_temperature,
present_voltage, present_load, hardware_error,
pending_writes, pending_limit, torque_enabled}Actuators write pending_writes — a list of {param, value} pairs naming
whichever goal registers their operating mode acts on — via
:ets.update_element/3. The controller groups them by param each tick so a
position move's goal_position and goal_speed still leave as one
sync_write each, then clears them. It uses BB.Actuator.to_joint_space/3 to
translate encoder readings to joint-space before publishing JointState.
pending_limit is the torque ceiling, and has a slot of its own because it
isn't a goal. A new motion command supersedes the last one, so pending_writes
is replaced wholesale; a ceiling outlives the move it was set for, and would be
lost if an Effort and a Position arriving in the same tick had to share a
slot.
Torque state
torque_enabled caches what this controller last told each servo, so an
actuator can tell whether its goal will actually be acted on without spending
a bus round trip per command. Every write to torque_enable passes through
this controller — the actuators, the parameter bridge and arming all go
through handle_call/3 or enable_all_torque/1 — which is what makes the
cache trustworthy.
It is only meaningful while the robot is armed. Disarming turns torque off without updating the cache, because commands can't reach an actuator in that state anyway, and arming re-establishes it for every registered servo.
Safety
This controller implements the BB.Controller behaviour's disarm/1 safety
callback. When the robot is disarmed or crashes, torque is disabled on all known
servo IDs using acknowledged per-servo writes, so disarm/1 only reports :ok
once the bus has confirmed every servo is safe; any failed or undeliverable
write returns {:error, reason} and drives the robot into the :error state.
Summary
Functions
Handle disarm based on the configured disarm_action.
Functions
Handle disarm based on the configured disarm_action.
Called by BB.Safety.Controller when the robot is disarmed or crashes.
By default, disables torque on all registered servo IDs.