GenServer implementing continuous position tracking with FABRIK.
Runs a periodic IK solve loop, continuously solving for updated targets and sending actuator commands. Useful for following moving targets or real-time position control from external sources.
Usage
# Start tracking
{:ok, pid} = BB.IK.FABRIK.Tracker.start_link(
robot: MyRobot,
target_link: :gripper,
source_link: :base_link,
initial_target: {0.3, 0.2, 0.1},
update_rate: 30
)
# Update target from vision callback
BB.IK.FABRIK.Tracker.update_target(pid, {0.35, 0.25, 0.15})
# Check status
%{residual: 0.001, tracking: true} = BB.IK.FABRIK.Tracker.status(pid)
# Stop and get final positions
{:ok, positions} = BB.IK.FABRIK.Tracker.stop(pid)Options
:robot- Robot module (required):target_link- Link to track (required):source_link- Link the chain starts at (required, no default):initial_target- Starting target position (required):update_rate- Solve frequency in Hz (default: 20):delivery- Actuator command delivery.:direct(default) casts each command and waits for nothing;:pubsubpublishes it and waits for the actuator to accept it, which blocks the loop for as long as that takes:timeout- How long to wait for each actuator under:pubsub, in milliseconds (default 5000). Ignored under:direct:max_iterations- Maximum FABRIK iterations per update (default: 50):tolerance- Convergence tolerance in metres (default: 1.0e-4):respect_limits- Whether to clamp to joint limits (default: true):name- Optional GenServer name for registration
Position feedback is a prerequisite
Every solve starts from the robot's current configuration, which is written
from BB.Message.Sensor.JointState messages and from nothing else - a
commanded position is not a measured one. A joint that nothing reports on
therefore stays at its initial configuration, and the tracker re-solves from
that same frozen pose on every tick.
That does not diverge: a solve is a function of its seed and its target, so a frozen seed still yields an absolute joint configuration that reaches the target. What it loses is the warm start, and with it the continuity between ticks. Each solve pays the iteration count a distant seed needs rather than the handful a nearby one does, and converges less reliably near singularities. The one that bites is that the answer stops depending on the path taken to reach the target, which leaves the arm free to change solution branch from one tick to the next: a redundant arm can be asked to swing between two equally valid postures inside a single tick period.
So the tracked joints want something that reports where they are: an encoder,
a driver that declares :position_feedback through
BB.Actuator.capabilities/1, or BB.Sensor.OpenLoopPositionEstimator
interpolating from the actuator's own BeginMotion messages. BB.Dsl warns
at compile time about a driven joint with none of the three, and simulation
supplies an estimator itself.
Notes
- Uses
:directdelivery by default for low latency. Under:pubsuba solve that outlives:timeoutexits the tracker, asGenServer.call/3does - Continues tracking even if individual solves fail (best-effort)
- Call
stop/1to cleanly terminate tracking
Summary
Functions
Returns a specification to start this module under a supervisor.
Start a tracker process.
Get current tracking status.
Stop tracking and return the configuration the last solve arrived at.
Update the current target position.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
Start a tracker process.
See module documentation for options.
Get current tracking status.
Stop tracking and return the configuration the last solve arrived at.
Options
:hold- Send hold commands to actuators (default: false)
Update the current target position.