SciLib development so far
Initial Discussion & motor builders
After the rapid react season, it became apparent that we needed a team library to keep high quality documented code for future use. It was initially supposed to include standardized wrappers for vendor specific motors, but we later decided to deal with motors via builders, since configuration was the most useful part to standardize.
Controls
The scope later increased to include a control and filter library, adapted from #frc694’s StuyLib. It was intended to be a minimal port, avoiding some aspects that we didn’t like. I jumped to implement this and eventually got to a prototype that hypothetically would work. However, we decided that there was no clearly justifiable reason to maintain this library over WPILib’s, especially when the stated purpose was simplicity. As of now, this feature has been dropped, but we’re exploring another fun implementation of controllers using streams.
Kotlin
We decided to start testing and teaching kotlin during the 2023 off-season, however it has many features that can be taken advantage of best with a team library. Since we can still use kotlin with java in the 2023 season, we started implementing features in it.
Immediately, kotlin’s named parameters and data class features drastically improved the usability of MotorConfig
Example usage for creating a drivetrain
val leftMotor = MotorConfig(neutralBehavior = NeutralBehavior.BRAKE, currentLimit = 80)
val rightMotor = leftMotor.copy(inverted = true)
val leftPorts = intArrayOf(1, 2, 3)
val rightPorts = intArrayOf(4, 5, 6)
val leftGroup = MotorControllerGroup(leftMotor.buildCanSparkMax(*leftPorts, motorType = MotorType.kBrushless))
val rightGroup = MotorControllerGroup(rightMotor.buildCanSparkMax(*rightPorts, motorType = MotorType.kBrushless))
val driveTrain = DifferentialDrive(leftGroup, rightGroup)
Leftover from the abandoned control library was a Filter
functional interface, representing a chainable DoubleUnaryOperator
, or (Double) -> Double
function. We realized a very helpful tool for debugging would be a LoggableFilter
, which would be a class that implements Filter
and Sendable
, allowing for the logging of all intermediary values. There was one problem with filters though, they were clunky. In every single use case, filter would be used to calculate based off of the same input every time. That’s why we cleaned up the api and replaced filters with streams, functional interfaces that extend DoubleSupplier
. They support operations between streams, scalar multiplication/division, a map
method to transform the stream, and a SendableStream
, which would override all Stream functionality to also log the last output value of every transformation.
An example usage of streams (Log annotation is from oblog and camera is made up) looks like
@Log val xStream = vision::x.stream().log().movingAverage(5).map { MathUtil.clamp(it, -100, 100) }
In this example, log()
converts the stream into a SendableStream
, and movingAverage
is shorthand for creating a new LinearFilter (wpilib class) and then running .map { movingAvg.calculate(it) }
Networktables would display the initial x value, the filtered value, and the clamped filtered value, so that you could locate where something is going wrong if a bug arises.
Or an inefficient velocity pid implementation that I made for fun
val error = Stream { setpoint - encoder.velocity }
val p = kp * error
val i = ki * error.integrate().map { MathUtil.clamp(it, minI, maxI) }
val d = kd * error.differentiate()
val pid = p + i + d
Note that SendableFilter is untested
I’m really curious to hear anyone’s opinion on this feature
Future
In the future, there are several large features we want to implement. Specifically, units and a coroutine based command scheduler. Not too much thought has been put into the coroutine stuff yet, but I would imagine it would be beneficial since we plan to use pose estimation in the future, and that requires lengthy calculations. One other goal with the coroutines is to make sure that the resulting code is close to the wpilib project structure, if this even is possible. Units are a lot more self explanatory, and we will begin working on them soon, assuming we have extra time during build season.