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?