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)
Minimum angle to include (degrees, 0-360)
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)
Angle that represents “forward” direction (degrees)
Direction of angle increase: CLOCKWISE (1) or COUNTER_CLOCKWISE (-1)
Time in milliseconds to collect measurements in run() mode
Methods:
Collect measurements for batch_ms duration and return list of (distance, angle, time, scan, index) tuples
Return most recent measurements from background thread
Continuously poll lidar in background thread
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 angle limit (degrees)
Upper angle limit (degrees)
Methods:
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:
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
Size of measurement marks in pixels
Maximum distance to plot (millimeters)
angle_direction
int
default:"COUNTER_CLOCKWISE"
Direction of increasing angles
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)
Installation:
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:
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:
- Check USB connection:
ls /dev/ttyUSB*
- Verify permissions:
sudo chmod 666 /dev/ttyUSB0
- Check power supply (lidar needs 5V)
Serial Port Conflicts
Symptoms: SerialException
Solutions:
- Close other programs using the port
- Unplug and replug USB
- Reboot
Poor Quality Scans
Symptoms: Noisy or incomplete scans
Solutions:
- Clean lidar lens
- Avoid reflective surfaces
- Check motor speed (should be steady)
- Reduce ambient light
- Use threading: Always run lidar in threaded mode
- Filter data: Use angle and distance filters to reduce data
- Batch processing: Adjust batch_ms for your needs
- 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