001/**
002 * WPI Compliant motor controller class.
003 * WPILIB's object model requires many interfaces to be implemented to use
004 * the various features.
005 * This includes...
006 * - Software PID loops running in the robot controller
007 * - LiveWindow/Test mode features
008 * - Motor Safety (auto-turn off of motor if Set stops getting called)
009 * - Single Parameter set that assumes a simple motor controller.
010 */
011package com.ctre.phoenix.motorcontrol.can;
012
013import com.ctre.phoenix.motorcontrol.ControlMode;
014import com.ctre.phoenix.motorcontrol.DemandType;
015import com.ctre.phoenix.motorcontrol.WPI_AutoFeedEnable;
016import com.ctre.phoenix.motorcontrol.WPI_MotorSafetyImplem;
017import com.ctre.phoenix.platform.DeviceType;
018import com.ctre.phoenix.platform.PlatformJNI;
019import com.ctre.phoenix.Logger;
020import com.ctre.phoenix.WPI_CallbackHelper;
021import com.ctre.phoenix.ErrorCode;
022
023import java.util.ArrayList;
024
025import edu.wpi.first.wpilibj.RobotController;
026import edu.wpi.first.wpilibj.motorcontrol.MotorController;
027import edu.wpi.first.wpilibj.simulation.CallbackStore;
028import edu.wpi.first.wpilibj.simulation.SimDeviceSim;
029import edu.wpi.first.util.sendable.SendableRegistry;
030import edu.wpi.first.wpilibj.DriverStation;
031import edu.wpi.first.util.sendable.Sendable;
032import edu.wpi.first.util.sendable.SendableBuilder;
033import edu.wpi.first.hal.FRCNetComm.tResourceType;
034import edu.wpi.first.hal.SimDevice.Direction;
035import edu.wpi.first.hal.simulation.SimDeviceDataJNI;
036import edu.wpi.first.hal.simulation.SimValueCallback;
037import edu.wpi.first.hal.simulation.SimulatorJNI;
038import edu.wpi.first.hal.HAL.SimPeriodicBeforeCallback;
039import edu.wpi.first.hal.HAL;
040import edu.wpi.first.hal.HALValue;
041import edu.wpi.first.hal.SimDevice;
042import edu.wpi.first.hal.SimDouble;
043import edu.wpi.first.hal.SimBoolean;
044
045/**
046 * CTRE Talon FX Motor Controller when used on CAN Bus.
047 */
048public class WPI_TalonFX extends TalonFX implements MotorController, Sendable, AutoCloseable {
049
050        private String _description;
051        private double _speed;
052
053        private SimDevice m_simMotor;
054        private SimDouble m_simPercOut;
055        private SimDouble m_simMotorOutputLeadVoltage;
056        private SimDouble m_simSupplyCurrent;
057        private SimDouble m_simMotorCurrent;
058        private SimDouble m_simVbat;
059
060        private SimDevice m_simIntegSens;
061        private SimDouble m_simIntegSensPos;
062        private SimDouble m_simIntegSensAbsPos;
063        private SimDouble m_simIntegSensRawPos;
064        private SimDouble m_simIntegSensVel;
065
066        private SimDevice m_simFwdLim;
067        private SimBoolean m_simFwdLimInit;
068        private SimBoolean m_simFwdLimInput;
069        private SimBoolean m_simFwdLimValue;
070
071        private SimDevice m_simRevLim;
072        private SimBoolean m_simRevLimInit;
073        private SimBoolean m_simRevLimInput;
074        private SimBoolean m_simRevLimValue;
075
076        // callbacks to register
077        private SimValueCallback onValueChangedCallback = new OnValueChangedCallback();
078        private Runnable onPeriodicCallback = new OnPeriodicCallback();
079
080        // returned registered callbacks
081        private ArrayList<CallbackStore> simValueChangedCallbacks = new ArrayList<CallbackStore>();
082        private SimPeriodicBeforeCallback simPeriodicCallback;
083
084        /**
085         * The default motor safety timeout IF calling application
086         * enables the feature.
087         */
088        public static final double kDefaultSafetyExpiration = 0.1;
089
090        /**
091         * Late-constructed motor safety, which ensures feature is off unless calling
092         * applications explicitly enables it.
093         */
094        private WPI_MotorSafetyImplem _motorSafety = null;
095        private final Object _lockMotorSaf = new Object();
096        private double _motSafeExpiration = kDefaultSafetyExpiration;
097
098        /**
099         * Constructor for motor controller
100         * @param deviceNumber device ID of motor controller
101         * @param canbus Name of the CANbus; can be a CANivore device name or serial number.
102         *               Pass in nothing or "rio" to use the roboRIO.
103         */
104        public WPI_TalonFX(int deviceNumber, String canbus) {
105                super(deviceNumber, canbus);
106                _description = "Talon FX " + deviceNumber;
107
108                SendableRegistry.addLW(this, "Talon FX ", deviceNumber);
109
110                m_simMotor = SimDevice.create("CANMotor:Talon FX", deviceNumber);
111                if(m_simMotor != null){
112                        WPI_AutoFeedEnable.getInstance();
113                        simPeriodicCallback = HAL.registerSimPeriodicBeforeCallback(onPeriodicCallback);
114
115                        m_simPercOut = m_simMotor.createDouble("percentOutput", Direction.kOutput, 0);
116                        m_simMotorOutputLeadVoltage = m_simMotor.createDouble("motorOutputLeadVoltage", Direction.kOutput, 0);
117
118                        m_simSupplyCurrent = m_simMotor.createDouble("supplyCurrent", Direction.kInput, 0);
119                        m_simMotorCurrent = m_simMotor.createDouble("motorCurrent", Direction.kInput, 0);
120                        m_simVbat = m_simMotor.createDouble("busVoltage", Direction.kInput, 12.0);
121
122                        SimDeviceSim sim = new SimDeviceSim("CANMotor:Talon FX");
123                        simValueChangedCallbacks.add(sim.registerValueChangedCallback(m_simSupplyCurrent, onValueChangedCallback, true));
124                        simValueChangedCallbacks.add(sim.registerValueChangedCallback(m_simMotorCurrent, onValueChangedCallback, true));
125                        simValueChangedCallbacks.add(sim.registerValueChangedCallback(m_simVbat, onValueChangedCallback, true));
126                }
127
128                String base = "Talon FX[" + deviceNumber + "]/";
129                m_simIntegSens = SimDevice.create("CANEncoder:" + base + "Integrated Sensor");
130                if(m_simIntegSens != null){
131                        m_simIntegSensPos = m_simIntegSens.createDouble("position", Direction.kOutput, 0);
132                        m_simIntegSensAbsPos = m_simIntegSens.createDouble("absolutePosition", Direction.kOutput, 0);
133
134                        m_simIntegSensRawPos = m_simIntegSens.createDouble("rawPositionInput", Direction.kInput, 0);
135                        m_simIntegSensVel = m_simIntegSens.createDouble("velocity", Direction.kInput, 0);
136
137                        SimDeviceSim sim = new SimDeviceSim("CANEncoder:" + base + "Integrated Sensor");
138                        simValueChangedCallbacks.add(sim.registerValueChangedCallback(m_simIntegSensRawPos, onValueChangedCallback, true));
139                        simValueChangedCallbacks.add(sim.registerValueChangedCallback(m_simIntegSensVel, onValueChangedCallback, true));
140                }
141
142                m_simFwdLim = SimDevice.create("CANDIO:" + base + "Fwd Limit");
143                if(m_simFwdLim != null){
144                        m_simFwdLimInit = m_simFwdLim.createBoolean("init", Direction.kOutput, true);
145                        m_simFwdLimInput = m_simFwdLim.createBoolean("input", Direction.kOutput, true);
146
147                        m_simFwdLimValue = m_simFwdLim.createBoolean("value", Direction.kBidir, false);
148
149                        SimDeviceSim sim = new SimDeviceSim("CANDIO:" + base + "Fwd Limit");
150                        simValueChangedCallbacks.add(sim.registerValueChangedCallback(m_simFwdLimValue, onValueChangedCallback, true));
151                }
152
153                m_simRevLim = SimDevice.create("CANDIO:" + base + "Rev Limit");
154                if(m_simRevLim != null){
155                        m_simRevLimInit = m_simRevLim.createBoolean("init", Direction.kOutput, true);
156                        m_simRevLimInput = m_simRevLim.createBoolean("input", Direction.kOutput, true);
157
158                        m_simRevLimValue = m_simRevLim.createBoolean("value", Direction.kBidir, false);
159
160                        SimDeviceSim sim = new SimDeviceSim("CANDIO:" + base + "Rev Limit");
161                        simValueChangedCallbacks.add(sim.registerValueChangedCallback(m_simRevLimValue, onValueChangedCallback, true));
162                }
163        }
164
165        /**
166         * Constructor for motor controller
167         * @param deviceNumber device ID of motor controller
168         */
169        public WPI_TalonFX(int deviceNumber) {
170                this(deviceNumber, "");
171        }
172
173    // ----- Auto-Closable ----- //
174    @Override
175    public void close(){
176        SendableRegistry.remove(this);
177        if(m_simMotor != null) {
178            m_simMotor.close();
179            m_simMotor = null;
180        }
181        if(m_simIntegSens != null) {
182            m_simIntegSens.close();
183            m_simIntegSens = null;
184        }
185        if(m_simFwdLim != null) {
186            m_simFwdLim.close();
187            m_simFwdLim = null;
188        }
189        if(m_simRevLim != null) {
190            m_simRevLim.close();
191            m_simRevLim = null;
192        }
193        super.DestroyObject(); //Destroy the device
194    }
195
196        // ----- Callbacks for Sim ----- //
197        private class OnValueChangedCallback implements SimValueCallback {
198                @Override
199                public void callback(String name, int handle, int direction, HALValue value) {
200                        String deviceName = SimDeviceDataJNI.getSimDeviceName(SimDeviceDataJNI.getSimValueDeviceHandle(handle));
201                        String physType = deviceName + ":" + name;
202                        PlatformJNI.JNI_SimSetPhysicsInput(DeviceType.TalonFX.value, getDeviceID(),
203                                                                               physType, WPI_CallbackHelper.getRawValue(value));
204                }
205        }
206
207        private class OnPeriodicCallback implements Runnable {
208                @Override
209                public void run() {
210                        double value = 0;
211                        int err = 0;
212
213                        int deviceID = getDeviceID();
214
215                        value = PlatformJNI.JNI_SimGetPhysicsValue(DeviceType.TalonFX.value, deviceID, "PercentOutput");
216                        err = PlatformJNI.JNI_SimGetLastError(DeviceType.TalonFX.value, deviceID);
217                        if (err == 0) {
218                                m_simPercOut.set(value);
219                        }
220                        value = PlatformJNI.JNI_SimGetPhysicsValue(DeviceType.TalonFX.value, deviceID, "MotorOutputLeadVoltage");
221                        err = PlatformJNI.JNI_SimGetLastError(DeviceType.TalonFX.value, deviceID);
222                        if (err == 0) {
223                                m_simMotorOutputLeadVoltage.set(value);
224                        }
225                        value = PlatformJNI.JNI_SimGetPhysicsValue(DeviceType.TalonFX.value, deviceID, "BusVoltage");
226                        err = PlatformJNI.JNI_SimGetLastError(DeviceType.TalonFX.value, deviceID);
227                        if (err == 0) {
228                                m_simVbat.set(value);
229                        }
230                        value = PlatformJNI.JNI_SimGetPhysicsValue(DeviceType.TalonFX.value, deviceID, "CurrentSupply");
231                        err = PlatformJNI.JNI_SimGetLastError(DeviceType.TalonFX.value, deviceID);
232                        if (err == 0) {
233                                m_simSupplyCurrent.set(value);
234                        }
235                        value = PlatformJNI.JNI_SimGetPhysicsValue(DeviceType.TalonFX.value, deviceID, "CurrentStator");
236                        err = PlatformJNI.JNI_SimGetLastError(DeviceType.TalonFX.value, deviceID);
237                        if (err == 0) {
238                                m_simMotorCurrent.set(value);
239                        }
240                        value = PlatformJNI.JNI_SimGetPhysicsValue(DeviceType.TalonFX.value, deviceID, "IntegSensPos");
241                        err = PlatformJNI.JNI_SimGetLastError(DeviceType.TalonFX.value, deviceID);
242                        if (err == 0) {
243                                m_simIntegSensPos.set(value);
244                        }
245                        value = PlatformJNI.JNI_SimGetPhysicsValue(DeviceType.TalonFX.value, deviceID, "IntegSensAbsPos");
246                        err = PlatformJNI.JNI_SimGetLastError(DeviceType.TalonFX.value, deviceID);
247                        if (err == 0) {
248                                m_simIntegSensAbsPos.set(value);
249                        }
250                        value = PlatformJNI.JNI_SimGetPhysicsValue(DeviceType.TalonFX.value, deviceID, "IntegSensRawPos");
251                        err = PlatformJNI.JNI_SimGetLastError(DeviceType.TalonFX.value, deviceID);
252                        if (err == 0) {
253                                m_simIntegSensRawPos.set(value);
254                        }
255                        value = PlatformJNI.JNI_SimGetPhysicsValue(DeviceType.TalonFX.value, deviceID, "IntegSensVel");
256                        err = PlatformJNI.JNI_SimGetLastError(DeviceType.TalonFX.value, deviceID);
257                        if (err == 0) {
258                                m_simIntegSensVel.set(value);
259                        }
260                        value = PlatformJNI.JNI_SimGetPhysicsValue(DeviceType.TalonFX.value, deviceID, "LimitFwd");
261                        err = PlatformJNI.JNI_SimGetLastError(DeviceType.TalonFX.value, deviceID);
262                        if (err == 0) {
263                                m_simFwdLimValue.set((int)value != 0);
264                        }
265                        value = PlatformJNI.JNI_SimGetPhysicsValue(DeviceType.TalonFX.value, deviceID, "LimitRev");
266                        err = PlatformJNI.JNI_SimGetLastError(DeviceType.TalonFX.value, deviceID);
267                        if (err == 0) {
268                                m_simRevLimValue.set((int)value != 0);
269                        }
270                }
271        }
272
273        // ------ set/get routines for WPILIB interfaces ------//
274        /**
275         * Common interface for setting the speed of a simple speed controller.
276         *
277         * @param speed The speed to set.  Value should be between -1.0 and 1.0.
278         *                                                                      Value is also saved for Get().
279         */
280        @Override
281        public void set(double speed) {
282                _speed = speed;
283                set(ControlMode.PercentOutput, _speed);
284                feed();
285        }
286
287        /**
288         * Common interface for getting the current set speed of a speed controller.
289         *
290         * @return The current set speed. Value is between -1.0 and 1.0.
291         */
292        @Override
293        public double get() {
294                return _speed;
295        }
296
297        // ---------Intercept CTRE calls for motor safety ---------//
298        /**
299         * Sets the appropriate output on the talon, depending on the mode.
300         * @param mode The output mode to apply.
301         * In PercentOutput, the output is between -1.0 and 1.0, with 0.0 as stopped.
302         * In Current mode, output value is in amperes.
303         * In Velocity mode, output value is in position change / 100ms.
304         * In Position mode, output value is in encoder ticks or an analog value,
305         *   depending on the sensor.
306         * In Follower mode, the output value is the integer device ID of the talon to
307         * duplicate.
308         *
309         * @param value The setpoint value, as described above.
310         *
311         *
312         *      Standard Driving Example:
313         *      _talonLeft.set(ControlMode.PercentOutput, leftJoy);
314         *      _talonRght.set(ControlMode.PercentOutput, rghtJoy);
315         */
316        public void set(ControlMode mode, double value) {
317                /* intercept the advanced Set and feed motor-safety */
318                super.set(mode, value);
319                feed();
320        }
321
322        /**
323         * @param mode Sets the appropriate output on the talon, depending on the mode.
324         * @param demand0 The output value to apply.
325         *      such as advanced feed forward and/or auxiliary close-looping in firmware.
326         * In PercentOutput, the output is between -1.0 and 1.0, with 0.0 as stopped.
327         * In Current mode, output value is in amperes.
328         * In Velocity mode, output value is in position change / 100ms.
329         * In Position mode, output value is in encoder ticks or an analog value,
330         *   depending on the sensor. See
331         * In Follower mode, the output value is the integer device ID of the talon to
332         * duplicate.
333         *
334         * @param demand1Type The demand type for demand1.
335         * Neutral: Ignore demand1 and apply no change to the demand0 output.
336         * AuxPID: Use demand1 to set the target for the auxiliary PID 1.
337         * ArbitraryFeedForward: Use demand1 as an arbitrary additive value to the
338         *       demand0 output.  In PercentOutput the demand0 output is the motor output,
339         *   and in closed-loop modes the demand0 output is the output of PID0.
340         * @param demand1 Supplmental output value.  Units match the set mode.
341         *
342         *
343         *  Arcade Drive Example:
344         *              _talonLeft.set(ControlMode.PercentOutput, joyForward, DemandType.ArbitraryFeedForward, +joyTurn);
345         *              _talonRght.set(ControlMode.PercentOutput, joyForward, DemandType.ArbitraryFeedForward, -joyTurn);
346         *
347         *      Drive Straight Example:
348         *      Note: Selected Sensor Configuration is necessary for both PID0 and PID1.
349         *              _talonLeft.follow(_talonRght, FollwerType.AuxOutput1);
350         *              _talonRght.set(ControlMode.PercentOutput, joyForward, DemandType.AuxPID, desiredRobotHeading);
351         *
352         *      Drive Straight to a Distance Example:
353         *      Note: Other configurations (sensor selection, PID gains, etc.) need to be set.
354         *              _talonLeft.follow(_talonRght, FollwerType.AuxOutput1);
355         *              _talonRght.set(ControlMode.MotionMagic, targetDistance, DemandType.AuxPID, desiredRobotHeading);
356         */
357        public void set(ControlMode mode, double demand0, DemandType demand1Type, double demand1){
358                /* intercept the advanced Set and feed motor-safety */
359                super.set(mode, demand0, demand1Type, demand1);
360                feed();
361        }
362
363        /**
364         * Sets the voltage output of the SpeedController.  Compensates for the current bus
365         * voltage to ensure that the desired voltage is output even if the battery voltage is below
366         * 12V - highly useful when the voltage outputs are "meaningful" (e.g. they come from a
367         * feedforward calculation).
368         *
369         * <p>NOTE: This function *must* be called regularly in order for voltage compensation to work
370         * properly - unlike the ordinary set function, it is not "set it and forget it."
371         *
372         * @param outputVolts The voltage to output.
373         */
374        @Override
375        public void setVoltage(double outputVolts) {
376                if(super.isVoltageCompensationEnabled())
377                {
378                        com.ctre.phoenix.Logger.log(ErrorCode.DoubleVoltageCompensatingWPI, _description + ": setVoltage ");
379                }
380                set(outputVolts / RobotController.getBatteryVoltage());
381        }
382
383        // ----------------------- Invert routines -------------------//
384        /**
385         * Common interface for inverting direction of a speed controller.
386         *
387         * @param isInverted The state of inversion, true is inverted.
388         */
389        @Override
390        public void setInverted(boolean isInverted) {
391                super.setInverted(isInverted);
392        }
393
394        /**
395         * Common interface for returning the inversion state of a speed controller.
396         *
397         * @return The state of inversion, true is inverted.
398         */
399        @Override
400        public boolean getInverted() {
401                return super.getInverted();
402        }
403
404        // ----------------------- turn-motor-off routines-------------------//
405        /**
406         * Common interface for disabling a motor.
407         */
408        @Override
409        public void disable() {
410                neutralOutput();
411        }
412
413        /**
414         * Common interface to stop the motor until Set is called again.
415         */
416        @Override
417        public void stopMotor() {
418                neutralOutput();
419        }
420
421        // ---- Sendable -------//
422
423        /**
424         * Initialize sendable
425         * @param builder Base sendable to build on
426         */
427        @Override
428        public void initSendable(SendableBuilder builder) {
429                builder.setSmartDashboardType("Motor Controller");
430                builder.setActuator(true);
431                builder.setSafeState(this::stopMotor);
432                builder.addDoubleProperty("Value", this::get, this::set);
433        }
434
435        /**
436         * @return description of controller
437         */
438        public String getDescription() {
439                return _description;
440        }
441
442        /* ----- Motor Safety ----- */
443        /** caller must lock appropriately */
444        private WPI_MotorSafetyImplem GetMotorSafety() {
445                if (_motorSafety == null) {
446                        /* newly created MS object */
447                        _motorSafety = new WPI_MotorSafetyImplem(this, getDescription());
448                        /* apply the expiration timeout */
449                        _motorSafety.setExpiration(_motSafeExpiration);
450                }
451                return _motorSafety;
452        }
453        /**
454         * Feed the motor safety object.
455         *
456         * <p>Resets the timer on this object that is used to do the timeouts.
457         */
458        public void feed() {
459                synchronized (_lockMotorSaf) {
460                        if (_motorSafety == null) {
461                                /* do nothing, MS features were never enabled */
462                        } else {
463                                GetMotorSafety().feed();
464                        }
465                }
466        }
467
468        /**
469         * Set the expiration time for the corresponding motor safety object.
470         *
471         * @param expirationTime The timeout value in seconds.
472         */
473        public void setExpiration(double expirationTime) {
474                synchronized (_lockMotorSaf) {
475                        /* save the value for if/when we do create the MS object */
476                        _motSafeExpiration = expirationTime;
477                        /* apply it only if MS object exists */
478                        if (_motorSafety == null) {
479                                /* do nothing, MS features were never enabled */
480                        } else {
481                                /* this call will trigger creating a registered MS object */
482                                GetMotorSafety().setExpiration(_motSafeExpiration);
483                        }
484                }
485        }
486
487        /**
488         * Retrieve the timeout value for the corresponding motor safety object.
489         *
490         * @return the timeout value in seconds.
491         */
492        public double getExpiration() {
493                synchronized (_lockMotorSaf) {
494                        /* return the intended expiration time */
495                        return _motSafeExpiration;
496                }
497        }
498
499        /**
500         * Determine of the motor is still operating or has timed out.
501         *
502         * @return a true value if the motor is still operating normally and hasn't timed out.
503         */
504        public boolean isAlive() {
505                synchronized (_lockMotorSaf) {
506                        if (_motorSafety == null) {
507                                /* MC is alive - MS features were never enabled to neutral the MC. */
508                                return true;
509                        } else {
510                                return GetMotorSafety().isAlive();
511                        }
512                }
513        }
514
515        /**
516         * Enable/disable motor safety for this device.
517         *
518         * <p>Turn on and off the motor safety option for this PWM object.
519         *
520         * @param enabled True if motor safety is enforced for this object
521         */
522        public void setSafetyEnabled(boolean enabled) {
523                synchronized (_lockMotorSaf) {
524                        if (_motorSafety == null && !enabled) {
525                                /* Caller wants to disable MS,
526                                        but MS features were nevere enabled,
527                                        so it doesn't need to be disabled. */
528                        } else {
529                                /* MS will be created if it does not exist */
530                                GetMotorSafety().setSafetyEnabled(enabled);
531                        }
532                }
533        }
534
535        /**
536         * Return the state of the motor safety enabled flag.
537         *
538         * <p>Return if the motor safety is currently enabled for this device.
539         *
540         * @return True if motor safety is enforced for this device
541         */
542        public boolean isSafetyEnabled() {
543                synchronized (_lockMotorSaf) {
544                        if (_motorSafety == null) {
545                                /* MS features were never enabled. */
546                                return false;
547                        } else {
548                                return GetMotorSafety().isSafetyEnabled();
549                        }
550                }
551        }
552
553        private void timeoutFunc() {
554                DriverStation ds = DriverStation.getInstance();
555                if (ds.isDisabled() || ds.isTest()) {
556                return;
557                }
558
559                DriverStation.reportError(getDescription() + "... Output not updated often enough.", false);
560
561                stopMotor();
562        }
563}