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}