Actuator Systems¶
This page describes how to command the neck, torso, holonomic base, and arms.
from feathersdk.robot.robot import initialize_feather_system, FeatherRobot
robot: FeatherRobot = initialize_feather_system()
neck = robot.neck_system
torso = robot.torso_system
nav = robot.navigation_system
arms = robot.arms_system
If a system is omitted in the robot config, the matching attribute is None.
Neck system¶
robot.neck_system is a 2-DOF head: pitch (look up/down) and yaw (look left/right). Angles are in radians.
Home / Zero position: After init the neck is at (pitch=0, yaw=0): the head will be looking straight down, parallel
to the ground. Positive pitch looks down; negative pitch looks up. Positive yaw turns right; negative yaw turns left.
go_to(pitch, yaw) points the neck at that pitch and yaw. Omit an argument (or pass None) to leave that axis
unchanged.
neck.go_to(pitch=-0.3, yaw=-0.4) # look up and to the left
neck.go_to(pitch=0.0, yaw=0.0) # return to home
neck.go_to(yaw=0.2) # change yaw only
set_speed(pitch_speed, yaw_speed) sets how fast the neck motors move toward the go_to target, in rad/s. It does
not start motion by itself — it is the speed used by the next (and current) go_to.
neck.set_speed(pitch_speed=1.0, yaw_speed=1.0)
neck.go_to(pitch=-0.5, yaw=0.0)
Torso system¶
robot.torso_system is the vertical lift. Heights are in millimeters.
Home / 600mm: Calibration zeros the lift at the top of travel. That top position is height 600. Other values
are millimeters below that home: 500 is 100 mm below the top, 400 is 200 mm below, and so on.
torso.go_to(600) # top / calibrated home
torso.go_to(500) # 100 mm below home
torso.go_to(500, velocity=20.0) # same target, 20 mm/s
go_to(height, velocity=None) moves to that height. velocity is a positive speed in mm/s; if omitted, the motor’s
configured default is used.
print(torso.get_height()) # current height in mm (600 at the top)
Arms system¶
Warning
Always stay outside of arms reach of the robot when the arms are enabled. At unrestricted speeds, they can move very fast. Getting hit with a large hunk of metal at those speeds could cause serious damage.
robot.arms_system owns the left and right 7-DOF arms. You can command both through the parent, or one arm
through left_arm / right_arm:
robot.arms_system.enable()
robot.arms_system.left_arm.enable()
robot.arms_system.right_arm.home()
The same methods exist on both levels: enable, disable, home, freeze, set_targets, set_control_mode,
capture_position.
Arms start disabled. Initialization calibrates them and starts the control thread, but they will not take motion
commands until you enable().
arms.enable() # required before any motion
arms.disable() # home, then power the motors down
Control modes¶
Set the mode with set_control_mode(...), using "targets" / "impedance" or ArmsControlMode.
Targets (default). Joints move to commanded positions under velocity / acceleration / jerk limits, planned by
Ruckig. Give those constraints with set_targets(...) — a dict of motor name → value for
whichever fields you want to change (positions, max_velocities, max_accelerations, max_jerks, kps, kds,
and so on). Omitted keys are left as they are.
from feathersdk.robot.actuator_systems.arms_system import ArmsControlMode
arms.set_control_mode(ArmsControlMode.Targets) # or "targets"
arms.left_arm.set_targets(
positions={"elbow_left": 0.5},
max_velocities={"elbow_left": 0.8},
)
Impedance. The arm holds its current pose (gravity is compensated) but yields if you push on it. External
non-gravity forces move the arm; when you let go, it holds the new pose. Switching to impedance captures the current
joints as the hold position. In this mode set_targets can only change kps, kds, and additional_torques.
arms.set_control_mode("impedance")
# move the arm by hand...
arms.set_control_mode("targets") # freezes at the current pose
home, freeze, disable, capture_position¶
home() switches to Targets mode and moves every joint to 0 (the calibrated home pose, arms down along the
body). By default it blocks until the joints are close to home.
freeze() holds the pose at the moment of the call (Targets mode, current positions as the target). Use this to
stop in place without homing.
disable() homes first, then powers the motors down. It is not an in-place stop — use freeze() if you want to
hold the current pose.
capture_position() returns {motor_name: angle_rad} for that arm, or both arms if called on arms_system.
Pass trigger_feedback=True if the arm is currently disabled and you need a fresh sample.
arms.enable()
q = arms.capture_position() # both arms
q_left = arms.left_arm.capture_position()
arms.freeze() # hold here
arms.home() # go to joint zeros
arms.disable() # home, then disable