Skip to content

Commit cbd4bba

Browse files
JohnYanxinLiuclaude
andcommitted
set the streamed body in the script, not the environment
The emulator read NATNET_BODY_NAME / NATNET_BODY_ID from the environment to stay in sync with the client. The client now takes its bodies from its per-robot profile in natnet_config.yaml, so the env hook was asymmetric and, being global, could not describe a multi-robot scene anyway. Both are now constants in the launch scripts, with the pairing spelled out inline, in the emulator sim doc, and in the optitrack-development skill — including that a mismatched id fails silently: the client connects and never publishes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 5d9a5fb commit cbd4bba

6 files changed

Lines changed: 44 additions & 24 deletions

File tree

.agents/skills/optitrack-development/SKILL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ metadata:
1717
- Understanding **NatNet wire protocol** (connect, model def, frame streaming)
1818
- Capturing what **`libNatNet.so`** actually sends on the network
1919
- Enabling OptiTrack in sim: `LAUNCH_NATNET=true`, `natnet_config.yaml`, Docker IPs
20+
- Sim testing with mocap: bring the stack up with `overrides/isaac-natnet-vision.env`, which starts Isaac + the emulator and switches PX4 EKF2 to external-vision fusion (GPS/baro/range aiding off, so mocap is the only position source)
2021

2122
## Architecture in AirStack
2223

@@ -50,7 +51,7 @@ flowchart LR
5051
| [`example_one_px4_pegasus_natnet_launch_script.py`](../../../simulation/isaac-sim/launch_scripts/example_one_px4_pegasus_natnet_launch_script.py) | Single drone + static ``Target`` |
5152
| [`example_multi_px4_pegasus_natnet_launch_script.py`](../../../simulation/isaac-sim/launch_scripts/example_multi_px4_pegasus_natnet_launch_script.py) | ``NUM_ROBOTS`` drones + shared ``Target`` (pair with 3-profile ``natnet_config.yaml``) |
5253

53-
Helpers: [`isaac/scene_setup.py`](../../../simulation/isaac-sim/extensions/optitrack.natnet.emulator/optitrack/natnet/emulator/isaac/scene_setup.py) (`start_drone_natnet_server`, `author_static_target`). Drone body: single = ``Drone``; multi = ``Drone<i>`` (id ``i``); target = ``Target`` (id 100). Override names with ``NATNET_BODY_NAME`` / ``NATNET_TARGET_NAME``. Baseline Pegasus scripts (no NatNet) remain ``example_one_px4_pegasus_launch_script.py`` / ``example_multi_px4_pegasus_launch_script.py``.
54+
Helpers: [`isaac/scene_setup.py`](../../../simulation/isaac-sim/extensions/optitrack.natnet.emulator/optitrack/natnet/emulator/isaac/scene_setup.py) (`start_drone_natnet_server`, `author_static_target`). Drone body: single = ``Drone`` (id 1); multi = ``Drone<i>`` (id ``i``); target = ``Target`` (id 100). These are **constants in the launch script**, not env vars — change them there AND in the matching ``natnet_config.yaml`` profile together. The client filters frames by numeric id, so a mismatch is silent: it connects and never publishes. Baseline Pegasus scripts (no NatNet) remain ``example_one_px4_pegasus_launch_script.py`` / ``example_multi_px4_pegasus_launch_script.py``.
5455

5556
**Default client config:** unicast, `server_ip` → Motive/emulator (use `172.31.0.200` for Isaac container), ports 1510/1511. The config is per-robot: each `robots[$ROBOT_NAME]` profile lists the bodies it tracks (each a `rigid_body_name` + `id` mapped to a relative `topic`, with `pose`/`pose_cov` toggles and per-body covariance) and an optional `vision_pose` block that drives the MAVROS bridge. See [`natnet_config.yaml`](../../../robot/ros_ws/src/perception/natnet_ros2/config/natnet_config.yaml).
5657

docs/simulation/isaac_sim/natnet_emulator.md

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,29 @@ PX4, start the NatNet emulator, and play the simulation automatically.
8282
| 1 | `Drone`, `Target` |
8383
| N > 1 | `Drone1`, `Drone2`, …, `DroneN`, `Target` |
8484

85-
Override the base name with `NATNET_BODY_NAME` (default `Drone`) and
86-
`NATNET_TARGET_NAME` (default `Target`). These must match the
87-
`rigid_body_name` entries in your robot's
88-
[`natnet_config.yaml`](../../../robot/ros_ws/src/perception/natnet_ros2/config/natnet_config.yaml)
89-
profile.
85+
### Changing which body is streamed
86+
87+
The streamed body name and streaming id are **constants in the launch script**
88+
(`NATNET_BODY_NAME` / `NATNET_BODY_ID` / `NATNET_TARGET_NAME`), not environment
89+
variables. They must match a body entry in the robot's profile in
90+
[`natnet_config.yaml`](../../../robot/ros_ws/src/perception/natnet_ros2/config/natnet_config.yaml),
91+
which is the only place the client reads its bodies from — that is what lets each robot
92+
in a multi-robot scene track a different body.
93+
94+
To retarget, edit **both** together:
95+
96+
| Where | What |
97+
|---|---|
98+
| `example_one_px4_pegasus_natnet_launch_script.py` | `NATNET_BODY_NAME`, `NATNET_BODY_ID` |
99+
| `natnet_config.yaml``robots.<robot_name>.bodies[]` | `rigid_body_name`, `id` |
100+
101+
!!! warning "A mismatch fails silently"
102+
The NatNet client filters incoming frames by **numeric id**. If the ids disagree, the
103+
client connects, the emulator streams, and the pose topic never publishes — with no
104+
error on either side. When debugging a silent stream, check the id first.
105+
106+
Deliberately not settable from an env file: one global variable cannot express
107+
per-robot bodies, so it would break multi-robot.
90108

91109
### What the robot container needs
92110

overrides/isaac-natnet-vision.env

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,6 @@ LAUNCH_NATNET="true"
3535
# between sim and a real deployment, where it is the Motive host's address.
3636
NATNET_SERVER_IP="172.31.0.200"
3737

38-
# Must agree between the emulator (launch script) and the client (natnet_config.yaml),
39-
# which both read these. The client filters incoming frames by NUMERIC id, so a mismatch
40-
# yields a connected client that never publishes.
41-
NATNET_BODY_NAME="Drone"
42-
NATNET_BODY_ID="1"
43-
4438
# --- PX4 EKF2 external-vision fusion -----------------------------------------
4539
# Without these PX4 ignores the vision stream entirely — EKF2_EV_CTRL defaults to 0 — and
4640
# flies on sim GPS, so the emulator can be streaming perfectly and change nothing.

simulation/isaac-sim/launch_scripts/example_multi_px4_pegasus_natnet_launch_script.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
- ``NUM_ROBOTS`` (default 1)
2323
- ``ENABLE_LIDAR`` (default false)
2424
- ``PLAY_SIM_ON_START`` (default true)
25-
- ``NATNET_BODY_NAME`` / ``NATNET_TARGET_NAME`` (optional name overrides)
2625
"""
2726

2827
import asyncio
@@ -73,8 +72,12 @@
7372

7473
NUM_ROBOTS = int(os.environ.get("NUM_ROBOTS", "1"))
7574
ENABLE_LIDAR = os.environ.get("ENABLE_LIDAR", "false").lower() == "true"
76-
NATNET_BODY_NAME = os.environ.get("NATNET_BODY_NAME", "Drone")
77-
NATNET_TARGET_NAME = os.environ.get("NATNET_TARGET_NAME", "Target")
75+
# Base name for the streamed drone bodies; drone i is streamed with id i (see
76+
# _drone_body_name below). These must match the body entries in the per-robot profiles
77+
# in natnet_config.yaml — the client filters frames by numeric id. Set them here, not in
78+
# an env file.
79+
NATNET_BODY_NAME = "Drone"
80+
NATNET_TARGET_NAME = "Target"
7881

7982
_NATNET_SERVER_KWARGS = {
8083
"pose_noise_enabled": True,

simulation/isaac-sim/launch_scripts/example_one_px4_pegasus_natnet_launch_script.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,17 @@
108108
{"domain_id": 1, "x_m": 0.0, "y_m": 0.0, "z_m": 0.07},
109109
]
110110

111-
NATNET_BODY_NAME = os.environ.get("NATNET_BODY_NAME", "Drone")
112-
# Streaming id the emulator advertises for the drone. The NatNet client filters incoming
113-
# frames by NUMERIC id, so this must match the body id in natnet_config.yaml, which reads
114-
# the same env var — a mismatch yields a connected client that never publishes. Both
115-
# default to 1, so the sim path works unconfigured.
116-
NATNET_BODY_ID = int(os.environ.get("NATNET_BODY_ID", "1"))
117-
NATNET_TARGET_NAME = os.environ.get("NATNET_TARGET_NAME", "Target")
111+
# Rigid body this scene streams, and the streaming id it advertises.
112+
#
113+
# These MUST match a body entry in the robot's profile in
114+
# robot/ros_ws/src/perception/natnet_ros2/config/natnet_config.yaml — the NatNet client
115+
# filters incoming frames by NUMERIC id, so a mismatch gives a client that connects and
116+
# then never publishes, with no error on either side. Change them here and in that file
117+
# together, never in an env file: the client reads the body from its per-robot profile
118+
# so that multiple robots can each track their own body.
119+
NATNET_BODY_NAME = "Drone"
120+
NATNET_BODY_ID = 1
121+
NATNET_TARGET_NAME = "Target"
118122

119123
_NATNET_SERVER_KWARGS = {
120124
"pose_noise_enabled": True,

tests/integration/natnet/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ The matching **system** check is
103103
- Asserts `/{robot_n}/{natnet pose topic}/pose_cov` ≥ 5 Hz per robot (the drone
104104
body's configured topic — default `perception/optitrack/drone`).
105105
- Override the checked topic with `NATNET_POSE_TOPIC` (default
106-
`perception/optitrack/drone`). The sim body name (`NATNET_BODY_NAME`, default
107-
`Drone`) is decoupled from the published topic, which the robot profile sets.
106+
`perception/optitrack/drone`). The body name and the published topic are decoupled;
107+
both are set in the robot's `natnet_config.yaml` profile.
108108

109109
Sim auto-start: set `ISAAC_SIM_SCRIPT_NAME` to a NatNet launch script and
110110
`LAUNCH_NATNET=true` on the robot. Convenience bundle:

0 commit comments

Comments
 (0)