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.phoenixpro.hardware; 008 009import java.util.ArrayList; 010import java.util.concurrent.locks.Lock; 011import java.util.concurrent.locks.ReentrantLock; 012 013import com.ctre.phoenix6.StatusCode; 014import com.ctre.phoenixpro.StatusSignalValue; 015import com.ctre.phoenixpro.configs.MotorOutputConfigs; 016import com.ctre.phoenixpro.controls.ControlRequest; 017import com.ctre.phoenixpro.controls.DutyCycleOut; 018import com.ctre.phoenixpro.controls.NeutralOut; 019import com.ctre.phoenixpro.controls.VoltageOut; 020import com.ctre.phoenixpro.hardware.core.CoreTalonFX; 021import com.ctre.phoenix6.jni.PlatformJNI; 022import com.ctre.phoenixpro.signals.InvertedValue; 023import com.ctre.phoenixpro.sim.DeviceType; 024import com.ctre.phoenixpro.wpiutils.AutoFeedEnable; 025import com.ctre.phoenixpro.wpiutils.CallbackHelper; 026import com.ctre.phoenixpro.wpiutils.MotorSafetyImplem; 027 028import edu.wpi.first.wpilibj.motorcontrol.MotorController; 029import edu.wpi.first.wpilibj.simulation.CallbackStore; 030import edu.wpi.first.wpilibj.simulation.SimDeviceSim; 031import edu.wpi.first.hal.HAL; 032import edu.wpi.first.hal.HALValue; 033import edu.wpi.first.hal.SimBoolean; 034import edu.wpi.first.hal.SimDevice; 035import edu.wpi.first.hal.SimDouble; 036import edu.wpi.first.hal.HAL.SimPeriodicBeforeCallback; 037import edu.wpi.first.hal.SimDevice.Direction; 038import edu.wpi.first.hal.simulation.SimDeviceDataJNI; 039import edu.wpi.first.hal.simulation.SimValueCallback; 040import edu.wpi.first.util.sendable.Sendable; 041import edu.wpi.first.util.sendable.SendableBuilder; 042import edu.wpi.first.util.sendable.SendableRegistry; 043 044/** 045 * 046 * @deprecated Classes in the phoenixpro package will be removed in 2024. 047 * Users should instead use classes from the phoenix6 package. 048 */ 049@Deprecated(forRemoval = true) 050public class TalonFX extends CoreTalonFX implements MotorController, Sendable, AutoCloseable { 051 /** 052 * The StatusSignalValue getters are copies so that calls 053 * to the WPI interface do not update any references 054 */ 055 private final StatusSignalValue<Double> m_dutyCycle = getDutyCycle().clone(); 056 057 private final DutyCycleOut m_setterControl = new DutyCycleOut(0); 058 private final NeutralOut m_neutralControl = new NeutralOut(); 059 private final VoltageOut m_voltageControl = new VoltageOut(0); 060 061 private final MotorOutputConfigs m_configs = new MotorOutputConfigs(); 062 063 private String m_description; 064 065 private static final DeviceType kSimDeviceType = DeviceType.PRO_TalonFXType; 066 067 private SimDevice m_simMotor; 068 private SimDouble m_simSupplyVoltage; 069 private SimDouble m_simDutyCycle; 070 private SimDouble m_simMotorVoltage; 071 private SimDouble m_simTorqueCurrent; 072 private SimDouble m_simSupplyCurrent; 073 074 private SimDevice m_simForwardLimit; 075 private SimBoolean m_simForwardLimitValue; 076 077 private SimDevice m_simReverseLimit; 078 private SimBoolean m_simReverseLimitValue; 079 080 private SimDevice m_simRotor; 081 private SimDouble m_simRotorPos; 082 private SimDouble m_simRotorRawPos; 083 private SimDouble m_simRotorVel; 084 085 // callbacks to register 086 private final SimValueCallback m_onValueChangedCallback = new OnValueChangedCallback(); 087 private final Runnable m_onPeriodicCallback = new OnPeriodicCallback(); 088 089 // returned registered callbacks 090 private final ArrayList<CallbackStore> m_simValueChangedCallbacks = new ArrayList<CallbackStore>(); 091 private SimPeriodicBeforeCallback m_simPeriodicBeforeCallback = null; 092 093 /** 094 * The default motor safety timeout IF calling application 095 * enables the feature. 096 */ 097 public static final double kDefaultSafetyExpiration = 0.1; 098 099 /** 100 * Late-constructed motor safety, which ensures feature is off unless calling 101 * applications explicitly enables it. 102 */ 103 private MotorSafetyImplem m_motorSafety = null; 104 private final Lock m_lockMotorSaf = new ReentrantLock(); 105 private double m_motSafeExpiration = kDefaultSafetyExpiration; 106 107 /** 108 * Constructs a new Talon FX motor controller object. 109 * <p> 110 * Constructs the device using the default CAN bus for the system: 111 * <ul> 112 * <li>"rio" on roboRIO 113 * <li>"can0" on Linux 114 * <li>"*" on Windows 115 * </ul> 116 * 117 * @param deviceId ID of the device, as configured in Phoenix Tuner. 118 * 119 * @deprecated Classes in the phoenixpro package will be removed in 2024. 120 * Users should instead use classes from the phoenix6 package. 121 */ 122 @Deprecated(forRemoval = true) 123 public TalonFX(int deviceId) { 124 this(deviceId, ""); 125 } 126 127 /** 128 * Constructs a new Talon FX motor controller object. 129 * 130 * @param deviceId ID of the device, as configured in Phoenix Tuner. 131 * @param canbus Name of the CAN bus this device is on. Possible CAN bus 132 * strings are: 133 * <ul> 134 * <li>"rio" for the native roboRIO CAN bus 135 * <li>CANivore name or serial number 136 * <li>SocketCAN interface (non-FRC Linux only) 137 * <li>"*" for any CANivore seen by the program 138 * <li>empty string (default) to select the default for the 139 * system: 140 * <ul> 141 * <li>"rio" on roboRIO 142 * <li>"can0" on Linux 143 * <li>"*" on Windows 144 * </ul> 145 * </ul> 146 * 147 * @deprecated Classes in the phoenixpro package will be removed in 2024. 148 * Users should instead use classes from the phoenix6 package. 149 */ 150 @Deprecated(forRemoval = true) 151 public TalonFX(int deviceId, String canbus) { 152 super(deviceId, canbus); 153 154 m_description = "Talon FX (Pro) " + deviceId; 155 SendableRegistry.addLW(this, "Talon FX (Pro) ", deviceId); 156 157 m_simMotor = SimDevice.create("CANMotor:Talon FX (Pro)", deviceId); 158 159 final String base = "Talon FX (Pro)[" + deviceId + "]/"; 160 m_simRotor = SimDevice.create("CANEncoder:" + base + "Rotor Sensor"); 161 m_simForwardLimit = SimDevice.create("CANDIO:" + base + "Fwd Limit"); 162 m_simReverseLimit = SimDevice.create("CANDIO:" + base + "Rev Limit"); 163 164 if (m_simMotor != null) { 165 /* First make sure our simulated device gets enabled */ 166 AutoFeedEnable.getInstance(); 167 m_simPeriodicBeforeCallback = HAL.registerSimPeriodicBeforeCallback(m_onPeriodicCallback); 168 169 m_simSupplyVoltage = m_simMotor.createDouble("supplyVoltage", Direction.kInput, 12.0); 170 171 m_simDutyCycle = m_simMotor.createDouble("dutyCycle", Direction.kOutput, 0); 172 m_simMotorVoltage = m_simMotor.createDouble("motorVoltage", Direction.kOutput, 0); 173 m_simTorqueCurrent = m_simMotor.createDouble("torqueCurrent", Direction.kOutput, 0); 174 m_simSupplyCurrent = m_simMotor.createDouble("supplyCurrent", Direction.kOutput, 0); 175 176 final SimDeviceSim sim = new SimDeviceSim("CANMotor:Talon FX (Pro)"); 177 m_simValueChangedCallbacks 178 .add(sim.registerValueChangedCallback(m_simSupplyVoltage, m_onValueChangedCallback, true)); 179 } 180 if (m_simRotor != null) { 181 m_simRotorPos = m_simRotor.createDouble("position", Direction.kOutput, 0); 182 183 m_simRotorRawPos = m_simRotor.createDouble("rawPositionInput", Direction.kInput, 0); 184 m_simRotorVel = m_simRotor.createDouble("velocity", Direction.kInput, 0); 185 186 final SimDeviceSim sim = new SimDeviceSim("CANEncoder:" + base + "Rotor Sensor"); 187 m_simValueChangedCallbacks 188 .add(sim.registerValueChangedCallback(m_simRotorRawPos, m_onValueChangedCallback, true)); 189 m_simValueChangedCallbacks 190 .add(sim.registerValueChangedCallback(m_simRotorVel, m_onValueChangedCallback, true)); 191 } 192 if (m_simForwardLimit != null) { 193 m_simForwardLimit.createBoolean("init", Direction.kOutput, true); 194 m_simForwardLimit.createBoolean("input", Direction.kOutput, true); 195 196 m_simForwardLimitValue = m_simForwardLimit.createBoolean("value", Direction.kBidir, false); 197 198 final SimDeviceSim sim = new SimDeviceSim("CANDIO:" + base + "Fwd Limit"); 199 m_simValueChangedCallbacks 200 .add(sim.registerValueChangedCallback(m_simForwardLimitValue, m_onValueChangedCallback, true)); 201 } 202 if (m_simReverseLimit != null) { 203 m_simReverseLimit.createBoolean("init", Direction.kOutput, true); 204 m_simReverseLimit.createBoolean("input", Direction.kOutput, true); 205 206 m_simReverseLimitValue = m_simReverseLimit.createBoolean("value", Direction.kBidir, false); 207 208 final SimDeviceSim sim = new SimDeviceSim("CANDIO:" + base + "Rev Limit"); 209 m_simValueChangedCallbacks 210 .add(sim.registerValueChangedCallback(m_simReverseLimitValue, m_onValueChangedCallback, true)); 211 } 212 } 213 214 // ----- Callbacks for Sim ----- // 215 private class OnValueChangedCallback implements SimValueCallback { 216 @Override 217 public void callback(String name, int handle, int direction, HALValue value) { 218 String deviceName = SimDeviceDataJNI.getSimDeviceName(SimDeviceDataJNI.getSimValueDeviceHandle(handle)); 219 String physType = deviceName + ":" + name; 220 PlatformJNI.JNI_SimSetPhysicsInput(kSimDeviceType.value, getDeviceID(), 221 physType, CallbackHelper.getRawValue(value)); 222 } 223 } 224 225 private class OnPeriodicCallback implements Runnable { 226 @Override 227 public void run() { 228 double value = 0; 229 int err = 0; 230 231 final int deviceID = getDeviceID(); 232 233 value = PlatformJNI.JNI_SimGetPhysicsValue(kSimDeviceType.value, deviceID, "SupplyVoltage"); 234 err = PlatformJNI.JNI_SimGetLastError(kSimDeviceType.value, deviceID); 235 if (err == 0) { 236 m_simSupplyVoltage.set(value); 237 } 238 value = PlatformJNI.JNI_SimGetPhysicsValue(kSimDeviceType.value, deviceID, "DutyCycle"); 239 err = PlatformJNI.JNI_SimGetLastError(kSimDeviceType.value, deviceID); 240 if (err == 0) { 241 m_simDutyCycle.set(value); 242 } 243 value = PlatformJNI.JNI_SimGetPhysicsValue(kSimDeviceType.value, deviceID, "MotorVoltage"); 244 err = PlatformJNI.JNI_SimGetLastError(kSimDeviceType.value, deviceID); 245 if (err == 0) { 246 m_simMotorVoltage.set(value); 247 } 248 value = PlatformJNI.JNI_SimGetPhysicsValue(kSimDeviceType.value, deviceID, "TorqueCurrent"); 249 err = PlatformJNI.JNI_SimGetLastError(kSimDeviceType.value, deviceID); 250 if (err == 0) { 251 m_simTorqueCurrent.set(value); 252 } 253 value = PlatformJNI.JNI_SimGetPhysicsValue(kSimDeviceType.value, deviceID, "SupplyCurrent"); 254 err = PlatformJNI.JNI_SimGetLastError(kSimDeviceType.value, deviceID); 255 if (err == 0) { 256 m_simSupplyCurrent.set(value); 257 } 258 value = PlatformJNI.JNI_SimGetPhysicsValue(kSimDeviceType.value, deviceID, "RotorPosition"); 259 err = PlatformJNI.JNI_SimGetLastError(kSimDeviceType.value, deviceID); 260 if (err == 0) { 261 m_simRotorPos.set(value); 262 } 263 value = PlatformJNI.JNI_SimGetPhysicsValue(kSimDeviceType.value, deviceID, "RawRotorPosition"); 264 err = PlatformJNI.JNI_SimGetLastError(kSimDeviceType.value, deviceID); 265 if (err == 0) { 266 m_simRotorRawPos.set(value); 267 } 268 value = PlatformJNI.JNI_SimGetPhysicsValue(kSimDeviceType.value, deviceID, "RotorVelocity"); 269 err = PlatformJNI.JNI_SimGetLastError(kSimDeviceType.value, deviceID); 270 if (err == 0) { 271 m_simRotorVel.set(value); 272 } 273 value = PlatformJNI.JNI_SimGetPhysicsValue(kSimDeviceType.value, deviceID, "ForwardLimit"); 274 err = PlatformJNI.JNI_SimGetLastError(kSimDeviceType.value, deviceID); 275 if (err == 0) { 276 m_simForwardLimitValue.set((int) value != 0); 277 } 278 value = PlatformJNI.JNI_SimGetPhysicsValue(kSimDeviceType.value, deviceID, "ReverseLimit"); 279 err = PlatformJNI.JNI_SimGetLastError(kSimDeviceType.value, deviceID); 280 if (err == 0) { 281 m_simReverseLimitValue.set((int) value != 0); 282 } 283 } 284 } 285 286 // ------- AutoCloseable ----- // 287 @Override 288 public void close() { 289 SendableRegistry.remove(this); 290 if (m_simPeriodicBeforeCallback != null) { 291 m_simPeriodicBeforeCallback.close(); 292 m_simPeriodicBeforeCallback = null; 293 } 294 if (m_simMotor != null) { 295 m_simMotor.close(); 296 m_simMotor = null; 297 } 298 if (m_simRotor != null) { 299 m_simRotor.close(); 300 m_simRotor = null; 301 } 302 if (m_simForwardLimit != null) { 303 m_simForwardLimit.close(); 304 m_simForwardLimit = null; 305 } 306 if (m_simReverseLimit != null) { 307 m_simReverseLimit.close(); 308 m_simReverseLimit = null; 309 } 310 311 for (var callback : m_simValueChangedCallbacks) { 312 callback.close(); 313 } 314 m_simValueChangedCallbacks.clear(); 315 } 316 317 // ------ set/get routines for WPILIB interfaces ------// 318 /** 319 * Common interface for setting the speed of a motor controller. 320 * 321 * @param speed The speed to set. Value should be between -1.0 and 1.0. 322 */ 323 @Override 324 public void set(double speed) { 325 feed(); 326 setControl(m_setterControl.withOutput(speed)); 327 } 328 329 /** 330 * Common interface for seting the direct voltage output of a motor controller. 331 * 332 * @param volts The voltage to output. 333 */ 334 @Override 335 public void setVoltage(double volts) { 336 feed(); 337 setControl(m_voltageControl.withOutput(volts)); 338 } 339 340 /** 341 * Common interface for getting the current set speed of a motor controller. 342 * 343 * @return The current set speed. Value is between -1.0 and 1.0. 344 */ 345 @Override 346 public double get() { 347 return m_dutyCycle.refresh().getValue(); 348 } 349 350 // ---------Intercept CTRE calls for motor safety ---------// 351 @Override 352 protected StatusCode setControlPrivate(ControlRequest request) { 353 /* intercept the control setter and feed motor-safety */ 354 feed(); 355 return super.setControlPrivate(request); 356 } 357 358 // ----------------------- turn-motor-off routines-------------------// 359 /** 360 * Common interface for disabling a motor controller. 361 */ 362 @Override 363 public void disable() { 364 setControl(m_neutralControl); 365 } 366 367 /** 368 * Common interface to stop motor movement until set is called again. 369 */ 370 @Override 371 public void stopMotor() { 372 disable(); 373 } 374 375 // ----------------------- Invert routines -------------------// 376 /** 377 * Common interface for inverting direction of a motor controller. 378 * 379 * @param isInverted The state of inversion, true is inverted. 380 */ 381 @Override 382 public void setInverted(boolean isInverted) { 383 /* First read the configs so they're up-to-date */ 384 getConfigurator().refresh(m_configs); 385 /* Then set the invert config to the appropriate value */ 386 m_configs.Inverted = isInverted ? InvertedValue.Clockwise_Positive : InvertedValue.CounterClockwise_Positive; 387 getConfigurator().apply(m_configs); 388 } 389 390 /** 391 * Common interface for returning the inversion state of a motor controller. 392 * 393 * @return The state of the inversion, true is inverted. 394 */ 395 @Override 396 public boolean getInverted() { 397 /* Read the configs so they're up-to-date */ 398 getConfigurator().refresh(m_configs); 399 /* Return true if we're Clockwise_Positive */ 400 return m_configs.Inverted == InvertedValue.Clockwise_Positive; 401 } 402 403 // ---- Sendable -------// 404 @Override 405 public void initSendable(SendableBuilder builder) { 406 builder.setSmartDashboardType("Motor Controller"); 407 builder.setActuator(true); 408 builder.setSafeState(this::stopMotor); 409 builder.addDoubleProperty("Value", this::get, this::set); 410 } 411 412 /** 413 * @return Description of motor controller 414 */ 415 public String getDescription() { 416 return m_description; 417 } 418 419 /* ----- Motor Safety ----- */ 420 /** caller must lock appropriately */ 421 private MotorSafetyImplem GetMotorSafety() { 422 if (m_motorSafety == null) { 423 /* newly created MS object */ 424 m_motorSafety = new MotorSafetyImplem(this, getDescription()); 425 /* apply the expiration timeout */ 426 m_motorSafety.setExpiration(m_motSafeExpiration); 427 } 428 return m_motorSafety; 429 } 430 431 /** 432 * Feed the motor safety object. 433 * <p> 434 * Resets the timer on this object that is used to do the timeouts. 435 */ 436 public void feed() { 437 m_lockMotorSaf.lock(); 438 try { 439 if (m_motorSafety == null) { 440 /* do nothing, MS features were never enabled */ 441 } else { 442 GetMotorSafety().feed(); 443 } 444 } finally { 445 m_lockMotorSaf.unlock(); 446 } 447 } 448 449 /** 450 * Set the expiration time for the corresponding motor safety object. 451 * 452 * @param expirationTime The timeout value in seconds. 453 */ 454 public void setExpiration(double expirationTime) { 455 m_lockMotorSaf.lock(); 456 try { 457 /* save the value for if/when we do create the MS object */ 458 m_motSafeExpiration = expirationTime; 459 /* apply it only if MS object exists */ 460 if (m_motorSafety == null) { 461 /* do nothing, MS features were never enabled */ 462 } else { 463 /* this call will trigger creating a registered MS object */ 464 GetMotorSafety().setExpiration(m_motSafeExpiration); 465 } 466 } finally { 467 m_lockMotorSaf.unlock(); 468 } 469 } 470 471 /** 472 * Retrieve the timeout value for the corresponding motor safety object. 473 * 474 * @return the timeout value in seconds. 475 */ 476 public double getExpiration() { 477 m_lockMotorSaf.lock(); 478 try { 479 /* return the intended expiration time */ 480 return m_motSafeExpiration; 481 } finally { 482 m_lockMotorSaf.unlock(); 483 } 484 } 485 486 /** 487 * Determine of the motor is still operating or has timed out. 488 * 489 * @return a true value if the motor is still operating normally and hasn't 490 * timed out. 491 */ 492 public boolean isAlive() { 493 m_lockMotorSaf.lock(); 494 try { 495 if (m_motorSafety == null) { 496 /* MC is alive - MS features were never enabled to neutral the MC. */ 497 return true; 498 } else { 499 return GetMotorSafety().isAlive(); 500 } 501 } finally { 502 m_lockMotorSaf.unlock(); 503 } 504 } 505 506 /** 507 * Enable/disable motor safety for this device. 508 * <p> 509 * Turn on and off the motor safety option for this PWM object. 510 * 511 * @param enabled True if motor safety is enforced for this object 512 */ 513 public void setSafetyEnabled(boolean enabled) { 514 m_lockMotorSaf.lock(); 515 try { 516 if (m_motorSafety == null && !enabled) { 517 /* 518 * Caller wants to disable MS, 519 * but MS features were nevere enabled, 520 * so it doesn't need to be disabled. 521 */ 522 } else { 523 /* MS will be created if it does not exist */ 524 GetMotorSafety().setSafetyEnabled(enabled); 525 } 526 } finally { 527 m_lockMotorSaf.unlock(); 528 } 529 } 530 531 /** 532 * Return the state of the motor safety enabled flag. 533 * <p> 534 * Return if the motor safety is currently enabled for this device. 535 * 536 * @return True if motor safety is enforced for this device 537 */ 538 public boolean isSafetyEnabled() { 539 m_lockMotorSaf.lock(); 540 try { 541 if (m_motorSafety == null) { 542 /* MS features were never enabled. */ 543 return false; 544 } else { 545 return GetMotorSafety().isSafetyEnabled(); 546 } 547 } finally { 548 m_lockMotorSaf.unlock(); 549 } 550 } 551}