Skip to main content

Overview

This tutorial covers the complete pick and place workflow, from scanning for objects to executing precise placement in designated zones. You’ll learn how to coordinate motion planning, serial communication, and error handling.
Pick and place operations require prior scanning to identify object positions. Always run a scan first.

Workflow Overview

Prerequisites

1

Scan for Objects

Before picking, you must scan the environment:
from arm_system.main import Robot

robot = Robot()
robot.run()

# In the menu, press 'n' to scan
# Or programmatically:
robot.handle_scan_command()
This populates robot.scan_results with detected objects.
2

Verify Scan Results

Check that objects were detected:
if not robot.scan_results:
    print("No objects detected. Run scan first.")
    return

print(f"Found {len(robot.scan_results)} objects")
for obj in robot.scan_results:
    print(f"Object {obj['index']}: {obj['class']} at {obj['center_angle']}°")

Placement Zone Configuration

Each object class has a designated placement zone:
# From main.py:17-22
placement_zones = {
    'apple': {'angle': 90, 'distance': 200},
    'orange': {'angle': 180, 'distance': 200},
    'bottle': {'angle': 45, 'distance': 200},
    'default': {'angle': 270, 'distance': 200},
}

Apple Zone

90° angle, 200mm distance

Orange Zone

180° angle, 200mm distance

Bottle Zone

45° angle, 200mm distance

Default Zone

270° angle, 200mm distance

Custom Placement Zones

You can customize zones for your workspace:
class CustomRobot(Robot):
    def __init__(self):
        super().__init__()
        
        # Override placement zones
        self.placement_zones = {
            'apple': {'angle': 60, 'distance': 180},
            'orange': {'angle': 120, 'distance': 180},
            'bottle': {'angle': 30, 'distance': 220},
            'default': {'angle': 300, 'distance': 200},
        }

Object Selection

1

Interactive Selection

The robot presents detected objects for selection:
# From main.py:151-169
def select_object_interactively(self):
    """Interface for object selection"""
    print("\n=== OBJECTS DETECTED LIST ===")
    for o in self.scan_results:
        i = o['index']
        print(f"[{i}] angle={o['center_angle']}° dist={o['distance']}mm class={o['class']} conf={o['confidence']:.2f}")
    print("[0] cancelar")
    
    try:
        selection = int(input("\nselect the object you want to take: "))
        if selection == 0:
            print("operation canceled")
            return {}
        
        return next((x for x in self.scan_results if x['index'] == selection), {})
    
    except ValueError:
        print("invalid input")
        return {}
Example interaction:
=== OBJECTS DETECTED LIST ===
[1] angle=45° dist=150mm class=apple conf=0.89
[2] angle=120° dist=180mm class=orange conf=0.92
[3] angle=200° dist=160mm class=bottle conf=0.87
[0] cancelar

select the object you want to take: 1
2

Programmatic Selection

Select objects programmatically:
# Select by index
selected = robot.scan_results[0]

# Select highest confidence
selected = max(robot.scan_results, key=lambda x: x['confidence'])

# Select by class
selected = next((x for x in robot.scan_results if x['class'] == 'apple'), None)

# Select closest
selected = min(robot.scan_results, key=lambda x: x['distance'])

Pick Sequence

The pick sequence moves the arm to the object and closes the gripper:
1

Understand the Pick Plan

# From main.py:171-183
def execute_pick_sequence(self, target_object: dict) -> bool:
    try:
        plan = [
            {'joint': 'base', 'angle': target_object['center_angle'], 'speed': 30},
            {'joint': 'arm', 'distance': target_object['distance'], 'action': 'pick'},
            {'joint': 'gripper', 'action': 'close'},
            {'joint': 'arm', 'distance': target_object['distance'], 'action': 'up'},
        ]
        self.execute_movement('pick_service', plan)
        return True
    except Exception as e:
        log.error(f"Error in pick sequence: {e}")
        return False
Movement breakdown:
  1. Rotate base to object angle
  2. Extend arm to object distance (pick action)
  3. Close gripper around object
  4. Retract arm upward
2

Execute Pick

# Select object
target = robot.scan_results[0]

# Execute pick
if robot.execute_pick_sequence(target):
    print("Pick completed successfully!")
else:
    print("Pick failed")
Expected output:
2026-03-07 10:31:00 - INFO - execution movements:
2026-03-07 10:31:00 - INFO - movement: base
2026-03-07 10:31:02 - INFO - -> ¡Movement base completed!
2026-03-07 10:31:02 - INFO - movement: arm
2026-03-07 10:31:04 - INFO - -> ¡Movement arm completed!
2026-03-07 10:31:04 - INFO - movement: gripper
2026-03-07 10:31:05 - INFO - -> ¡Movement gripper completed!
2026-03-07 10:31:05 - INFO - movement: arm
2026-03-07 10:31:06 - INFO - -> ¡Movement arm completed!

Place Sequence

The place sequence moves to the designated zone and releases the object:
1

Understand the Place Plan

# From main.py:185-200
def execute_place_sequence(self, target_object: dict):
    """Execute object placement"""
    try:
        zone_params = target_object['placement_zone']
        movement_plan = [
            {'joint': 'base', 'angle': zone_params['angle'], 'speed': 30},
            {'joint': 'arm', 'distance': zone_params['distance'], 'action': 'place'},
            {'joint': 'gripper', 'action': 'open'},
            {'joint': 'arm', 'distance': target_object['distance'], 'action': 'up'},
            {'joint': 'base', 'angle': 0, 'speed': 60},
        ]
        self.execute_movement('place_service', movement_plan)
        return True
    except Exception as e:
        log.error(f"Error in place sequence: {e}")
        return False
Movement breakdown:
  1. Rotate base to placement zone angle
  2. Extend arm to placement distance
  3. Open gripper to release object
  4. Retract arm upward
  5. Return base to home position (0°)
2

Execute Place

# After successful pick
if robot.execute_place_sequence(target):
    print("Place completed successfully!")
else:
    print("Place failed")
Expected output:
2026-03-07 10:31:06 - INFO - execution movements:
2026-03-07 10:31:06 - INFO - movement: base
2026-03-07 10:31:08 - INFO - -> ¡Movement base completed!
2026-03-07 10:31:08 - INFO - movement: arm
2026-03-07 10:31:10 - INFO - -> ¡Movement arm completed!
2026-03-07 10:31:10 - INFO - movement: gripper
2026-03-07 10:31:11 - INFO - -> ¡Movement gripper completed!
2026-03-07 10:31:11 - INFO - movement: arm
2026-03-07 10:31:12 - INFO - -> ¡Movement arm completed!
2026-03-07 10:31:12 - INFO - movement: base
2026-03-07 10:31:14 - INFO - -> ¡Movement base completed!

Complete Pick and Place

The full operation combines both sequences:
# From main.py:132-149
def handle_pick_place_command(self):
    """Pick & place command"""
    if not self.scan_results:
        log.warning("1. first scanning the environment (option 'n')")
        return

    selected_object = self.select_object_interactively()
    if not selected_object:
        return

    log.info(f"\ninit pick & place to object: {selected_object['index']}:")
    log.info(f"angle: {selected_object['center_angle']}°")
    log.info(f"distance: {selected_object['distance']} mm")
    
    if self.execute_pick_sequence(selected_object):
        log.info(f"¡pick completed!")
        if self.execute_place_sequence(selected_object):
            log.info(f"¡pick and place completed!")

Full Example

pick_and_place_demo.py
from arm_system.main import Robot
import logging as log

log.basicConfig(level=log.INFO, format="%(asctime)s - %(levelname)s - %(message)s")

def automated_pick_and_place():
    """Automated pick and place demo"""
    robot = Robot()
    
    # Connect
    if not robot.serial_manager.connect():
        print("Failed to connect")
        return
    
    print("Connected! Starting scan...")
    
    # Scan environment
    robot.handle_scan_command()
    
    if not robot.scan_results:
        print("No objects found")
        robot.serial_manager.close()
        return
    
    # Pick and place all objects
    for obj in robot.scan_results:
        print(f"\nProcessing object {obj['index']}: {obj['class']}")
        
        if robot.execute_pick_sequence(obj):
            print(f"✓ Picked {obj['class']}")
            
            if robot.execute_place_sequence(obj):
                print(f"✓ Placed {obj['class']} in zone")
            else:
                print(f"✗ Failed to place {obj['class']}")
                break
        else:
            print(f"✗ Failed to pick {obj['class']}")
            break
    
    print("\nAll operations complete!")
    robot.serial_manager.close()

if __name__ == '__main__':
    automated_pick_and_place()

Motion Execution

All movements use the unified execute_movement() method:
# From main.py:217-242
def execute_movement(self, message_type: str, movement_sequence: list):
    """Send commands to arm"""
    log.info("\nexecution movements:")
    
    for move in movement_sequence:
        try:
            joint = move['joint']
            log.info(f"movement: {joint}")
            
            # Reset state
            self.serial_manager.movement_status[joint] = {'state': 'pending'}
            
            # Send command
            self.serial_manager.send_message(message_type, move)
            
            # Wait for confirmation
            if not self.serial_manager.wait_for_confirmation(joint):
                raise Exception(f"movement failed in: {joint}")
            
            # Log success
            log.info(f"-> ¡Movement {joint} completed!")
                
        except Exception as e:
            log.error(f'error in movement: {str(e)}')
            self.handle_movement_failure()
            raise
Key features:
  1. Sequential execution with confirmation
  2. Timeout handling (30s default)
  3. Automatic error recovery
  4. Status tracking per joint

Error Handling

1

Movement Failure Detection

The system monitors each movement for completion or error:
# Wait for movement confirmation
if not self.serial_manager.wait_for_confirmation(joint, timeout=30):
    raise Exception(f"movement failed in: {joint}")
2

Safety Protocol Activation

On failure, the safety protocol is triggered:
# From main.py:244-258
def handle_movement_failure(self):
    """Handles faults in the motion sequence"""
    log.error('executing security protocol')
    self.serial_manager.send_message('safety_service', {})
    
    start_time = time.time()
    while (time.time() - start_time) < 15:
        if self.serial_manager.safety_status.get('state') == 'completed':
            log.info("system safety")
            return
        time.sleep(0.5)
        
    log.error("error in safety service")
    self.serial_manager.close()
    exit(1)
The safety service:
  1. Stops all motors
  2. Moves arm to safe position
  3. Opens gripper
  4. Returns base to home

Advanced Features

Get Current Joint Angles

# From main.py:203-215
def get_current_angles(self) -> dict:
    """Get current joint angles"""
    try:
        self.serial_manager.send_message('get_angles', {})
        
        if self.serial_manager.wait_for_angles_response(timeout=5):
            return self.serial_manager.current_angles
        else:
            raise Exception('timeout waiting for angles')
            
    except Exception as e:
        log.error(f"error getting angles: {e}")
        return None
Usage:
# Check current position
angles = robot.get_current_angles()

if angles:
    print(f"Base: {angles['base']}°")
    print(f"Arm: {angles['arm']}°")
    print(f"Gripper: {angles['gripper']}°")

Custom Movement Sequences

Create custom movement plans:
# Custom sequence to move to specific position
custom_plan = [
    {'joint': 'base', 'angle': 135, 'speed': 40},
    {'joint': 'arm', 'distance': 250, 'action': 'extend'},
    {'joint': 'gripper', 'action': 'close'},
]

robot.execute_movement('pick_service', custom_plan)

Conditional Placement

Place objects based on custom logic:
def place_by_confidence(robot, scan_results):
    """Place high confidence objects in premium zone"""
    for obj in scan_results:
        # Override placement zone based on confidence
        if obj['confidence'] > 0.9:
            obj['placement_zone'] = {'angle': 45, 'distance': 180}
        else:
            obj['placement_zone'] = {'angle': 270, 'distance': 220}
        
        # Execute pick and place
        if robot.execute_pick_sequence(obj):
            robot.execute_place_sequence(obj)

Troubleshooting

Error: first scanning the environment (option 'n')Solutions:
  • Run scan before pick and place: press n in menu
  • Verify scan completed successfully
  • Check robot.scan_results is not empty
  • Ensure objects were detected during scan
Error: movement failed in: base/arm/gripperSolutions:
  • Check motor connections to VEX brain
  • Verify power supply is adequate
  • Ensure no physical obstructions
  • Check joint is within mechanical limits
  • Increase timeout: wait_for_confirmation(joint, timeout=60)
Issue: Object not grasped properlySolutions:
  • Check gripper motor functionality
  • Verify object is within gripper range
  • Adjust gripper close command parameters
  • Ensure sufficient grip force
  • Check gripper sensors if available
Issue: Object placed in wrong locationSolutions:
  • Calibrate base rotation encoder
  • Verify placement zone coordinates
  • Check arm extension accuracy
  • Ensure base returns to 0° between operations
  • Add validation step before opening gripper
Message: executing security protocolSolutions:
  • Check VEX brain error logs
  • Verify all sensors are functional
  • Ensure workspace is clear
  • Test individual joints separately
  • Restart system and recalibrate

Best Practices

Always Scan First

Never attempt pick and place without recent scan data. Objects may have moved.

Validate Selection

Verify selected object exists and has valid position data before executing.

Monitor Feedback

Watch for movement confirmation messages to detect issues early.

Handle Errors Gracefully

Always implement error handling and safety protocol activation.

Next Steps

Quick Start

Complete end-to-end workflow tutorial

Serial Communication

Deep dive into motion commands

Robot API

Complete Robot class documentation

Motion Planning

Advanced motion planning techniques

Build docs developers (and LLMs) love