Skip to main content

Obstacle Avoider: LiDAR-Based Navigation

This tutorial demonstrates autonomous obstacle avoidance using LiDAR sensor data. The robot moves forward by default and intelligently maneuvers around obstacles.

Overview

The obstacle avoider:

  • Subscribes to LiDAR scan data (/scan)
  • Analyzes surroundings to detect obstacles
  • Chooses optimal escape maneuvers (turn left, turn right, or back up)
  • Resumes forward movement when path is clear

Prerequisites

  • Active TensorFleet VM with robot + LiDAR simulation
  • Simulation View open in VS Code sidebar
  • Completed Robot Mover tutorial

Running the Script

bun robot:obstacle
# or
bun src/obstacle_avoider.js

Expected Output

Connected to rosbridge.
Obstacle avoider initialized:
- Obstacle distance threshold: 0.5 m
- Clear distance required: 1.0 m
- Linear speed: 3.0 m/s
- Angular speed: 4.0 rad/s

Starting obstacle avoidance...
Robot will move forward and avoid obstacles.
Press Ctrl+C to stop.

Path clear at 2.45 m. Rolling forward (path wide open).
Obstacle at 0.48 m. Turning LEFT (opening left (1.23 vs 0.87)).
Path clear at 1.12 m. Rolling forward (cautious advance).
Obstacle at 0.35 m. Backing up (escape: too close).
Path clear at 1.56 m. Rolling forward (path wide open).

How It Works

State Machine

The avoider operates as a state machine with four states:

┌────────────────────────────────────────────────────────────┐
│ State Machine │
├────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ obstacle ┌──────────────┐ │
│ │ FORWARD │ ──────────────▶ │ TURNING_LEFT │ │
│ │ │ ◀────────────── │ │ │
│ └────┬─────┘ path clear └──────────────┘ │
│ │ │
│ │ obstacle ┌───────────────┐ │
│ └─────────────────────▶ │ TURNING_RIGHT │ │
│ ◀───────────────────── │ │ │
│ path clear └───────────────┘ │
│ │
│ │ boxed in ┌───────────────┐ │
│ └─────────────────────▶ │ BACKING_UP │ │
│ ◀───────────────────── │ │ │
│ space available └───────────────┘ │
└────────────────────────────────────────────────────────────┘

Initialization

class ObstacleAvoider {
constructor(ros) {
this.ros = ros;
this.obstacleDistance = OBSTACLE_DISTANCE; // 0.5m
this.clearDistance = CLEAR_DISTANCE; // 1.0m
this.linearSpeed = LINEAR_SPEED; // 3.0 m/s
this.angularSpeed = ANGULAR_SPEED; // 4.0 rad/s

// Publisher for velocity commands
this.cmdVelPub = new ROSLIB.Topic({
ros,
name: CMD_VEL_TOPIC,
messageType: "geometry_msgs/Twist"
});

// Subscriber for LiDAR data
this.scanSub = new ROSLIB.Topic({
ros,
name: SCAN_TOPIC,
messageType: "sensor_msgs/LaserScan"
});

this.latestScan = null;
this.state = "FORWARD";

this.scanSub.subscribe((msg) => {
this.latestScan = msg;
});
}
}

LiDAR Data Analysis

The avoider divides the LiDAR scan into sectors and analyzes each:

analyzeSurroundings() {
const scan = this.latestScan;
const ranges = scan.ranges;
const totalPoints = ranges.length;

const quarter = Math.floor(totalPoints / 4);
const eighth = Math.floor(totalPoints / 8);

// Analyze different sectors
const sectors = {
front: this.getArcStats(ranges, 0, 70),
frontLeft: this.getArcStats(ranges, eighth, 70),
frontRight: this.getArcStats(ranges, totalPoints - eighth, 70),
left: this.getArcStats(ranges, quarter, 70),
right: this.getArcStats(ranges, quarter * 3, 70),
back: this.getArcStats(ranges, quarter * 2, 80)
};

const frontDist = sectors.front.min;

// Decision logic
if (frontDist >= this.clearDistance) {
return { action: "FORWARD", reason: "path wide open" };
}

if (frontDist > this.obstacleDistance) {
return { action: "FORWARD", reason: "cautious advance" };
}

// Choose best escape direction
const leftScore = this.scoreDirection(sectors.frontLeft, sectors.left);
const rightScore = this.scoreDirection(sectors.frontRight, sectors.right);

if (leftScore > rightScore) {
return { action: "TURN_LEFT", reason: "opening left" };
} else {
return { action: "TURN_RIGHT", reason: "opening right" };
}
}

Main Control Loop

loop() {
if (!this.running || !this.ros.isConnected) return;

if (!this.latestScan) {
console.log("Waiting for LiDAR data...");
this.publishVelocity(0.0, 0.0);
return;
}

const decision = this.analyzeSurroundings();
const { action, frontDist, linear, angular, reason } = decision;

if (action === "FORWARD") {
this.state = "FORWARD";
this.publishVelocity(linear, 0.0);
} else if (action === "TURN_LEFT") {
this.state = "TURNING_LEFT";
this.publishVelocity(0.0, this.angularSpeed);
} else if (action === "TURN_RIGHT") {
this.state = "TURNING_RIGHT";
this.publishVelocity(0.0, -this.angularSpeed);
} else if (action === "BACK_UP") {
this.state = "BACKING_UP";
this.publishVelocity(-this.linearSpeed * 0.7, 0.0);
}
}

start() {
this.running = true;
this.loopTimer = setInterval(() => this.loop(), 50); // 20 Hz
}

Configuration

VariableDefaultDescription
OBSTACLE_DISTANCE0.5Distance to trigger avoidance (m)
CLEAR_DISTANCE1.0Distance considered "clear" (m)
LINEAR_SPEED3.0Forward speed (m/s)
ANGULAR_SPEED4.0Turning speed (rad/s)
SCAN_TOPIC/scanLiDAR topic
CMD_VEL_TOPIC/cmd_vel_rawVelocity command topic

Understanding LiDAR Data

The sensor_msgs/LaserScan message contains:

FieldDescription
ranges[]Array of distance measurements
angle_minStart angle of scan (radians)
angle_maxEnd angle of scan (radians)
angle_incrementAngular step between readings
range_minMinimum valid range
range_maxMaximum valid range

Typically, index 0 is straight ahead, with indices wrapping around 360°.

Scoring Algorithm

The direction scoring considers:

  • Minimum distance in that sector (65% weight)
  • Average distance in that sector (25% weight)
  • Wider area average for that direction (10% weight)
  • Turn preference bias to avoid oscillation
scoreDirection(primaryStats, secondaryStats, label) {
const minScore = primaryStats.min;
const avgScore = primaryStats.avg;
const farAvg = secondaryStats.avg;

const base = minScore * 0.65 + avgScore * 0.25 + farAvg * 0.1;
const preferenceBoost = (label === this.turnPreference) ? 0.05 : 0;

return base + preferenceBoost;
}

Common Issues

IssueCauseSolution
Robot spins in placeOscillating between left/rightIncrease turn preference bias
Robot gets stuckBoxed in on all sidesEnsure backup behavior triggers
No LiDAR dataTopic mismatchVerify SCAN_TOPIC matches simulation
Jerky movementLoop rate too slowEnsure 20 Hz control loop

Next Steps

  • Vision YOLO: Add camera-based object detection
  • Combine obstacle avoidance with waypoint navigation

The obstacle avoider demonstrates reactive navigation - responding to sensor data in real-time. This pattern is foundational for autonomous robot behavior.