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 java.util.function.DoubleFunction; 010import java.util.function.Supplier; 011 012import com.ctre.phoenix6.hardware.DeviceIdentifier; 013import com.ctre.phoenix6.hardware.ParentDevice.MapGenerator; 014 015import edu.wpi.first.units.Measure; 016 017/** 018 * Represents a status signal with data of type T, and 019 * operations available to retrieve information about 020 * the signal. 021 */ 022public class StatusSignal<T> extends BaseStatusSignal implements Cloneable { 023 private final Class<T> classOfSignal; 024 private final DoubleFunction<T> toValue; 025 026 public StatusSignal( 027 DeviceIdentifier deviceIdentifier, int spn, String signalName, 028 Runnable reportIfOldFunc, Class<T> classOfSignal, DoubleFunction<T> toValue 029 ) { 030 super(deviceIdentifier, spn, signalName, reportIfOldFunc); 031 this.classOfSignal = classOfSignal; 032 this.toValue = toValue; 033 } 034 035 public StatusSignal( 036 DeviceIdentifier deviceIdentifier, int spn, String signalName, 037 Runnable reportIfOldFunc, MapGenerator unitsGenerator, 038 Class<T> classOfSignal, DoubleFunction<T> toValue 039 ) { 040 super(deviceIdentifier, spn, signalName, reportIfOldFunc, unitsGenerator); 041 this.classOfSignal = classOfSignal; 042 this.toValue = toValue; 043 } 044 045 /* Constructor for an invalid StatusSignal */ 046 public StatusSignal(StatusCode error, Class<T> classOfSignal, DoubleFunction<T> toValue) { 047 super(error); 048 this.classOfSignal = classOfSignal; 049 this.toValue = toValue; 050 } 051 052 public final Class<T> getTypeClass() { 053 return classOfSignal; 054 } 055 056 @Override 057 public StatusSignal<T> clone() { 058 try { 059 @SuppressWarnings("unchecked") 060 StatusSignal<T> toReturn = StatusSignal.class.cast(super.clone()); 061 toReturn.timestamps = timestamps.clone(); 062 toReturn.jni = jni.clone(); 063 return toReturn; 064 } catch (CloneNotSupportedException ex) { 065 /* this should never happen */ 066 return new StatusSignal<T>(StatusCode.InvalidParamValue, classOfSignal, toValue); 067 } 068 } 069 070 /** 071 * Gets the cached value from this status signal. 072 * <p> 073 * Gets the cached value. To make sure the value is up-to-date call 074 * {@link #refresh()} or {@link #waitForUpdate(double)} 075 * 076 * @return Cached value 077 */ 078 public final T getValue() { 079 return toValue.apply(baseValue); 080 } 081 082 /** 083 * Refreshes the value of this status signal. 084 * <p> 085 * If the user application caches this StatusSignal object 086 * instead of periodically fetching it from the hardware device, 087 * this function must be called to fetch fresh data. 088 * <p> 089 * This performs a non-blocking refresh operation. If you want to wait until you 090 * receive the signal, call {@link #waitForUpdate(double)} instead. 091 * 092 * @return Reference to itself 093 */ 094 public final StatusSignal<T> refresh() { 095 return refresh(true); 096 } 097 098 /** 099 * Refreshes the value of this status signal. 100 * <p> 101 * If the user application caches this StatusSignal object 102 * instead of periodically fetching it from the hardware device, 103 * this function must be called to fetch fresh data. 104 * <p> 105 * This performs a non-blocking refresh operation. If you want to wait until you 106 * receive the signal, call {@link #waitForUpdate(double)} instead. 107 * 108 * @param reportError Whether to report any errors to the Driver Station/stderr, 109 * defaults to true 110 * @return Reference to itself 111 */ 112 public final StatusSignal<T> refresh(boolean reportError) { 113 refreshValue(false, 0, reportError); // Don't block and error if signal is older than a default timeout 114 return this; 115 } 116 117 /** 118 * Waits up to timeoutSec to get the up-to-date status signal value. 119 * <p> 120 * This performs a blocking refresh operation. If you want to non-blocking 121 * refresh the signal, call {@link #refresh()} instead. 122 * 123 * @param timeoutSec Maximum time to wait for the signal to update 124 * @return Reference to itself 125 */ 126 public final StatusSignal<T> waitForUpdate(double timeoutSec) { 127 return waitForUpdate(timeoutSec, true); 128 } 129 130 /** 131 * Waits up to timeoutSec to get the up-to-date status signal value. 132 * <p> 133 * This performs a blocking refresh operation. If you want to non-blocking 134 * refresh the signal, call {@link #refresh()} instead. 135 * 136 * @param timeoutSec Maximum time to wait for the signal to update 137 * @param reportError Whether to report any errors to the Driver Station/stderr, 138 * defaults to true 139 * @return Reference to itself 140 */ 141 public final StatusSignal<T> waitForUpdate(double timeoutSec, boolean reportError) { 142 refreshValue(true, timeoutSec, reportError); 143 return this; 144 } 145 146 /** 147 * Checks whether the signal is near a target value within the 148 * given tolerance. 149 * 150 * @param target The target value of the signal 151 * @param tolerance The error tolerance between the target and measured values 152 * @return Whether the signal is near the target value 153 */ 154 public final boolean isNear(double target, double tolerance) { 155 return Math.abs(getValueAsDouble() - target) <= tolerance; 156 } 157 158 /** 159 * Checks whether the signal is near a target value within the 160 * given tolerance. This signal must be a numeric or unit type. 161 * 162 * @param target The target value of the signal 163 * @param tolerance The error tolerance between the target and measured values 164 * @return Whether the signal is near the target value 165 */ 166 public final boolean isNear(T target, T tolerance) { 167 var value = getValue(); 168 if (value == null || classOfSignal.isEnum()) { 169 return false; 170 } else if (value instanceof Measure<?> meas) { 171 return Math.abs(meas.baseUnitMagnitude() - ((Measure<?>)target).baseUnitMagnitude()) <= 172 Math.abs(((Measure<?>)tolerance).baseUnitMagnitude()); 173 } else { 174 return Math.abs((double)value - (double)target) <= (double)tolerance; 175 } 176 } 177 178 /** 179 * Returns a lambda that calls {@link #refresh} and {@link #getValue} on this object. 180 * This is useful for command-based programming. 181 * 182 * @return Supplier that refreshes this signal and returns it 183 */ 184 public final Supplier<T> asSupplier() { 185 return () -> refresh().getValue(); 186 } 187 188 @Override 189 public String toString() { 190 var value = getValue(); 191 if (value == null || units == null) { 192 return "Invalid signal"; 193 } else if (value instanceof Measure) { 194 return getValueAsDouble() + " " + units; 195 } else { 196 return value.toString() + " " + units; 197 } 198 } 199 200 /** 201 * Information from a single measurement of a status signal. 202 */ 203 public static class SignalMeasurement<V> { 204 /** 205 * The name of the signal 206 */ 207 public String name; 208 /** 209 * The value of the signal 210 */ 211 public V value; 212 /** 213 * Timestamp of when the data point was taken, in seconds 214 */ 215 public double timestamp; 216 /** 217 * The units of the signal measurement 218 */ 219 public String units; 220 /** 221 * Status code response of getting the data 222 */ 223 public StatusCode status; 224 }; 225 226 /** 227 * Get a basic data-only container with a copy of the current signal data. 228 * <p> 229 * If looking for Phoenix 6 logging features, see the {@link SignalLogger} 230 * class instead. 231 * <p> 232 * This function returns a new object every call. As a result, we recommend that 233 * this is not called inside a tight loop. 234 * 235 * @return Basic structure with all relevant information 236 */ 237 public final SignalMeasurement<T> getDataCopy() { 238 SignalMeasurement<T> toRet = new SignalMeasurement<T>(); 239 toRet.name = getName(); 240 toRet.value = getValue(); 241 toRet.timestamp = getTimestamp().getTime(); 242 toRet.units = getUnits(); 243 toRet.status = getStatus(); 244 return toRet; 245 } 246}