IO hardware classes

Some questions regarding designing an architecture with IO classes

My team has been looking to switch to a method of abstracting hardware like advantage kit or the standard way teams handle swerve modules (eg. hardware interface with real variants/sim implementations), since it would enable us to easily implement sim and easily go between different pieces of hardware

The basic structure of one of these classes can be generalized to

public interface ExampleIO extends Sendable, AutoClosable {
  public State getState();
  public State getDesiredState();
  public void updateDesiredState(State state);
}

Where implementations abstract hardware and include closed loop control (with updateDesiredState) called periodically.
However, how to organize IO classes becomes a strange problem.

The exception

public interface SwerveModule extends Sendable, AutoCloseable {

  public SwerveModuleState getState();

  public SwerveModulePosition getPosition();

  public void setDesiredState(SwerveModuleState desiredState);

  public SwerveModuleState getDesiredState();

  public void resetEncoders();

  public void setTurnPID(PIDConstants constants);

  public void setDrivePID(PIDConstants constants);

  @Override
  default void initSendable(SendableBuilder builder) {
    builder.addDoubleProperty("current velocity", () -> getState().speedMetersPerSecond, null);
    builder.addDoubleProperty("current angle", () -> getPosition().angle.getRadians(), null);
    builder.addDoubleProperty("current position", () -> getPosition().distanceMeters, null);
    builder.addDoubleProperty(
        "target velocity", () -> getDesiredState().speedMetersPerSecond, null);
    builder.addDoubleProperty("target angle", () -> getDesiredState().angle.getRadians(), null);
  }
}

One of the driving While being similar to other kinds of IO interfaces, the swerve module has a lot more aspects to it, since it has two motors that it has to control.

Boilerplate and weird abstractions

We use an (absurd) double jointed arm on an elevator for scoring, so we thought it would be most natural to have 2 JointIO instances and 1 ElevatorIO instance in an Arm subsystem.
And this… works, but JointIO and ElevatorIO are basically identical, not to mention that we already consolidated WristIO and ElbowIO into the same interface since they are exactly the same. Also, to minimize more boilerplate, we were planning on using a single JointSparkMax class to handle both the elbow and wrist, which doesn’t work because the elbow encoder is relative and the wrist encoder is absolute.
In addition, ElevatorIO and JointIO might end up being the same, so it might keep things clean to consolidate everything into a single HardwareIO class, although this leaves a bad taste in my mouth.

Any ideas or advice?

1 Like