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