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}