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