Adding Joystick Control To TurtleBot4 Lite Edition

If you’re the lucky owner of a new TurtleBot4 Lite from ClearPath Robotics and Open Robotics, then you’ve probably started to explore a bunch of the available features. One notable difference between the TurtleBot4 Standard Edition and TurtleBot4 Lite edition is that the Standard version comes with a pre-configured Bluetooth controller for navigating the robot.

It’s actually pretty easy to bind a 3rd party Bluetooth Joystick to the Lite robot as well, but some of the information provided in the official documentation isn’t quite complete. We recently went through this process and decided to document it here in case other folks decide to do this as well.

Hardware

In our case, we’re using the TurtleBot4 Lite from ClearPath Robotics (which you likely already have if you’re here reading this article) and a SteelSeries Nimbus Controller which we had on hand – originally purchased at an Apple Store for playing iPad/AppleTV games.

Other generic Bluetooth Joysticks should probably work fine as well, but your mileage may vary a bit with some of the nuances of your specific controller. If in doubt, it’s worth giving it a shot to bind and try out your controller. Most of the axis/button controls are configurable, so even if your controller is a slightly different layout, or has different conventions for the inputs, you should be able to adjust the configuration to work for your device.

SteelSeries Nimbus Controller

Enable Bluetooth on TurtleBot4 Lite and Quick Test of Joystick

By default, Bluetooth is not enabled on the TurtleBot4 Lite Edition. The documentation provided by ClearPath shows you how to get this set up on the Raspberry Pi 4 inside the TurtleBot, but there’s a bit more to the story, which we’ll return to in a moment.

To get started though, your baseline is to follow these instructions in the ClearPath documentation for the TurtleBot4 to get things running. Instructions can be found here: https://turtlebot.github.io/turtlebot4-user-manual/tutorials/driving.html#joystick-teleoperation

To summarize though, you perform the following steps:

  1. SSH Into the TurtleBot4
  2. Run ‘sudo bluetooth.sh'to enable bluetooth
  3. Perform the Controller Setup sequence as described in the docs here: https://turtlebot.github.io/turtlebot4-user-manual/overview/quick_start.html#turtlebot-4-controller-setup

Once you’ve done that, you should be able to go back and manually launch the joy_telop node in the shell on the Pi and get control of your robot with the controller:

ros2 launch turtlebot4_bringup joy_teleop.launch.py

In my case, I found that the Y-axis on the control was inverted compared with how I expected to drive the robot, but otherwise everything was working as advertised between my controller and robot. I fixed that by inverting that axis in the controller configuration file, which we discuss down below.

Summary and Next Steps

So, at this point you should have joystick control of your robot, even if imperfect. If not, you need to go back and troubleshoot the steps above because they are the foundation of what happens next.

Also, if you’re happy enough to manually launch your joystick control from the shell each time you want to control your robot that way, you can also just stop here and manually run the joy_telop node when you want to use your joystick.

However, If you want joystick control to automatically be enabled at robot launch time, the documentation is actually slightly incorrect where it states:

Once your controller is connected, make sure that the joy_teleop nodes are running. These are launched as part of the Standard and Lite launch files under turtlebot4_bringup.

In reality, joy_telop is not actually launched as part of the Lite launch file, so we set out to remedy that below.

What is the Difference Between the TurtleBot4 Standard Edition and TurtleBot4 Lite Edition Launch Files?

Conceptually, the task ahead of us is to figure out what the difference is and why the Standard Edition Launch Files bring up the joy_telop node and why the Lite Edition Launch Files do not.

The answer to this is pretty straightforward – the lite files omit launching the node. To see this, we look at the launch files in GitHub for the turtlebot4_robot package. You can find those files here: https://github.com/turtlebot/turtlebot4_robot/tree/galactic/turtlebot4_bringup/launch

The generate_launch_description() function in standard_launch.py contains the calls to automatically launch the joy_telop node. We should be able to add these to the lite_launch.py file and get the node launched as well on boot.

Screen grab of the nodes that we’re missing from our Lite launch file

So, it’s straight-forward enough to see how we might edit our file, but this opens up a couple other questions:

  • What’s the best way to override this launch file? If you’re like me, you got the TurtleBot4 out of the box and fired it up as opposed to building it from source or anything like that. So, this involves some messing with the ROS goings-on under the hood.
  • How can we adjust the Joystick configuration at the same time? As it turns out, the sibling folder to the turtlebot4_robot/turtlebot4_bringup/launch folder is the turtlebot4_robot/turtlebot4_bringup/config folder which just so happens to contain the turtlebot4_controller_config.yaml file that we need to modify in the case where we need to adjust any buttons or axes for differences between our controller and the baseline.

Potential Approaches to Adding Joystick Control to TurtleBot4 Lite Launch Files

  1. Hack and Slash – Edit the package files directly
  2. Load an Alternate Launch File at Boot
  3. Compile from Source / Create a ROS Overlay
  4. (Probably others, but not ROSsy-enough to know what they are)

Hack and Slash

There’s nothing really preventing us from going and just editing those files directly on the TurtleBot. This could potentially cause some grief downstream if those packages got updated in subsequent revisions and our changes were overwritten.

But, really, since we know what they were, it wouldn’t be catastrophic. If you’re in a hurry and not super concerned about downstream effects, you might choose to go this path for expedience. Just follow the steps below on the original files in the ROS install for the TurtleBot, and you can short circuit the process.

I decided against “hack and slash” because I think there’s more to be learned here about how ROS works and doing things the correct way, but you do you.

Load an Alternate Launch File at Boot

So, another thought was that we could go create a new alternative version of the launch file (something like lite_launch_plus_joy.py and add in the nodes we would like to enable the joystick.

This would work fine, and be relatively easy to implement. We did a deep dive into how the “Robot Upstart” process works and how the launch file is used by the turtlebot4 Linux Service in a previous blog post. You could follow that approach and set up a new launch file to load and run the existing nodes, plus the joy_telop service.

The main concern with this approach is that it wouldn’t solve the configuration issue we’ve got with our SteelSeries Nimbus (the Y-axis on the controller is inverted for us) and we’d like to have the flexibility to change that, and anything else that’s going to come up in the future. So, it really feels like the launch file solution isn’t ideal. However, if you’re happy with the way the default controller configuration is working for your controller, you can probably just create a new launch file and roll with that solution.

Create a ROS Overlay and Compile the turtlebot4_robot Ourselves to Override the Base Implementation

This option feels like they are the most correct from a ROS philosophy perspective. The other nice side effect of setting up an overlay is that it also set us up to being able to modify the TurtleBot and create our own code and extensions to the platform in the future. So, for this reason, I chose to see this path through.

This option still forces us to modify how the turtlebot4 ROS service is launched on the robot. But, it feels like time well spent to figure out how to do things right.

What is a ROS Overlay?

Overlays involve creating a workspace where we can source and modify existing ROS packages to overlay (or basically “override” the existing install underneath). We can also add our own packages in the future into the overlay to add new functionality to our TurtleBot. The “underlay” in this case is the base ROS implementation for the TurtleBot installed as packages. Anything that we don’t override will fall through to the base implementation.

However, there are also some nuances with creating the overlay in terms of how to get the Linux environment and automatic launch of our TurtleBot4 nodes to recognize the presence of our overlay, and ideally to be able to switch back and forth between the overlay and vanilla TurtleBot packages if we needed to revert.

We previously documented the robot_upstart package and how the Raspberry Pi is loading ROS as a Linux service in this blog post. We can use what we learned from that process to try set up an overlay with a custom turtlebot4_robot package so that we can upgrade both the launch file and adjust the controller configuration. How bad can it be?

Creating a ROS Overlay and Adding Joystick Support to the TurtleBot4 Lite Edition

Ok. We have a plan. Let’s figure out how to execute it.

Step 1: Create a Workspace and Custom Build of the turtlebot4_robot Package

You can create a workspace anywhere you like on the robot, but the convention in most of the Turtlebot4 tutorials and code examples is to create a workspace as follows. If you haven’t already done this call the following command from the shell on the Raspberry Pi:

mkdir ~/turtlebot4_ws/src -p

This will create the workspace in the home directory of the ubuntu user on the Pi.

Once that’s done, or if you already have the directory, change into it:

cd ~/turtlebot4_ws/src

Step 2: Clone the turtlebot4_robot package from it’s GitHub repo

Fairly straight forward – to clone the repo, run:

git clone https://github.com/turtlebot/turtlebot4_robot.git

Step 3: Install Build Dependencies

We’ll need to get all the dependencies required for the turtlebot4_robot repo to build. To do this, call:

cd ~/turtlebot4_ws

rosdep install --from-path src -yi

Note – if you’ve never run rosdep before, it may ask to be initialized, but it will provide instructions for how to do this.

Once you’ve run it, you should get a message saying

#All required rosdeps installed successfully

Step 4: Build the turtlebot4_robot Package

Back to the package folder:

cd ~/turtlebot4_ws/src

And then run colcon to build the package:

colcon build --symlink-install

The build process should run showing the packages being compiled. The Raspberry Pi isn’t exactly a speed demon on the build side, but it gets the job done after a few minutes.

Step 5: Make our Modifications

At this point, the instructions depend a bit on what you want to do. At minimum, we’re going to add the joy_telop node to our launch file, but if you also want to make changes to your controller configuration, that is totally up to you.

Launch File Changes

The launch file for the Lite Edition of the TurtleBot4 is found in the turtlebot4_bringup/launch folder of the package. To get there quickly:

cd ~/turtlebot4_ws/src/turtlebot4_robot/turtlebot4_bringup/launch

You can edit the lite.launch.py script in the editor of your choice (nano lite.launch.py will work if you don’t have another editor installed).

Basically, what we’re doing is comparing the code in standard.launch.py and grabbing the lines that are responsible for launching joy_telop and pasting them in to the lite configuration.

Be careful to maintain Python indentation with your changes so that you don’t inadvertently cause problems.

We’ve added a comment below in front of all the code that has been added. Look for #Adding joy_telop based on standard.launch.py to see the changes below.

Make the changes, and save the file.

def generate_launch_description():

    pkg_turtlebot4_bringup = get_package_share_directory('turtlebot4_bringup')
    pkg_turtlebot4_diagnostics = get_package_share_directory('turtlebot4_diagnostics')
    pkg_turtlebot4_description = get_package_share_directory('turtlebot4_description')

    param_file_cmd = DeclareLaunchArgument(
        'param_file',
        default_value=PathJoinSubstitution(
            [pkg_turtlebot4_bringup, 'config', 'turtlebot4.yaml']),
        description='Turtlebot4 Robot param file'
    )

    turtlebot4_param_yaml_file = LaunchConfiguration('param_file')

    turtlebot4_robot_launch_file = PathJoinSubstitution(
        [pkg_turtlebot4_bringup, 'launch', 'robot.launch.py'])
    # Adding joy_telop based on standard.launch.py
    joy_teleop_launch_file = PathJoinSubstitution(
        [pkg_turtlebot4_bringup, 'launch', 'joy_teleop.launch.py'])
    diagnostics_launch_file = PathJoinSubstitution(
        [pkg_turtlebot4_diagnostics, 'launch', 'diagnostics.launch.py'])
    rplidar_launch_file = PathJoinSubstitution(
        [pkg_turtlebot4_bringup, 'launch', 'rplidar.launch.py'])
    oakd_launch_file = PathJoinSubstitution(
        [pkg_turtlebot4_bringup, 'launch', 'oakd.launch.py'])
    description_launch_file = PathJoinSubstitution(
        [pkg_turtlebot4_description, 'launch', 'robot_description.launch.py']
    )

    lite_launch = IncludeLaunchDescription(
        PythonLaunchDescriptionSource([turtlebot4_robot_launch_file]),
        launch_arguments=[('model', 'lite'),
                          ('param_file', turtlebot4_param_yaml_file)])
    # Adding joy_telop based on standard.launch.py
    teleop_launch = IncludeLaunchDescription(
        PythonLaunchDescriptionSource([joy_teleop_launch_file]))
    diagnostics_launch = IncludeLaunchDescription(
        PythonLaunchDescriptionSource([diagnostics_launch_file]))
    rplidar_launch = IncludeLaunchDescription(
        PythonLaunchDescriptionSource([rplidar_launch_file]))
    oakd_launch = IncludeLaunchDescription(
        PythonLaunchDescriptionSource([oakd_launch_file]),
        launch_arguments=[('tf_prefix', 'oakd_lite')])
    description_launch = IncludeLaunchDescription(
        PythonLaunchDescriptionSource([description_launch_file]),
        launch_arguments=[('model', 'lite')]
    )

    ld = LaunchDescription()
    ld.add_action(param_file_cmd)
    ld.add_action(lite_launch)
    # Adding joy_telop based on standard.launch.py
    ld.add_action(teleop_launch)
    ld.add_action(diagnostics_launch)
    ld.add_action(rplidar_launch)
    ld.add_action(oakd_launch)
    ld.add_action(description_launch)
    return ld

Joystick Configuration Changes

The controller config is specified in turtlebot4_bringup/config/turtlebot4_controller_config.yaml

The top of the file contains the documentation for how it works – edit to match your controller configuration as you see fit.

Step 6: Re-Build Package

Run the build process again

colcon build --symlink-install

Step 7: Create Workspace Launch Script

If you are running a ROS overlay from the command-line, you need to source your workspace environment setup in order to register your ROS nodes and modifications. We’re going to need to do the same thing when the Linux services start up on the device. So, we need to create a script that will first source the default Galactic environment, and then overlay our workspace on top of it.

We’re going to put this composite Workspace Launch Script into our workspace root folder. Change to that directory:

cd ~/turtlebot4_ws

Create a bash script in that folder (using your text editor) named setup.bash with the following lines:

source /opt/ros/galactic/setup.bash
source /home/ubuntu/src/turtlebot4_robot/install/local_setup.bash

Step 8: Shut Down Default ROS Service and Do a Test Run

The first thing we need to do is to disable the default turtlebot4 service running on the Raspberry Pi. ClearPath provides a Python script for uninstalling the default services. To disable them, you run the uninstall script and then restart the robot (just to be safe):

uninstall.py
sudo reboot now


Next, we’re going to launch our environment from the command line just to check for errors. First, we set up the environment (using the setup.bash file we created earlier):

cd ~/turtlebot4_ws
source setup.bash

Now, we’re going to launch ROS using our new launch file:

cd ~/turtlebot4_ws/src/turtlebot4_robot/turtlebot4_bringup/launch/
ros2 launch lite.launch.py


After a lot of debug output, the robot should come online if you’ve done everything correctly and… you should be able to drive it around with your controller based on the joystick configuration you specified.

If you need to brush up on the controls, you can check the documentation here:

https://turtlebot.github.io/turtlebot4-user-manual/tutorials/driving.html#joystick-teleoperation

When you’re done testing press CTRL-C to kill the script.

Step 9: Modify install.py to Create Our Own Linux Service for Our Overlay

Ok. Almost there! We know that our launch script works and we have an environment with our overlay supported with the default Turtlebot4 underlay.

Now, we want to install a Linux service that will automatically run our launch file boot time and create a background service based on our overlay and modifications.

ClearPath provides a script called install.py (the exact opposite script of the uninstall.py we used earlier to clean up the environment). We want to leave this script intact in case we want to go back to default environment in the future, so we’ll make a copy and modify it to run our new environment.

First, we copy the script:

sudo cp /usr/local/bin/install.py /usr/local/bin/overlay-install.py

Then, we’ll modify it to use the setup.bash file we created in our overlay. Find the definition of the turtlebot4_job near the bottom of the file that looks like this:

turtlebot4_job = robot_upstart.Job(name='turtlebot4',
     rmw='rmw_cyclonedds_cpp',
     rmw_config='/etc/cyclonedds_rpi.xml',
     workspace_setup='/opt/ros/galactic/setup.bash',
     ros_domain_id=domain_id)

And, just change the value of workspace_setup to point to our new workspace setup file (/home/ubuntu/turtlebot4_ws/setup.bash), and the name of the job to turtlebot4-overlay (so that we can tell we’re running the new service)

turtlebot4_job = robot_upstart.Job(name='turtlebot4-overlay',
     rmw='rmw_cyclonedds_cpp',
     rmw_config='/etc/cyclonedds_rpi.xml',
     workspace_setup='/home/ubuntu/turtlebot4_ws/setup.bash',
     ros_domain_id=domain_id)

Save the file, and then run it to install our new service:

install.py lite

Once installed, you need to refresh the services and start our new service:

sudo systemctl daemon-reload
sudo systemctl start turtlebot4-overlay

Thats it! Your robot should be back online and ready for action.

Things To Remember

So, are there implications of having built our own version of the turtlebot4_robot package? Hopefully not too any, but the most important thing to realize is that you will need to manually pull any updates to the package from GitHub in the future as you are now in control of the code in your overlay.

In other words, updating the turtlebot4_robot package via 'apt update' will update the underlay, but not your version of the package.

Thankfully, it’s not difficult to do this manually. If there are changes in the GitHub repo that you’d like to pull down to your robot, just do the following:

cd ~/turtlebot4_ws/src/turtlebot4_robot
git pull
colcon build --symlink-install

This pulls the latest version from GitHub and then runs the build process again to update the code.

Happy Driving!

Leave a Reply