MRC/Tutorials/Obtaining laser, odometry, and control effort data

From Control Systems Technology Group
Jump to navigation Jump to search

Introduction

This page provides a short description of the laser data, odometry data, and control effort data that can be obtained throught the IO object introduced earlier.

Laser Data

To obtain the laser data, do the following:

emc::LaserData scan;
if (io.readLaserData(scan))
{
    // ... We got the laser data, now do something useful with it!
}

The LaserData struct is defined as follows:

struct LaserData
{
    double range_min;
    double range_max;
    
    double angle_min;
    double angle_max;
    double angle_increment;

    std::vector<float> ranges;

    double timestamp;
};

The range_min and range_max values define what the smallest and largest measurable distances are. If a distance reading is below range_min or above range_max, that reading is invalid. The values angle_min and angle_max determine the angle of the first and last beam in the measurement. The value angle_increment is the angle difference between two beams. Note that it is actually superfluous, as it can be derived from angle_min, angle_max and the number of beams.

The actual sensor readings are stored in ranges. It is an std::vector, a vector of values which, in this case, stores floats. Each vector element corresponds to one measured distance in meters at a particular angle. That angle can be calculated from angle_min, angle_increment and the index of the element in the vector.

Finally, the timestamp specifies at which point in time the data was measured. The timestamp is in Unix time, i.e., the number of seconds since 1 January 1970. Note that the absolute value is not necessarily important, but that the timestamp can be handy to keep track of laser data over time, or to synchronize it with other input data (e.g., the odometry data).

Odometry Data

The PICO robot has a holonomic wheel base which consists of three so-called omni-wheels. The specific configuration of the wheels allows the robot to move both forwards and sideways, and enables it to rotate around its axis. Each wheel has an encoder which keeps track of the rotations of that wheel. By using all three encoders and knowing the wheel configuration, the displacement and rotation of the robot can be calculated. In other words: we can calculate how far the robot drove and how far it rotated since it's initial position. This translation and rotation based on the wheel encoders is called odometry. However, note that this information is highly sensitive to noise: small errors caused by measurement errors and wheel slip are accumalate over time. Therefore, relying on odometry data alone over longer periods of time is not recommended!

To obtain the odometry information, do the following:

emc::OdometryData odom;
if (io.readOdometryData(odom))
{
    // ... We got the odom data, now do something useful with it!
}

The OdometryData struct is defined as follows:

struct OdometryData
{
    double x;
    double y;
    double a;
    double timestamp;
};

Here x, y and a define the displacement and rotation of the robot since its start, according to the wheel rotations. The translation (x, y) is in meters. The rotation, a is in radians between -pi and pi. Like the laser data, the odometry data also contains a timestamp which is in seconds (Unix time).

Control Effort

remark: The control effort data will not give any output in the simulator and will only give outputs when testing on the real pico.
To obtain the control effort information, do the following:

emc::ControlEffort ce;
if (io.readControlEffort(ce))
{
    // ... We got the control effort data, now do something useful with it!
}

The ControlEffort struct is defined as follows:

struct ControlEffort
{
    double x;
    double y;
    double th;
    double timestamp;
};

Here, the 'x','y', and 'th' correspond to, respectively, the control effort in the x, y, and theta direction. Similar to all the data structures above, the timestamp is defined in Unix time.

Lazy Boolean Evaluation

The functions shown above are boolean function, i.e., they return True or False to indicate whether a new measurement of the sensor is available. If you want something to happen when either laser or odometer data is found you may be tempted to use something like:

emc::LaserData scan;
emc::OdometryData odom;
if (io.readLaserData(scan) || io.readOdometryData(odom))
{
    // ... We got sensor data, now do something useful with it!
}

However c++ uses lazy boolean evaluation. Meaning it won't evaluate the second argument in an OR statement if the first is already True. Similarly it won't evaluate the second argument in an AND statement if the first is False. In this example the laser data will be read but the odom struct remains empty!