Best way to implement rotating surface velocity in Java Units

Velocity<Angle> is good for reading rotary encoders as rotations per second (typical motor controller standard unit) but that is not engineering design units for say a shooter flywheel. What matters is the linear speed of the rotating surface in say feet per second (fps is my team’s standard).

I can’t figure out how to combine the two dimensions. Ideally we’d be able to specify any angular velocity to read the encoder (such as rotations per second) and interpret that input in any linear velocity (such as feet per second). [πd is the constant conversion factor between the two dimensions]

I haven’t figured out how to use derive() or a new class RotatingSurfaceVelocity extends Unit<RotatingSurfaceVelocity> to combine the two dimensions and retain the automatic conversion features of both. Both appear to force staying in one dimension - pick one but not both at the same time. [It would be nice to be able to have the diameter d as an easily changeable parameter but that isn’t required. A separate unit class for each different flywheel diameter is not unreasonable.]

(Ultimately it’d be nice to extend this to the PID gains such as voltage per feet per second given an encoder value of rotations per second, for example.)

What’s the closest we can come to making a Unit this way?

@SamCarlberg is currently working on a larger change to the Java units library to make custom compound units easier to create. The current library is not conducive to creating compound units due to language limitations (Java generics are quite limited in several ways).

3 Likes

You wouldn’t need a custom unit for this. Instead, you’d convert the angular velocity of the encoder to a linear velocity with a conversion factor like Measure<Per<Velocity<Distance>, Velocity<Angle>>> and multiply it by your encoder’s speed to get a Measure<Velocity<Distance>>

The rewrite would give you a better type to work with - probably Ratio<LinearVelocity, AngularVelocity> - but it would still be a conversion factor and would be used in the same way.

2 Likes

I can’t get the right units from the times(). It returns type Measure<?> which can’t be used in subsequent steps. I tried a variety of casts but am stuck on the <?> instead of Velocity<Distance>>.

// defining the conversion factor works
Measure<Per<Velocity<Distance>, Velocity<Angle>>> flywheelConvertUnits = FeetPerSecond.per(RotationsPerSecond).of(10.);
System.out.println(flywheelConvertUnits.toLongString()); // => 10.0 Foot per Second per Rotation per Second

// setting a test sensor value works
Measure<Velocity<Angle>> encoder = RotationsPerSecond.of(25.);
System.out.println(encoder.toLongString()); // => 25.0 Rotation per Second

// the times() works but not how I need for subsequent usage of result
/*Measure<Velocity<Distance>>*/var result = flywheelConvertUnits.times(encoder); // result is type Measure<?> instead of Measure<Velocity<Distance>>
System.out.println(result.toLongString()); // => 250.0 Foot per Second    

System.out.println(result.in(FeetPerSecond)); // ERROR with result being Measure<?> it can't combine with anything - two versions of the error message:

// The method in(Unit<capture#3-of ?>) in the type Measure<capture#3-of ?> is not applicable for the arguments (Velocity<Distance>)Java(67108979)

// C:\Users\RKT\frc\FRC2024\Code\TunePID\src\main\java\frc\robot\TunePositionPIDTalonFX.java:74: error: incompatible types:
// Velocity<Distance> cannot be converted to Unit<CAP#1>
// System.out.println(result.in(FeetPerSecond));
//                              ^
// where CAP#1 is a fresh type-variable:
// CAP#1 extends Unit<CAP#1> from capture of ? extends Unit<?>

You would have to do something like this:

Measure<Velocity<Distance>> tangentialLinearVelocity = Units.MetersPerSecond.of(m_angularVelocity.times(conversionFactor).in(Units.RPM));

That’s exactly the statement I am trying to use. The problem is the times() returns a type of Measure<?> which I don’t know how to cast to a known Measure or more to the point why wasn’t it cast to the numerator of the conversion factor after the other terms cancelled each other? The WPILib internal stuff for the units and magnitude did multiply correctly but the Java class is ? which prevents using it for the in().

And in trying to diagnose the problem I see this method definition for times and I don’t know how to read all of it:
default <U2 extends Unit<U2>> Measure<?> times(Measure<U2> other) {
I’m not very familiar with default and I don’t know what the Java syntax means for the units <U2 extends Unit<U2>> being separate from the Measure. I know what ? means but I don’t like it because I can’t use it.

But when you do .in(Units.RPM) that returns a primitive double.

The in() doesn’t compile - produces the Cannot Convert error message I showed. If I ignore the compile error and run anyway, it fails with essentially the same Cannot Convert error message. It doesn’t know how to convert <?>.

I think you need to define your conversion factor just as a double. That’s what I’ve always done.

Okay, but what about default <REM extends Unit<REM>> Measure<?> times(Measure<REM> other) {