BB.Robot (bb v0.28.0)

Copy Markdown View Source

An optimised robot representation for kinematic computations.

This struct is built from the Spark DSL at compile-time and contains:

  • All physical values converted to SI base units (floats)
  • Flat maps for O(1) lookup of links, joints, sensors, and actuators by name
  • Pre-computed topology metadata for efficient traversal
  • Bidirectional parent/child references

Structure

The robot is organised as flat maps indexed by name:

  • links - all links in the robot, keyed by atom name
  • joints - all joints in the robot, keyed by atom name
  • sensors - all sensors (at any level), keyed by atom name
  • actuators - all actuators, keyed by atom name

Unit Conventions

All physical quantities are stored as native floats in SI base units:

  • Length: meters
  • Angle: radians
  • Mass: kilograms
  • Moment of inertia: kg·m²
  • Force: newtons
  • Torque: newton-meters
  • Linear velocity: m/s
  • Angular velocity: rad/s

Summary

Functions

Get the full path from root to an actuator.

Get the child joints of a link.

Get a joint by name.

Get a link by name.

Get all joints in traversal order.

Get all links in topological order (root first).

Get the parent joint of a link.

Get the path from a source link down to a target link.

Get the path from root to a given link or joint.

Get the link at the root of the kinematic tree.

Types

actuator_info()

@type actuator_info() :: %{
  name: atom(),
  joint: atom(),
  transmission: transmission() | nil
}

param_location()

@type param_location() ::
  {:joint, atom(), [atom()]}
  | {:actuator, atom(), [atom()]}
  | {:sensor, atom(), [atom()]}

sensor_info()

@type sensor_info() :: %{
  name: atom(),
  attached_to: {:link, atom()} | {:joint, atom()} | :robot,
  transmission: transmission() | nil
}

t()

@type t() :: %BB.Robot{
  actuators: %{required(atom()) => actuator_info()},
  joints: %{required(atom()) => BB.Robot.Joint.t()},
  links: %{required(atom()) => BB.Robot.Link.t()},
  name: atom(),
  param_subscriptions: %{required([atom()]) => [param_location()]},
  root_link: atom(),
  sensors: %{required(atom()) => sensor_info()},
  topology: BB.Robot.Topology.t()
}

transmission()

@type transmission() :: %{
  reduction: float() | nil,
  offset: float() | nil,
  reversed?: boolean() | nil
}

Functions

actuator_path(robot, name)

@spec actuator_path(t(), atom()) ::
  {:ok, [atom()]} | {:error, BB.Error.Kinematics.UnknownActuator.t()}

Get the full path from root to an actuator.

Actuator names are unique per robot, so the path is derivable: an actuator always hangs off a joint, and the joint's own path gives the links and joints above it. The result matches the :path the framework injects into the actuator's :bb option, and therefore the topic its commands are published to — [:actuator | actuator_path(robot, name)].

BB.Robot.actuator_path(robot, :pan_servo)
#=> {:ok, [:base, :pan, :pan_servo]}

child_joints(robot, link_name)

@spec child_joints(t(), atom()) ::
  {:ok, [BB.Robot.Joint.t()]} | {:error, BB.Error.Kinematics.UnknownLink.t()}

Get the child joints of a link.

A link with no children returns {:ok, []}, which is distinct from naming a link that doesn't exist.

get_joint(robot, name)

@spec get_joint(t(), atom()) ::
  {:ok, BB.Robot.Joint.t()} | {:error, BB.Error.Kinematics.UnknownJoint.t()}

Get a joint by name.

get_link(robot, name)

@spec get_link(t(), atom()) ::
  {:ok, BB.Robot.Link.t()} | {:error, BB.Error.Kinematics.UnknownLink.t()}

Get a link by name.

joints_in_order(robot)

@spec joints_in_order(t()) :: [BB.Robot.Joint.t()]

Get all joints in traversal order.

parent_joint(robot, link_name)

Get the parent joint of a link.

The root link has no parent joint, which is reported as {:error, %BB.Error.Kinematics.NoParentJoint{}} — a distinct type from UnknownLink so a caller walking up the tree can match on it as a termination signal rather than being told a valid root link doesn't exist.

path_between(robot, source_link, target_link)

@spec path_between(t(), atom(), atom()) ::
  {:ok, [atom()]}
  | {:error,
     BB.Error.Kinematics.UnknownLink.t() | BB.Error.Kinematics.NotAnAncestor.t()}

Get the path from a source link down to a target link.

Restricted to the case where source_link is an ancestor of target_link, which is a prefix drop on the precomputed root-relative paths. The result starts at source_link and ends at target_link, interleaving the joints and links between them, so path_to/2 is the special case of a source at the root.

A source that isn't above the target reports BB.Error.Kinematics.NotAnAncestor, carrying the nearest common ancestor so the message names the link the caller should have passed.

BB.Robot.path_between(robot, :chassis, :sensor_head)
#=> {:ok, [:chassis, :mast, :sensor_head]}

path_to(robot, name)

@spec path_to(t(), atom()) ::
  {:ok, [atom()]} | {:error, BB.Error.Kinematics.UnknownLink.t()}

Get the path from root to a given link or joint.

Equivalent to path_between/3 from the root link, and delegates to BB.Robot.Topology.path_to/2.

root_link(robot)

@spec root_link(t()) :: atom()

Get the link at the root of the kinematic tree.

Returns a bare atom rather than a result tuple: unlike every other lookup here it cannot fail, because BB.Dsl.TopologyTransformer guarantees exactly one root link exists.

Useful when a caller genuinely wants a whole-tree chain and has to say so — BB.Motion requires :source_link with no default, precisely so that root-to-target is recorded as a decision rather than assumed.

BB.Motion.move_to(robot, :gripper, target,
  source_link: BB.Robot.root_link(robot),
  solver: BB.IK.DLS
)