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.lang.reflect.InvocationTargetException; 010import java.util.HashMap; 011import java.util.Map; 012import java.util.function.Supplier; 013 014import com.ctre.phoenix6.Timestamp.TimestampSource; 015import com.ctre.phoenix6.hardware.DeviceIdentifier; 016import com.ctre.phoenix6.hardware.ParentDevice.MapGenerator; 017import com.ctre.phoenix6.jni.ErrorReportingJNI; 018 019/** 020 * Represents a status signal with data of type T, and 021 * operations available to retrieve information about 022 * the signal. 023 */ 024public class StatusSignal<T> extends BaseStatusSignal implements Cloneable { 025 private Class<T> classOfSignal; 026 private boolean _containsUnderlyingTypes; 027 private Map<Integer, StatusSignal<T>> _basicTypeMap; 028 029 public StatusSignal(DeviceIdentifier deviceIdentifier, int spn, Runnable reportIfOldFunc, 030 Class<T> classOfSignal, String signalName) { 031 super(deviceIdentifier, spn, signalName, reportIfOldFunc); 032 this.classOfSignal = classOfSignal; 033 _containsUnderlyingTypes = false; 034 _basicTypeMap = null; 035 } 036 037 public StatusSignal(DeviceIdentifier deviceIdentifier, int spn, Runnable reportIfOldFunc, 038 Class<T> classOfSignal, 039 MapGenerator<T> generator, String signalName) { 040 super(deviceIdentifier, spn, signalName, reportIfOldFunc); 041 this.classOfSignal = classOfSignal; 042 _containsUnderlyingTypes = true; 043 _basicTypeMap = generator.run(); 044 } 045 046 /* Constructor for an invalid StatusSignal */ 047 public StatusSignal(Class<T> classOfSignal, StatusCode error) { 048 super(error); 049 this.classOfSignal = classOfSignal; 050 _containsUnderlyingTypes = false; 051 _basicTypeMap = null; 052 } 053 054 /** 055 * Returns a lambda that calls {@link #refresh} and {@link #getValue} on this object. 056 * This is useful for command-based programming. 057 * 058 * @return Supplier that refreshes this signal and returns it 059 */ 060 public Supplier<T> asSupplier() { 061 return () -> { 062 return refresh().getValue(); 063 }; 064 } 065 066 /** 067 * Information from a single measurement of a status signal. 068 */ 069 public static class SignalMeasurement<L> { 070 /** 071 * The value of the signal, this may be an enum so it is stored as a string 072 */ 073 public L value; 074 /** 075 * Timestamp of when the data point was taken 076 */ 077 public double timestamp; 078 /** 079 * Status code response of getting the data 080 */ 081 public StatusCode status; 082 /** 083 * Units that correspond to this point 084 */ 085 public String units; 086 }; 087 088 @Override 089 public String toString() { 090 if (getValue() != null && units != null) 091 return getValue().toString() + " " + units; 092 return "Invalid signal"; 093 } 094 095 @Override 096 public StatusSignal<T> clone() { 097 try { 098 @SuppressWarnings("unchecked") 099 StatusSignal<T> toReturn = StatusSignal.class.cast(super.clone()); 100 toReturn.timestamps = timestamps.clone(); 101 toReturn.jni = jni.clone(); 102 103 if (_basicTypeMap != null) { 104 toReturn._basicTypeMap = new HashMap<>(); 105 for (var entry : _basicTypeMap.entrySet()) { 106 toReturn._basicTypeMap.put(entry.getKey(), entry.getValue().clone()); 107 } 108 } 109 return toReturn; 110 } catch (CloneNotSupportedException ex) { 111 /* this should never happen */ 112 return new StatusSignal<T>(classOfSignal, StatusCode.InvalidParamValue); 113 } 114 } 115 116 public Class<T> getTypeClass() { 117 return classOfSignal; 118 } 119 120 /** 121 * Gets the cached value from this status signal. 122 * <p> 123 * Gets the cached value. To make sure the value is up-to-date call 124 * {@link #refresh()} or {@link #waitForUpdate(double)} 125 * 126 * @return Cached value 127 */ 128 public T getValue() { 129 if (classOfSignal.equals(Double.class)) { 130 return classOfSignal.cast(baseValue); 131 } else if (classOfSignal.equals(Integer.class)) { 132 return classOfSignal.cast((int) baseValue); 133 } else if (classOfSignal.equals(Boolean.class)) { 134 return classOfSignal.cast(baseValue != 0 ? true : false); 135 } else if (classOfSignal.isEnum()) { 136 try { 137 /* This is an enum, so it contains a valueOf class method that we can invoke instead */ 138 return classOfSignal.cast(classOfSignal.getMethod("valueOf", Integer.TYPE).invoke(null, (int)baseValue)); 139 } catch (IllegalAccessException excep) { 140 /* valueOf is not accessible */ 141 error = StatusCode.CouldNotCast; 142 } catch (IllegalArgumentException excep) { 143 /* Invalid valueOf argument */ 144 error = StatusCode.CouldNotCast; 145 } catch (InvocationTargetException excep) { 146 /* Could not invoke valueOf on this enum */ 147 error = StatusCode.CouldNotCast; 148 } catch (NoSuchMethodException excep) { 149 /* valueOf with parameter of int is not available for this enum */ 150 error = StatusCode.CouldNotCast; 151 } catch (ClassCastException excep) { 152 /* The valueOf return didn't match the class type that we need to return */ 153 error = StatusCode.CouldNotCast; 154 } 155 } else { 156 /* Try to cast it, I guess */ 157 try { 158 return classOfSignal.cast(baseValue); 159 } catch (ClassCastException excep) { 160 /* Cast failed, do something I guess */ 161 error = StatusCode.CouldNotCast; 162 } 163 } 164 return null; 165 } 166 167 private void refreshMappable(boolean waitForSignal, double timeout) { 168 if (!_containsUnderlyingTypes) 169 return; 170 if (waitForSignal) { 171 error = StatusCode.valueOf(jni.JNI_WaitForSignal(timeout)); 172 } else { 173 error = StatusCode.valueOf(jni.JNI_RefreshSignal(timeout)); 174 } 175 176 if (_basicTypeMap.containsKey((int) jni.value)) { 177 StatusSignal<T> gottenValue = _basicTypeMap.get((int) jni.value); 178 gottenValue.updateValue(waitForSignal, timeout, false); 179 /* no lock needed, pointer and primitive copies are atomic */ 180 copyFrom(gottenValue); 181 } 182 } 183 184 private void refreshNonmappable(boolean waitForSignal, double timeout) { 185 if (_containsUnderlyingTypes) 186 return; 187 if (waitForSignal) { 188 error = StatusCode.valueOf(jni.JNI_WaitForSignal(timeout)); 189 } else { 190 error = StatusCode.valueOf(jni.JNI_RefreshSignal(timeout)); 191 } 192 if (error.isError()) { // don't update on an error 193 return; 194 } 195 baseValue = jni.value; 196 timestamps.update(jni.swtimeStampSeconds, TimestampSource.System, true, 197 jni.hwtimeStampSeconds, TimestampSource.CANivore, true, 198 jni.ecutimeStampSeconds, TimestampSource.Device, jni.ecutimeStampSeconds != 0.0); 199 } 200 201 private void updateValue(boolean waitForSignal, double timeout, boolean reportError) { 202 _reportIfOldFunc.run(); 203 if (_containsUnderlyingTypes) { 204 refreshMappable(waitForSignal, timeout); 205 } else { 206 refreshNonmappable(waitForSignal, timeout); 207 } 208 if (reportError && !this.error.isOK()) { 209 String device = this.deviceIdentifier.toString() + " Status Signal " + this.signalName; 210 ErrorReportingJNI.reportStatusCode(this.error.value, device); 211 } 212 } 213 214 /** 215 * Refreshes the value of this status signal. 216 * <p> 217 * If the user application caches this StatusSignal object 218 * instead of periodically fetching it from the hardware device, 219 * this function must be called to fetch fresh data. 220 * <p> 221 * This performs a non-blocking refresh operation. If you want to wait until you 222 * receive the signal, call {@link #waitForUpdate(double)} instead. 223 * 224 * @param reportError Whether to report any errors to the Driver Station/stderr. 225 * Defaults true 226 * 227 * @return Reference to itself 228 */ 229 public StatusSignal<T> refresh(boolean reportError) { 230 updateValue(false, 0, reportError); // Don't block and error if signal is older than a default timeout 231 return this; 232 } 233 234 /** 235 * Refreshes the value of this status signal. 236 * <p> 237 * If the user application caches this StatusSignal object 238 * instead of periodically fetching it from the hardware device, 239 * this function must be called to fetch fresh data. 240 * <p> 241 * This performs a non-blocking refresh operation. If you want to wait until you 242 * receive the signal, call {@link #waitForUpdate(double)} instead. 243 * 244 * @return Reference to itself 245 */ 246 public StatusSignal<T> refresh() { 247 return refresh(true); 248 } 249 250 /** 251 * Waits up to timeoutSec to get the up-to-date status signal value. 252 * <p> 253 * This performs a blocking refresh operation. If you want to non-blocking 254 * refresh the signal, call {@link #refresh()} instead. 255 * 256 * @param timeoutSec Maximum time to wait for the signal to update 257 * @param reportError Whether to report any errors to the Driver Station/stderr. 258 * Defaults true 259 * 260 * @return Reference to itself 261 */ 262 public StatusSignal<T> waitForUpdate(double timeoutSec, boolean reportError) { 263 updateValue(true, timeoutSec, reportError); 264 return this; 265 } 266 267 /** 268 * Waits up to timeoutSec to get the up-to-date status signal value. 269 * <p> 270 * This performs a blocking refresh operation. If you want to non-blocking 271 * refresh the signal, call {@link #refresh()} instead. 272 * 273 * @param timeoutSec Maximum time to wait for the signal to update 274 * 275 * @return Reference to itself 276 */ 277 public StatusSignal<T> waitForUpdate(double timeoutSec) { 278 return waitForUpdate(timeoutSec, true); 279 } 280 281 /** 282 * Sets the rate at which the device will publish this signal. 283 * <p> 284 * A frequency of 0 Hz will turn off the signal. Otherwise, the minimum supported signal 285 * frequency is 4 Hz, and the maximum is 1000 Hz. 286 * <p> 287 * If other StatusSignals in the same status frame have been set to an update frequency, 288 * the fastest requested update frequency will be applied to the frame. 289 * 290 * @param frequencyHz Rate to publish the signal in Hz. 291 * @param timeoutSeconds Maximum amount of time to wait when performing the action 292 * @return Status code of setting the update frequency 293 */ 294 @Override 295 public StatusCode setUpdateFrequency(double frequencyHz, double timeoutSeconds) { 296 if (_containsUnderlyingTypes) { 297 return _basicTypeMap.values().iterator().next().setUpdateFrequency(frequencyHz, timeoutSeconds); 298 } else { 299 return StatusCode.valueOf(jni.JNI_SetUpdateFrequency(frequencyHz, timeoutSeconds)); 300 } 301 } 302 303 /** 304 * Gets the rate at which the device will publish this signal. 305 * <p> 306 * This is typically the last value passed into {@link #setUpdateFrequency}. The returned value 307 * may be higher if another StatusSignal in the same status frame has been set to a higher 308 * update frequency. 309 * 310 * @return Applied update frequency of the signal in Hz 311 */ 312 @Override 313 public double getAppliedUpdateFrequency() { 314 if (_containsUnderlyingTypes) { 315 return _basicTypeMap.values().iterator().next().getAppliedUpdateFrequency(); 316 } else { 317 return jni.JNI_GetAppliedUpdateFrequency(); 318 } 319 } 320 321 /** 322 * Get a basic data-only container with this information, to be used for things 323 * such as data logging. 324 * <p> 325 * Note if looking for Phoenix 6 logging features, see the {@link SignalLogger} 326 * class instead. 327 * <p> 328 * This function returns a new object every call. As a result, we recommend that 329 * this is not called inside a tight loop. 330 * 331 * @return Basic structure with all relevant information 332 */ 333 public SignalMeasurement<T> getDataCopy() { 334 SignalMeasurement<T> toRet = new SignalMeasurement<T>(); 335 toRet.value = getValue(); 336 toRet.status = getStatus(); 337 toRet.units = getUnits(); 338 toRet.timestamp = getTimestamp().getTime(); 339 return toRet; 340 } 341}