Thursday, 15 August 2024

Using a Game Controller with ROS2 in Docker

Continuing our learning of ROS2 running in Docker containers on top of Raspberry Pi OS (see previous posts in this blog). ROS comes with joystick support in the form of the joy package. https://docs.ros.org/en/jazzy/p/joy/ 
To get this working inside Docker, we need to map our joystick input to our container. In the previous post, we created a Docker Compose configuration file which looked like this:

services:

    sim:

      image: ros_docker:latest

      command: ros2 run turtlesim turtlesim_node

      environment:

        DISPLAY:

        QT_X11_NO_MITSHM: 1

      volumes:

        - /tmp/.X11-unix:/tmp/.X11-unix:rw

    dev-build:

      image: ros_docker:latest

      command: rqt

      environment:

        DISPLAY:

        QT_X11_NO_MITSHM: 1

      volumes:

        - /tmp/.X11-unix:/tmp/.X11-unix:rw

        - ~/ros2_ws:/ros2_ws


Before we try mapping our controller, let’s check it is actually connected to the Raspberry Pi. We are using a bluetooth game controller here. This should work with a PS4 Dualshock Controller, an X-Box One S wireless controller, or an 8BitDo Pro2 controller (which can mimic various controllers). Start by pairing your controller through the Raspberry Pi OS bluetooth tray icon. Select ‘Add Device’, put the controller into pairing mode, wait for it to appear in the detected devices list and then select it and press ‘Pair’.

Once you have a controller connected, type the following command in a terminal:


ls /dev/input


You should see among the various event items, there is a 'js' item.

 

$ ls /dev/input  

by-id    event0  event2  event4  event6  event9  mice

by-path  event1  event3  event5  event7  js0     mouse0


In my case I saw js0 which indicates my game controller is available as an input device.


Now we can map our input devices into our docker container. I mapped all of them, so any input device becomes available. Add the following section to dev-build service section in the docker-compose.yml file:


      devices:

        - /dev/input:/dev/input


Now we have changed our docker-compose.yml file, we need to stop and restart our container. Docker Compose will detect the change, and destroy the old container and recreate it. So once it starts up again, we will need to source our ROS2 packages again (just once if we put the command into the bash.rc file again). Open a shell in the container:

docker exec -it jazzy-dev-build-1 bash


Then run following command from the shell:

echo "source /opt/ros/jazzy/setup.bash" >> ~/.bashrc


Now exit the shell, and use docker exec to start a new one. You should be able to run ROS2 commands from the shell now. You can also confirm that the joystick input is available inside the container by using the command ‘ls /dev/input’ from the container shell prompt. If you can see the 'js' item then we are ready to use it in ROS.


First, we can use the joy package to list all the available joysticks:

ros2 run joy joy_enumerate_devices


This should list the game controller. There are some gotchas to be aware of here. I found that if my game controller was not connected to the host Pi over bluetooth before I started my container then it would not be detected inside the container after it was connected. So if the controller turns off/loses the connection, then it still appears in the input devices list under /dev/input but does not get detected by ROS. You have to restart the container after reconnecting the controller.


Once you have seen your controller in the list output by the joy_enumerate_devices node, you can start the joy node as follows:

ros2 run joy joy_node


Then in a new terminal, open another shell in your container and run the following to see the outputs from the controller:

ros2 topic echo /joy


This confirms the joy node is posting messages to the /joy topic which you can consume in ROS nodes which subscribe to this topic.


You can also monitor the joy topic in the rqt tool. Open the ‘Plugins/Topics’ menu and select the ‘Topic Monitor’. Tick the /joy topic and you should be able to see the values of the various game controller buttons and joysticks (hold them and wait a little as the refresh rate appears to be only once per second or so in the topic monitor).


Now this is all working, we can try and write a node to subscribe to the /joy topic and make use of the game controller input. We will start with the existing ros2-teleop_twist_joy package: https://index.ros.org/p/teleop_twist_joy/github-ros2-teleop_twist_joy/#jazzy 


We can initially run this directly on our ROS desktop container as it is installed there already. Run the command:

ros2 launch teleop_twist_joy teleop-launch.py


This worked without needing more specific configuration and detected my game controller. It launches the joy node, so you do not need to run that separately. The teleop_twist_joy node subscribes to the /joy topic, and converts the data received from the game controller to 'Twist' data which it publishes to the /cmd_vel topic. In a new shell, you can monitor the /cmd_vel topic:

ros2 topic echo /cmd_vel


The teleop twist node only sends messages when ‘button 8’ is pressed on the controller. This was the left trigger (L2) on my controller. I had to hold it down to see messages in the /cmd_vel topic. 


We can remap the topic name which teleop_twist_joy uses, by passing in a different topic via a command line parameter. So let’s set it up to use the /turtle1/cmd_vel topic:

ros2 launch teleop_twist_joy teleop-launch.py joy_vel:='turtle1/cmd_vel'


You should now be able to control the turtle1 in the turtlesim node, using the game controller. Hold down the enable button (L2 in the case of the default mapping) and the left joystick should be able to drive the turtle around.


Open another shell in the container, and run the command:

rqt_graph


This should open a window showing the node graph. Change the drop down selector to show ‘Nodes/Topics (all)’ and you should see the complete node and topics graph as follows.





No comments:

Post a Comment