Steps to Make Your Robot Code More Reliable

FRC Best Practices Feb 01, 2020

There is no greater feeling than having reliable robot code. Even if you are careful while writing code, it can be very easy to make mistakes with your robot code that could have catastrophic effects. During my last mini-project, developing a WPILib Trajectory-based program for a robot with NEOs, I encountered several issues that could've easily been solved if I had taken these steps earlier.


Why Do I Care About Reliable Code?

Writing code takes time. It can be very easy to just do the bare minimum to the end result, but planning for the future takes a lot more personal initiative. There are times that I have decided to cut corners to make code faster, but I almost always regret the decisions later.

Robots are complex machines, since mechanics, electronics, and software all have to work together. The best thing that I did last year was to write reliable code, and then make sure that the rest of the team knew that our code was reliable. Why does this matter? Well, we had a lot of problems with our robot:

This is not supposed to happen

By having reliable code, the rest of the team had confidence that if the Software sub-team said "it isn't a software problem," we would be telling the truth. It can be incredibly easy to blame Software for any problems, because Software has to account for the bad decisions or unintended consequences made from the rest of the team. If we had not proven on our practice bot that we had reliable code, the responsibility for the image above as well as a disaster of a San Francisco Regional was not blamed on the Software sub-team, and if we had unreliable code, our second regional would've been just as bad.

Even though FRC is a team sport, when problems arise, anger and frustration will be directed to someone. By having reliable code, I made sure that anger wasn't directed towards me.

The more important benefit is that with reliable software, you can make sure that you score as many points as possible with as few problems as possible. Even though last year the problems originated from another sub-team, the Software sub-team is just as capable as any other sub-team to make mistakes, and you don't want a great robot to be let down by software. If you spend the time to make reliable software now, you may see much better improvements in matches.

The last major benefit is that it is a huge competitive advantage. Many robots at FRC competitions are not very reliable, so if you stand out as one of the more reliable ones, you may receive more points or be more likely to be picked. A mediocre but reliable robot can fairly easily beat out an advanced but very unreliable robot. Many teams that scout look for reliability, so offering a very reliable robot can be a huge advantage for your team when teams are picking.


Plan Your Code

If you want to have reliable code, you should plan it before. There are several ways to do this. If you are using Finite State Machines (see my post about them, they can be a huge help to reliability), you should start by making a State Machine Diagram. As a quick refresher, the State Machine Diagram lays out transitions between states. This diagram can guide your development, and we have found a lot of success by developing with the goal of completing the State Machine Diagram.

If you aren't using a Finite State Machine or want to make sure that you take every advantage you can to have reliable code, write pseudo-code. Pseudo-code is conceptual code  (such as driveForward(10);) that can help lay out what you want to do before you write a line of actual code. Not only does Pseudo-code let you figure out what you need to do, but if done well, can look very similar to the actual code in your method. Once you have an action, you can create a method or Command to fulfill the action, and you can compartmentalize your code. If something goes wrong, it is much easier to see where the problem is with this compartmentalized code. Pseudo-code can also avoid making major issues with logic, since your logic is already figured out.

Planning your code is not as fun as just sitting down and writing, but it is worth every second of time. I have seen huge improvements in reliability by planning, and I hope that your team will as well.

Use A Whiteboard

Photo by Kaleidico / Unsplash

This applies for many aspects of programming, especially anything that involves math, and not following has caused me immense amounts of pain recently. When I was writing unit conversions for the NEO autonomous, I tried to figure out what I needed by just laying it out in code. The end result was that I made a combination of stupid mistakes, and I ended up pulling out a whiteboard.

If you are doing any math, lay your math out on a whiteboard and rewrite your code to follow your whiteboard logic as clearly and verbosely as possible. This helps reduce the potential for conversion issues, and makes sure that anything (especially autonomous routines) do exactly what you want.


Comment

I needed a picture for my Website :D I tried to shoot with my phone and realized that it would be many times better self done. 
I was also trying to shoot some interesting code and I choosed Request from HttpFoundation :-)
Photo by Henri L. / Unsplash

Going along with planning your code is another task that is usually not fun, but is a life saver for reliability. It can be a very good idea to put JavaDoc comments in (that have the units as part of the comment), so that you can find the information and units whenever you need it, thanks to IntelliSense. Oracle has a great post about how to write JavaDoc comments, that I recommend you check out if you aren't familiar with JavaDoc already.

Even if you write self-documenting code, there is always a risk that you will make a mistake. Commenting helps make clear exactly what is going on, helps you keep track of units, and makes your code easier to read.

/**
	 * 
	 * Convert velocities from Revolutions per Minute to Meters per Second
	 * @param neoVelocity 	the native velocity of a NEO in RPM
	 * @param wheelDiameter the width of the wheel in meters
	 * @param gearingRatio 	the ratio of gearing from the Neo to output wheel
	 * @return the velocity of the Neo in Meters per Second (WPILib Trajectory)
	 */
	public static final double convertRPMToMpS(double neoVelocity, double wheelDiameter, double gearingRatio){}

Use Builders

This is one of the things that could've saved me hours because of a super dumb mistake. When I was working on the autonomous code for the NEO mini-bot, I accidentally swapped two parameters and spent four days trying figure out what the problem was. I ended up figuring it out after endless amounts of time debugging, and there is a solution that could've made my life a million times easier.

The solution is the Builder pattern in Java. The Builder Pattern is a way to emulate named-parameters in Java.

Builder Design Pattern in Java - JournalDev
Builder Design Pattern in java. Why we need builder pattern? Builder Pattern in java example code. Creational design pattern in java.

Fundamentally, it works in the following way:

  • You have two classes: one is the primary class and the other is the Builder class
  • The Builder class has a set of methods that configure an instance variable and return this;
  • The Builder class also has a method called build that returns a constructed object.
  • The main class can construct based on the variables from the Builder

I created a very simple Java program to illustrate the concept:

Let's go over what each thing does.

Car

The Car class is our primary class. It has all of the methods and instance variables that a Car needs, and any logic that needs to apply will happen in this Car class.

Car.Builder

The Car.Builder class is what acts as the Builder, and it assigns a way to achieve named parameters. The instance variables match with the parameters in a Car and will be assigned to the Car object returned from build().

public Builder withMake(String make){
            this.make = make;
            return this;
}

Each of the methods like the one above allow us to configure everything one parameter at a time.

Main

The Main class is the entrance point for our program, and serves the role of allowing testing of the app. As you can see, the Builder allows us to construct an object in a much more verbose way.

Car car = new Car.Builder()
                .withMake("Honda")
                .withModel("Civic")
                .withColor(Color.WHITE)
                .withYear(2020)
                .build();

This is a lot easier to understand exactly what is happening compared to something along the lines of:

Car car = new Car("Honda","Civic",Color.WHITE,2020);

If you accidentally swap the parameters with the parameter format, it would be hard to figure out exactly what is going on (such as if the first parameter was model). However, the Builder lets you easily see what the Make, Model, Color, and Year are.

If you are working in a group, this can be even more helpful, since it means that the collaborators don't need to refer to the Constructor declaration in order to understand exactly what is happening in the code.

Color

Color is just a helper enum, and has no impact on the concept of a Builder.

Pro Tip

If your team uses IntelliJ, there is an awesome plugin that generates Builders. IntelliJ is great at generating repetitive code, so I have it on my computer even though most of our development happens in VS Code.

Builder Generator - Plugins | JetBrains
Adds ability to generate builder for a class and switch between them.

Shuffleboard

Shuffleboard is one of the most valuable tools we have for debugging both during and after a robot is run. It is part of WPILib and allows you to view, record, and replay SmartDashboard data. I will quickly cover a couple of tips to help make the most of Shuffleboard.

Log Everything (into sub-tables)

Shuffleboard is only valuable if you log lots of data, since you may not know what you need until afterwards. My team logs any data we think could ever be helpful, using the SmartDashboard class.

To make your Shuffleboard easier to find data, logging into sub-tables can be a huge help. These allow you to establish a hierarchy of data, and it is very easy to achieve. All you have to do is separate your path using / when putting data to SmartDashboard (e.g. SmartDashboard.putNumber("path/to/entry/name",0);. In Shuffleboard (and OutlineViewer) this will show up like a hierarchy, so similar data can be kept together.

If you use encapsulation, specifying a SmartDashboard path can be a huge help to segmenting SmartDashboard puts across motors or any other encapsulated class.

Record Data

At the bottom of Shuffleboard, there is a window that has a record button. Whenever you do a test, you should start a recording, and stop after the test is complete. This will let you replay all of your data and get a more complete understanding of the robot's actions.

Data is stored into C:\Users\user\Shuffleboard\Recordings on Windows.

Use Graphs

Watching one number change over time can be difficult to discern information from. Fortunately, Shuffleboard has a great tool where if there is a number plot, you can right click and show as a graph. You can then drag multiple fields from the left bar onto the graph to compare data. This makes anything that changes over time, like PID tuning, very easy to do, and it is a huge time saver.

Share Data

Having multiple people look through data can help identify any issues. We often post our data on Slack so that our mentors can look through data. This means that our entire programming team can easily see exactly what is going on and offer opinions on what needs to happen.


Encapsulation, Encapsulation, Encapsulation

One way to help with the debugging process is to create a Class to encapsulate other objects such as motors. This means that you can add debugging information for one motor and have it applied to all of them.

One of my most recent projects was to start writing a class to host a motor controller. This allows us to easily print to SmartDashboard or give any other critical information in one place, and then have it apply to all of our motors. Most of the work was put into creating the instance variables and then applying the instance variables to the motor controller. The getters, setters, and Builder were all generated in IntelliJ.

Example TalonSRX Encapulation
Example TalonSRX Encapulation. GitHub Gist: instantly share code, notes, and snippets.

The one above is for a TalonSRX, but I have also written some internally for Victor SPX and Spark MAX motor controllers. I haven't added it yet, but we will add SmartDashboard puts for extensive logging ability later.

I first wrote this motor controller encapsulation for the Spark MAX because it needs three different objects, but it worked so well that our Talon SRX will now also have it.


Pair Programming

Searching
Photo by Kobu Agency / Unsplash

Having one person that exclusively knows one of the aspects of robot programming is very dangerous. What happens if they get sick or can't make it to competition? The solution to this is pair programming, which has two main benefits:

  • Spread knowledge across two people, so that the team is not reliant on one for robot success
  • Offer a second set of eyes and second opinion while developing

If you don't have the resources to spare, an inanimate object (such as the famous rubber duck) can help with the second aspect, but having another human review can be a good idea.

Yellow Ducks
Photo by Andrew Wulf / Unsplash

Design Reviews

This is the last important way to help reliability: having other people review your code.

In a way similar to how a CAD team gets together and critiques some of the design decisions, you can have your software members present the code to others. In the presentation, you can have:

  • A brief overview of how it works
  • Diagrams (such as State Machine Diagrams)
  • Any problems that you had
  • Any conflicts between any subsystems
  • How the code integrates with the rest of the robot
  • Anything else that you think is necessary

Make sure to encourage questions, since these questions can help understand the complexities of the system better. It also could be a good idea to invite other people on the team (such as mechanics or electronics) to join if they want to learn more about the software or have any opinions.

Creating a culture of sharing allows your team to identify problems, propose solutions, and encourage out-of-the-box thinking, all of which lead to reliable robot code.


Encourage Bug Finding

This solution is becoming more popular in industry, and it turns finding bugs into a competition. While this may or may not work well for your team, using a system like this has the opportunity to encourage your programmers to go out of their way to comb through the code of others and find bugs. This solution probably would work best on larger teams, were some people aren't specializing in a subsystem.

In many companies, this is achieved by using the internal bug tracker and then creating a competition from people to create the largest number of bugs in the internal bug tracker.


Reliability as a Culture

Photo by Goh Rhy Yan / Unsplash / Race car teams are famous for their reliability and quick changes due to the nature of competitions

Team culture has an immense impact on the reliability of software. It is important that a team has a culture that emphasizes reliability and quality over merely achieving the end result. Every team has a different culture, so I can't say how to achieve a reliability of culture, but the following has worked well for us.

Get Started with Robot Code Early

Getting a head start on robot code means that your programmers have time to write code and check over. Your robot doesn't need to be complete for coding to start. This year, we have mini powered "prototypes" that we can develop software for. Once we get to our actual robot in our competition repository, we will be able to port over the code with relative ease.

Despite all of the jokes that you may have heard about software getting the robot late (especially back when Bag Day still existed), it is important that your programmers have ample time to write code and perform robot bring-up. We have had to bag a smoked motor because Software had too little time to perform bring-up before bag. Coding takes lots of time, so the whole team should respect that it takes time and the Software sub-team should make sure that things stay on track.

Get Mentors Involved

Mentors have the strongest ability to create long-term change. By having mentors explore options to create reliable robot code and involving the students, students will see that reliability is important.

If you have industries in your area that emphasized reliability (such as medical engineering, automotive engineering, or aerospace engineering), reaching out to people in these industries to ask if they want to give a presentation or be a mentor can be another great way to learn about industry practices to maintain reliability. A presentation from an engineer at a local medical robotics company is what led to us using FSMs and emphasizing reliability during the development process, so these opportunities could be a huge help to your team.

Rule of 5

This was something that I learned when mentoring a VEX team. The basic idea is that something does not work until it has achieved the same desired result five times in a row. On VEX and FRC, getting something to run five times in a row is very difficult. If the robot fails, the counter goes back to zero. By using this philosophy, the code is much more likely to run reliably in competition, and was part of why we could say confidently that the code works.

The core idea from this is that it does not work if it achieves the end result, it works if it achieves the end result reliably. This philosophy is what drove our initiative to have a reliable robot.

Understand the Value of Reliable Code

All of these ideas will fail if your programmers do not understand the value of reliable code. I will be the first to admit that I used to think of working on reliability as unnecessary, since I assumed that since it worked once it would be repeatable. I don't have a lot of advice on how to achieve this, but it is important that people understand that the extra time spent now on writing reliable code will save time in the future and make sure that more points are gained.

If there are any examples during a competition of how the robot suffered because reliability was not emphasized, it could be a good idea to bring this up in the debrief. The realization that our robot suffered because we didn't have reliable code was what got me interested and focused on reliability, and it could very well have the same effect on other people.


Conclusion

There is no panacea for making robot code reliable and you will almost certainly never have perfect robot code. However, steps can be taken to reduce the risk of things going wrong. A reliable, mediocre robot can beat an unreliable, advanced robot, so taking the time can be a huge competitive advantage and is completely worth the investment.

Alex Beaver

Hi! I'm Alex, and I am on leadership and software on Team 100. I want to help others through the FRC process by sharing tips and tricks on how to be effective in FRC.