Status Signals#

Signals represent live data reported by a device; these can be yaw, position, etc. To make use of the live data, users need to know the value, timestamp, latency, units, and error condition of the data. Additionally, users may need to synchronize with fresh data to minimize latency.

Tip

A Signal Logging API is available for logging received signals. This can be useful for any form of post analysis, including diagnosing issues after a match or using WPILib SysID.

StatusSignal#

The StatusSignal (Java, C++, Python) is a signal object that provides APIs to address all of the requirements listed above.

The device object provides getters for all available signals. Each getter returns a StatusSignal that is typed appropriately for the signal.

Note

The device getters return a cached StatusSignal. As a result, frequently calling the getter does not influence RAM performance.

var supplyVoltageSignal = m_device.getSupplyVoltage();
auto& supplyVoltageSignal = m_device.GetSupplyVoltage();
supply_voltage_signal = self.device.get_supply_voltage()

The value of the signal can be retrieved from the StatusSignal by calling getValue().

var supplyVoltage = supplyVoltageSignal.getValue();
auto supplyVoltage = supplyVoltageSignal.GetValue();
supply_voltage = supply_voltage_signal.value

Note

Phoenix 6 utilizes the C++ units library when applicable.

The StatusCode (Java, C++, Python) of the signal can be retrieved by calling getStatus(). This can be used to determine if the device is not present on the CAN bus.

Note

If a status signal is not available on the CAN bus, an error will be reported to the Driver Station.

Refreshing the Signal Value#

The device StatusSignal getters implicitly refresh the cached signal values. However, if the user application caches the StatusSignal object, the refresh() method must be called to fetch fresh data. Multiple signals can be refreshed in one call using BaseStatusSignal.refreshAll() (Java, C++, Python).

Tip

The refresh() method can be method-chained. As a result, you can call refresh() and getValue() on one line.

// refresh the supply voltage signal
supplyVoltageSignal.refresh();
// refresh the position and velocity signals
BaseStatusSignal.refreshAll(positionSignal, velocitySignal);
// refresh the supply voltage signal
supplyVoltageSignal.Refresh();
// refresh the position and velocity signals
BaseStatusSignal::RefreshAll(positionSignal, velocitySignal);
# refresh the supply voltage signal
supply_voltage_signal.refresh()
# refresh the position and velocity signals
BaseStatusSignal.refresh_all(position_signal, velocity_signal)

Waiting for Signal Updates#

Instead of using the latest value, the user can instead opt to synchronously wait for a signal update. StatusSignal provides a waitForUpdate(timeoutSec) method that will block the current robot loop until the signal is retrieved or the timeout has been exceeded. This replaces the need to call refresh() on cached StatusSignal objects.

Tip

If you want to zero your sensors, you can use this API to ensure the set operation has completed before continuing program flow.

Tip

The waitForUpdate() method can be method-chained. As a result, you can call waitForUpdate() and getValue() on one line.

// wait up to 1 robot loop iteration (20ms) for fresh data
supplyVoltageSignal.waitForUpdate(0.020);
// wait up to 1 robot loop iteration (20ms) for fresh data
supplyVoltageSignal.WaitForUpdate(20_ms);
# wait up to 1 robot loop iteration (20ms) for fresh data
supply_voltage_signal.wait_for_update(0.020)

Changing Update Frequency#

All signals can have their update frequency configured via the setUpdateFrequency() method. Additionally, the update frequency of multiple signals can be specified at once using BaseStatusSignal.setUpdateFrequencyForAll() (Java, C++, Python).

Warning

Increasing signal frequency will also increase CAN bus utilization, which can cause indeterminate behavior at high utilization rates (>90%). This is less of a concern when using CANivore, which uses the higher-bandwidth CAN FD bus.

// disable supply voltage reporting (0 Hz)
supplyVoltageSignal.setUpdateFrequency(0);
// speed up position and velocity reporting to 200 Hz
BaseStatusSignal.setUpdateFrequencyForAll(200, positionSignal, velocitySignal);
// disable supply voltage reporting (0 Hz)
supplyVoltageSignal.SetUpdateFrequency(0_Hz);
// speed up position and velocity reporting to 200 Hz
BaseStatusSignal::SetUpdateFrequencyForAll(200_Hz, positionSignal, velocitySignal);
# disable supply voltage reporting (0 Hz)
supply_voltage_signal.set_update_frequency(0)
# speed up position and velocity reporting to 200 Hz
BaseStatusSignal.set_update_frequency_for_all(200, position_signal, velocity_signal)

When different update frequencies are specified for signals that share a status frame, the highest update frequency of all the relevant signals will be applied to the entire frame. Users can get a signal’s applied update frequency using the getAppliedUpdateFrequency() method.

Signal update frequencies are automatically reapplied by the robot program on device reset.

Optimizing Bus Utilization#

For users that wish to disable every unused status signal for their devices to reduce bus utilization, device objects have an optimizeBusUtilization() method (Java, C++, Python). Additionally, multiple devices can be optimized at once using ParentDevice.optimizeBusUtilizationForAll() (Java, C++, Python).

When optimizing the bus utilization for devices, all status signals that have not been given an update frequency using setUpdateFrequency() will be disabled. This results in an opt-in model for status signals, maximizing the reduction in bus utilization.

m_pigeon.optimizeBusUtilization();
ParentDevice.optimizeBusUtilizationForAll(m_leftMotor, m_rightMotor, m_cancoder);
m_pigeon.OptimizeBusUtilization();
hardware::ParentDevice::OptimizeBusUtilizationForAll(m_leftMotor, m_rightMotor, m_cancoder);
self.pigeon.optimize_bus_utilization()
hardware.ParentDevice.optimize_bus_utilization_for_all(self.left_motor, self.right_motor, self.cancoder)

Timestamps#

The timestamps of a StatusSignal can be retrieved by calling getAllTimestamps(), which returns a collection of Timestamp (Java, C++, Python) objects. The Timestamp objects can be used to perform latency compensation math.

CANivore Timesync#

Important

CANivore Timesync requires the devices or the CANivore to be Pro licensed.

When using CANivore, the attached CAN devices will automatically synchronize their time bases. This allows devices to sample and publish their signals in a synchronized manner.

Users can synchronously wait for these signals to update using BaseStatusSignal.waitForAll() (Java, C++, Python).

Tip

waitForAll() with a timeout of zero matches the behavior of refreshAll(), performing a non-blocking refresh on all signals passed in.

Because the devices are synchronized, time-critical signals are sampled and published on the same schedule. This combined with the waitForAll() routine means applications can considerably reduce the latency of the timesync signals. This is particularly useful for multi-device mechanisms, such as swerve odometry.

Note

When using a non-zero timeout, the signals passed into waitForAll() should have the same update frequency for synchronous data acquisition. This can be done by calling setUpdateFrequency() or by referring to the API documentation.

The diagram below demonstrates the benefits of using timesync to synchronously acquire signals from multiple devices.

Diagram of timesync operation

Check the API documentation for information on whether a status signal supports CANivore Timesync.

var talonFXPositionSignal = m_talonFX.getPosition();
var cancoderPositionSignal = m_cancoder.getPosition();
var pigeon2YawSignal = m_pigeon2.getYaw();

BaseStatusSignal.waitForAll(0.020, talonFXPositionSignal, cancoderPositionSignal, pigeon2YawSignal);
auto& talonFXPositionSignal = m_talonFX.GetPosition();
auto& cancoderPositionSignal = m_cancoder.GetPosition();
auto& pigeon2YawSignal = m_pigeon2.GetYaw();

BaseStatusSignal::WaitForAll(20_ms, talonFXPositionSignal, cancoderPositionSignal, pigeon2YawSignal);
talonfx_position_signal = self.talonfx.get_position()
cancoder_position_signal = self.cancoder.get_position()
pigeon2_yaw_signal = self.pigeon2.get_yaw()

BaseStatusSignal.wait_for_all(0.020, talonfx_position_signal, cancoder_position_signal, pigeon2_yaw_signal)

Latency Compensation#

Users can perform latency compensation using BaseStatusSignal.getLatencyCompensatedValue() (Java, C++, Python).

Important

getLatencyCompensatedValue() does not automatically refresh the signals. As a result, the user must ensure the signal and signalSlope parameters are refreshed before retrieving a compensated value.

double compensatedTurns = BaseStatusSignal.getLatencyCompensatedValue(m_motor.getPosition(), m_motor.getVelocity());
auto compensatedTurns = BaseStatusSignal::GetLatencyCompensatedValue(m_motor.GetPosition(), m_motor.GetVelocity());
compensated_turns = BaseStatusSignal.get_latency_compensated_value(self.motor.get_position(), self.motor.get_velocity())

SignalMeasurement#

All StatusSignal objects have a getDataCopy() method that returns a new SignalMeasurement (Java, C++) object. SignalMeasurement is a Passive Data Structure that provides all the information about a signal at the time of the getDataCopy() call, which can be useful for data logging.

Warning

getDataCopy() returns a new SignalMeasurement object every call. Java users should avoid using this API in RAM-constrained applications.