Asynchronous action help

So I have created a motion algorithm for an odometry system. I have been trying to make the robot perform a function while performing the motion algorithm. To do this, I created two classes:

  class Varcond{
  private:
  double *cond1, *cond2;
  std::string compar;
  int null = 0;
  public:
  Varcond(double &a, double &b, std::string c): cond1(&a), cond2(&b), compar(c){}; 
  Varcond(){null = 1;};

  bool result(){
    if(compar == "<"){
      return *cond1 < *cond2;
    }
    else if(compar == ">"){
      return *cond1 > *cond2;
    }
    else if(compar == "<="){
      return *cond1 <= *cond2;
    }
    else if(compar == ">="){
      return *cond1 >= *cond2;
    }
    else{
      return *cond1 == *cond2;
    }
  }
  bool isnull(){
    return this->null == 1;
  }
};
class exec_func{
  private:
  Varcond condition;
  //initially assigns both pointers as null so I can execute correct function
  void (*func)() = NULL;
  void (*intfunc)(double) = NULL;
  double rev;
  public:
  //constructors
  exec_func(Varcond a, void (*b)()): condition(a), func(*b){};
  exec_func(Varcond a, void (*b)(double), double c):condition(a), intfunc(*b), rev(c){};
  exec_func(){};
  
  void execute(){
    intfunc != NULL ? intfunc(rev): func();
  }
  bool istrue(){
    if(!condition.isnull()){
      return condition.result();
    }
    else{
      return false;
    }
  }
};

The first class creates a custom conditional that can compare the values of two variables. The second basically stores the reference to a function and the condition that it should start at( Run Intakes, disttopoint < target). I tried implementing them in my algorithm like this:

   void Turntopoint(parameters){
  //do stuff
  while(1){
    //checks for condition and executes function if neccesary
    if(execs.istrue()){
              execs.execute();
    //Does Stuff
    Motors.spin(spd);
    task::sleep(10);
    
    
  }
}

The robot completes the motion algorithm and then executes the function. This is true even when the distance is obviously less than the target. I tested the classes on a c++ compiler and they worked. I put them in a while loop and the function executed when needed. However, is there any reason why the function is executing at the end instead of in the middle of the function? Does it have something to do with the spin command?

Edit: can I do something with events? The documentation seems to indicate that you can only callback functions that are void with no parameters. Is there any way to use events to callback a function with a double parameter?

There’s not really enough code there to see what you are doing, I assume that you just have one function blocking another, where do all the execs.istrue() etc, calls actually get made in you code, what do the execute functions do ?

2 Likes

I made the functions outside of my turn function. Here are the functions I want to call back:

void Deposit () {
    
    Brain.Screen.clearScreen();
    Brain.Screen.newLine();
    
    Brain.Screen.print("Initializing...");
    Brain.Screen.newLine();
    task::sleep(100);
    
    Brain.Screen.print("Pushing...");
    Brain.Screen.newLine();
    
    
    
    StackRelease.rotateTo(1235, rotationUnits::deg, 50, velocityUnits::pct, true);
    StackRelease.rotateTo(1730, rotationUnits::deg, 20, velocityUnits::pct);
    task::sleep(1000);   

}


void LiftToLevel1 () {
    Brain.Screen.newLine();
    
    Brain.Screen.print("Initializing...");
    Brain.Screen.newLine();
    task::sleep(100);
   
    Brain.Screen.print("Pushing...");
    Brain.Screen.newLine();
    StackRelease.rotateTo(960, rotationUnits::deg, 70, velocityUnits::pct, false);
    task::sleep(500);
    
    Brain.Screen.print("Lifting...");
    Brain.Screen.newLine();
    BarMotor.rotateTo(650 - Liftpos, rotationUnits::deg, 100, velocityUnits::pct, true);

    Brain.Screen.print("Finalizing...");
    Brain.Screen.newLine();
}

void LiftToLevel2 () {
    Brain.Screen.newLine();
    
    Brain.Screen.print("Initializing...");
    Brain.Screen.newLine();
    task::sleep(100);
   
    Brain.Screen.print("Pushing...");
    Brain.Screen.newLine();
    StackRelease.rotateTo(960, rotationUnits::deg, 70, velocityUnits::pct, false);
    task::sleep(500);
    
    Brain.Screen.print("Lifting...");
    Brain.Screen.newLine();
    BarMotor.rotateTo(825 - Liftpos, rotationUnits::deg, 100, velocityUnits::pct, true);

    Brain.Screen.print("Finalizing...");
    Brain.Screen.newLine();
}

void LiftToLevel3 () {
    Brain.Screen.newLine();
    
    Brain.Screen.print("Initializing...");
    Brain.Screen.newLine();
    task::sleep(100);
   
    Brain.Screen.print("Pushing...");
    Brain.Screen.newLine();
    StackRelease.rotateTo(350, rotationUnits::deg, 30, velocityUnits::pct, false);
    task::sleep(500);
    
    Brain.Screen.print("Lifting...");
    Brain.Screen.newLine();
    BarMotor.rotateTo(975 - Liftpos, rotationUnits::deg, 100, velocityUnits::pct, true);

    Brain.Screen.print("Finalizing...");
    Brain.Screen.newLine();
}
void CollectPosition () {
    Brain.Screen.newLine();
    
    Brain.Screen.print("Initializing...");
    Brain.Screen.newLine();
    task::sleep(100);

    Brain.Screen.print("Lifting...");
    Brain.Screen.newLine();

    if(BarMotor.position(rotationUnits::deg) > 320){

      BarMotor.rotateTo(320, rotationUnits::deg, 100, velocityUnits::pct,true);
      StackRelease.rotateTo(700, rotationUnits::deg, 100, velocityUnits::pct,true);

    }

    BarMotor.rotateTo(120, rotationUnits::deg, 100, velocityUnits::pct, true);
    Brain.Screen.print("Retracting...");
    Brain.Screen.newLine();
    StackRelease.rotateTo(120, rotationUnits::deg,100, velocityUnits::pct, true);

    Brain.Screen.print("Finalizing...");
    Brain.Screen.newLine();
 }
void SuckIn (double revelutions) {

  LeftIntakeMotor.rotateFor(directionType::rev, revelutions, rotationUnits::rev, 100, velocityUnits::pct, false);
  RightIntakeMotor.rotateFor(directionType::fwd, revelutions, rotationUnits::rev, 100, velocityUnits::pct, false);

}

void SpitOut (double revelutions) {

  LeftIntakeMotor.rotateFor(directionType::fwd, revelutions, rotationUnits::rev, 100, velocityUnits::pct, false);
  RightIntakeMotor.rotateFor(directionType::rev, revelutions, rotationUnits::rev, 100, velocityUnits::pct, false);

}

I have been specifically trying to call back the Suck In function in the middle of the program (when the robot is twenty inches away from the ideal point).
This is the function I use in int main (execs and the other variables were declared at the beginning of my program)

void Auton(){
  autonfinished = 2.0;
  CollectPosition();
  idealpos = vector(0, 40, "Rect");
  Turntopoint(0, 4, 0.001, 60);
  

  idealdir = getangpoints(pos,  vector(25, 25, "Rect"));
  idealpos = vector(0, 80, "Rect");
  target = 20;
  execs = exec_func(Varcond(Dist, target, "<"), SuckIn, 2);
  
  Turntopoint(0, 4, 0.001, 60);
  

  
  autonfinished = 3.0;
}

Dist is a global variable that records the distance to the ideal point

I call execs.execute in the turntopoint code that is then executed in the Auton code.
here is my main function:

int main() {
  // Initializing Robot Configuration. DO NOT REMOVE!
  vexcodeInit();
  //mode control/screen switching
  task Print(controllerprint);
 //Positioning system task
  task aps(APS);
  
  Auton();
}

Edit: If you want me to DM you my whole code, I am fine with that.

1 Like

Do you expect, for example, the function Deposit to run at the same time as other code ? Wherever that function gets called from, either directly or from some class wrapper like you have, it will block the calling thread while it is running, in this case for a little over 1 second. If you want that function to run while other code continues, if so it needs to run in its own thread.

Using our event system may be the answer, you can create a user event that will pass back a void *arg when triggered (I need to double check on that, it’s generally used for device triggered events). You could pass the class instance into the event if needed. create the event and then use broadcast to trigger it, it runs in its own thread. look in vex_event.h for the event constructors.

I’ll try and write an example for you later.

3 Likes

Thanks! I do want the function to run at the same time as other code, but at a custom trigger. I tried using events but probably did it wrong. I will wait for the example.

Some simple examples.
(comment out the Brain instance in main.cpp if using RobotConfig.cpp)

First is most basic use of a user event.
create the event with vex::event myEvent( callback );
trigger the event using myEvent.broadcast();

example_1
/*----------------------------------------------------------------------------*/
/*                                                                            */
/*    Module:       main.cpp                                                  */
/*    Author:       james                                                     */
/*    Created:      Sat Apr 18 2020                                           */
/*    Description:  V5 project                                                */
/*                                                                            */
/*----------------------------------------------------------------------------*/
#include "vex.h"

using namespace vex;

// A global instance of vex::brain used for printing to the V5 brain screen
vex::brain       Brain;

void
callback() {
    Brain.Screen.print("event called\n");
    Brain.Screen.newLine();
}

int main() {
    // create a user event
    vex::event myEvent( callback );

    while(1) {
        // trigger the event
        myEvent.broadcast();
        // Allow other tasks to run
        this_thread::sleep_for(1000);
    }
}

Next is a bit more complex, we create an event that can take a single void * argument. To pass multiple parameters, collect them together in a structure and pass a ptr to that.

example_2
/*----------------------------------------------------------------------------*/
/*                                                                            */
/*    Module:       main.cpp                                                  */
/*    Author:       james                                                     */
/*    Created:      Sat Apr 18 2020                                           */
/*    Description:  V5 project                                                */
/*                                                                            */
/*----------------------------------------------------------------------------*/
#include "vex.h"

using namespace vex;

// A global instance of vex::brain used for printing to the V5 brain screen
vex::brain       Brain;

typedef struct {
  int   somevalue;
  double some_double;
} event_data;

void
callback_with_arg( void *arg ) {
    if( arg == NULL )
      return; // some error, no data
  
    event_data *pData = (event_data *)arg;

    Brain.Screen.print("event called %d, %.4f\n", pData->somevalue, pData->some_double );
    Brain.Screen.newLine();
}


int main() {
   // data passed to the event, make sure this function
   // does not go out of scope or make data static
   event_data myData = { 4, 3.1415 };
   
    // create a user event
   vex::event myEvent( callback_with_arg, &myData );

    while(1) {
      // trigger the event
      myEvent.broadcast();
      // Allow other tasks to run
      this_thread::sleep_for(1000);
    }
}

Now something that uses your Varcond and exec_func classes.
Instead of saving the callbacks using function pointers, create an event using the callback and then trigger it instead of calling the function. Also, optionally, put all the event monitoring ( checking for isTrue() ) in another thread so it can always run. You probably want to add some code so events don’t continuously trigger, or perhaps your use case is different, you can figure that out on your own.

example_3
/*----------------------------------------------------------------------------*/
/*                                                                            */
/*    Module:       main.cpp                                                  */
/*    Author:       james                                                     */
/*    Created:      Sat Apr 18 2020                                           */
/*    Description:  V5 project                                                */
/*                                                                            */
/*----------------------------------------------------------------------------*/
#include "vex.h"

using namespace vex;

// A global instance of vex::brain used for printing to the V5 brain screen
vex::brain       Brain;

class Varcond {
private:
  double *cond1, *cond2;
  std::string compar;
  int null = 0;
public:
  Varcond(double &a, double &b, std::string c): cond1(&a), cond2(&b), compar(c){};
  Varcond(){null = 1;};
  
  bool result(){
    if(compar == "<"){
      return *cond1 < *cond2;
    }
    else if(compar == ">"){
      return *cond1 > *cond2;
    }
    else if(compar == "<="){
      return *cond1 <= *cond2;
    }
    else if(compar == ">="){
      return *cond1 >= *cond2;
    }
    else{
      return *cond1 == *cond2;
    }
  }
  bool isnull(){
    return this->null == 1;
  }
};

class exec_func {
  private:
    Varcond condition;
    vex::event theEvent;
    double rev;
  public:
    //constructors
    exec_func(Varcond a, void (*b)()): condition(a) {
      theEvent = vex::event( b );
    };
    exec_func(Varcond a, void (*b)(void *), double c):condition(a), rev(c) {
      theEvent = vex::event( b, &rev );
    };
    exec_func(){};
  
    void execute(){
      // multiple triggers or just one ?
      // perhaps add another flag to decide.
      theEvent.broadcast();
    }
    bool istrue(){
      if(!condition.isnull()){
        return condition.result();
      }
      else{
        return false;
      }
    }
};

void SuckIn (void *arg) {
    if( arg == NULL )
      return;
    
    double revolutions = *(double *)arg;

    Brain.Screen.print("SuckIn %.2f", revolutions );
    Brain.Screen.newLine();
}

exec_func execs;

int eventMonitorTask() {
    while(1) {
      if( execs.istrue() )
        execs.execute();

      this_thread::sleep_for(20);
    }
}

int main() {
    double  Dist = 200;
    double  target = 100;
    int     count = 0;

    execs = exec_func( Varcond(Dist, target, "<"), SuckIn, 2.34  );
   
    // thread to monitor for events to trigger
    vex::thread et( eventMonitorTask );

    while(1) {
      // you could check for events here
      //if( execs.istrue() )
      //  execs.execute();
      
      Dist -= 10;
      Brain.Screen.printAt( 240, 20, "Count %d, Dist %.1f", count++, Dist );
      // Allow other tasks to run
      this_thread::sleep_for(200);
    }
}

you could get clever and pass the instance of the exec_func to the event, that would change some of the code like this.

in the constructor

code
    exec_func(Varcond a, void (*b)(void *), double c):condition(a), rev(c) {
      theEvent = vex::event( b, static_cast<void *>(this) );
    };
    exec_func(){};
    double get_value() {
      return rev;
    }

and in the callback

code
void SuckIn (void *arg) {
    if( arg == NULL )
      return;
    
    exec_func *instance = static_cast<exec_func *>(arg);

    double revolutions = instance->get_value();

    Brain.Screen.print("SuckIn %.2f", revolutions );
    Brain.Screen.newLine();
}

Have fun.

7 Likes

Thanks for the reply. It worked for all the void functions without arguments. However, the SuckIn function starts at the right time, but the intakes spin very slowly in a jerky motion. When I stop the program, the intakes spin at the right speed for a little bit and then stop. Is this a coding problem? I used the third example.

You probably have some other code also trying to control the motor.

or are constantly sending the same “goto” position to the motor causing the PID loop in the motor to start over. rotateTo should only be sent once.

3 Likes

That solved the problem. Thanks for your time and effort!

1 Like