3.2 - Tips for CTRE Status Signals
When using CTRE devices over a CAN network, methods such as getVoltage() or
getPosition() don’t just return the value, but instead return
StatusSignals that wrap the internal value.
StatusSignals can be cached, refreshed, and they also carry data about
how the request went (i.e. success or failure).
Basic Status Signals
A StatusSignal is just a class that represents the live data reported by a
device. They carry data about the value, but also latency, timestamp, units,
and any errors that were encountered, making them more versatile than just a
DoubleSupplier.
A CTRE device has many getXXX() methods which all return
StatusSignals. You can then use these status signals to query the data
that you’re looking for.
For example, we can get the position of a motor like this:
StatusSignal<Angle> positionSignal = motor.getPosition();
Angle position = positionSignal.getValue();
However, in times when performance is critical and we want to ensure that our
code is as efficient as possible, this code has a few issues. Most importantly,
the method motor.getPosition() not only retrieves a StatusSignal
object, but it also automatically refreshes it. This isn’t a problem, per say,
but it does leave some room for a bit of improvement. It turns out that
updating each StatusSignal you use in your robot program individually is
far less performant than updating them all at once. I can’t really tell you
why; feel free to investigate yourself.
The takeaway here is that the robot program shouldn’t call
motor.getPosition() each loop, because it refreshes the status signal each
time the method is called. You can instead pass an optional boolean value
into the getXXX() method, which enables or disables the automatic refresh
behavior:
StatusSignal<Angle> positionSignal = motor.getPosition(false);
// Now we have to update the signal ourselves:
positionSignal.refresh();
Angle position = positionSignal.getValue();
The advantage here is hard to see, but it is more clear if you’re familiar with
the BaseStatusSignal class. That class provides a method refreshAll()
which updates a group of status signals, all at once.
Because of how unintuitive the boolean argument is to the getXXX()
method, sometime you will also see robot programs simply cache the
StatusSignal object at the program initialization and keep calling
BaseStatusSignal.refrehAll() on the same objects.
Note
There is no performance difference between calling
motor.getXXX(false) and caching the output; the method getXXX(false)
simply returns a cached object anyways.
Updating Status Signals
There are two main ways to update a cached StatusSignal value:
Calling
refreshon a single instance updates its value.Calling
BaseStatusSignal.refreshAll(StatusSignal[] signals)updates all signals in the array. All the status signals’ associated devices must be on the same CAN network.
Although the first option is simpler, it can be better to use refreshAll()
because it’s more performant. Although we can group status signals by
subsystem, we get better performance if all status signals on one bus are
grouped together. Therefore, we need some global helper class to update all
signals.
Status Signal Helper
Here’s what a basic status signal class may look like that has two CAN buses.
package frc.robot.util;
import java.util.ArrayList;
import java.util.List;
import com.ctre.phoenix6.BaseStatusSignal;
import com.ctre.phoenix6.StatusSignal;
/**
* A class with helper static methods to register status signals. This class lets all status signals
* update at once, instead of each and every time they're read individually, bettering performance.
*/
@SuppressWarnings("rawtypes")
public class StatusSignalUtil {
private static List<BaseStatusSignal> rioSignals = new ArrayList<>();
private static List<BaseStatusSignal> canivoreSignals = new ArrayList<>();
public static void registerRioSignals(StatusSignal... signals) {
for (StatusSignal signal : signals) {
rioSignals.add(signal);
}
}
public static void registerCANivoreSignals(StatusSignal... signals) {
for (StatusSignal signal : signals) {
canivoreSignals.add(signal);
}
}
public static void refreshAll() {
if (!rioSignals.isEmpty()) {
BaseStatusSignal.refreshAll(rioSignals);
}
if (!canivoreSignals.isEmpty()) {
BaseStatusSignal.refreshAll(canivoreSignals);
}
}
}
Subsystems can register all StatusSignals that they want refreshed, and
then StatusSignalUtil.refreshAll() can be called every periodic loop in
robotPeriodic() in Robot.java.