Giving a TurtleBot3 a Namespace for Multi-Robot Experiments

Introduction

As I was working on my ICRA paper, I noticed that ROBOTIS doesn’t provided a guide on how to run multiple TurtleBot3 robots together. It is especially dangerous if you run them in the same network because they all run on the same topic names and node names, which can interfere with their individual operation. So to help run multiple TurtleBots on the same network, you need to give each robot a unique namespace. The following guide will show you how to do this for the TurtleBot3.

For this guide, we will be using tb3_0 as the namespace we wish to use for our TurtleBot3 Burger robot. This helps us number our robots easier when running multiple robot experiments. This guide also assumes you have followed the procedure located here for installing and setting up your TurtleBot3 with ros2!

Step 1: Create a New ros2 Package

Start by changing into your src directory of your workspace that also contains the turtlebot3 and utils packages provided by ROBOTIS.

~$ cd ~/turtlebot3_ws/src
~$ ros2 pkg create my_tb3_launcher

Now, create two empty directories in the new package:

~$ cd ~/turtlebot3_ws/src/my_tb3_launcher
~$ mkdir launch
~$ mkdir param

Change into the launch directory and create a new bringup launch file.

~$ cd launch
~$ touch my_tb3_bringup.launch.py

Step 2: Copy and Modify Contents from the TB3 Bringup Package into Your Package

In the turtlebot3/turtlebot3_bringup ros2 package, copy the contents of robot.launch.py into the my_tb3_bringup.launch.py with the following changes marked as # comments in the following code:

import os

from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration
from launch.substitutions import ThisLaunchFileDir
from launch_ros.actions import Node


def generate_launch_description():
    TURTLEBOT3_MODEL = os.environ['TURTLEBOT3_MODEL']

    usb_port = LaunchConfiguration('usb_port', default='/dev/ttyACM0')

    tb3_param_dir = LaunchConfiguration(
        'tb3_param_dir',
        default=os.path.join(
            get_package_share_directory('my_tb3_launcher'),  # <--- CHANGE THIS!
            'param',
            TURTLEBOT3_MODEL + '.yaml'))

    use_sim_time = LaunchConfiguration('use_sim_time', default='false')

    return LaunchDescription([
        DeclareLaunchArgument(
            'use_sim_time',
            default_value=use_sim_time,
            description='Use simulation (Gazebo) clock if true'),

        DeclareLaunchArgument(
            'usb_port',
            default_value=usb_port,
            description='Connected USB port with OpenCR'),

        DeclareLaunchArgument(
            'tb3_param_dir',
            default_value=tb3_param_dir,
            description='Full path to turtlebot3 parameter file to load'),

        IncludeLaunchDescription(
            PythonLaunchDescriptionSource(
                [ThisLaunchFileDir(), '/turtlebot3_state_publisher.launch.py']),
            launch_arguments={'use_sim_time': use_sim_time}.items(),
        ),

        IncludeLaunchDescription(
            PythonLaunchDescriptionSource([ThisLaunchFileDir(), '/hlds_laser.launch.py']),  <--- CHANGE THIS
            launch_arguments={'port': '/dev/ttyUSB0', 'frame_id': 'base_scan'}.items(),
        ),

        Node(
            package='turtlebot3_node',
            node_executable='turtlebot3_ros',
            node_namespace='tb3_0',  # <------------------- ADD THIS!
            parameters=[tb3_param_dir],
            arguments=['-i', usb_port],
            output='screen'),
    ])

Next, copy the file turtlebot3_state_publisher.launch.py from the turtlebot3_bringup/launch directory into your package’s launch directory. Make sure it has the same name! Once complete, make the following changes as marked by the following comments:

import os

from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node


def generate_launch_description():
    TURTLEBOT3_MODEL = os.environ['TURTLEBOT3_MODEL']

    use_sim_time = LaunchConfiguration('use_sim_time', default='false')
    urdf_file_name = 'turtlebot3_' + TURTLEBOT3_MODEL + '.urdf'

    print("urdf_file_name : {}".format(urdf_file_name))

    urdf = os.path.join(
        get_package_share_directory('turtlebot3_description'),
        'urdf',
        urdf_file_name)

    return LaunchDescription([
        DeclareLaunchArgument(
            'use_sim_time',
            default_value='false',
            description='Use simulation (Gazebo) clock if true'),

        Node(
            package='robot_state_publisher',
            node_executable='robot_state_publisher',
            node_name='robot_state_publisher',
            node_namespace='tb3_0',  # <------------------- ADD THIS!
            output='screen',
            parameters=[{'use_sim_time': use_sim_time}],
            arguments=[urdf]),
    ])

Finally, copy the file hlds_laser.launch.py from the hls_lfcd_lds_driver package located in the launch directory into your package’s launch directory. Again, make sure it has the same name!. Modify the launch file with the following changes marked by the comments below:

import os

from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.actions import LogInfo
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node


def generate_launch_description():
    port = LaunchConfiguration('port', default='/dev/ttyUSB0')

    frame_id = LaunchConfiguration('frame_id', default='laser')

    return LaunchDescription([

        DeclareLaunchArgument(
            'port',
            default_value=port,
            description='Specifying usb port to connected lidar'),

        DeclareLaunchArgument(
            'frame_id',
            default_value=frame_id,
            description='Specifying frame_id of lidar. Default frame_id is \'laser\''),

        Node(
            package='hls_lfcd_lds_driver',
            node_executable='hlds_laser_publisher',
            node_name='hlds_laser_publisher',
            node_namespace='tb3_0',  # <------------------- ADD THIS!
            parameters=[{'port': port, 'frame_id': frame_id}],
            output='screen'),
    ])

Step 3: Modify the Parameter YAML File

Now copy the burger.yaml file located in the param directory of the turtlebot3_bringup package, and make the following modification at the top!

tb3_0:
  turtlebot3_node:
    ros__parameters:

      opencr:
        id: 200
        baud_rate: 1000000
        protocol_version: 2.0

      wheels:
        separation: 0.160
        radius: 0.033

      motors:
        profile_acceleration_constant: 214.577

        # [rev/min2]
        # ref) http://emanual.robotis.com/docs/en/dxl/x/xl430-w250/#profile-acceleration
        profile_acceleration: 0.0

      sensors:
        bumper_1: false
        bumper_2: false

        illumination: false

        ir: false

        sonar: false

tb3_0:
  diff_drive_controller:
    ros__parameters:

      odometry:
        publish_tf: true
        use_imu: true
        frame_id: "odom"
        child_frame_id: "base_footprint"

As you can see, the top most parameter used to be the node name (turtlebot3_node and diff_drive_controller). For the node namespace that you added to work, you will need to add the node namespace (tb3_0) one level above the node name!

In Step 2, we already changed the launch file to point to this yaml file instead of the one located in the turtlebot3_bringup package.

Step 4: Modify the CMakeLists File

For this section, we will just be adding a small code snipet to our CMakeLists.txt that will install the launch and param contents of our my_tb3_launcher package.

...
install(DIRECTORY
  launch
  param
  DESTINATION share/${PROJECT_NAME}/
)
...

Add this snippet right before the if(BUILD_TESTING) section of the CMakeLists.txt file.

Step 5: Compile and Run!

Finally, compile the code on your TurtleBot3:

~$ cd ~/turtlebot3_ws
~$ colcon build --symlink-install --parallel-workers 1
~$ . install/setup.bash

Now, run your launch file to make sure it works!

~$ export TURTLEBOT3_MODEL=burger
~$ ros2 launch my_tb3_launcher my_tb3_bringup.launch.py

You should get the following topics when you run ros2 topic list in another bash session:

/tb3_0/battery_state
/tb3_0/cmd_vel
/tb3_0/imu
/tb3_0/joint_states
/tb3_0/magnetic_field
/tb3_0/odom
/tb3_0/parameter_events
/tb3_0/robot_description
/tb3_0/rosout
/tb3_0/scan
/tb3_0/sensor_state
/tb3_0/tf
/tb3_0/tf_static

You can repeat these procedures with other TurtleBot3 robots with different namespaces to have multiple robots working in your network. Hope this helps and happy programming everyone!

Setting-up the Turtlebot3 with ros2 on Ubuntu Server IoT 18.04

Introduction

One of the cool things about ros2 is that ROS Master is finally gone. The new DDS approach allows for interesting ways of controlling multiple autonomous agents without having to rely on a centralized ROS Master running the show. For this guide, I’ll show you how to set up ros2 on a Turtlebot3 burger or waffle using Ubuntu Server IoT 18.04. Why use this instead of Ubuntu Mate 18.04 like the Turtlebot guide suggests? Well, I’ve run into a lot of problems during the installation and its just kind of bloated. I don’t Firefox, VLC, Thunderbird, Libreoffice etc. All I need is just a bash shell because I’m going to be writing most of the code for it on a different computer anyways. So let’s start!

NOTE: If you are not comfortable with just using a terminal, I suggest installing Ubuntu Mate 18.04 instead. Make sure to download and install the armhf version!

Step 1: Download and Install Ubuntu Server IoT 18.04

You can get it from this link here. Make sure to download the Raspberry Pi 3 version!

As an aside, the Ubuntu Mate image for the Raspberry Pi 3 version that is 32-bit, but that won’t work with this distro. Only the Raspberry Pi 2 version of Ubuntu Server IoT is 32-bit. I tried that version with the Raspberry Pi 3 and it won’t boot past the color screen.

Step 2: Start the OS on the Turtlebot3

Insert the microSD card into the slot on the Raspberry Pi 3 and begin the boot up process. It will go through the whole configuration and you will reach the login screen which is just the bash shell. The login is ubuntu and the password is ubuntu. Once entered you will be greeted with an option to change the password. Do it since it is much more secure this way.

Step 3: Change the Device’s Hostname

This may not be necessary if you plan on just using one Turtlebot3, but if you want to use multiple ones, I would suggest changing the device’s hostname using:

~$ hostnamectl set-hostname <the-new-hostname-you-want>

NOTE: When I use < >, in a command, it typically means you do not use them the command. So the above command would look like this:

~$ hostnamectl set-hostname my-new-tb3-hostname

Step 4: Create a New Username

Just like the step above, this is optional if you only plan on using one turtlebot3. If not, I would suggest either changing the current one, or generating a new one and giving permissions. I decided with just creating a new one. You can do that this way:

~$ sudo adduser <username>

Once the command is executed, it will ask for information about the user such as First Name, Last Name, etc. You can just keep them blank and press ENTER to continue to the next options. Next, we will add group permissions to the new user once back to the command prompt:

~$ sudo usermod -aG sudo <the-new-username-you-made>

The above command gives the new username sudoer permissions by adding them to the sudo group. To have the same permissions as the ubuntu user you logged on with, you will need to repeat this command and add the user to the following permission groups:

~$ sudo usermod -aG adm <the-new-username-you-made>
~$ sudo usermod -aG dialout <the-new-username-you-made>
~$ sudo usermod -aG cdrom <the-new-username-you-made>
~$ sudo usermod -aG floppy <the-new-username-you-made>
~$ sudo usermod -aG audio <the-new-username-you-made>
~$ sudo usermod -aG dip <the-new-username-you-made>
~$ sudo usermod -aG video <the-new-username-you-made>
~$ sudo usermod -aG plugdev <the-new-username-you-made>
~$ sudo usermod -aG lxd <the-new-username-you-made>
~$ sudo usermod -aG netdev <the-new-username-you-made>

Not all of these groups are useful or needed (I have yet to see a turtlebot with a floppy drive lol), but I added them just to stay on the safe side.

Once this is done, reboot using the reboot command and login using the new username.

Step 5: Configure Static IP

This is a step for people that want a static IP for their turtlebots. In our lab, this is super useful because we can only connect to the robots using a wifi router separated from the universities internet. To install packages and such on the Raspberry Pi requires the use of an ethernet cable. Therefore, the following configuration is a wifi connection with no internet and static IP and an ethernet connection with a dynamic IP that lets us access the internet.

The newest versions of Ubuntu have a really neat systemd tool called netplan. Instead of configuring those old /etc/network/interfaces and /etc/dhcpcd.conf files, we just have to modify one yaml file located in the /etc/netplan/ folder. We will begin by changing directory into the /etc/netplan/ folder:

~$ cd /etc/netplan/

In this folder, you will find a file which we can ignore, and we will create a new file called 01-netcfg.yaml:

~$ sudo touch 01-netcfg.yaml

Now we will modify it using nano:

~$ sudo nano 01-netcfg.yaml
# My Turtlebot3 Network Configuration
network:
    version: 2
    renderer: networkd
    ethernets:
      eth0:
        dhcp4: yes
        dhcp6: yes
        optional: true
    wifis:
      wlan0:
        dhcp4: no
        dhcp6: yes
        addresses: [xxx.xxx.xxx.xxx/24]
        access-points:
          "my-wifi-connection-name":
            password: "my-connection-password"

NOTE: For the access-points section, the connection name and password should be in quotes like above.

Finally, apply the new network configuration using this command:

~$ sudo netplan apply

Step 6: Tweak systemd

For some reason, the OS will attempt to configure a network IP at startup. This can delay boot-up by up to 5 minutes. So to prevent this, we are going to mask this systemd process using the following command:

~$ systemctl mask systemd-networkd-wait-online.service

Now boot-up should be super quick!

Step 7: Install Turtlebot3 ros2 Packages

Next, we are going to install the Turtlebot3 ros2 packages. Instructions are found here. These are not downloadable through the ros2 repositories yet, so you will need to compile many of these. Just a little heads up, the Micro-XRCE-DDS-Agent installation takes the longest because it will compile third party packages too. Everything afterwards is pretty quick.

NOTE: The Build LIDAR Client installation is wrong. The link for the turtlebot3_lidar.tar.bz2 driver is broken and according to this issue, they are attempting to fix the problem. In the meantime, you can use the Crystal Clemmys driver. The command to download that is this:

~$ wget https://github.com/ROBOTIS-GIT/turtlebot3/raw/24d14d772520508e409b80c43859b7020d76bb82/turtlebot3_lidar/turtlebot3_lidar.tar.bz2

Step 8: Install Update to OpenCR Board

Next, you will have to update the OpenCR board to use ros2. The guide to do that is provided here The issue with this is that Ubuntu Server IoT 18.04 is 64-bit arm OS. The drivers provided by ROBOTIS can only be applied by a 32-bit arm OS. I raised an issue here on there GitHub page, so they might provide one in the future.

For now, the best thing you can do is get a spare Ubuntu Mate armhf microSD card and use that to install the new updated OpenCR Drivers. They also provide x86 drivers, so you can connect your laptop to the OpenCR board and install the drivers with that too.

Step 9: Install ros2

Follow the binary installation guide found here.

Step 10: Edit .bashrc File

Since I use the turtlebots for a multi-agent configuration, a nice way of having multiple robots interact in the network easier is to have all the devices use the same ROS_DOMAIN_ID. You can add a value to it on startup by adding it to your .bashrc file like this:

# ros2 source information
source /opt/ros/dashing/setup.bash
export ROS_DOMAIN_ID=42

The number can be anything you want, just make sure all the devices you wish to use together have the same one.

Thats it! If you have any feedback or suggestions please let me know!

Spawning Robots in Gazebo with ros2

Introduction

Now that ros2 has done away with the old way of launching nodes (i.e. using XML .launch files), the process has become more stream-lined and versatile than ever before thanks to using Python. The new launch system can, however, be confusing to use the first time, and I’m probably going to do a deep-dive on it. For this blog post, I want to touch on something that is kind of missing from the old approach to the new one: spawning robots into Gazebo.

The old way of doing it was just to use an xml tag like this

<launch>
...
  <!-- URDF XML Pheeno robot description loaded on the parameter server. -->
  <param name="robot_description"
         command="$(find xacro)/xacro.py '$(find pheeno_ros_sim)/urdf/pheeno_v1/pheeno.xacro'"/>
  <!-- Run a python script to the send a service call to gazebo_ros to spawn a URDF robot -->
  <node name="urdf_spawner" pkg="gazebo_ros" type="spawn_model" respawn="false" output="screen"
        args="-urdf -model pheeno_01 -param robot_description -robot_namespace pheeno_01"/>
...
</launch>

As you can see (or remember if you have done this before), the old spawner is a node that can be called and provided arguments. I’m not sure what the main way of doing this in ros2 is, but according to Louise Poubel (@chapulinaBR) in this ros2 migration guide it is kind of hinted that the official node approach is out. The best way to spawn a robot in Gazebo is to use a service call to spawn_entity for both urdf and sdf files. It looks something like this for sdf:

~$ ros2 service call /spawn_entity 'gazebo_msgs/SpawnEntity' '{name: "sdf_ball", xml: "<?xml version=\"1.0\" ?><sdf version=\"1.5\"><model name=\"will_be_ignored\"><static>true</static><link name=\"link\"><visual name=\"visual\"><geometry><sphere><radius>1.0</radius></sphere></geometry></visual></link></model></sdf>"}'

and this for urdf:

~$ ros2 service call /spawn_entity 'gazebo_msgs/SpawnEntity' '{name: "urdf_ball", xml: "<?xml version=\"1.0\" ?><robot name=\"will_be_ignored\"><link name=\"link\"><visual><geometry><sphere radius=\"1.0\"/></geometry></visual><inertial><mass value=\"1\"/><inertia ixx=\"1\" ixy=\"0.0\" ixz=\"0.0\" iyy=\"1\" iyz=\"0.0\" izz=\"1\"/></inertial></link></robot>"}'

This is pretty straightforward, but one interesting thing about this is that the urdf or sdf file need to be given as the xml and not the path to the xml file. So if you have something a robot file that is super long this approach might not work for you.

An approach that does work, and if you are ok with using the robot_state_publisher, is to add the robot within the world file, like this, and using a launch file to start it alongside gazebo like this. Both example files are from Robotis’ ros2 simulation package located here. This is a great resource for learning how to use ros2 using their platform.

Node for Spawning Your Own Entities

Instead of using a service call or generating a .world file containing the robot, you can create a node that can place robots in gazebo and use them in a launch file. For this example, I’m going to assume you have the turtlebot3 ros2 files installed and setup and have done the simulations section of the guide. To start, go into the src folder of the ros2 workspace that you created for the turtlebot3 (As an additional assumption, I will assume the workspace is the same name as the one created in the turtlebot3 guide):

~$ cd ~/turtlebot3_ws/src
~$ ros2 pkg create robot_spawner_pkg

Next, we are going to make this a python file only, so we will need to change delete the CMakeLists.txt file and create a setup.py and setup.cfg file containing the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from setuptools import setup

PACKAGE_NAME = 'robot_spawner_pkg'

setup(
    name=PACKAGE_NAME,
    version='1.0.0',
    package_dir={'': 'src'}
    packages=[PACKAGE_NAME],
    install_requires=['setuptools'],
    zip_safe=True,
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'spawn_turtlebot = robot_spawner_pkg.spawn_turtlebot:main',
        ],
    },
)
[develop]
script-dir=$base/lib/robot_spawner_pkg
[install]
install-scripts=$base/lib/robot_spawner_pkg

Now, we edit the package.xml file to add the necessary dependencies to run the program:

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>robot_controller_utils</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="your_email@email.com">your_name</maintainer>
  <license>TODO: License declaration</license>

  <exec_depend>rclpy</exec_depend>

  <export>
    <build_type>ament_python</build_type>
  </export>
</package>

Finally, we create our spawn_turtlebot.py file. Before we do that, however, we need to look at the current structure of our package.

robot_spawner_pkg/
    |
    ----include/
    |
    ---src/
    |
    ----setup.cfg
    ----setup.py
    ----package.xml

because in our setup.py file we defined the package_dir={'': 'src'} and the packages=[PACKAGE_NAME], we need to make our package structure look like this:

robot_spawner_pkg/
    |
    ----include/
    |
    ---src/
    |  |
    |  ----robot_spawner_pkg/
    |
    ----setup.cfg
    ----setup.py
    ----package.xml

Within the second robot_spwaner_pkg folder will we be placing our spawn_turtlebot.py file!

The Robot Spawner Script

In this node, we will essentially be making the service call within the node like the following:

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
"""
spawn_turtlebot.py

Script used to spawn a turtlebot in a generic position
"""
import os
import sys
import rclpy
from ament_index_python.packages import get_package_share_directory
from gazebo_msgs.srv import SpawnEntity

def main():
    """ Main for spwaning turtlebot node """
    # Get input arguments from user
    argv = sys.argv[1:]

    # Start node
    rclpy.init()
    sdf_file_path = os.path.join(
        get_package_share_directory("turtlebot3_gazebo"), "models",
        "turtlebot3_burger", "model-1_4.sdf")
    node = rclpy.create_node("entity_spawner")

    node.get_logger().info(
        'Creating Service client to connect to `/spawn_entity`')
    client = node.create_client(SpawnEntity, "/spawn_entity")

    node.get_logger().info("Connecting to `/spawn_entity` service...")
    if not client.service_is_ready():
        client.wait_for_service()
        node.get_logger().info("...connected!")

    # Get path to the turtlebot3 burgerbot
    sdf_file_path = os.path.join(
        get_package_share_directory("turtlebot3_gazebo"), "models",
        "turtlebot3_burger", "model-1_4.sdf")

    # Set data for request
    request = SpawnEntity.Request()
    request.name = argv[0]
    request.xml = open(sdf_file_path, 'r').read()
    request.robot_namespace = argv[1]
    request.initial_pose.position.x = float(argv[2])
    request.initial_pose.position.y = float(argv[3])
    request.initial_pose.position.z = float(argv[4])

    node.get_logger().info("Sending service request to `/spawn_entity`")
    future = client.call_async(request)
    rclpy.spin_until_future_complete(node, future)
    if future.result() is not None:
        print('response: %r' % future.result())
    else:
        raise RuntimeError(
            'exception while calling service: %r' % future.exception())

    node.get_logger().info("Done! Shutting down node.")
    node.destroy_node()
    rclpy.shutdown()


if __name__ == "__main__":
    main()
    

I want to give credit to Dirk Thomas (can’t seem to find the link to the code…) for this, because it is a modification of his code that inspired me to do this. Now build using colcon, source the install, and export the path to the turtlebot3 model like this:

~$ cd ~/turtlebot3_ws
~$ colcon build --symlink-install
~$ . install/setup.bash
~$ export GAZEBO_MODEL_PATH=$GAZEBO_MODEL_PATH:~/turlebot3_ws/src/turtlebot3/turtlebot3_simulations/turtlebot3_gazebo/models

To run the node, we first start a new terminal, source the installation, and start gazebo:

~$ cd ~/turtlebot3_ws
~$ . install/setup.bash
~$ export GAZEBO_MODEL_PATH=$GAZEBO_MODEL_PATH:~/turlebot3_ws/src/turtlebot3/turtlebot3_simulations/turtlebot3_gazebo/models
~$ gazebo --verbose -s libgazebo_ros_factory.so

Notice that we are using libgazebo_ros_factory.so instead of libgazebo_ros_init.so as used in the turtlebot3 and other tutorials. That is because only libgazebo_ros_factory.so contains the service call to /spawn_entity! Finally, back in your original terminal, use the following command to add a robot!

~$ ros2 run robot_spawner_pkg spawn_turtlebot the_robot_name robot_namespace 0.0 0.0 0.1

The first two arguments are the robot’s name in gazebo and a namespace for the robot if you would like to add more than one. Hope that helps!

ros2 with VSCode and macOS

Introduction

VSCode is one of the most powerful code editors I have tried in a long time. While I know it using Electron as a framework has gotten some people erked at the thought, I rather enjoy the customizability afforded to it by the JavaScript/HTML/CSS backend. However, not all Electron text editors are made the same. Atom, which is a competitior to VSCode (well…maybe not anymore), relies on Electron and a very similar way of doing things, but Microsoft had the advantage of adding some of that special Intellisense code to it. VSCode is far superior when it comes to coding in C++ because Atom really can’t do it. So when I program in ROS on both my Linux and macOS rigs, I tend to just use VSCode out of ease of use, but there are always a couple issues when first setting up the system which I will address in this blog post.

VSCode with ROS2 and macOS

I could never really get the original ROS to work with macOS. It always seemed like a bunch of hurdles to jump through, especially if you wanted to use the latest version. ROS2, however, has among its goals to be compatible with multiple platforms (Windows, macOS, and Linux). They have a long road ahead, but they have made some great progress and amazing features.

Binary or Source installation of ROS2 on macOS can be here and here, respectively.

Once installed, you can easily start writing a project using VSCode. All you need to do is modify the file that is generated in the .vscode folder of the project called c_cpp_properties.json. In the includePath json tag, add the path (line 13) to where the ROS2 installation’s include folder is as such:

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
{
    "configurations": [
        {
            "name": "Mac",
            "browse": {
                "path": [
                    "${workspaceFolder}"
                ],
                "limitSymbolsToIncludedHeaders": true
            },
            "includePath": [
                "${workspaceFolder}",
                "/Users/YOURUSERNAME/ros2_install/ros2-osx/include"
            ],
            "defines": [],
            "macFrameworkPath": [
                "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk/System/Library/Frameworks"
            ],
            "compilerPath": "/usr/bin/clang",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "intelliSenseMode": "clang-x64"
        }
    ],
    "version": 4
}

There you have it! Intellisense will now help you autocomplete your future ROS2 code.