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
47 changes: 45 additions & 2 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2330,6 +2330,12 @@ dependencies = [
"parking_lot_core",
]

[[package]]
name = "data-encoding"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"

[[package]]
name = "data-url"
version = "0.3.1"
Expand Down Expand Up @@ -3110,15 +3116,17 @@ dependencies = [

[[package]]
name = "dimos-viewer"
version = "0.30.0-alpha.1+dev"
version = "0.30.0-alpha.4"
dependencies = [
"bincode",
"clap",
"futures-util",
"mimalloc",
"parking_lot",
"rerun",
"serde",
"serde_json",
"tokio",
"tokio-tungstenite",
]

[[package]]
Expand Down Expand Up @@ -12596,6 +12604,18 @@ dependencies = [
"tokio-util",
]

[[package]]
name = "tokio-tungstenite"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857"
dependencies = [
"futures-util",
"log",
"tokio",
"tungstenite",
]

[[package]]
name = "tokio-util"
version = "0.7.16"
Expand Down Expand Up @@ -13013,6 +13033,23 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78122066b0cb818b8afd08f7ed22f7fdbc3e90815035726f0840d0d26c0747a"

[[package]]
name = "tungstenite"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442"
dependencies = [
"bytes",
"data-encoding",
"http",
"httparse",
"log",
"rand 0.9.2",
"sha1",
"thiserror 2.0.17",
"utf-8",
]

[[package]]
name = "twox-hash"
version = "2.1.2"
Expand Down Expand Up @@ -13192,6 +13229,12 @@ dependencies = [
"xmlwriter",
]

[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"

[[package]]
name = "utf8-ranges"
version = "1.0.5"
Expand Down
151 changes: 151 additions & 0 deletions WS_EVENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# dimos-viewer WebSocket Event Stream

When `dimos-viewer` is started with `--connect`, LCM multicast is not available
(LCM uses UDP multicast which is limited to the local machine or subnet). Instead,
the viewer starts a WebSocket server that broadcasts click and keyboard events as
JSON to any connected client.

## Starting the server

```sh
dimos-viewer --connect [<grpc-proxy-url>] [--ws-port <port>]
```

| Flag | Default | Description |
|------|---------|-------------|
| `--connect [url]` | — | Enable connect mode. Optionally specify the gRPC proxy URL (defaults to `rerun+http://127.0.0.1:9877/proxy`). |
| `--ws-port <port>` | `3030` | Port for the WebSocket event server. |

The WebSocket server listens on:

```
ws://0.0.0.0:<ws-port>/ws
```

Multiple clients can connect simultaneously. All connected clients receive every
message. The server does not accept any inbound messages from clients.

## Message format

All messages are UTF-8 JSON objects with a `"type"` string discriminant.

### `heartbeat`

Sent once per second regardless of viewer activity. Useful for connection health
checks and detecting viewer restarts.

```json
{
"type": "heartbeat",
"timestamp_ms": 1773869091428
}
```

| Field | Type | Description |
|-------|------|-------------|
| `timestamp_ms` | `u64` | Unix timestamp in milliseconds (from the viewer's system clock). |

### `click`

Sent when the user clicks a 3D point in the Rerun viewport. Corresponds to the
`/clicked_point` convention from RViz / `geometry_msgs/PointStamped`.

```json
{
"type": "click",
"x": 1.234,
"y": -0.567,
"z": 0.000,
"entity_path": "/world/ground_plane",
"timestamp_ms": 1773869091428
}
```

| Field | Type | Description |
|-------|------|-------------|
| `x` | `f64` | World-space X coordinate (metres). |
| `y` | `f64` | World-space Y coordinate (metres). |
| `z` | `f64` | World-space Z coordinate (metres). |
| `entity_path` | `string` | Rerun entity path of the clicked object. |
| `timestamp_ms` | `u64` | Unix timestamp in milliseconds at the moment of the click. |

Click events are debounced: a minimum of 100 ms must elapse between successive
published clicks. Rapid clicks within that window are silently dropped (with a
warning logged after 5 consecutive rapid clicks).

### `twist`

Sent every frame (~60 Hz) while movement keys are held on the keyboard teleop
overlay. Corresponds to `geometry_msgs/Twist` / `/cmd_vel` convention.

The keyboard overlay must first be **engaged** by clicking on it (green border =
active). Clicking anywhere outside the overlay disengages it and sends a `stop`.

```json
{
"type": "twist",
"linear_x": 0.5,
"linear_y": 0.0,
"linear_z": 0.0,
"angular_x": 0.0,
"angular_y": 0.0,
"angular_z": 0.8
}
```

| Field | Type | Description |
|-------|------|-------------|
| `linear_x` | `f64` | Forward (+) / backward (−) velocity in m/s. |
| `linear_y` | `f64` | Strafe left (+) / right (−) velocity in m/s. |
| `linear_z` | `f64` | Vertical velocity in m/s (always 0 for ground robots). |
| `angular_x` | `f64` | Roll rate in rad/s (always 0). |
| `angular_y` | `f64` | Pitch rate in rad/s (always 0). |
| `angular_z` | `f64` | Yaw left (+) / right (−) rate in rad/s. |

**Key bindings:**

| Key | Effect |
|-----|--------|
| `W` / `↑` | Forward (`linear_x = +0.5`) |
| `S` / `↓` | Backward (`linear_x = −0.5`) |
| `A` / `←` | Turn left (`angular_z = +0.8`) |
| `D` / `→` | Turn right (`angular_z = −0.8`) |
| `Q` | Strafe left (`linear_y = +0.5`) |
| `E` | Strafe right (`linear_y = −0.5`) |
| `Shift` | Speed multiplier ×2 |

Multiple keys can be held simultaneously; their effects are summed.

### `stop`

Sent when all movement keys are released, the overlay is disengaged, or `Space`
is pressed (emergency stop). Signals the robot to halt immediately.

```json
{
"type": "stop"
}
```

No additional fields. Semantically equivalent to a `twist` with all fields zero.

## Example client (Deno)

A reference test client is provided at `dimos/test_ws_client.ts`:

```sh
deno run --allow-net dimos/test_ws_client.ts
# or with a custom address:
deno run --allow-net dimos/test_ws_client.ts ws://192.168.1.10:3030/ws
```

## Local mode (no `--connect`)

Without `--connect`, the viewer uses LCM UDP multicast instead of WebSocket:

| Channel | Message type |
|---------|-------------|
| `/clicked_point#geometry_msgs.PointStamped` | Click events |
| `/cmd_vel#geometry_msgs.Twist` | Twist / stop commands |

The WebSocket server is **not** started in this mode.
7 changes: 6 additions & 1 deletion dimos/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ rerun = { path = "../crates/top/rerun", default-features = false, features = [

clap = { workspace = true, features = ["derive"] }
bincode.workspace = true
futures-util.workspace = true
mimalloc.workspace = true
parking_lot.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
tokio = { workspace = true, features = [
"io-util",
"macros",
Expand All @@ -40,3 +41,7 @@ tokio = { workspace = true, features = [
"sync",
"time",
] }
tokio-tungstenite = "0.28.0"

[lints]
workspace = true
22 changes: 11 additions & 11 deletions dimos/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ version = "0.30.0a4"
description = "Interactive Rerun viewer for DimOS with click-to-navigate support"
readme = "README.md"
requires-python = ">=3.10"
license = {text = "MIT OR Apache-2.0"}
authors = [{name = "Dimensional Inc.", email = "engineering@dimensional.com"}]
license = { text = "MIT OR Apache-2.0" }
authors = [{ name = "Dimensional Inc.", email = "engineering@dimensional.com" }]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Topic :: Scientific/Engineering",
"Topic :: Scientific/Engineering :: Visualization",
"Programming Language :: Rust",
"License :: OSI Approved :: MIT License",
"License :: OSI Approved :: Apache Software License",
"Operating System :: POSIX :: Linux",
"Operating System :: MacOS",
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Topic :: Scientific/Engineering",
"Topic :: Scientific/Engineering :: Visualization",
"Programming Language :: Rust",
"License :: OSI Approved :: MIT License",
"License :: OSI Approved :: Apache Software License",
"Operating System :: POSIX :: Linux",
"Operating System :: MacOS",
]

[project.urls]
Expand Down
4 changes: 2 additions & 2 deletions dimos/src/interaction/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ impl InteractionHandle {
is_2d,
};

if let Err(e) = self.tx.send(event) {
eprintln!("Failed to send click event: {}", e);
if let Err(err) = self.tx.send(event) {
eprintln!("Failed to send click event: {}", err);
}
}
}
Expand Down
Loading
Loading