ROS2, Python GUI program to control diff drive robot

GUI example

Introduction

Files are available as teleop_gui here

In this section we are going to create a GUI Dashboard that can control the robot either in an automated mode (very primitive, as autonomous driving is not our objective) or in responce to buttons that user presses.
It will also display lidar and odometry information.
Finally, it will receive and display data from a video camera.

GUI library

In order to create GUI, am going to use GUI library called tkinter. It is neither powerful nor convenient, but simple enough to save us time: after all, we are not going to create anything complex.

                    $ sudo apt-get install python3-tk
                

Directory structure

The project is called teleop_gui, so we are going to create a folder for it under harsh (which is a name of my ROS2 workspace, you can use your own) / src.

The project is almost identical to the ros2_teleop above, as the mater of fact, I simply copied it over and went through all files, changing the name of an old package to a new one.

Alternatively, you can create a new package and add necessary files:

                    $ ros2 pkg create --build-type ament_python teleop_gui
                

Compared to ros2_teleop, the only new file is controller_gui.py that contains GUI code:

                    $ cd cd ~/SnowCron/ros_projects/harsh
                    $ touch src/teleop_gui/teleop_gui/controller_gui.py
                

The directory structure looks like this:

                    $ cd cd ~/SnowCron/ros_projects/harsh
                    $ tree --dirsfirst src/teleop_gui
                    src/teleop_gui
                    ├── launch
                    │   └── teleop_gui.launch.py
                    ├── resource
                    │   └── teleop_gui              # Empty
                    ├── teleop_gui
                    │   ├── controller_gui.py
                    │   ├── __init__.py             # Empty
                    │   └── robot_controller.py
                    ├── package.xml
                    ├── setup.cfg
                    └── setup.py
                    
                

teleop_gui.launch.py

                    import os
                    from launch import LaunchDescription
                    from launch_ros.actions import Node
                    
                    def generate_launch_description():
                    return LaunchDescription([
                        Node(package='teleop_gui', executable='controller_gui', 
                            output='screen', emulate_tty=True)
                    ])
                

As you can see, all this launch file does is starting teleop_gui node, looking for a main function in a controller_gui file.

package.xml

                    
                

This file contains your contact/Copyright data and dependencies the project requires.

setup.cfg

                    [develop]
                    script-dir=$base/lib/teleop_gui
                    [install]
                    install-scripts=$base/lib/teleop_gui
                

setup.py

                    import os
                    from setuptools import setup
                    from glob import glob
                    
                    package_name = 'teleop_gui'
                    
                    setup(
                        name=package_name,
                        version='0.0.0',
                        packages=[package_name],
                        data_files=[
                            ('share/ament_index/resource_index/packages',
                                ['resource/' + package_name]),
                            ('share/' + package_name, ['package.xml']),
                            (os.path.join('share', package_name), glob('launch/*.launch.py'))
                        ],
                        install_requires=['setuptools'],
                        zip_safe=True,
                        maintainer='harsh',
                        maintainer_email='contact@mail.com',
                        description='TODO: Package description',
                        license='TODO: License declaration',
                        tests_require=['pytest'],
                        entry_points={
                            'console_scripts': [
                            'controller_gui = teleop_gui.controller_gui:main'
                            ],
                        },
                    )
                

Note that if you (like myself) make a copy of ros2_teleop instead of creating it, you will have to pay attention to package names, otherwise it just will not work.

robot_controller.py

This is a modified copy of a file from ros2_teleop. It now has odometry, that is not required for the code to run, but we use it to display coordinates on our GUI dashboard.

It also has some additional member variables. There are many ways to exchange data between robot_controller that receives/publishes data and controller_gui that displays data in GUI and reacts to user's actions.

A hardcore ROS2 practitioner would probably send ROS2 messages between them... while I just access member variables directly: not very structured approach, but definitely a simple one.

                    
                

controller_gui.py

This file creates a Dashboard with Lidar, Odometry and Video.

                    
                

Running the code

                    # In Terminal 1, bring up GUI Dashboard:
                    $ colcon build --packages-select teleop_gui
                    $ source install/setup.bash
                    $ ros2 launch teleop_gui teleop_gui.launch.py

                    # In Terminal 2, bring up Gazebo:
                    $ colcon build --packages-select diff_drive_controller_bot
                    $ source install/setup.bash
                    $ ros2 launch diff_drive_controller_bot launch_sim.launch world:=src/worlds/maze.sdf
                

(C) snowcron.com, all rights reserved

Please read the disclaimer