Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions dimos/mapping/utils/cli/map.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,16 @@ def _accumulate(
block_count: int,
device: str,
graph: PoseGraph | None = None,
world_frame: bool = False,
world_frame: bool = True,
carve_columns: bool = False,
progress_cb: Callable[[Observation[Any]], None] | None = None,
) -> PointCloud2 | None:
"""Accumulate a voxel map from `obs_iter`, optionally PGO-correcting each frame.

By default each frame's per-frame pose is applied to register the (sensor/body
frame) cloud into the world. Set ``world_frame=True`` (the ``--go2`` path) when
the clouds are already world-registered (e.g. fastlio) — then only the PGO
correction is applied, if any.
By default the clouds are assumed already world-registered (the go2/fastlio
path) — only the PGO correction is applied, if any. Set ``world_frame=False``
(the ``--use-tf`` path) when each frame's cloud is in the sensor/body frame
and must be registered into the world via its per-frame pose.

Returns the final ``PointCloud2`` (or ``None`` if the input was empty).
Disposal of the underlying ``VoxelGrid`` is handled by ``VoxelMapTransformer``.
Expand All @@ -133,7 +133,7 @@ def prepared() -> Iterable[Observation[PointCloud2]]:
if len(obs.data) == 0:
continue
# body->world via the per-frame pose, unless the clouds are already
# world-registered (--go2). graph adds the PGO correction on top
# world-registered (go2 default). graph adds the PGO correction on top
# (correction ∘ pose), applied after the pose.
tf: Transform | None = None
if not world_frame:
Expand Down Expand Up @@ -328,11 +328,11 @@ def main(
None, "--out", help="Output .rrd path (default: ./<dataset>.rrd)"
),
no_gui: bool = typer.Option(False, "--no-gui", help="Write the .rrd but don't launch rerun"),
go2: bool = typer.Option(
use_tf: bool = typer.Option(
False,
"--go2",
help="Clouds are already world-registered (e.g. fastlio); skip applying the "
"per-frame pose. Default registers each (body-frame) cloud by its pose.",
"--use-tf",
help="Clouds are in the sensor/body frame; register each by its per-frame pose. "
"By default clouds are assumed already world-registered (e.g. go2/fastlio).",
),
Comment thread
jeff-hykin marked this conversation as resolved.
carve: bool = typer.Option(
False,
Expand Down Expand Up @@ -470,7 +470,7 @@ def main(
block_count=block_count,
device=device,
graph=graph,
world_frame=go2,
world_frame=not use_tf,
carve_columns=carve,
progress_cb=progress(n_kept, "pgo pass 2 (rebuilding)"),
)
Expand All @@ -484,7 +484,7 @@ def main(
block_count=block_count,
device=device,
graph=graph,
world_frame=go2,
world_frame=not use_tf,
carve_columns=carve,
progress_cb=progress(total, "full pgo (rebuilding)"),
)
Expand All @@ -495,7 +495,7 @@ def main(
voxel=voxel,
block_count=block_count,
device=device,
world_frame=go2,
world_frame=not use_tf,
carve_columns=carve,
progress_cb=progress(n_kept, "reconstructing global map"),
)
Expand Down
14 changes: 13 additions & 1 deletion dimos/robot/unitree/go2/blueprints/smart/unitree_go2.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
from dimos.mapping.costmapper import CostMapper
from dimos.mapping.relocalization.module import RelocalizationModule
from dimos.mapping.voxels import VoxelGridMapper
from dimos.memory2.module import Recorder, RecorderConfig
from dimos.memory2.module import Recorder, RecorderConfig, pose_setter_for
from dimos.msgs.geometry_msgs.Pose import Pose
from dimos.msgs.geometry_msgs.PoseStamped import PoseStamped
from dimos.msgs.sensor_msgs.Image import Image
from dimos.msgs.sensor_msgs.PointCloud2 import PointCloud2
Expand Down Expand Up @@ -58,6 +59,17 @@ class Go2Memory(Recorder):
odom: In[PoseStamped]
config: Go2MemoryConfig

_last_odom_pose: Pose | None = None

@pose_setter_for("odom")
def _odom_pose(self, msg: PoseStamped) -> Pose | None:
self._last_odom_pose = msg

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is why I'm not fond of too much magic. My understanding is that _odom_pose is never called directly, instead @pose_setter_for registers the method to be used in a subscription, so _odom_pose and _lidar_pose can be called from different threads meaning self._last_odom_pose is not safe to use without a lock.

If we used async we wouldn't have to worry so much about concurrent read/writes.

Also, what does odom alwyas wins the race mean? How can you guarantee that? Is it because odom runs at a higher frequency?

return self._last_odom_pose

@pose_setter_for("lidar")
def _lidar_pose(self, msg: PointCloud2) -> Pose | None:
return self._last_odom_pose # should always exist (odom alwyas wins the race)


unitree_go2_markers = (
autoconnect(
Expand Down
10 changes: 9 additions & 1 deletion docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,15 @@
{
"group": "Quadruped",
"pages": [
"platforms/quadruped/go2/index"
{
"group": "Unitree Go2",
"pages": [
"platforms/quadruped/go2/index",
"platforms/quadruped/go2/setup",
"platforms/quadruped/go2/simulation",
"platforms/quadruped/go2/premap"
]
}
]
},
{
Expand Down
151 changes: 10 additions & 141 deletions docs/platforms/quadruped/go2/index.md
Original file line number Diff line number Diff line change
@@ -1,150 +1,19 @@
# Unitree Go2 — Getting Started
# Unitree Go2 Quick Start

The Unitree Go2 is DimOS's primary reference platform. Full autonomous navigation, mapping, and agentic control — no ROS required.

## Requirements

- Unitree Go2 Pro or Air (stock firmware 1.1.7+, no jailbreak needed)
- Ubuntu 22.04/24.04 with CUDA GPU (recommended), or macOS (experimental)
- Python 3.12

## Install

First, install system dependencies for your platform:
- [Ubuntu](/docs/installation/ubuntu.md)
- [macOS](/docs/installation/osx.md)
- [Nix](/docs/installation/nix.md)

Then install DimOS:

```bash
uv venv --python "3.12"
source .venv/bin/activate
uv pip install 'dimos[base,unitree]'
```

## Try It — No Hardware Needed

```bash
# Replay a recorded Go2 navigation session
# First run downloads ~2.4 GB of LiDAR/video data from LFS
dimos --replay run unitree-go2
```

Opens the command center at [localhost:7779](http://localhost:7779) with Rerun 3D visualization — watch the Go2 map and navigate an office in real time.

## Run on Your Go2

### First-time setup, connecting to wifi, finding robot IP

Use `dimos go2tool` to provision wifi and find the robot's IP. Skip if the robot is already on your network and you know its IP.

1. Power on the Go2 — it advertises over BLE immediately.

2. Provision wifi (one-time per network):

optionally use discover to make sure robot is detected

```bash
dimos go2tool discover
```

configure wifi

```bash
dimos go2tool connect-wifi --ssid <wifi> --password <password>
```

Scans BLE and connects to the only robot it finds, or prompts you to pick if there are several.

3. Find the robot's IP:

```bash
dimos go2tool discover
```

Prints `SOURCE NAME IP MAC SERIAL` for every robot it sees over BLE and LAN. Export the IP:

```bash
export ROBOT_IP=<discovered_ip>
```

### Pre-flight checks

1. Robot is reachable and low latency `<10ms`, 0% packet loss
```bash
ping $ROBOT_IP
```

2. Built-in obstacle avoidance is on. (DimOS handles path planning, but the onboard obstacle avoidance provides an extra safety layer around tight spots)

### Ready to run DimOS

```bash
export ROBOT_IP=<YOUR_GO2_IP>
dimos run unitree-go2
```

That's it. DimOS connects via WebRTC (no jailbreak required), starts the full navigation stack, and opens the command center in your browser.

### What's Running

| Module | What It Does |
|--------|-------------|
| **GO2Connection** | WebRTC connection to the robot — streams LiDAR, video, odometry |
| **VoxelGridMapper** | Builds a 3D voxel map using column-carving (CUDA accelerated) |
| **CostMapper** | Converts 3D map → 2D costmap via terrain slope analysis |
| **ReplanningAStarPlanner** | Continuous A* path planning with dynamic replanning |
| **WavefrontFrontierExplorer** | Autonomous exploration of unmapped areas |
| **RerunBridge** | 3D visualization in browser |
| **WebsocketVis** | Command center at localhost:7779 |

### Send Goals

From the command center ([localhost:7779](http://localhost:7779)):
- Click on the map to set navigation goals
- Toggle autonomous exploration
- Monitor robot pose, costmap, and planned path

## MuJoCo Simulation

```bash
uv pip install 'dimos[base,unitree,sim]'
dimos --simulation run unitree-go2
```

Full navigation stack in MuJoCo — same code, simulated robot.

## Agentic Control

Natural language control with an LLM agent that understands physical space:

```bash
export OPENAI_API_KEY=<YOUR_KEY>
export ROBOT_IP=<YOUR_GO2_IP>
dimos run unitree-go2-agentic
```

Then use the human CLI to talk to the agent:

```bash
humancli
> explore the space
```

The agent subscribes to camera, LiDAR, and spatial memory streams — it sees what the robot sees.
- [Setup your Dog](/docs/platforms/quadruped/go2/setup.md) — requirements, install, connecting to your Go2, and agentic control
- [Simulation](/docs/platforms/quadruped/go2/simulation.md) — try it with no hardware via replay or MuJoCo
- [Record & Load Maps](/docs/platforms/quadruped/go2/premap.md) — record a run, export a premap, and relocalize on replay or a live Go2
Comment thread
jeff-hykin marked this conversation as resolved.

## Available Blueprints

| Blueprint | Description |
|-----------|-------------|
| `unitree-go2-basic` | Connection + visualization (no navigation) |
| `unitree-go2` | Full navigation stack |
| `unitree-go2-agentic` | Navigation + LLM agent + MCP tool access |
| `unitree-go2-agentic-ollama` | Agent with local Ollama models |
| `unitree-go2-spatial` | Navigation + spatial memory |
| `unitree-go2-detection` | Navigation + object detection |
| `unitree-go2-ros` | ROS 2 bridge mode |
| `dimos run unitree-go2-basic` | Connection + visualization (no navigation) |
| `dimos run unitree-go2` | Full navigation stack |
| `dimos run unitree-go2-agentic` | Navigation + LLM agent + MCP tool access |
| `dimos run unitree-go2-agentic-ollama` | Agent with local Ollama models |
| `dimos run unitree-go2-spatial` | Navigation + spatial memory |
| `dimos run unitree-go2-detection` | Navigation + object detection |

## Deep Dive

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Relocalization

This walkthrough shows the pre-map capabilities, including how to record,
export a premap from a mem2 `.db`, and run the robot with relocalization
enabled. You can also navigate to a place the robot hasn't visited during
this run, as long as it's part of the global map.
# Creating a Map

Main steps:
1. `dimos run unitree-go2-memory` to create a .db file
2. Auto-clean the .db file into a usable map
3. Load the map back (replay)
4. Load the map into the Go2 (live)

![relocalize on the live go2 and nav_to a point in the premap](assets/reloc_and_nav_to.webp)

Expand All @@ -15,6 +15,7 @@ this run, as long as it's part of the global map.
dimos --robot-ip {YOUR_ROBOT_IP} run unitree-go2-memory
```


If `DIMOS_ROBOT_IP` is set in your environment (or `.env`), you can drop
the `--robot-ip` flag:

Expand All @@ -27,16 +28,16 @@ from the repo root so the bare-name lookup finds this file. In the next
steps `{DB_NAME}` refers to the stem of your recording - `recording_go2`
if you kept the default.

## 2. Export the premap
## 2. Auto-clean the .db, convert into a Map

Convert the recording to a relocalization premap (`.pc2.lcm`):
To create the map file (`.pc2.lcm`):

```bash
# default name from step 1:
dimos map recording_go2 --export
dimos map global recording_go2 --export

# renamed:
dimos map {DB_NAME} --export
dimos map global {DB_NAME} --export
```

`--export` implies `--pgo` (runs pose graph optimization) and writes
Expand All @@ -53,10 +54,10 @@ When a bare file name is given, the tool searches in:
Examples:

```bash
dimos map go2_hongkong_office --export
dimos map ./go2_hongkong_office.db --export
dimos map data/go2_hongkong_office.db --export
dimos map /abs/path/to/scan.db --export
dimos map global go2_hongkong_office --export
dimos map global ./go2_hongkong_office.db --export
dimos map global data/go2_hongkong_office.db --export
dimos map global /abs/path/to/scan.db --export
```

Sample log:
Expand Down
Loading
Loading