The Problem
On 78 we ran into an interesting, and I suspect pretty common, issue. We have two swerve chassis, similar but with slightly different dimensions, that we want to run simultaneously throughout the season. One will be the competition chassis, the other a test chassis used for April tag alignment and first-iteration testing of autonomous routines.
Swerve drive kinematics and odometer are very particular about robot measurements, and even the closed-loop parameters will change between bots. We also want different controls on one bot vs the other.
Ideal Solution
The simplest solutions would be to have two code projects. I didnât like this because any work that you do on the test robot would need to be copied over to the competition bot project, and vice versa. This would get really messy as the code bases mature, and keeping everything in sync would be a nightmare and weâd probably end up giving up on the test bot altogether.
The overall solution is to have 2 RobotContainer
objects, one for the test chassis, and one for the competition chassis. Each one would define (either directly in the RobotContainer
class or an adjacent Constants
class) the necessary robot characteristics to be used in shared SwerveDrive, April Tag Detection, and any other shared functionality classes / packages. It should be impossible to mix up the constants in the code, and should be hard to accidentally run the test robot code on the competition bot, and vice versa. Finally, the method of determining which robot was being run should not require a redeploy under any circumstance.
Iâve seen some solutions to this that utilize the MAC address of the wifi radio. MAC addresses are unique to hardware devices, so this is certainly a solution that could work, and teams make it work for them. I didnât like this solution because the MAC address for the test robot would need to be defined in the code. While we could default to the competition code if an unknown radio was detected, if the radio on the test-chassis was swapped out, weâd have to redeploy code to detect the new radio.
Instead, I decided that Environment Variables were the solution. It should have been obvious from the start, since itâs right there in the name. We want to adjust code based on the environment the code is running in (in this case, which roborio).
The Setup
Our project now has two packages: frc.robot.test
for the test robot and frc.robot.competition
for the competition bot. Inside each of these packages are 3 files:
Robot.java
RobotContainer.java
RobotConstants.java
Both sets of files act similarly to how they would in a single bot setup. Robot.java
handles the init
/ periodic
methods, RobotContainer
sets up subsystems, commands, and trigger bindings, and RobotConstants
contains static constants that the subsystems use.
Both RobotContainer
and RobotConstants
are package-private, so itâs impossible to access them directly in any subsystem or command class. Instead, the commands / subsystems should accept config parameters in their constructors, and each RobotContainer
is responsible for configuring the subsystems as is necessary for each robot.
The magic happens in the main
method.
public static void main(String... args) {
if ("TEST".equalsIgnoreCase(System.getenv("FRC_BOT"))) {
RobotBase.startRobot(frc.robot.test.Robot::new);
} else {
RobotBase.startRobot(frc.robot.competition.Robot::new);
}
}
When the program starts, we check the environment variable FRC_BOT
. If it is set to TEST
, that launches the test Robot
, and if not, we launch the competition bot.
Setting an environment variable on the roboRIO is easy. You can ssh in using powershell or terminal (ssh [email protected]
) and add export FRC_BOT=TEST
to the end of .profile
in lvuser
âs home folder.
This solution means that we do not have to worry about having specific hardware connected to the test chassis to identify it as a test bot, and on the off chance the test roborio was put on the competition robot, no redeploy of code is required. Just ssh
in and remove the environment variable from being set.