BB.IK.FABRIK.Tracker (bb_ik_fabrik v0.8.0)

Copy Markdown View Source

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; :pubsub publishes 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 :direct delivery by default for low latency. Under :pubsub a solve that outlives :timeout exits the tracker, as GenServer.call/3 does
  • Continues tracking even if individual solves fail (best-effort)
  • Call stop/1 to 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

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

start_link(opts)

Start a tracker process.

See module documentation for options.

status(tracker)

Get current tracking status.

stop(tracker, opts \\ [])

Stop tracking and return the configuration the last solve arrived at.

Options

  • :hold - Send hold commands to actuators (default: false)

update_target(tracker, target)

Update the current target position.