NAV
cpp

Introduction

Welcome to GREATAPI, a Vex Robotics library for the PROS API.

Installation

To install GREATAPI, head to our releases page and download the lastest GREATAPI@{something}.zip.

Open up a console on the folder you've downloaded GREATAPI to, and enter the following command:

prosv5 conduct fetch greatapi@[whatever version we are on].zip

From here, GREATAPI should be addable on the upgrade tab of the PROS editor. You may have to manually type in the name for it to be detected. If GREATAPI isn't there or you want to use vscode instead, open up a console in the directory of your PROS project(the folder with project.pros) and enter the following:

prosv5 conduct apply greatapi

Odometry

GREATAPI provides multiple feature levels of odometry for different robot configurations.

Rotational Odometry

The lowest feature level GREATAPI provides is rotation tracking via the odom_rotation class.

Various tracking approaches are supported by child classes of odom_rotation

Class Name Utlized Sensors Required Measurements
IMU_odom_rotation 1x V5 Inertial Sensor Inertial drift compensation multiplier
DoubleIMU_odom_rotation 2x V5 Inertial Sensor none
TWheel_odom_rotation 2x Wheel encoders(any type) Distance between wheel encoders, wheel diameters

odom_rotation child classes provide two functions for the end user:

Function Purpose Output
get_heading() Returns absolute heading of robot from starting position SRAD angle object
applyOffset(SRAD real) Calibrates odometry output to match that of the real angle nothing

IMU_odom_rotation

Prototype

  IMU_odom_rotation(int port, double driftcompensationfac)

Example

  greatapi::odometry::IMU_odom_rotation example(15, 1.01); //IMU on port 15, 101% drift compensation factor
  while(true){
    greatapi::SRAD currentangle = example.get_heading(); //get current heading
    cout << currentangle; //print angle on terminal
    pros::delay(10); //10ms delay
  }

The v5 IMU has an update period of 10ms. Checking more frequently than that will not yield different results.

IMU_odom_rotation is a basic wrapper for the vex IMU.

It provides the capability to counteract integration drift via an automatic scaling the calculated angle by a specific amount.

Integration drift

Integration drift is a phenomenon commonly found in inertial sensors. Inertial sensors measure the acceleration, and angular velocity of the robot at any point in time. To convert these values into more useful measurements like speed, heading, or position, we must use a technique known as integration. While we don't know where the robot actually is with these sensor values, we can assume that it starts at some fixed state(like not moving). From there, we can convert our accelerations and angular velocities into velocity, position, and heading changes. These can be added to the previous calculated position to get the current position.

The problem with this approach is obvious - Inaccuracies cannot be removed, and can add up until the sensor is inaccurate.

Incidentally, this is why most vex libraries don't provide the feature to calculate position using the IMU. To get position data, one needs to compound in two layers(acceleration -> velocity -> position), meaning that any inaccuracy in velocity will indefinitely increase inaccuracy in position. Heading data only needs one compounding step(angular velocity -> heading), so this infinite compounding situation doesn't occur.

A quick and quite dirty way of compensating for this is to just scale the total degrees traveled by a hard set multiplier. One can experimentally determine a multiplier just by spinning the robot a known amount of degrees, and then dividing the known value by the sensor reported angle. Spinning over 10 rotations is suggested to minimize inaccuracy in determining the real traveled angle of the robot.

The multiplier is to be put in as the driftcompensationfac parameter of the constructor.

DoubleIMU_odom_rotation

Supposedly, there is a method of canceling out inertial drift with the same model of sensor by flipping one over and averaging out their outputs. Somehow, the drift direction of each sensor would be opposite to each other, which leads to a rough canceling out of them due to the same sensor hardware being used. There are specifics to the implementation that I am not clear on, but it is a possiblity to be added in future versions.

TWheel_odom_rotation

Prototype

  TWheel_odom_rotation(TWheel* L, TWheel* R, double dist_btwn)

Example

  //TWheel is an abstract class. You should be using the constructor of specific TWheels
  greatapi::TWheel* leftwheel = new greatapi::TWheel_Motor(5, pros::E_MOTOR_GEARSET_18,true, 2.75); //V5 motor, 200RPM, reversed, 2.75in wheel

  //For information on TWheel, please see the TWheel section of the site.
  greatapi::TWheel* rightwheel = new greatapi::TWheel_RotationSensor(4, false, 4); //V5 rotation sensor, not reversed, 4in wheel
  //note that we are making TWheel* (TWheel pointers), not TWheels.

  greatapi::odometry::TWheel_odom_rotation example = *new greatapi::odometry::TWheel_odom_rotation(leftwheel,rightwheel,15) //15 inches between

  while(true){
    greatapi::SRAD currentangle = example.get_heading(); //get current heading
    cout << currentangle; //print angle on terminal
    pros::delay(10); //10ms delay
  }

TWheel_odom_rotation provides rotational odometry through usage of two parallel wheels mounted onto some type of encoder.

By comparing the traveled distances between two parallel wheels, it is possible to determine the relative distance from their starting orientations. We can logically explain this by noting that two parallel wheels will always traverse the same distance in translations. Therefore, any difference in their travel must be from rotations.

The mathematical expression to determine this distance is the following:

Angle from start(in radians) = (distance of right wheel - distance of left wheel) / (distance between wheels)

This yields positive angles for CCW rotations, and negative angles for CW rotations.

Positional Odometry

GREATAPI also provides the capability to determine the absolute position of a robot in all relevant axises for a vex game(X, Y, and heading)

Prototype

  greatapi::odometry::odometry(TWheel* X, distance X_to_ctr, TWheel* Y, distance Y_to_ctr, odom_rotation* rotation)

Example

    //TWheel is an abstract class. You should be using the constructor of specific TWheels
    greatapi::TWheel* leftwheel = new greatapi::TWheel_Motor(5, pros::E_MOTOR_GEARSET_18,true, 2.75); //V5 motor, 200RPM, reversed, 2.75in wheel

    //For information on TWheel, please see the TWheel section of the site.
    greatapi::TWheel* rightwheel = new greatapi::TWheel_RotationSensor(4, false, 4); //V5 rotation sensor, not reversed, 4in wheel
    //note that we are making TWheel* (TWheel pointers), not TWheels.

    greatapi::TWheel* rearwheel = new greatapi::TWheel_ADIEncoder('A','B',false,2.75); //V4 rotation encoder, not reversed, 2.75in wheel

    greatapi::odometry::TWheel_odom_rotation example = *new greatapi::odometry::TWheel_odom_rotation(leftwheel,rightwheel,15); //15 inches between the wheels

    greatapi::odometry::odometry test(leftwheel, greatapi::inches(7.5), rearwheel, greatapi::inches(8), &example); 
  //left wheel is 7.5in from center of rotation, rear wheel is 8 inches away.

  greatapi::position location(greatapi::coord(0,0),SRAD(0)); //the position object storing the location of our robot

  //this loop is probably the update pattern you would want to use for your projects.
  while(true){ 
        location = test.calculateposition(location);
    cout << location.x << " " << location.y << " " << location.angle; //print X, Y and angle after each test
    pros::delay(10); //10ms wait before updating position.
    }

Configuration

Positional odometry requires a set of two tracking wheels, positioned 90 degrees away from each other, as well as any valid odom_rotation object. In addition, the distance of each tracking wheel from the center of rotation of the robot is required.

TBD: ADD IMAGE HERE TO EXPLAIN CONFIG

odom_rotation child classes provide a single function for the end user:

Function Purpose Output
calculateposition(position initial) Returns absolute position and heading of robot position object

TBD: EXPLAIN HOW IT WORKS

Control Loops

GREATAPI provides PID control loops to assist you in developing PID control systems for your robot.

Proportional, Integral, and Derivative controllers

Prototype

struct Proportional: public controlelement{
    double maxcap;
    double mincap;
    Proportional(double fac):controlelement(fac){
        mincap = __DBL_MIN__;
        maxcap = __DBL_MAX__;
    }

    Proportional(double fac, std::pair<double,double> caps):controlelement(fac){
        maxcap = std::get<0>(caps);
        mincap = std::get<1>(caps);
    }

    //standard offset format: target-current. This class assumes the offset is in the correct direction already
    double compute(double target, double current){
        double rawval = (target-current);
        double returnval = factor*rawval;
        return (returnval <= maxcap) ? ((returnval >= mincap) ? returnval : mincap) : maxcap;
    }
};

struct Integral: public controlelement{
    double last = 0;
    double maxcap;
    double mincap;

    //this is NOT recommended due to integral windup
    Integral(double fac):controlelement(fac){
        mincap = __DBL_MIN__;
        maxcap = __DBL_MAX__;
    }

    Integral(double fac, std::pair<double,double> caps):controlelement(fac){
        maxcap = std::get<0>(caps);
        mincap = std::get<1>(caps);
    }

    //standard offset format: target-current. This class assumes the offset is in the correct direction already
    double compute(double target, double current){
        if((int)target == (int)current) last = 0;
        double rawval = (target-current);
        last += rawval;
        double returnval = last*factor;
        return (returnval <= maxcap) ? ((returnval >= mincap) ? returnval : mincap) : maxcap;
    }
};

struct Derivative: public controlelement{
    double past = 0;
    double maxcap;
    double mincap;
    Derivative(double fac):controlelement(fac){
        mincap = __DBL_MIN__;
        maxcap = __DBL_MAX__;
    }

    Derivative(double fac, std::pair<double,double> caps):controlelement(fac){
        maxcap = std::get<0>(caps);
        mincap = std::get<1>(caps);
    }

    //standard offset format: target-current. This class assumes the offset is in the correct direction already
      double compute(double target, double current){
          double rawval = target-current;
          double returnval = factor * (rawval-past);
          past = rawval;
          return (returnval <= maxcap) ? ((returnval >= mincap) ? returnval : mincap) : maxcap;
      }
};

Example cpp placeholder

The controllers GREATAPI offers are separate P, I, and D controllers throught the controlelement class. Each of the three controllers have their own child class of controlelement.

Class Name
Proportional
Integral
Derivative

controlelement child classes provide one function to the user:

Function Parameters Purpose
compute(double target, double current) Target value and current value Returns the corresponding component of the control loop

Unified Control Loop

Prototype

//Modular control loop, computes values for a set of control elements
struct control_loop{
    std::vector<controlelement*> elementset;
    double maxcap;
    double mincap;
    //if you dont like caps just set them really high, like +-INT_MAX or something
    control_loop(std::vector<controlelement*> val, std::pair<double,double> caps):elementset(val){
        maxcap = std::get<0>(caps);
        mincap = std::get<1>(caps);
    }
    double update(double target, double current){
        double returnval = 0;
        //no enhanced for to minimize risk of sketchy copying issues
        for (int i = 0; i < elementset.size(); i++){
            returnval += elementset[i]->compute(target,current);
        }
        return (returnval <= maxcap) ? ((returnval >= mincap) ? returnval : mincap) : maxcap;
    }
};

Example

//create the new controlelements
greatapi::controlelement *PPos = new greatapi::Proportional(20.0, std::pair(__DBL_MAX__, __DBL_MIN__));
greatapi::controlelement *IPos = new greatapi::Integral(0, std::pair(__DBL_MAX__, __DBL_MIN__));
greatapi::controlelement *DPos = new greatapi::Derivative(0, std::pair(__DBL_MAX__, __DBL_MIN__));

//add the controlements to a new std::vector
std::vector<greatapi::controlelement *> PIDPosElements;
PIDPosElements.push_back(PPos);
PIDPosElements.push_back(IPos);
PIDPosElements.push_back(DPos);

//Create the control_loop unified control loop
greatapi::control_loop PIDPos(PIDPosElements, std::pair(INT32_MAX, INT32_MIN));

The Unified Control Loop computes the final outputs for a PID controller with any amount of P, I, and D controllerelements by summing their outputs together.

cpp