Okapi Scales setup help

I am trying to create a chassis using the ChassisControllerBuilder() and whatever I try for scales does not produce accurate results.

For example chassis->turnAngle(180_deg) produces an almost 90 degree turn.

I believe the error is in the way that I set up ChassisScales Object

Code:

MotorGroup leftChassi({frontL.get_port(), rearL.get_port()});
MotorGroup rightChassi({-frontR.get_port(), -rearR.get_port()});
ChassisScales scale({2.783_in, 12.20_in, 3_in, 2.783_in}, quadEncoderTPR);

chassis =
  ChassisControllerBuilder()
    .withMotors(leftChassi, rightChassi)
    .withSensors( okapi::ADIEncoder {'A', 'B'},  okapi::ADIEncoder {'C', 'D', true}, okapi::ADIEncoder {'E', 'F'})//(trackL, trackR, trackB)
    .withDimensions(AbstractMotor::gearset::green, scale)
    .withLogger(
      std::make_shared<Logger>(
          TimeUtilFactory::createDefault().getTimer(),
          "/ser/sout",
          Logger::LogLevel::debug
      )
    )
    .withOdometry(StateMode::CARTESIAN) // use the same scales as the chassis (above)
    .buildOdometry(); // build an odometry chassis

Picture of the bot:

Blue Horizontal represents the “wheel track” = 12.20 in
Blue Vertical represents the “the length from the center of rotation to the middle wheel” = 3 in
All tracking wheels are 2.783in in diameter
Hence: ChassisScales scale({2.783_in, 12.20_in, 3_in, 2.783_in}, quadEncoderTPR);
Okapi ChassisScales Documentation Here

I’ve spent countless hours trying to figure out what’s wrong.
Thanks.

My best advice is to try basing the constructor off this example from the Okapilib documentation. It does not require a chassis scales object. Then test it from there, adding the ChassisScales objects, but use different scale for the encoders and the chassis. My guess is that since the TPR and wheel track is kind of different it is messing up or that the constructor you are using somehow is using the motor encoders for odometry but honestly that does not seem to be supported by the documentation. Have you tested your odometry values in driver control to see how accurate they are? That might shed more light on the problem.

ChassisControllerBuilder()
.withMotors(1, -2) // left motor is 1, right motor is 2 (reversed)
// green gearset, 4 inch wheel diameter, 11.5 inch wheel track
.withDimensions(AbstractMotor::gearset::green, {{4_in, 11.5_in}, imev5GreenTPR})
.withSensors(
    ADIEncoder{'A', 'B'}, // left encoder in ADI ports A & B
    ADIEncoder{'C', 'D', true},  // right encoder in ADI ports C & D (reversed)
    ADIEncoder{'E', 'F'}  // middle encoder in ADI ports E & F
)
// specify the tracking wheels diameter (2.75 in), track (7 in), and TPR (360)
// specify the middle encoder distance (1 in) and diameter (2.75 in)
.withOdometry({{2.75_in, 7_in, 1_in, 2.75_in}, quadEncoderTPR})
.buildOdometry();
1 Like

I was thinking the same thing and I should have realized that I could add different scales for odometry and dimensions. I’m going to try putting the tracking wheels in the withOdometry and the actual wheels in withDimensions and post the results.

So I have an interface that uses odometry to show my location on the field. And even though it is not 100% accurate, it does a way better job than the Okapi::odometry. Here is the link to the project I found that gives life feedback and it works very well.

If the okapi odometry is not working well, then the ChassisController is definitely initialized incorrectly. I really need to use that debugging software, it looks really good. Did you use it to test your custom odometry?

From reading the source code, it seems like okapi’s OdomChassisController works like this: It gets the current global position once before the control loop begins, and then feeds the value to a ChassisController, which controls the chassis based on your chassis’ specifications. Therefore, you will need two ChassisScales in your instantiation: one for odometry calculations, one for chassis control. Something like this below should work

auto chassis = ChassisControllerBuilder()
	.withMotors({1, 2, 3}, {4, 5, 6}) // left motor, right motor, negative if reversed
	.withDimensions(AbstractMotor::gearset::blue, {{3.25_in, 12_in}, imev5BlueTPR}), //gearset, wheel size, track diameter, motor tpr
	.withSensors(ADIEncoder{'A', 'B'}, ADIEncoder{'C', 'D'}, ADIEncoder{'E', 'F'}) // left encoder, right encoder, middle encoder
	.withOdometry({{2.75_in, 8_in, 4_in, 2.75_in}, quadEncoderTPR}, StateMode::CARTESIAN) // tracking wheel size, odom track diam, dist to middle wheel, middle tracking wheel diam, state mode
    .withLogger(std::make_shared<Logger>(TimeUtilFactory::createDefault().getTimer(), "/ser/sout", Logger::LogLevel::debug)
    .buildOdometry();
1 Like

So that’s what I was thinking at first and I made two scales, one for chassis and one for encoders. It didn’t work. But through the process, I concluded that if withGains is not included, it uses the actual motor encoders, however, if withGains is included, it uses the actual tracking wheels.

However, don’t quote me on this, I will test if that’s the case and get back to you.

I will post my code, but I think I figured out the problem in my comment above. I will test it out and get back to you guys. I believe that whoever made Okapi could make a better ChassisScale class because this does not make much sense.

Also, is there anyway to access the okapi source code?
Thanks again.

So here is the debug with & without gains (everything else is constant).

With Gains:

Without Gains:

As you can see, the tick value is the same, however since it uses a different type of controller, it takes values from the motor encoder if gains aren’t present.

For ChassisControllerIntegrated, the odometry value is taken only once. For example, if my current global position is -30 degrees, and I want to turn to 90 degrees, it will simply tell the robot to turn 120 degrees using internal control with no further odometry feedback.

When the gains are supplied, a ChassisControllerPID is built instead. Like the previous case, if my current position is -30 degrees, and I want to turn to 90 degrees, It will set the PID target to 120 degrees, and use motor encoder as feedback to the loop.

As you can see, Okapi’s Odometry control doesn’t actually use Odometry to its fullest extent. It is perfectly possible for you to run Odometry constantly as feedback, instead of only being taken once. If you would like to do that, you would have to implement your own functions (or create your own odom controller) if you want to achieve that.

You can understand how the ChassisController / Chassis Model / Odometry are able to have multiple variations by learning about polymorphism. It is actually very interesting how okapi implemented the builder.

3 Likes

Thank you so much for your help, I figured it out and will try to manage for the next comp.