Skip to main content
Lidar parts provide 2D laser scanning for obstacle detection, distance measurement, and SLAM mapping.

Lidar Drivers

RPLidar2

Adafruit RPLidar driver with filtering and batch processing. Constructor:
RPLidar2(min_angle=0.0, max_angle=360.0, min_distance=sys.float_info.min,
         max_distance=sys.float_info.max, forward_angle=0.0,
         angle_direction=CLOCKWISE, batch_ms=50, debug=False)
min_angle
float
default:"0.0"
Minimum angle to include (degrees, 0-360)
max_angle
float
default:"360.0"
Maximum angle to include (degrees, 0-360)
min_distance
float
default:"sys.float_info.min"
Minimum distance to include (millimeters)
max_distance
float
default:"sys.float_info.max"
Maximum distance to include (millimeters)
forward_angle
float
default:"0.0"
Angle that represents “forward” direction (degrees)
angle_direction
int
default:"CLOCKWISE"
Direction of angle increase: CLOCKWISE (1) or COUNTER_CLOCKWISE (-1)
batch_ms
float
default:"50"
Time in milliseconds to collect measurements in run() mode
debug
bool
default:"false"
Enable debug logging
Methods:
run
() -> list
Collect measurements for batch_ms duration and return list of (distance, angle, time, scan, index) tuples
run_threaded
() -> list
Return most recent measurements from background thread
update
() -> None
Continuously poll lidar in background thread
shutdown
() -> None
Stop lidar and release resources
Measurement Format: Each measurement is a tuple:
(distance, angle, timestamp, scan_number, index_in_scan)
  • distance: Distance in millimeters (float)
  • angle: Angle in degrees (float), adjusted for forward_angle
  • timestamp: Time in seconds (float)
  • scan_number: Full scan sequence number (int)
  • index_in_scan: Index within current scan (int)
Performance:
  • Scan rate: ~7 scans/second
  • Measurement rate: ~1850 measurements/second
Installation:
pip install Adafruit_CircuitPython_RPLIDAR
Usage Example:
from donkeycar.parts.lidar import RPLidar2, COUNTER_CLOCKWISE

lidar = RPLidar2(
    min_angle=0,
    max_angle=180,  # Front 180 degrees only
    max_distance=4000,  # 4 meters max
    forward_angle=0,
    angle_direction=COUNTER_CLOCKWISE,
    batch_ms=100
)

# Threaded mode
V.add(lidar, outputs=['lidar/measurements'], threaded=True)

RPLidar (Legacy)

Simpler RPLidar interface (deprecated, use RPLidar2). Constructor:
RPLidar(lower_limit=0, upper_limit=360, debug=False)
lower_limit
float
default:"0"
Lower angle limit (degrees)
upper_limit
float
default:"360"
Upper angle limit (degrees)
Methods:
run_threaded
() -> np.ndarray
Returns sorted distances as NumPy array

YDLidar

YDLidar X4 driver. Constructor:
YDLidar(port='/dev/ttyUSB0')
port
str
default:"/dev/ttyUSB0"
Serial port for lidar
Installation:
pip install PyLidar3
Methods:
run_threaded
() -> (distances, angles)
Returns tuple of (distance_list, angle_list)

Visualization

LidarPlot2

Plots lidar measurements as a polar plot image. Constructor:
LidarPlot2(resolution=(500,500), plot_type=PLOT_TYPE_CIRCLE, mark_px=3,
           max_dist=4000, angle_direction=COUNTER_CLOCKWISE, rotate_plot=0,
           background_color=(224, 224, 224), border_color=(128, 128, 128),
           point_color=(255, 64, 64))
resolution
tuple
default:"(500, 500)"
Output image size in pixels (width, height)
plot_type
int
default:"PLOT_TYPE_CIRCLE"
PLOT_TYPE_CIRCLE or PLOT_TYPE_LINE
mark_px
int
default:"3"
Size of measurement marks in pixels
max_dist
float
default:"4000"
Maximum distance to plot (millimeters)
angle_direction
int
default:"COUNTER_CLOCKWISE"
Direction of increasing angles
rotate_plot
float
default:"0"
Angle to rotate plot (degrees)
background_color
tuple
default:"(224, 224, 224)"
RGB background color
border_color
tuple
default:"(128, 128, 128)"
RGB border color
point_color
tuple
default:"(255, 64, 64)"
RGB measurement point color
Methods:
run
(measurements: list) -> PIL.Image
Convert measurements to polar plot image
Usage Example:
from donkeycar.parts.lidar import RPLidar2, LidarPlot2, COUNTER_CLOCKWISE

# Create lidar
lidar = RPLidar2(max_distance=4000)

# Create plotter
plotter = LidarPlot2(
    resolution=(640, 480),
    plot_type=LidarPlot2.PLOT_TYPE_CIRCLE,
    max_dist=4000,
    angle_direction=COUNTER_CLOCKWISE,
    background_color=(32, 32, 32),
    point_color=(0, 255, 0)
)

# Add to vehicle
V.add(lidar, outputs=['lidar/measurements'], threaded=True)
V.add(plotter, inputs=['lidar/measurements'], outputs=['lidar/plot'])

LidarPlot (Legacy)

Simpler plotting interface (deprecated, use LidarPlot2). Constructor:
LidarPlot(resolution=(500,500), max_dist=1000, radius_plot=3,
          plot_type=PLOT_TYPE_CIRC)

SLAM Mapping

BreezySLAM

Simple SLAM implementation for map building. Constructor:
BreezySLAM(MAP_SIZE_PIXELS=500, MAP_SIZE_METERS=10)
MAP_SIZE_PIXELS
int
default:"500"
Map resolution in pixels
MAP_SIZE_METERS
int
default:"10"
Map size in meters
Installation:
pip install breezyslam
Methods:
run
(distances, angles, map_bytes) -> (x, y, theta)
Update SLAM with new scan and return position estimate
Usage Example:
from donkeycar.parts.lidar import BreezySLAM, BreezyMap, MapToImage

# Create SLAM components
map_store = BreezyMap(MAP_SIZE_PIXELS=500)
slam = BreezySLAM(MAP_SIZE_PIXELS=500, MAP_SIZE_METERS=10)
map_img = MapToImage(resolution=(500, 500))

# Add to vehicle
V.add(map_store, outputs=['map/bytes'])
V.add(slam,
      inputs=['lidar/distances', 'lidar/angles', 'map/bytes'],
      outputs=['slam/x', 'slam/y', 'slam/theta'])
V.add(map_img,
      inputs=['map/bytes'],
      outputs=['map/image'])

BreezyMap

Map byte storage for BreezySLAM. Constructor:
BreezyMap(MAP_SIZE_PIXELS=500)
Methods:
run
() -> bytearray
Return map bytes

MapToImage

Converts SLAM map bytes to PIL image. Constructor:
MapToImage(resolution=(500, 500))
Methods:
run
(map_bytes: bytearray) -> PIL.Image
Convert map to image

Obstacle Detection

Simple Obstacle Detector

import numpy as np

class ObstacleDetector:
    def __init__(self, min_angle=45, max_angle=135, threshold_distance=500):
        """
        Detect obstacles in front of the vehicle.
        
        min_angle, max_angle: angle range to check (degrees)
        threshold_distance: obstacle distance threshold (mm)
        """
        self.min_angle = min_angle
        self.max_angle = max_angle
        self.threshold = threshold_distance
    
    def run(self, measurements):
        """
        Returns True if obstacle detected in range.
        """
        if not measurements:
            return False
        
        for distance, angle, *_ in measurements:
            if self.min_angle <= angle <= self.max_angle:
                if distance < self.threshold:
                    return True
        return False

# Add to vehicle
obstacle = ObstacleDetector(min_angle=60, max_angle=120, threshold_distance=500)
V.add(obstacle,
      inputs=['lidar/measurements'],
      outputs=['obstacle/detected'])

Configuration

Typical lidar configuration in myconfig.py:
# Lidar Settings
LIDAR_TYPE = 'rplidar'  # 'rplidar', 'ydlidar'
LIDAR_MIN_ANGLE = 0
LIDAR_MAX_ANGLE = 360
LIDAR_MIN_DISTANCE = 100  # mm
LIDAR_MAX_DISTANCE = 4000  # mm
LIDAR_FORWARD_ANGLE = 0
LIDAR_ANGLE_DIRECTION = -1  # COUNTER_CLOCKWISE

# Visualization
LIDAR_PLOT_RESOLUTION = (640, 480)
LIDAR_PLOT_TYPE = 'circle'  # 'circle' or 'line'

Integration Example

Complete Lidar Setup

from donkeycar.parts.lidar import RPLidar2, LidarPlot2, COUNTER_CLOCKWISE

# Create lidar
lidar = RPLidar2(
    min_angle=cfg.LIDAR_MIN_ANGLE,
    max_angle=cfg.LIDAR_MAX_ANGLE,
    min_distance=cfg.LIDAR_MIN_DISTANCE,
    max_distance=cfg.LIDAR_MAX_DISTANCE,
    forward_angle=cfg.LIDAR_FORWARD_ANGLE,
    angle_direction=cfg.LIDAR_ANGLE_DIRECTION,
    batch_ms=100
)

# Create plotter
plotter = LidarPlot2(
    resolution=cfg.LIDAR_PLOT_RESOLUTION,
    max_dist=cfg.LIDAR_MAX_DISTANCE,
    angle_direction=cfg.LIDAR_ANGLE_DIRECTION,
    background_color=(32, 32, 32),
    point_color=(0, 255, 0)
)

# Add to vehicle
V.add(lidar, outputs=['lidar/measurements'], threaded=True)
V.add(plotter, inputs=['lidar/measurements'], outputs=['lidar/plot'])

# Record lidar data
if cfg.RECORD_LIDAR:
    inputs.append('lidar/measurements')
    types.append('list')

Troubleshooting

Lidar Not Found

Symptoms: RuntimeError: “No RPLidar is connected” Solutions:
  1. Check USB connection: ls /dev/ttyUSB*
  2. Verify permissions: sudo chmod 666 /dev/ttyUSB0
  3. Check power supply (lidar needs 5V)

Serial Port Conflicts

Symptoms: SerialException Solutions:
  1. Close other programs using the port
  2. Unplug and replug USB
  3. Reboot

Poor Quality Scans

Symptoms: Noisy or incomplete scans Solutions:
  1. Clean lidar lens
  2. Avoid reflective surfaces
  3. Check motor speed (should be steady)
  4. Reduce ambient light

Performance Tips

  1. Use threading: Always run lidar in threaded mode
  2. Filter data: Use angle and distance filters to reduce data
  3. Batch processing: Adjust batch_ms for your needs
  4. Visualization: Reduce plot resolution for better performance

Requirements

# For RPLidar
pip install Adafruit_CircuitPython_RPLIDAR pyserial

# For YDLidar
pip install PyLidar3

# For SLAM
pip install breezyslam

# For plotting
pip install Pillow numpy

Build docs developers (and LLMs) love