Back to [[ros:main-ros|ROS main page]] Here we will see how to create the ROS driver for an existing non ROS robot. ====== Hardware ====== The DART V2 robot is an educational robot, built at ENSTA Bretagne to teach mobile robotics. The DARTV2 robot is a 4X4 wheeled robot with differential drive. {{ :ros:dartv2_img_7309.jpg?400 |}} The actuators are : - 2 left motors : front and rear left wheels with the same command - 2 right motors : same as above for right wheels - the 2 digits seven segment display : if not changed show the ID (number) of the robot The motors are driven by a T-REX power board, connected in I2C. The sensors are : - Pololu IMU 9V5 inertial motion unit, i2C bus - Home made encoders (odometers) on rear wheels, i2C bus - T-REX encoders (odometers) on front wheels, i2C bus - 4 cardinal (front, left, right and rear) ultrasonic sensors, I2C bus - 2 diagonal (front-left and front-right) ultrasonic sensors , I2C bus ====== Software : ROS drivers ====== This first version the DARTV2 ROS drivers is written in Python 2.7. We will see after how to to the same drivers in C++ and in Python 3 for ROS2. ===== ROS drivers Python 2.7 for kinetic and melodic ===== ROS kinetic is running on the robot and ROS melodic on the host computer. ==== Connect the Robot to the ROS master on the host computer ==== The workspace is created on the robot , to get the link between the robot and the host computer we define the host computer as the master. The master executes the **roscore** command. The IP address of the master is for example 172.20.22.34 and the master runs ROS melodic (Ubuntu 18.04) : export ROS_IP=172.20.22.34 export ROS_MASTER_URI=http://172.20.22.34:11311 source /opt/ros/melodic/setup.bash roscore The robot runs ROS kinetic and the link to the master is defined as follows : export ROS_IP=172.20.25.102 export ROS_HOSTNAME=DART02 export ROS_MASTER_URI=http://172.20.22.34:11311 source /opt/ros/kinetic/setup.bash ==== Create ROS workspace and package ==== On the robot, create a folder to implement the ROS nodes with the drivers code. Then create a ROS package called **dartv2_drivers**for the drivers : cd $HOME mkdir -p ros/dart_drivers_py27_ws/src cd ros/dart_drivers_py27_ws/src catkin_create_pkg dartv2_drivers std_msgs message_generation rospy cd .. catkin_make Add folders for Python scripts, launch files and custom messages : cd $HOME/ros/dart_drivers_py27_ws/src/dartv2_drivers mkdir bin mkdir launch mkdir msg The python code of the node will be in the **src** folder. The **bin** folder contains the python executable code of the nodes. The file names of the nodes codes are generally not ended by .py, and must be executable. To check that all is working fine, we create a simple python node. We are using recommendations in [[http://wiki.ros.org/rospy_tutorials/Tutorials/Makefile|this ROS makefile guide]] to create the folder tree structure. The node is called **hello** and is located in **bin** folder. It uses a function defined in **hello.py** located in **src** folder. cd $HOME/ros/dart_drivers_py27_ws/src/dartv2_drivers cd bin cat << EOF > hello #! /usr/bin/env python import dartv2_drivers.hello as drt if __name__ == '__main__': drt.say('my friend!') EOF chmod +x hello cd ../src mkdir dartv2_drivers cd dartv2_drivers touch __init__.py cat << EOF > hello.py def say(name): print('Hello ' + name) EOF A Python setup file (setup.py) must be added in the package folder (dart_drivers_py27_ws) : cd $HOME/ros/dart_drivers_py27_ws/src/dartv2_drivers cat << EOF > setup.py ## ! DO NOT MANUALLY INVOKE THIS setup.py, USE CATKIN INSTEAD from distutils.core import setup from catkin_pkg.python_setup import generate_distutils_setup # fetch values from package.xml setup_args = generate_distutils_setup( packages=['dartv2_drivers'], package_dir={'': 'src'}, ) setup(**setup_args) EOF The line **# catkin_python_setup()** in **CMakeLists.txt** must be uncommented to execute **setup.py** during **catkin_make**. This change can be made with sed. Once it's done the workspace is updated : cd $HOME/ros/dart_drivers_py27_ws/src/dartv2_drivers sed -i 's/# catkin_python_setup()/catkin_python_setup()/g' CMakeLists.txt cd ../.. catkin_make source devel/setup.bash Finally, the test program should execute well and it can be removed rosrun dartv2_drivers hello rm $HOME/ros/dart_drivers_py27_ws/src/dartv2_drivers/bin/hello rm $HOME/ros/dart_drivers_py27_ws/src/dartv2_drivers/src/dartv2_drivers/hello.py* ==== First driver - Odometers ==== We will use a custom message format for the odometers. A micro-controller gives the value of the 2 rear wheel odometers with a time stamp. The micro-controller is also giving the battery voltage. The front odometers are given by the T-REX motor power board. All these data will be placed in custom messages. So we need to setup the node so that custom message can be recognized and made available to ROS. The custom messages are defined is the **msg** folder : cd $HOME/ros/dart_drivers_py27_ws/src/dartv2_drivers/msg cat << EOF > Odometers.msg int32 odom_front_left int32 odom_front_right int32 odom_rear_left int32 odom_rear_right float32 time_stamp_rear EOF cat << EOF > Battery.msg float32 v EOF To use the new messages, the package has to be recompiled and then the messages can be accessed (with **rosmsg** for example) : cd $HOME/ros/dart_drivers_py27_ws catkin_make source devel/setup.bash rosmsg show dartv2_drivers/Battery rosmsg show dartv2_drivers/Odometers **CMakeLists.txt** (in dartv2_drivers folder) must be modified to be able to import new messages in Python code. In **CMakeLists.txt**, remove comments in **add_message_files** section and add the .msg file names, also remove comments in **generate_messages** section. In **CMakeLists.txt**, the default **add_message_files** section should look like this : ## Generate messages in the 'msg' folder # add_message_files( # FILES # Message1.msg # Message2.msg # ) To add our custom messages we change it to : ## Generate messages in the 'msg' folder add_message_files( FILES Odometers.msg Battery.msg ) After removing comments in **generate_messages** section of **CMakeLists.txt**, you should have this : ## Generate added messages and services with any dependencies listed here generate_messages( DEPENDENCIES std_msgs ) cd $HOME/ros/dart_drivers_py27_ws catkin_make source devel/setup.bash Once the custom messages are defined and recognized, a dummy publisher can be written and tested cd $HOME/ros/dart_drivers_py27_ws/src/dartv2_drivers/bin cat << EOF > Odometers #!/usr/bin/env python # license removed for brevity import rospy from dartv2_drivers.msg import Odometers, Battery import dartv2_drivers.drivers.encoders as odo_rear_drv import time def talker(): global odo_rear pub = rospy.Publisher('dartv2_odometers_pub', Odometers) rospy.init_node('dartv2_odometers', anonymous=True) r = rospy.Rate(1) #1hz msg = Odometers() t0 = time.time() while not rospy.is_shutdown(): orl,orr,odt = odo_rear.read_encoders_both(dt=True) msg.odom_rear_left = orl msg.odom_rear_right = orr rospy.loginfo(msg) pub.publish(msg) r.sleep() if __name__ == '__main__': try: odo_rear = odo_rear_drv.EncodersIO() talker() except rospy.ROSInterruptException: pass EOF chmod +x Odometers rosrun dartv2_drivers Odometers ==== Motors ==== The motors are actuators. Instead of a "talking" node we need to define a "listening" node. In ROS motor command is generally achieved thorough the classical topic **/cmd_vel**. Here, we will use a more simple topic based on a custom message that directly controls the PWM of the left and right sides : cd $HOME/ros/dart_drivers_py27_ws/src/dartv2_drivers/msg cat << EOF > Motors.msg int16 pwm_l int16 pwm_r EOF ==== Launch ==== roslaunch dartv2_drivers dartv2_drivers.launch in another terminal send PWM commands to wheel motors rostopic pub -1 dartv2_motors dartv2_drivers/Motors "{pwm_l: -90, pwm_r: 90}" rostopic pub -1 dartv2_motors dartv2_drivers/Motors "{pwm_l: 0, pwm_r: 0}" ==== Sonars ==== DART V2 uses two types of sonars : * 4 cardinal sonars (front,left,back and right) managed by a dedicated micro-controller via I2C bus * 2 front diagonal sonars (front-left and front-right) directly connected on I2C The 4 cardinal sonars are used in mode 2 (sync) that gives a new measurement every 200 ms, associated with the time stamp of the measurement. All 4 sonars are fired simultaneously. The 2 diagonal sonars are acquired simultaneously every 200 ms. The python code **Sonars** publishes the results on a custom message defined in **Sonars.msg** : cd $HOME/ros/dart_drivers_py27_ws/src/dartv2_drivers/msg cat << EOF > Sonars.msg float32 front float32 left float32 back float32 right float32 front_left float32 front_right EOF The **add_message_files** section of **CMakeList.txt** must be updated to add **Sonars.msg**. ==== IMU ==== IMU is a mini IMU 9 from Pololu. The raw data message is mode of the 3 coordinates of the magnetic filed, the acceleration along the 3 axis and rotation speed around the 3 axis. A custom message, **ImuRaw.msg** is created : cd $HOME/ros/dart_drivers_py27_ws/src/dartv2_drivers/msg cat << EOF > ImuRaw.msg float32 magx float32 magy float32 magz float32 accelx float32 accely float32 accelz float32 gyrox float32 gyroy float32 gyroz EOF The **add_message_files** section of **CMakeList.txt** must be updated to add **ImuRaw.msg**.