-
Notifications
You must be signed in to change notification settings - Fork 611
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[wpilibj] Add wpiunits subproject for a typesafe unit system #5371
Conversation
wpiunits/src/main/java/edu/wpi/first/units/ElectricPotential.java
Outdated
Show resolved
Hide resolved
wpiunits/src/main/java/edu/wpi/first/units/ElectricCurrent.java
Outdated
Show resolved
Hide resolved
protected Unit( | ||
Class<? extends U> baseType, | ||
UnaryFunction toBaseConverter, | ||
UnaryFunction fromBaseConverter, | ||
String name, | ||
String symbol) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should document that subclasses need to define a constructor matching this signature, less the baseType (which should be hardcoded by the subclass). This is required for Units#derive
to work
eg
class SomeUnit extends Unit<SomeUnit> {
SomeUnit(
UnaryFunction toBaseConverter,
UnaryFunction fromBaseConverter,
String name,
String symbol) {
super((Class) SomeUnit.class, toBaseConverter, fromBaseConverter, name, symbol);
}
}
* | ||
* @param magnitude the magnitude of the measure to create | ||
*/ | ||
public Measure<U> of(double magnitude) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
quantity
or qty
may be more descriptive, eg Inches.quantity(5)
or Seconds.qty(0.020)
|
||
// Time | ||
public static final Time Seconds = BaseUnits.Time; | ||
public static final Time Second = Seconds; // singularized alias |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These aliases are really nice for making combined units read nicely like Feet.per(Second)
instead of Feet.per(Seconds)
. Would probably be worthwhile making singular aliases for the rest of the unit definitions in this file
public static final Velocity<Angle> DegreesPerSecond = Degrees.per(Second); | ||
|
||
// Acceleration | ||
public static final Velocity<Velocity<Distance>> MetersPerSecondPerSecond = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really wish Java had something like typedef
so we could just alias Velocity<Velocity<X>>
as Acceleration<X>
- and, while we're at it, alias Per<X, Time>
to Velocity<X>
. Too bad.
.named("Fahrenheit") | ||
.symbol("°F") | ||
.make(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Force and torque units could be helpful, too
Here's what we call all the C++ units, for reference: https://github.com/wpilibsuite/allwpilib/tree/main/wpimath/src/main/native/include/units |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This (and ReadOnlyPrimitiveLong) feel like they should be hoisted to wpiutil, as they're very generic? Although I know that would add a dependency that's not currently present.
/format |
Checkstyle is failing with two errors:
|
Currently exists as a standalone project with nothing depending on it
Avoids autoboxing allocations when dealing with combinatory unit caches
Realized LongToObjectMap was fairly complex, left some documentation to describe the workings
Prefer the Unit#of and Unit#ofBaseUnits instead Tweak equivlance/isNear checks to broaden accepted unit inputs
Add some tests for the Mult class
ElectricPotential -> Voltage ElectricCurrent -> Current
Docs said it the method converted *to* the new unit, not *from* it
Lets us use unit declarations as `Unit<XYZ>` instead of needing the internal type `XYZ` Backwards compatible because `U` is declared to extend `Unit<U>`
Change RPM to base on rotation instead of revolution
eg `x.mut_plus(y).mut_divide(z)` for `(x + y) / z` with zero allocations can still use `x.plus(y).divide(z)` for readability at the cost of extra allocations, which is fine if not used in a hot loop
Currently exists as a standalone project with nothing depending on it. Will be used by wpimath and wpilibj. Using a separate project to allow easy reuse between projects and for publishing for vendor use.
This is large enough that I'll be doing wpimath and wpilibj in a followup PR
Background
Unit safety has always been a problem in WPILib. Any value corresponding to a physical measurement, such as current draw or distance traveled, is represented by a bare number with no unit tied to it; it's up to the programmer to know what units they're working and take care to remember that while working on their robot program. This leads to bugs when programmers accidentally mix units without knowing, or measure something (such as a wheel diameter) in one unit and program using another.
wpiunits
is intended to eliminate that class of bugs.Another source of friction is the controllers and models in
wpimath
that expect all inputs to be in terms of SI units (meter, kilogram, and so on), while most FRC teams are US-based and most commonly use imperial units. wpimath does a good job of noting unit types in method names and argument names; however, it still relies on users properly converting values (and knowing they even have to do so).API
There are really only two core classes in this library:
Unit
andMeasure
. AUnit
represents some dimension like distance or time.Unit
is subclassed to define specific dimensions (egDistance
andTime
) and those subclasses are instantiated to defined particular units in those dimensions, such asMeters
andFeet
being instances of theDistance
class.A
Measure
is a value tied to a particular dimension like distance and knows what unit that value is tied to.Measure
has two implementations - one immutable and one mutable. TheMeasure
interface only defines read-only operations; any API working with measurements should use the interface. The default implementation isImmutableMeasure
, which only implements those read-only operations and is useful for tracking constants.MutableMeasure
also adds some methods that will allow for mutation of its internal state; this class is intended for use for things like sensors and controllers that track internal state and don't want to allocate newMeasure
objects every time something likemyEncoder.getDistance()
is called. However, the APIs for those methods should still only expose the read-onlyMeasure
interface so users can't (without casting or reflection) change the internal values.A
Units
class provides convenient definitions for most of the commonly used unit types, such asMeters
,Feet
, andMilliseconds
. I recommend static importing these units egimport static edu.wpi.first.units.Units.Meters
) so they can be used likeMeters.of(1.234)
instead ofUnits.Meters.of(1.234)
Examples
These examples are admittedly contrived. Users shouldn't be interacting much with measure objects themselves, since wpimath and wpilibj classes will be updated to support working with them; users will often just have to take a
Measure
output from one place (such as an encoder) and feed it as input to something else (such as a PID controller or kinematics model)