001/* 002 * Copyright (C) Cross The Road Electronics. All rights reserved. 003 * License information can be found in CTRE_LICENSE.txt 004 * For support and suggestions contact support@ctr-electronics.com or file 005 * an issue tracker at https://github.com/CrossTheRoadElec/Phoenix-Releases 006 */ 007package com.ctre.phoenix6; 008 009import com.ctre.phoenix6.Timestamp.TimestampSource; 010import com.ctre.phoenix6.hardware.DeviceIdentifier; 011import com.ctre.phoenix6.jni.ErrorReportingJNI; 012import com.ctre.phoenix6.jni.StatusSignalJNI; 013 014/** 015 * Class that provides operations to retrieve 016 * information about a status signal. 017 */ 018public abstract class BaseStatusSignal { 019 protected DeviceIdentifier deviceIdentifier; 020 protected int spn; 021 protected String units; 022 protected StatusCode error = StatusCode.StatusCodeNotInitialized; 023 protected double baseValue = 0; 024 protected AllTimestamps timestamps = new AllTimestamps(); 025 protected final String signalName; 026 027 protected Runnable _reportIfOldFunc; 028 029 private double _lastTimestamp = 0.0; 030 031 protected StatusSignalJNI jni = new StatusSignalJNI(); 032 033 BaseStatusSignal(DeviceIdentifier deviceIdentifier, int spn, String signalName, Runnable reportIfOldFunc) { 034 this.deviceIdentifier = deviceIdentifier; 035 this.spn = spn; 036 this.signalName = signalName; 037 038 this._reportIfOldFunc = reportIfOldFunc; 039 040 jni.network = deviceIdentifier.getNetwork(); 041 jni.deviceHash = deviceIdentifier.getDeviceHash(); 042 jni.spn = spn; 043 044 this.units = jni.JNI_GetUnits(); 045 } 046 047 /* Constructor for an invalid BaseStatusSignal */ 048 BaseStatusSignal(StatusCode error) { 049 this(new DeviceIdentifier(), 0, "Invalid", () -> {}); 050 this.error = error; 051 } 052 053 protected void copyFrom(BaseStatusSignal other) { 054 this.units = other.units; 055 this.error = other.error; 056 this.baseValue = other.baseValue; 057 this.timestamps = other.timestamps; 058 // Don't copy the signal name since the user expects the original name 059 } 060 061 /** 062 * Implementation of the waitForAll API. 063 * 064 * @param location Location of the calling function 065 * @param timeoutSeconds Maximum time to wait for all the signals 066 * @param signals Signals to wait on 067 * @return Status of the wait 068 */ 069 private static StatusCode waitForAllImpl(String location, double timeoutSeconds, BaseStatusSignal... signals) { 070 if (signals.length < 1) { 071 /* We don't have any signals to wait for, so return early */ 072 ErrorReportingJNI.reportStatusCode(StatusCode.InvalidParamValue.value, location); 073 return StatusCode.InvalidParamValue; 074 } 075 076 String network = signals[0].deviceIdentifier.getNetwork(); 077 StatusSignalJNI[] toGet = new StatusSignalJNI[signals.length]; 078 for (int i = 0; i < signals.length; ++i) { 079 var sig = signals[i]; 080 if (i != 0 && !sig.deviceIdentifier.getNetwork().equals(network)) { 081 // Networks don't match, return early 082 ErrorReportingJNI.reportStatusCode(StatusCode.InvalidNetwork.value, location); 083 return StatusCode.InvalidNetwork; 084 } 085 toGet[i] = sig.jni; 086 } 087 088 /* Report if any device firmware versions are too old */ 089 for (var signal : signals) { 090 signal._reportIfOldFunc.run(); 091 } 092 093 /* Now wait for all the signals */ 094 int err = StatusSignalJNI.JNI_WaitForAll(network, timeoutSeconds, toGet); 095 for (int i = 0; i < signals.length; ++i) { 096 signals[i].error = StatusCode.valueOf(toGet[i].statusCode); 097 signals[i].baseValue = toGet[i].value; 098 signals[i].timestamps.update(toGet[i].swtimeStampSeconds, TimestampSource.System, true, 099 toGet[i].hwtimeStampSeconds, TimestampSource.CANivore, true, 100 toGet[i].ecutimeStampSeconds, TimestampSource.Device, toGet[i].ecutimeStampSeconds != 0.0); 101 } 102 103 /* error reporting */ 104 StatusCode retval = StatusCode.valueOf(err); 105 if (false == retval.isOK()) { 106 ErrorReportingJNI.reportStatusCode(retval.value, location); 107 } 108 return StatusCode.valueOf(err); 109 } 110 111 /** 112 * Waits for new data on all provided signals up to timeout. 113 * This API is typically used with CANivore Bus signals as they will be synced using the 114 * CANivore Timesync feature and arrive simultaneously. Signals on a roboRIO bus cannot 115 * be synced and may require a significantly longer blocking call to receive all signals. 116 * <p> 117 * Note that CANivore Timesync requires Phoenix Pro. 118 * <p> 119 * This can also be used with a timeout of zero to refresh many signals at once, which 120 * is faster than calling refresh() on every signal. This is equivalent to calling {@link #refreshAll}. 121 * 122 * @param timeoutSeconds Maximum time to wait for new data in seconds. 123 * Pass zero to refresh all signals without blocking. 124 * @param signals Signals to wait for new data against 125 * @return An InvalidParamValue if signals array is empty, 126 * InvalidNetwork if signals are on different CAN bus networks, 127 * RxTimeout if it took longer than timeoutSeconds to receive all the signals, 128 * MultiSignalNotSupported if using the roboRIO bus with more than one signal and a non-zero timeout. 129 * An OK status code means that all signals arrived within timeoutSeconds and they are all OK. 130 * 131 * Any other value represents the StatusCode of the first failed signal. 132 * Call getStatus() on each signal to determine which ones failed. 133 */ 134 public static StatusCode waitForAll(double timeoutSeconds, BaseStatusSignal... signals) { 135 return waitForAllImpl("ctre.phoenix6.BaseStatusSignal.waitForAll", timeoutSeconds, signals); 136 } 137 138 /** 139 * Performs a non-blocking refresh on all provided signals. 140 * <p> 141 * This provides a performance improvement over separately calling refresh() on each signal. 142 * 143 * @param signals Signals to refresh 144 * @return An InvalidParamValue if signals array is empty, 145 * InvalidNetwork if signals are on different CAN bus networks. 146 * An OK status code means that all signals are OK. 147 * 148 * Any other value represents the StatusCode of the first failed signal. 149 * Call getStatus() on each signal to determine which ones failed. 150 */ 151 public static StatusCode refreshAll(BaseStatusSignal... signals) { 152 return waitForAllImpl("ctre.phoenix6.BaseStatusSignal.refreshAll", 0, signals); 153 } 154 155 /** 156 * Performs latency compensation on signal using the signalSlope and signal's latency to determine 157 * the magnitude of compensation. The caller must refresh these StatusSignals beforehand; 158 * this function only does the math required for latency compensation. 159 * <p> 160 * <b>Important</b>: The signalSlope must be the rate of change of the signal. If it is not the latency 161 * compensation may not perform as expected. 162 * <p> 163 * This compensates for up to 300ms of latency by default. 164 * <p> 165 * <b>Example</b>: 166 * double compensatedTurns = BaseStatusSignal.getLatencyCompensatedValue(fx.getPosition(), fx.getVelocity()); 167 * 168 * @param signal Signal to be latency compensated. Caller must make sure this signal is up to date 169 * either by calling {@link StatusSignal#refresh()} or {@link StatusSignal#waitForUpdate(double)}. 170 * @param signalSlope Derivative of signal that informs compensation magnitude. Caller must make sure this 171 * signal is up to date either by calling {@link StatusSignal#refresh()} or 172 * {@link StatusSignal#waitForUpdate(double)}. 173 * @return Latency compensated value from the signal StatusSignal. 174 */ 175 public static double getLatencyCompensatedValue(StatusSignal<Double> signal, StatusSignal<Double> signalSlope) 176 { 177 return getLatencyCompensatedValue(signal, signalSlope, 0.300); 178 } 179 180 /** 181 * Performs latency compensation on signal using the signalSlope and signal's latency to determine 182 * the magnitude of compensation. The caller must refresh these StatusSignals beforehand; 183 * this function only does the math required for latency compensation. 184 * <p> 185 * <b>Important</b>: The signalSlope must be the rate of change of the signal. If it is not the latency 186 * compensation may not perform as expected. 187 * <p> 188 * <b>Example</b>: 189 * double compensatedTurns = BaseStatusSignal.getLatencyCompensatedValue(fx.getPosition(), fx.getVelocity()); 190 * 191 * @param signal Signal to be latency compensated. Caller must make sure this signal is up to date 192 * either by calling {@link StatusSignal#refresh()} or {@link StatusSignal#waitForUpdate(double)}. 193 * @param signalSlope Derivative of signal that informs compensation magnitude. Caller must make sure this 194 * signal is up to date either by calling {@link StatusSignal#refresh()} or 195 * {@link StatusSignal#waitForUpdate(double)}. 196 * @param maxLatencySeconds The maximum amount of latency to compensate for in seconds. A negative or zero 197 * value disables the max latency cap. This is used to cap the contribution of 198 * latency compensation for stale signals, such as after the device has been 199 * disconnected from the CAN bus. 200 * @return Latency compensated value from the signal StatusSignal. 201 */ 202 public static double getLatencyCompensatedValue(StatusSignal<Double> signal, StatusSignal<Double> signalSlope, double maxLatencySeconds) 203 { 204 final double nonCompensatedSignal = signal.getValue(); 205 final double changeInSignal = signalSlope.getValue(); 206 double latency = signal.getTimestamp().getLatency(); 207 if (maxLatencySeconds > 0.0 && latency > maxLatencySeconds) { 208 latency = maxLatencySeconds; 209 } 210 return nonCompensatedSignal + (changeInSignal * latency); 211 } 212 213 /** 214 * Checks if all signals have an OK error code. 215 * 216 * @param signals Signals to check error code of 217 * @return True if all are good, false otherwise 218 */ 219 public static boolean isAllGood(BaseStatusSignal... signals) { 220 for (BaseStatusSignal sig : signals) { 221 if (!sig.getStatus().isOK()) { 222 return false; 223 } 224 } 225 return true; 226 } 227 228 /** 229 * Sets the update frequency of all specified status signals to the provided common frequency. 230 * <p> 231 * A frequency of 0 Hz will turn off the signal. Otherwise, the minimum supported signal 232 * frequency is 4 Hz, and the maximum is 1000 Hz. 233 * <p> 234 * If other StatusSignals in the same status frame have been set to an update frequency, 235 * the fastest requested update frequency will be applied to the frame. 236 * <p> 237 * This will wait up to 0.050 seconds (50ms) for each signal. 238 * 239 * @param frequencyHz Rate to publish the signal in Hz. 240 * @param signals Signals to apply the update frequency to 241 * @return Status code of the first failed update frequency set call, or OK if all succeeded 242 */ 243 public static StatusCode setUpdateFrequencyForAll(double frequencyHz, BaseStatusSignal... signals) { 244 StatusSignalJNI[] toSet = new StatusSignalJNI[signals.length]; 245 for (int i = 0; i < signals.length; ++i) { 246 toSet[i] = signals[i].jni; 247 } 248 return StatusCode.valueOf(StatusSignalJNI.JNI_SetUpdateFrequencyForAll(frequencyHz, toSet, 0.050)); 249 } 250 251 /** 252 * Sets the rate at which the device will publish this signal. 253 * <p> 254 * A frequency of 0 Hz will turn off the signal. Otherwise, the minimum supported signal 255 * frequency is 4 Hz, and the maximum is 1000 Hz. 256 * <p> 257 * If other StatusSignals in the same status frame have been set to an update frequency, 258 * the fastest requested update frequency will be applied to the frame. 259 * <p> 260 * This will wait up to 0.050 seconds (50ms) by default. 261 * 262 * @param frequencyHz Rate to publish the signal in Hz. 263 * @return Status code of setting the update frequency 264 */ 265 public StatusCode setUpdateFrequency(double frequencyHz) 266 { 267 return setUpdateFrequency(frequencyHz, 0.050); 268 } 269 270 /** 271 * Sets the rate at which the device will publish this signal. 272 * <p> 273 * A frequency of 0 Hz will turn off the signal. Otherwise, the minimum supported signal 274 * frequency is 4 Hz, and the maximum is 1000 Hz. 275 * <p> 276 * If other StatusSignals in the same status frame have been set to an update frequency, 277 * the fastest requested update frequency will be applied to the frame. 278 * 279 * @param frequencyHz Rate to publish the signal in Hz. 280 * @param timeoutSeconds Maximum amount of time to wait when performing the action 281 * @return Status code of setting the update frequency 282 */ 283 public abstract StatusCode setUpdateFrequency(double frequencyHz, double timeoutSeconds); 284 285 /** 286 * Gets the rate at which the device will publish this signal. 287 * <p> 288 * This is typically the last value passed into {@link #setUpdateFrequency}. The returned value 289 * may be higher if another StatusSignal in the same status frame has been set to a higher 290 * update frequency. 291 * 292 * @return Applied update frequency of the signal in Hz 293 */ 294 public abstract double getAppliedUpdateFrequency(); 295 296 /** 297 * Gets the name of this signal 298 * 299 * @return Name of this signal 300 */ 301 public String getName() { 302 return signalName; 303 } 304 305 /** 306 * Gets the units for this signal 307 * 308 * @return String representation of units for this signal 309 */ 310 public String getUnits() { 311 return units; 312 } 313 314 /** 315 * Gets the value of this signal as a double 316 * 317 * @return Value of this signal as a double instead of the generic type 318 */ 319 public double getValueAsDouble() { 320 return baseValue; 321 } 322 323 /** 324 * Get all timestamps relevant for this signal 325 * 326 * @return All timestamps available for this signal 327 */ 328 public AllTimestamps getAllTimestamps() { 329 return timestamps; 330 } 331 332 /** 333 * Get the best available timestamp for this signal 334 * 335 * @return Best available timestamp 336 */ 337 public Timestamp getTimestamp() { 338 return timestamps.getBestTimestamp(); 339 } 340 341 /** 342 * Get the error code from when we last received this signal 343 * 344 * @return Last cached Error Code 345 */ 346 public StatusCode getStatus() { 347 return error; 348 } 349 350 /** 351 * Check whether the signal has been updated since the last check. 352 * <p> 353 * Note that the signal must be refreshed before calling this routine. 354 * 355 * @return true if the signal has updated since the previous call of this routine 356 */ 357 public boolean hasUpdated() 358 { 359 boolean retval = false; 360 /* did we receive an update */ 361 final var timestamp = getAllTimestamps().getSystemTimestamp(); 362 if (timestamp.isValid()) { 363 /* if the update timestamp is new, then a new frame was sent */ 364 if (_lastTimestamp != timestamp.getTime()) { 365 _lastTimestamp = timestamp.getTime(); 366 retval = true; 367 } 368 } 369 return retval; 370 } 371};