lds2d

June 4, 2026 · View on GitHub

Python driver for 2D LiDARs. 23 models supported across LDROBOT, YDLIDAR, RPLIDAR, 3irobotix, Neato / Xiaomi, Camsense and Hitachi-LG — see the model table below.

Try it in 60 seconds - without any hardware

pip install 'lds2d[viz]'
lds2d viz --demo

and open http://localhost:8080.

Video: Xiaomi LDS02RR LiDAR Sensor Connected to Raspberry Pi 5

Xiaomi LDS02RR LiDAR Sensor Connected to Raspberry Pi 5

Read the intro: lds2d: one Python library for 2D LiDARs.

Install

pip install lds2d

# for host-driven-motor LiDARs (the Pi spins them) — see "Software setup" below:
pip install 'lds2d[pwm]'

# for the live browser visualizer:
pip install 'lds2d[viz]'

Quick start

from lds2d import Lidar

with Lidar.open("LDROBOT-LD14P", "/dev/serial0") as lidar:
    for scan in lidar.scans():            # one full rotation at a time
        pts = scan.valid_points
        print(f"{scan.scan_freq_hz:.1f} Hz  {len(pts)} points")
        for p in pts:
            print(p.angle_deg, p.dist_mm, p.quality)

Model names are MANUFACTURER-MODEL (e.g. YDLIDAR-X4, RPLIDAR-A1) — see the model table. Use lidar.points() to get a flat stream instead of grouped scans.

Motor control (LDROBOT-LD14P)

with Lidar.open("LDROBOT-LD14P", "/dev/serial0") as lidar:
    lidar.set_scan_freq(6)     # 2–8 Hz
    lidar.stop()               # stop the motor (data stream halts)
    lidar.start()              # spin back up
    print(lidar.get_scan_freq())

Xiaomi LDS02RR (host-driven motor)

The LDS02RR has no onboard motor controller — it only streams data while the host spins it at ~5 Hz. lds2d runs that PID + PWM loop for you: just iterating drives the motor, and leaving the with block stops it.

# needs the [pwm] extra and the Maker's Pet LDS02RR adapter
with Lidar.open("XIAOMI-LDS02RR", "/dev/serial0", pwm="software", pwm_pin=18) as lidar:
    for scan in lidar.scans():            # the motor is held at 5 Hz for you
        print(f"{scan.scan_freq_hz:.1f} Hz  {len(scan.valid_points)} points")

pwm="software" drives any GPIO via gpiozero (tested). pwm="hardware" uses Pi hardware PWM (pwm_channel/pwm_chip) and is supported but not yet hardware-verified. Tune with target_hz=, kp=, ki=, kd=.

The 3irobotix Delta-2A works the same way (it also needs host motor control — e.g. via the Maker's Pet driver board), just a different model name and a 6 Hz default:

with Lidar.open("3IROBOTIX-DELTA-2A", "/dev/serial0", pwm="software", pwm_pin=18) as lidar:
    for scan in lidar.scans():
        ...
# the 230400-baud Delta-2A variant: add baud=230400

Supported models

Open any of these with its name, e.g. Lidar.open("YDLIDAR-X4", "/dev/serial0"). Motor is how the LiDAR spins: onboard (self-spinning or started by a serial command — nothing extra needed), or host PWM (the Pi must drive the motor — needs the [pwm] extra and a driver board; lds2d runs the PID for you). HW marks whether the port has been confirmed on real hardware yet.

Modelopen(...) nameBaudMotorHW
LDROBOT LD14PLDROBOT-LD14P230400onboard (serial cmd)
LDROBOT LD19LDROBOT-LD19230400onboardspec¹
LDROBOT LD06LDROBOT-LD06230400onboardspec¹
LDROBOT STL19PLDROBOT-STL19P230400onboardspec¹
3irobotix Delta-2A3IROBOTIX-DELTA-2A115200host PWM
3irobotix Delta-2B3IROBOTIX-DELTA-2B230400host PWMspec¹
3irobotix Delta-2D3IROBOTIX-DELTA-2D115200host PWMspec¹
3irobotix Delta-2G3IROBOTIX-DELTA-2G115200host PWMspec¹
3irobotix LDS08RR3IROBOTIX-LDS08RR115200host PWMspec¹
Xiaomi LDS02RRXIAOMI-LDS02RR115200host PWM
Xiaomi LDS01RRXIAOMI-LDS01RR115200host PWMspec¹
Neato XV11NEATO-XV11115200host PWMspec¹
YDLIDAR X4YDLIDAR-X4128000onboardspec¹
YDLIDAR X2 / X2LYDLIDAR-X2115200onboardspec¹
YDLIDAR X3YDLIDAR-X3115200onboardspec¹
YDLIDAR X3-PROYDLIDAR-X3-PRO115200onboardspec¹
YDLIDAR X4-PROYDLIDAR-X4-PRO128000onboardspec¹
YDLIDAR SCLYDLIDAR-SCL115200onboardspec¹
YDLIDAR T-miniYDLIDAR-TMINI230400onboardspec¹
RPLIDAR A1RPLIDAR-A1115200onboard (serial cmd)spec¹
RPLIDAR C1RPLIDAR-C1460800onboard (serial cmd)spec¹
Camsense X1CAMSENSE-X1115200onboardspec¹
Hitachi-LG HLS-LFCD2 (TurtleBot3 LDS-01)HLS-LFCD2230400onboard (serial cmd)spec¹

lds2d.available_models() lists every accepted name.

¹ spec = faithfully ported from the kaiaai/LDS C++ and unit-tested against synthetic packets, but not yet confirmed on physical hardware. If you run one of these, a report (success or bug) is very welcome.

Command line

lds2d read                 # summarized: one line per full scan
lds2d read --raw           # one line per measurement
lds2d --port /dev/ttyUSB0 read
lds2d viz                  # live polar plot in your browser (needs [viz])

lds2d motor status
lds2d motor stop
lds2d motor start
lds2d motor speed 6        # set 6 Hz

# host-PWM models: read drives the motor (software PWM on GPIO18)
lds2d --model XIAOMI-LDS02RR --pwm software --pwm-pin 18 read

The read/motor commands default to LDROBOT-LD14P; pass --model for others.

Live visualizer

lds2d viz shows a live polar plot you can open in any browser on your network — no GUI on the Pi required.

With a real sensor attached:

lds2d viz                                   # LDROBOT-LD14P on /dev/serial0, port 8080
lds2d --model XIAOMI-LDS02RR --pwm software viz    # host-driven-motor models work too
lds2d viz --port 9000

(lds2d read --demo prints the same synthetic scans as text, no browser needed.)

Then open http://<your-pi>:8080. Points are coloured by signal strength and the range ring auto-scales to the room; the HUD shows the live scan rate and point count. Under the hood it's a background reader thread feeding a thread-safe latest-scan buffer that a tiny Flask app exposes as JSON — and like every other moving part in lds2d, the buffer and scan-to-JSON conversion are unit-tested without any hardware.

from lds2d import Lidar
from lds2d.viz import serve

with Lidar.open("LDROBOT-LD14P", "/dev/serial0") as lidar:
    serve(lidar, port=8080)

Wiring & Setup (Linux PC)

Self-spinning LiDARs (LDROBOT, YDLIDAR, RPLIDAR, Camsense, Hitachi-LG) need only a serial or USB-to-serial port. Connect as follows:

  • LiDAR TX to serial RX
  • LiDAR RX (if available) to serial TX
  • GND to GND
  • 5V to 5V power

The LiDAR 5V current supply ranges from 0.3 to 1A peak depending on LiDAR model.

Wiring & setup (Raspberry Pi)

The wiring is as follows:

  • LiDAR 5V → Raspberry Pi header Pin2
  • LiDAR GND → Pin6
  • LiDAR TX → GPIO15/Pin10 (reading)
  • LiDAR RX → GPIO14/Pin8 (motor stop, start, speed)

LiDAR logic is typically 3.3V except old Neato - no level shifter.

Alternatively, instead of the serial port available on the Raspberry Pi's header, you can use a USB-to-Serial adapter - see the Linux PC wiring instructions above.

See these step-by-step tutorials for wiring illustrations:

Host-driven-motor low-cost LiDARs (3irobotix Delta-*, Xiaomi LDS02RR / LDS01RR, Neato XV11) require GPIO to operate in addition to a serial or USB-to-serial port. Therefore, host-driven-motor LiDARs require a Linux device with GPIO - like Raspberry Pi.

Host-driven-motor LiDARs require one GPIO connection:

  • LiDAR MOT+, MOT- → PWM-to-motor-driver adapter → GPIO18/Pin12

The PWM-to-motor-driver adapter for host-driven-motor LiDARs is a simple circuit - see these step-by-step tutorials:

Software setup

Enable the serial port (once): sudo raspi-configInterface Options → Serial Portlogin shell over serial? No, serial hardware enabled? Yes, then reboot. The LiDAR then appears at /dev/serial0. Your user needs the dialout group to open the port (and gpio to drive the motor) — both are default for the standard Pi user; otherwise sudo usermod -aG dialout,gpio $USER and log back in.

Self-spinning LiDARs — LDROBOT, YDLIDAR, RPLIDAR, Camsense, Hitachi-LG — need nothing else:

python3 -m venv ~/lidar && source ~/lidar/bin/activate
pip install lds2d
lds2d --model YDLIDAR-X4 read

Host-driven-motor low-cost LiDARs — 3irobotix Delta-*, Xiaomi LDS02RR / LDS01RR, Neato XV11 — let the Pi spin the motor over a GPIO with gpiozero, which on the Pi 5 talks through the lgpio backend. Both ship with Raspberry Pi OS as python3-gpiozero / python3-lgpio. Two snags to avoid: recent Raspberry Pi OS blocks pip from installing into the system Python (PEP 668), and the lgpio wheel won't build from PyPI without swig. The painless way is a venv that can see the system GPIO packages:

sudo apt install -y python3-gpiozero python3-lgpio   # usually already present
python3 -m venv --system-site-packages ~/lidar
source ~/lidar/bin/activate
pip install lds2d
lds2d --model 3IROBOTIX-DELTA-2A --pwm software --pwm-pin 18 read

--system-site-packages lets the venv use the system gpiozero/lgpio while lds2d and pyserial come from PyPI. If you're using a fully isolated venv instead, install the build tools first — sudo apt install -y swig python3-dev — then pip install 'lds2d[pwm]' lgpio builds the backend inside it.

The motor PWM pin is --pwm-pin on the CLI / pwm_pin= in Python (default GPIO18). --pwm hardware uses the Pi's hardware PWM instead — cleaner, but needs a dtoverlay=pwm-2chan line in /boot/firmware/config.txt (on the Pi 5 also --pwm-chip 2).

Extending

A driver subclasses LidarDriver, implements _packets() (yielding (scan_freq_hz, [ScanPoint, ...])), and registers itself:

from lds2d.core import LidarDriver, ScanPoint, register

@register("MY_MODEL")
class MyModel(LidarDriver):
    DEFAULT_BAUD = 115200
    def _packets(self):
        ...

points() and scans() come for free. The transport is any object with read(n) / write(data) / close(), so drivers are unit-tested against recorded byte streams — no hardware required (see tests/).

Development

pip install -e ".[dev]"
pytest

Revision History

  • 0.6.0: added lds2d viz --demo mode
  • 0.5.0: initial release

Acknowledgements

lds2d is a Pythonic port of the kaiaai/LDS C++ library.

License

Apache License 2.0 — see LICENSE.