Perception guide

LiDAR on Unitree Robots — Working Guide

Which LiDAR ships on which platform, how the point cloud reaches your code, ROS 2 and SDK access paths, and the mapping stacks that work.

FACTS VERIFIED 6 AUG 2026 · 15 MIN READ

Reference, not instruction. Confidence tags mark how each claim was established: VerifiedReportedInferredNot established. Firmware and SDKs move: re-verify anything you are about to act on against the code you have. These are heavy machines, see the Product Safety Notice.

<!-- KB doc 08 | key: guide-lidar | facets: lidar-sensor, official-ros2, dev-onboarding -->

Status: 2026-07-16 accuracy pass — primary LiDAR IP flipped to .20 per KB 25 (2026-07-14); .120 demoted to legacy/minority; rt/utlidar/* claims downgraded to GO2-verified (G1 presence = open action item, KB 25 §0 item 3).
TL;DR / Quick Answer - The G1 EDU 3D LiDAR is a Livox Mid-360 (360° horizontal, up to 59° vertical FOV, ~200k pts/sec, ~10 Hz) in the head, alongside an Intel RealSense D435i. It is standard on the high-compute EDU config (not a Unitree Unilidar L1/L2, not a Livox HAP). - It lives on the robot's internal 192.168.123.0/24 subnet at IP 192.168.123.120 (OFFICIAL primary per Unitree docs 11/733 upd 2026-06 + 11/266/11/734; the .20 value was a secondary 'Weston table' claim, now demoted — KB 31 §5 — see the IP caveat below). Confirm by ping on the unit. - The Mid-360 is mounted upside-down, so you must set extrinsic roll = 180.0 in the Livox driver config. - Two access paths: 1. RAW cloud — run the official Livox stack (Livox-SDK2 + livox_ros_driver2) yourself; publishes /livox/lidar + /livox/imu. Requires editing MID360_config.json (lidar IP .20, host IP = your 123 address, roll 180). 2. PROCESSED cloud — subscribe to Unitree's onboard LiDAR service, which republishes derived clouds on DDS topics rt/utlidar/* (e.g. voxel_map, height_map, range_map, cloud_deskewed). Reachable from ROS2 via unitree_ros2 over CycloneDDS. ⚠️ This topic set is VERIFIED on the GO2; whether the G1 mirrors it is an OPEN action item (KB 25 §0 item 3) — do not treat it as an established G1 path. - The Unitree unitree_ros2 bridge does NOT itself publish the LiDAR or camera — those come from separate drivers. The bridge only makes you a native DDS participant so you can see the rt/utlidar/* topics the onboard service emits. - Important: unitree_ros2 provides the DDS plumbing for the processed path. The raw-driver path is independent.

---

1. Background: which LiDAR, where it is, how it's mounted

The 3D LiDAR option on the G1 EDU is a Livox Mid-360, mounted in the head together with a RealSense D435i depth camera. It is described as standard on the EDU / high-compute configuration per the official manual. Key sensor characteristics from the research:

  • 360° horizontal FOV, up to 59° vertical FOV
  • ~200,000 points/second
  • ~10 Hz frame rate

Sources: Unitree LiDAR service doc, deepglint FAST_LIO_LOCALIZATION_HUMANOID, quadruped.de G1 ROS2 driver.

Mounting orientation (critical)

The Mid-360 is mounted upside-down on the G1. You must set the extrinsic roll = 180.0 in livox_ros_driver2/config/MID360_config.json. This is confirmed by the deepglint G1-specific localization pipeline, which is pre-adapted for the upside-down G1 Mid-360 and bundles a modified livox_ros_driver2.

⚠️ Needs verification: The default Livox driver only lets you flip the point-cloud extrinsics, not the IMU, without modification. If you need a correctly oriented LiDAR IMU (e.g. for FAST-LIO), prefer the deepglint G1-adapted driver/config rather than the stock one. (Flagged in research as a known driver limitation.)

Source: deepglint FAST_LIO_LOCALIZATION_HUMANOID.

---

2. Network map and prerequisites

The robot runs an isolated 192.168.123.0/24 LAN with no DHCP. You must statically configure any PC you connect.

DeviceIPNotes
Livox Mid-360 LiDAR192.168.123.120LiDAR sensor on the internal subnet (OFFICIAL primary per Unitree docs; .20 = demoted secondary)
MCU / locomotion-control unit (PC1)192.168.123.161Unitree-reserved; not user-accessible
Development computing unit / Jetson Orin (PC2)192.168.123.164Your dev target; SSH here
Your dev PCstatic 192.168.123.x (not equal to the above)e.g. .99, .222, .199; netmask 255.255.255.0; no DHCP

Physical connection: use Ethernet port 4 or 5 on the robot's interface board (both bridged to the same internal LAN). SSH into the Orin with ssh unitree@192.168.123.164 (documented default password 123).

⚠️ LiDAR IP — OFFICIAL primary, pending on-unit confirm (corrected 2026-07-16, KB 31 §5): the G1's Mid-360 primary IP is 192.168.123.120 (host_ip = PC2 .164), used throughout this guide and in the Livox config below — this is what the official Unitree docs (lidar service 11/733 upd 2026-06-04, lidar routine 11/266, SLAM 11/734) state. The 192.168.123.20 value (a secondary "Weston table" claim briefly promoted 2026-07-16 AM) is demoted. Still confirm the actual LiDAR IP on the unit (ping, or check the running Unitree LiDAR service config) before committing to MID360_config.json. Note: units built after April 2026 use Mid360s.

Sources: Weston Robot G1 dev guide, quadruped.de G1 ROS2 driver, unitree_sdk2_python issue #43.

---

3. Two ways to get the point cloud

There are two distinct paths. Choose based on whether you want the unprocessed sensor stream or Unitree's already-processed maps.

Path A — RAW cloud via the Livox stack (you run the driver)

You run Livox-SDK2 (C/C++) plus livox_ros_driver2 (ROS2 Humble). The driver supports HAP + Mid-360 only and publishes:

  • /livox/lidar — the point cloud
  • /livox/imu — the LiDAR's IMU

This is the right path if you want to feed a SLAM/LIO pipeline (e.g. FAST-LIO) or need control over the point-cloud format and frequency.

Path B — PROCESSED cloud via Unitree's onboard LiDAR service (DDS)

Unitree's onboard LiDAR service republishes derived clouds on DDS topics under rt/utlidar/*.

⚠️ G1 status downgraded (KB 25, 2026-07): the rt/utlidar/* republished topic set below is VERIFIED on the GO2. Whether the G1's onboard service mirrors these topics is an OPEN action item (KB 25 §0 item 3) — verify with ros2 topic list on the unit before building on this path.

From the research (GO2-verified set):

DDS topicMessage type
rt/utlidar/voxel_mapsensor_msgs::msg::dds_::PointCloud2_
rt/utlidar/height_mapsensor_msgs::msg::dds_::PointCloud2_
rt/utlidar/range_mapsensor_msgs::msg::dds_::PointCloud2_
rt/utlidar/cloud_deskewedsensor_msgs::msg::dds_::PointCloud2_
rt/utlidar/robot_odomnav_msgs Odometry

These are exposed through unitree_sdk2 / unitree_sdk2_python / unitree_ros2. In ROS2, the rt/ DDS prefix is stripped, so e.g. rt/utlidar/cloud_deskewed appears to ROS2 as utlidar/cloud_deskewed. The research also notes Unitree's own utlidar publishes a cloud (e.g. utlidar/cloud) in frame utlidar_lidar.

⚠️ Needs verification: The exact list and field layout of the rt/utlidar/* topics, and the precise frame names, come from discovery-level findings and the official Unitree LiDAR pages, which are JS-rendered SPAs that could not be scraped (LiDAR_service, G1 lidar_services_interface). Open these pages in a real browser to confirm the authoritative topic list, message definitions, and the subscribe API before depending on them.
⚠️ Needs verification — service contention: Running livox_ros_driver2 directly (Path A) may require first stopping Unitree's own LiDAR service that binds the sensor — analogous to stopping the sport/loco service before low-level joint control. This is unverified because Unitree's LiDAR_service doc is a JS-rendered SPA that could not be read this pass. If the raw driver reports the sensor is busy / no cloud appears, suspect the onboard service is holding the device.

Sources: Unitree LiDAR service doc, G1 lidar_services_interface, unitree_ros2, Livox-SDK2, livox_ros_driver2.

---

4. Path A — Raw Livox Mid-360 cloud (step by step)

4.1 Build the driver

livox_ros_driver2 builds for ROS2 Humble. The documented build invocation is:

# inside the livox_ros_driver2 source tree, after installing Livox-SDK2
./build.sh humble

If the Orin's ROS2/Ubuntu version cannot match livox_ros_driver2, fall back to the lower-level Livox-SDK2 (C/C++) directly.

Sources: livox_ros_driver2 README, Livox-SDK2.

4.2 Edit MID360_config.json (mandatory — stock ships wrong IPs)

The stock file ships 192.168.1.x placeholders and must be edited or no cloud appears. Required edits for the G1:

  • lidar_configs[].ip must = 192.168.123.120 (the Mid-360's IP per OFFICIAL Unitree docs — see the IP caveat in §2; .20 was a demoted secondary claim).
  • host_net_info cmd / push / point / imu IPs must = the host's 192.168.123.x address (i.e. the Orin's 123 IP, since the driver typically runs on PC2).
  • Set the Mid-360 extrinsic roll = 180.0 (upside-down mount).

Config file reference: config/MID360_config.json.

4.3 Ports

From the research (Livox networking):

  • LiDAR ports: 56100 / 56200 / 56300 / 56400
  • Host ports: LiDAR port +1
  • Discovery port: 56000

4.4 Output format and frequency knobs

livox_ros_driver2 exposes:

  • xfer_format:
    • 0 = Livox PointXYZRTLT PointCloud2
    • 1 = Livox CustomMsg (for FAST-LIO)
    • 2 = PCL PointCloud2
  • publish_freq: default 10 Hz

Published topics: /livox/lidar and /livox/imu.

Source: livox_ros_driver2 README.

4.5 G1-specific shortcut: use the deepglint pipeline

The deepglint FAST_LIO_LOCALIZATION_HUMANOID repo is pre-adapted for the upside-down G1 Mid-360. It ships a G1-adapted MID360_config.json and launch files (roll = 180 already set) plus a full LIO/localization pipeline and a modified livox_ros_driver2. If you want a working cloud + localization quickly on the real robot, start from this rather than the stock Livox config.

---

5. Path B — Processed clouds via ROS2 / CycloneDDS

To receive the rt/utlidar/* topics in ROS2, you make ROS2 a native CycloneDDS participant on the robot's subnet using unitree_ros2. There is no translation/bridge node — both Unitree's SDK2 and ROS2 ride on DDS, and forcing ROS2 to use rmw_cyclonedds_cpp on the same network lets ROS2 talk directly to the robot's internal DDS topics. ROS2 strips the rt/ prefix (so rt/foofoo).

5.1 The entire bridge is two environment variables

#!/bin/bash
echo "Setup unitree ros2 environment"
source /opt/ros/foxy/setup.bash
source $HOME/unitree_ros2/cyclonedds_ws/install/setup.bash
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
export CYCLONEDDS_URI='<CycloneDDS><Domain><General><Interfaces>
                            <NetworkInterface name="enp3s0" priority="default" multicast="default" />
                        </Interfaces></General></Domain></CycloneDDS>'
# NOTE: change /opt/ros/foxy -> /opt/ros/humble on Ubuntu 22.04, and
# replace enp3s0 with your actual NIC connected to the robot (192.168.123.x).

Source: setup.sh.

  • Supported distros: Ubuntu 22.04 + Humble (recommended), or Ubuntu 20.04 + Foxy.
  • The NIC name (enp3s0 placeholder) must match your real wired interface in both the URI and your network config, and that interface must sit on 192.168.123.x. If the NIC name is wrong, CycloneDDS binds the wrong interface and you see nothing from the robot.

5.2 Build flow

# Dependencies (Foxy shown; use ros-humble-* on 22.04)
sudo apt install ros-foxy-rmw-cyclonedds-cpp ros-foxy-rosidl-generator-dds-idl libyaml-cpp-dev

# (Foxy only) build CycloneDDS first — SKIP this whole step on Humble
cd ~/unitree_ros2/cyclonedds_ws/src
git clone https://github.com/ros2/rmw_cyclonedds -b foxy
git clone https://github.com/eclipse-cyclonedds/cyclonedds -b releases/0.10.x
cd .. && colcon build --packages-select cyclonedds

# Build message packages
source /opt/ros/foxy/setup.bash
cd ~/unitree_ros2 && colcon build

# Verify the robot's topics are visible on ROS2
source ~/unitree_ros2/setup.sh
ros2 topic list           # expect lowstate/lowcmd (G1), sportmodestate, etc.

Source: unitree_ros2 README.

Humble shortcut (official): the README's CycloneDDS clone/build section literally says "(If using Humble, this step can be skipped)" — Humble already ships a compatible CycloneDDS (the robot uses CycloneDDS 0.10.2). On Humble, just sudo apt install ros-humble-rmw-cyclonedds-cpp and set the two exports.
Foxy build gotcha: comment out source /opt/ros/foxy/setup.bash in ~/.bashrc before building CycloneDDS (a sourced ROS2 env breaks the cyclonedds compile). If the build fails, run export LD_LIBRARY_PATH=/opt/ros/foxy/lib first.

5.3 Visualize / consume

Once setup.sh is sourced, standard ROS2 tooling works unchanged: ros2 topic list / ros2 topic echo, rosbag, and rviz2 for the LiDAR cloud (the research cites utlidar/cloud and RealSense point clouds as RViz-displayable). Sensor fusion combines these external/derived topics with the unitree_hg lowstate (IMU, joints) from the same DDS bus.

5.4 CycloneDDS on the Orin may need a rebuild (for unitree_hg)

If you also intend to read the G1's unitree_hg state topics from the Orin (relevant for fusing LiDAR with body state/IMU), note the documented gotcha: the stock CycloneDDS on the Orin does not support the unitree_hg IDL and must be rebuilt. The Python SDK pins cyclonedds==0.10.2:

# G1 low-level uses the unitree_hg IDL, which the default Orin CycloneDDS may not support.
cd ~
git clone https://github.com/eclipse-cyclonedds/cyclonedds -b releases/0.10.x
cd cyclonedds && mkdir build install && cd build
cmake .. -DCMAKE_INSTALL_PREFIX=../install
cmake --build . --target install
# then point the python SDK at it:
cd ~/unitree_sdk2_python
export CYCLONEDDS_HOME=~/cyclonedds/install
pip3 install -e .

Source: unitree_sdk2_python, unitree_sdk2_python issue #43.

---

6. Important: unitree_ros2 does NOT publish the LiDAR or camera

This is the single most common misconception. The unitree_ros2 repo provides the unitree_go / unitree_hg / unitree_api message packages and the CycloneDDS plumbing — it covers joints, IMU, BMS/battery, hand, and mainboard thermals. It does not publish a RealSense or Livox topic itself.

  • The head RealSense D435i is published by realsense2_camera (rs_launch.py, enable pointcloud) — see realsense-ros.
  • The Livox Mid-360 raw cloud comes from livox_ros_driver2 / unilidar_sdk2 (Path A).
  • The processed rt/utlidar/* clouds come from Unitree's onboard LiDAR service, which you see via the unitree_ros2 DDS plumbing (Path B) — but they are emitted by the onboard service, not by unitree_ros2.

So do not expect ros2 topic list to show /camera or /livox/* topics merely from sourcing setup.sh. Those require the separate drivers running (typically on the Jetson Orin / PC2).

Sources: unitree_ros2 README, DeepWiki G1 overview.

---

7. Choosing a path — quick decision guide

  • Want the unprocessed cloud / building your own SLAM or LIO → Path A (Livox stack). Use the deepglint G1-adapted config to handle the upside-down mount and IMU.
  • Want Unitree's ready-made maps (voxel/height/range/deskewed) with minimal setup → Path B (subscribe to rt/utlidar/* over the unitree_ros2 CycloneDDS bridge).
  • Either way: set static IP on your PC (no DHCP), confirm the actual LiDAR IP on your unit, and be ready to stop the onboard LiDAR service if you run the raw driver and the device is busy.

---

8. Open questions / things to verify on your unit

⚠️ Needs verification — carried directly from research open questions and low-confidence flags: - LiDAR IP: primary = 192.168.123.120 per OFFICIAL Unitree docs (2026-06); 192.168.123.20 (secondary 'Weston table') demoted — KB 31 §5. Confirm by ping on the unit before relying on it. - rt/utlidar/* on the G1: the republished topic set is verified on the GO2 only; whether the G1 mirrors it is an open action item (KB 25 §0 item 3). - Onboard LiDAR service stop procedure: whether/how you must stop Unitree's LiDAR service before running livox_ros_driver2 directly. Unconfirmed (official LiDAR_service page is a JS-rendered SPA). Open it in a browser. - Authoritative rt/utlidar/* topic list, message field names, and the subscribe API: confirm via the JS-rendered G1 lidar_services_interface page in a real browser. - IMU extrinsic flip: stock livox_ros_driver2 flips point-cloud extrinsics but not the IMU without modification; prefer the deepglint driver if you need a correct LiDAR IMU.

---

Sources

---

<!-- iteration-2 deep-read appendix: lidar -->

Livox LiDAR on the Unitree G1 (Mid-360)

Model and placement

The G1's 3D LiDAR is the Livox Mid-360, integrated (mounted upside-down) in the robot's head next to the Intel RealSense D435i. It is standard on G1 EDU units (U1 EDU Standard, U2 EDU Plus) and is launched as part of the default G1 bringup; reseller comparison charts list it across G1 configurations. Key specs: 360 deg horizontal FOV, 59 deg vertical (-7 deg to +52 deg), ~200,000 pts/s, built-in IMU (~200 Hz), ~10 Hz native point-cloud frame rate, 100BASE-TX Ethernet, ~65x65x60 mm / ~265 g.

Network configuration (robot subnet 192.168.123.0/24)

ItemValue
LiDAR static IP192.168.123.120 (OFFICIAL primary per Unitree docs 2026-06; .20 = demoted secondary — confirm by ping on our unit)
Host / onboard PC IPany 192.168.123.x (deepglint G1 config uses 192.168.123.222)
Subnet mask255.255.255.0
Device portscmd 56100, push 56200, point 56300, imu 56400, log 56500
Host portscmd 56101, push 56201, point 56301, imu 56401, log 56501
Head-mount extrinsicroll = 180.0 (LiDAR is inverted), pitch/yaw/x/y/z = 0

The Mid-360 is static-IP only (no DHCP); the host NIC must be manually set to a 192.168.123.x address.

Two ways to read the cloud onboard

Path A - Livox driver (raw, recommended for SLAM): run livox_ros_driver2 (ROS2), which UDP-connects to 192.168.123.120 and publishes:

  • /livox/lidar - sensor_msgs/PointCloud2 (xfer_format 0/2) or livox_ros_driver2/CustomMsg (xfer_format 1)
  • /livox/imu - sensor_msgs/Imu

Path B - Unitree DDS service (pre-processed): Unitree's onboard LiDAR service republishes maps over DDS: rt/utlidar/cloud, rt/utlidar/voxel_map, rt/utlidar/voxel_map_compressed, rt/utlidar/height_map, rt/utlidar/range_map, rt/utlidar/range_info. ⚠️ This topic set is VERIFIED on the GO2; G1 presence is an OPEN action item (a related reference §0 item 3) — check ros2 topic list on the unit before depending on it. In RViz use point-cloud topic utlidar/cloud with Fixed Frame utlidar_lidar. Don't run both against the same device simultaneously (UDP port contention).

Setup (Path A, ROS2 Humble)

  1. Install Livox-SDK2 (Livox-SDK/Livox-SDK2) first - the ROS2 driver depends on it.
  2. Clone Livox-SDK/livox_ros_driver2, then source /opt/ros/humble/setup.sh && ./build.sh humble (use ROS2 for Foxy).
  3. Edit config/MID360_config.json: set lidar_configs[].ip = 192.168.123.120, set all host_net_info IPs to your NIC (e.g. PC2 = 192.168.123.164, or deepglint's 192.168.123.222), and set extrinsic_parameter.roll = 180.0.
  4. source install/setup.sh && ros2 launch livox_ros_driver2 msg_MID360_launch.py (or rviz_MID360_launch.py).
  5. Verify: ping 192.168.123.120 (if no reply, also try .20), ros2 topic hz /livox/lidar (~10 Hz), ros2 topic echo /livox/imu --once.

Common pitfalls

  • Wrong subnet (stock config ships 192.168.1.x) -> driver publishes nothing. Always re-IP to 192.168.123.x.
  • Missing roll=180 -> flipped cloud + inverted IMU gravity, breaks FAST-LIO/Point-LIO.
  • xfer_format mismatch -> SLAM nodes often need CustomMsg (1); PCL tools need PointCloud2 (2).
  • Forgetting to build Livox-SDK2 before the ROS2 driver -> link failure.

---

G1 SLAM & NAVIGATION SERVICE (slam_operate) — an under-used control plane on the G1

(added 2026-07-22 from the fleet-SDK audit; official doc re-published 2026-07-20)

The gap: this reference documented slam_operate only for the A2 quadruped. It is an official,

fully-specified G1 service — and the G1 already carries the Livox Mid-360 it needs. Note:

been treating the lidar as a sensor while an autonomy interface sat unused.

  • Service: slam_operate v1.0.0.1, JSON request/response over unitree_sdk2 RPC.
  • API ids observed in the official doc: 1801, 1802, 1804, 1102, 1201, 1202, 1901.
  • Topics: seven rt/unitree/slam_* plus rt/slam_info.
  • rt/slam_info also carries CPU / battery / motor-temperature fields → a useful independent

health probe. ⚠️ Not available during a SONIC show (stock services released).

PRECONDITIONS (add to the pre-flight checklist, a related reference):

  1. unitree_slam and lidar_driver services must be ON in the Unitree App before any

slam_operate call.

  1. App navigation must be closed — App navigation and SDK navigation are mutually exclusive.
  2. Same balance-owner law as everything else: SLAM navigation commands motion, so SKILL §3.1

applies in full (confirmed balance controller, tiered first-run gate per §3.2-A = GANTRY).

Related repos found in the same audit (record before use):

  • unitreerobotics/unitree_slam — waypoint-graph API (QtNode / QtEdge / QtCommand) + examples.
  • ⚠️ LICENCE BLOCKER: two of the three newly-published navigation repos ship no LICENSE file

= default all-rights-reserved = not usable commercially. Verify the licence file exists before

any client work; record the check in a related reference's licence table.

A THIRD control plane exists (a DDS + WebRTC model only had DDS + WebRTC): an **HTTP REST API on port

1448** (slamware-style navigation). Add to the control-planes table in a related reference §1 / a related reference §3:

PlaneTransportScopeGate
DDS / CycloneDDSrt/* on 192.168.123.xlow-level + servicesEDU-gated for low level
WebRTC app protocolapp / legion1581 drivernon-EDU consumer unitsapp pairing
HTTP REST :1448slamware navigationmapping/navigationverify per unit

Verdict: VALUABLE, NOT URGENT. It is the natural path to autonomous booth/patrol behaviour

(cf. a related reference §8 GO2 patrol stack). Do not touch it inside an event week.

Maintained by MCM Robotics, the official Unitree Robotics distributor for South Africa. This is our own engineering documentation: it is not Unitree's official documentation and is not endorsed by Unitree. Trademarks belong to their respective owners. Provided as-is, without warranty. Found an error? Tell us and we will correct it.

Buying or specifying

We import, commission and support these robots.

Official distributor for South Africa. Talk to the people who wrote this page.

View the fleet Talk to us →