What is the purpose of RobotMap.java in CommandBased?

Q: What is the purpose of RobotMap.java in CommandBased?

I understand that the actuators, motor controllers, and sensors are all instantiated within. But I feel like it would be more logical to instantiate them in their respective subsystems?

I have tried instantiating the motor actuators etc, in the subsystem and the code does work as expected.

I guess what I am really wondering is what principle of Object Oriented Design, (or any other reason) does RobotBuilder scaffold the code in this way? Does it make the code more maintainable somehow?

We use RobotMap to store all “robot-wide” constants - i.e. any values that the entire robot would care about.

Stuff like wiring channels (can’t double-book a PWM channel), robot-wide deadzone values, etc.

It gives an added benefit of one easy place anyone wiring/inspecting wiring of the robot to look to see how things should be, or were in the past.

This year we also started using static subclasses in RobotMap to make things more readable, especially with the new PD monitoring features.

Ex. we now do

RobotMap.PWMChannels.FRONT_LEFT_DRIVE
RobotMap.PDChannels.FRONT_LEFT_DRIVE

instead of

RobotMap.FRONT_LEFT_DRIVE_PWM_CHANNEL
RobotMap.FRONT_LEFT_DRIVE_PD_CHANNEL

you are not supposed to instantiate anything! it is meant to hold all the constants of the code, like pwm ports or dio ports.

It centralizes the allocation of ports in one location, so that you don’t need to look everywhere in your code to figure out whether a port is allocated.

In OO-speak, it hides the concern of port mapping in one location in your code, so that the subsystems don’t have to know how you have things wired up.

RobotBuilder does this automatically unfortunately. But I agree, this class should only be used for robot constants like device channels.

I feel like pulling the port numbers out makes a bit more sense than instantiating the actuators etc. in RobotMap.

Either way, you are breaking the separation of concerns and making it harder to transfer the subsystem to a different codebase.

By RobotBuilder doing it the way it does it is actually breaking the principle of Encapsulation by giving other objects access to the devices of another subsystem.

Generally, when using the command-based framework, you instantiate things like speed controllers in the owning subsystem, so that they can be kept hidden from other subsystems. This makes it easier to reason about the subsystem, because you know that all of the logic impacting the motor is in front of you in one file.

3081 commonly creates a special SensorSubsystem that is the owner of nearly all of the sensors on the robot. SensorSubsystem has methods on it that allow other subsystems and commands to read sensor values. For example, suppose your elevator has a limit switch when your traveler is at the lowest desired position.

In the SensorSubsystem, you’d say something like (note C++ here, not Java):


	this->elevatorLowLimit = new DigitalInput(ELEVATOR_LIMIT_LOW);

and have a method like this:


bool SensorSubsystem::IsElevatorAtLowLimit() { 
	return this->towerLowLimit->Get(); 
} 

Then code in other subsystems/commands would say:


if (sensorSubsystem->IsElevatorAtLowLimit() {
	// turn off motor, or whatever
}

Why not put the sensor (limit switch from your example) in your “lift” subsystem. If other subsystems need the value, just provide an accessor?

That is the route we take. Subsystems have access to each other so we put accessor methods in each of the subsystems for the values needed.

That can be a valid approach, too, if the limit switch is exclusively part of one subsystem. In the past, we’ve gotten in situations where there’s a tangle of subsystem references, only to get access to sensor state. The SensorSubsystem prevents that.

Having one subsystem referencing another also makes it easier to accidentally command the referenced subsystem. This breaks the “requires” model in the command-based framework, which is present to help you reason about where commanding might be coming from. You can certainly produce a correct program by doing so, but it is harder to think about. We deliberately design our robot program so that it is easy to think about.

Historically we never use a RobotMap class. We have a class that contains all the objects (Talons, Joysticks, etc.) statically so we can do Storage.someTalon.set(1.0) (or just someTalon.set(1.0) since we can import static now). However, this year we do have a RobotMap class to hold constants, and we have all praised its usefulness, especially for when the electronics team needs a port ID. I do agree that RobotMap should just be used for holding constants, not the actual objects, which you should have a separate Storage class for.

I’m a C++ guy and I think one could solve the problem of subsystems driving other subsystems by making all motor driving functions non-const and all sensor reading functions const. This way one could provide only a const pointer to the other subsystems but non-const pointers to the commands allowing commands to drive the subsystems but not allow the other subsystems to, yet allow both access to sensor readings.

I did a quick look to see if Java provides similar, but it looks like perhaps not.

I’m trying to think if the idea of sharing a sensor between two subsystems perhaps indicates that a better subsystem configuration is possible. I’m not sure I like collecting all sensors into a globally accessible sensor class-- one subsystem may reset an encoder and mess up code in another for example.

I guess I prefer the idea of making access functions in a subsystem that owns a sensor better, but I think it could use further discussion.

I suspect perhaps that code in a subsystem that needs a sensor from another subsystem might better be coded in a command that requires both subsystems. But I think I can also think of counterexample where that won’t work well.

Can you give a good concrete example where two subsystems require a single sensor so we can kick around various approaches to resolve it within the command based paradigm?

Here is an example this year. We have a command that runs a gripper on top of an elevator. I want to be able to do certain things with that gripper, but only when that elevator is below a certain height. The sensor is also used to run a controller that sets the elevator at a different height. Both subsystems want access to the sensor, but the sensor is mainly used with the elevator.

You can accidentally control any subsystem from any command, not sure how its any less easy with a sensor subsystem.

I do agree that RobotMap should just be used for holding constants, not the actual objects, which you should have a separate Storage class for.

I strongly disagree with the second part of this statement, and its the same reason our team does not use the RobotBuilder system. Look up programming principles like Data Hiding and Encapsulation. By putting all of the devices in a storage class you are breaking those principles.

Craig’s notion of using static methods to expose the sensors is another approach that’s worth considering. Note that we don’t expose the sensor object directly in our sensor subsystem; we have getter methods that return higher level states, based on the sensor state.

notmattlythgoe’s example is a good one. In general, one category is where you have two subsystems that occupy the same space, and one subsystem wants to verify that the other subsystem isn’t present.

Another example might be a sonar that points out of the front of the robot. The robot’s drive train uses the sonar as an input to slow down as it approaches an object. The robot might also have a light that flashes that indicates an object is close.

Or your elevator keeps track of the number of totes in the elevator, and you change drive train behavior based on the number of totes.

If that works for you, fine, but it’s not O-O. The sensors and actuators that work together should be part of the same subsystem. In particular, limit switches, encoders, and potentiometers whose purpose is to provide feedback on a particular actuator should be in the same subsystem with that actuator.