Can I instance a subsystem in another subsystem just for using sensor value

Hi, I am a member of the 6986 FRC Team.
Now I am trying to calculate the angle between robot’s turret and the vision target. My idea is using the turret encoder value add the gyro value to get the angle.
so, I need to get gyro value from driveBaseSubsystem and use it in turretSubsystem. I just wonder would it be ok to instance driveBaseSubsystem in turretSubsystem.
My code is like this, thanks!

Unfortunately, this is not be possible. If you’ll re-instantiate DriveSubsystem inside turretToTargetAngle you’ll re-instantiate all of its hardware (motor controllers, sensors, etc.) every time you call the method and you’ll get a “resource already allocated” error.
A possible solution is passing the DriveSubsystem object as a parameter to turretToTargetAngle. This method will look similar to the following:

public double turretToTargetAngle(DriveSubsystem drivetrain) {
    double baseToTarget = 180.0 - drivetrain.getHeading();
    double turretToBase = tickToAngle(m_turretEncoder.getPosition());
    return turretToBase + baseToTarget;
}

However, you can also put getHeading somewhere more global so you won’t have to use the DriveSubsystem class/object at all.

I just wanted to say that this question was asked very well. Only suggestion for next time is to paste the code itself instead of a screenshot.
Hope my answer helps! :grinning:

All that Dan said above. Also, when you tried instantiating the subsystem in your other subsystem, the allocation of resources (PWMs, DIOs, CAN addresses) would fail because the other subsystem would already have them.

It might make more sense to make the common sensors their own subsystem which can be accessed by (but not required by) both the drive train and the turret subsystems. It would probably only have a single default command for the sensor maintenance, if needed.

1 Like

Another option is to pass the DriveSubsystem instance to the constructor of TurretSubsystem, and store it in a member variable. Eg in TurretSubsystem:

private DriveSubsystem m_drive;

public TurretSubsystem(DriveSubsystem drive) {
  m_drive = drive;
}

public void func() {
  m_drive.getHeading() etc
}

This requires that the subsystems be created in a certain order in RobotContainer:

m_drive = new DriveSubsystem();
m_turret = new TurretSubsystem(m_drive);

This technique is called Dependency Injection.

2 Likes

Although the other ideas brought here are great ideas, why do you need this function in the subsystem file, and not in a command? It would not require a specific initialization order, and is more appropriate for command-based (which is what WPILib encourages).

1 Like

This is the line our team drew - a subsystem only provides primitives based on the components it “owns”. Once subsystems need to be combined, that functionality goes in a command.

The bottom line is that you only want to do this in one place. That way if anything when something changes, you don’t risk having two pieces of code doing incompatible things.
Further, if it needs to be running when otherwise unrelated (or loosely related) commands are going, you do not want to put it in a command because it may not be always running when the function is needed.

1 Like

I think if our team had a set of sensors that could be used in common, independent of other subsystems, and without being set as a requirement I’d tell them to use a Singleton pattern and not bother treating it as a Subsystem.

Afaict, the reason for the subsystem concept is largely so WPILib can handle locking across commands and so resources aren’t double allocated. If there’s no need for locking, I’m not sure there’s a major advantage in inheriting. The only thing it buys you is a default Sendable implementation at that point, right?

3468 had a few instances like that this year. In both cases, the solution was to use Lambda functions to pass resources, between Subsystems.

  1. We have three Photoelectric IR Reflection Diffuse Sensors. These are being used to track balls in our Ball Handing subsystems, so they are involved in processes for three of our subsystems, BallIntake, Conveyor, and Launcher. Because they are mounted to our Conveyor subsystem board, we put them in that Subsystem, and since we decided to just use them as Trigger/Buttons for Commands rather than read them directly in any Commands or Subsystems, we just create Trigger objects in the RobotContainer for them and use them in triggering commands.
Trigger initialConveyorDetector = new Trigger(() -> conveyor.getInitialConveyorSensor());
Trigger finalConveyorDetector = new Trigger(() -> conveyor.getFinalConveyorSensor());
Trigger launcherConveyorDetector = new Trigger(() -> conveyor.getLauncherConveyorSensor());
...
// Intake
intakeButton.and(finalConveyorDetector.or(launcherConveyorDetector).negate())
    .whileActiveContinuous(new IntakeBallIntake(ballIntake));

// Conveyor
intakeButton.and(initialConveyorDetector.or(finalConveyorDetector).and(launcherConveyorDetector.negate()))
    .whileActiveContinuous(new AdvanceConveyor(conveyor));
  1. We are using a Raspberry Pi running Chameleon Vision to track the Goal, and use the distance from the goal to set the Velocity of our Launcher subsystem. Our solution here was to create a Static method in Launcher that converts the distance from the camera to an RPM and put that in a lambda that gets passed to our SetLauncherVelocity command.
// Launcher
launchButton.or(setLauncherVelocityOverrideButton).whileActiveContinuous(
    new SetLauncherVelocity(launcher, () -> Launcher.distanceToVelocity(camera.getDistanceFromGoal())));

Got it , Thank you for your patient reply. :blush:

1 Like

Hi, kevinvlark
I thought about putting this fuction in Command, And this command requires two subsystems: DriveBaseSubsystem and TurretSubsystem. Whether it means when this command is running, then other command (also requires Drivesubsystem) such as teleopCommand will be interrupted and the robot stops moving? That is what we don’t want.
In fact, we use this fuction just for a rough adjustment before vision, so it will be used when the robot is moving.

Thanks GeeTwo,
As you said, if I create a new class for gyro, would it be ok if I create two intances of gyro such as gyro1 and gyro2 in two different places? if it’s all right, when I rest the gyro in gyro1, will getting the value from gyro2 be affected?
The question may be silly, but it puzzled me. :face_with_head_bandage:

If you’re talking about the same physical gyroscope, you’ll get a resource conflict when you instantiate the second copy of the gyro with the same address. You should instantiate it once (e.g. in RobotContainer) and inject it into the subsystems (or less pretty, have it be global). Only having one gyro would also ensure that the two subsystems get consistent answers. If you inject the gyro during instantiation of the subsystems, you can save a copy of the gyro object in each of your subsystems and afterwards just use that over and over. (In java, copying an object with a normal assignment statement actually creates a second reference to the same object.)

If (less likely) you’re talking about two different gyroscopes (e.g. one at port 3 and one at port 5), then yes you can declare two of them.

I think I got it. I can’t instantiate a hardware twice, but I can use the reference of the first instance.
So Dan’s way to solve this is simple, I just need use the Drivesubsystem reference to get the gyro heading. If I instantiate the gyro in RobotContainer, then I need use it’s reference in both DriveSybsystem and TurretSubsystem.
Thank you so much. :grin:

3 Likes

as an amusing aside, we had a student put a new LightSubsystem().update(); inside of periodic, instead of reusing the existing instance.

Ran out of memory pretty quickly.

2 Likes

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