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}