Writing your VL53L1X driver (Python)
This tutorial will guide you through combining distance measurement and relative velocity measurement of the VL53L1X Time-of Flight sensor into a driver, which will be included in a ROS wrapper.
It is assumed that you have already read Distance measurement with ToF sensor VL53L1X (Python) and Speed measurement with ToF sensor VL53L1X (Python).
Besides the basics in ROS from the Tutorials, it is also already assumed that you have read the chapters What is a ROS Wrapper?, Write a ROS Wrapper (Python) and Package and Test Your Driver.
Programming our Driver code
Finally, we build a wrapper around an existing driver code so that we can query the speed and distance with a single line later. Our driver looks like this:
import os
import sys
import time
import RPi.GPIO as GPIO
import VL53L1X
class ToFVL53L1X(object):
def __init__(self, address, xshut):
self.address = address
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
self.xshut = xshut
GPIO.setup(self.xshut, GPIO.IN)
# 1 = Short Range, 2 = Medium Range, 3 = Long Range
self.range = 1
if(self.address == 0x29):
self.tof = VL53L1X.VL53L1X(i2c_bus=1, i2c_address = 0x29)
else:
self.tof = VL53L1X.VL53L1X(i2c_bus=1, i2c_address = self.address)
self.tof.open()
def start_sensor(self, pin):
# The XSHUT pin is set HIGH to activate the sensor.
GPIO.setup(pin, GPIO.OUT)
GPIO.output(pin, True)
time.sleep(0.2)
self.tof.open()
self.tof.start_ranging(self.range)
def stop_sensor(self, pin):
self.tof.stop_ranging()
GPIO.output(pin, False)
def set_range(self, range):
if range == "short":
self.range = 1
elif range == "medium":
self.range = 2
else:
self.range = 3
self.tof.stop_ranging()
self.tof.start_ranging(self.range)
def get_range(self):
if self.range == 1:
currentRange = "short"
elif self.range == 2:
currentRange = "medium"
else:
currentRange = "long"
return currentRange
def get_distance(self):
distance = 0.0
distance = self.tof.get_distance() * 0.1 # mm to cm conversion
if distance >= 0:
return distance
else:
return -1
def get_speed(self):
start_distance = self.get_distance() * 0.01 # to m conversion
time.sleep(1)
end_distance = self.get_distance() * 0.01 # to m conversion
speed = (end_distance - start_distance) / 1.0 # m/s
return speedWe need the XSHUT pin so that a sensor can also be activated.
Programming our Wrapper code
The next step is to build our ROS wrapper for this driver. This looks like the following for example:
import rospy
from sensor_msgs.msg import Range
# import custom message type for Relative Velocity
from tof_vl53l1x import ToFVL53L1X # import our driver module
class ToFVL53L1XWrapper(object):
def __init__(self):
self.min_range = rospy.get_param("~minimum_range", 3)
self.max_range = rospy.get_param("~maximum_range", 400)
self.fov = rospy.get_param("~field_of_view", 27) * 0.0174532925199433 # degree to radian conversion
i2c_address = rospy.get_param("~i2c_address", 0x29)
xshut = rospy.get_param("~xshut", 17)
self.tofPub = rospy.Publisher('/tof/distance', Range, queue_size=10)
self.tofVelocityPub = rospy.Publisher('/tof/relative_velocity', Range, queue_size=10)
# loading the driver
self.tof_sensor = ToFVL53L1X(i2c_address, xshut)
self.rate = rospy.Rate(10) # 10hz
rospy.Timer(self.rate, self.publish_current_distance)
rospy.Timer(self.rate, self.publish_current_velocity)
def publish_current_distance(self):
distance = self.tof_sensor.distance()
message_str = "Distance: %s cm" % distance
rospy.loginfo(message_str)
#for distance in ranges:
r = Range()
r.header.stamp = rospy.Time.now()
r.header.frame_id = "/base_link"
r.radiation_type = Range.INFRARED
r.field_of_view = self.fov
r.min_range = self.min_range
r.max_range = self.max_range
r.range = distance
self.tofPub.publish(r)
def publish_current_velocity(self):
relative_velocity = self.tof_sensor.speed()
message_str = " Speed: %s m/s" % relative_velocity
rospy.loginfo(message_str)
rv = Range() # using range instead of a custom message type
rv.header.stamp = rospy.Time.now()
rv.header.frame_id = "/base_link"
rv.radiation_type = Range.INFRARED
rv.field_of_view = self.fov
rv.min_range = self.min_range
rv.max_range = self.max_range
rv.rage = relative_velocity # using range instead of a custom message type with a field relative velocity
self.tofVelocityPub.publish(rv)
def stop(self):
self.tofPub.unregister()
self.tofVelocityPub.unregister()
# Main function.
if __name__ == '__main__':
# Initialize the node and name it.
rospy.init_node("tof_driver", anonymous=False)
tof_wrapper = ToFVL53L1XWrapper()
rospy.on_shutdown(tof_wrapper.stop)
rospy.loginfo("Ultrasonic driver is now started.")
rospy.spin()Now we have a nice ROS driver that we can use for our ToF sensors. However, we haven't even talked about the fact that you can only assign an I2C address once. For this we look at Using multiple VL53L1X sensors at the same time (Python).