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