BB.Servo.Feetech.Actuator (bb_servo_feetech v0.7.0)

Copy Markdown View Source

An actuator that uses a Feetech controller to drive a serial bus servo.

Configuration is derived from the joint's motor_profile injected by BB.Actuator.Server:

  • Position limits from motor_profile.motor_lower / motor_upper
  • Velocity limit from motor_profile.motor_velocity_limit
  • Position range maps to the servo's goal_position register

When initialised, the actuator:

  1. Disables torque on the servo
  2. Asks the servo what model it is, and sets its operating mode if it isn't already in the configured one
  3. Registers with the controller, receiving the shared ETS table reference
  4. Subscribes to the commands its mode admits

When a position command is received, the actuator:

  1. Clamps the position to motor limits
  2. Converts to servo position units (0-4095 for 360 degrees)
  3. Writes goal_position and goal_speed to the controller's ETS table
  4. Publishes a BB.Message.Actuator.BeginMotion via BB.Actuator.publish_begin_motion/3 (which handles the motor → joint-space conversion)

The controller picks up pending commands on its next loop tick and batches them into efficient sync_write operations on the serial bus.

Operating modes

An STS servo does one thing at a time, and :mode picks which. It's set once at startup and never changed while running: the register lives in EEPROM, which has a finite write budget and can only be written with torque off and the servo unlocked.

:modecommands it accepts
:position (default)Position, Trajectory, Effort, Hold, Stop
:velocityVelocity, Effort, Hold, Stop

Anything outside that list is refused by the framework with BB.Error.State.UnsupportedCommand before it reaches the driver.

The servo's :pwm and :step modes aren't offered. PWM is an open-loop duty cycle with no feedback, which no BB command means; step mode is multi-turn positioning, which the joint limits this driver derives its range from don't describe.

Commands

  • Command.Position — travel to a position. Clamped to the joint's limits, and travelling no faster than its velocity limit.
  • Command.Trajectory — walk a list of waypoints, one timer per leg.
  • Command.Velocity — turn at a rate, clamped to the joint's velocity limit.
  • Command.Efforta ceiling, not a goal. These servos have no torque goal register; torque_limit caps what a move may draw, as a fraction of the model's rated stall torque. Setting one won't make the joint move, and the torque it eventually produces is approximate — see BB.Servo.Feetech.Model for how rough. The servo's own overload protection can also wind the ceiling down under sustained load.
  • Command.Stop — cut torque, leaving the joint passive and free to be backdriven. Both :immediate and :decelerate do the same thing, because becoming passive is a single register write with no ramp available.
  • Command.Hold — stay under power without driving. A servo already doing that needs nothing; after a Stop it re-applies torque where the joint has come to rest.

A motion command sent to a joint left passive by Stop re-applies torque on the way past, so callers don't have to pair the two.

Velocity and Effort carry a duration. When it runs out, :expiry_action decides whether the joint goes passive (:stop, the default) or stays under power without driving (:hold) — the same choice the controller's :disarm_action makes for the whole bus.

Beware that a passive joint under load will move, and Stop does not wait for it to settle. Hold and Position both re-apply torque from the servo's own present position, so neither snaps back to a pre-Stop goal, but a joint that has sagged will still be somewhere the caller may not expect.

None of this is the safety path: making the hardware safe is disarm/1, which is robot-wide and leaves the robot unable to move until it is armed again.

Position feedback

These servos answer position queries, so a joint driven by one needs no separate sensor and capabilities/1 says so. It is the controller that reads and publishes, not the actuator: BB.Servo.Feetech.Controller sync_reads present_position for every registered servo on each tick of its loop and publishes a BB.Message.Sensor.JointState per joint, which is what writes the joint's configuration in BB.Robot.State. Nothing turns that off — :status_poll_interval_ms governs only the temperature/voltage/load poll, and :position_deadband suppresses republishing an unchanged position rather than reading it.

Velocity and effort feedback aren't declared. Speed is never read back at all, and present_load is read only by the status poll, which reports it as BB.Servo.Feetech.Message.ServoStatus — a percentage of the servo's own limit, not a torque — rather than as joint state.

Example DSL Usage

controllers do
  controller :feetech, {BB.Servo.Feetech.Controller,
    port: "/dev/ttyUSB0",
    baud_rate: 1_000_000
  }
end

joint :shoulder, type: :revolute do
  limit lower: ~u(-90 degree), upper: ~u(90 degree), velocity: ~u(60 degree_per_second)

  actuator :servo, {BB.Servo.Feetech.Actuator, servo_id: 1, controller: :feetech}
end

Summary

Functions

Safety disarm callback.

Functions

disarm(opts)

Safety disarm callback.

Returns :ok because torque management is handled by the controller. The controller receives all registered servo IDs and disables torque for all of them in a single sync_write operation, which is more efficient for bus-based protocols.