Skip to content

🤝 Contact Sensing

Contact force information can serve as the robot's "tactile perception."

For the contact query API, see 📡 State Query API.

Contact as Tactile Signals

Contact forces can provide rich information:

  • Grasp Detection — detecting whether contact is made
  • Force Control — maintaining a target contact force
  • Surface Identification — contact normal direction
  • Collision Detection — unexpected collisions

Contact Query Pipeline

# 1. Get all active contacts
contacts = env.query_contact_simple()
# Returns: [{"geom1": 12, "geom2": 34, "dist": ..., "pos": ..., "frame": ...}, ...]

# 2. Filter contacts of interest (note: keys are lowercase "geom1"/"geom2")
target_body_id = env.model.body_name2id("robot_finger")
finger_contacts = [
    c for c in contacts
    if env.model.get_geom_body_id(c["geom1"]) == target_body_id
    or env.model.get_geom_body_id(c["geom2"]) == target_body_id
]

# 3. Get contact forces (by list index, not by an ID field in the contact dict)
if finger_contacts:
    contact_ids = list(range(len(contacts)))
    forces = env.query_contact_force(contact_ids)
    # Get the force for the corresponding contact
    for i, c in enumerate(finger_contacts):
        contact_idx = contacts.index(c)
        f = forces[contact_idx]
        normal_force = f[0]  # 0th component is the normal force

Grasp Detection

def is_grasped(env, finger_names: list[str], object_name: str) -> bool:
    """Check whether fingers are in contact with a target object"""
    contacts = env.query_contact_simple()
    object_id = env.model.body_name2id(object_name)
    finger_ids = [env.model.body_name2id(f) for f in finger_names]

    for c in contacts:
        geom1_body = env.model.get_geom_body_id(c["geom1"])
        geom2_body = env.model.get_geom_body_id(c["geom2"])
        bodies = {geom1_body, geom2_body}
        if object_id in bodies and any(f in bodies for f in finger_ids):
            return True
    return False

Force Control

def force_control(env, target_force: float = 10.0):
    """Simple force control: maintain a target contact force"""
    contacts = env.query_contact_simple()

    if not contacts:
        return np.zeros(env.model.nu)  # no contact

    contact_ids = list(range(len(contacts)))
    forces = env.query_contact_force(contact_ids)
    total_force = sum(np.linalg.norm(f[:3]) for f in forces.values())

    # PID force control
    force_error = target_force - total_force
    correction = force_error * 0.01

    ctrl = np.zeros(env.model.nu)
    # ... distribute correction to the appropriate actuators
    return ctrl

Contact Information Summary

def contact_summary(env) -> dict:
    """Generate a contact summary"""
    contacts = env.query_contact_simple()

    summary = {
        "total_contacts": len(contacts),
        "body_pairs": set(),
        "total_force": 0.0,
        "max_force": 0.0,
    }

    if contacts:
        contact_ids = list(range(len(contacts)))
        forces = env.query_contact_force(contact_ids)

        for i, c in enumerate(contacts):
            f = forces[i][:3]  # linear force component (contact frame)
            magnitude = np.linalg.norm(f)

            summary["total_force"] += magnitude
            summary["max_force"] = max(summary["max_force"], magnitude)
            summary["body_pairs"].add((
                env.model.get_geom_body_id(c["geom1"]),
                env.model.get_geom_body_id(c["geom2"]),
            ))

    return summary

Standing Detection

def is_standing(env, min_normal_force: float = 50.0) -> bool:
    """Check if the robot is standing (whether foot contact normal force is sufficient)"""
    contacts = env.query_contact_simple()
    if not contacts:
        return False
    contact_ids = list(range(len(contacts)))
    forces = env.query_contact_force(contact_ids)
    # The 0th component in contact frame is the normal force, significantly positive when standing
    max_normal = max(abs(f[0]) for f in forces.values())
    return max_normal > min_normal_force