TeleOp help - RUN_TO_POSITION mode for linear slide

Hello!
We have now qualified for the Regionals and looking for some help to improve our TeleOp code.

Our current TeleOp is a giant set of ‘if’ conditions which we are segmenting into a RobotHardware class and a OpMode class.

We are looking for a functionality that would allow us to move the drivetrain while the linear slide is moving up/down.

Here’s the code snippet for the slide:

            if(gamepad2.dpad_up)
                robot.setArmPower(robot.ARM_UP_POWER);
            else if (gamepad2.dpad_down)
                robot.setArmPower(robot.ARM_DOWN_POWER);
            else 
                robot.setArmPower(0);
  /*          
            if (gamepad2.a) 
                //Slide presets to height = small junction
                robot.setArmPostion(robot.LOW_JUNCTION_A);

and in the RobotHardware class:

    public void setArmPower(double power) {
        //armMotor.setPower(power);
        Arm.setPower(power);
    }
    
    public void setArmPostion(int position) {
        Arm.setTargetPosition(position);
        Arm.setPower(LIFT_POWER);
        Arm.setMode(DcMotor.RunMode.RUN_TO_POSITION);
        while (Arm.isBusy()) {
            myOpMode.telemetry.addData("Current Position", Arm.getCurrentPosition());
            myOpMode.telemetry.addData("Target Position", Arm.getTargetPosition());
            myOpMode.telemetry.update();
        }

    }

The dpad_up and dpad_down works fine. However when we use gamepad2.a. (for example - which should go to Small junction using the RUN_TO_POSITION mode). Once we do that, all other controls on the gamepad2 stop working.

How do we troubleshoot this to see if there is a loop somewhere?

Thanks
Prasad

Congratulations on making it to Regionals!

Would recommend using a state machine for your slide if you want to move your drivetrain while you slide is going to a position. Otherwise, yes, other movements are blocked until the position is met. Another option is java threads. A Google search should get you enough information to learn the techniques.

Coach Breton
Bearbotics FTC Teams 19541, 19970 & 20771

This is likely too large a change, and I wouldn’t recommend it this late in the season, but I’m a big fan of iterative opmodes for just this reason. Properly designed, they allow you to appear to do multiple things at the same time without actually having to use threads. And while threads can often appear alluring, like a moth to a flame, they can burn you very easily.

See the command infrastructure here.

To solve your problem in the short term, if you encapsulate your drivetrain software, e.g. whatever is in a while loop while you are driving around the field, into a function and then iteratively call that function from setArmPosition() you can likely accomplish what you are looking for.

What you’re experiencing here is “it’s doing exactly what you told it to do.”

First, since we cannot see all of your code, we have to assume your OpMode is a LinearOpMode. That’s fine, there are significant benefits to using a LinearOpMode. However, you have to think iteratively.

I’m assuming that your first snippet is within the main loop of your program, like thus:

while (opModeIsActive()) {
   
   // A bunch of code here

   if(gamepad2.dpad_up)
      robot.setArmPower(robot.ARM_UP_POWER);
   else if(gamepad2.dpad_down)
      robot.setArmPower(robot.ARM_DOWN_POWER);
   else 
      robot.setArmPower(0);

   if( gamepad2.a) {
      //Slide presets to height = small junction
      robot.setArmPostion(robot.LOW_JUNCTION_A);
   }

   // Whatever code or function you use for controlling drivetrain motors
   ManageDrivetrainCode();

   // A bunch more code, whatever
}

This code loops while the program is running. Each time you hit your drivetrain code, the program can then issue new commands to the drivetrain. Remember that this loop must continue to run unimpeded in order for all operations of the robot to work correctly.

Okay, now let’s take a look at your setArmPower function:

    public void setArmPower(double power) {
        //armMotor.setPower(power);
        Arm.setPower(power);
    }

This is a good function. It simply sets the power, nothing more and nothing less. The motor will continue doing whatever it was last told to do until it is told to do something else - easy peasy lemon squeezy. But do you really understand the ramifications of this? Probably, but let me reiterate:

  • In your main loop, the power will be set each and every main loop iteration.
  • If your DPAD_UP button is CURRENTLY pressed, the motor is told to run robot.ARM_UP_POWER value, which let’s assume is a positive number. Only while that button is being held down will the motor keep being told to do that.
  • If your DPAD_UP button is NOT pressed, and if your DPAD_DOWN button is pressed, the motor is told to run the robot.ARM_DOWN_POWER value, which let’s assume that is a negative value. Only while that button is being held down will the motor keep being told to do that.
  • As soon as you release both buttons, no buttons are being pressed, and you’re setting the power to ZERO, which stops the motor.

This is all being controlled via the if statement. This is only possible because each iteration of the Main Loop the joystick buttons are being re-evaluated, and a new value (or the same value) is being set for the Arm Motor’s power.

So what does this mean for your drivetrain motors? Since your setArmPower() function just sets a value and exits, the Drivetrain code can execute and can be updated very quickly. This is why it seems as if the Drivetrain motors and the Arm motors are working in tandem. They’re NOT working in tandem, however, that’s just an illusion. The Arm Motors are being updated, and THEN the Drivetrain motors are being updated, each loop iteration. It’s just that we’re not waiting for the arm motors to do anything, and we’re not waiting for the drivetrain to do anything, so they’re just updated extremely quickly - quickly enough to make it seem like they’re happening in parallel but they’re actually being run consecutively (in series).

So now let’s look at setArmPosition:

    public void setArmPostion(int position) {
        Arm.setTargetPosition(position);
        Arm.setPower(LIFT_POWER);
        Arm.setMode(DcMotor.RunMode.RUN_TO_POSITION);
        while (Arm.isBusy()) {
            myOpMode.telemetry.addData("Current Position", Arm.getCurrentPosition());
            myOpMode.telemetry.addData("Target Position", Arm.getTargetPosition());
            myOpMode.telemetry.update();
        }
   }

First, you’ve got a logic error in this code. Your code should ALWAYS follow this sequence when performing a RUN_TO_POSITION command:

  1. Set the motor’s Target Position.
  2. Set the motor’s Mode to RUN_TO_POSITION
  3. Set the motor’s Power.

It may seem dumb, but this will save you from a lot of problems when commanding the firmware.

The what? Yes, the firmware. You see, when you use modes like RUN_TO_POSITION you aren’t actually issuing commands to the motors. Nope. You’re actually asking the firmware to command the motors on your behalf, and execute a pre-planned algorithm to make the motor rotate a certain amount using the encoders - THAT is why the previous sequence of commands is important, as the firmware expects to see this sequence. What you’ve done probably doesn’t crash only because you’re setting the motor’s mode earlier, and it just happens to not conflict with other modes the motors could be using.

So what does the firmware do? Well, it moves the motors around. You know that, because you’ve got a while loop in that code - you’re actually WAITING for the firmware to finish moving the motor to its position. The Arm.isBusy() command is asking the firmware if it’s done moving the arm, and the while loop patiently waits until it’s no longer moving. That means the setArmPosition function is sitting there WAITING for the arm motor to finish moving - while you’re sitting there, you’re not updating the Drivetrain code, so you’re not able to command the robot’s drivetrain (or anything else) while you’re waiting. That’s no fun at all.

So how do we “fix” your code? Well, that depends on your intent for the code. If the intent of the code is to execute the arm code WHILE A BUTTON IS BEING PRESSED, then let’s update the code as thus. This requires updating BOTH the Main Loop as well as your functions:

while (opModeIsActive()) {
   
   // A bunch of code here

   if(gamepad2.dpad_up) {
      robot.setArmPower(robot.ARM_UP_POWER);
   }
   else if(gamepad2.dpad_down) {
      robot.setArmPower(robot.ARM_DOWN_POWER);
   }
   else if(gamepad2.a) {
      /*
      Setting the Arm Position is being done within this if/else
      block because we only want ONE if block ever controlling
      the same motor resource
      */
      //Slide presets to height = small junction 
      robot.setArmPostion(robot.LOW_JUNCTION_A);
   }
   else {
      robot.setArmPower(0);
   }

   // Whatever code or function you use for controlling drivetrain motors
   ManageDrivetrainCode();

   // A bunch more code, whatever
}

Okay, and now we need to correct the setArmPower function:

    public void setArmPower(double power) {
        /* Set intended motor mode. Since the motor can be set to 
           multiple modes, we should be specific about what we're 
           wanting the motor to actually do. It's just good form.
           If you want to use RUN_WITH_ENCODER, that's fine too.
        */
        Arm.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER);
        Arm.setPower(power);
    }

Okay, and now let’s fix your remaining function:

   public void setArmPostion(int position) {
      /* Check to see if the arm is busy. If so, only update telemetry.
         This way, we don't interrupt the command while the firmware is
         still commanding the motor. */
      if( Arm.isBusy() )
      {
            myOpMode.telemetry.addData("Current Position", Arm.getCurrentPosition());
            myOpMode.telemetry.addData("Target Position", Arm.getTargetPosition());
            myOpMode.telemetry.update();
      }
      else
      {
         // Arm isn't busy, so we can issue a new command.
         Arm.setTargetPosition(position);
         Arm.setMode(DcMotor.RunMode.RUN_TO_POSITION);
         Arm.setPower(LIFT_POWER);
      }
   }

There are two caveats to using this new code:

  1. You have to hold down the buttons to command the arm. For instance, you already had to hold down the button in order to make the arm go Up and Down using the DPAD, if you didn’t then the motor was told to STOP. Now the same thing is true for the A button - if you hold down the button, then the motor is never commanded to stop, it will stop on its own when it reaches its location. If you release the A button, the motor is told to stop because of the else which commands the motor to set the power to zero. That’s a good thing for safety anyway, if you release your finger off the button it will stop the motor.
  2. This makes some assumptions about how you want to drive your robot. If those assumptions aren’t valid, you may want to change the code.

Notice what this code doesn’t do - it doesn’t wait in a while loop watching the firmware like a bump on a log while the firmware is doing its bidding. It trusts the firmware to do what it needs to do, and gives you the immediate ability to keep commanding the drivetrain which allows you to appear to keep doing multiple things at once. And that is a “Good Thing.”

Rock on,
-Danny

2 Likes

The other way of fixing this code is by preserving the previous functionality (I press the A button, and the motor is uninterruptible while it moves to position, and I don’t have to hold the button down). I don’t like this solution because it’s dangerous (it still has that “oh crap, the arm won’t stop moving but something is wrong, hurry stop the program!”) but it’s a good education tool to teach programming.

So your main loop can remain unchanged or changed to my version - I prefer my version as it’s more “correct”, but whatever. We already said I don’t like this solution but it preserves your previous behavior and is a good “programming example.”

So what we need to do is modify setArmPower to only set the power if the motor is not busy, like so:

    public void setArmPower(double power) {
        // If the motor is busy, don't allow this function to command the motor.
        // However, if the motor is busy, update telemetry.
        if( Arm.isBusy() )
        {
           // This function always gets called, so it makes sense for the telemetry
           // to be here whenever this function isn't commanding the arm.
           myOpMode.telemetry.addData("Current Position", Arm.getCurrentPosition());
           myOpMode.telemetry.addData("Target Position", Arm.getTargetPosition());
           myOpMode.telemetry.update();
        }
        else
        {
           // Only command the arm if the firmware isn't currently using it.
           Arm.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER);
           Arm.setPower(power);
        }
    }

By doing this, we effectively negate calls to setArmPower if the position code is still moving the arm. In this way, the DPAD button checking code won’t interfere with the firmware-controlled arm position movements. Again, it’s best if a motor resource can only be commanded by one source, and here I at least coded a hierarchy (position code has control, then power code has control) so that there’s minimal conflict.

And we also need to make sure the setArmPosition function is set correctly, which is similar’ish to my previous example:

   public void setArmPostion(int position) {
      /* Check to see if the arm is busy. If so, don't interfere with its work.
         This way, we don't interrupt the command while the firmware is
         still commanding the motor. */
      if( !Arm.isBusy() )
      {
         // Arm isn't busy, so we can issue a new command.
         Arm.setTargetPosition(position);
         Arm.setMode(DcMotor.RunMode.RUN_TO_POSITION);
         Arm.setPower(LIFT_POWER);
      }
   }

The big difference is that I removed the telemetry code, because this function isn’t going to be “spammed” any more - the setArmPower function is going to be called a lot more, so it makes sense for the telemetry update to be there.

OR, if you’re feeling especially fun, we can allow multiple position commands to override each other by doing the following to the setArmPosition code (so that if you press a button by accident, and really wanted a different position, you can press the new position and it’ll go after THAT new position without fully going to the first position:

   public void setArmPostion(int position) {
         Arm.setTargetPosition(position);
         Arm.setMode(DcMotor.RunMode.RUN_TO_POSITION);
         Arm.setPower(LIFT_POWER);
   }

See that? We just removed any checks and tell the arm to go to the position. Again, this removes both caveats from the code, improves on the original behavior, but I still like it much less. C’est la vie.

-Danny

Thank you! We will definitely explore the state machine soon!

Thanks @ddiaz Read your reply a few times just for the sheer knowledge involved. We were aware that the code was ‘working as designed’ :slight_smile: However thank you for explaning the nuances - specially setting the motor mode and the order of setting the motor power. We tried all the 3 options that you proposed and they all work. With the regionals this weekend (and we just had 5 days to prepare as League was Sunday), it will come down to driver choice - (they will most likely pick Option 2 ).

Thanks again for all your help.

2 Likes

Your response aligns with what I thought. We experienced the same thing. (Rookie team, BTW.)

Does this mean that RUN_TO_POSITION cannot be used? The reason I ask is because I have a motor driving a heavy arm, and I want to hold it at a particular position. I have the BRAKE setting on, but it’s not working. The internet tells me to use RUN_TO_POSITION, but it will hold my code (as was the original issue).

Bottom line: how do I hold an arm steady, which still commanding other motors?

RTP mode will not “hold” (block) your code unless you make a loop to wait for isBusy() to return false. Once a motor is properly activated in RTP mode, the closed loop control is offloaded to the firmware inside the REV Hub and your code is free to do other tasks without disrupting the motion control.

1 Like

@Windwoes is completely correct (as usual). “The internet” is a cruel place with code examples that rarely resemble “good ideas.” If you have a code snippet you don’t understand, post it here on the forum and we can assist.

-Danny