Revolutionizing Healthcare: The Future Impact of Robots

The healthcare industry has witnessed incredible advancements over the years, from groundbreaking medical discoveries to innovative surgical techniques. However, as we take a look into the future, there is one technological revolution that promises to redefine healthcare as we know it: robots.

Robots are going to transform every aspect of healthcare, from diagnostics and surgery to patient care and beyond. In this blog post, we’ll cover the exciting potential of robots in healthcare and the profound impact they are likely to have on the industry.

Robots in Diagnostics

human-skeleton

One of the primary areas where robots are already making significant strides is in diagnostics.

Imagine a robot capable of analyzing vast amounts of patient data, including medical history, genetic information, and real-time monitoring data. This robot can quickly identify potential health issues, predict disease risks, and recommend personalized treatment plans. With machine learning and artificial intelligence, these robots can continuously improve their diagnostic accuracy, ultimately saving lives by catching diseases at their earliest stages.

Let’s take a look at some companies that are currently involved in this market:

  • Diagnostic Robotics: This company has developed an AI-powered platform that can be used to diagnose a wide range of diseases, including cancer, heart disease, and diabetes. The platform is currently being used by healthcare providers around the world.
  • Paige AI: This company has developed AI-powered software that can be used to analyze pathology images and detect cancer cells. The software is currently being used by pathologists around the world to help them diagnose cancer more accurately and efficiently.

Robotic-assisted Surgery

surgey-operating-room

Robotic-assisted surgery is another promising frontier in healthcare. Robots equipped with advanced surgical instruments and guided by skilled surgeons can perform intricate procedures with unmatched precision and minimal invasiveness. These practices will reduce patient trauma, speed up recovery times, and lower the risk of complications.

davinci-surgical-robot

Also, robots can bridge geographical gaps by enabling remote surgery, allowing a surgeon in one location to operate on a patient in another, potentially saving lives in emergency situations and improving access to specialized care in remote areas.

Here are some of the leading companies involved in robotic-assisted surgery:

  • Intuitive Surgical: Intuitive Surgical is the dominant player in the robotic-assisted surgery market, with its da Vinci surgical system being the most widely used robotic surgical system in the world.
  • Medtronic: Medtronic recently entered the robotic-assisted surgery market with its Hugo robotic surgery system.
  • Johnson & Johnson: Johnson & Johnson offers a variety of robotic surgery systems through its Johnson & Johnson Medical Devices division, including the Monarch platform for bronchoscopy, and the Velys platform for orthopedic surgery.
  • Stryker: Stryker offers the Mako robotic-assisted surgery system for orthopedic surgery.
  • Brainlab: Brainlab offers the Curve robotic-assisted surgery system for spinal and neurosurgery.
  • CMR Surgical: CMR Surgical offers the Versius robotic-assisted surgery system for a variety of procedures, including general surgery, gynecologic surgery, and urologic surgery.

Enhanced Rehabilitation

rehabilitation

Rehabilitation is a cornerstone of healthcare, especially for patients recovering from injuries, surgeries, or chronic conditions. Robots are playing an increasingly important role in this area, providing consistent, personalized, and intensive therapy.

For example, robotic exoskeletons help patients regain mobility, while robotic arms assist those with limited dexterity. These devices not only enhance the quality of life for patients but also alleviate the strain on healthcare professionals by automating routine tasks, allowing them to focus on more complex aspects of care.

One of the first companies that comes to mind is ReWalk Robotics. ReWalk Robotics develops and manufactures robotic exoskeletons for people with spinal cord injuries.

ReWalk’s most well-known product is the ReWalk exoskeleton, which allows people with paraplegia to walk again. ReWalk Robotics also produces the ReStore exoskeleton, which is used to help people with lower limb disability regain their mobility.

Another company is Cyberdyne. Cyberdyne is a Japanese company that develops and manufactures robotic exoskeletons for medical and industrial use. Its most well-known product is the HAL (Hybrid Assistive Limb) exoskeleton, which is used to help people with disabilities walk, stand, and climb stairs. The HAL exoskeleton is also used in industrial settings to help workers lift heavy objects and perform other physically demanding tasks.

Robots in Patient Care

xtend_ai

The future of healthcare also includes robots directly interacting with patients. Social robots equipped with natural language processing capabilities can provide companionship and emotional support to patients, particularly those in long-term care facilities or dealing with mental health issues. These robots can engage in conversations, give reminders when patients need to take medication, and monitor vital signs, ensuring patients receive the attention and care they need.

The company I work for, Xtend AI, is doing just that.

Pharmaceutical Advancements

pharmaceuical-advancements

In the pharmaceutical industry, robots are revolutionizing drug discovery and development. Robotic systems can automate high-throughput screening of compounds, drastically accelerating the process of identifying potential drug candidates.

Additionally, robots can handle complex chemical reactions with precision, leading to the creation of more effective and safe medications. This not only reduces the time and cost of drug development but also opens doors to personalized medicine, where treatments are tailored to individual patients based on their genetic makeup and health data.

One robot that already has a head start in this area is NiCoLA-B. This robot is used at the U.K. Center for Lead Discovery to test more than 300,000 compounds a day in search of promising drug candidates. It uses sound waves to move droplets of potential drugs into miniature wells on assay plates, where they are tested for activity.

Logistics and Supply Chain Management

logistics

Efficient logistics and supply chain management are critical in healthcare, ensuring that medications, medical equipment, and supplies are readily available when needed. Robots are already playing a pivotal role in this aspect by automating inventory management, drug dispensing, and even transportation within healthcare facilities. This not only reduces human errors but also optimizes resource allocation and minimizes wastage, ultimately leading to cost savings and improved patient care.

Challenges and Ethical Considerations

While the future of healthcare with robots is incredibly promising, it is not without its challenges and ethical considerations. Privacy concerns, data security, and the potential for bias in AI algorithms must be addressed. I predict there will always be a need for human oversight and expertise to ensure robots operate safely and effectively.

The Best is Yet to Come

The future impact of robots on healthcare is nothing short of transformative. From diagnostics and surgery to patient care and pharmaceutical advancements, robots are set to revolutionize every facet of the healthcare industry. As technology continues to advance, we can look forward to a healthcare system that is more efficient, accessible, and personalized, ultimately leading to better patient outcomes and an improved quality of life for all.

However, it is important we navigate this transformation with careful consideration of the ethical and privacy implications, ensuring that the benefits of healthcare robotics are realized while minimizing potential risks. The future of healthcare is indeed exciting, and robots will undoubtedly be at the forefront of this revolution.

Keep building!

How to Load a Robot Model (SDF Format) into Gazebo – ROS 2

In this post, I will show you how to load an SDF file into Gazebo. Simulation Description Format (SDF) is the standard Gazebo format for robot modeling. 

If you would like to learn more about SDF files, check out this page.

Prerequisites

You can find the files for this post here on my Google Drive.

Create the SDF File

I am going to open up a terminal window, and type the following command to go to the directory where my SDF file will be located.

cd ~/dev_ws/src/two_wheeled_robot/models

Add the folder for the model into this directory. The name of my folder is two_wheeled_robot_description. You can find the folder here on my Google Drive.

Launch the Model Manually

To launch the model manually, you will need to go to your bashrc file and add the path to the model so that Gazebo can find it.

Open a terminal window, and type:

gedit ~/.bashrc

Add the following line to the bottom of the file:

export GAZEBO_MODEL_PATH=$GAZEBO_MODEL_PATH:/home/focalfossa/dev_ws/src/two_wheeled_robot/models
1-add-this-line

Save the file and close it.

Open Gazebo.

gazebo 

Click Insert in the top left.

2-click-insert

I will scroll down until I find “Two Wheeled Robot”. I click on the robot and place it inside the Gazebo empty world.

3-two-wheeled-robot

Here is what it looks like:

4-two-wheeled-robot-1

Press CTRL+C in all windows to close everything down.

Launch the Model Automatically

Now I want to launch the model automatically.

Create the Launch File

Now we want to create a launch file.

I am going to go to my launch folder and create the file. Here is the command I will type in my terminal window.

cd ~/dev_ws/src/two_wheeled_robot/launch
gedit launch_sdf_into_gazebo.launch.py

Type the following code inside the file.

# Author: Addison Sears-Collins
# Date: September 27, 2021
# Description: Load an SDF and world file into Gazebo.
# https://automaticaddison.com

import os
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, ExecuteProcess, IncludeLaunchDescription
from launch.conditions import IfCondition, UnlessCondition
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import Command, LaunchConfiguration, PythonExpression
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageShare


def generate_launch_description():

  # Constants for paths to different files and folders
  gazebo_models_path = 'models'
  package_name = 'two_wheeled_robot'
  robot_name_in_model = 'two_wheeled_robot'
  sdf_model_path = 'models/two_wheeled_robot_description/model.sdf'
  world_file_path = 'worlds/neighborhood.world'
    
  # Pose where we want to spawn the robot
  spawn_x_val = '0.0'
  spawn_y_val = '0.0'
  spawn_z_val = '0.0'
  spawn_yaw_val = '0.0'

  ############ You do not need to change anything below this line #############
  
  # Set the path to different files and folders.  
  pkg_gazebo_ros = FindPackageShare(package='gazebo_ros').find('gazebo_ros')   
  pkg_share = FindPackageShare(package=package_name).find(package_name)
  world_path = os.path.join(pkg_share, world_file_path)
  gazebo_models_path = os.path.join(pkg_share, gazebo_models_path)
  os.environ["GAZEBO_MODEL_PATH"] = gazebo_models_path
  sdf_model_path = os.path.join(pkg_share, sdf_model_path)
  
  # Launch configuration variables specific to simulation
  gui = LaunchConfiguration('gui')
  headless = LaunchConfiguration('headless')
  namespace = LaunchConfiguration('namespace')
  sdf_model = LaunchConfiguration('sdf_model')
  use_namespace = LaunchConfiguration('use_namespace')
  use_sim_time = LaunchConfiguration('use_sim_time')
  use_simulator = LaunchConfiguration('use_simulator')
  world = LaunchConfiguration('world')
  
  # Declare the launch arguments  
  declare_namespace_cmd = DeclareLaunchArgument(
    name='namespace',
    default_value='',
    description='Top-level namespace')

  declare_use_namespace_cmd = DeclareLaunchArgument(
    name='use_namespace',
    default_value='false',
    description='Whether to apply a namespace to the navigation stack')
            
  declare_sdf_model_path_cmd = DeclareLaunchArgument(
    name='sdf_model', 
    default_value=sdf_model_path, 
    description='Absolute path to robot sdf file')

  declare_simulator_cmd = DeclareLaunchArgument(
    name='headless',
    default_value='False',
    description='Whether to execute gzclient')
    
  declare_use_sim_time_cmd = DeclareLaunchArgument(
    name='use_sim_time',
    default_value='true',
    description='Use simulation (Gazebo) clock if true')

  declare_use_simulator_cmd = DeclareLaunchArgument(
    name='use_simulator',
    default_value='True',
    description='Whether to start the simulator')

  declare_world_cmd = DeclareLaunchArgument(
    name='world',
    default_value=world_path,
    description='Full path to the world model file to load')
  
  # Start Gazebo server
  start_gazebo_server_cmd = IncludeLaunchDescription(
    PythonLaunchDescriptionSource(os.path.join(pkg_gazebo_ros, 'launch', 'gzserver.launch.py')),
    condition=IfCondition(use_simulator),
    launch_arguments={'world': world}.items())

  # Start Gazebo client    
  start_gazebo_client_cmd = IncludeLaunchDescription(
    PythonLaunchDescriptionSource(os.path.join(pkg_gazebo_ros, 'launch', 'gzclient.launch.py')),
    condition=IfCondition(PythonExpression([use_simulator, ' and not ', headless])))

  # Launch the robot
  spawn_entity_cmd = Node(
    package='gazebo_ros', 
    executable='spawn_entity.py',
    arguments=['-entity', robot_name_in_model, 
               '-file', sdf_model,
                  '-x', spawn_x_val,
                  '-y', spawn_y_val,
                  '-z', spawn_z_val,
                  '-Y', spawn_yaw_val],
                  output='screen')

  # Create the launch description and populate
  ld = LaunchDescription()

  # Declare the launch options
  ld.add_action(declare_namespace_cmd)
  ld.add_action(declare_use_namespace_cmd)
  ld.add_action(declare_sdf_model_path_cmd)
  ld.add_action(declare_simulator_cmd)
  ld.add_action(declare_use_sim_time_cmd)
  ld.add_action(declare_use_simulator_cmd)
  ld.add_action(declare_world_cmd)

  # Add any actions
  ld.add_action(start_gazebo_server_cmd)
  ld.add_action(start_gazebo_client_cmd)
  ld.add_action(spawn_entity_cmd)

  return ld

Save the file and close it.

Build the Package

Go to the root folder.

cd ~/dev_ws/

Build the package.

colcon build

Launch the Launch File

Now let’s launch the launch file.

cd ~/dev_ws/
ros2 launch two_wheeled_robot launch_sdf_into_gazebo.launch.py

Here is the output:

5-launch-sdf-file-gazebo

How to Load a URDF File into Gazebo – ROS 2

In this post, I will show you how to load a URDF file into Gazebo. Universal Robot Description Format (URDF) is the standard ROS format for robot modeling. 

If you would like to learn more about URDF files, check out this page.

Prerequisites

You can find the files for this post here on my Google Drive.

Create the URDF File

I am going to open up a terminal window, and type the following command to go to the directory where my URDF file will be located.

cd ~/dev_ws/src/two_wheeled_robot/urdf

Add your URDF file to this folder. For example, you can add a URDF file like this:

<?xml version="1.0"?>
<robot name="two_wheeled_robot" xmlns:xacro="http://ros.org/wiki/xacro">

  <!-- ****************** ROBOT CONSTANTS *******************************  -->
  <!-- Define the size of the robot's main chassis in meters -->
  <xacro:property name="base_width" value="0.39"/>
  <xacro:property name="base_length" value="0.70"/>
  <xacro:property name="base_height" value="0.20"/>
	
  <!-- Define the shape of the robot's two back wheels in meters -->
  <xacro:property name="wheel_radius" value="0.14"/>
  <xacro:property name="wheel_width" value="0.06"/>

  <!-- x-axis points forward, y-axis points to left, z-axis points upwards -->
  <!-- Define the gap between the wheel and chassis along y-axis in meters -->
  <xacro:property name="wheel_ygap" value="0.035"/>

  <!-- Position the wheels along the z-axis -->
  <xacro:property name="wheel_zoff" value="0.05"/>

  <!-- Position the wheels along the x-axis -->
  <xacro:property name="wheel_xoff" value="0.221"/>

  <!-- Position the caster wheel along the x-axis -->
  <xacro:property name="caster_xoff" value="0.217"/>

  <!-- Define intertial property macros  -->
  <xacro:macro name="box_inertia" params="m w h d">
    <inertial>
      <origin xyz="0 0 0" rpy="${pi/2} 0 ${pi/2}"/>
      <mass value="${m}"/>
      <inertia ixx="${(m/12) * (h*h + d*d)}" ixy="0.0" ixz="0.0" iyy="${(m/12) * (w*w + d*d)}" iyz="0.0" izz="${(m/12) * (w*w + h*h)}"/>
    </inertial>
  </xacro:macro>

  <xacro:macro name="cylinder_inertia" params="m r h">
    <inertial>
      <origin xyz="0 0 0" rpy="${pi/2} 0 0" />
      <mass value="${m}"/>
      <inertia ixx="${(m/12) * (3*r*r + h*h)}" ixy = "0" ixz = "0" iyy="${(m/12) * (3*r*r + h*h)}" iyz = "0" izz="${(m/2) * (r*r)}"/>
    </inertial>
  </xacro:macro>

  <xacro:macro name="sphere_inertia" params="m r">
    <inertial>
      <mass value="${m}"/>
      <inertia ixx="${(2/5) * m * (r*r)}" ixy="0.0" ixz="0.0" iyy="${(2/5) * m * (r*r)}" iyz="0.0" izz="${(2/5) * m * (r*r)}"/>
    </inertial>
  </xacro:macro>
  
  <!-- ********************** ROBOT BASE *********************************  -->
  <link name="base_link">
    <visual>
      <origin xyz="0 0 -0.05" rpy="1.5707963267949 0 3.141592654"/>
      <geometry>
        <mesh filename="file://$(find two_wheeled_robot)/meshes/robot_base.stl" />
      </geometry>
      <material name="Red">
        <color rgba="1.0 0.0 0.0 1.0"/>
      </material>
    </visual>

    <collision>
      <geometry>
        <box size="${base_length} ${base_width} ${base_height}"/>
      </geometry>
    </collision>

    <xacro:box_inertia m="15.0" w="${base_width}" d="${base_length}" h="${base_height}"/>
  </link>

  <gazebo reference="base_link">
    <material>Gazebo/Red</material>
  </gazebo>

  
  <!-- ****************** ROBOT BASE FOOTPRINT ***************************  -->
  <!-- Define the center of the main robot chassis projected on the ground -->	
  <link name="base_footprint">
  	<xacro:box_inertia m="0" w="0" d="0" h="0"/>
  </link>

  <!-- The base footprint of the robot is located underneath the chassis -->
  <joint name="base_joint" type="fixed">
    <parent link="base_link"/>
    <child link="base_footprint"/>
    <origin xyz="0.0 0.0 ${-(wheel_radius+wheel_zoff)}" rpy="0 0 0"/>
  </joint>

  <!-- *********************** DRIVE WHEELS ******************************  -->
  <xacro:macro name="wheel" params="prefix x_reflect y_reflect">
    <link name="${prefix}_link">
      <visual>
        <origin xyz="0 0 0" rpy="${pi/2} 0 0"/>
        <geometry>
            <cylinder radius="${wheel_radius}" length="${wheel_width}"/>
        </geometry>
        <material name="Gray">
          <color rgba="0.5 0.5 0.5 1.0"/>
        </material>
      </visual>

      <collision>
        <origin xyz="0 0 0" rpy="${pi/2} 0 0"/> 
        <geometry>
          <cylinder radius="${wheel_radius}" length="${wheel_width}"/>
        </geometry>
      </collision>

      <xacro:cylinder_inertia m="0.5" r="${wheel_radius}" h="${wheel_width}"/>
    </link>

    <!-- Connect the wheels to the base_link at the appropriate location, and 
         define a continuous joint to allow the wheels to freely rotate about
         an axis -->
    <joint name="${prefix}_joint" type="continuous">
      <parent link="base_link"/>
      <child link="${prefix}_link"/>
      <origin xyz="${x_reflect*wheel_xoff} ${y_reflect*(base_width/2+wheel_ygap)} ${-wheel_zoff}" rpy="0 0 0"/>
      <axis xyz="0 1 0"/>
    </joint>
  </xacro:macro>

  <!-- Instantiate two wheels using the macro we just made through the 
       xacro:wheel tags. We also define the parameters to have one wheel
       on both sides at the back of our robot (i.e. x_reflect=-1). -->
  <xacro:wheel prefix="drivewhl_l" x_reflect="-1" y_reflect="1" />
  <xacro:wheel prefix="drivewhl_r" x_reflect="-1" y_reflect="-1" />

  <!-- *********************** CASTER WHEEL ******************************  -->
  <!-- We add a caster wheel. It will be modeled as sphere.
       We define the wheel’s geometry, material and the joint to connect it to 
       base_link at the appropriate location. -->
  <link name="front_caster">
    <visual>
      <geometry>
        <sphere radius="${(wheel_radius+wheel_zoff-(base_height/2))}"/>
      </geometry>
      <material name="Cyan">
        <color rgba="0 1.0 1.0 1.0"/>
      </material>
    </visual>

    <collision>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
        <sphere radius="${(wheel_radius+wheel_zoff-(base_height/2))}"/>
      </geometry>
    </collision>

    <xacro:sphere_inertia m="0.5" r="${(wheel_radius+wheel_zoff-(base_height/2))}"/>
  </link>
  
  <gazebo reference="front_caster">
    <mu1>0.01</mu1>
    <mu2>0.01</mu2>
    <material>Gazebo/White</material>
  </gazebo>

  <joint name="caster_joint" type="fixed">
    <parent link="base_link"/>
    <child link="front_caster"/>
    <origin xyz="${caster_xoff} 0.0 ${-(base_height/2)}" rpy="0 0 0"/>
  </joint>

  <!-- *********************** IMU SETUP *********************************  -->
  <!-- Each sensor must be attached to a link.                              --> 
  <link name="imu_link">
    <visual>
      <geometry>
        <box size="0.1 0.1 0.1"/>
      </geometry>
    </visual>
    
    <collision>
      <geometry>
        <box size="0.1 0.1 0.1"/>
      </geometry>
    </collision>
      
    <xacro:box_inertia m="0.1" w="0.1" d="0.1" h="0.1"/>
  </link>
    
  <joint name="imu_joint" type="fixed">
    <parent link="base_link"/>
    <child link="imu_link"/>
    <origin xyz="0 0 0.01"/>
  </joint>
    
  <gazebo reference="imu_link">
    <gravity>true</gravity>
    <sensor name="twr_imu" type="imu">
      <always_on>true</always_on>
      <update_rate>100</update_rate>
      <visualize>true</visualize>
      <imu>
        <orientation>
          <x>
            <noise type="gaussian">
              <mean>0.0</mean>
              <stddev>2e-3</stddev>
            </noise>
          </x>
          <y>
            <noise type="gaussian">
              <mean>0.0</mean>
              <stddev>2e-3</stddev>
            </noise>
          </y>
          <z>
            <noise type="gaussian">
              <mean>0.0</mean>
              <stddev>2e-3</stddev>
            </noise>
          </z>
        </orientation>
        <angular_velocity>
          <x>
            <noise type="gaussian">
              <mean>0.0</mean>
              <stddev>2e-4</stddev>
            </noise>
          </x>
          <y>
            <noise type="gaussian">
              <mean>0.0</mean>
              <stddev>2e-4</stddev>
            </noise>
          </y>
          <z>
            <noise type="gaussian">
              <mean>0.0</mean>
              <stddev>2e-4</stddev>
            </noise>
          </z>
        </angular_velocity>
        <linear_acceleration>
          <x>
            <noise type="gaussian">
              <mean>0.0</mean>
              <stddev>1.7e-2</stddev>
            </noise>
          </x>
          <y>
            <noise type="gaussian">
              <mean>0.0</mean>
              <stddev>1.7e-2</stddev>
            </noise>
          </y>
          <z>
            <noise type="gaussian">
              <mean>0.0</mean>
              <stddev>1.7e-2</stddev>
            </noise>
          </z>
        </linear_acceleration>
      </imu>
      <plugin name="two_wheeled_robot_imu" filename="libgazebo_ros_imu_sensor.so">
        <initial_orientation_as_reference>false</initial_orientation_as_reference>
        <frame_name>imu_link</frame_name>
        <ros>
          <namespace>/imu</namespace>
          <remapping>~/out:=data</remapping>
        </ros>
      </plugin>
    </sensor>
</gazebo>

  <!-- *********************** LIDAR SETUP **********************************  -->
 <link name="lidar_link">
    <inertial>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <mass value="0.125"/>
      <inertia ixx="0.001"  ixy="0"  ixz="0" iyy="0.001" iyz="0" izz="0.001" />
    </inertial>

    <collision>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
         <cylinder radius="0.0508" length="0.75"/>
      </geometry>
    </collision>

    <visual>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
         <cylinder radius="0.0508" length="0.75"/>
      </geometry>
    </visual>
  </link>
    
  <joint name="lidar_joint" type="fixed">
    <parent link="base_link"/>
    <child link="lidar_link"/>
    <origin xyz="0.215 0.0 0.30" rpy="0 0 0"/>
  </joint>
    
  <gazebo reference="lidar_link">
    <sensor name="lidar" type="ray">
      <always_on>true</always_on>
      <visualize>true</visualize>
      <update_rate>5</update_rate>
      <ray>
        <scan>
          <horizontal>
            <samples>120</samples>
            <resolution>1.000000</resolution>
            <min_angle>-3.14159</min_angle>
            <max_angle>3.14159</max_angle>
          </horizontal>
        </scan>
        <range>
          <min>0.3</min>
          <max>15.0</max>
          <resolution>0.015</resolution>
        </range>
        <noise>
          <type>gaussian</type>
          <mean>0.0</mean>
          <stddev>0.01</stddev>
        </noise>
      </ray>
      <plugin name="scan" filename="libgazebo_ros_ray_sensor.so">
        <ros>
          <remapping>~/out:=scan</remapping>
        </ros>
        <output_type>sensor_msgs/LaserScan</output_type>
        <frame_name>lidar_link</frame_name>
      </plugin>
    </sensor>
  </gazebo>
  <gazebo reference="lidar_link">
    <mu1>0.01</mu1>
    <mu2>0.01</mu2>
    <material>Gazebo/Black</material>
  </gazebo>


 <!-- *********************** WHEEL ODOMETRY ***************************    --> 
  <gazebo>
    <plugin name="two_wheeled_robot_diff_drive" filename="libgazebo_ros_diff_drive.so">

      <update_rate>30</update_rate>
     
      <!-- wheels -->
      <left_joint>drivewhl_l_joint</left_joint>
      <right_joint>drivewhl_r_joint</right_joint>

      <!-- kinematics -->
      <wheel_separation>0.52</wheel_separation>
      <wheel_diameter>0.28</wheel_diameter>

      <!-- limits -->
      <max_wheel_torque>20</max_wheel_torque>
      <max_wheel_acceleration>1.0</max_wheel_acceleration>

      <!-- Receive velocity commands on this ROS topic -->
      <command_topic>cmd_vel</command_topic>

      <!-- output -->
      <!-- When false, publish no wheel odometry data to a ROS topic -->
      <publish_odom>true</publish_odom>

      <!-- When true, publish coordinate transform from odom to base_footprint -->
      <!-- I usually use the robot_localization package to publish this transform -->   
      <publish_odom_tf>false</publish_odom_tf>
    
      <!-- When true, publish coordinate transform from base_link to the wheels -->
      <!-- The robot_state_publisher package is often used to publish this transform -->   
      <publish_wheel_tf>true</publish_wheel_tf>

      <odometry_topic>odom</odometry_topic>
      <odometry_frame>odom</odometry_frame>
      <robot_base_frame>base_link</robot_base_frame>

      <odometry_source>1</odometry_source>
      <ros>
        <remapping>odom:=wheel/odometry</remapping>
      </ros>
    </plugin>
  </gazebo>
</robot>

Create the Launch File

Now we want to create a launch file.

I am going to go to my launch folder and create the file. Here is the command I will type in my terminal window.

cd ~/dev_ws/src/two_wheeled_robot/launch
gedit launch_urdf_into_gazebo.launch.py

Type the following code inside the file.

# Author: Addison Sears-Collins
# Date: September 23, 2021
# Description: Load a URDF and world file into Gazebo.
# https://automaticaddison.com

import os
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, ExecuteProcess, IncludeLaunchDescription
from launch.conditions import IfCondition, UnlessCondition
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import Command, LaunchConfiguration, PythonExpression
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageShare


def generate_launch_description():

  # Constants for paths to different files and folders
  gazebo_models_path = 'models'
  package_name = 'two_wheeled_robot'
  robot_name_in_model = 'two_wheeled_robot'
  rviz_config_file_path = 'rviz/urdf_gazebo_config.rviz'
  urdf_file_path = 'urdf/two_wheeled_robot_nav2.urdf'
  world_file_path = 'worlds/neighborhood.world'
    
  # Pose where we want to spawn the robot
  spawn_x_val = '0.0'
  spawn_y_val = '0.0'
  spawn_z_val = '0.0'
  spawn_yaw_val = '0.00'

  ############ You do not need to change anything below this line #############
  
  # Set the path to different files and folders.  
  pkg_gazebo_ros = FindPackageShare(package='gazebo_ros').find('gazebo_ros')   
  pkg_share = FindPackageShare(package=package_name).find(package_name)
  default_urdf_model_path = os.path.join(pkg_share, urdf_file_path)
  default_rviz_config_path = os.path.join(pkg_share, rviz_config_file_path)
  world_path = os.path.join(pkg_share, world_file_path)
  gazebo_models_path = os.path.join(pkg_share, gazebo_models_path)
  os.environ["GAZEBO_MODEL_PATH"] = gazebo_models_path
  
  # Launch configuration variables specific to simulation
  gui = LaunchConfiguration('gui')
  headless = LaunchConfiguration('headless')
  namespace = LaunchConfiguration('namespace')
  rviz_config_file = LaunchConfiguration('rviz_config_file')
  urdf_model = LaunchConfiguration('urdf_model')
  use_namespace = LaunchConfiguration('use_namespace')
  use_robot_state_pub = LaunchConfiguration('use_robot_state_pub')
  use_rviz = LaunchConfiguration('use_rviz')
  use_sim_time = LaunchConfiguration('use_sim_time')
  use_simulator = LaunchConfiguration('use_simulator')
  world = LaunchConfiguration('world')
  
  # Declare the launch arguments  
  declare_use_joint_state_publisher_cmd = DeclareLaunchArgument(
    name='gui',
    default_value='True',
    description='Flag to enable joint_state_publisher_gui')
    
  declare_namespace_cmd = DeclareLaunchArgument(
    name='namespace',
    default_value='',
    description='Top-level namespace')

  declare_use_namespace_cmd = DeclareLaunchArgument(
    name='use_namespace',
    default_value='false',
    description='Whether to apply a namespace to the navigation stack')
            
  declare_rviz_config_file_cmd = DeclareLaunchArgument(
    name='rviz_config_file',
    default_value=default_rviz_config_path,
    description='Full path to the RVIZ config file to use')

  declare_simulator_cmd = DeclareLaunchArgument(
    name='headless',
    default_value='False',
    description='Whether to execute gzclient')

  declare_urdf_model_path_cmd = DeclareLaunchArgument(
    name='urdf_model', 
    default_value=default_urdf_model_path, 
    description='Absolute path to robot urdf file')
    
  declare_use_robot_state_pub_cmd = DeclareLaunchArgument(
    name='use_robot_state_pub',
    default_value='True',
    description='Whether to start the robot state publisher')

  declare_use_rviz_cmd = DeclareLaunchArgument(
    name='use_rviz',
    default_value='True',
    description='Whether to start RVIZ')
    
  declare_use_sim_time_cmd = DeclareLaunchArgument(
    name='use_sim_time',
    default_value='true',
    description='Use simulation (Gazebo) clock if true')

  declare_use_simulator_cmd = DeclareLaunchArgument(
    name='use_simulator',
    default_value='True',
    description='Whether to start the simulator')

  declare_world_cmd = DeclareLaunchArgument(
    name='world',
    default_value=world_path,
    description='Full path to the world model file to load')
  
  # Subscribe to the joint states of the robot, and publish the 3D pose of each link.    
  start_robot_state_publisher_cmd = Node(
    package='robot_state_publisher',
    executable='robot_state_publisher',
    parameters=[{'robot_description': Command(['xacro ', urdf_model])}])

  # Publish the joint states of the robot
  start_joint_state_publisher_cmd = Node(
    package='joint_state_publisher',
    executable='joint_state_publisher',
    name='joint_state_publisher',
    condition=UnlessCondition(gui))

  # Launch RViz
  start_rviz_cmd = Node(
    package='rviz2',
    executable='rviz2',
    name='rviz2',
    output='screen',
    arguments=['-d', rviz_config_file])

  # Start Gazebo server
  start_gazebo_server_cmd = IncludeLaunchDescription(
    PythonLaunchDescriptionSource(os.path.join(pkg_gazebo_ros, 'launch', 'gzserver.launch.py')),
    condition=IfCondition(use_simulator),
    launch_arguments={'world': world}.items())

  # Start Gazebo client    
  start_gazebo_client_cmd = IncludeLaunchDescription(
    PythonLaunchDescriptionSource(os.path.join(pkg_gazebo_ros, 'launch', 'gzclient.launch.py')),
    condition=IfCondition(PythonExpression([use_simulator, ' and not ', headless])))

  # Launch the robot
  spawn_entity_cmd = Node(
    package='gazebo_ros', 
    executable='spawn_entity.py',
    arguments=['-entity', robot_name_in_model, 
                '-topic', 'robot_description',
                    '-x', spawn_x_val,
                    '-y', spawn_y_val,
                    '-z', spawn_z_val,
                    '-Y', spawn_yaw_val],
                    output='screen')

  # Create the launch description and populate
  ld = LaunchDescription()

  # Declare the launch options
  ld.add_action(declare_use_joint_state_publisher_cmd)
  ld.add_action(declare_namespace_cmd)
  ld.add_action(declare_use_namespace_cmd)
  ld.add_action(declare_rviz_config_file_cmd)
  ld.add_action(declare_simulator_cmd)
  ld.add_action(declare_urdf_model_path_cmd)
  ld.add_action(declare_use_robot_state_pub_cmd)  
  ld.add_action(declare_use_rviz_cmd) 
  ld.add_action(declare_use_sim_time_cmd)
  ld.add_action(declare_use_simulator_cmd)
  ld.add_action(declare_world_cmd)

  # Add any actions
  ld.add_action(start_gazebo_server_cmd)
  ld.add_action(start_gazebo_client_cmd)
  ld.add_action(spawn_entity_cmd)
  ld.add_action(start_robot_state_publisher_cmd)
  ld.add_action(start_joint_state_publisher_cmd)
  ld.add_action(start_rviz_cmd)

  return ld

Save the file and close it.

Build the Package

Go to the root folder.

cd ~/dev_ws/

Build the package.

colcon build

Launch the Launch File

Now let’s launch the launch file.

cd ~/dev_ws/
ros2 launch two_wheeled_robot launch_urdf_into_gazebo.launch.py

Here is the output:

1-load-urdf-into-gazebo-1

You will also see that an Rviz window pops up. You can change the Fixed Frame to base_link so the robot appears.

2-fixed-frame-base-link
3-robot-base-link

That’s it. Keep building!