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}