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.sim;
008
009import static edu.wpi.first.units.Units.*;
010
011import com.ctre.phoenix6.StatusCode;
012import com.ctre.phoenix6.hardware.core.CoreTalonFXS;
013import com.ctre.phoenix6.hardware.TalonFXS;
014import com.ctre.phoenix6.jni.PlatformJNI;
015
016import edu.wpi.first.units.measure.*;
017
018/**
019 * Class to control the state of a simulated {@link TalonFXS}.
020 * <p>
021 * For simulated PID control and current limits to behave correctly,
022 * {@link #setRawRotorPosition} and {@link #setRotorVelocity} must be updated
023 * periodically. This is typically done by sending the motor output from
024 * {@link #getMotorVoltage()} or {@link #getTorqueCurrent()} to a physics
025 * simulator (such as WPILib's {@code DCMotorSim}), which then calculates the
026 * new motor position and velocity.
027 */
028public class TalonFXSSimState {
029    private static final DeviceType kDevType = DeviceType.P6_TalonFXSType;
030
031    private final int _id;
032
033    /**
034     * The orientation of the motor attached to the TalonFXS relative
035     * to the robot chassis. This include the Commutation sensor source.
036     * <p>
037     * This value should not be changed based on the TalonFXS invert.
038     * Rather, this value should be changed when the mechanical linkage
039     * between the motor and the robot changes.
040     */
041    public ChassisReference MotorOrientation;
042
043    /**
044     * The orientation of an external sensor attached to the TalonFXS
045     * relative to the robot chassis. This does NOT include the Commutation
046     * sensor source.
047     * <p>
048     * This value should not be changed based on the TalonFXS invert.
049     * Rather, this value should be changed when the mechanical linkage
050     * between the external sensor and the robot changes.
051     */
052    public ChassisReference ExtSensorOrientation;
053    /**
054     * The offset of an absolute external sensor attached to the Talon FXS 
055     * relative to the robot chassis, in rotations. This offset is subtracted
056     * from the Pulse Width position, allowing for a non-zero sensor offset
057     * config to behave correctly in simulation.
058     * <p>
059     * This value should not be changed after initialization unless the
060     * mechanical linkage between the external sensor and the robot changes.
061     */
062    public double PulseWidthSensorOffset = 0.0;
063    /**
064     * The number of quadrature edges per sensor rotation for an external
065     * quadrature sensor attached to the TalonFXS.
066     */
067    public int QuadratureEdgesPerRotation = 4096;
068
069    /**
070     * Creates an object to control the state of the given {@link TalonFXS}.
071     * <p>
072     * This constructor defaults to a counter-clockwise positive motor and
073     * external sensor orientation relative to the robot chassis.
074     * <p>
075     * Note the recommended method of accessing simulation features is to use
076     * {@link TalonFXS#getSimState}.
077     *
078     * @param device Device to which this simulation state is attached
079     */
080    public TalonFXSSimState(CoreTalonFXS device) {
081        this(
082            device,
083            ChassisReference.CounterClockwise_Positive,
084            ChassisReference.CounterClockwise_Positive
085        );
086    }
087    /**
088     * Creates an object to control the state of the given {@link TalonFXS}.
089     * <p>
090     * Note the recommended method of accessing simulation features is to use
091     * {@link TalonFXS#getSimState}.
092     *
093     * @param device Device to which this simulation state is attached
094     * @param motorOrientation Orientation of the motor (and commutation sensor) relative to the robot chassis
095     * @param extSensorOrientation Orientation of the external sensor relative to the robot chassis
096     */
097    public TalonFXSSimState(
098        CoreTalonFXS device,
099        ChassisReference motorOrientation,
100        ChassisReference extSensorOrientation
101    ) {
102        _id = device.getDeviceID();
103        MotorOrientation = motorOrientation;
104        ExtSensorOrientation = extSensorOrientation;
105    }
106
107    /**
108     * Gets the last status code generated by a simulation function.
109     * <p>
110     * Not all functions return a status code but can potentially report errors.
111     * This function can be used to retrieve those status codes.
112     *
113     * @return Last status code generated by a simulation function
114     */
115    public final StatusCode getLastStatusCode() {
116        return StatusCode.valueOf(PlatformJNI.JNI_SimGetLastError(kDevType.value, _id));
117    }
118
119    /**
120     * Gets the simulated output voltage of the motor.
121     *
122     * @return Voltage applied to the motor in Volts
123     */
124    public final double getMotorVoltage() {
125        double value = PlatformJNI.JNI_SimGetPhysicsValue(kDevType.value, _id, "MotorVoltage");
126        if (MotorOrientation == ChassisReference.Clockwise_Positive) {
127            value = -value;
128        }
129        return value;
130    }
131    /**
132     * Gets the simulated output voltage of the motor as a unit type.
133     *
134     * @return Voltage applied to the motor
135     */
136    public final Voltage getMotorVoltageMeasure() {
137        return Volts.of(getMotorVoltage());
138    }
139
140    /**
141     * Gets the simulated output torque current of the motor.
142     * <p>
143     * Phoenix 6 simulation automatically calculates current.
144     *
145     * @return Torque current applied to the motor in Amperes
146     */
147    public final double getTorqueCurrent() {
148        double value = PlatformJNI.JNI_SimGetPhysicsValue(kDevType.value, _id, "TorqueCurrent");
149        if (MotorOrientation == ChassisReference.Clockwise_Positive) {
150            value = -value;
151        }
152        return value;
153    }
154    /**
155     * Gets the simulated output torque current of the motor as a unit type.
156     * <p>
157     * Phoenix 6 simulation automatically calculates current.
158     *
159     * @return Torque current applied to the motor
160     */
161    public final Current getTorqueCurrentMeasure() {
162        return Amps.of(getTorqueCurrent());
163    }
164
165    /**
166     * Gets the simulated supply current of the TalonFXS.
167     * <p>
168     * Phoenix 6 simulation automatically calculates current.
169     *
170     * @return Supply current of the TalonFXS in Amperes
171     */
172    public final double getSupplyCurrent() {
173        double value = PlatformJNI.JNI_SimGetPhysicsValue(kDevType.value, _id, "SupplyCurrent");
174        return value;
175    }
176    /**
177     * Gets the simulated supply current of the TalonFXS as a unit type.
178     * <p>
179     * Phoenix 6 simulation automatically calculates current.
180     *
181     * @return Supply current of the TalonFXS
182     */
183    public final Current getSupplyCurrentMeasure() {
184        return Amps.of(getSupplyCurrent());
185    }
186
187    /**
188     * Gets the simulated analog voltage of the TalonFXS.
189     * 
190     * @return Voltage of the simulated analog input pin on the TalonFXS.
191     */
192    public final double getAnalogVoltage() {
193        double value = PlatformJNI.JNI_SimGetPhysicsValue(kDevType.value, _id, "AnalogVoltage");
194        return value;
195    }
196
197    /**
198     * Gets the simulated analog voltage of the TalonFXS.
199     * 
200     * @return Voltage of the simulated analog input pin on the TalonFXS.
201     */
202    public final Voltage getAnalogVoltageMeasure() {
203        return Volts.of(getAnalogVoltage());
204    }
205
206    /**
207     * Sets the simulated supply voltage of the TalonFXS.
208     * <p>
209     * The minimum allowed supply voltage is 4 V - values below this
210     * will be promoted to 4 V.
211     *
212     * @param volts The supply voltage in Volts
213     * @return Status code
214     */
215    public final StatusCode setSupplyVoltage(double volts) {
216        return StatusCode.valueOf(PlatformJNI.JNI_SimSetPhysicsInput(kDevType.value, _id, "SupplyVoltage", volts));
217    }
218    /**
219     * Sets the simulated supply voltage of the TalonFXS.
220     * <p>
221     * The minimum allowed supply voltage is 4 V - values below this
222     * will be promoted to 4 V.
223     *
224     * @param voltage The supply voltage
225     * @return Status code
226     */
227    public final StatusCode setSupplyVoltage(Voltage voltage) {
228        return setSupplyVoltage(voltage.in(Volts));
229    }
230
231    /**
232     * Sets the simulated forward limit switch of the TalonFXS.
233     *
234     * @param closed Whether the limit switch is closed
235     * @return Status code
236     */
237    public final StatusCode setForwardLimit(boolean closed) {
238        return StatusCode.valueOf(PlatformJNI.JNI_SimSetPhysicsInput(kDevType.value, _id, "ForwardLimit", closed ? 1 : 0));
239    }
240    /**
241     * Sets the simulated reverse limit switch of the TalonFXS.
242     *
243     * @param closed Whether the limit switch is closed
244     * @return Status code
245     */
246    public final StatusCode setReverseLimit(boolean closed) {
247        return StatusCode.valueOf(PlatformJNI.JNI_SimSetPhysicsInput(kDevType.value, _id, "ReverseLimit", closed ? 1 : 0));
248    }
249
250    /**
251     * Sets the simulated raw rotor position of the TalonFXS. This is the position
252     * of the rotor (before gear ratio) used for the Commutation feedback source.
253     * <p>
254     * Inputs to this function over time should be continuous, as user calls of {@link TalonFXS#setPosition} will be accounted for in the callee.
255     * <p>
256     * The TalonFXS integrates this to calculate the true reported rotor position.
257     * <p>
258     * When using the WPI Sim GUI, you will notice a readonly {@code position} and settable {@code rawPositionInput}.
259     * The readonly signal is the emulated position which will match self-test in Tuner and the hardware API.
260     * Changes to {@code rawPositionInput} will be integrated into the emulated position.
261     * This way a simulator can modify the position without overriding hardware API calls for home-ing the sensor.
262     *
263     * @param rotations The raw position in rotations
264     * @return Status code
265     */
266    public final StatusCode setRawRotorPosition(double rotations) {
267        if (MotorOrientation == ChassisReference.Clockwise_Positive) {
268            rotations = -rotations;
269        }
270        return StatusCode.valueOf(PlatformJNI.JNI_SimSetPhysicsInput(kDevType.value, _id, "RawRotorPosition", rotations));
271    }
272    /**
273     * Sets the simulated raw rotor position of the TalonFXS. This is the position
274     * of the rotor (before gear ratio) used for the Commutation feedback source.
275     * <p>
276     * Inputs to this function over time should be continuous, as user calls of {@link TalonFXS#setPosition} will be accounted for in the callee.
277     * <p>
278     * The TalonFXS integrates this to calculate the true reported rotor position.
279     * <p>
280     * When using the WPI Sim GUI, you will notice a readonly {@code position} and settable {@code rawPositionInput}.
281     * The readonly signal is the emulated position which will match self-test in Tuner and the hardware API.
282     * Changes to {@code rawPositionInput} will be integrated into the emulated position.
283     * This way a simulator can modify the position without overriding hardware API calls for home-ing the sensor.
284     *
285     * @param position The raw position
286     * @return Status code
287     */
288    public final StatusCode setRawRotorPosition(Angle position) {
289        return setRawRotorPosition(position.in(Rotations));
290    }
291
292    /**
293     * Sets the simulated voltage of the analog input pin on the TalonFXS data port.
294     * 
295     * @param voltage Voltage of the pin
296     * @return Status code
297     */
298    public final StatusCode setAnalogVoltage(double voltage) {
299        return StatusCode.valueOf(PlatformJNI.JNI_SimSetPhysicsInput(kDevType.value, _id, "AnalogVoltage", voltage));
300    }
301
302    /**
303     * Sets the simulated voltage of the analog input pin on the TalonFXS data port.
304     * 
305     * @param voltage Voltage of the pin
306     * @return Status code
307     */
308    public final StatusCode setAnalogVoltage(Voltage voltage) {
309        return setAnalogVoltage(voltage.in(Volts));
310    }
311
312    /**
313     * Adds to the simulated rotor position of the TalonFXS. This adds to the position
314     * of the rotor (before gear ratio) used for the Commutation feedback source.
315     *
316     * @param dRotations The change in position in rotations
317     * @return Status code
318     */
319    public final StatusCode addRotorPosition(double dRotations) {
320        if (MotorOrientation == ChassisReference.Clockwise_Positive) {
321            dRotations = -dRotations;
322        }
323        return StatusCode.valueOf(PlatformJNI.JNI_SimSetPhysicsInput(kDevType.value, _id, "AddRotorPosition", dRotations));
324    }
325    /**
326     * Adds to the simulated rotor position of the TalonFXS. This adds to the position
327     * of the rotor (before gear ratio) used for the Commutation feedback source.
328     *
329     * @param dPosition The change in position
330     * @return Status code
331     */
332    public final StatusCode addRotorPosition(Angle dPosition) {
333        return addRotorPosition(dPosition.in(Rotations));
334    }
335
336    /**
337     * Sets the simulated rotor velocity of the TalonFXS. This is the velocity
338     * of the rotor (before gear ratio) used for the Commutation feedback source.
339     *
340     * @param rps The new velocity in rotations per second
341     * @return Status code
342     */
343    public final StatusCode setRotorVelocity(double rps) {
344        if (MotorOrientation == ChassisReference.Clockwise_Positive) {
345            rps = -rps;
346        }
347        return StatusCode.valueOf(PlatformJNI.JNI_SimSetPhysicsInput(kDevType.value, _id, "RotorVelocity", rps));
348    }
349    /**
350     * Sets the simulated rotor velocity of the TalonFXS. This is the velocity
351     * of the rotor (before gear ratio) used for the Commutation feedback source.
352     *
353     * @param velocity The new velocity
354     * @return Status code
355     */
356    public final StatusCode setRotorVelocity(AngularVelocity velocity) {
357        return setRotorVelocity(velocity.in(RotationsPerSecond));
358    }
359
360    /**
361     * Sets the simulated rotor acceleration of the TalonFXS. This is the acceleration
362     * of the rotor (before gear ratio) used for the Commutation feedback source.
363     *
364     * @param rpss The new acceleration in rotations per second²
365     * @return Status code
366     */
367    public final StatusCode setRotorAcceleration(double rpss) {
368        if (MotorOrientation == ChassisReference.Clockwise_Positive) {
369            rpss = -rpss;
370        }
371        return StatusCode.valueOf(PlatformJNI.JNI_SimSetPhysicsInput(kDevType.value, _id, "RotorAcceleration", rpss));
372    }
373    /**
374     * Sets the simulated rotor acceleration of the TalonFXS. This is the acceleration
375     * of the rotor (before gear ratio) used for the Commutation feedback source.
376     *
377     * @param acceleration The new acceleration
378     * @return Status code
379     */
380    public final StatusCode setRotorAcceleration(AngularAcceleration acceleration) {
381        return setRotorAcceleration(acceleration.in(RotationsPerSecondPerSecond));
382    }
383
384    /**
385     * Sets the simulated raw quadrature position of the TalonFXS. This is the position
386     * of an external quadrature encoder after any gear ratio between the rotor and the sensor.
387     * <p>
388     * Inputs to this function over time should be continuous, as user calls of {@link TalonFXS#setPosition} will be accounted for in the callee.
389     * <p>
390     * The TalonFXS integrates this to calculate the true reported quadrature position.
391     * <p>
392     * When using the WPI Sim GUI, you will notice a readonly {@code position} and settable {@code rawPositionInput}.
393     * The readonly signal is the emulated position which will match self-test in Tuner and the hardware API.
394     * Changes to {@code rawPositionInput} will be integrated into the emulated position.
395     * This way a simulator can modify the position without overriding hardware API calls for home-ing the sensor.
396     *
397     * @param rotations The raw position in rotations
398     * @return Status code
399     */
400    public final StatusCode setRawQuadraturePosition(double rotations) {
401        if (ExtSensorOrientation == ChassisReference.Clockwise_Positive) {
402            rotations = -rotations;
403        }
404        return StatusCode.valueOf(
405            PlatformJNI.JNI_SimSetPhysicsInput(kDevType.value, _id, "RawQuadraturePosition", rotations * QuadratureEdgesPerRotation)
406        );
407    }
408    /**
409     * Sets the simulated raw quadrature position of the TalonFXS. This is the position
410     * of an external quadrature encoder after any gear ratio between the rotor and the sensor.
411     * <p>
412     * Inputs to this function over time should be continuous, as user calls of {@link TalonFXS#setPosition} will be accounted for in the callee.
413     * <p>
414     * The TalonFXS integrates this to calculate the true reported quadrature position.
415     * <p>
416     * When using the WPI Sim GUI, you will notice a readonly {@code position} and settable {@code rawPositionInput}.
417     * The readonly signal is the emulated position which will match self-test in Tuner and the hardware API.
418     * Changes to {@code rawPositionInput} will be integrated into the emulated position.
419     * This way a simulator can modify the position without overriding hardware API calls for home-ing the sensor.
420     *
421     * @param position The raw position
422     * @return Status code
423     */
424    public final StatusCode setRawQuadraturePosition(Angle position) {
425        return setRawQuadraturePosition(position.in(Rotations));
426    }
427
428    /**
429     * Adds to the simulated quadrature position of the TalonFXS. This adds to the position
430     * of an external quadrature encoder after any gear ratio between the rotor and the sensor.
431     *
432     * @param dRotations The change in position in rotations
433     * @return Status code
434     */
435    public final StatusCode addQuadraturePosition(double dRotations) {
436        if (ExtSensorOrientation == ChassisReference.Clockwise_Positive) {
437            dRotations = -dRotations;
438        }
439        return StatusCode.valueOf(
440            PlatformJNI.JNI_SimSetPhysicsInput(kDevType.value, _id, "AddQuadraturePosition", dRotations * QuadratureEdgesPerRotation)
441        );
442    }
443    /**
444     * Adds to the simulated quadrature position of the TalonFXS. This adds to the position
445     * of an external quadrature encoder after any gear ratio between the rotor and the sensor.
446     *
447     * @param dPosition The change in position
448     * @return Status code
449     */
450    public final StatusCode addQuadraturePosition(Angle dPosition) {
451        return addQuadraturePosition(dPosition.in(Rotations));
452    }
453
454    /**
455     * Sets the simulated quadrature velocity of the TalonFXS. This is the velocity
456     * of an external quadrature encoder after any gear ratio between the rotor and the sensor.
457     *
458     * @param rps The new velocity in rotations per second
459     * @return Status code
460     */
461    public final StatusCode setQuadratureVelocity(double rps) {
462        if (ExtSensorOrientation == ChassisReference.Clockwise_Positive) {
463            rps = -rps;
464        }
465        return StatusCode.valueOf(
466            PlatformJNI.JNI_SimSetPhysicsInput(kDevType.value, _id, "QuadratureVelocity", rps * QuadratureEdgesPerRotation)
467        );
468    }
469    /**
470     * Sets the simulated quadrature velocity of the TalonFXS. This is the velocity
471     * of an external quadrature encoder after any gear ratio between the rotor and the sensor.
472     *
473     * @param velocity The new velocity
474     * @return Status code
475     */
476    public final StatusCode setQuadratureVelocity(AngularVelocity velocity) {
477        return setQuadratureVelocity(velocity.in(RotationsPerSecond));
478    }
479
480    /**
481     * Sets the simulated quadrature acceleration of the TalonFXS. This is the acceleration
482     * of an external quadrature encoder after any gear ratio between the rotor and the sensor.
483     *
484     * @param rpss The new acceleration in rotations per second²
485     * @return Status code
486     */
487    public final StatusCode setQuadratureAcceleration(double rpss) {
488        if (ExtSensorOrientation == ChassisReference.Clockwise_Positive) {
489            rpss = -rpss;
490        }
491        return StatusCode.valueOf(
492            PlatformJNI.JNI_SimSetPhysicsInput(kDevType.value, _id, "QuadratureAcceleration", rpss * QuadratureEdgesPerRotation)
493        );
494    }
495    /**
496     * Sets the simulated quadrature acceleration of the TalonFXS. This is the acceleration
497     * of an external quadrature encoder after any gear ratio between the rotor and the sensor.
498     *
499     * @param acceleration The new acceleration
500     * @return Status code
501     */
502    public final StatusCode setQuadratureAcceleration(AngularAcceleration acceleration) {
503        return setQuadratureAcceleration(acceleration.in(RotationsPerSecondPerSecond));
504    }
505
506    /**
507     * Sets the simulated pulse width position of the TalonFXS. This is the position
508     * of an external PWM encoder after any gear ratio between the rotor and the sensor.
509     *
510     * @param rotations The new position in rotations
511     * @return Status code
512     */
513    public final StatusCode setPulseWidthPosition(double rotations) {
514        rotations -= PulseWidthSensorOffset;
515        if (ExtSensorOrientation == ChassisReference.Clockwise_Positive) {
516            rotations = -rotations;
517        }
518        return StatusCode.valueOf(PlatformJNI.JNI_SimSetPhysicsInput(kDevType.value, _id, "PulseWidthPosition", rotations));
519    }
520    /**
521     * Sets the simulated pulse width position of the TalonFXS. This is the position
522     * of an external PWM encoder after any gear ratio between the rotor and the sensor.
523     *
524     * @param position The new position
525     * @return Status code
526     */
527    public final StatusCode setPulseWidthPosition(Angle position) {
528        return setPulseWidthPosition(position.in(Rotations));
529    }
530
531    /**
532     * Sets the simulated pulse width velocity of the TalonFXS. This is the position
533     * of an external PWM encoder after any gear ratio between the rotor and the sensor.
534     *
535     * @param rps The new velocity in rotations per second
536     * @return Status code
537     */
538    public final StatusCode setPulseWidthVelocity(double rps) {
539        if (ExtSensorOrientation == ChassisReference.Clockwise_Positive) {
540            rps = -rps;
541        }
542        return StatusCode.valueOf(PlatformJNI.JNI_SimSetPhysicsInput(kDevType.value, _id, "PulseWidthVelocity", rps));
543    }
544    /**
545     * Sets the simulated pulse width velocity of the TalonFXS. This is the position
546     * of an external PWM encoder after any gear ratio between the rotor and the sensor.
547     *
548     * @param velocity The new velocity
549     * @return Status code
550     */
551    public final StatusCode setPulseWidthVelocity(AngularVelocity velocity) {
552        return setPulseWidthVelocity(velocity.in(RotationsPerSecond));
553    }
554}