Clean ways to configure robot constants

This past year our team used a file on the roborio which we would read in and load our robot constants into when the robot code started. However, the code style we used was clunky because we had to have the file uploaded to the Rio, the header file which held all the variables, and the cpp file which loaded in all the constants from the file.

This led to a lot of variable rewriting and name duplication which honestly ended up being more work to confirm everything was correctly written.

For reference we use C++ and we used an INI file format.

Are there any other ways of doing this that I am missing here?

I’d be interested in taking a look at your code to refine my advice. Differences in how often you’re tweaking and refining code also play in here. If you’re constantly tuning constants, then compiling things in may not be the best solution for you.

There are a few battle-tested options you might consider:

  • Dependency Injection - this one is the most popular in bigger frameworks because it scales well. Probably overkill for an FRC robot, but might also be a good experience for students to learn things that will translate to non-FRC robots.
  • Factory/Abstract Factory Pattern - without more information, this would be my preference. Essentially, you have a constants map class that you query for the value of a constant, probably set up as a singleton somewhere. This lets you have a few different options: you can continue to read it in from a file, have some default configurations that are compiled in, etc. It gives you a lot of flexibility.
  • Use NetworkTables and SmartDashboard/Shuffleboard - this has worked well for us, but is specific to FRC. I’ve heard mixed reactions across the FRC community.

We have all our constants in a class:

1 Like

Here is the main file I’m worried about. It just led to a lot of typos and risk of missing something.

YAML is pretty great. If you’re clever, you can set it up so that you can query arbitrary hierarchical fields from your configuration files - I made a system a while back that lets you do something like this:
Config.getDouble('arm.setpoints.hatch_l1')

That way, you don’t have to have any sort of class parallel to your config file, you can just query using strings (in the case of the above example, it would read from arm.yaml, which has a list named setpoints and a field named hatch_l1.

Makes everything from first time setup to adding and changing fields a breeze.

This year we used the approach already suggested above where one has a single class full of constants. On initialization, each subsystem, command, or other structure fetches initial values of parameters from this class. Most of these values are not expected to change throughout the life of the robot program. Those which might need to be tweaked “on the fly” are published to shuffleboard where they can be altered. Every “component” that has any of these dynamic parameters listens for changes on a network tables entry so that the appropriate procedure can run whenever a value is changed (e.g. when a new PID parameter is received, it needs to be set on the PIDController/TalonSRX/whatever other structure you’re using for PID control).

2 Likes

This seems similar to the WPILib Preferences class. Have you looked at that?

2 Likes

Our students implemented a constants class, but each constant is actually specified as an array of values. The items in the array represent the value for each physical or virtual platform: the practice robot, the competition robot, and a simulator version (which I don’t actually think we use). A file is prewritten on the file system of each RoboRio with an identifier that tells the code whether this is the practice robot or competition robot (and if no file is found, it assumes competition robot). A class method checks the file value on startup and uses this to index into the arrays and retrieve the correct value for any given robot.

I personally would never trust a config file over values in the actual code, unless any errors (invalid format, missing key, incorrect value type) are raised at compile time. The only thing config files really help with is verbosity, which isn’t a large issue in any language except Java. Even then it’s a small, one-time cost mitigated by copy-paste. You save a lot more time by having editor goto support when changing the constants.

In C++ I would just use something simple like this:

namespace driveConf{
	namespace fl{
		const int DRIVE_ENCODER_A = 6;
		const int DRIVE_ENCODER_B = 7;
	}
	namespace fr{
		const int DRIVE_ENCODER_A = 4;
		const int DRIVE_ENCODER_B = 5;
	}
// etc
}
namespace intake {
    const int LIFT_SOLENOID_ID = 1;
}

If you want to swap sets of constants statically, you can use preprocessor directives. If you want to do so dynamically, I’d recommend using a struct and a global static variable that is set at startup. Both of these methods avoid the pains of string keys.

It also depends on how constant the “constant” is. If it’s physical wiring configuration, static makes a lot of sense (although you still may want a way to switch it easily at startup between e.g. practice and competition robots without recompiling your code). If it’s something that’s typically tuned (e.g. soft limits, setpoints), it is very useful to be able to quickly tune the “constant” from the dashboard and then save it (make it persistent) without needing to recompile and deploy.

2 Likes

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.