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
297 changes: 149 additions & 148 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"@types/react-dom": "^19.1.9",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-scripts": "5.0.1",
"react-scripts": "^5.0.1",
"roslib": "^1.3.0",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
Expand All @@ -22,7 +22,10 @@
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"eject": "react-scripts eject",
"bag:record": "wsl bash ./scripts/record-bag.sh",
"bag:play": "wsl bash ./scripts/play-bag.sh",
"bag:list": "wsl bash -c \"mkdir -p bags && ls -lh bags/\""
},
"eslintConfig": {
"extends": [
Expand Down
108 changes: 108 additions & 0 deletions scripts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# ROS Bag Helper Scripts

Quick commands to record and play back ROS2 bags for testing the simulation GUI.

## Quick Start

### 1. Record a test bag from the GUI

```powershell
# In PowerShell (Windows) - default 15 seconds, saves to bags/pool_test_01
npm run bag:record
```

This will:
- Prompt you to start the GUI simulation first
- Record `/imu/data`, `/dvl/odom`, and `/depth/pose` for 15 seconds
- Save to `bags/pool_test_01/`

**Custom bag name and duration:**
```powershell
# Record to a custom bag name for 30 seconds
wsl bash ./scripts/record-bag.sh my_test_bag 30
```

### 2. Play back a bag

```powershell
# In PowerShell (Windows) - plays bags/pool_test_01 in loop
npm run bag:play
```

This will:
- Play `bags/pool_test_01/` in loop mode at normal speed
- Press Ctrl+C to stop

**Custom bag name and playback rate:**
```powershell
# Play custom bag at 2x speed
wsl bash ./scripts/play-bag.sh my_test_bag 2.0
```

### 3. List available bags

```powershell
npm run bag:list
```

## Manual Usage (from WSL)

If you prefer to run the scripts directly in WSL:

```bash
# Record
./scripts/record-bag.sh [bag_name] [duration_seconds]

# Play
./scripts/play-bag.sh [bag_name] [rate]

# Examples
./scripts/record-bag.sh pool_test_02 20
./scripts/play-bag.sh pool_test_02 1.5
```

## Workflow for Testing Bag Mode

1. **Record a bag:**
```powershell
npm start # Start GUI
# Set Data Source: Synthetic Simulation, click Start
npm run bag:record # In another terminal
```

2. **Play it back:**
```powershell
npm run bag:play # Plays in loop
# In GUI: switch to "Bag Playback (listen only)"
# Click "Seed from ROS topics"
```

3. **Verify seeding works:**
- Topics should show Hz values (not "lost!")
- Seed button should be enabled
- After clicking "Seed from ROS topics", you should see:
- Green status line: "Seeded orientation from /imu/data, depth from /depth/pose, velocity from /dvl/odom — HH:MM:SS"

## Troubleshooting

**"Bag not found" error:**
- Check `npm run bag:list` to see available bags
- Make sure you recorded a bag first with `npm run bag:record`

**"No topics" when recording:**
- Ensure rosbridge is running in WSL: `ros2 launch rosbridge_server rosbridge_websocket_launch.xml`
- Start the GUI and begin synthetic simulation
- Verify topics with: `wsl ros2 topic list`

**Bag playback but GUI shows "lost!" banners:**
- Verify topics are publishing: `wsl ros2 topic hz /imu/data`
- Check rosbridge is connected (green banner in GUI)
- Ensure bag contains the right topics: `wsl ros2 bag info bags/pool_test_01`

## Notes

- All scripts run inside WSL2 (where ROS2 is installed)
- The npm commands are wrappers that invoke WSL automatically
- Bags are stored in the `bags/` directory at the repo root
- Recording uses a 15-second default duration (configurable)
- Playback runs in loop mode by default
39 changes: 39 additions & 0 deletions scripts/play-bag.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/bash
# Helper script to play back a recorded bag
# Usage: ./scripts/play-bag.sh [bag_name] [rate]

# Source ROS2 setup
source /opt/ros/humble/setup.bash

BAG_NAME="${1:-pool_test_01}"
RATE="${2:-1.0}"
BAG_DIR="$(cd "$(dirname "$0")/.." && pwd)/bags/${BAG_NAME}"

echo "========================================="
echo "Playing ROS2 bag"
echo "Bag: ${BAG_NAME}"
echo "Path: ${BAG_DIR}"
echo "Rate: ${RATE}x"
echo "========================================="
echo ""

# Check if bag exists
if [ ! -f "${BAG_DIR}/metadata.yaml" ]; then
echo "ERROR: Bag not found at ${BAG_DIR}"
echo ""
echo "Available bags:"
ls -1 "$(dirname "$0")/../bags" 2>/dev/null || echo " (none)"
echo ""
echo "To record a new bag:"
echo " npm run bag:record -- ${BAG_NAME}"
exit 1
fi

# Show bag info
ros2 bag info "$BAG_DIR"
echo ""
echo "Playing in loop mode. Press Ctrl+C to stop."
echo ""

# Play bag in loop mode
ros2 bag play "$BAG_DIR" -l -r "$RATE"
90 changes: 90 additions & 0 deletions scripts/record-bag.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/bin/bash
# Helper script to record a test bag from the GUI's synthetic simulation
# Usage: ./scripts/record-bag.sh [bag_name] [duration_seconds]

# Source ROS2 setup
source /opt/ros/humble/setup.bash

BAG_NAME="${1:-pool_test_01}"
DURATION="${2:-15}"
BAG_DIR="$(cd "$(dirname "$0")/.." && pwd)/bags/${BAG_NAME}"

echo "========================================="
echo "Recording ROS2 bag for ${DURATION} seconds"
echo "Bag: ${BAG_NAME}"
echo "Path: ${BAG_DIR}"
echo "========================================="
echo ""
echo "Topics to record:"
echo " - /imu/data (sensor_msgs/Imu)"
echo " - /dvl/odom (nav_msgs/Odometry)"
echo " - /depth/pose (geometry_msgs/PoseWithCovarianceStamped)"
echo ""
echo "Before recording:"
echo " 1. Start the GUI (npm start)"
echo " 2. Set Data Source: Synthetic Simulation"
echo " 3. Click 'Start Simulation'"
echo " 4. Wait a few seconds for topics to stabilize"
echo ""
read -p "Press Enter when simulation is running, or Ctrl+C to cancel..."

# Remove existing bag if present
if [ -d "$BAG_DIR" ]; then
echo "Removing existing bag at $BAG_DIR"
rm -rf "$BAG_DIR"
fi

echo ""
echo "Recording for ${DURATION} seconds..."
echo "Press Ctrl+C to stop recording."
echo ""

# Start recording in background and capture its PID
ros2 bag record \
/imu/data \
/dvl/odom \
/depth/pose \
-o "$BAG_DIR" &

RECORD_PID=$!

# Function to kill the recording process
cleanup() {
echo ""
echo "Stopping recording..."
kill $RECORD_PID 2>/dev/null
wait $RECORD_PID 2>/dev/null
echo "Recording stopped."
}

# Set up trap to catch Ctrl+C
trap cleanup SIGINT SIGTERM

# Wait for the specified duration
sleep ${DURATION}

# Stop recording
cleanup

echo ""
echo "========================================="
echo "Recording complete!"
echo "========================================="
echo ""

# Show bag info
if [ -f "${BAG_DIR}/metadata.yaml" ]; then
ros2 bag info "$BAG_DIR"
echo ""
echo "To play this bag:"
echo " npm run bag:play -- ${BAG_NAME}"
echo ""
echo "Or manually:"
echo " ros2 bag play ${BAG_DIR} -l"
else
echo "ERROR: No metadata.yaml found. Recording may have failed."
echo "Make sure:"
echo " - rosbridge is running (ros2 launch rosbridge_server rosbridge_websocket_launch.xml)"
echo " - GUI simulation is publishing topics"
echo " - You can see topics with: ros2 topic list"
fi
38 changes: 37 additions & 1 deletion src/components/RosContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,44 @@ export function RosProvider({ children }: RosProviderProps) {
});


/**
* resolve the ROS bridge websocket URL, made it ***Dynamic*** :)
* priority (highest to lowest):
* - URL query params: ?ros_host=HOST&ros_port=PORT or ?ros_ws=ws://host:port
* - localStorage: ros.host, ros.port, ros.ws
* - ENV (build-time): REACT_APP_ROSBRIDGE_HOST, REACT_APP_ROSBRIDGE_PORT
* - if page host is not localhost/127.0.0.1, use window.location.hostname
* - fallback default: localhost:9090 (works with WSL2)
*/
function getRosBridgeUrl(): string {
try {
const params = new URLSearchParams(window.location.search);
const qpWs = params.get('ros_ws');
if (qpWs) return qpWs;

const lsWs = localStorage.getItem('ros.ws');
if (lsWs) return lsWs;

const qpHost = params.get('ros_host') ?? localStorage.getItem('ros.host') ?? process.env.REACT_APP_ROSBRIDGE_HOST ?? '';
const qpPort = params.get('ros_port') ?? localStorage.getItem('ros.port') ?? process.env.REACT_APP_ROSBRIDGE_PORT ?? '';

const pageHost = window.location.hostname;
// default to localhost (works with WSL2), or use page hostname if deployed remotely
const defaultHost = (pageHost && pageHost !== 'localhost' && pageHost !== '127.0.0.1') ? pageHost : 'localhost';
const host = qpHost || defaultHost;
const port = qpPort || '9090';
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
return `${protocol}://${host}:${port}`;
} catch {
// safe fallback
return 'ws://localhost:9090';
}
}

function connect_to_ros() {
rosRef.current.connect('ws://localhost:9090');
const url = getRosBridgeUrl();
console.log(`[ROS] connecting to ${url}`);
rosRef.current.connect(url);
}

// on start up
Expand Down
Loading