nerdherd / InfiniteRecharge2020

Code for 687's 2020 Robot, Thomas

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

2020 Code Review Party - Team 4183 Feedback

cpostbitbuckets opened this issue · comments

2020 Code Review Party - Team 4183 Feedback

Hi Team 687, thank you for sharing your code with our team. Three of our software students and our software mentor all cloned your codebase, reviewed the code, and discussed it as a team to come up with this feedback. We approached the codebase as if we were new students on your team trying to understand how the code works.

Overall Thoughts

The codebase is quite large and complex. There is a lot of great code and it is very well structured. Just by reading class names and packages we could get a sense of what the robot is supposed to do. We had a bit of a tough time determining specifics of what a subsystem did because of the level of abstraction in NerdyLib. There was also a lot of unused and uncommented code in NerdyLib which made it hard to figure out what we should focus on.

Things we Liked

  • Command based everything - We have only used command base programming for part of our robot. It was neat to see an entire codebase where everything had a command.
  • Logging - We liked the extensive use of logging and logging with lambdas. It was great to see a standard for logging being used throughout the project.
  • ParallelRaceGroup in Auto - Using a parallel race group for stopping the intake motor after the trajectory is complete is a very elegant solution.
  • State Space - We have been working towards adding state space control to our robot, so it was great to see another team with code for it.
  • Auto Trajectory Classes - Having different classes for each auto trajectory made it very easy to understand what each trajectory was supposed to do.
  • Ether Joystick Sensitivity - The Ether joystick sensitivty code was very neat!

Things to Improve

NerdyLib Abstraction

  • As a new reader, NerdyLib contained many layers of abstraction that made it very hard to follow. The Hood was a subsystem with a single motor, but the chain of inheritance looked like this:

Hood -> SingleMotorArm -> GravityAffectedMechanism -> StaticFrictionMechanism -> SingleMotorMechanism -> SingleMotorMechanism -> SmartMotorControllerSubsystem -> AbstractSingleMotor -> SusbsystemBase

Each of these classes defined a small piece of functionality and it was difficult to figure out exactly what the Hood class did. We weren't even sure it was a WPI Subsystem until we followed the code 7 classes down the chain. For new students or for students coming from different teams, this abstraction would make it difficult to understand the "whole picture" of what a class does.

If the Hood is managing a single motor, it would be clearer to configure and operate the motor within the Hood class, even if it means some duplicate code in other single motor classes. If you want to reduce boilerplate, perhaps you could use utility functions to configure the motor with the arguments you need for each type of functionality?

For example, these super.* methods in the Hood class could be part of a MotorConfigurer() class, instead of part of the Hood inheritance:

  public Hood() {
    super(RobotMap.kHoodID, "Hood", false, false);

    MotorConfigurer motorConfigurer = new MotorConfigurer(this.motor);
    motorConfigurer.configAngleConversion(HoodConstants.kHoodAngleRatio, HoodConstants.kHoodAngleOffset);
    motorConfigurer.configTrapezoidalConstraints(new TrapezoidProfile.Constraints(HoodConstants.kHoodVel, HoodConstants.kHoodAccel));
    motorConfigurer.configPIDF(HoodConstants.kHoodP, 0, 0, HoodConstants.kHoodF);
    motorConfigurer.configFFs(HoodConstants.kHoodGravityFF, HoodConstants.kHoodStaticFriction);
    motorConfigurer.configOblargConstants(HoodConstants.kHoodS, HoodConstants.kHoodCos, HoodConstants.kHoodV, HoodConstants.kHoodA);
    motorConfigurer.configMotionMagic(HoodConstants.kMotionMagicAcceleration, HoodConstants.kMotionMagicVelocity);
    motorConfigurer.configDeadband(0.0004);
    motorConfigurer.setCoastMode();
  }

This way, rather than looking through a bunch of classes to figure out how this motor is configured, you can look in one place.

Other NerdyLib Suggestions

  • A README with a diagram showing the various classes and what they are for would be a great step towards helping the code be more understandable.
  • A comment above each subsystem to describe how it worked would be helpful

Magic Numbers

  • The Hood class uses magic numbers instead of constants to change behavior based on distance
  • OJ.java should use joystick constants instead of numbers. It's tough to tell what buttons map up to which commands.

distance vs distanceWidth

  • We had to ask what distanceWidth was. distance is not used at all. It would be clearer to remove the distance variable and rename distanceWidth to distance.

Odometry

  • WPI has a built in characterization and odometry library. WPILib uses some fancy method with pose exponentials that's more accurate. See Controls Engineering in the FIRST Robotics Competition by Tyler Veness. WPILib uses a higher order approximation. Your current calcXY() is an Euler approximation so it's first order which means it'll be less accurate compared to WPILib's unless it's run at a very fast rate.

Shooter Distance Approximation

  • For the shooter distance to speed approximation, a spline is probably more accurate. You certainly don't need it, but given how mature your codebase is, it might be a fun project.

Miscellaneous

  • OI.java has large swaths of code that is commented out. Is this code necessary? Can it be removed? There is no comment saying "This code will go back in when debugging" or anything like that.
  • OI.java could use commented sections to better separate functionality. For example:
//
// Driver Controls
//
shiftHigh_6L = new JoystickButton(super.driveJoyLeft, 6);
shiftLow_6R = new JoystickButton(super.driveJoyRight, 6);
turnToAngle_1L = new JoystickButton(super.driveJoyLeft, 1);
turnToAngle_1R = new JoystickButton(super.driveJoyRight, 1);

//
// Intake Controls
//
ploughIntake_2 = new JoystickButton(super.driveJoyLeft, 2); // Check in with Drivers
intake_1 = new JoystickButton(super.operatorJoy, 1);

//
// Climb Controls
//
climbReady_3L = new JoystickButton(super.driveJoyLeft, 3);
climbLift_4L = new JoystickButton(super.driveJoyLeft, 4);

This way you can easily find the functionality you are looking for.

  • There is a lot of unused code in the robot that should probably be moved to NerdyLib rather than left in the main robot. It's a large code base and only made more complicated by sifting through unused classes.
  • Voltage Compensation should be less than 12V. The battery will drop when it is being used, so it's better to set your voltage compensation to a realistic value.

State Space Feedback

  • In SSTalonSRXPos, when there's no observer enabled, you are setting the state of the motor as:
this.xHat.set(0, 0, this.getPosition() / this.gains.C.get(0, 0));
this.xHat.set(1, 0, this.getVelocity() / this.gains.C.get(1, 1));

In the case of most FRC applications, C happens to be diagonal and D happens to be a zero matrix so this works, but you should also account for when it doesn't (example: acceleration reported by an IMU is a linear combination of velocity and control, so D is not 0. That's because of the continuous-time representation of a system, a = v' is a linear combination of v and u). So just more generally, assuming C is invertible (which it almost surely will be), x_hat = inv(C) * (y - D*u) is what you should go for.

Along those lines, when updating your observer, you assume D = 0, which again may not be the case. It usually is, but it's best to be prepared and not have to make changes to your state-space library during build season! That being said, there are people working on submitting a pull request to WPILib with a state-space library, so we both have that to look forward to!

  • It was hard to tell whether you're using plant augmentation or just have unused variables for it, and how the plant was being augmented. In general, commenting state-space code is a very good idea since regular code is confusing enough as is, without having linear algebra and control theory concepts.

  • In JamaUtils, it seems like you're using a Taylor expansion to approximate the matrix exponential, along with some fancy scaling in order to make it more accurate. As you said, a Padé approximation is much better for this kind of thing. In general, it's best to not write your own matrix utilities unless it's simple stuff like your identity, diagonals,...
    Luckily, WPILib already has a SimpleMatrixUtils class with an expm function, but it's written with the EJML matrix library instead of Jama. Maybe you could convert your Jama matrix into an EJML SimpleMatrix, run expm(), then convert back? We had to do something similar to take the logarithm of a SimpleMatrix, which WPILib does not have but another Java matrix library - Jeigen, which is an interface for the C library Eigen - does have. So there are ways to do it! But you said you're doing all the pre-processing in Python anyways so it doesn't really matter.

Summary

Thank you for giving us the opportunity to see your code. It was very informative to see how your team coded a robot. We learned a lot from it and we hope our feedback is helpful to you in your future robot coding. Good luck in the rest of the season!

Thanks for taking the time to look through all of our stuff so in depth! Documenting our codebase has always been a struggle on our team. A few more things to note however, are that we switched to using WPILib's odometry this year, but we still have the Euler approximation in the codebase as legacy code. Our miscellaneous state space stuff was all done by one our senior's last year in his spare time, and hasn't been worked on since. We've mostly dropped all work on it in favor of simpler, more sustainable controls that are easier to teach to our students, most of who don't have the math background required for state space control. If I recall correctly, 1678 switched to TalonSRX Motion Magic after a year or two of state space for a similar reason. We've been thinking about at a pretty major rewrite to our mechanism classes in order to support WPILib's characterization and rio-side control loops, so hopefully that'll get cleaned up. Once again, thanks a bunch for looking through everything so in depth, we'll definitely make use of your feedback.