Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding ROS Bridge #515

Merged
merged 43 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
e5bfada
update to newest metadrive
BoSmallEar Oct 17, 2023
92c586e
rename package
BoSmallEar Oct 17, 2023
8f46d0c
update
BoSmallEar Oct 17, 2023
effcaa8
update rviz
BoSmallEar Oct 17, 2023
f3995c3
update readme
BoSmallEar Oct 17, 2023
22e6362
update package name
BoSmallEar Oct 17, 2023
5c74ad9
update package name
BoSmallEar Oct 17, 2023
d9542da
reformat use indent=4space
QuanyiLi Oct 18, 2023
fe82e6d
Merge branch 'main' into ros_bridge_new
QuanyiLi Oct 18, 2023
896667c
update readme
QuanyiLi Oct 18, 2023
126b581
fix dependency and install instruction
BoSmallEar Oct 19, 2023
60a4e6b
Merge branch 'ros_bridge_new' of github.com:metadriverse/metadrive in…
QuanyiLi Oct 19, 2023
00d3b69
update readme
QuanyiLi Oct 19, 2023
83759bf
test is successful with ros humble
QuanyiLi Oct 19, 2023
7506a2a
add test
QuanyiLi Oct 19, 2023
c811c96
fix test
QuanyiLi Oct 19, 2023
ece12f1
remove source
QuanyiLi Oct 19, 2023
0ca51ca
remove source line
QuanyiLi Oct 19, 2023
9955923
add python dependency
QuanyiLi Oct 19, 2023
cfee2a0
clean the code
BoSmallEar Oct 19, 2023
61ecb4a
clean the code; run isort and yapf
BoSmallEar Oct 19, 2023
428ac15
add python dependency
QuanyiLi Oct 19, 2023
195c09f
remove container
QuanyiLi Oct 19, 2023
b8cfaa8
debug
QuanyiLi Oct 19, 2023
88eecb6
remove pip install -e .for test
QuanyiLi Oct 19, 2023
684e8d5
use container
QuanyiLi Oct 19, 2023
fd8461e
use container
QuanyiLi Oct 19, 2023
f142702
install gym outside the pip
QuanyiLi Oct 19, 2023
b29f6bf
stop using container
QuanyiLi Oct 19, 2023
6284d1c
remove init
QuanyiLi Oct 19, 2023
38ca0b7
fix init
QuanyiLi Oct 19, 2023
f2eeeba
no update and init
QuanyiLi Oct 20, 2023
cc26327
source
QuanyiLi Oct 20, 2023
b83cc32
source
QuanyiLi Oct 20, 2023
c70a1f1
Merge branch 'main' into ros_bridge_new
QuanyiLi Oct 20, 2023
e63704b
use sudo rosdep
QuanyiLi Oct 20, 2023
3e9364b
disable plugin
QuanyiLi Oct 20, 2023
607f60a
fix bug server
QuanyiLi Oct 20, 2023
e447a6d
restore vision plugins
QuanyiLi Oct 20, 2023
e10213e
remove line
QuanyiLi Oct 20, 2023
46a9f70
use default python
QuanyiLi Oct 20, 2023
fc03a36
test!
QuanyiLi Oct 20, 2023
9d940e2
bypass doc string check when merging to main
QuanyiLi Oct 20, 2023
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
42 changes: 42 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ jobs:
bash format.sh --test

code_docstring:
if: github.event_name != 'pull_request' || github.head_ref != 'main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
Expand Down Expand Up @@ -200,6 +201,47 @@ jobs:
cp ./examples/Basic_MetaDrive_Usages.ipynb ./tests/test_ipynb/
pytest --nbmake -n=auto ./tests/test_ipynb/

test_ros:
runs-on: ubuntu-22.04
# container:
# image: ubuntu:jammy
steps:
- name: Set up ROS2 humble
uses: ros-tooling/setup-ros@v0.7
with:
required-ros-distributions: humble
- name: checkout code
uses: actions/checkout@v2
- name: Blackbox tests
run: |

ls -al
pwd
source /opt/ros/humble/setup.bash

sudo rosdep update

pwd

pip install pyzmq
pip install gymnasium==0.28
ls -al
pip install -e .
python -m metadrive.pull_asset

cd bridges/ros_bridge
ls -al
python _delete_rviz_line.py
sudo rosdep install --from-paths src --ignore-src -y --rosdistro humble
pwd
colcon build
source install/setup.bash
pip install pyzmq
nohup ros2 launch metadrive_example_bridge metadrive_example_bridge.launch.py > ros.txt 2>&1 &
python ros_socket_server.py --test

# python ros_socket_server.py --test

# - name: Upload coverage to Codecov
# uses: codecov/codecov-action@v1
# with:
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,8 @@
**/test_read_copy_waymo
/metadrive/tests/vis_functionality/*.png
**/assets/

/bridges/ros_bridge/build
/bridges/ros_bridge/install
/bridges/ros_bridge/log

43 changes: 43 additions & 0 deletions bridges/ros_bridge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Connecting MetaDrive Simulator with ROS2

We provide an example bridge for connecting MetaDrive and ROS2 using zmq sockets, which publishes messages for camera
images, LiDAR point clouds, and object 3D bounding boxes.

## Installation

To install the bridge, first [install ROS2 **humble**](https://docs.ros.org/en/humble/Installation.html) and follow the scripts
below:

```bash
cd bridges/ros_bridge

# activate env, ${ROS_DISTRO} is something like foxy, iron, humble
source /opt/ros/${ROS_DISTRO}/setup.bash
# You may need to run init, if you are installing ros2 for the first time
sudo rosdep init
# zmq should be installed with system python interpreter
pip install pyzmq
rosdep update
rosdep install --from-paths src --ignore-src -y

# build
colcon build
source install/setup.bash
```

## Usage

```bash
# Terminal 1, launch ROS publishers
ros2 launch metadrive_example_bridge metadrive_example_bridge.launch.py
# Terminal 2, launch socket server
python ros_socket_server.py

```

[Demo Video](https://www.youtube.com/watch?v=WWwdnURnOBM&list=TLGGdRGbC4RGzhAxNzEwMjAyMw)


## Known Issues
If you are using the `conda`, it is very likely that the interpreter will not match the system interpreter
and will be incompatible with ROS 2 binaries.
28 changes: 28 additions & 0 deletions bridges/ros_bridge/_delete_rviz_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""
Only for github CI using! The visualization tool is not available on github server, remove it.
"""
import os


def delete_line_from_file(file_name, line_number):
# Check if the line number is valid
if line_number < 1:
return

lines = []
with open(file_name, 'r') as f:
lines = f.readlines()

# Check if the line number is beyond the file's length
if line_number > len(lines):
return

with open(file_name, 'w') as f:
for idx, line in enumerate(lines):
if idx + 1 != line_number:
f.write(line)


# Usage
if __name__ == '__main__':
delete_line_from_file(os.path.join(os.path.dirname(__file__), 'src', "metadrive_example_bridge", 'package.xml'), 10)
169 changes: 169 additions & 0 deletions bridges/ros_bridge/ros_socket_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# launch sockets to send sensor readings to ROS
import argparse
import struct

import numpy as np
import zmq

from metadrive.constants import HELP_MESSAGE
from metadrive.engine.asset_loader import AssetLoader
from metadrive.envs.scenario_env import ScenarioEnv
from metadrive.policy.replay_policy import ReplayEgoCarPolicy


class RosSocketServer():

def __init__(self):
self.context = zmq.Context().instance()
self.context.setsockopt(zmq.IO_THREADS, 2)
self.img_socket = self.context.socket(zmq.PUSH)
self.img_socket.setsockopt(zmq.SNDBUF, 4194304)
self.img_socket.bind("ipc:///tmp/rgb_camera")
self.img_socket.set_hwm(5)
self.lidar_socket = self.context.socket(zmq.PUSH)
self.lidar_socket.setsockopt(zmq.SNDBUF, 4194304)
self.lidar_socket.bind("ipc:///tmp/lidar")
self.lidar_socket.set_hwm(5)
self.obj_socket = self.context.socket(zmq.PUSH)
self.obj_socket.setsockopt(zmq.SNDBUF, 4194304)
self.obj_socket.bind("ipc:///tmp/obj")
self.obj_socket.set_hwm(5)

def run(self, test=False):
config = dict(
use_render=True if not test else False, # need this on to get the camera
num_scenarios=1,
horizon=1000,
image_observation=True if not test else False,
manual_control=False,
agent_policy=ReplayEgoCarPolicy,
rgb_clip=False,
show_logo=False,
show_fps=False,
show_interface=False,
physics_world_step_size=0.02,
vehicle_config=dict(
image_source="main_camera",
show_navi_mark=False,
),
data_directory=AssetLoader.file_path("nuscenes", return_raw_style=False),
)

env = ScenarioEnv(config)

try:

env.reset()
print(HELP_MESSAGE)
env.vehicle.expert_takeover = False
while True:
o = env.step([0, 0])
if test:
image_data = np.zeros((512, 512, 3)) # fake data for testing
image_data[::16, :, :] = 255
else:
image_data = o[0]['image'][..., -1]
# send via socket
image_data = image_data.astype(np.uint8)
dim_data = struct.pack('ii', image_data.shape[1], image_data.shape[0])
image_data = bytearray(image_data)
# concatenate the dimensions and image data into a single byte array
image_data = dim_data + image_data
try:
self.img_socket.send(image_data, zmq.NOBLOCK)
except zmq.error.Again:
msg = "ros_socket_server: error sending image"
if test:
raise ValueError(msg)
else:
print(msg)
del image_data # explicit delete to free memory

lidar_data, objs = env.vehicle.lidar.perceive(
env.vehicle,
env.engine.physics_world.dynamic_world,
env.vehicle.config["lidar"]["num_lasers"],
env.vehicle.config["lidar"]["distance"],
height=1.0,
)

ego_x = env.vehicle.position[0]
ego_y = env.vehicle.position[1]
ego_theta = np.arctan2(env.vehicle.heading[1], env.vehicle.heading[0])

num_data = struct.pack('i', len(objs))
obj_data = []
for obj in objs:
obj_x = obj.position[0]
obj_y = obj.position[1]
obj_theta = np.arctan2(obj.heading[1], obj.heading[0])

obj_x = obj_x - ego_x
obj_y = obj_y - ego_y
obj_x_new = np.cos(-ego_theta) * obj_x - np.sin(-ego_theta) * obj_y
obj_y_new = np.sin(-ego_theta) * obj_x + np.cos(-ego_theta) * obj_y

obj_data.append(obj_x_new)
obj_data.append(obj_y_new)
obj_data.append(obj_theta - ego_theta)
obj_data.append(obj.LENGTH)
obj_data.append(obj.WIDTH)
obj_data.append(obj.HEIGHT)
obj_data = np.array(obj_data, dtype=np.float32)
obj_data = bytearray(obj_data)
obj_data = num_data + obj_data
try:
self.obj_socket.send(obj_data, zmq.NOBLOCK)
except zmq.error.Again:
msg = "ros_socket_server: error sending objs"
if test:
raise ValueError(msg)
else:
print(msg)
del obj_data # explicit delete to free memory

# convert lidar data to xyz
lidar_data = np.array(lidar_data) * env.vehicle.config["lidar"]["distance"]
lidar_range = env.vehicle.lidar._get_lidar_range(
env.vehicle.config["lidar"]["num_lasers"], env.vehicle.lidar.start_phase_offset
)
point_x = lidar_data * np.cos(lidar_range)
point_y = lidar_data * np.sin(lidar_range)
point_z = np.ones(lidar_data.shape) # assume height = 1.0
lidar_data = np.stack([point_x, point_y, point_z], axis=-1).astype(np.float32)
dim_data = struct.pack('i', len(lidar_data))
lidar_data = bytearray(lidar_data)
# concatenate the dimensions and lidar data into a single byte array
lidar_data = dim_data + lidar_data
try:
self.lidar_socket.send(lidar_data, zmq.NOBLOCK)
except zmq.error.Again:
msg = "ros_socket_server: error sending lidar"
if test:
raise ValueError(msg)
else:
print(msg)
del lidar_data # explicit delete to free memory

if o[2]: # done
break

except Exception as e:
import traceback
traceback.print_exc()
raise e

finally:
env.close()


def main(test=False):
server = RosSocketServer()
server.run(test)


if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--test", action="store_true")
args = parser.parse_args()
main(args.test)
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import os

from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch_ros.actions import Node


def generate_launch_description():
ld = LaunchDescription()
camera_node = Node(package="metadrive_example_bridge", executable="camera_bridge", name='camera_bridge')
ld.add_action(camera_node)
lidar_node = Node(package="metadrive_example_bridge", executable="lidar_bridge", name='lidar_bridge')
ld.add_action(lidar_node)
obj_node = Node(package="metadrive_example_bridge", executable="obj_bridge", name='obj_bridge')
ld.add_action(obj_node)

pkg_dir = get_package_share_directory('metadrive_example_bridge')
rviz_node = Node(
package='rviz2', executable='rviz2', name='rviz2', arguments=['-d', [os.path.join(pkg_dir, 'default.rviz')]]
)
ld.add_action(rviz_node)
return ld


if __name__ == '__main__':
generate_launch_description()
Loading
Loading