├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── config └── sim.yaml ├── docker-compose.yml ├── f1tenth_gym_ros.png ├── f1tenth_gym_ros ├── __init__.py └── gym_bridge.py ├── launch ├── ego_racecar.xacro ├── gym_bridge.rviz ├── gym_bridge_launch.py └── opp_racecar.xacro ├── maps ├── Spielberg_map.png ├── Spielberg_map.yaml ├── levine.png └── levine.yaml ├── package.xml ├── resource └── f1tenth_gym_ros ├── setup.cfg ├── setup.py └── test ├── test_copyright.py ├── test_flake8.py └── test_pep257.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | .DS_Store 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | pip-wheel-metadata/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 97 | __pypackages__/ 98 | 99 | # Celery stuff 100 | celerybeat-schedule 101 | celerybeat.pid 102 | 103 | # SageMath parsed files 104 | *.sage.py 105 | 106 | # Environments 107 | .env 108 | .venv 109 | env/ 110 | venv/ 111 | ENV/ 112 | env.bak/ 113 | venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # mkdocs documentation 123 | /site 124 | 125 | # mypy 126 | .mypy_cache/ 127 | .dmypy.json 128 | dmypy.json 129 | 130 | # Pyre type checker 131 | .pyre/ 132 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2020 Hongrui Zheng 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | FROM ros:foxy 24 | 25 | SHELL ["/bin/bash", "-c"] 26 | 27 | # dependencies 28 | RUN apt-get update --fix-missing && \ 29 | apt-get install -y git \ 30 | nano \ 31 | vim \ 32 | python3-pip \ 33 | libeigen3-dev \ 34 | tmux \ 35 | ros-foxy-rviz2 36 | RUN apt-get -y dist-upgrade 37 | RUN pip3 install transforms3d 38 | 39 | # f1tenth gym 40 | RUN git clone https://github.com/f1tenth/f1tenth_gym 41 | RUN cd f1tenth_gym && \ 42 | pip3 install -e . 43 | 44 | # ros2 gym bridge 45 | RUN mkdir -p sim_ws/src/f1tenth_gym_ros 46 | COPY . /sim_ws/src/f1tenth_gym_ros 47 | RUN source /opt/ros/foxy/setup.bash && \ 48 | cd sim_ws/ && \ 49 | apt-get update --fix-missing && \ 50 | rosdep install -i --from-path src --rosdistro foxy -y && \ 51 | colcon build 52 | 53 | WORKDIR '/sim_ws' 54 | ENTRYPOINT ["/bin/bash"] 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Hongrui Zheng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # F1TENTH gym environment ROS2 communication bridge 2 | This is a containerized ROS communication bridge for the F1TENTH gym environment that turns it into a simulation in ROS2. 3 | 4 | # Installation 5 | 6 | **Supported System:** 7 | 8 | - Ubuntu (tested on 20.04) native with ROS 2 9 | - Ubuntu (tested on 20.04) with an NVIDIA gpu and nvidia-docker2 support 10 | - Windows 10, macOS, and Ubuntu without an NVIDIA gpu (using noVNC) 11 | 12 | This installation guide will be split into instruction for installing the ROS 2 package natively, and for systems with or without an NVIDIA gpu in Docker containers. 13 | 14 | ## Native on Ubuntu 20.04 15 | 16 | **Install the following dependencies:** 17 | - **ROS 2** Follow the instructions [here](https://docs.ros.org/en/foxy/Installation.html) to install ROS 2 Foxy. 18 | - **F1TENTH Gym** 19 | ```bash 20 | git clone https://github.com/f1tenth/f1tenth_gym 21 | cd f1tenth_gym && pip3 install -e . 22 | ``` 23 | 24 | **Installing the simulation:** 25 | - Create a workspace: ```cd $HOME && mkdir -p sim_ws/src``` 26 | - Clone the repo into the workspace: 27 | ```bash 28 | cd $HOME/sim_ws/src 29 | git clone https://github.com/f1tenth/f1tenth_gym_ros 30 | ``` 31 | - Update correct parameter for path to map file: 32 | Go to `sim.yaml` [https://github.com/f1tenth/f1tenth_gym_ros/blob/main/config/sim.yaml](https://github.com/f1tenth/f1tenth_gym_ros/blob/main/config/sim.yaml) in your cloned repo, change the `map_path` parameter to point to the correct location. It should be `'/sim_ws/src/f1tenth_gym_ros/maps/levine'` 33 | - Install dependencies with rosdep: 34 | ```bash 35 | source /opt/ros/foxy/setup.bash 36 | cd .. 37 | rosdep install -i --from-path src --rosdistro foxy -y 38 | ``` 39 | - Build the workspace: ```colcon build``` 40 | 41 | ## With an NVIDIA gpu: 42 | 43 | **Install the following dependencies:** 44 | 45 | - **Docker** Follow the instructions [here](https://docs.docker.com/install/linux/docker-ce/ubuntu/) to install Docker. A short tutorial can be found [here](https://docs.docker.com/get-started/) if you're not familiar with Docker. If you followed the post-installation steps you won't have to prepend your docker and docker-compose commands with sudo. 46 | - **nvidia-docker2**, follow the instructions [here](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html) if you have a support GPU. It is also possible to use Intel integrated graphics to forward the display, see details instructions from the Rocker repo. If you are on windows with an NVIDIA GPU, you'll have to use WSL (Windows Subsystem for Linux). Please refer to the guide [here](https://developer.nvidia.com/cuda/wsl), [here](https://docs.nvidia.com/cuda/wsl-user-guide/index.html), and [here](https://dilililabs.com/zh/blog/2021/01/26/deploying-docker-with-gpu-support-on-windows-subsystem-for-linux/). 47 | - **rocker** [https://github.com/osrf/rocker](https://github.com/osrf/rocker). This is a tool developed by OSRF to run Docker images with local support injected. We use it for GUI forwarding. If you're on Windows, WSL should also support this. 48 | 49 | **Installing the simulation:** 50 | 51 | 1. Clone this repo 52 | 2. Build the docker image by: 53 | ```bash 54 | $ cd f1tenth_gym_ros 55 | $ docker build -t f1tenth_gym_ros -f Dockerfile . 56 | ``` 57 | 3. To run the containerized environment, start a docker container by running the following. (example showned here with nvidia-docker support). By running this, the current directory that you're in (should be `f1tenth_gym_ros`) is mounted in the container at `/sim_ws/src/f1tenth_gym_ros`. Which means that the changes you make in the repo on the host system will also reflect in the container. 58 | ```bash 59 | $ rocker --nvidia --x11 --volume .:/sim_ws/src/f1tenth_gym_ros -- f1tenth_gym_ros 60 | ``` 61 | 62 | ## Without an NVIDIA gpu: 63 | 64 | **Install the following dependencies:** 65 | 66 | If your system does not support nvidia-docker2, noVNC will have to be used to forward the display. 67 | - Again you'll need **Docker**. Follow the instruction from above. 68 | - Additionally you'll need **docker-compose**. Follow the instruction [here](https://docs.docker.com/compose/install/) to install docker-compose. 69 | 70 | **Installing the simulation:** 71 | 72 | 1. Clone this repo 73 | 2. Bringup the novnc container and the sim container with docker-compose: 74 | ```bash 75 | docker-compose up 76 | ``` 77 | 3. In a separate terminal, run the following, and you'll have the a bash session in the simulation container. `tmux` is available for convenience. 78 | ```bash 79 | docker exec -it f1tenth_gym_ros-sim-1 /bin/bash 80 | ``` 81 | 4. In your browser, navigate to [http://localhost:8080/vnc.html](http://localhost:8080/vnc.html), you should see the noVNC logo with the connect button. Click the connect button to connect to the session. 82 | 83 | # Launching the Simulation 84 | 85 | 1. `tmux` is included in the contianer, so you can create multiple bash sessions in the same terminal. 86 | 2. To launch the simulation, make sure you source both the ROS2 setup script and the local workspace setup script. Run the following in the bash session from the container: 87 | ```bash 88 | $ source /opt/ros/foxy/setup.bash 89 | $ source install/local_setup.bash 90 | $ ros2 launch f1tenth_gym_ros gym_bridge_launch.py 91 | ``` 92 | A rviz window should pop up showing the simulation either on your host system or in the browser window depending on the display forwarding you chose. 93 | 94 | You can then run another node by creating another bash session in `tmux`. 95 | 96 | # Configuring the simulation 97 | - The configuration file for the simulation is at `f1tenth_gym_ros/config/sim.yaml`. 98 | - Topic names and namespaces can be configured but is recommended to leave uncahnged. 99 | - The map can be changed via the `map_path` parameter. You'll have to use the full path to the map file in the container. The map follows the ROS convention. It is assumed that the image file and the `yaml` file for the map are in the same directory with the same name. See the note below about mounting a volume to see where to put your map file. 100 | - The `num_agent` parameter can be changed to either 1 or 2 for single or two agent racing. 101 | - The ego and opponent starting pose can also be changed via parameters, these are in the global map coordinate frame. 102 | 103 | The entire directory of the repo is mounted to a workspace `/sim_ws/src` as a package. All changes made in the repo on the host system will also reflect in the container. After changing the configuration, run `colcon build` again in the container workspace to make sure the changes are reflected. 104 | 105 | # Topics published by the simulation 106 | 107 | In **single** agent: 108 | 109 | `/scan`: The ego agent's laser scan 110 | 111 | `/ego_racecar/odom`: The ego agent's odometry 112 | 113 | `/map`: The map of the environment 114 | 115 | A `tf` tree is also maintained. 116 | 117 | In **two** agents: 118 | 119 | In addition to the topics available in the single agent scenario, these topics are also available: 120 | 121 | `/opp_scan`: The opponent agent's laser scan 122 | 123 | `/ego_racecar/opp_odom`: The opponent agent's odometry for the ego agent's planner 124 | 125 | `/opp_racecar/odom`: The opponent agents' odometry 126 | 127 | `/opp_racecar/opp_odom`: The ego agent's odometry for the opponent agent's planner 128 | 129 | # Topics subscribed by the simulation 130 | 131 | In **single** agent: 132 | 133 | `/drive`: The ego agent's drive command via `AckermannDriveStamped` messages 134 | 135 | `/initalpose`: This is the topic for resetting the ego's pose via RViz's 2D Pose Estimate tool. Do **NOT** publish directly to this topic unless you know what you're doing. 136 | 137 | TODO: kb teleop topics 138 | 139 | In **two** agents: 140 | 141 | In addition to all topics in the single agent scenario, these topics are also available: 142 | 143 | `/opp_drive`: The opponent agent's drive command via `AckermannDriveStamped` messages. Note that you'll need to publish to **both** the ego's drive topic and the opponent's drive topic for the cars to move when using 2 agents. 144 | 145 | `/goal_pose`: This is the topic for resetting the opponent agent's pose via RViz's 2D Goal Pose tool. Do **NOT** publish directly to this topic unless you know what you're doing. 146 | 147 | # Keyboard Teleop 148 | 149 | The keyboard teleop node from `teleop_twist_keyboard` is also installed as part of the simulation's dependency. To enable keyboard teleop, set `kb_teleop` to `True` in `sim.yaml`. After launching the simulation, in another terminal, run: 150 | ```bash 151 | ros2 run teleop_twist_keyboard teleop_twist_keyboard 152 | ``` 153 | Then, press `i` to move forward, `u` and `o` to move forward and turn, `,` to move backwards, `m` and `.` to move backwards and turn, and `k` to stop in the terminal window running the teleop node. 154 | 155 | # Developing and creating your own agent in ROS 2 156 | 157 | There are multiple ways to launch your own agent to control the vehicles. 158 | 159 | - The first one is creating a new package for your agent in the `/sim_ws` workspace inside the sim container. After launch the simulation, launch the agent node in another bash session while the sim is running. 160 | - The second one is to create a new ROS 2 container for you agent node. Then create your own package and nodes inside. Launch the sim container and the agent container both. With default networking configurations for `docker`, the behavior is to put The two containers on the same network, and they should be able to discover and talk to each other on different topics. If you're using noVNC, create a new service in `docker-compose.yml` for your agent node. You'll also have to put your container on the same network as the sim and novnc containers. 161 | -------------------------------------------------------------------------------- /config/sim.yaml: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2020 Hongrui Zheng 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | bridge: 24 | ros__parameters: 25 | # topics and namespaces 26 | ego_namespace: 'ego_racecar' 27 | ego_scan_topic: 'scan' 28 | ego_odom_topic: 'odom' 29 | ego_opp_odom_topic: 'opp_odom' 30 | ego_drive_topic: 'drive' 31 | opp_namespace: 'opp_racecar' 32 | opp_scan_topic: 'opp_scan' 33 | opp_odom_topic: 'odom' 34 | opp_ego_odom_topic: 'opp_odom' 35 | opp_drive_topic: 'opp_drive' 36 | 37 | # transform related 38 | scan_distance_to_base_link: 0.0 39 | 40 | # laserscan parameters 41 | scan_fov: 4.7 42 | scan_beams: 1080 43 | 44 | # map parameters 45 | map_path: '/sim_ws/src/f1tenth_gym_ros/maps/levine' 46 | map_img_ext: '.png' 47 | 48 | # opponent parameters 49 | num_agent: 1 50 | 51 | # ego starting pose on map 52 | sx: 0.0 53 | sy: 0.0 54 | stheta: 0.0 55 | 56 | # opp starting pose on map 57 | sx1: 2.0 58 | sy1: 0.5 59 | stheta1: 0.0 60 | 61 | # teleop 62 | kb_teleop: True -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2020 Hongrui Zheng 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | version: '3' 24 | services: 25 | sim: 26 | image: f1tenth_gym_ros 27 | build: ./ 28 | volumes: 29 | - .:/sim_ws/src/f1tenth_gym_ros 30 | environment: 31 | - DISPLAY=novnc:0.0 32 | networks: 33 | - x11 34 | stdin_open: true 35 | tty: true 36 | novnc: 37 | image: theasp/novnc:latest 38 | environment: 39 | - DISPLAY_WIDTH=1728 40 | - DISPLAY_HEIGHT=972 41 | ports: 42 | - "8080:8080" 43 | networks: 44 | - x11 45 | networks: 46 | x11: 47 | -------------------------------------------------------------------------------- /f1tenth_gym_ros.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f1tenth/f1tenth_gym_ros/151820202d6b530491d22c308f22638182f1298d/f1tenth_gym_ros.png -------------------------------------------------------------------------------- /f1tenth_gym_ros/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f1tenth/f1tenth_gym_ros/151820202d6b530491d22c308f22638182f1298d/f1tenth_gym_ros/__init__.py -------------------------------------------------------------------------------- /f1tenth_gym_ros/gym_bridge.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2020 Hongrui Zheng 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import rclpy 24 | from rclpy.node import Node 25 | 26 | from sensor_msgs.msg import LaserScan 27 | from nav_msgs.msg import Odometry 28 | from geometry_msgs.msg import PoseStamped 29 | from geometry_msgs.msg import PoseWithCovarianceStamped 30 | from geometry_msgs.msg import Twist 31 | from geometry_msgs.msg import TransformStamped 32 | from geometry_msgs.msg import Transform 33 | from geometry_msgs.msg import Quaternion 34 | from ackermann_msgs.msg import AckermannDriveStamped 35 | from tf2_ros import TransformBroadcaster 36 | 37 | import gym 38 | import numpy as np 39 | from transforms3d import euler 40 | 41 | class GymBridge(Node): 42 | def __init__(self): 43 | super().__init__('gym_bridge') 44 | 45 | self.declare_parameter('ego_namespace') 46 | self.declare_parameter('ego_odom_topic') 47 | self.declare_parameter('ego_opp_odom_topic') 48 | self.declare_parameter('ego_scan_topic') 49 | self.declare_parameter('ego_drive_topic') 50 | self.declare_parameter('opp_namespace') 51 | self.declare_parameter('opp_odom_topic') 52 | self.declare_parameter('opp_ego_odom_topic') 53 | self.declare_parameter('opp_scan_topic') 54 | self.declare_parameter('opp_drive_topic') 55 | self.declare_parameter('scan_distance_to_base_link') 56 | self.declare_parameter('scan_fov') 57 | self.declare_parameter('scan_beams') 58 | self.declare_parameter('map_path') 59 | self.declare_parameter('map_img_ext') 60 | self.declare_parameter('num_agent') 61 | self.declare_parameter('sx') 62 | self.declare_parameter('sy') 63 | self.declare_parameter('stheta') 64 | self.declare_parameter('sx1') 65 | self.declare_parameter('sy1') 66 | self.declare_parameter('stheta1') 67 | self.declare_parameter('kb_teleop') 68 | 69 | # check num_agents 70 | num_agents = self.get_parameter('num_agent').value 71 | if num_agents < 1 or num_agents > 2: 72 | raise ValueError('num_agents should be either 1 or 2.') 73 | elif type(num_agents) != int: 74 | raise ValueError('num_agents should be an int.') 75 | 76 | # env backend 77 | self.env = gym.make('f110_gym:f110-v0', 78 | map=self.get_parameter('map_path').value, 79 | map_ext=self.get_parameter('map_img_ext').value, 80 | num_agents=num_agents) 81 | 82 | sx = self.get_parameter('sx').value 83 | sy = self.get_parameter('sy').value 84 | stheta = self.get_parameter('stheta').value 85 | self.ego_pose = [sx, sy, stheta] 86 | self.ego_speed = [0.0, 0.0, 0.0] 87 | self.ego_requested_speed = 0.0 88 | self.ego_steer = 0.0 89 | self.ego_collision = False 90 | ego_scan_topic = self.get_parameter('ego_scan_topic').value 91 | ego_drive_topic = self.get_parameter('ego_drive_topic').value 92 | scan_fov = self.get_parameter('scan_fov').value 93 | scan_beams = self.get_parameter('scan_beams').value 94 | self.angle_min = -scan_fov / 2. 95 | self.angle_max = scan_fov / 2. 96 | self.angle_inc = scan_fov / scan_beams 97 | self.ego_namespace = self.get_parameter('ego_namespace').value 98 | ego_odom_topic = self.ego_namespace + '/' + self.get_parameter('ego_odom_topic').value 99 | self.scan_distance_to_base_link = self.get_parameter('scan_distance_to_base_link').value 100 | 101 | if num_agents == 2: 102 | self.has_opp = True 103 | self.opp_namespace = self.get_parameter('opp_namespace').value 104 | sx1 = self.get_parameter('sx1').value 105 | sy1 = self.get_parameter('sy1').value 106 | stheta1 = self.get_parameter('stheta1').value 107 | self.opp_pose = [sx1, sy1, stheta1] 108 | self.opp_speed = [0.0, 0.0, 0.0] 109 | self.opp_requested_speed = 0.0 110 | self.opp_steer = 0.0 111 | self.opp_collision = False 112 | self.obs, _ , self.done, _ = self.env.reset(np.array([[sx, sy, stheta], [sx1, sy1, stheta1]])) 113 | self.ego_scan = list(self.obs['scans'][0]) 114 | self.opp_scan = list(self.obs['scans'][1]) 115 | 116 | opp_scan_topic = self.get_parameter('opp_scan_topic').value 117 | opp_odom_topic = self.opp_namespace + '/' + self.get_parameter('opp_odom_topic').value 118 | opp_drive_topic = self.get_parameter('opp_drive_topic').value 119 | 120 | ego_opp_odom_topic = self.ego_namespace + '/' + self.get_parameter('ego_opp_odom_topic').value 121 | opp_ego_odom_topic = self.opp_namespace + '/' + self.get_parameter('opp_ego_odom_topic').value 122 | else: 123 | self.has_opp = False 124 | self.obs, _ , self.done, _ = self.env.reset(np.array([[sx, sy, stheta]])) 125 | self.ego_scan = list(self.obs['scans'][0]) 126 | 127 | # sim physical step timer 128 | self.drive_timer = self.create_timer(0.01, self.drive_timer_callback) 129 | # topic publishing timer 130 | self.timer = self.create_timer(0.004, self.timer_callback) 131 | 132 | # transform broadcaster 133 | self.br = TransformBroadcaster(self) 134 | 135 | # publishers 136 | self.ego_scan_pub = self.create_publisher(LaserScan, ego_scan_topic, 10) 137 | self.ego_odom_pub = self.create_publisher(Odometry, ego_odom_topic, 10) 138 | self.ego_drive_published = False 139 | if num_agents == 2: 140 | self.opp_scan_pub = self.create_publisher(LaserScan, opp_scan_topic, 10) 141 | self.ego_opp_odom_pub = self.create_publisher(Odometry, ego_opp_odom_topic, 10) 142 | self.opp_odom_pub = self.create_publisher(Odometry, opp_odom_topic, 10) 143 | self.opp_ego_odom_pub = self.create_publisher(Odometry, opp_ego_odom_topic, 10) 144 | self.opp_drive_published = False 145 | 146 | # subscribers 147 | self.ego_drive_sub = self.create_subscription( 148 | AckermannDriveStamped, 149 | ego_drive_topic, 150 | self.drive_callback, 151 | 10) 152 | self.ego_reset_sub = self.create_subscription( 153 | PoseWithCovarianceStamped, 154 | '/initialpose', 155 | self.ego_reset_callback, 156 | 10) 157 | if num_agents == 2: 158 | self.opp_drive_sub = self.create_subscription( 159 | AckermannDriveStamped, 160 | opp_drive_topic, 161 | self.opp_drive_callback, 162 | 10) 163 | self.opp_reset_sub = self.create_subscription( 164 | PoseStamped, 165 | '/goal_pose', 166 | self.opp_reset_callback, 167 | 10) 168 | 169 | if self.get_parameter('kb_teleop').value: 170 | self.teleop_sub = self.create_subscription( 171 | Twist, 172 | '/cmd_vel', 173 | self.teleop_callback, 174 | 10) 175 | 176 | 177 | def drive_callback(self, drive_msg): 178 | self.ego_requested_speed = drive_msg.drive.speed 179 | self.ego_steer = drive_msg.drive.steering_angle 180 | self.ego_drive_published = True 181 | 182 | def opp_drive_callback(self, drive_msg): 183 | self.opp_requested_speed = drive_msg.drive.speed 184 | self.opp_steer = drive_msg.drive.steering_angle 185 | self.opp_drive_published = True 186 | 187 | def ego_reset_callback(self, pose_msg): 188 | rx = pose_msg.pose.pose.position.x 189 | ry = pose_msg.pose.pose.position.y 190 | rqx = pose_msg.pose.pose.orientation.x 191 | rqy = pose_msg.pose.pose.orientation.y 192 | rqz = pose_msg.pose.pose.orientation.z 193 | rqw = pose_msg.pose.pose.orientation.w 194 | _, _, rtheta = euler.quat2euler([rqw, rqx, rqy, rqz], axes='sxyz') 195 | if self.has_opp: 196 | opp_pose = [self.obs['poses_x'][1], self.obs['poses_y'][1], self.obs['poses_theta'][1]] 197 | self.obs, _ , self.done, _ = self.env.reset(np.array([[rx, ry, rtheta], opp_pose])) 198 | else: 199 | self.obs, _ , self.done, _ = self.env.reset(np.array([[rx, ry, rtheta]])) 200 | 201 | def opp_reset_callback(self, pose_msg): 202 | if self.has_opp: 203 | rx = pose_msg.pose.position.x 204 | ry = pose_msg.pose.position.y 205 | rqx = pose_msg.pose.orientation.x 206 | rqy = pose_msg.pose.orientation.y 207 | rqz = pose_msg.pose.orientation.z 208 | rqw = pose_msg.pose.orientation.w 209 | _, _, rtheta = euler.quat2euler([rqw, rqx, rqy, rqz], axes='sxyz') 210 | self.obs, _ , self.done, _ = self.env.reset(np.array([list(self.ego_pose), [rx, ry, rtheta]])) 211 | def teleop_callback(self, twist_msg): 212 | if not self.ego_drive_published: 213 | self.ego_drive_published = True 214 | 215 | self.ego_requested_speed = twist_msg.linear.x 216 | 217 | if twist_msg.angular.z > 0.0: 218 | self.ego_steer = 0.3 219 | elif twist_msg.angular.z < 0.0: 220 | self.ego_steer = -0.3 221 | else: 222 | self.ego_steer = 0.0 223 | 224 | def drive_timer_callback(self): 225 | if self.ego_drive_published and not self.has_opp: 226 | self.obs, _, self.done, _ = self.env.step(np.array([[self.ego_steer, self.ego_requested_speed]])) 227 | elif self.ego_drive_published and self.has_opp and self.opp_drive_published: 228 | self.obs, _, self.done, _ = self.env.step(np.array([[self.ego_steer, self.ego_requested_speed], [self.opp_steer, self.opp_requested_speed]])) 229 | self._update_sim_state() 230 | 231 | def timer_callback(self): 232 | ts = self.get_clock().now().to_msg() 233 | 234 | # pub scans 235 | scan = LaserScan() 236 | scan.header.stamp = ts 237 | scan.header.frame_id = self.ego_namespace + '/laser' 238 | scan.angle_min = self.angle_min 239 | scan.angle_max = self.angle_max 240 | scan.angle_increment = self.angle_inc 241 | scan.range_min = 0. 242 | scan.range_max = 30. 243 | scan.ranges = self.ego_scan 244 | self.ego_scan_pub.publish(scan) 245 | 246 | if self.has_opp: 247 | opp_scan = LaserScan() 248 | opp_scan.header.stamp = ts 249 | opp_scan.header.frame_id = self.opp_namespace + '/laser' 250 | opp_scan.angle_min = self.angle_min 251 | opp_scan.angle_max = self.angle_max 252 | opp_scan.angle_increment = self.angle_inc 253 | opp_scan.range_min = 0. 254 | opp_scan.range_max = 30. 255 | opp_scan.ranges = self.opp_scan 256 | self.opp_scan_pub.publish(opp_scan) 257 | 258 | # pub tf 259 | self._publish_odom(ts) 260 | self._publish_transforms(ts) 261 | self._publish_laser_transforms(ts) 262 | self._publish_wheel_transforms(ts) 263 | 264 | def _update_sim_state(self): 265 | self.ego_scan = list(self.obs['scans'][0]) 266 | if self.has_opp: 267 | self.opp_scan = list(self.obs['scans'][1]) 268 | self.opp_pose[0] = self.obs['poses_x'][1] 269 | self.opp_pose[1] = self.obs['poses_y'][1] 270 | self.opp_pose[2] = self.obs['poses_theta'][1] 271 | self.opp_speed[0] = self.obs['linear_vels_x'][1] 272 | self.opp_speed[1] = self.obs['linear_vels_y'][1] 273 | self.opp_speed[2] = self.obs['ang_vels_z'][1] 274 | 275 | self.ego_pose[0] = self.obs['poses_x'][0] 276 | self.ego_pose[1] = self.obs['poses_y'][0] 277 | self.ego_pose[2] = self.obs['poses_theta'][0] 278 | self.ego_speed[0] = self.obs['linear_vels_x'][0] 279 | self.ego_speed[1] = self.obs['linear_vels_y'][0] 280 | self.ego_speed[2] = self.obs['ang_vels_z'][0] 281 | 282 | 283 | 284 | def _publish_odom(self, ts): 285 | ego_odom = Odometry() 286 | ego_odom.header.stamp = ts 287 | ego_odom.header.frame_id = 'map' 288 | ego_odom.child_frame_id = self.ego_namespace + '/base_link' 289 | ego_odom.pose.pose.position.x = self.ego_pose[0] 290 | ego_odom.pose.pose.position.y = self.ego_pose[1] 291 | ego_quat = euler.euler2quat(0., 0., self.ego_pose[2], axes='sxyz') 292 | ego_odom.pose.pose.orientation.x = ego_quat[1] 293 | ego_odom.pose.pose.orientation.y = ego_quat[2] 294 | ego_odom.pose.pose.orientation.z = ego_quat[3] 295 | ego_odom.pose.pose.orientation.w = ego_quat[0] 296 | ego_odom.twist.twist.linear.x = self.ego_speed[0] 297 | ego_odom.twist.twist.linear.y = self.ego_speed[1] 298 | ego_odom.twist.twist.angular.z = self.ego_speed[2] 299 | self.ego_odom_pub.publish(ego_odom) 300 | 301 | if self.has_opp: 302 | opp_odom = Odometry() 303 | opp_odom.header.stamp = ts 304 | opp_odom.header.frame_id = 'map' 305 | opp_odom.child_frame_id = self.opp_namespace + '/base_link' 306 | opp_odom.pose.pose.position.x = self.opp_pose[0] 307 | opp_odom.pose.pose.position.y = self.opp_pose[1] 308 | opp_quat = euler.euler2quat(0., 0., self.opp_pose[2], axes='sxyz') 309 | opp_odom.pose.pose.orientation.x = opp_quat[1] 310 | opp_odom.pose.pose.orientation.y = opp_quat[2] 311 | opp_odom.pose.pose.orientation.z = opp_quat[3] 312 | opp_odom.pose.pose.orientation.w = opp_quat[0] 313 | opp_odom.twist.twist.linear.x = self.opp_speed[0] 314 | opp_odom.twist.twist.linear.y = self.opp_speed[1] 315 | opp_odom.twist.twist.angular.z = self.opp_speed[2] 316 | self.opp_odom_pub.publish(opp_odom) 317 | self.opp_ego_odom_pub.publish(ego_odom) 318 | self.ego_opp_odom_pub.publish(opp_odom) 319 | 320 | def _publish_transforms(self, ts): 321 | ego_t = Transform() 322 | ego_t.translation.x = self.ego_pose[0] 323 | ego_t.translation.y = self.ego_pose[1] 324 | ego_t.translation.z = 0.0 325 | ego_quat = euler.euler2quat(0.0, 0.0, self.ego_pose[2], axes='sxyz') 326 | ego_t.rotation.x = ego_quat[1] 327 | ego_t.rotation.y = ego_quat[2] 328 | ego_t.rotation.z = ego_quat[3] 329 | ego_t.rotation.w = ego_quat[0] 330 | 331 | ego_ts = TransformStamped() 332 | ego_ts.transform = ego_t 333 | ego_ts.header.stamp = ts 334 | ego_ts.header.frame_id = 'map' 335 | ego_ts.child_frame_id = self.ego_namespace + '/base_link' 336 | self.br.sendTransform(ego_ts) 337 | 338 | if self.has_opp: 339 | opp_t = Transform() 340 | opp_t.translation.x = self.opp_pose[0] 341 | opp_t.translation.y = self.opp_pose[1] 342 | opp_t.translation.z = 0.0 343 | opp_quat = euler.euler2quat(0.0, 0.0, self.opp_pose[2], axes='sxyz') 344 | opp_t.rotation.x = opp_quat[1] 345 | opp_t.rotation.y = opp_quat[2] 346 | opp_t.rotation.z = opp_quat[3] 347 | opp_t.rotation.w = opp_quat[0] 348 | 349 | opp_ts = TransformStamped() 350 | opp_ts.transform = opp_t 351 | opp_ts.header.stamp = ts 352 | opp_ts.header.frame_id = 'map' 353 | opp_ts.child_frame_id = self.opp_namespace + '/base_link' 354 | self.br.sendTransform(opp_ts) 355 | 356 | def _publish_wheel_transforms(self, ts): 357 | ego_wheel_ts = TransformStamped() 358 | ego_wheel_quat = euler.euler2quat(0., 0., self.ego_steer, axes='sxyz') 359 | ego_wheel_ts.transform.rotation.x = ego_wheel_quat[1] 360 | ego_wheel_ts.transform.rotation.y = ego_wheel_quat[2] 361 | ego_wheel_ts.transform.rotation.z = ego_wheel_quat[3] 362 | ego_wheel_ts.transform.rotation.w = ego_wheel_quat[0] 363 | ego_wheel_ts.header.stamp = ts 364 | ego_wheel_ts.header.frame_id = self.ego_namespace + '/front_left_hinge' 365 | ego_wheel_ts.child_frame_id = self.ego_namespace + '/front_left_wheel' 366 | self.br.sendTransform(ego_wheel_ts) 367 | ego_wheel_ts.header.frame_id = self.ego_namespace + '/front_right_hinge' 368 | ego_wheel_ts.child_frame_id = self.ego_namespace + '/front_right_wheel' 369 | self.br.sendTransform(ego_wheel_ts) 370 | 371 | if self.has_opp: 372 | opp_wheel_ts = TransformStamped() 373 | opp_wheel_quat = euler.euler2quat(0., 0., self.opp_steer, axes='sxyz') 374 | opp_wheel_ts.transform.rotation.x = opp_wheel_quat[1] 375 | opp_wheel_ts.transform.rotation.y = opp_wheel_quat[2] 376 | opp_wheel_ts.transform.rotation.z = opp_wheel_quat[3] 377 | opp_wheel_ts.transform.rotation.w = opp_wheel_quat[0] 378 | opp_wheel_ts.header.stamp = ts 379 | opp_wheel_ts.header.frame_id = self.opp_namespace + '/front_left_hinge' 380 | opp_wheel_ts.child_frame_id = self.opp_namespace + '/front_left_wheel' 381 | self.br.sendTransform(opp_wheel_ts) 382 | opp_wheel_ts.header.frame_id = self.opp_namespace + '/front_right_hinge' 383 | opp_wheel_ts.child_frame_id = self.opp_namespace + '/front_right_wheel' 384 | self.br.sendTransform(opp_wheel_ts) 385 | 386 | def _publish_laser_transforms(self, ts): 387 | ego_scan_ts = TransformStamped() 388 | ego_scan_ts.transform.translation.x = self.scan_distance_to_base_link 389 | # ego_scan_ts.transform.translation.z = 0.04+0.1+0.025 390 | ego_scan_ts.transform.rotation.w = 1. 391 | ego_scan_ts.header.stamp = ts 392 | ego_scan_ts.header.frame_id = self.ego_namespace + '/base_link' 393 | ego_scan_ts.child_frame_id = self.ego_namespace + '/laser' 394 | self.br.sendTransform(ego_scan_ts) 395 | 396 | if self.has_opp: 397 | opp_scan_ts = TransformStamped() 398 | opp_scan_ts.transform.translation.x = self.scan_distance_to_base_link 399 | opp_scan_ts.transform.rotation.w = 1. 400 | opp_scan_ts.header.stamp = ts 401 | opp_scan_ts.header.frame_id = self.opp_namespace + '/base_link' 402 | opp_scan_ts.child_frame_id = self.opp_namespace + '/laser' 403 | self.br.sendTransform(opp_scan_ts) 404 | 405 | def main(args=None): 406 | rclpy.init(args=args) 407 | gym_bridge = GymBridge() 408 | rclpy.spin(gym_bridge) 409 | 410 | if __name__ == '__main__': 411 | main() 412 | -------------------------------------------------------------------------------- /launch/ego_racecar.xacro: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /launch/gym_bridge.rviz: -------------------------------------------------------------------------------- 1 | Panels: 2 | - Class: rviz_common/Displays 3 | Help Height: 78 4 | Name: Displays 5 | Property Tree Widget: 6 | Expanded: 7 | - /Global Options1 8 | - /Status1 9 | - /Map1/Topic1 10 | Splitter Ratio: 0.5 11 | Tree Height: 802 12 | - Class: rviz_common/Selection 13 | Name: Selection 14 | - Class: rviz_common/Tool Properties 15 | Expanded: 16 | - /2D Goal Pose1 17 | - /Publish Point1 18 | Name: Tool Properties 19 | Splitter Ratio: 0.5886790156364441 20 | - Class: rviz_common/Views 21 | Expanded: 22 | - /Current View1 23 | Name: Views 24 | Splitter Ratio: 0.5 25 | Visualization Manager: 26 | Class: "" 27 | Displays: 28 | - Alpha: 0.5 29 | Cell Size: 1 30 | Class: rviz_default_plugins/Grid 31 | Color: 160; 160; 164 32 | Enabled: true 33 | Line Style: 34 | Line Width: 0.029999999329447746 35 | Value: Lines 36 | Name: Grid 37 | Normal Cell Count: 0 38 | Offset: 39 | X: 0 40 | Y: 0 41 | Z: 0 42 | Plane: XY 43 | Plane Cell Count: 10 44 | Reference Frame: 45 | Value: true 46 | - Alpha: 0.699999988079071 47 | Class: rviz_default_plugins/Map 48 | Color Scheme: map 49 | Draw Behind: false 50 | Enabled: true 51 | Name: Map 52 | Topic: 53 | Depth: 5 54 | Durability Policy: Transient Local 55 | History Policy: Keep Last 56 | Reliability Policy: Reliable 57 | Value: /map 58 | Update Topic: 59 | Depth: 5 60 | Durability Policy: Volatile 61 | History Policy: Keep Last 62 | Reliability Policy: Reliable 63 | Value: /map_updates 64 | Use Timestamp: false 65 | Value: true 66 | - Alpha: 1 67 | Autocompute Intensity Bounds: true 68 | Autocompute Value Bounds: 69 | Max Value: 30.2736759185791 70 | Min Value: -0.7132953405380249 71 | Value: true 72 | Axis: X 73 | Channel Name: intensity 74 | Class: rviz_default_plugins/LaserScan 75 | Color: 255; 255; 255 76 | Color Transformer: AxisColor 77 | Decay Time: 0 78 | Enabled: true 79 | Invert Rainbow: false 80 | Max Color: 255; 255; 255 81 | Max Intensity: 4096 82 | Min Color: 0; 0; 0 83 | Min Intensity: 0 84 | Name: LaserScan 85 | Position Transformer: XYZ 86 | Selectable: true 87 | Size (Pixels): 3 88 | Size (m): 0.10000000149011612 89 | Style: Flat Squares 90 | Topic: 91 | Depth: 5 92 | Durability Policy: Volatile 93 | History Policy: Keep Last 94 | Reliability Policy: Reliable 95 | Value: /scan 96 | Use Fixed Frame: true 97 | Use rainbow: true 98 | Value: true 99 | - Alpha: 1 100 | Class: rviz_default_plugins/RobotModel 101 | Collision Enabled: false 102 | Description File: "" 103 | Description Source: Topic 104 | Description Topic: 105 | Depth: 5 106 | Durability Policy: Volatile 107 | History Policy: Keep Last 108 | Reliability Policy: Reliable 109 | Value: /ego_robot_description 110 | Enabled: true 111 | Links: 112 | All Links Enabled: true 113 | Expand Joint Details: false 114 | Expand Link Details: false 115 | Expand Tree: false 116 | Link Tree Style: Links in Alphabetic Order 117 | ego_racecar/back_left_wheel: 118 | Alpha: 1 119 | Show Axes: false 120 | Show Trail: false 121 | Value: true 122 | ego_racecar/back_right_wheel: 123 | Alpha: 1 124 | Show Axes: false 125 | Show Trail: false 126 | Value: true 127 | ego_racecar/base_link: 128 | Alpha: 1 129 | Show Axes: false 130 | Show Trail: false 131 | Value: true 132 | ego_racecar/front_left_hinge: 133 | Alpha: 1 134 | Show Axes: false 135 | Show Trail: false 136 | ego_racecar/front_left_wheel: 137 | Alpha: 1 138 | Show Axes: false 139 | Show Trail: false 140 | Value: true 141 | ego_racecar/front_right_hinge: 142 | Alpha: 1 143 | Show Axes: false 144 | Show Trail: false 145 | ego_racecar/front_right_wheel: 146 | Alpha: 1 147 | Show Axes: false 148 | Show Trail: false 149 | Value: true 150 | ego_racecar/laser_model: 151 | Alpha: 1 152 | Show Axes: false 153 | Show Trail: false 154 | Value: true 155 | Name: RobotModel 156 | TF Prefix: "" 157 | Update Interval: 0 158 | Value: true 159 | Visual Enabled: true 160 | Enabled: true 161 | Global Options: 162 | Background Color: 48; 48; 48 163 | Fixed Frame: map 164 | Frame Rate: 30 165 | Name: root 166 | Tools: 167 | - Class: rviz_default_plugins/Interact 168 | Hide Inactive Objects: true 169 | - Class: rviz_default_plugins/MoveCamera 170 | - Class: rviz_default_plugins/Select 171 | - Class: rviz_default_plugins/FocusCamera 172 | - Class: rviz_default_plugins/Measure 173 | Line color: 128; 128; 0 174 | - Class: rviz_default_plugins/SetInitialPose 175 | Topic: 176 | Depth: 5 177 | Durability Policy: Volatile 178 | History Policy: Keep Last 179 | Reliability Policy: Reliable 180 | Value: /initialpose 181 | - Class: rviz_default_plugins/SetGoal 182 | Topic: 183 | Depth: 5 184 | Durability Policy: Volatile 185 | History Policy: Keep Last 186 | Reliability Policy: Reliable 187 | Value: /goal_pose 188 | - Class: rviz_default_plugins/PublishPoint 189 | Single click: true 190 | Topic: 191 | Depth: 5 192 | Durability Policy: Volatile 193 | History Policy: Keep Last 194 | Reliability Policy: Reliable 195 | Value: /clicked_point 196 | Transformation: 197 | Current: 198 | Class: rviz_default_plugins/TF 199 | Value: true 200 | Views: 201 | Current: 202 | Class: rviz_default_plugins/Orbit 203 | Distance: 10 204 | Enable Stereo Rendering: 205 | Stereo Eye Separation: 0.05999999865889549 206 | Stereo Focal Distance: 1 207 | Swap Stereo Eyes: false 208 | Value: false 209 | Focal Point: 210 | X: 0 211 | Y: 0 212 | Z: 0 213 | Focal Shape Fixed Size: true 214 | Focal Shape Size: 0.05000000074505806 215 | Invert Z Axis: false 216 | Name: Current View 217 | Near Clip Distance: 0.009999999776482582 218 | Pitch: 0.5353981852531433 219 | Target Frame: 220 | Value: Orbit (rviz) 221 | Yaw: 3.1453983783721924 222 | Saved: ~ 223 | Window Geometry: 224 | Displays: 225 | collapsed: false 226 | Height: 900 227 | Hide Left Dock: false 228 | Hide Right Dock: false 229 | QMainWindow State: 000000ff00000000fd000000040000000000000156000003abfc0200000008fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003b000003ab000000c700fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261000000010000010f000003abfc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073010000003b000003ab000000a000fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d006501000000000000045000000000000000000000075b000003ab00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 230 | Selection: 231 | collapsed: false 232 | Tool Properties: 233 | collapsed: false 234 | Views: 235 | collapsed: false 236 | Width: 1600 237 | X: 52 238 | Y: 25 239 | -------------------------------------------------------------------------------- /launch/gym_bridge_launch.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2020 Hongrui Zheng 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from launch import LaunchDescription 24 | from launch_ros.actions import Node 25 | from launch.substitutions import Command 26 | from ament_index_python.packages import get_package_share_directory 27 | import os 28 | import yaml 29 | 30 | def generate_launch_description(): 31 | ld = LaunchDescription() 32 | config = os.path.join( 33 | get_package_share_directory('f1tenth_gym_ros'), 34 | 'config', 35 | 'sim.yaml' 36 | ) 37 | config_dict = yaml.safe_load(open(config, 'r')) 38 | has_opp = config_dict['bridge']['ros__parameters']['num_agent'] > 1 39 | teleop = config_dict['bridge']['ros__parameters']['kb_teleop'] 40 | 41 | bridge_node = Node( 42 | package='f1tenth_gym_ros', 43 | executable='gym_bridge', 44 | name='bridge', 45 | parameters=[config] 46 | ) 47 | rviz_node = Node( 48 | package='rviz2', 49 | executable='rviz2', 50 | name='rviz', 51 | arguments=['-d', os.path.join(get_package_share_directory('f1tenth_gym_ros'), 'launch', 'gym_bridge.rviz')] 52 | ) 53 | map_server_node = Node( 54 | package='nav2_map_server', 55 | executable='map_server', 56 | parameters=[{'yaml_filename': config_dict['bridge']['ros__parameters']['map_path'] + '.yaml'}, 57 | {'topic': 'map'}, 58 | {'frame_id': 'map'}, 59 | {'output': 'screen'}, 60 | {'use_sim_time': True}] 61 | ) 62 | nav_lifecycle_node = Node( 63 | package='nav2_lifecycle_manager', 64 | executable='lifecycle_manager', 65 | name='lifecycle_manager_localization', 66 | output='screen', 67 | parameters=[{'use_sim_time': True}, 68 | {'autostart': True}, 69 | {'node_names': ['map_server']}] 70 | ) 71 | ego_robot_publisher = Node( 72 | package='robot_state_publisher', 73 | executable='robot_state_publisher', 74 | name='ego_robot_state_publisher', 75 | parameters=[{'robot_description': Command(['xacro ', os.path.join(get_package_share_directory('f1tenth_gym_ros'), 'launch', 'ego_racecar.xacro')])}], 76 | remappings=[('/robot_description', 'ego_robot_description')] 77 | ) 78 | opp_robot_publisher = Node( 79 | package='robot_state_publisher', 80 | executable='robot_state_publisher', 81 | name='opp_robot_state_publisher', 82 | parameters=[{'robot_description': Command(['xacro ', os.path.join(get_package_share_directory('f1tenth_gym_ros'), 'launch', 'opp_racecar.xacro')])}], 83 | remappings=[('/robot_description', 'opp_robot_description')] 84 | ) 85 | 86 | # finalize 87 | ld.add_action(rviz_node) 88 | ld.add_action(bridge_node) 89 | ld.add_action(nav_lifecycle_node) 90 | ld.add_action(map_server_node) 91 | ld.add_action(ego_robot_publisher) 92 | if has_opp: 93 | ld.add_action(opp_robot_publisher) 94 | 95 | return ld -------------------------------------------------------------------------------- /launch/opp_racecar.xacro: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /maps/Spielberg_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f1tenth/f1tenth_gym_ros/151820202d6b530491d22c308f22638182f1298d/maps/Spielberg_map.png -------------------------------------------------------------------------------- /maps/Spielberg_map.yaml: -------------------------------------------------------------------------------- 1 | image: Spielberg_map.png 2 | resolution: 0.05796 3 | origin: [-84.85359914210505,-36.30299725862132, 0.000000] 4 | negate: 0 5 | occupied_thresh: 0.45 6 | free_thresh: 0.196 7 | -------------------------------------------------------------------------------- /maps/levine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f1tenth/f1tenth_gym_ros/151820202d6b530491d22c308f22638182f1298d/maps/levine.png -------------------------------------------------------------------------------- /maps/levine.yaml: -------------------------------------------------------------------------------- 1 | image: levine.png 2 | resolution: 0.050000 3 | origin: [-51.224998, -51.224998, 0.000000] 4 | negate: 0 5 | occupied_thresh: 0.65 6 | free_thresh: 0.196 -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | f1tenth_gym_ros 5 | 0.0.0 6 | Bridge for using f1tenth_gym in ROS2 7 | Billy Zheng 8 | MIT 9 | 10 | geometry_msgs 11 | nav_msgs 12 | sensor_msgs 13 | ackermann_msgs 14 | launch 15 | launch_ros 16 | tf2_ros 17 | rclpy 18 | joint_state_publisher 19 | xacro 20 | nav2_map_server 21 | nav2_lifecycle_manager 22 | teleop_twist_keyboard 23 | 24 | python-transforms3d-pip 25 | 26 | ament_copyright 27 | ament_flake8 28 | ament_pep257 29 | python3-pytest 30 | 31 | 32 | ament_python 33 | 34 | 35 | -------------------------------------------------------------------------------- /resource/f1tenth_gym_ros: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f1tenth/f1tenth_gym_ros/151820202d6b530491d22c308f22638182f1298d/resource/f1tenth_gym_ros -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [develop] 2 | script_dir=$base/lib/f1tenth_gym_ros 3 | [install] 4 | install_scripts=$base/lib/f1tenth_gym_ros 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | import os 3 | from glob import glob 4 | 5 | package_name = 'f1tenth_gym_ros' 6 | 7 | setup( 8 | name=package_name, 9 | version='0.0.0', 10 | packages=[package_name], 11 | data_files=[ 12 | ('share/ament_index/resource_index/packages', 13 | ['resource/' + package_name]), 14 | ('share/' + package_name, ['package.xml']), 15 | (os.path.join('share', package_name, 'launch'), glob('launch/*.py')), 16 | (os.path.join('share', package_name, 'launch'), glob('launch/*.xacro')), 17 | (os.path.join('share', package_name, 'launch'), glob('launch/*.rviz')), 18 | (os.path.join('share', package_name, 'config'), glob('config/*.yaml')), 19 | ], 20 | install_requires=['setuptools'], 21 | zip_safe=True, 22 | maintainer='Billy Zheng', 23 | maintainer_email='billyzheng.bz@gmail.com', 24 | description='Bridge for using f1tenth_gym in ROS2', 25 | license='MIT', 26 | tests_require=['pytest'], 27 | entry_points={ 28 | 'console_scripts': [ 29 | 'gym_bridge = f1tenth_gym_ros.gym_bridge:main' 30 | ], 31 | }, 32 | ) 33 | -------------------------------------------------------------------------------- /test/test_copyright.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from ament_copyright.main import main 16 | import pytest 17 | 18 | 19 | @pytest.mark.copyright 20 | @pytest.mark.linter 21 | def test_copyright(): 22 | rc = main(argv=['.', 'test']) 23 | assert rc == 0, 'Found errors' 24 | -------------------------------------------------------------------------------- /test/test_flake8.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from ament_flake8.main import main_with_errors 16 | import pytest 17 | 18 | 19 | @pytest.mark.flake8 20 | @pytest.mark.linter 21 | def test_flake8(): 22 | rc, errors = main_with_errors(argv=[]) 23 | assert rc == 0, \ 24 | 'Found %d code style errors / warnings:\n' % len(errors) + \ 25 | '\n'.join(errors) 26 | -------------------------------------------------------------------------------- /test/test_pep257.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from ament_pep257.main import main 16 | import pytest 17 | 18 | 19 | @pytest.mark.linter 20 | @pytest.mark.pep257 21 | def test_pep257(): 22 | rc = main(argv=['.', 'test']) 23 | assert rc == 0, 'Found code style errors / warnings' 24 | --------------------------------------------------------------------------------