Potentially naive “command-based” Java question. I want to call subsystem methods from the periodic() method of a different subsystem. Yes, this is distinct from using a command.
For instance, in the periodic() method of our Vision subsystem I’d like to:
Query the poseEstimator in the Drive Subsystem (to get the current pose)
Update it with poseEstimator.addVisionMeasurement in the Drive Subsystem depending on several conditions
I’m happy making both of the above methods in the Drive subsystem (rather than trying to tamper with the poseEstimator directly). The issue is I can’t get the drive subsystem instance (say m_Drive) to resolve from the Vision subsystem no matter what I import (RobotContainer.java, Drive.java, etc.) If I can’t see the Drive subsystem instance from the Vision subsystem, I can’t see its methods.
What am I missing? Is there a better way to do this?
There are a few alternatives that I know of and easily thousands I haven’t even heard of.
To call a method from a different subsystem, you either need to have an instance of that subsystem, such as DrivetrainSubsystem drivetrain = new DrivetrainSubsystem(); and then drivetrain.poseEstimator();
or if the method in question is static, you can just do DrivetrainSubsystem.poseEstimator();
For the first solution, an instance of the drivetrain is created within RobotContainer. You can access that instance in other subsystems by creating a getter method for it.
public static DrivetrainSubsystem getDrivetrain() {
return m_drivetrainSubsystem; }
You could give the Drive subsystem a reference to the Vision subsystem through its constructor (presumably in RobotContainer).
It’s also possible that the Vision subsystem should not be…a subsystem. Calling it a “Subsystem” can hide the true meaning behind a name/symbol; the Command robot framework uses this term to mean:
If the Vision class isn’t writing changes out to the hardware it interfaces with, it might not need to be a subsystem, just a plain-old-Java-object, and thus not subject to the subsystem/command structure. In that case, it wouldn’t be odd to pass a reference to Vision in through Drive’s constructor.
It sounds like Drive currently owns the pose, and you’d like to update it with additional sensor information from Vision. If so, it may be appropriate to do this from Drive’s periodic() method, assuming the “several conditions” are not dependent on the other subsystems of the robot.
Then when you need the pose call m_poseSupplier.get()
This decouples the need for vision to know about a drivetrain which it really cares nothing about.
It only needs to know about the pose. Any way of determining the pose would work. It doesn’t care if it is a drivetrain or a GPS. It may seem academic in code as short lived as a robot with so few people working on it but decoupling is a good practice in industry. It makes your code more flexible and less dependent on specific implementations.
Another approach is to promote the pose estimator outside of the subsystems. We use the singleton pattern and the drivetrain and vision subsystems can both access the pose estimator without having any dependencies between the subsystems. Here’s our singleton class:
public class RobotOdometry {
private static final RobotOdometry robotOdometry = new RobotOdometry();
private SwerveDrivePoseEstimator estimator;
private SwerveModulePosition[] defaultPositions =
new SwerveModulePosition[] {
new SwerveModulePosition(),
new SwerveModulePosition(),
new SwerveModulePosition(),
new SwerveModulePosition()
};
private RobotOdometry() {
estimator =
new SwerveDrivePoseEstimator(
RobotConfig.getInstance().getSwerveDriveKinematics(),
new Rotation2d(),
defaultPositions,
new Pose2d());
}
public static RobotOdometry getInstance() {
return robotOdometry;
}
public SwerveDrivePoseEstimator getPoseEstimator() {
return estimator;
}
}
I think I like the idea of extracting the odometry from the Drive subsystem and making it a singleton, since it is not really hardware specific. That way periodic methods from multiple subsystems (Drive and Vision) can access and update odometry as needed.
I do take the point that something like Vision (a Limelight in our case) doesn’t really need to be a subsystem.
Moreover, there’s really no benefit to making something that doesn’t need the hardware mutexing a subsystem. The Subsystem class offers features almost entirely designed around requirement mutexing; without that you just have a normal class.
Since something like a vision class doesn’t have the hardware requirements to fully utilize being a subsystem, is there an alternative to subsystems that are capable of running something akin to vision periodic() and/or default commands?
You can schedule a periodic actions using the addPeriodic methods (this also lets you specify a timing offset relative to the main loop!), or by manually adding a call to a class method in robotPeriodic.
We’re considering deprecating subsystem periodic entirely, because it encourages unsafe usage patterns that circumvent the hardware mutexing by performing motor control outside of a (mutex-guarded) command. Our example projects (even some of the ones I’ve written…) are really bad about this.
This is also a reason I think we should strongly encourage the use of the subsystem factories run, runOnce, etc. over direct instantiation of command classes (to the point of removing other usage patterns from our example projects entirely) - they declare their subsystem requirements automatically in the background, so with them you can’t accidentally forget to reserve the mutex (which is an extremely common error).
I think periodics make perfect sense and are essential for some scheme’s while they are dangerous if used incorrectly they are very useful when used correctly.
Telemetry outputs (like advantage scope data), if your control scheme is to only control your subsystem periodically to maintain a known “state” every loop, to update static variables which can affect other subsystems safety’s if one sensor is used for 2 subsystems (for example a sensor that detects when a trade off between a multi-stage intake is possible). Those are the only ones I can think of off the top of my head.
The problem is that subsystem periodic is a builtin escape hatch out of the command framework that is misused to do things like run motors. I think that if you want an escape hatch, you should be forced to add one in yourself, not have one handed to you on a silver platter.
If you really want subsystem periodic as an escape hatch, you can always call ~robot.addPeriodic(my subsystem.periodic).
For telemetry, you can schedule a function called telemetry or log yourself.
For accessing sensors from other subsystems, you can have getters.
For setting state, you use commands. The whole point of the command framework is to manage state using commands. The state machine comes with the framework.
tldr: If your car falls into a lake, you can use a car safety glass breaker to break the window and get out. Toyota doesn’t make all their cars without windows from the get go as a “feature”.