[Documentation] [TitleIndex] [WordIndex

Writing your GP2Y0A02YKOF driver (Python)

This tutorial will guide you through combining distance measurement and relative velocity measurement of the GP2Y0A02YKOF infrared sensor into a driver, which will be included in a ROS wrapper.

It is assumed that you have already read Distance measurement with IR sensor GP2Y0A02YKOF (Python) and Speed measurement with IR sensor GP2Y0A02YKOF (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

Important: I would like to write two drivers. Namely, our MCP3008 analog-to-digital converter gets its own driver. Later we want to include this driver in our infrared scanning sensor driver. The MCP3008 can also be reused for other analog sensors on the Raspberry Pi with it. The code looks like this:

import sys
import spidev
import time

class MCP3008(object):

    def __init__(self, bus, device):
        self.spi = spidev.SpiDev()
        self.spi.open(bus, device) # bus = 0, device = 0
        self.spi.max_speed_hz = 1000000

    def readIRAdc(self, channel = 0):
        # analog to digital conversion
        # http://arduinomega.blogspot.com/2011/05/infrared-long-range-sensor-gift-of.html
        adc = self.spi.xfer2([1,(8 + channel) << 4, 0])
        data = ((adc[1] & 3) << 8) + adc[2]

        return data

    def close(self):
        self.spi.close()

This driver is universal and can be used independently of ROS on any Raspberry Pi for the MCP3008.

Following then the driver for our infrared distance sensor:

import os
import sys
import getpass
import time
from mcp3008 import MCP3008

class IRGP2Y0A02YKOF(object):

    def __init__(self, channel):
        self.mcp = MCP3008(0, 0)
        self.channel = channel

    def distance(self):
        read = self.mcp.readIRAdc(self.channel)

        if read > 0:
            # 10650.08 * x ^ (-0.935)
            distance = 10650.08 * pow(read,-0.935)
        else:
            distance = 0

        if distance >= 0:
            return distance
        else:
            return -1

    def speed(self):
        start_time = time.time()

        start_distance = self.distance() * 0.01     # to m conversion
        end_distance = self.distance() * 0.01       # to m conversion

        end_time = time.time()

        speed = (end_distance - start_distance) / (end_time - start_time)   # m/s

        return speed

As you can see, I used a different formula for calculating the distance towards the end.

Writing our ROS Wrapper

Finally the ROS Wrapper looks like this:

import rospy
from sensor_msgs.msg import Range
# import custom message type for Relative Velocity
from ir_irgp2y0a02ykof import IRGP2Y0A02YKOF # import our driver module

class IRGP2Y0A02YKOFWrapper(object):

    def __init__(self):
        self.min_range = rospy.get_param("~minimum_range", 2)
        self.max_range = rospy.get_param("~maximum_range", 150)
        self.fov = rospy.get_param("~field_of_view", 5) * 0.0174532925199433 # degree to radian conversion

        channel = rospy.get_param("~channel", 0)

        self.irPub = rospy.Publisher('/infrared/distance', Range, queue_size=10)
        self.irVelocityPub = rospy.Publisher('/infrared/relative_velocity', Range, queue_size=10)

        # loading the driver
        self.ir_sensor = IRGP2Y0A02YKOF(channel)

        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.ir_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.irPub.publish(r)

    def publish_current_velocity(self):
        relative_velocity = self.ir_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.irVelocityPub.publish(rv)

    def stop(self):
        self.irPub.unregister()
        self.irVelocityPub.unregister()

# Main function.
if __name__ == '__main__':
    # Initialize the node and name it.
    rospy.init_node("tof_driver", anonymous=False)

    ir_wrapper = IRGP2Y0A02YKOFWrapper()

    rospy.on_shutdown(ir_wrapper.stop)
    rospy.loginfo("Ultrasonic driver is now started.")

    rospy.spin()

2024-12-21 12:14