) method can instead be used to get a command that periodically applies the SwerveRequest returned by the lambda. Java private double MaxSpeed = TunerConstants . kSpeedAt12Volts . in ( MetersPerSecond ); private double MaxAngularRate = RotationsPerSecond . of ( 0.75 ). in ( RadiansPerSecond ); private final SwerveRequest . FieldCentric m_driveRequest = new SwerveRequest . FieldCentric () . withDeadband ( MaxSpeed * 0.1 ). withRotationalDeadband ( MaxAngularRate * 0.1 ) // Add a 10% deadband . withDriveRequestType ( DriveRequestType . OpenLoopVoltage ) . withSteerRequestType ( SteerRequestType . Position ); private final CommandXboxController m_joystick = new CommandXboxController ( 0 ); public final CommandSwerveDrivetrain drivetrain = TunerConstants . createDrivetrain (); public void configureBindings () { // Note that X is defined as forward according to WPILib convention, // and Y is defined as to the left according to WPILib convention. drivetrain . setDefaultCommand ( // Drivetrain will execute this command periodically drivetrain . applyRequest (() -> m_driveRequest . withVelocityX ( - joystick . getLeftY () * MaxSpeed ) . withVelocityY ( - joystick . getLeftX () * MaxSpeed ) . withRotationalRate ( - joystick . getRightX () * MaxAngularRate ) ) ); // Idle while the robot is disabled. This ensures the configured // neutral mode is applied to the drive motors while disabled. final var idle = new SwerveRequest . Idle (); RobotModeTriggers . disabled (). whileTrue ( drivetrain . applyRequest (() -> idle ). ignoringDisable ( true ) ); } C++ private : units :: meters_per_second_t MaxSpeed = TunerConstants :: kSpeedAt12Volts ; units :: radians_per_second_t MaxAngularRate = 0.75 _tps ; swerve :: requests :: FieldCentric m_driveRequest = swerve :: requests :: FieldCentric {} . WithDeadband ( MaxSpeed * 0.1 ). WithRotationalDeadband ( MaxAngularRate * 0.1 ) // Add a 10% deadband . WithDriveRequestType ( swerve :: DriveRequestType :: OpenLoopVoltage ) . WithSteerRequestType ( swerve :: SteerRequestType :: Position ); frc :: XboxController m_joystick { 0 }; public : subsystems :: CommandSwerveDrivetrain drivetrain { TunerConstants :: CreateDrivetrain ()}; void ConfigureBindings () { // Note that X is defined as forward according to WPILib convention, // and Y is defined as to the left according to WPILib convention. drivetrain . SetDefaultCommand ( // Drivetrain will execute this command periodically drivetrain . ApplyRequest ([ this ]() -> auto && { return m_driveRequest . WithVelocityX ( - joystick . GetLeftY () * MaxSpeed ) . WithVelocityY ( - joystick . GetLeftX () * MaxSpeed ) . WithRotationalRate ( - joystick . GetRightX () * MaxAngularRate ); }) ); // Idle while the robot is disabled. This ensures the configured // neutral mode is applied to the drive motors while disabled. frc2 :: RobotModeTriggers :: Disabled (). WhileTrue ( drivetrain . ApplyRequest ([] { return swerve :: requests :: Idle {}; }). IgnoringDisable ( true ) ); } Python self . _max_speed = ( TunerConstants . speed_at_12_volts ) self . _max_angular_rate = rotationsToRadians ( 0.75 ) self . _drive_request = ( swerve . requests . FieldCentric () . with_deadband ( self . _max_speed * 0.1 ) . with_rotational_deadband ( self . _max_angular_rate * 0.1 ) # Add a 10% deadband . with_drive_request_type ( swerve . SwerveModule . DriveRequestType . OPEN_LOOP_VOLTAGE ) . with_steer_request_type ( swerve . SwerveModule . SteerRequestType . POSITION ) ) self . _joystick = CommandXboxController ( 0 ) self . drivetrain = TunerConstants . create_drivetrain () def configureButtonBindings () -> None : # Note that X is defined as forward according to WPILib convention, # and Y is defined as to the left according to WPILib convention. self . drivetrain . setDefaultCommand ( # Drivetrain will execute this command periodically self . drivetrain . apply_request ( lambda : ( self . _drive_request . with_velocity_x ( - self . _joystick . getLeftY () * self . _max_speed ) # Drive forward with negative Y (forward) . with_velocity_y ( - self . _joystick . getLeftX () * self . _max_speed ) # Drive left with negative X (left) . with_rotational_rate ( - self . _joystick . getRightX () * self . _max_angular_rate ) # Drive counterclockwise with negative X (left) ) ) ) # Idle while the robot is disabled. This ensures the configured # neutral mode is applied to the drive motors while disabled. idle = swerve . requests . Idle () Trigger ( DriverStation . isDisabled ) . whileTrue ( self . drivetrain . apply_request ( lambda : idle ) . ignoringDisable ( True ) ) Custom Swerve Requests In many cases, advanced control logic can live in the command applying the swerve request. For example, path following is typically implemented using a WPILib Command factory in the subsystem. Most path planning libraries generate the path setpoints in the main robot loop, and PID on the Pose2d must be run inline with setpoint generation. However, there are some advanced cases where it is beneficial to run some of the control logic at the higher update frequency of the odometry thread. To accomplish that, users can define custom swerve requests by implementing the SwerveRequest interface. In a custom swerve request, the control logic lives in the apply(...) method, which is called by the odometry thread. Important Custom swerve requests can have a performance cost compared to the native implementations. Additionally, the apply(...) method must be fast to avoid blocking odometry updates. Swerve Requests with Composition To maximize performance and minimize duplicate code, most custom swerve requests should be built on top of existing ones. For example, the built-in FieldCentricFacingAngle ( Java , C++ , Python ) request uses a regular FieldCentric request under the hood, as demonstrated below. Java private final FieldCentric m_fieldCentric = new FieldCentric (); @Override public StatusCode apply ( SwerveControlParameters parameters , SwerveModule , ? , ?> ... modulesToApply ) { Rotation2d angleToFace = TargetDirection ; if ( ForwardPerspective == ForwardPerspectiveValue . OperatorPerspective ) { /* If we're operator perspective, rotate the direction we want to face by the angle */ angleToFace = angleToFace . rotateBy ( parameters . operatorForwardDirection ); } double toApplyOmega = TargetRateFeedforward + HeadingController . calculate ( parameters . currentPose . getRotation (). getRadians (), angleToFace . getRadians (), parameters . timestamp ); /* The rest of the logic is the same as FieldCentric, so * set up and call FieldCentric's apply() method */ return m_fieldCentric . withVelocityX ( VelocityX ) . withVelocityY ( VelocityY ) . withRotationalRate ( toApplyOmega ) . withDeadband ( Deadband ) . withRotationalDeadband ( RotationalDeadband ) . withCenterOfRotation ( CenterOfRotation ) . withDriveRequestType ( DriveRequestType ) . withSteerRequestType ( SteerRequestType ) . withDesaturateWheelSpeeds ( DesaturateWheelSpeeds ) . withForwardPerspective ( ForwardPerspective ) . apply ( parameters , modulesToApply ); } C++ ctre :: phoenix :: StatusCode Apply ( swerve :: requests :: SwerveRequest :: ControlParameters const & parameters , std :: span < std :: unique_ptr < swerve :: impl :: SwerveModuleImpl > const > modulesToApply ) override { swerve :: Rotation2d angleToFace = TargetDirection ; if ( ForwardPerspective == swerve :: requests :: ForwardPerspectiveValue :: OperatorPerspective ) { /* If we're operator perspective, rotate the direction we want to face by the angle */ angleToFace = angleToFace . RotateBy ( parameters . operatorForwardDirection ); } units :: radians_per_second_t toApplyOmega = TargetRateFeedforward + units :: radians_per_second_t { HeadingController . Calculate ( parameters . currentPose . Rotation (). Radians (). value (), angleToFace . Radians (). value (), parameters . timestamp )}; /* The rest of the logic is the same as FieldCentric, so * set up and call FieldCentric's Apply() method */ return swerve :: requests :: FieldCentric {} . WithVelocityX ( VelocityX ) . WithVelocityY ( VelocityY ) . WithRotationalRate ( toApplyOmega ) . WithDeadband ( Deadband ) . WithRotationalDeadband ( RotationalDeadband ) . WithCenterOfRotation ( CenterOfRotation ) . WithDriveRequestType ( DriveRequestType ) . WithSteerRequestType ( SteerRequestType ) . WithDesaturateWheelSpeeds ( DesaturateWheelSpeeds ) . WithForwardPerspective ( ForwardPerspective ) . Apply ( parameters , modulesToApply ); } Python def __init__ ( self ): # ... self . __field_centric = FieldCentric () def apply ( self , parameters : swerve . SwerveControlParameters , modules_to_apply : list [ swerve . SwerveModule ] ) -> StatusCode : angle_to_face = self . target_direction if self . forward_perspective is swerve . requests . ForwardPerspectiveValue . OPERATOR_PERSPECTIVE : # If we're operator perspective, rotate the direction we want to face by the angle angle_to_face = angle_to_face . rotateBy ( parameters . operator_forward_direction ) to_apply_omega = self . target_rate_feedforward + self . heading_controller . calculate ( parameters . current_pose . rotation () . radians (), angle_to_face . radians (), parameters . timestamp ) # The rest of the logic is the same as FieldCentric, so # set up and call FieldCentric's apply() method return ( self . __field_centric . with_velocity_x ( self . velocity_x ) . with_velocity_y ( self . velocity_y ) . with_rotational_rate ( to_apply_omega ) . with_deadband ( self . deadband ) . with_rotational_deadband ( self . rotational_deadband ) . with_center_of_rotation ( self . center_of_rotation ) . with_drive_request_type ( self . drive_request_type ) . with_steer_request_type ( self . steer_request_type ) . with_desaturate_wheel_speeds ( self . desaturate_wheel_speeds ) . with_forward_perspective ( self . forward_perspective ) . apply ( parameters , modules_to_apply ) ) Swerve Requests with Module Targets In a few cases, none of the existing swerve request implementations may be suitable for the desired request. For example, there is no built-in swerve request that directly accepts an array of SwerveModuleState instances. In that situation, the custom swerve request can call apply(SwerveModule.ModuleRequest) ( Java , C++ , Python ) on each SwerveModule instance provided to the apply(...) method. Note, however, that this can negatively impact performance of the robot, both in terms of loop times and control accuracy, compared to reusing the built-in requests. As a result, we recommend converting to supported types, such as ChassisSpeeds , and reusing existing swerve requests, such as ApplyFieldSpeeds ( Java , C++ , Python ), whenever possible. Warning We recommend against using a custom swerve request for the WPILib SwerveControllerCommand , as it does not follow modern WPILib best practices. Instead, the command can be reimplemented as a command factory using ApplyFieldSpeeds to maximize performance. Java public class ApplyModuleStates implements SwerveRequest { public SwerveModuleState [] ModuleStates = new SwerveModuleState [ 0 ] ; @Override public StatusCode apply ( SwerveControlParameters parameters , SwerveModule , ? , ?> ... modulesToApply ) { var moduleRequest = new SwerveModule . ModuleRequest () . withUpdatePeriod ( parameters . updatePeriod ); for ( int i = 0 ; i < modulesToApply . length && i < ModuleStates . length ; ++ i ) { /* apply the SwerveModuleState to the module */ modulesToApply [ i ] . apply ( moduleRequest . withState ( ModuleStates [ i ] )); } } } C++ struct ApplyModuleStates : public swerve :: requests :: SwerveRequest { std :: vector < SwerveModuleState > ModuleStates ; ctre :: phoenix :: StatusCode Apply ( swerve :: requests :: SwerveRequest :: ControlParameters const & parameters , std :: span < std :: unique_ptr < swerve :: impl :: SwerveModuleImpl > const > modulesToApply ) override { auto moduleRequest = impl :: SwerveModuleImpl :: ModuleRequest {} . WithUpdatePeriod ( parameters . updatePeriod ); for ( size_t i = 0 ; i < modulesToApply . size () && i < ModuleStates . size (); ++ i ) { /* apply the SwerveModuleState to the module */ modulesToApply [ i ] -> Apply ( moduleRequest . WithState ( ModuleStates [ i ])); } } }; Python class ApplyModuleStates ( swerve . requests . SwerveRequest ): def __init__ ( self ): self . module_states : list [ SwerveModuleState ] = [] def apply ( self , parameters : swerve . SwerveControlParameters , modules_to_apply : list [ swerve . SwerveModule ] ) -> StatusCode : module_request = ( SwerveModule . ModuleRequest () . with_update_period ( parameters . update_period ) ) for ( module , state ) in zip ( modules_to_apply , self . module_states ): # apply the SwerveModuleState to the module module . apply ( module_request . with_state ( state )) } } Swerve Requests with Direct Control Swerve modules by default have some built-in control optimizations and support a limited set of control types. However, for something like the built-in SysId swerve requests, such high-level control may not be desirable. As a result, SwerveModule also has apply(ControlRequest drive, ControlRequest steer) ( Java , C++ , Python ) to directly apply control requests to the drive and steer motors. For example, the built-in SysIdSwerveSteerGains ( Java , C++ , Python ) request directly applies a CoastOut to the drive motor and a VoltageOut to the steer motor. Important We recommend against using this strategy in competition code, as it does not benefit from the built-in control optimizations. Java private final CoastOut m_driveRequest = new CoastOut (); private final VoltageOut m_steerRequest = new VoltageOut ( 0 ); @Override public StatusCode apply ( SwerveControlParameters parameters , SwerveModule , ? , ?> ... modulesToApply ) { for ( int i = 0 ; i < modulesToApply . length ; ++ i ) { /* directly apply the control requests to the drive and steer motors */ modulesToApply [ i ] . apply ( m_driveRequest , m_steerRequest . withOutput ( VoltsToApply )); } return StatusCode . OK ; } C++ ctre :: phoenix :: StatusCode Apply ( SwerveRequest :: ControlParameters const & parameters , std :: span < std :: unique_ptr < impl :: SwerveModuleImpl > const > modulesToApply ) override { for ( size_t i = 0 ; i < modulesToApply . size (); ++ i ) { /* directly apply the control requests to the drive and steer motors */ modulesToApply [ i ] -> Apply ( controls :: CoastOut {}, controls :: VoltageOut { VoltsToApply }); } return ctre :: phoenix :: StatusCode :: OK ; } Python def __init__ ( self ): # ... self . __drive_request = CoastOut () self . __steer_request = VoltageOut ( 0 ) def apply ( self , parameters : swerve . SwerveControlParameters , modules_to_apply : list [ swerve . SwerveModule ] ) -> StatusCode : for module in modules_to_apply : # directly apply the control requests to the drive and steer motors module . apply ( self . __drive_request , self . __steer_request . with_output ( self . volts_to_apply ) ) return StatusCode . OK",
+ "content_preview": "Swerve Requests Controlling the drivetrain is done by calling setControl(SwerveRequest) ( Java , C++ , Python ) periodically, which takes a given SwerveRequest ( Java , C++ , Python ). There are multiple pre-defined SwerveRequest implementations that cover the majority of use cases."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/device-specific/talonfx/talonfx-control-intro.html",
- "title": "Introduction to TalonFX Control",
- "section": "TalonFX",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/mechanisms/swerve/swerve-builder-api.html",
+ "title": "Swerve Builder API",
+ "section": "API Reference",
"language": "All",
- "content": "Introduction to TalonFX Control The TalonFX has a variety of open-loop and closed-loop control requests and supports Field Oriented Control. Control Output Types The TalonFX currently supports three base control output types: DutyCycle, Voltage, and TorqueCurrentFOC. Note There are various configuration options available that influence the onboard control (for example, see Improving Performance with Current Limits ). DutyCycle A DutyCycle control request outputs a proportion of the supply voltage, which typically ranges from -1.0 to 1.0, inclusive. This control output type is typically used in systems where it is important to be capable of running at the maximum speed possible, such as in a typical robot drivetrain. Voltage A Voltage control request directly controls the output voltage of the motor. The output voltage is capped by the supply voltage to the device. Since the output of a Voltage control request is typically unaffected by the supply voltage, this control output type results in more stable and reproducible behavior than a DutyCycle control request. TorqueCurrentFOC Important This feature requires the device to be Pro licensed . A TorqueCurrentFOC control request uses Field Oriented Control to directly control the output torque current of the motor. Unlike the other control output types, where output roughly controls the velocity of the motor, a TorqueCurrentFOC request directly controls the acceleration of the motor. Field Oriented Control Important This feature requires the device to be Pro licensed . Field Oriented Control (FOC) is a commutation mode that increases peak power by ~15%. All control modes that optionally support FOC have an EnableFOC field ( Java , C++ , Python ). There are also control types that require FOC, such as TorqueCurrentFOC. Behavior While Unlicensed When controlling an unlicensed device, the device will automatically fall back to non-FOC commutation for control requests that support the EnableFOC field. For control requests that require FOC, such as TorqueCurrentFOC, the unlicensed device will: Disable control output Set the UnlicensedFeatureInUse fault Blink unlicensed",
- "content_preview": "Introduction to TalonFX Control The TalonFX has a variety of open-loop and closed-loop control requests and supports Field Oriented Control. Control Output Types The TalonFX currently supports three base control output types: DutyCycle, Voltage, and TorqueCurrentFOC."
+ "content": "Swerve Builder API To simplify the API surface, both builder and factory paradigms are used. Users create a SwerveDrivetrain by first defining the global drivetrain characteristics and then each module characteristics. Note Phoenix 6 supports the Java units library when applicable. Defining Drivetrain Characteristics Drivetrain, in this instance, refers to the SwerveDrivetrainConstants class ( Java , C++ , Python ). This class defines characteristics that are not tied to the swerve modules, such as the CAN bus or Pigeon 2 device ID. Note All devices in the swerve drivetrain must be on the same CAN bus. Users can optionally provide a configuration object to apply custom configs to the Pigeon 2, such as mount orientation. Leaving the configuration object null will skip applying configs to the Pigeon 2. Defining Module Characteristics The typical FRC drivetrain includes 4 identical modules. To simplify module creation, the SwerveModuleConstantsFactory ( Java , C++ , Python ) class is used to set up constants common across all modules, such as the drive/steer gear ratios and the wheel radius. Some extra steps may be required to determine some constants, described below. CouplingGearRatio The ratio at which the output wheel rotates when the azimuth spins. In a traditional swerve module, this is the inverse of the 1st stage of the drive motor. To manually determine the coupling ratio, lock the drive wheel in-place, then rotate the azimuth three times. Observe the number of rotations reported by the drive motor. The coupling ratio will be \\(driveRotations / 3\\) , or \\(driveRotations / azimuthRotations\\) . SlipCurrent This is the amount of stator current the drive motors can apply without slippage. Follow the instructions in Preventing Wheel Slip to find the slip current of the drivetrain. DriveMotorInitialConfigs / SteerMotorInitialConfigs / EncoderInitialConfigs An initial configuration object that can be used to apply custom configs to the backing devices for each swerve module. This is useful for situations such as applying supply current limits. Building the Swerve Module Constants SwerveModuleConstants ( Java , C++ , Python ) can be created from the previous SwerveModuleConstantsFactory . A typical swerve drivetrain consists of four identical modules: Front Left, Front Right, Back Left, Back Right. While these modules can be instantiated directly (only really useful if the modules have different physical characteristics), the modules can also be created by calling createModuleConstants(...) with the aforementioned factory. Note The X and Y position of the modules is measured from the center point of the robot along the X and Y axes, respectively. These values use the same coordinate system as Translation2d ( Java , C++ , Python ), where forward is positive X and left is positive Y. Building the SwerveDrivetrain SwerveDrivetrain ( Java , C++ , Python ) is the class that handles odometry, configuration and control of the drivetrain. The constructor for this class takes the previous SwerveDrivetrainConstants and a list of SwerveModuleConstants . Utilization of SwerveDrivetrain consists of SwerveRequests that define the state of the drivetrain. For full details of using SwerveRequests to control your swerve, see Swerve Requests . Full Example Note CommandSwerveDrivetrain is a version created by the Tuner X Swerve Project Generator that implements Subsystem ( Java , C++ , Python ) for easy command-based integration. Java 1 // Generated by the 2026 Tuner X Swerve Project Generator 2 // https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-swerve/index.html 3 public class TunerConstants { 4 // Both sets of gains need to be tuned to your individual robot. 5 6 // The steer motor uses any SwerveModule.SteerRequestType control request with the 7 // output type specified by SwerveModuleConstants.SteerMotorClosedLoopOutput 8 private static final Slot0Configs steerGains = new Slot0Configs () 9 . withKP ( 100 ). withKI ( 0 ). withKD ( 0.5 ) 10 . withKS ( 0.1 ). withKV ( 1.91 ). withKA ( 0 ) 11 . withStaticFeedforwardSign ( StaticFeedforwardSignValue . UseClosedLoopSign ); 12 // When using closed-loop control, the drive motor uses the control 13 // output type specified by SwerveModuleConstants.DriveMotorClosedLoopOutput 14 private static final Slot0Configs driveGains = new Slot0Configs () 15 . withKP ( 0.1 ). withKI ( 0 ). withKD ( 0 ) 16 . withKS ( 0 ). withKV ( 0.124 ); 17 18 // The closed-loop output type to use for the steer motors; 19 // This affects the PID/FF gains for the steer motors 20 private static final ClosedLoopOutputType kSteerClosedLoopOutput = ClosedLoopOutputType . Voltage ; 21 // The closed-loop output type to use for the drive motors; 22 // This affects the PID/FF gains for the drive motors 23 private static final ClosedLoopOutputType kDriveClosedLoopOutput = ClosedLoopOutputType . Voltage ; 24 25 // The type of motor used for the drive motor 26 private static final DriveMotorArrangement kDriveMotorType = DriveMotorArrangement . TalonFX_Integrated ; 27 // The type of motor used for the steer motor 28 private static final SteerMotorArrangement kSteerMotorType = SteerMotorArrangement . TalonFX_Integrated ; 29 30 // The remote sensor feedback type to use for the steer motors; 31 // When not Pro-licensed, Fused*/Sync* automatically fall back to Remote* 32 private static final SteerFeedbackType kSteerFeedbackType = SteerFeedbackType . FusedCANcoder ; 33 34 // The stator current at which the wheels start to slip; 35 // This needs to be tuned to your individual robot 36 private static final Current kSlipCurrent = Amps . of ( 120 ); 37 38 // Initial configs for the drive and steer motors and the azimuth encoder; these cannot be null. 39 // Some configs will be overwritten; check the `with*InitialConfigs()` API documentation. 40 private static final TalonFXConfiguration driveInitialConfigs = new TalonFXConfiguration () 41 . withCurrentLimits ( 42 new CurrentLimitsConfigs () 43 // Default supply current limit is 70 A, but it can be lowered to avoid brownouts. 44 // Supply current limits can be larger than the breaker current rating. 45 . withSupplyCurrentLimit ( Amps . of ( 70 )) 46 . withSupplyCurrentLimitEnable ( true ) 47 ); 48 private static final TalonFXConfiguration steerInitialConfigs = new TalonFXConfiguration () 49 . withCurrentLimits ( 50 new CurrentLimitsConfigs () 51 // Swerve azimuth does not require much torque output, so we can set a relatively low 52 // stator current limit to help avoid brownouts without impacting performance. 53 . withStatorCurrentLimit ( Amps . of ( 60 )) 54 . withStatorCurrentLimitEnable ( true ) 55 ); 56 private static final CANcoderConfiguration encoderInitialConfigs = new CANcoderConfiguration (); 57 // Configs for the Pigeon 2; leave this null to skip applying Pigeon 2 configs 58 private static final Pigeon2Configuration pigeonConfigs = null ; 59 60 // CAN bus that the devices are located on; 61 // All swerve devices must share the same CAN bus 62 public static final CANBus kCANBus = new CANBus ( \"canivore\" , \"./logs/example.hoot\" ); 63 64 // Measured robot speed (m/s) at 12 V applied output; 65 // This is NOT the desired max robot speed - see MaxSpeed in RobotContainer instead; 66 // This needs to be tuned to your individual robot 67 public static final LinearVelocity kSpeedAt12Volts = MetersPerSecond . of ( 4.54 ); 68 69 // Every 1 rotation of the azimuth results in kCoupleRatio drive motor turns; 70 // This may need to be tuned to your individual robot 71 private static final double kCoupleRatio = 3.8181818181818183 ; 72 73 private static final double kDriveGearRatio = 7.363636363636365 ; 74 private static final double kSteerGearRatio = 15.42857142857143 ; 75 private static final Distance kWheelRadius = Inches . of ( 2.167 ); 76 77 private static final boolean kInvertLeftSide = false ; 78 private static final boolean kInvertRightSide = true ; 79 80 private static final int kPigeonId = 1 ; 81 82 // These are only used for simulation 83 private static final MomentOfInertia kSteerInertia = KilogramSquareMeters . of ( 0.01 ); 84 private static final MomentOfInertia kDriveInertia = KilogramSquareMeters . of ( 0.035 ); 85 // Simulated voltage necessary to overcome friction 86 private static final Voltage kSteerFrictionVoltage = Volts . of ( 0.2 ); 87 private static final Voltage kDriveFrictionVoltage = Volts . of ( 0.2 ); 88 89 public static final SwerveDrivetrainConstants DrivetrainConstants = new SwerveDrivetrainConstants () 90 . withCANBusName ( kCANBus . getName ()) 91 . withPigeon2Id ( kPigeonId ) 92 . withPigeon2Configs ( pigeonConfigs ); 93 94 private static final SwerveModuleConstantsFactory < TalonFXConfiguration , TalonFXConfiguration , CANcoderConfiguration > ConstantCreator = 95 new SwerveModuleConstantsFactory < TalonFXConfiguration , TalonFXConfiguration , CANcoderConfiguration > () 96 . withDriveMotorGearRatio ( kDriveGearRatio ) 97 . withSteerMotorGearRatio ( kSteerGearRatio ) 98 . withCouplingGearRatio ( kCoupleRatio ) 99 . withWheelRadius ( kWheelRadius ) 100 . withSteerMotorGains ( steerGains ) 101 . withDriveMotorGains ( driveGains ) 102 . withSteerMotorClosedLoopOutput ( kSteerClosedLoopOutput ) 103 . withDriveMotorClosedLoopOutput ( kDriveClosedLoopOutput ) 104 . withSlipCurrent ( kSlipCurrent ) 105 . withSpeedAt12Volts ( kSpeedAt12Volts ) 106 . withDriveMotorType ( kDriveMotorType ) 107 . withSteerMotorType ( kSteerMotorType ) 108 . withFeedbackSource ( kSteerFeedbackType ) 109 . withDriveMotorInitialConfigs ( driveInitialConfigs ) 110 . withSteerMotorInitialConfigs ( steerInitialConfigs ) 111 . withEncoderInitialConfigs ( encoderInitialConfigs ) 112 . withSteerInertia ( kSteerInertia ) 113 . withDriveInertia ( kDriveInertia ) 114 . withSteerFrictionVoltage ( kSteerFrictionVoltage ) 115 . withDriveFrictionVoltage ( kDriveFrictionVoltage ); 116 117 118 // Front Left 119 private static final int kFrontLeftDriveMotorId = 3 ; 120 private static final int kFrontLeftSteerMotorId = 2 ; 121 private static final int kFrontLeftEncoderId = 1 ; 122 private static final Angle kFrontLeftEncoderOffset = Rotations . of ( 0.15234375 ); 123 private static final boolean kFrontLeftSteerMotorInverted = true ; 124 private static final boolean kFrontLeftEncoderInverted = false ; 125 126 private static final Distance kFrontLeftXPos = Inches . of ( 10 ); 127 private static final Distance kFrontLeftYPos = Inches . of ( 10 ); 128 129 // Front Right 130 private static final int kFrontRightDriveMotorId = 1 ; 131 private static final int kFrontRightSteerMotorId = 0 ; 132 private static final int kFrontRightEncoderId = 0 ; 133 private static final Angle kFrontRightEncoderOffset = Rotations . of ( - 0.4873046875 ); 134 private static final boolean kFrontRightSteerMotorInverted = true ; 135 private static final boolean kFrontRightEncoderInverted = false ; 136 137 private static final Distance kFrontRightXPos = Inches . of ( 10 ); 138 private static final Distance kFrontRightYPos = Inches . of ( - 10 ); 139 140 // Back Left 141 private static final int kBackLeftDriveMotorId = 7 ; 142 private static final int kBackLeftSteerMotorId = 6 ; 143 private static final int kBackLeftEncoderId = 3 ; 144 private static final Angle kBackLeftEncoderOffset = Rotations . of ( - 0.219482421875 ); 145 private static final boolean kBackLeftSteerMotorInverted = true ; 146 private static final boolean kBackLeftEncoderInverted = false ; 147 148 private static final Distance kBackLeftXPos = Inches . of ( - 10 ); 149 private static final Distance kBackLeftYPos = Inches . of ( 10 ); 150 151 // Back Right 152 private static final int kBackRightDriveMotorId = 5 ; 153 private static final int kBackRightSteerMotorId = 4 ; 154 private static final int kBackRightEncoderId = 2 ; 155 private static final Angle kBackRightEncoderOffset = Rotations . of ( 0.17236328125 ); 156 private static final boolean kBackRightSteerMotorInverted = true ; 157 private static final boolean kBackRightEncoderInverted = false ; 158 159 private static final Distance kBackRightXPos = Inches . of ( - 10 ); 160 private static final Distance kBackRightYPos = Inches . of ( - 10 ); 161 162 163 public static final SwerveModuleConstants < TalonFXConfiguration , TalonFXConfiguration , CANcoderConfiguration > FrontLeft = 164 ConstantCreator . createModuleConstants ( 165 kFrontLeftSteerMotorId , kFrontLeftDriveMotorId , kFrontLeftEncoderId , kFrontLeftEncoderOffset , 166 kFrontLeftXPos , kFrontLeftYPos , kInvertLeftSide , kFrontLeftSteerMotorInverted , kFrontLeftEncoderInverted 167 ); 168 public static final SwerveModuleConstants < TalonFXConfiguration , TalonFXConfiguration , CANcoderConfiguration > FrontRight = 169 ConstantCreator . createModuleConstants ( 170 kFrontRightSteerMotorId , kFrontRightDriveMotorId , kFrontRightEncoderId , kFrontRightEncoderOffset , 171 kFrontRightXPos , kFrontRightYPos , kInvertRightSide , kFrontRightSteerMotorInverted , kFrontRightEncoderInverted 172 ); 173 public static final SwerveModuleConstants < TalonFXConfiguration , TalonFXConfiguration , CANcoderConfiguration > BackLeft = 174 ConstantCreator . createModuleConstants ( 175 kBackLeftSteerMotorId , kBackLeftDriveMotorId , kBackLeftEncoderId , kBackLeftEncoderOffset , 176 kBackLeftXPos , kBackLeftYPos , kInvertLeftSide , kBackLeftSteerMotorInverted , kBackLeftEncoderInverted 177 ); 178 public static final SwerveModuleConstants < TalonFXConfiguration , TalonFXConfiguration , CANcoderConfiguration > BackRight = 179 ConstantCreator . createModuleConstants ( 180 kBackRightSteerMotorId , kBackRightDriveMotorId , kBackRightEncoderId , kBackRightEncoderOffset , 181 kBackRightXPos , kBackRightYPos , kInvertRightSide , kBackRightSteerMotorInverted , kBackRightEncoderInverted 182 ); 183 184 /** 185 * Creates a CommandSwerveDrivetrain instance. 186 * This should only be called once in your robot program,. 187 */ 188 public static CommandSwerveDrivetrain createDrivetrain () { 189 return new CommandSwerveDrivetrain ( 190 DrivetrainConstants , FrontLeft , FrontRight , BackLeft , BackRight 191 ); 192 } 193 194 195 /** 196 * Swerve Drive class utilizing CTR Electronics' Phoenix 6 API with the selected device types. 197 */ 198 public static class TunerSwerveDrivetrain extends SwerveDrivetrain < TalonFX , TalonFX , CANcoder > { 199 /** 200 * Constructs a CTRE SwerveDrivetrain using the specified constants. 201 * 202 * This constructs the underlying hardware devices, so users should not construct 203 * the devices themselves. If they need the devices, they can access them through 204 * getters in the classes. 205 * 206 * @param drivetrainConstants Drivetrain-wide constants for the swerve drive 207 * @param modules Constants for each specific module 208 */ 209 public TunerSwerveDrivetrain ( 210 SwerveDrivetrainConstants drivetrainConstants , 211 SwerveModuleConstants , ? , ?> ... modules 212 ) { 213 super ( 214 TalonFX :: new , TalonFX :: new , CANcoder :: new , 215 drivetrainConstants , modules 216 ); 217 } 218 219 /** 220 * Constructs a CTRE SwerveDrivetrain using the specified constants. 221 *
222 * This constructs the underlying hardware devices, so users should not construct 223 * the devices themselves. If they need the devices, they can access them through 224 * getters in the classes. 225 * 226 * @param drivetrainConstants Drivetrain-wide constants for the swerve drive 227 * @param odometryUpdateFrequency The frequency to run the odometry loop. If 228 * unspecified or set to 0 Hz, this is 250 Hz on 229 * CAN FD, and 100 Hz on CAN 2.0. 230 * @param modules Constants for each specific module 231 */ 232 public TunerSwerveDrivetrain ( 233 SwerveDrivetrainConstants drivetrainConstants , 234 double odometryUpdateFrequency , 235 SwerveModuleConstants , ? , ?> ... modules 236 ) { 237 super ( 238 TalonFX :: new , TalonFX :: new , CANcoder :: new , 239 drivetrainConstants , odometryUpdateFrequency , modules 240 ); 241 } 242 243 /** 244 * Constructs a CTRE SwerveDrivetrain using the specified constants. 245 *
246 * This constructs the underlying hardware devices, so users should not construct 247 * the devices themselves. If they need the devices, they can access them through 248 * getters in the classes. 249 * 250 * @param drivetrainConstants Drivetrain-wide constants for the swerve drive 251 * @param odometryUpdateFrequency The frequency to run the odometry loop. If 252 * unspecified or set to 0 Hz, this is 250 Hz on 253 * CAN FD, and 100 Hz on CAN 2.0. 254 * @param odometryStandardDeviation The standard deviation for odometry calculation 255 * in the form [x, y, theta]ᵀ, with units in meters 256 * and radians 257 * @param visionStandardDeviation The standard deviation for vision calculation 258 * in the form [x, y, theta]ᵀ, with units in meters 259 * and radians 260 * @param modules Constants for each specific module 261 */ 262 public TunerSwerveDrivetrain ( 263 SwerveDrivetrainConstants drivetrainConstants , 264 double odometryUpdateFrequency , 265 Matrix < N3 , N1 > odometryStandardDeviation , 266 Matrix < N3 , N1 > visionStandardDeviation , 267 SwerveModuleConstants , ? , ?> ... modules 268 ) { 269 super ( 270 TalonFX :: new , TalonFX :: new , CANcoder :: new , 271 drivetrainConstants , odometryUpdateFrequency , 272 odometryStandardDeviation , visionStandardDeviation , modules 273 ); 274 } 275 } 276 } C++ (Header) 1 #include \"ctre/phoenix6/swerve/SwerveDrivetrain.hpp\" 2 #include \"ctre/phoenix6/CANcoder.hpp\" 3 #include \"ctre/phoenix6/TalonFX.hpp\" 4 5 using namespace ctre :: phoenix6 ; 6 7 namespace subsystems { 8 /* Forward declaration */ 9 class CommandSwerveDrivetrain ; 10 } 11 12 // Generated by the 2026 Tuner X Swerve Project Generator 13 // https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-swerve/index.html 14 class TunerConstants { 15 // Both sets of gains need to be tuned to your individual robot. 16 17 // The steer motor uses any SwerveModule.SteerRequestType control request with the 18 // output type specified by SwerveModuleConstants::SteerMotorClosedLoopOutput 19 static constexpr configs :: Slot0Configs steerGains = configs :: Slot0Configs {} 20 . WithKP ( 100 ). WithKI ( 0 ). WithKD ( 0.5 ) 21 . WithKS ( 0.1 ). WithKV ( 1.91 ). WithKA ( 0 ) 22 . WithStaticFeedforwardSign ( signals :: StaticFeedforwardSignValue :: UseClosedLoopSign ); 23 // When using closed-loop control, the drive motor uses the control 24 // output type specified by SwerveModuleConstants::DriveMotorClosedLoopOutput 25 static constexpr configs :: Slot0Configs driveGains = configs :: Slot0Configs {} 26 . WithKP ( 0.1 ). WithKI ( 0 ). WithKD ( 0 ) 27 . WithKS ( 0 ). WithKV ( 0.124 ); 28 29 // The closed-loop output type to use for the steer motors; 30 // This affects the PID/FF gains for the steer motors 31 static constexpr swerve :: ClosedLoopOutputType kSteerClosedLoopOutput = swerve :: ClosedLoopOutputType :: Voltage ; 32 // The closed-loop output type to use for the drive motors; 33 // This affects the PID/FF gains for the drive motors 34 static constexpr swerve :: ClosedLoopOutputType kDriveClosedLoopOutput = swerve :: ClosedLoopOutputType :: Voltage ; 35 36 // The type of motor used for the drive motor 37 static constexpr swerve :: DriveMotorArrangement kDriveMotorType = swerve :: DriveMotorArrangement :: TalonFX_Integrated ; 38 // The type of motor used for the steer motor 39 static constexpr swerve :: SteerMotorArrangement kSteerMotorType = swerve :: SteerMotorArrangement :: TalonFX_Integrated ; 40 41 // The remote sensor feedback type to use for the steer motors; 42 // When not Pro-licensed, Fused*/Sync* automatically fall back to Remote* 43 static constexpr swerve :: SteerFeedbackType kSteerFeedbackType = swerve :: SteerFeedbackType :: FusedCANcoder ; 44 45 // The stator current at which the wheels start to slip; 46 // This needs to be tuned to your individual robot 47 static constexpr units :: ampere_t kSlipCurrent = 120 _A ; 48 49 // Initial configs for the drive and steer motors and the azimuth encoder; these cannot be null. 50 // Some configs will be overwritten; check the `With*InitialConfigs()` API documentation. 51 static constexpr configs :: TalonFXConfiguration driveInitialConfigs = configs :: TalonFXConfiguration {} 52 . WithCurrentLimits ( 53 configs :: CurrentLimitsConfigs {} 54 // Default supply current limit is 70 A, but it can be lowered to avoid brownouts. 55 // Supply current limits can be larger than the breaker current rating. 56 . WithSupplyCurrentLimit ( 70 _A ) 57 . WithSupplyCurrentLimitEnable ( true ) 58 ); 59 static constexpr configs :: TalonFXConfiguration steerInitialConfigs = configs :: TalonFXConfiguration {} 60 . WithCurrentLimits ( 61 configs :: CurrentLimitsConfigs {} 62 // Swerve azimuth does not require much torque output, so we can set a relatively low 63 // stator current limit to help avoid brownouts without impacting performance. 64 . WithStatorCurrentLimit ( 60 _A ) 65 . WithStatorCurrentLimitEnable ( true ) 66 ); 67 static constexpr configs :: CANcoderConfiguration encoderInitialConfigs {}; 68 // Configs for the Pigeon 2; leave this nullopt to skip applying Pigeon 2 configs 69 static constexpr std :: optional < configs :: Pigeon2Configuration > pigeonConfigs = std :: nullopt ; 70 71 static constexpr std :: string_view kCANBusName = \"canivore\" ; 72 73 public : 74 // CAN bus that the devices are located on; 75 // All swerve devices must share the same CAN bus 76 static inline const CANBus kCANBus { kCANBusName , \"./logs/example.hoot\" }; 77 78 // Measured robot speed (m/s) at 12 V applied output; 79 // This is NOT the desired max robot speed - see MaxSpeed in RobotContainer instead; 80 // This needs to be tuned to your individual robot 81 static constexpr units :: meters_per_second_t kSpeedAt12Volts = 4.54 _mps ; 82 83 private : 84 // Every 1 rotation of the azimuth results in kCoupleRatio drive motor turns; 85 // This may need to be tuned to your individual robot 86 static constexpr units :: scalar_t kCoupleRatio = 3.8181818181818183 ; 87 88 static constexpr units :: scalar_t kDriveGearRatio = 7.363636363636365 ; 89 static constexpr units :: scalar_t kSteerGearRatio = 15.42857142857143 ; 90 static constexpr units :: inch_t kWheelRadius = 2.167 _in ; 91 92 static constexpr bool kInvertLeftSide = false ; 93 static constexpr bool kInvertRightSide = true ; 94 95 static constexpr int kPigeonId = 1 ; 96 97 // These are only used for simulation 98 static constexpr units :: kilogram_square_meter_t kSteerInertia = 0.01 _kg_sq_m ; 99 static constexpr units :: kilogram_square_meter_t kDriveInertia = 0.035 _kg_sq_m ; 100 // Simulated voltage necessary to overcome friction 101 static constexpr units :: volt_t kSteerFrictionVoltage = 0.2 _V ; 102 static constexpr units :: volt_t kDriveFrictionVoltage = 0.2 _V ; 103 104 public : 105 static constexpr swerve :: SwerveDrivetrainConstants DrivetrainConstants = swerve :: SwerveDrivetrainConstants {} 106 . WithCANBusName ( kCANBusName ) 107 . WithPigeon2Id ( kPigeonId ) 108 . WithPigeon2Configs ( pigeonConfigs ); 109 110 private : 111 static constexpr swerve :: SwerveModuleConstantsFactory ConstantCreator = 112 swerve :: SwerveModuleConstantsFactory < configs :: TalonFXConfiguration , configs :: TalonFXConfiguration , configs :: CANcoderConfiguration > {} 113 . WithDriveMotorGearRatio ( kDriveGearRatio ) 114 . WithSteerMotorGearRatio ( kSteerGearRatio ) 115 . WithCouplingGearRatio ( kCoupleRatio ) 116 . WithWheelRadius ( kWheelRadius ) 117 . WithSteerMotorGains ( steerGains ) 118 . WithDriveMotorGains ( driveGains ) 119 . WithSteerMotorClosedLoopOutput ( kSteerClosedLoopOutput ) 120 . WithDriveMotorClosedLoopOutput ( kDriveClosedLoopOutput ) 121 . WithSlipCurrent ( kSlipCurrent ) 122 . WithSpeedAt12Volts ( kSpeedAt12Volts ) 123 . WithDriveMotorType ( kDriveMotorType ) 124 . WithSteerMotorType ( kSteerMotorType ) 125 . WithFeedbackSource ( kSteerFeedbackType ) 126 . WithDriveMotorInitialConfigs ( driveInitialConfigs ) 127 . WithSteerMotorInitialConfigs ( steerInitialConfigs ) 128 . WithEncoderInitialConfigs ( encoderInitialConfigs ) 129 . WithSteerInertia ( kSteerInertia ) 130 . WithDriveInertia ( kDriveInertia ) 131 . WithSteerFrictionVoltage ( kSteerFrictionVoltage ) 132 . WithDriveFrictionVoltage ( kDriveFrictionVoltage ); 133 134 135 // Front Left 136 static constexpr int kFrontLeftDriveMotorId = 3 ; 137 static constexpr int kFrontLeftSteerMotorId = 2 ; 138 static constexpr int kFrontLeftEncoderId = 1 ; 139 static constexpr units :: turn_t kFrontLeftEncoderOffset = 0.15234375 _tr ; 140 static constexpr bool kFrontLeftSteerMotorInverted = true ; 141 static constexpr bool kFrontLeftEncoderInverted = false ; 142 143 static constexpr units :: inch_t kFrontLeftXPos = 10 _in ; 144 static constexpr units :: inch_t kFrontLeftYPos = 10 _in ; 145 146 // Front Right 147 static constexpr int kFrontRightDriveMotorId = 1 ; 148 static constexpr int kFrontRightSteerMotorId = 0 ; 149 static constexpr int kFrontRightEncoderId = 0 ; 150 static constexpr units :: turn_t kFrontRightEncoderOffset = -0.4873046875 _tr ; 151 static constexpr bool kFrontRightSteerMotorInverted = true ; 152 static constexpr bool kFrontRightEncoderInverted = false ; 153 154 static constexpr units :: inch_t kFrontRightXPos = 10 _in ; 155 static constexpr units :: inch_t kFrontRightYPos = -10 _in ; 156 157 // Back Left 158 static constexpr int kBackLeftDriveMotorId = 7 ; 159 static constexpr int kBackLeftSteerMotorId = 6 ; 160 static constexpr int kBackLeftEncoderId = 3 ; 161 static constexpr units :: turn_t kBackLeftEncoderOffset = -0.219482421875 _tr ; 162 static constexpr bool kBackLeftSteerMotorInverted = true ; 163 static constexpr bool kBackLeftEncoderInverted = false ; 164 165 static constexpr units :: inch_t kBackLeftXPos = -10 _in ; 166 static constexpr units :: inch_t kBackLeftYPos = 10 _in ; 167 168 // Back Right 169 static constexpr int kBackRightDriveMotorId = 5 ; 170 static constexpr int kBackRightSteerMotorId = 4 ; 171 static constexpr int kBackRightEncoderId = 2 ; 172 static constexpr units :: turn_t kBackRightEncoderOffset = 0.17236328125 _tr ; 173 static constexpr bool kBackRightSteerMotorInverted = true ; 174 static constexpr bool kBackRightEncoderInverted = false ; 175 176 static constexpr units :: inch_t kBackRightXPos = -10 _in ; 177 static constexpr units :: inch_t kBackRightYPos = -10 _in ; 178 179 180 public : 181 static constexpr swerve :: SwerveModuleConstants FrontLeft = ConstantCreator . CreateModuleConstants ( 182 kFrontLeftSteerMotorId , kFrontLeftDriveMotorId , kFrontLeftEncoderId , kFrontLeftEncoderOffset , 183 kFrontLeftXPos , kFrontLeftYPos , kInvertLeftSide , kFrontLeftSteerMotorInverted , kFrontLeftEncoderInverted ); 184 static constexpr swerve :: SwerveModuleConstants FrontRight = ConstantCreator . CreateModuleConstants ( 185 kFrontRightSteerMotorId , kFrontRightDriveMotorId , kFrontRightEncoderId , kFrontRightEncoderOffset , 186 kFrontRightXPos , kFrontRightYPos , kInvertRightSide , kFrontRightSteerMotorInverted , kFrontRightEncoderInverted ); 187 static constexpr swerve :: SwerveModuleConstants BackLeft = ConstantCreator . CreateModuleConstants ( 188 kBackLeftSteerMotorId , kBackLeftDriveMotorId , kBackLeftEncoderId , kBackLeftEncoderOffset , 189 kBackLeftXPos , kBackLeftYPos , kInvertLeftSide , kBackLeftSteerMotorInverted , kBackLeftEncoderInverted ); 190 static constexpr swerve :: SwerveModuleConstants BackRight = ConstantCreator . CreateModuleConstants ( 191 kBackRightSteerMotorId , kBackRightDriveMotorId , kBackRightEncoderId , kBackRightEncoderOffset , 192 kBackRightXPos , kBackRightYPos , kInvertRightSide , kBackRightSteerMotorInverted , kBackRightEncoderInverted ); 193 194 /** 195 * Creates a CommandSwerveDrivetrain instance. 196 * This should only be called once in your robot program. 197 */ 198 static subsystems :: CommandSwerveDrivetrain CreateDrivetrain (); 199 }; 200 201 202 /** 203 * \\brief Swerve Drive class utilizing CTR Electronics' Phoenix 6 API with the selected device types. 204 */ 205 class TunerSwerveDrivetrain : public swerve :: SwerveDrivetrain < hardware :: TalonFX , hardware :: TalonFX , hardware :: CANcoder > { 206 public : 207 using SwerveModuleConstants = swerve :: SwerveModuleConstants < configs :: TalonFXConfiguration , configs :: TalonFXConfiguration , configs :: CANcoderConfiguration > ; 208 209 /** 210 * \\brief Constructs a CTRE SwerveDrivetrain using the specified constants. 211 * 212 * This constructs the underlying hardware devices, so users should not construct 213 * the devices themselves. If they need the devices, they can access them 214 * through getters in the classes. 215 * 216 * \\param drivetrainConstants Drivetrain-wide constants for the swerve drive 217 * \\param modules Constants for each specific module 218 */ 219 template < std :: same_as < SwerveModuleConstants > ... ModuleConstants > 220 TunerSwerveDrivetrain ( swerve :: SwerveDrivetrainConstants const & driveTrainConstants , ModuleConstants const & ... modules ) : 221 SwerveDrivetrain { driveTrainConstants , modules ...} 222 {} 223 224 /** 225 * \\brief Constructs a CTRE SwerveDrivetrain using the specified constants. 226 * 227 * This constructs the underlying hardware devices, so users should not construct 228 * the devices themselves. If they need the devices, they can access them 229 * through getters in the classes. 230 * 231 * \\param drivetrainConstants Drivetrain-wide constants for the swerve drive 232 * \\param odometryUpdateFrequency The frequency to run the odometry loop. If 233 * unspecified or set to 0 Hz, this is 250 Hz on 234 * CAN FD, and 100 Hz on CAN 2.0. 235 * \\param modules Constants for each specific module 236 */ 237 template < std :: same_as < SwerveModuleConstants > ... ModuleConstants > 238 TunerSwerveDrivetrain ( 239 swerve :: SwerveDrivetrainConstants const & driveTrainConstants , 240 units :: hertz_t odometryUpdateFrequency , 241 ModuleConstants const & ... modules 242 ) : 243 SwerveDrivetrain { driveTrainConstants , odometryUpdateFrequency , modules ...} 244 {} 245 246 /** 247 * \\brief Constructs a CTRE SwerveDrivetrain using the specified constants. 248 * 249 * This constructs the underlying hardware devices, so users should not construct 250 * the devices themselves. If they need the devices, they can access them 251 * through getters in the classes. 252 * 253 * \\param drivetrainConstants Drivetrain-wide constants for the swerve drive 254 * \\param odometryUpdateFrequency The frequency to run the odometry loop. If 255 * unspecified or set to 0 Hz, this is 250 Hz on 256 * CAN FD, and 100 Hz on CAN 2.0. 257 * \\param odometryStandardDeviation The standard deviation for odometry calculation 258 * in the form [x, y, theta]ᵀ, with units in meters 259 * and radians 260 * \\param visionStandardDeviation The standard deviation for vision calculation 261 * in the form [x, y, theta]ᵀ, with units in meters 262 * and radians 263 * \\param modules Constants for each specific module 264 */ 265 template < std :: same_as < SwerveModuleConstants > ... ModuleConstants > 266 TunerSwerveDrivetrain ( 267 swerve :: SwerveDrivetrainConstants const & driveTrainConstants , 268 units :: hertz_t odometryUpdateFrequency , 269 std :: array < double , 3 > const & odometryStandardDeviation , 270 std :: array < double , 3 > const & visionStandardDeviation , 271 ModuleConstants const & ... modules 272 ) : 273 SwerveDrivetrain { 274 driveTrainConstants , odometryUpdateFrequency , 275 odometryStandardDeviation , visionStandardDeviation , modules ... 276 } 277 {} 278 }; C++ (Source) 1 #include \"generated/TunerConstants.h\" 2 #include \"subsystems/CommandSwerveDrivetrain.h\" 3 4 subsystems :: CommandSwerveDrivetrain TunerConstants::CreateDrivetrain () 5 { 6 return { DrivetrainConstants , FrontLeft , FrontRight , BackLeft , BackRight }; 7 } Python 1 from subsystems.command_swerve_drivetrain import CommandSwerveDrivetrain 2 3 4 class TunerConstants : 5 \"\"\" 6 Generated by the 2026 Tuner X Swerve Project Generator 7 https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-swerve/index.html 8 \"\"\" 9 10 # Both sets of gains need to be tuned to your individual robot 11 12 # The steer motor uses any SwerveModule.SteerRequestType control request with the 13 # output type specified by SwerveModuleConstants.SteerMotorClosedLoopOutput 14 _steer_gains = ( 15 configs . Slot0Configs () 16 . with_k_p ( 100 ) 17 . with_k_i ( 0 ) 18 . with_k_d ( 0.5 ) 19 . with_k_s ( 0.1 ) 20 . with_k_v ( 1.91 ) 21 . with_k_a ( 0 ) 22 . with_static_feedforward_sign ( 23 signals . StaticFeedforwardSignValue . USE_CLOSED_LOOP_SIGN 24 ) 25 ) 26 # When using closed-loop control, the drive motor uses the control 27 # output type specified by SwerveModuleConstants.DriveMotorClosedLoopOutput 28 _drive_gains = ( 29 configs . Slot0Configs () 30 . with_k_p ( 0.1 ) 31 . with_k_i ( 0 ) 32 . with_k_d ( 0 ) 33 . with_k_s ( 0 ) 34 . with_k_v ( 0.124 ) 35 ) 36 37 # The closed-loop output type to use for the steer motors; 38 # This affects the PID/FF gains for the steer motors 39 _steer_closed_loop_output = swerve . ClosedLoopOutputType . VOLTAGE 40 # The closed-loop output type to use for the drive motors; 41 # This affects the PID/FF gains for the drive motors 42 _drive_closed_loop_output = swerve . ClosedLoopOutputType . VOLTAGE 43 44 # The type of motor used for the drive motor 45 _drive_motor_type = swerve . DriveMotorArrangement . TALON_FX_INTEGRATED 46 # The type of motor used for the steer motor 47 _steer_motor_type = swerve . SteerMotorArrangement . TALON_FX_INTEGRATED 48 49 # The remote sensor feedback type to use for the steer motors; 50 # When not Pro-licensed, Fused*/Sync* automatically fall back to Remote* 51 _steer_feedback_type = swerve . SteerFeedbackType . FUSED_CANCODER 52 53 # The stator current at which the wheels start to slip; 54 # This needs to be tuned to your individual robot 55 _slip_current : units . ampere = 120.0 56 57 # Initial configs for the drive and steer motors and the azimuth encoder; these cannot be null. 58 # Some configs will be overwritten; check the `with_*_initial_configs()` API documentation. 59 _drive_initial_configs = configs . TalonFXConfiguration () . with_current_limits ( 60 configs . CurrentLimitsConfigs () 61 # Default supply current limit is 70 A, but it can be lowered to avoid brownouts. 62 # Supply current limits can be larger than the breaker current rating. 63 . with_supply_current_limit ( 70.0 ) 64 . with_supply_current_limit_enable ( True ) 65 ) 66 _steer_initial_configs = configs . TalonFXConfiguration () . with_current_limits ( 67 configs . CurrentLimitsConfigs () 68 # Swerve azimuth does not require much torque output, so we can set a relatively low 69 # stator current limit to help avoid brownouts without impacting performance. 70 . with_stator_current_limit ( 60.0 ) 71 . with_stator_current_limit_enable ( True ) 72 ) 73 _encoder_initial_configs = configs . CANcoderConfiguration () 74 # Configs for the Pigeon 2; leave this None to skip applying Pigeon 2 configs 75 _pigeon_configs : configs . Pigeon2Configuration | None = None 76 77 # CAN bus that the devices are located on; 78 # All swerve devices must share the same CAN bus 79 canbus = CANBus ( \"canivore\" , \"./logs/example.hoot\" ) 80 81 # Measured robot speed (m/s) at 12 V applied output; 82 # This is NOT the desired max robot speed - see _max_speed in RobotContainer instead; 83 # This needs to be tuned to your individual robot 84 speed_at_12_volts : units . meters_per_second = 4.54 85 86 # Every 1 rotation of the azimuth results in _couple_ratio drive motor turns; 87 # This may need to be tuned to your individual robot 88 _couple_ratio = 3.8181818181818183 89 90 _drive_gear_ratio = 7.363636363636365 91 _steer_gear_ratio = 15.42857142857143 92 _wheel_radius : units . meter = inchesToMeters ( 2.167 ) 93 94 _invert_left_side = False 95 _invert_right_side = True 96 97 _pigeon_id = 1 98 99 # These are only used for simulation 100 _steer_inertia : units . kilogram_square_meter = 0.01 101 _drive_inertia : units . kilogram_square_meter = 0.035 102 # Simulated voltage necessary to overcome friction 103 _steer_friction_voltage : units . volt = 0.2 104 _drive_friction_voltage : units . volt = 0.2 105 106 drivetrain_constants = ( 107 swerve . SwerveDrivetrainConstants () 108 . with_can_bus_name ( canbus . name ) 109 . with_pigeon2_id ( _pigeon_id ) 110 . with_pigeon2_configs ( _pigeon_configs ) 111 ) 112 113 _constants_creator : swerve . SwerveModuleConstantsFactory [ 114 configs . TalonFXConfiguration , 115 configs . TalonFXConfiguration , 116 configs . CANcoderConfiguration , 117 ] = ( 118 swerve . SwerveModuleConstantsFactory () 119 . with_drive_motor_gear_ratio ( _drive_gear_ratio ) 120 . with_steer_motor_gear_ratio ( _steer_gear_ratio ) 121 . with_coupling_gear_ratio ( _couple_ratio ) 122 . with_wheel_radius ( _wheel_radius ) 123 . with_steer_motor_gains ( _steer_gains ) 124 . with_drive_motor_gains ( _drive_gains ) 125 . with_steer_motor_closed_loop_output ( _steer_closed_loop_output ) 126 . with_drive_motor_closed_loop_output ( _drive_closed_loop_output ) 127 . with_slip_current ( _slip_current ) 128 . with_speed_at12_volts ( speed_at_12_volts ) 129 . with_drive_motor_type ( _drive_motor_type ) 130 . with_steer_motor_type ( _steer_motor_type ) 131 . with_feedback_source ( _steer_feedback_type ) 132 . with_drive_motor_initial_configs ( _drive_initial_configs ) 133 . with_steer_motor_initial_configs ( _steer_initial_configs ) 134 . with_encoder_initial_configs ( _encoder_initial_configs ) 135 . with_steer_inertia ( _steer_inertia ) 136 . with_drive_inertia ( _drive_inertia ) 137 . with_steer_friction_voltage ( _steer_friction_voltage ) 138 . with_drive_friction_voltage ( _drive_friction_voltage ) 139 ) 140 141 142 # Front Left 143 _front_left_drive_motor_id = 3 144 _front_left_steer_motor_id = 2 145 _front_left_encoder_id = 1 146 _front_left_encoder_offset : units . rotation = 0.15234375 147 _front_left_steer_motor_inverted = True 148 _front_left_encoder_inverted = False 149 150 _front_left_x_pos : units . meter = inchesToMeters ( 10 ) 151 _front_left_y_pos : units . meter = inchesToMeters ( 10 ) 152 153 # Front Right 154 _front_right_drive_motor_id = 1 155 _front_right_steer_motor_id = 0 156 _front_right_encoder_id = 0 157 _front_right_encoder_offset : units . rotation = - 0.4873046875 158 _front_right_steer_motor_inverted = True 159 _front_right_encoder_inverted = False 160 161 _front_right_x_pos : units . meter = inchesToMeters ( 10 ) 162 _front_right_y_pos : units . meter = inchesToMeters ( - 10 ) 163 164 # Back Left 165 _back_left_drive_motor_id = 7 166 _back_left_steer_motor_id = 6 167 _back_left_encoder_id = 3 168 _back_left_encoder_offset : units . rotation = - 0.219482421875 169 _back_left_steer_motor_inverted = True 170 _back_left_encoder_inverted = False 171 172 _back_left_x_pos : units . meter = inchesToMeters ( - 10 ) 173 _back_left_y_pos : units . meter = inchesToMeters ( 10 ) 174 175 # Back Right 176 _back_right_drive_motor_id = 5 177 _back_right_steer_motor_id = 4 178 _back_right_encoder_id = 2 179 _back_right_encoder_offset : units . rotation = 0.17236328125 180 _back_right_steer_motor_inverted = True 181 _back_right_encoder_inverted = False 182 183 _back_right_x_pos : units . meter = inchesToMeters ( - 10 ) 184 _back_right_y_pos : units . meter = inchesToMeters ( - 10 ) 185 186 187 front_left = _constants_creator . create_module_constants ( 188 _front_left_steer_motor_id , 189 _front_left_drive_motor_id , 190 _front_left_encoder_id , 191 _front_left_encoder_offset , 192 _front_left_x_pos , 193 _front_left_y_pos , 194 _invert_left_side , 195 _front_left_steer_motor_inverted , 196 _front_left_encoder_inverted , 197 ) 198 front_right = _constants_creator . create_module_constants ( 199 _front_right_steer_motor_id , 200 _front_right_drive_motor_id , 201 _front_right_encoder_id , 202 _front_right_encoder_offset , 203 _front_right_x_pos , 204 _front_right_y_pos , 205 _invert_right_side , 206 _front_right_steer_motor_inverted , 207 _front_right_encoder_inverted , 208 ) 209 back_left = _constants_creator . create_module_constants ( 210 _back_left_steer_motor_id , 211 _back_left_drive_motor_id , 212 _back_left_encoder_id , 213 _back_left_encoder_offset , 214 _back_left_x_pos , 215 _back_left_y_pos , 216 _invert_left_side , 217 _back_left_steer_motor_inverted , 218 _back_left_encoder_inverted , 219 ) 220 back_right = _constants_creator . create_module_constants ( 221 _back_right_steer_motor_id , 222 _back_right_drive_motor_id , 223 _back_right_encoder_id , 224 _back_right_encoder_offset , 225 _back_right_x_pos , 226 _back_right_y_pos , 227 _invert_right_side , 228 _back_right_steer_motor_inverted , 229 _back_right_encoder_inverted , 230 ) 231 232 @classmethod 233 def create_drivetrain ( cls ) -> \"CommandSwerveDrivetrain\" : 234 \"\"\" 235 Creates a CommandSwerveDrivetrain instance. 236 This should only be called once in your robot program. 237 \"\"\" 238 from subsystems.command_swerve_drivetrain import CommandSwerveDrivetrain 239 240 return CommandSwerveDrivetrain ( 241 cls . drivetrain_constants , 242 [ 243 cls . front_left , 244 cls . front_right , 245 cls . back_left , 246 cls . back_right , 247 ], 248 ) 249 250 251 class TunerSwerveDrivetrain ( 252 swerve . SwerveDrivetrain [ hardware . TalonFX , hardware . TalonFX , hardware . CANcoder ] 253 ): 254 \"\"\"Swerve Drive class utilizing CTR Electronics' Phoenix 6 API with the selected device types.\"\"\" 255 256 @overload 257 def __init__ ( 258 self , 259 drivetrain_constants : swerve . SwerveDrivetrainConstants , 260 modules : list [ swerve . SwerveModuleConstants ], 261 / , 262 ) -> None : 263 \"\"\" 264 Constructs a CTRE SwerveDrivetrain using the specified constants. 265 266 This constructs the underlying hardware devices, so users should not construct 267 the devices themselves. If they need the devices, they can access them through 268 getters in the classes. 269 270 :param drivetrain_constants: Drivetrain-wide constants for the swerve drive 271 :type drivetrain_constants: swerve.SwerveDrivetrainConstants 272 :param modules: Constants for each specific module 273 :type modules: list[swerve.SwerveModuleConstants] 274 \"\"\" 275 ... 276 277 @overload 278 def __init__ ( 279 self , 280 drivetrain_constants : swerve . SwerveDrivetrainConstants , 281 odometry_update_frequency : units . hertz , 282 modules : list [ swerve . SwerveModuleConstants ], 283 / , 284 ) -> None : 285 \"\"\" 286 Constructs a CTRE SwerveDrivetrain using the specified constants. 287 288 This constructs the underlying hardware devices, so users should not construct 289 the devices themselves. If they need the devices, they can access them through 290 getters in the classes. 291 292 :param drivetrain_constants: Drivetrain-wide constants for the swerve drive 293 :type drivetrain_constants: swerve.SwerveDrivetrainConstants 294 :param odometry_update_frequency: The frequency to run the odometry loop. If 295 unspecified or set to 0 Hz, this is 250 Hz on 296 CAN FD, and 100 Hz on CAN 2.0. 297 :type odometry_update_frequency: units.hertz 298 :param modules: Constants for each specific module 299 :type modules: list[swerve.SwerveModuleConstants] 300 \"\"\" 301 ... 302 303 @overload 304 def __init__ ( 305 self , 306 drivetrain_constants : swerve . SwerveDrivetrainConstants , 307 odometry_update_frequency : units . hertz , 308 odometry_standard_deviation : tuple [ float , float , float ], 309 vision_standard_deviation : tuple [ float , float , float ], 310 modules : list [ swerve . SwerveModuleConstants ], 311 / , 312 ) -> None : 313 \"\"\" 314 Constructs a CTRE SwerveDrivetrain using the specified constants. 315 316 This constructs the underlying hardware devices, so users should not construct 317 the devices themselves. If they need the devices, they can access them through 318 getters in the classes. 319 320 :param drivetrain_constants: Drivetrain-wide constants for the swerve drive 321 :type drivetrain_constants: swerve.SwerveDrivetrainConstants 322 :param odometry_update_frequency: The frequency to run the odometry loop. If 323 unspecified or set to 0 Hz, this is 250 Hz on 324 CAN FD, and 100 Hz on CAN 2.0. 325 :type odometry_update_frequency: units.hertz 326 :param odometry_standard_deviation: The standard deviation for odometry calculation 327 in the form [x, y, theta]ᵀ, with units in meters 328 and radians 329 :type odometry_standard_deviation: tuple[float, float, float] 330 :param vision_standard_deviation: The standard deviation for vision calculation 331 in the form [x, y, theta]ᵀ, with units in meters 332 and radians 333 :type vision_standard_deviation: tuple[float, float, float] 334 :param modules: Constants for each specific module 335 :type modules: list[swerve.SwerveModuleConstants] 336 \"\"\" 337 ... 338 339 @overload 340 def __init__ ( 341 self , 342 drivetrain_constants : swerve . SwerveDrivetrainConstants , 343 arg0 : None , 344 arg1 : None , 345 arg2 : None , 346 arg3 : None , 347 / , 348 ) -> None : ... 349 350 def __init__ ( 351 self , 352 drivetrain_constants : swerve . SwerveDrivetrainConstants , 353 arg0 = None , 354 arg1 = None , 355 arg2 = None , 356 arg3 = None , 357 ): 358 swerve . SwerveDrivetrain . __init__ ( 359 self , 360 hardware . TalonFX , 361 hardware . TalonFX , 362 hardware . CANcoder , 363 drivetrain_constants , 364 arg0 , 365 arg1 , 366 arg2 , 367 arg3 , 368 )",
+ "content_preview": "Swerve Builder API To simplify the API surface, both builder and factory paradigms are used. Users create a SwerveDrivetrain by first defining the global drivetrain characteristics and then each module characteristics. Note Phoenix 6 supports the Java units library when applicable."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/device-specific/talonfx/open-loop-requests.html",
- "title": "Open",
- "section": "TalonFX",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/canivore/canivore-api.html",
+ "title": "CANivore API",
+ "section": "CANivore",
"language": "All",
- "content": "Open-Loop Control Open-Loop control typically refers to directly controlling device output. There are open-loop control requests for all TalonFX control output types. With the exception of FOC-only control requests, all open-loop control requests follow the naming pattern {ControlOutputType}Out . For example, the open-loop Voltage control request is called VoltageOut . FOC-only control requests follow the naming pattern {ControlOutputType} . In the below example, note that devices are initialized with two arguments. These arguments correspond to the device ID and CAN bus name. For CANivore, this is the name of the CANivore as configured in Phoenix Tuner X . For more information, see the CANivore API documentation. Java // initialize devices on the rio can bus final CANBus kCANBus = CANBus . roboRIO (); final TalonFX m_leftLeader = new TalonFX ( 0 , kCANBus ); final TalonFX m_rightLeader = new TalonFX ( 1 , kCANBus ); // users should reuse control requests when possible final DutyCycleOut m_leftRequest = new DutyCycleOut ( 0.0 ); final DutyCycleOut m_rightRequest = new DutyCycleOut ( 0.0 ); // retrieve joystick inputs var forward = - m_driverJoy . getLeftY (); var turn = m_driverJoy . getRightX (); // calculate motor outputs, utilizes a \"arcade\" style of driving; // where left Y controls forward and right X controls rotation/turn var leftOut = forward + turn ; var rightOut = forward - turn ; // set request to motor controller m_leftLeader . setControl ( m_leftRequest . withOutput ( leftOut )); m_rightLeader . setControl ( m_rightRequest . withOutput ( rightOut )); C++ // initialize devices on the rio can bus static constexpr CANBus kCANBus = CANBus :: RoboRIO (); hardware :: TalonFX m_leftLeader { 0 , kCANBus }; hardware :: TalonFX m_rightLeader { 1 , kCANBus }; // users should reuse control requests when possible controls :: DutyCycleOut m_leftRequest { 0.0 }; controls :: DutyCycleOut m_rightRequest { 0.0 }; // retrieve joystick inputs auto forward = - m_driverJoy . GetLeftY (); auto turn = m_driverJoy . GetRightX (); // calculate motor outputs, utilizes a \"arcade\" style of driving; // where left Y controls forward and right X controls rotation/turn auto leftOut = forward + turn ; auto rightOut = forward - turn ; // set request to motor controller m_leftLeader . SetControl ( m_leftRequest . WithOutput ( leftOut )); m_rightLeader . SetControl ( m_rightRequest . WithOutput ( rightOut )); Python # initialize devices on the rio can bus self . canbus = CANBus . roborio () self . left_leader = hardware . TalonFX ( 0 , self . canbus ) self . right_leader = hardware . TalonFX ( 1 , self . canbus ) # users should reuse control requests when possible self . left_request = controls . DutyCycleOut ( 0.0 ) self . right_request = controls . DutyCycleOut ( 0.0 ) # retrieve joystick inputs forward = - self . driver_joy . getLeftY () turn = self . driver_joy . getRightX () # calculate motor outputs, utilizes a \"arcade\" style of driving # where left Y controls forward and right X controls rotation/turn left_out = forward + turn right_out = forward - turn # set request to motor controllers self . left_leader . set_control ( self . left_request . with_output ( left_out )) self . right_leader . set_control ( self . right_request . with_output ( right_out ))",
- "content_preview": "Open-Loop Control Open-Loop control typically refers to directly controlling device output. There are open-loop control requests for all TalonFX control output types. With the exception of FOC-only control requests, all open-loop control requests follow the naming pattern {ControlOutputType}Out ."
+ "content": "CANivore API All device constructors have an overload that takes a CANBus object ( Java , C++ , Python ). The native roboRIO CAN bus can be constructed using CANBus.roboRIO() . Otherwise, the CANBus constructor takes a string identifier. This identifier can be * to select the first available CANivore, or it can be a CANivore’s name or serial number. On non-FRC Linux systems, this string can also be a SocketCAN interface. Note If there are multiple CANivores with the same name, the system will use the first CANivore found. If no CAN bus string is passed into the constructor, or the CAN bus string is empty, the behavior is platform-dependent: roboRIO: use the roboRIO native CAN bus Windows: use the first CANivore found non-FRC Linux: use SocketCAN interface can0 Java final TalonFX fx_default = new TalonFX ( 0 ); // On roboRIO, this constructs a TalonFX on the RIO native CAN bus final TalonFX fx_rio = new TalonFX ( 1 , CANBus . roboRIO ()); // This also constructs a TalonFX on the RIO native CAN bus final TalonFX fx_drivebase = new TalonFX ( 0 , new CANBus ( \"Drivebase\" )); // This constructs a TalonFX on the CANivore bus named \"Drivebase\" final CANcoder cc_elevator = new CANcoder ( 0 , new CANBus ( \"Elevator\" )); // This constructs a CANcoder on the CANivore bus named \"Elevator\" C++ (Header) hardware :: TalonFX fx_default { 0 }; // On roboRIO, this constructs a TalonFX on the RIO native CAN bus hardware :: TalonFX fx_rio { 1 , CANBus :: RoboRIO ()}; // This also constructs a TalonFX on the RIO native CAN bus hardware :: TalonFX fx_drivebase { 0 , CANBus { \"Drivebase\" }}; // This constructs a TalonFX on the CANivore bus named \"Drivebase\" hardware :: CANcoder cc_elevator { 0 , CANBus { \"Elevator\" }}; // This constructs a CANcoder on the CANivore bus named \"Elevator\" Python self . _fx_default = hardware . TalonFX ( 0 ) # On roboRIO, this constructs a TalonFX on the RIO native CAN bus self . _fx_rio = hardware . TalonFX ( 1 , CANBus . roborio ()) # This also constructs a TalonFX on the RIO native CAN bus self . _fx_drivebase = hardware . TalonFX ( 0 , CANBus ( \"Drivebase\" )) # This constructs a TalonFX on the CANivore bus named \"Drivebase\" self . _cc_elevator = hardware . CANcoder ( 0 , CANBus ( \"Elevator\" )) # This constructs a CANcoder on the CANivore bus named \"Elevator\" The CANBus API can also be used to retrieve information about any given CAN bus, such as the bus utilization. Java // create a CAN bus for the CANivore named drivetrain final CANBus canbus = new CANBus ( \"drivetrain\" ); // construct a TalonFX on the CAN bus final TalonFX fx = new TalonFX ( 0 , canbus ); // retrieve bus utilization for the CAN bus CANBusStatus canInfo = canbus . getStatus (); float busUtil = canInfo . BusUtilization ; if ( busUtil > 0.8 ) { System . out . println ( \"CAN bus utilization is greater than 80%!\" ); } C++ // create a CAN bus for the CANivore named drivetrain CANBus canbus { \"drivetrain\" }; // construct a TalonFX on the CAN bus hardware :: TalonFX fx { 0 , canbus }; // retrieve bus utilization for the CANivore named drivetrain CANBus :: CANBusStatus canInfo = canbus . GetStatus (); float busUtil = canInfo . BusUtilization ; if ( busUtil > 0.8 ) { std :: cout << \"CAN bus utilization is greater than 80%!\" << std :: endl ; } Python # create a CAN bus for the CANivore named drivetrain self . _canbus = CANBus ( \"drivetrain\" ) # construct a TalonFX on the CAN bus self . _fx = hardware . TalonFX ( 0 , self . _canbus ) # retrieve bus utilization for the CANivore named drivetrain can_info = self . _canbus . get_status () bus_util = can_info . bus_utilization if bus_util > 0.8 : print ( \"CAN bus utilization is greater than 80%!\" ) CANivore Status Prints When working with CANivore CAN buses in a robot program, Phoenix prints some messages to report the state of the CANivore connection. These messages can be useful to debug connection issues (bad USB vs bad CAN) or report bugs to CTR Electronics. Connection Messages Message Connection Status CANbus Failed to Connect Could not connect to a CANivore with the given name or serial number CANbus Connected Successfully found and connected to the CANivore with the given name or serial number CANbus Disconnected Detected that a CANivore USB device has been disconnected CANivore Bring-up Messages (Linux only) Message Bring-up Status CANbus Failed Bring-up Found and connected to the CANivore, but could not configure the device or start the network CANbus Successfully Started Successfully configured the CANivore and started the network Network State Messages Message Network State CANbus Network Down Linux: The SocketCAN network has been deactivated, USB-to-CAN activity has stopped Windows: Could not open the communication channels for USB-to-CAN traffic CANbus Network Up Linux: The SocketCAN network has been activated, USB-to-CAN activity has resumed Windows: Successfully opened the communication channels for USB-to-CAN traffic",
+ "content_preview": "CANivore API All device constructors have an overload that takes a CANBus object ( Java , C++ , Python ). The native roboRIO CAN bus can be constructed using CANBus.roboRIO() . Otherwise, the CANBus constructor takes a string identifier."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/api-usage/status-signals.html",
- "title": "Status Signals",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/simulation/simulation-intro.html",
+ "title": "Introduction to Simulation",
"section": "API Reference",
"language": "All",
- "content": "Status Signals Signals represent live data reported by a device; these can be yaw, position, etc. To make use of the live data, users need to know the value, timestamp, latency, units, and error condition of the data. Additionally, users may need to synchronize with fresh data to minimize latency. Tip A Signal Logging API is available for logging received signals. This can be useful for any form of post analysis, including diagnosing issues after a match or using WPILib SysId . StatusSignal The StatusSignal ( Java , C++ , Python ) is a signal object that provides APIs to address all of the requirements listed above. The device object provides getters for all available signals. Each getter returns a StatusSignal that is typed appropriately for the signal. Note The device getters return a cached StatusSignal . As a result, frequently calling the getter does not influence RAM performance. Java // fetch with refresh var supplyVoltageSignal = m_device . getSupplyVoltage (); // fetch WITHOUT refresh var supplyVoltageSignal = m_device . getSupplyVoltage ( false ); C++ // fetch with refresh auto & supplyVoltageSignal = m_device . GetSupplyVoltage (); // fetch WITHOUT refresh auto & supplyVoltageSignal = m_device . GetSupplyVoltage ( false ); Python # fetch with refresh supply_voltage_signal = self . device . get_supply_voltage () # fetch WITHOUT refresh supply_voltage_signal = self . device . get_supply_voltage ( False ) The value of the signal can be retrieved from the StatusSignal by calling getValue() . Java // get the value as a unit type var supplyVoltage = supplyVoltageSignal . getValue (); // convert the unit type to our desired units double supplyVoltageVolts = supplyVoltage . in ( Volts ); // alternatively, get the value as a double in the documented canonical units double supplyVoltage = supplyVoltageSignal . getValueAsDouble (); C++ // get the value as a unit type auto supplyVoltage = supplyVoltageSignal . GetValue (); // pull out the underlying value from the unit type double supplyVoltageVolts = supplyVoltage . value (); // alternatively, get the value as a double in the documented canonical units double supplyVoltage = supplyVoltageSignal . GetValueAsDouble (); Python supply_voltage = supply_voltage_signal . value Note Phoenix 6 utilizes the Java units library and C++ units library when applicable. Using the Java units library may increase GC overhead. The StatusCode ( Java , C++ , Python ) of the signal can be retrieved by calling getStatus() . This can be used to determine if the device is not present on the CAN bus. Note If a status signal is not available on the CAN bus, an error will be reported to the Driver Station. Refreshing the Signal Value The device StatusSignal getters implicitly refresh the cached signal values by default. However, if the user application caches the StatusSignal object or passes in false to the device signal getters, the refresh() method must be called to fetch fresh data. Multiple signals can be refreshed in one call using BaseStatusSignal.refreshAll() ( Java , C++ , Python ), which can improve performance compared to individual refreshes. Tip The refresh() method can be method-chained. As a result, you can call refresh() and getValue() on one line. Java // refresh the supply voltage signal supplyVoltageSignal . refresh (); // refresh the position and velocity signals BaseStatusSignal . refreshAll ( positionSignal , velocitySignal ); // refresh an array or List of differential signals BaseStatusSignal [] signals = { diffPositionSignal , diffVelocitySignal }; BaseStatusSignal . refreshAll ( signals ); C++ // refresh the supply voltage signal supplyVoltageSignal . Refresh (); // refresh the position and velocity signals BaseStatusSignal :: RefreshAll ( positionSignal , velocitySignal ); // refresh a std::span of differential signals std :: vector < BaseStatusSignal *> signals { & diffPositionSignal , & diffVelocitySignal }; BaseStatusSignal :: RefreshAll ( signals ); Python # refresh the supply voltage signal supply_voltage_signal . refresh () # refresh the position and velocity signals BaseStatusSignal . refresh_all ( position_signal , velocity_signal ) # refresh a list of differential signals signals = [ diff_position_signal , diff_velocity_signal ] BaseStatusSignal . refresh_all ( signals ) Waiting for Signal Updates Instead of using the latest value, the user can instead opt to synchronously wait for a signal update. StatusSignal provides a waitForUpdate(timeoutSec) method that will block the current robot loop until the signal is retrieved or the timeout has been exceeded. This replaces the need to call refresh() on cached StatusSignal objects. Tip If you want to zero your sensors, you can use this API to ensure the set operation has completed before continuing program flow. Tip The waitForUpdate() method can be method-chained. As a result, you can call waitForUpdate() and getValue() on one line. Java // wait up to 1 robot loop iteration (20ms) for fresh data supplyVoltageSignal . waitForUpdate ( 0.020 ); C++ // wait up to 1 robot loop iteration (20ms) for fresh data supplyVoltageSignal . WaitForUpdate ( 20 _ms ); Python # wait up to 1 robot loop iteration (20ms) for fresh data supply_voltage_signal . wait_for_update ( 0.020 ) Changing Update Frequency All signals can have their update frequency configured via the setUpdateFrequency() method. Additionally, the update frequency of multiple signals can be specified at once using BaseStatusSignal.setUpdateFrequencyForAll() ( Java , C++ , Python ). Warning Increasing signal frequency will also increase CAN bus utilization, which can cause indeterminate behavior at high utilization rates (>90%). This is less of a concern when using CANivore, which uses the higher-bandwidth CAN FD bus. Java // disable supply voltage reporting (0 Hz) supplyVoltageSignal . setUpdateFrequency ( 0 ); // speed up position and velocity reporting to 200 Hz BaseStatusSignal . setUpdateFrequencyForAll ( 200 , positionSignal , velocitySignal ); // speed up array or List of differential signals to 100 Hz BaseStatusSignal [] signals = { diffPositionSignal , diffVelocitySignal }; BaseStatusSignal . setUpdateFrequencyForAll ( 100 , signals ); C++ // disable supply voltage reporting (0 Hz) supplyVoltageSignal . SetUpdateFrequency ( 0 _Hz ); // speed up position and velocity reporting to 200 Hz BaseStatusSignal :: SetUpdateFrequencyForAll ( 200 _Hz , positionSignal , velocitySignal ); // speed up std::span of differential signals to 100 Hz std :: vector < BaseStatusSignal *> signals { & diffPositionSignal , & diffVelocitySignal }; BaseStatusSignal :: SetUpdateFrequencyForAll ( 100 _Hz , signals ); Python # disable supply voltage reporting (0 Hz) supply_voltage_signal . set_update_frequency ( 0 ) # speed up position and velocity reporting to 200 Hz BaseStatusSignal . set_update_frequency_for_all ( 200 , position_signal , velocity_signal ) # speed up list of differential signals to 100 Hz signals = [ diff_position_signal , diff_velocity_signal ] BaseStatusSignal . set_update_frequency_for_all ( 100 , signals ) When different update frequencies are specified for signals that share a status frame, the highest update frequency of all the relevant signals will be applied to the entire frame. Users can get a signal’s applied update frequency using the getAppliedUpdateFrequency() method. Signal update frequencies are automatically reapplied by the robot program on device reset. Optimizing Bus Utilization For users that wish to disable or slow down every unused status signal for their devices to reduce bus utilization, device objects have an optimizeBusUtilization() method ( Java , C++ , Python ). Additionally, multiple devices can be optimized at once using ParentDevice.optimizeBusUtilizationForAll() ( Java , C++ , Python ). When optimizing the bus utilization for devices, all status signals that have not been given an update frequency using setUpdateFrequency() will be disabled or slowed down. This results in an opt-in model for status signals, maximizing the reduction in bus utilization. Tip The update frequency of optimized signals can be specified ( Java , C++ , Python ), where 0 Hz completely disables the signals. The default update frequency is 4 Hz, ensuring that all signals are available when using Signal Logging . Warning When using followers, the leader motor must keep the DutyCycle , MotorVoltage , and TorqueCurrent status signals enabled. Additionally, remote sensors must keep related status signals enabled (such as position and velocity). Java m_pigeon . optimizeBusUtilization (); ParentDevice . optimizeBusUtilizationForAll ( m_leftMotor , m_rightMotor , m_cancoder ); C++ m_pigeon . OptimizeBusUtilization (); hardware :: ParentDevice :: OptimizeBusUtilizationForAll ( m_leftMotor , m_rightMotor , m_cancoder ); Python self . pigeon . optimize_bus_utilization () hardware . ParentDevice . optimize_bus_utilization_for_all ( self . left_motor , self . right_motor , self . cancoder ) Resetting All to Default The update frequencies of all status signals for a device can be reset to the defaults by calling resetSignalFrequencies() on the device ( Java , C++ , Python ). Additionally, multiple devices can be reset at once using ParentDevice.resetSignalFrequenciesForAll() ( Java , C++ , Python ). Since devices typically maintain their configured status signal update frequencies until they are power cycled, this can be useful to restore everything to the defaults before reconfiguring update frequencies. Java m_pigeon . resetSignalFrequencies (); ParentDevice . resetSignalFrequenciesForAll ( m_leftMotor , m_rightMotor , m_cancoder ); C++ m_pigeon . ResetSignalFrequencies (); hardware :: ParentDevice :: ResetSignalFrequenciesForAll ( m_leftMotor , m_rightMotor , m_cancoder ); Python self . pigeon . reset_signal_frequencies () hardware . ParentDevice . reset_signal_frequencies_for_all ( self . left_motor , self . right_motor , self . cancoder ) Timestamps StatusSignals can have multiple timestamps associated from them, as there are often multiple sources of time. Users can call getTimestamp() ( Java , C++ , Python ) to return the “best” timestamp determined by Phoenix. getAllTimestamps() can also be used to return all timestamps associated with a given StatusSignal , which returns a collection of Timestamp ( Java , C++ , Python ) objects. The Timestamp objects can be used to perform latency compensation math. CANivore Timesync Important CANivore Timesync requires the devices or the CANivore to be Pro licensed . When using CANivore , the attached CAN devices will automatically synchronize their time bases. This allows devices to sample and publish their signals in a synchronized manner. Users can synchronously wait for these signals to update using BaseStatusSignal.waitForAll() ( Java , C++ , Python ). Tip waitForAll() with a timeout of zero matches the behavior of refreshAll() , performing a non-blocking refresh on all signals passed in. Because the devices are synchronized, time-critical signals are sampled and published on the same schedule. This combined with the waitForAll() routine means applications can considerably reduce the latency of the timesync signals. This is particularly useful for multi-device mechanisms, such as swerve odometry. Note When using a non-zero timeout, the signals passed into waitForAll() should have the same update frequency for synchronous data acquisition. This can be done by calling setUpdateFrequency() or by referring to the API documentation. The diagram below demonstrates the benefits of using timesync to synchronously acquire signals from multiple devices. Check the API documentation for information on whether a status signal supports CANivore Timesync. Java var talonFXPositionSignal = m_talonFX . getPosition ( false ); var cancoderPositionSignal = m_cancoder . getPosition ( false ); var pigeon2YawSignal = m_pigeon2 . getYaw ( false ); BaseStatusSignal . waitForAll ( 0.020 , talonFXPositionSignal , cancoderPositionSignal , pigeon2YawSignal ); // can also send down an array or List of signals BaseStatusSignal [] signals = { talonFXPositionSignal , cancoderPositionSignal , pigeon2YawSignal }; BaseStatusSignal . waitForAll ( 0.020 , signals ); C++ auto & talonFXPositionSignal = m_talonFX . GetPosition ( false ); auto & cancoderPositionSignal = m_cancoder . GetPosition ( false ); auto & pigeon2YawSignal = m_pigeon2 . GetYaw ( false ); BaseStatusSignal :: WaitForAll ( 20 _ms , talonFXPositionSignal , cancoderPositionSignal , pigeon2YawSignal ); // can also send down a std::span of signal pointers std :: vector < BaseStatusSignal *> signals { & talonFXPositionSignal , & cancoderPositionSignal , & pigeon2YawSignal }; BaseStatusSignal :: WaitForAll ( 20 _ms , signals ); Python talonfx_position_signal = self . talonfx . get_position ( False ) cancoder_position_signal = self . cancoder . get_position ( False ) pigeon2_yaw_signal = self . pigeon2 . get_yaw ( False ) BaseStatusSignal . wait_for_all ( 0.020 , talonfx_position_signal , cancoder_position_signal , pigeon2_yaw_signal ) # can also send down a list of signals signals = [ talonfx_position_signal , cancoder_position_signal , pigeon2_yaw_signal ] BaseStatusSignal . wait_for_all ( 0.020 , signals ) Latency Compensation Users can perform latency compensation using BaseStatusSignal.getLatencyCompensatedValue() ( Java , C++ , Python ). Important getLatencyCompensatedValue() does not automatically refresh the signals. As a result, the user must ensure the signal and signalSlope parameters are refreshed before retrieving a compensated value. Java double compensatedTurns = BaseStatusSignal . getLatencyCompensatedValue ( m_motor . getPosition (), m_motor . getVelocity () ); C++ auto compensatedTurns = BaseStatusSignal :: GetLatencyCompensatedValue ( m_motor . GetPosition (), m_motor . GetVelocity () ); Python compensated_turns = BaseStatusSignal . get_latency_compensated_value ( self . motor . get_position (), self . motor . get_velocity () ) StatusSignalCollection The StatusSignalCollection ( Java , C++ , Python ) class provides a lightweight wrapper around a list of status signals on a common network. This simplifies the process of refreshing or waiting on multiple status signals. Java final StatusSignalCollection signals = new StatusSignalCollection (); // register all the signals we want to refresh (on the same network) signals . addSignals ( m_talonFX . getPosition ( false ), m_cancoder . getPosition ( false ), m_pigeon2 . getYaw ( false ) ); // set all the signals to a 200 Hz update frequency signals . setUpdateFrequencyForAll ( Hertz . of ( 200 )); // now wait on all the signals in the collection signals . waitForAll ( 0.010 ); C++ StatusSignalCollection signals {}; // register all the signals we want to refresh (on the same network) signals . AddSignals ( m_talonFX . GetPosition ( false ), m_cancoder . GetPosition ( false ), m_pigeon2 . GetYaw ( false ) ); // set all the signals to a 200 Hz update frequency signals . SetUpdateFrequencyForAll ( 200 _Hz ); // now wait on all the signals in the collection signals . WaitForAll ( 10 _ms ); Python self . signals = StatusSignalCollection () # register all the signals we want to refresh (on the same network) self . signals . add_signals ( self . talonfx . get_position ( False ), self . cancoder . get_position ( False ), self . pigeon2 . get_yaw ( False ) ) # set all the signals to a 200 Hz update frequency self . signals . set_update_frequency_for_all ( 200.0 ) # now wait on all the signals in the collection self . signals . wait_for_all ( 0.010 ) SignalMeasurement All StatusSignal objects have a getDataCopy() method that returns a new SignalMeasurement ( Java , C++ ) object. SignalMeasurement is a Passive Data Structure that provides all the information about a signal at the time of the getDataCopy() call, which can be useful for data logging. Warning getDataCopy() returns a new SignalMeasurement object every call. Java users should avoid using this API in RAM-constrained applications.",
- "content_preview": "Status Signals Signals represent live data reported by a device; these can be yaw, position, etc. To make use of the live data, users need to know the value, timestamp, latency, units, and error condition of the data. Additionally, users may need to synchronize with fresh data to minimize latency."
- },
- {
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-elev/generation.html",
- "title": "Generation",
- "section": "Phoenix Tuner X",
- "language": "All",
- "content": "Generation The subsystem is generated directly into an existing robot project . Select Browser and navigate to the root of a robot project. Then, press Generate . Open the robot project in WPILib VS Code.",
- "content_preview": "Generation The subsystem is generated directly into an existing robot project . Select Browser and navigate to the root of a robot project. Then, press Generate . Open the robot project in WPILib VS Code."
+ "content": "Introduction to Simulation Many CTR Electronics devices support high-fidelity simulation, allowing the simulated robot to match the behavior of the real robot hardware as closely as possible. This makes simulation a powerful tool to quickly diagnose and fix bugs in robot code without relying on access to hardware. Supported Devices Currently, all Phoenix 6 devices are supported in simulation. Warning Multiple CAN buses using the CANivore API is not supported at this time. All CAN devices will appear on the same CAN bus . If you wish to run your robot code in simulation, ensure devices have unique IDs across CAN buses . Simulation API Each supported device has a device-specific SimState object that can be used to manage I/O with the simulated device. The object can be retrieved by calling getSimState() on an instance of a device. Java var talonFXSim = m_talonFX . getSimState (); C++ auto & talonFXSim = m_talonFX . GetSimState (); Python talon_fx_sim = self . talon_fx . sim_state Note Phoenix 6 utilizes the Java units library and C++ units library when applicable. In general, simulation logic should only interact with the SimState class and the physics simulator (such as WPILib’s DCMotorSim ), and the rest of the robot code should never interact with those simulation classes. This clear separation between simulation and hardware logic ensures that simulation most accurately reflects the behavior of the real robot hardware. Orientation The SimState API ignores typical device invert settings, as the user may change invert for any reason (such as flipping which direction is forward for a drivebase). As a result, for some devices, the SimState object supports specifying the orientation of the device relative to the robot chassis ( Java , C++ , Python ). This orientation represents the mechanical linkage between the device and the robot chassis. It should not be changed with runtime invert , as runtime invert specifies the logical orientation of the device. Rather, the orientation should only be modified when the mechanical linkage itself changes , such as when switching between two gearboxes inverted from each other. Java var leftTalonFXSim = m_leftTalonFX . getSimState (); var rightTalonFXSim = m_rightTalonFX . getSimState (); // left drivetrain motors are typically CCW+ leftTalonFXSim . Orientation = ChassisReference . CounterClockwise_Positive ; // right drivetrain motors are typically CW+ rightTalonFXSim . Orientation = ChassisReference . Clockwise_Positive ; C++ auto & leftTalonFXSim = m_leftTalonFX . GetSimState (); auto & rightTalonFXSim = m_rightTalonFX . GetSimState (); // left drivetrain motors are typically CCW+ leftTalonFXSim . Orientation = sim :: ChassisReference :: CounterClockwise_Positive ; // right drivetrain motors are typically CW+ rightTalonFXSim . Orientation = sim :: ChassisReference :: Clockwise_Positive ; Python left_talon_fx_sim = self . left_talon_fx . sim_state right_talon_fx_sim = self . right_talon_fx . sim_state # left drivetrain motors are typically CCW+ left_talon_fx_sim . orientation = sim . ChassisReference . COUNTER_CLOCKWISE_POSITIVE # right drivetrain motors are typically CW+ right_talon_fx_sim . orientation = sim . ChassisReference . CLOCKWISE_POSITIVE Inputs and Outputs All SimState objects contain multiple inputs to manipulate the state of the device based on simulation physics calculations. For example, all device SimState objects have a supply voltage input: Important Non-FRC platforms are required to set supply voltage, as it affects simulation calculations. It’s recommended that FRC users set supply voltage to RobotController.getBatteryVoltage() ( Java , C++ ) to take advantage of WPILib’s BatterySim ( Java , C++ ) API. Java // set the supply voltage of the TalonFX to 12 V m_talonFXSim . setSupplyVoltage ( Volts . of ( 12 )); C++ // set the supply voltage of the TalonFX to 12 V m_talonFXSim . SetSupplyVoltage ( 12 _V ); Python # set the supply voltage of the TalonFX to 12 V self . talon_fx_sim . set_supply_voltage ( 12.0 ) Some device SimState objects also contain outputs that can be used in simulation physics calculations. For example, the TalonFXSimState ( Java , C++ , Python ) object has a motor voltage output that can be used to calculate position and velocity: Important For all motor controllers, the RawRotorPosition and RotorVelocity values must be set for simulated PID and current limits to behave correctly. Additionally, simulated PID more closely matches hardware when updating the SimState more frequently, such as using a WPILib Notifier . Java private static final double kGearRatio = 10.0 ; private final DCMotorSim m_motorSimModel = new DCMotorSim ( LinearSystemId . createDCMotorSystem ( DCMotor . getKrakenX60Foc ( 1 ), 0.001 , kGearRatio ), DCMotor . getKrakenX60Foc ( 1 ) ); public void simulationInit () { var talonFXSim = m_talonFX . getSimState (); talonFXSim . Orientation = ChassisReference . CounterClockwise_Positive ; talonFXSim . setMotorType ( TalonFXSimState . MotorType . KrakenX60 ); } public void simulationPeriodic () { var talonFXSim = m_talonFX . getSimState (); // set the supply voltage of the TalonFX talonFXSim . setSupplyVoltage ( RobotController . getBatteryVoltage ()); // get the motor voltage of the TalonFX var motorVoltage = talonFXSim . getMotorVoltageMeasure (); // use the motor voltage to calculate new position and velocity // using WPILib's DCMotorSim class for physics simulation m_motorSimModel . setInputVoltage ( motorVoltage . in ( Volts )); m_motorSimModel . update ( 0.020 ); // assume 20 ms loop time // apply the new rotor position and velocity to the TalonFX; // note that this is rotor position/velocity (before gear ratio), but // DCMotorSim returns mechanism position/velocity (after gear ratio) talonFXSim . setRawRotorPosition ( m_motorSimModel . getAngularPosition (). times ( kGearRatio )); talonFXSim . setRotorVelocity ( m_motorSimModel . getAngularVelocity (). times ( kGearRatio )); } C++ static constexpr double kGearRatio = 10.0 ; frc :: sim :: DCMotorSim m_motorSimModel { frc :: LinearSystemId :: DCMotorSystem { frc :: DCMotor :: KrakenX60FOC ( 1 ), 0.001 _kg_sq_m , kGearRatio }, frc :: DCMotor :: KrakenX60FOC ( 1 ) }; void SimulationInit () { auto & talonFXSim = m_talonFX . GetSimState (); talonFXSim . Orientation = sim :: ChassisReference :: CounterClockwise_Positive ; talonFXSim . SetMotorType ( sim :: TalonFXSimState :: MotorType :: KrakenX60 ); } void SimulationPeriodic () { auto & talonFXSim = m_talonFX . GetSimState (); // set the supply voltage of the TalonFX talonFXSim . SetSupplyVoltage ( frc :: RobotController :: GetBatteryVoltage ()); // get the motor voltage of the TalonFX auto motorVoltage = talonFXSim . GetMotorVoltage (); // use the motor voltage to calculate new position and velocity // using WPILib's DCMotorSim class for physics simulation m_motorSimModel . SetInputVoltage ( motorVoltage ); m_motorSimModel . Update ( 20 _ms ); // assume 20 ms loop time // apply the new rotor position and velocity to the TalonFX; // note that this is rotor position/velocity (before gear ratio), but // DCMotorSim returns mechanism position/velocity (after gear ratio) talonFXSim . SetRawRotorPosition ( kGearRatio * m_motorSimModel . GetAngularPosition ()); talonFXSim . SetRotorVelocity ( kGearRatio * m_motorSimModel . GetAngularVelocity ()); } Python GEAR_RATIO = 10.0 def __init__ ( self ): gearbox = DCMotor . krakenX60FOC ( 1 ) self . motor_sim_model = DCMotorSim ( LinearSystemId . DCMotorSystem ( gearbox , 0.001 , GEAR_RATIO ), gearbox ) def simulationInit ( self ): talon_fx_sim = self . talon_fx . sim_state talon_fx_sim . orientation = sim . ChassisReference . COUNTER_CLOCKWISE_POSITIVE talon_fx_sim . set_motor_type ( sim . TalonFXSimState . MotorType . KRAKEN_X60 ) def simulationPeriodic ( self ): talon_fx_sim = self . talon_fx . sim_state # set the supply voltage of the TalonFX talon_fx_sim . set_supply_voltage ( RobotController . getBatteryVoltage ()) # get the motor voltage of the TalonFX motor_voltage = talon_fx_sim . motor_voltage # use the motor voltage to calculate new position and velocity # using WPILib's DCMotorSim class for physics simulation self . motor_sim_model . setInputVoltage ( motor_voltage ) self . motor_sim_model . update ( 0.020 ) # assume 20 ms loop time # apply the new rotor position and velocity to the TalonFX; # note that this is rotor position/velocity (before gear ratio), but # DCMotorSim returns mechanism position/velocity (after gear ratio) talon_fx_sim . set_raw_rotor_position ( GEAR_RATIO * units . radiansToRotations ( self . motor_sim_model . getAngularPosition ()) ) talon_fx_sim . set_rotor_velocity ( GEAR_RATIO * units . radiansToRotations ( self . motor_sim_model . getAngularVelocity ()) ) High Fidelity CAN Bus Simulation As a part of high-fidelity simulation, the influence of the CAN bus is simulated at a level similar to what happens on a real robot. This means that the timing behavior of control and status signals in simulation will align to the same framing intervals seen on a real CAN bus. In simulation, this may appear as a delay between setting a signal and getting its real value, or between setting its real value and getting it in API. In unit tests, it may be useful to increase the update rate of status signals to avoid erroneous failures and minimize delays. The update rate can be modified for simulation by wrapping the signal update frequency in a Utils.isSimulation() ( Java , C++ , Python ) condition. Java if ( Utils . isSimulation ()) { // set update rate to 1ms for unit tests m_velocitySignal . setUpdateFrequency ( Hertz . of ( 1000 )); } C++ if ( utils :: IsSimulation ()) { // set update rate to 1ms for unit tests m_velocitySignal . SetUpdateFrequency ( 1000 _Hz ); } Python if utils . is_simulation (): # set update rate to 1ms for unit tests self . velocity_signal . set_update_frequency ( 1000.0 )",
+ "content_preview": "Introduction to Simulation Many CTR Electronics devices support high-fidelity simulation, allowing the simulated robot to match the behavior of the real robot hardware as closely as possible."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-elev/setup.html",
- "title": "Setup",
- "section": "Phoenix Tuner X",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/api-usage/api-overview.html",
+ "title": "API Overview",
+ "section": "API Reference",
"language": "All",
- "content": "Setup Prerequisites The elevator generator and generated project make a few assumption. To determine if the elevator generator is the best fit for your mechanism, consult the following checklist. The elevator is a single or two gearbox mechanism. All gearboxes on the elevator consist of the same gearing. All gearboxes on the elevator have the same number of motors. If using two gearboxes, ensure that the orientation of the gearbox’s are identical. All motors on the elevator are identical. Initial Configuration Setup is done with two steps. 1. Choosing your Gearbox The supported gearbox types are Single and Dual . 2. Configuration A typical elevator is composed of a number of motors, driving a gearbox which spins a spool that drives the elevator up or down. The generated Elevator subsystem will automatically handle conversion between raw mechanism rotations to linear units (inches, meters, feet, etc). To handle this scenario, certain constants cannot be automatically determined. Users will need to input the Drum Radius (in) , Gear Ratio , and Num Motors . Once this has been done, a list of motor controllers will be populated per gearbox.",
- "content_preview": "Setup Prerequisites The elevator generator and generated project make a few assumption. To determine if the elevator generator is the best fit for your mechanism, consult the following checklist. The elevator is a single or two gearbox mechanism."
+ "content": "API Overview The Phoenix 6 API resides in the com.ctre.phoenix6 package in Java, the ctre::phoenix6 namespace in C++, and the phoenix6 module in Python. The API is then further organized into smaller packages and namespaces that group together similar types of classes and functions: configs - classes related to device configuration controls - classes related to device control hardware - the device hardware classes, such as TalonFX mechanisms - advanced multi-device mechanisms , such as DifferentialMechanism signals - enumeration types for device signals sim - classes related to device simulation swerve - classes related to the Phoenix 6 Swerve API C++ IntelliSense In C++, this namespace structure has the advantage of cleaning up IntelliSense when searching for classes: // first use the ctre::phoenix6 namespace using namespace ctre :: phoenix6 ; // now types are organized cleanly by namespace hardware :: TalonFX m_talonFX { 0 }; sim :: TalonFXSimState & m_talonFXSim { m_talonFX . GetSimState ()}; controls :: DutyCycleOut m_talonFXOut { 0 }; configs :: TalonFXConfiguration m_talonFXConfig {}; signals :: InvertedValue m_talonFXInverted { signals :: InvertedValue :: CounterClockwise_Positive }; All C++ code examples in this documentation will assume the presence of using namespace ctre::phoenix6; . Python Imports Python also takes advantage of the module structure to improve IntelliSense: # first import the relevant modules and types from phoenix6 import controls , configs , hardware , signals # now types are organized cleanly by module self . talonfx = hardware . TalonFX ( 0 ) self . talonfx_out = controls . DutyCycleOut ( 0 ) talonfx_configs = configs . TalonFXConfiguration () talonfx_inverted = signals . InvertedValue . COUNTER_CLOCKWISE_POSITIVE All Python code examples in this documentation will assume the presence of from phoenix6 import * . Thread Safety The vast majority of Phoenix 6 is thread-safe with a few exceptions. Objects that are not thread-safe include: StatusSignal objects Calling the same device StatusSignal getter (e.g. TalonFX.getVelocity() ) from multiple threads is unsafe. This is because device signal getters refresh the StatusSignal implicitly. Users should clone or copy a StatusSignal object to get a unique instance for a given thread. Config objects Includes TalonFX.setInverted() and TalonFX.setNeutralMode() However, device Configurator objects and other setters (e.g. TalonFX.setPosition() ) are thread-safe. Control objects However, sending a control request to a device is thread-safe.",
+ "content_preview": "API Overview The Phoenix 6 API resides in the com.ctre.phoenix6 package in Java, the ctre::phoenix6 namespace in C++, and the phoenix6 module in Python."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/migration/migration-guide/feature-replacements-guide.html",
- "title": "Feature Replacements",
- "section": "General",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/wpilib-integration/sysid-integration/index.html",
+ "title": "SysId Integration",
+ "section": "API Reference",
"language": "All",
- "content": "Feature Replacements In addition to the changes shown in the other sections, several other Phoenix 5 features have been replaced or improved upon in Phoenix 6. Motor Invert In Phoenix 6, motor invert is now a persistent config ( Java , C++ ) instead of a control signal. Warning Since invert is a persistent config, getting and setting motor inverts are now blocking API calls. We recommend that users only set the invert once at program startup. Neutral Mode In Phoenix 6, Neutral mode is now available in API as a config ( Java , C++ ). Many control requests also have the ability to override the neutral mode to either force braking ( Java , C++ ) or force coasting ( Java , C++ ). Nominal Output The Talon FX forward and reverse Nominal Output configs have been removed in Phoenix 6. The typical use case of the nominal output configs is to overcome friction in closed-loop control modes, which can now be achieved using the kS feedforward parameter ( Java , C++ ). Sensor Phase The Talon FX setSensorPhase() method has been removed in Phoenix 6. The Talon FX integrated sensor is always in phase, so the method does nothing in Phoenix 5. When using a remote sensor, you can invert the remote sensor to bring it in phase with the Talon FX. Sensor Initialization Strategy The Talon FX and CANcoder sensors are always initialized to their absolute position in Phoenix 6. Clear Position on Limit In Phoenix 5, users could configure the TalonFX to clear its sensor position (i.e. set to 0) when a limit switch is triggered. In Phoenix 6, this feature has been improved to allow users to specify the applied sensor position when a limit switch is triggered. This can be configured using the *LimitAutosetPositionValue configs ( Java , C++ ). Velocity Measurement Period/Window In Phoenix 6, the velocity rolling average window in Talon FX and CANcoder has been replaced with a Kalman filter, resulting in a less noisy velocity signal with a minimal impact on latency (~1 ms). As a result, the velocity measurement period/window configs are no longer necessary in Phoenix 6 and have been removed. Integral Zone and Max Integral Accumulator Phoenix 6 automatically prevents integral windup in closed-loop controls. As a result, the Integral Zone and Max Integral Accumulator configs are no longer necessary and have been removed. CANcoder Sensor Coefficient and Units In Phoenix 6, CANcoder does not support setting a custom sensor coefficient, unit string, and sensor time base. Instead, the CANcoder uses canonical units of rotations and rotations per second using the C++ units library . Features to Be Implemented The following Phoenix 5 features are not implemented in the current release of Phoenix 6 but are planned to be implemented in the future. Feature Status CANdle Support Normal priority Features Omitted The following Phoenix 5 features have been omitted from Phoenix 6. While there are no plans for these features to be added, if there is customer demand for these features, they may be considered for addition in the future. Feedback is welcome at feedback @ ctr-electronics . com . Motion Profile Executor Control requests have been improved to cover many of the use cases of the Motion Profile Executor. Allowable Closed-Loop Error",
- "content_preview": "Feature Replacements In addition to the changes shown in the other sections, several other Phoenix 5 features have been replaced or improved upon in Phoenix 6. Motor Invert In Phoenix 6, motor invert is now a persistent config ( Java , C++ ) instead of a control signal."
+ "content": "SysId Integration System Identification, or commonly referred to as SysId, is the process of identifying the characteristics of a given system . This identification usually consists of: Mechanism testing In FRC, two tests are performed: Quasistatic and Dynamic. Data collection Position , Velocity , and MotorOutput samples are collected while the tests are running. Data analysis Collected data is analyzed to calculate constants such as PID gains, slip current (maximum stator current), maximum robot velocity, etc. Note This documentation assumes that the user is utilizing a command-based robot program . Advantages of SignalLogger over DataLog When collecting data for analysis, it’s important to take into account several factors, such as: Impact of CAN latency Signals sent faster than the 20ms main robot loop Language data collection issues (such as Java garbage collection causing pauses in the log) When users utilize the Phoenix 6 signal logging API , these issues are eliminated. This section guides the user through characterizing a motor, converting hoot logs to a WPILib WPILOG for data analysis, and integrating gains for control. This section can also be used as a characterizing other mechanisms such as a swerve azimuth and drive motors. Characterization begins with a functioning robot program. Users should have basic code for the mechanism already put together, and all configs in the FeedbackConfigs group should be applied. Any changes to the gear ratios and sensor source in FeedbackConfigs may require the user to recharacterize their mechanism. Get started: Plumbing & Running SysId",
+ "content_preview": "SysId Integration System Identification, or commonly referred to as SysId, is the process of identifying the characteristics of a given system . This identification usually consists of: Mechanism testing In FRC, two tests are performed: Quasistatic and Dynamic."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/device-list.html",
- "title": "Device List",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/signal-and-control.html",
+ "title": "Multi",
"section": "Phoenix Tuner X",
"language": "All",
- "content": "Device List Card Layout Grid Layout The Devices page is the first page that is shown to the user upon launching the application. The Devices page by default shows a grid of cards, but can be changed to a flat grid view (similar to Phoenix Tuner v1) by clicking on the 4 grid square icon located in the top right corner (not available in Android Tuner X). Card Colors The color of the device cards is helpful as a visual indicator of device state. The meaning of the card color is also shown as text underneath the device title. Color Description Green Device has latest firmware. Purple Device has an unexpected/beta firmware version. Yellow A new firmware version is available. Check the changelog to determine if the new version matters to your application Red Device has a duplicate ID. Blue Failed to retrieve list of available firmware. Clipboard Options & Licensing Phoenix Tuner X provides icons at the bottom right of each card that will allow the user to copy to the clipboard the device details, configs and Self Test. This can be useful for support requests and additional debugging. Devices that support CAN FD are shown via a CAN FD icon in the bottom right of the card. Note The CAN FD icon does not indicate that the device is currently on a CAN FD bus, merely that it supports CAN FD. The other major icon in the bottom right of the device card is the licensing indicator. This showcases the licensing states and when clicked, will open the licensing dialog. Batch Field Upgrade Phoenix Tuner X allows the user to batch field upgrade from the Devices page. The user can either select devices by their checkbox (in the top right corner of their respective card) or by selecting the checkmark icon in the top right. Tip Selecting a device using their checkbox and clicking the checkmark in the top right will select all devices of the same models Step 1 in the above image selects all devices of the same models selected (or all devices if no device is currently check-boxed). Step 2 in the above image opens the field-upgrade dialog. Once the dialog is opened, information detailing the device name, model, ID, and firmware version is presented. There is a year selector in the top-left corner to select the firmware version year. Once the correct firmware year is selected user can begin the upgrade progress by selecting Update to latest . If the user does not want to use the latest firmware version, the Custom year selection allows for the selection of a specific firmware version for each device model. Tip Generally, users should update their devices to the latest available firmware version. If manually selecting a CRF is important, the firmware files are available for download on our GitHub Repo . Important While the user can cancel firmware upgrading using the “X” button in the top-right, this will not cancel the current device in progress. It will finish upgrading the current device and will not upgrade subsequent devices. Typical Tuner X behavior will resume once the current device finishes flashing. Batch Licensing See Batch Activating Licenses",
- "content_preview": "Device List Card Layout Grid Layout The Devices page is the first page that is shown to the user upon launching the application. The Devices page by default shows a grid of cards, but can be changed to a flat grid view (similar to Phoenix Tuner v1) by clicking on the 4 grid square icon located in..."
+ "content": "Multi-device Plot & Control Multiple devices can be controlled and plotted simultaneously using the Signal & Control tab in Phoenix Tuner X. The interface mirrors the existing device details page, with dropdowns or comboboxes for choosing devices in a specific context. The Signal & Control interface can be incredibly useful when tuning subsystems with multiple motors. Adding Devices Devices can be added or removed to the instance using the Devices tab on the left-hand side.",
+ "content_preview": "Multi-device Plot & Control Multiple devices can be controlled and plotted simultaneously using the Signal & Control tab in Phoenix Tuner X. The interface mirrors the existing device details page, with dropdowns or comboboxes for choosing devices in a specific context."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/mechanisms/swerve/using-swerve-api.html",
- "title": "Using the Swerve Drivetrain",
- "section": "API Reference",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/hardware-reference/pigeon2/pigeon-issues.html",
+ "title": "Pigeon 2 Troubleshooting",
+ "section": "Pigeon 2",
"language": "All",
- "content": "Using the Swerve Drivetrain In addition to control and simulation , the SwerveDrivetrain ( Java , C++ , Python ) has many other APIs to manage the built-in pose estimator, telemetry, and more. Changing Neutral Mode The neutral mode of the drive motors can be reconfigured at runtime using configNeutralMode ( Java , C++ , Python ). Tip The neutral mode can be applied on construction by modifying the DriveMotorInitialConfigs and SteerMotorInitialConfigs provided to the SwerveModuleConstants . Java // The drivetrain was constructed in brake mode, switch to coast drivetrain . configNeutralMode ( NeutralModeValue . Coast ); C++ // The drivetrain was constructed in brake mode, switch to coast drivetrain . ConfigNeutralMode ( signals :: NeutralModeValue :: Coast ); Python # The drivetrain was constructed in brake mode, switch to coast self . drivetrain . config_neutral_mode ( signals . NeutralModeValue . COAST ) Using Field-Centric Control In field-centric control, the robot is driven using velocities relative to the field. This makes it so the forward direction is constant (typically away from the driver) regardless of the robot orientation. There are two common field coordinate systems: Blue Alliance Perspective (which are absolute coordinates) and Operator Perspective (which are alliance-relative coordinates). For field-centric control to behave correctly, the drivetrain needs to know which directions the driver and the robot are facing. Setting the Operator Perspective The OperatorPerspective is typically used during teleop control, ensuring that forward (+X) is always away from the driver. The forward direction for OperatorPerspective can be set using setOperatorPerspectiveForward ( Java , C++ , Python ). This tells the drivetrain which direction the driver is facing. Looking at the Blue Alliance Perspective coordinates, facing away from the blue alliance is a heading of 0 degrees . On the other hand, the red alliance is flipped, so facing away from the red alliance is a heading of 180 degrees . Important When using CommandSwerveDrivetrain from our examples or Tuner X, this is already handled by the subsystem. Java /* Blue alliance sees forward as 0 degrees (toward red alliance wall) */ private static final Rotation2d kBlueAlliancePerspectiveRotation = Rotation2d . kZero ; /* Red alliance sees forward as 180 degrees (toward blue alliance wall) */ private static final Rotation2d kRedAlliancePerspectiveRotation = Rotation2d . k180deg ; /* Keep track if we've ever applied the operator perspective before or not */ private boolean m_hasAppliedOperatorPerspective = false ; @Override public void periodic () { // Periodically try to apply the operator perspective // if we haven't yet or if we're currently disabled. if ( ! m_hasAppliedOperatorPerspective || DriverStation . isDisabled ()) { DriverStation . getAlliance (). ifPresent ( allianceColor -> { setOperatorPerspectiveForward ( allianceColor == Alliance . Red ? kRedAlliancePerspectiveRotation : kBlueAlliancePerspectiveRotation ); m_hasAppliedOperatorPerspective = true ; }); } } C++ /* Blue alliance sees forward as 0 degrees (toward red alliance wall) */ static constexpr frc :: Rotation2d kBlueAlliancePerspectiveRotation { 0 _deg }; /* Red alliance sees forward as 180 degrees (toward blue alliance wall) */ static constexpr frc :: Rotation2d kRedAlliancePerspectiveRotation { 180 _deg }; /* Keep track if we've ever applied the operator perspective before or not */ bool m_hasAppliedOperatorPerspective = false ; void Periodic () override { // Periodically try to apply the operator perspective // if we haven't yet or if we're currently disabled. if ( ! m_hasAppliedOperatorPerspective || frc :: DriverStation :: IsDisabled ()) { auto const allianceColor = frc :: DriverStation :: GetAlliance (); if ( allianceColor ) { SetOperatorPerspectiveForward ( * allianceColor == frc :: DriverStation :: Alliance :: kRed ? kRedAlliancePerspectiveRotation : kBlueAlliancePerspectiveRotation ); m_hasAppliedOperatorPerspective = true ; } } } Python _BLUE_ALLIANCE_PERSPECTIVE_ROTATION = Rotation2d . fromDegrees ( 0 ) \"\"\"Blue alliance sees forward as 0 degrees (toward red alliance wall)\"\"\" _RED_ALLIANCE_PERSPECTIVE_ROTATION = Rotation2d . fromDegrees ( 180 ) \"\"\"Red alliance sees forward as 180 degrees (toward blue alliance wall)\"\"\" def __init__ ( self , ... ): # ... self . _has_applied_operator_perspective = False \"\"\"Keep track if we've ever applied the operator perspective before or not\"\"\" def periodic ( self ): # Periodically try to apply the operator perspective # if we haven't yet or if we're currently disabled. if not self . _has_applied_operator_perspective or DriverStation . isDisabled (): alliance_color = DriverStation . getAlliance () if alliance_color is not None : self . set_operator_perspective_forward ( self . _RED_ALLIANCE_PERSPECTIVE_ROTATION if alliance_color == DriverStation . Alliance . kRed else self . _BLUE_ALLIANCE_PERSPECTIVE_ROTATION ) self . _has_applied_operator_perspective = True Setting the Robot Heading After setting the operator perspective, the drivetrain also needs to know which direction the robot is facing. Note Many path planning libraries automatically reset the full pose of the robot, including heading, at the start of the path. Current Direction is Forward If the robot is currently facing the driver’s forward direction, call seedFieldCentric() ( Java , C++ , Python ) to reset the heading. Tip The Tuner X generated swerve project and our examples bind seedFieldCentric() to the left bumper. Java // Reset the field-centric heading on left bumper press. joystick . leftBumper (). onTrue ( drivetrain . runOnce ( drivetrain :: seedFieldCentric )); C++ // reset the field-centric heading on left bumper press joystick . LeftBumper (). OnTrue ( drivetrain . RunOnce ([ this ] { drivetrain . SeedFieldCentric (); })); Python # reset the field-centric heading on left bumper press self . _joystick . leftBumper () . onTrue ( self . drivetrain . runOnce ( self . drivetrain . seed_field_centric ) ) Angle Relative to Forward If the robot is facing some other angle relative to the driver’s forward direction, call seedFieldCentric(Rotation2d) ( Java , C++ , Python ) with the relative angle. For example, if the robot is facing left, then pass in an angle of +90 degrees (counter-clockwise). Tip The Tuner X generated swerve project calls seedFieldCentric(Rotation2d) at the start of the default autonomous command. Java // Reset the field-centric heading so the robot is facing left (90 deg CCW) drivetrain . seedFieldCentric ( Rotation2d . kCCW_90deg ); C++ // Reset the field-centric heading so the robot is facing left (+90 deg) drivetrain . SeedFieldCentric ( frc :: Rotation2d { 90 _deg }); Python # Reset the field-centric heading so the robot is facing left (+90 deg) self . drivetrain . seed_field_centric ( Rotation2d . fromDegrees ( 90 )) Blue Alliance Heading or Pose When using a path planning library such as PathPlanner or Choreo , the paths often operate using the BlueAlliancePerspective and reset the robot’s pose at the start of the path. Vision libraries similarly often operate using a BlueAlliancePerspective heading or pose. The robot’s heading can be reset to a BlueAlliancePerspective heading using resetRotation(Rotation2d) ( Java , C++ , Python ), and the pose can be reset using resetPose(Pose2d) ( Java , C++ , Python ). Tip PathPlanner and Choreo can call resetPose automatically at the start of the autonomous path. Java // Reset the robot's heading to 0 deg (away from the Blue Alliance wall) drivetrain . resetRotation ( Rotation2d . kZero ); // Reset the robot's pose to the initial pose of the autonomous path drivetrain . resetPose ( initialPose ); C++ // Reset the robot's heading to 0 deg (away from the Blue Alliance wall) drivetrain . ResetRotation ( frc :: Rotation2d {}); // Reset the robot's pose to the initial pose of the autonomous path drivetrain . ResetPose ( initialPose ); Python # Reset the robot's heading to 0 deg (away from the Blue Alliance wall) drivetrain . reset_rotation ( Rotation2d ()) # Reset the robot's pose to the initial pose of the autonomous path self . drivetrain . reset_pose ( initial_pose ) Odometry and State The SwerveDrivetrain has a built-in pose estimator running on a separate odometry thread (250 Hz on CANivore, 100 Hz on roboRIO). This significantly improves the accuracy and consistency of odometry and robot pose estimation. Information about the robot’s state can be retrieved using getState() ( Java , C++ , Python ), and a thread-safe copy can be retrieved using getStateCopy() ( Java , Python ). This returns a SwerveDriveState ( Java , C++ , Python ) instance that includes information such as the pose estimate, module states, and chassis speeds. Java var state = drivetrain . getState (); // pull out the pose estimate and chassis speeds Pose2d pose = state . Pose ; ChassisSpeeds speeds = state . Speeds ; C++ auto state = drivetrain . GetState (); // pull out the pose estimate and chassis speeds frc :: Pose2d pose = state . Pose ; frc :: ChassisSpeeds speeds = state . Speeds ; Python state = self . drivetrain . get_state () # pull out the pose estimate and chassis speeds pose = state . pose speeds = state . speeds Drivetrain Telemetry The state of the drivetrain can also be telemeterized inline with odometry updates, ensuring that all information is captured in logs. A telemetry function that accepts the latest SwerveDriveState as a parameter can be registered using registerTelemetry ( Java , C++ , Python ). Tip The Tuner X generated swerve project and our examples have a Telemetry class that is already registered with the drivetrain. Java public Robot () { drivetrain . registerTelemetry ( this :: telemeterize ); } /** Accept the swerve drive state and telemeterize it to SignalLogger. */ public void telemeterize ( SwerveDriveState state ) { SignalLogger . writeStruct ( \"DriveState/Pose\" , Pose2d . struct , state . Pose ); SignalLogger . writeStruct ( \"DriveState/Speeds\" , ChassisSpeeds . struct , state . Speeds ); SignalLogger . writeStructArray ( \"DriveState/ModuleStates\" , SwerveModuleState . struct , state . ModuleStates ); SignalLogger . writeStructArray ( \"DriveState/ModuleTargets\" , SwerveModuleState . struct , state . ModuleTargets ); SignalLogger . writeStructArray ( \"DriveState/ModulePositions\" , SwerveModulePosition . struct , state . ModulePositions ); SignalLogger . writeDouble ( \"DriveState/OdometryPeriod\" , state . OdometryPeriod , \"seconds\" ); } C++ Robot () { drivetrain . RegisterTelemetry ( [ this ]( auto const & state ) { Telemeterize ( state ); } ); } /** Accept the swerve drive state and telemeterize it to SignalLogger. */ void Telemeterize ( subsystems :: TunerSwerveDrivetrain :: SwerveDriveState const & state ) { SignalLogger :: WriteStruct ( \"DriveState/Pose\" , state . Pose ); SignalLogger :: WriteStruct ( \"DriveState/Speeds\" , state . Speeds ); SignalLogger :: WriteStructArray < frc :: SwerveModuleState > ( \"DriveState/ModuleStates\" , state . ModuleStates ); SignalLogger :: WriteStructArray < frc :: SwerveModuleState > ( \"DriveState/ModuleTargets\" , state . ModuleTargets ); SignalLogger :: WriteStructArray < frc :: SwerveModulePosition > ( \"DriveState/ModulePositions\" , state . ModulePositions ); SignalLogger :: WriteValue ( \"DriveState/OdometryPeriod\" , state . OdometryPeriod ); } Python def __init__ ( self ): # ... self . drivetrain . register_telemetry ( self . telemeterize ) def telemeterize ( self , state : swerve . SwerveDrivetrain . SwerveDriveState ): \"\"\" Accept the swerve drive state and telemeterize it to SignalLogger. \"\"\" SignalLogger . write_struct ( \"DriveState/Pose\" , Pose2d , state . pose ) SignalLogger . write_struct ( \"DriveState/Speeds\" , ChassisSpeeds , state . speeds ) SignalLogger . write_struct_array ( \"DriveState/ModuleStates\" , SwerveModuleState , state . module_states ) SignalLogger . write_struct_array ( \"DriveState/ModuleTargets\" , SwerveModuleState , state . module_targets ) SignalLogger . write_struct_array ( \"DriveState/ModulePositions\" , SwerveModulePosition , state . module_positions ) SignalLogger . write_double ( \"DriveState/OdometryPeriod\" , state . odometry_period , \"seconds\" )",
- "content_preview": "Using the Swerve Drivetrain In addition to control and simulation , the SwerveDrivetrain ( Java , C++ , Python ) has many other APIs to manage the built-in pose estimator, telemetry, and more."
+ "content": "Pigeon 2 Troubleshooting A functional limitation was discovered in Pigeon 2s manufactured in September of 2022. When used on a CANivore (CAN FD) Bus, the Pigeon 2 may not transmit CAN FD frames correctly. As a result, you may find that all CAN device LEDs go red when the Pigeon 2 is in-circuit and powered. A firmware fix has been published, to update the firmware of an affected Pigeon 2, one of the below options can be used. Option 1: Workaround with Tuner X Note If you do not see the below option, then Tuner X is likely older than version 2023.1.5.0 . A new section in Tuner X Settings labeled Pigeon 2 Workaround has been added. When the Execute Pigeon 2 workaround button is pressed, all CANivores will enter a special mode that allows them to see the offending Pigeon 2s. This mode is reverted when the CANivore is power cycled. Once the workaround has been applied, the device will show up in the Devices menu and the LED should be alternating green/orange. Field-upgrade the firmware version and power cycle the CANivore. Option 2: Connect to the roboRIO Bus Connect the Pigeon 2 to the roboRIO CAN Bus and field-upgrade the firmware version. Note We recommend power cycling Pigeon after moving CAN bus leads from CANivore to roboRIO CAN bus to ensure a clean transition.",
+ "content_preview": "Pigeon 2 Troubleshooting A functional limitation was discovered in Pigeon 2s manufactured in September of 2022. When used on a CANivore (CAN FD) Bus, the Pigeon 2 may not transmit CAN FD frames correctly."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/simulation/index.html",
- "title": "Simulation",
- "section": "API Reference",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/device-specific/talonfx/closed-loop-requests.html",
+ "title": "Closed",
+ "section": "TalonFX",
"language": "All",
- "content": "Simulation Phoenix 6 supports comprehensive simulation support. All hardware features are available in simulation, including configs, control requests, simulated CAN bus timing, and Phoenix Tuner X support. Introduction to Simulation",
- "content_preview": "Simulation Phoenix 6 supports comprehensive simulation support. All hardware features are available in simulation, including configs, control requests, simulated CAN bus timing, and Phoenix Tuner X support. Introduction to Simulation"
+ "content": "Closed-Loop Overview Closed-loop control typically refers to control of a motor that relies on sensor data to adjust based on error. Systems/mechanisms that rely on maintaining a certain position or velocity achieve this state using closed-loop control. This is achieved by feedback (PID) and feedforward control. Closed-loop control can be performed on the robot controller or on the individual motor controllers. The benefits of onboard closed-loop control are that there is no sensor latency, and the closed-loop controller has a 1 kHz update frequency. This can result in a more responsive output compared to running the closed-loop on the robot controller. Since closed-loop control changes based on the dynamics of the system (velocity, mass, CoG, etc.), closed-loop relies on PID and feedforward parameters. These parameters are configured either via Tuner Configs or in code . The parameters can be determined using System Identification (such as with WPILib SysId ) or through manual tuning . Manual tuning typically follows this process: Set all gains to zero. Determine \\(K_g\\) if using an elevator or arm . Select the appropriate Static Feedforward Sign for your closed-loop type. Increase \\(K_s\\) until just before the motor moves. If using velocity setpoints, increase \\(K_v\\) until the output velocity closely matches the velocity setpoints. Increase \\(K_p\\) until the output starts to oscillate around the setpoint. Increase \\(K_d\\) as much as possible without introducing jittering to the response. All closed-loop control requests follow the naming pattern {ClosedLoopMode}{ControlOutputType} . For example, the VelocityVoltage control request performs a velocity closed-loop using voltage output. Choosing Output Type The choice of control output type can affect the reproducibility and stability of the closed-loop control. DutyCycle has the benefit of being the simplest control output type, as it is unaffected by voltage and current measurements. However, because DutyCycle represents a proportion of the supply voltage, changes in battery voltage can affect the reproducibility of the control request. Voltage control output takes into account the supply voltage to ensure its voltage output remains consistent. As a result, Voltage control often results in more stable and reproducible behavior compared to DutyCycle control, so Voltage control is often preferred. A disadvantage with both DutyCycle and Voltage control output types is that they control acceleration indirectly and require a velocity feedforward \\(K_v\\) to hold a constant velocity. On the other hand, torque-based control output types, such as TorqueCurrentFOC, directly control acceleration , which has several advantages: Since the torque request is directly proportional to acceleration, \\(K_v\\) is generally unnecessary. A torque output of 0 corresponds to a constant velocity, assuming no external forces. \\(K_a\\) can be tuned independently of all the other closed-loop gains by comparing the measured acceleration with the requested acceleration. Because the output is in units of torque, the units of the gains more closely match those of forces in the real world. As a result, torque-based control output types offer more stable and reproducible behavior that can be easier to tune compared to the other control output types. Gain Slots It may be useful to switch between presets of gains in a motor controller, so the TalonFX supports multiple gain slots. All closed-loop control requests have a member variable Slot that can be assigned an integer ID to select the set of gains used by the closed-loop. The gain slots can be configured in code using Slot*Configs ( Java , C++ , Python ) objects. Gravity Feedforward The gravity feedforward \\(K_g\\) is the output necessary to overcome gravity, in units of the control output type . Phoenix 6 supports the two most common use cases for \\(K_g\\) —elevators and arms—using the GravityType config in the gain slots. Elevator/Static For systems with a constant gravity component, such as an elevator, \\(K_g\\) adds a constant value to the closed-loop output. To find \\(K_g\\) , determine the output necessary to hold the elevator at a constant height in open-loop control. Arm/Cosine For systems with an angular gravity component, such as an arm, the output of \\(K_g\\) is dependent on the cosine of the angle between the arm and horizontal. The value of \\(K_g\\) can be found by determining the output necessary to hold the arm horizontally forward. Since the arm \\(K_g\\) uses the angle of the arm relative to horizontal, the Talon FX often requires an absolute sensor whose position is 1:1 with the arm, and the sensor offset and ratios must be configured. When using an absolute sensor, such as a CANcoder, the sensor offset must be configured such that a position of 0 represents the arm being held horizontally forward. From there, the RotorToSensor ratio must be configured to the ratio between the absolute sensor and the Talon FX rotor. Some arm mechanisms have a center of gravity that is offset from the zero position. The GravityArmPositionOffset config can be adjusted to account for this offset within ±0.25 rotations. Static Feedforward Sign The static feedforward \\(K_s\\) is the output needed to overcome the system’s static friction, in units of the control output type . Because friction always opposes the direction of motion, the sign of \\(K_s\\) also depends on the direction of motion. Phoenix 6 provides two possible methods of determining this signage using the StaticFeedforwardSign config in the gain slots. Velocity Sign By default, the signage of \\(K_s\\) is determined by the signage of the velocity setpoint. In other words, if the velocity setpoint is positive, then the output of \\(K_s\\) is positive; if the velocity setpoint is negative, then \\(K_s\\) is negative. This option is always used when running velocity closed loops, and it is recommended for Motion Magic® controls and motion-profiled position closed loops. Closed-Loop Sign When using a position closed-loop controller, signage of \\(K_s\\) can instead be determined by the sign of the closed-loop error. For example, if the position error (target - measured) is positive, then the output of \\(K_s\\) is positive; if the error is negative, then \\(K_s\\) is negative. This option is typically used when a velocity setpoint is otherwise not available, such as when running unprofiled position closed loops. Important When using the sign of closed-loop error for \\(K_s\\) , it is important that the selected \\(K_s\\) value is not too large. Otherwise, the motor output may dither or oscillate when near the closed-loop target. Converting from Meters In some applications, it may be useful to translate between meters and rotations. This can be done using the following equation: \\[rotations = \\frac{meters}{2 \\pi \\cdot wheelRadius} \\cdot gearRatio\\] where meters is the target in meters, wheelRadius is the radius of the wheel in meters, and gearRatio is the gear ratio between the output shaft and the wheel. This equation also works with converting velocity from m/s to rps or acceleration from m/s² to rps/s. Continuous Mechanism Wrap A continuous mechanism is a mechanism with unlimited travel in any direction, and whose rotational position can be represented with multiple unique position values. Some examples of continuous mechanisms are swerve drive steer mechanisms or turrets (without cable management). ContinuousWrap ( Java , C++ , Python ) is a mode of closed loop operation that enables the Talon to take the “shortest path” to a target position for a continuous mechanism. It does this by assuming that the mechanism is continuous within 1 rotation. For example, if a Talon is currently at 2.1 rotations, it knows this is equivalent to every position that is exactly 1.0 rotations away from each other (3.1, 1.1, 0.1, -0.9, etc.). If that Talon is then commanded to a position of 0.8 rotations, instead of driving backwards 1.3 rotations or forwards 0.7 rotations, it will drive backwards 0.3 rotations to a target of 1.8 rotations. Note The ContinuousWrap config only affects the closed loop operation. Other signals such as Position are unaffected by this config. In order to use this feature, the FeedbackConfigs ( Java , C++ , Python ) ratio configs must be configured so that the mechanism is properly described. An example is provided below, where there is a continuous mechanism with a 12.8:1 speed reduction between the rotor and mechanism.",
+ "content_preview": "Closed-Loop Overview Closed-loop control typically refers to control of a motor that relies on sensor data to adjust based on error. Systems/mechanisms that rely on maintaining a certain position or velocity achieve this state using closed-loop control."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/device-specific/talonfx/remote-sensors.html",
- "title": "TalonFX Remote Sensors",
- "section": "TalonFX",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/hardware-reference/pigeon2/index.html",
+ "title": "Pigeon 2.0",
+ "section": "Pigeon 2",
"language": "All",
- "content": "TalonFX Remote Sensors The TalonFX supports various remote sensors. Remote sensors allow onboard closed-loop functionality at rates faster than a traditional robot processor (~1Khz) by reading the remote sensor directly from the CAN bus. This allows supported motor controllers to execute closed-loop modes with sensor values sourced by supported sensors. A list of supported remote sensors can be found in the API docs ( Java , C++ , Python ). Remote sensors can be configured using Tuner X or via code. This document highlights how to configure a remote sensor in a robot program. RemoteCANcoder A supported motor controller will update its position and velocity whenever the CANcoder publishes its information on the CAN bus. Java var fx_cfg = new TalonFXConfiguration (); fx_cfg . Feedback . FeedbackRemoteSensorID = m_cancoder . getDeviceID (); fx_cfg . Feedback . FeedbackSensorSource = FeedbackSensorSourceValue . RemoteCANcoder ; m_talonFX . getConfigurator (). apply ( fx_cfg ); C++ configs :: TalonFXConfiguration fx_cfg {}; fx_cfg . Feedback . FeedbackRemoteSensorID = m_cancoder . GetDeviceID (); fx_cfg . Feedback . FeedbackSensorSource = signals :: FeedbackSensorSourceValue :: RemoteCANcoder ; m_talonFX . GetConfigurator (). Apply ( fx_cfg ); Python fx_cfg = configs . TalonFXConfiguration () fx_cfg . feedback . feedback_remote_sensor_id = self . cancoder . device_id fx_cfg . feedback . feedback_sensor_source = signals . FeedbackSensorSourceValue . REMOTE_CANCODER self . talonfx . configurator . apply ( fx_cfg ) FusedCANcoder Important This feature requires the device to be Pro licensed . When unlicensed, the TalonFX will fall back to RemoteCANcoder and trip the UsingFusedCANcoderWhileUnlicensed fault. New in Phoenix 6 is a feedback sensor type called FusedCANcoder . FusedCANcoder will fuse another CANcoder’s information with the motor’s internal rotor, which provides the best possible position and velocity for accuracy and bandwidth. This is useful in applications such as swerve azimuth. FusedCANcoder requires the configuration of several Feedback config group items, shown below. Full example: Java , C++ Java 60 /* Configure CANcoder to zero the magnet appropriately */ 61 CANcoderConfiguration cc_cfg = new CANcoderConfiguration (); 62 cc_cfg . MagnetSensor . AbsoluteSensorRange = AbsoluteSensorRangeValue . Signed_PlusMinusHalf ; 63 cc_cfg . MagnetSensor . SensorDirection = SensorDirectionValue . CounterClockwise_Positive ; 64 cc_cfg . MagnetSensor . withMagnetOffset ( Rotations . of ( 0.4 )); 65 m_cc . getConfigurator (). apply ( cc_cfg ); 66 67 TalonFXConfiguration fx_cfg = new TalonFXConfiguration (); 68 fx_cfg . Feedback . FeedbackRemoteSensorID = m_cc . getDeviceID (); 69 fx_cfg . Feedback . FeedbackSensorSource = FeedbackSensorSourceValue . FusedCANcoder ; 70 fx_cfg . Feedback . SensorToMechanismRatio = 1.0 ; 71 fx_cfg . Feedback . RotorToSensorRatio = 12.8 ; 72 73 m_fx . getConfigurator (). apply ( fx_cfg ); C++ 11 /* Configure CANcoder to zero the magnet appropriately */ 12 configs :: CANcoderConfiguration cc_cfg {}; 13 cc_cfg . MagnetSensor . AbsoluteSensorRange = signals :: AbsoluteSensorRangeValue :: Signed_PlusMinusHalf ; 14 cc_cfg . MagnetSensor . SensorDirection = signals :: SensorDirectionValue :: CounterClockwise_Positive ; 15 cc_cfg . MagnetSensor . MagnetOffset = 0.4 _tr ; 16 m_cc . GetConfigurator (). Apply ( cc_cfg ); 17 18 configs :: TalonFXConfiguration fx_cfg {}; 19 fx_cfg . Feedback . FeedbackRemoteSensorID = m_cc . GetDeviceID (); 20 fx_cfg . Feedback . FeedbackSensorSource = signals :: FeedbackSensorSourceValue :: FusedCANcoder ; 21 fx_cfg . Feedback . SensorToMechanismRatio = 1.0 ; 22 fx_cfg . Feedback . RotorToSensorRatio = 12.8 ; 23 24 m_fx . GetConfigurator (). Apply ( fx_cfg ); Python cc_cfg = configs . CANcoderConfiguration () cc_cfg . magnet_sensor . absolute_sensor_range = signals . AbsoluteSensorRangeValue . SIGNED_PLUS_MINUS_HALF cc_cfg . magnet_sensor . sensor_direction = signals . SensorDirectionValue . COUNTER_CLOCKWISE_POSITIVE cc_cfg . magnet_sensor . magnet_offset = 0.4 self . cc . configurator . apply ( cc_cfg ) fx_cfg = configs . TalonFXConfiguration () fx_cfg . feedback . feedback_remote_sensor_id = self . cc . device_id fx_cfg . feedback . feedback_sensor_source = signals . FeedbackSensorSourceValue . FUSED_CANCODER fx_cfg . feedback . sensor_to_mechanism_ratio = 1.0 fx_cfg . feedback . rotor_to_sensor_ratio = 12.8 self . fx . configurator . apply ( fx_cfg ) Usage is the same as any status signal : Java fx_pos . refresh (); cc_pos . refresh (); System . out . println ( \"FX Position: \" + fx_pos . toString ()); System . out . println ( \"CANcoder Position: \" + cc_pos . toString ()); C++ fx_pos . Refresh (); cc_pos . Refresh (); std :: cout << \"FX Position: \" << fx_pos << std :: endl ; std :: cout << \"CANcoder Position: \" << cc_pos << std :: endl ; Python fx_pos . refresh () cc_pos . refresh () print ( \"FX Position: \" + fx_pos . value ) print ( \"CANcoder Position: \" + cc_pos . value ) SyncCANcoder Important This feature requires the device to be Pro licensed . When unlicensed, the TalonFX will fall back to RemoteCANcoder and trip the UsingFusedCANcoderWhileUnlicensed fault. SyncCANcoder allows users to synchronize the TalonFX’s internal rotor sensor against the remote CANcoder, but continue to use the rotor sensor for all closed loop control. TalonFX will continue to monitor the remote CANcoder and report if its internal position differs significantly from the reported position or if the remote CANcoder disappears from the bus. Users may want SyncCANcoder instead of FusedCANcoder if there is risk that the sensor can fail in a way that the sensor is still reporting “good” data, but the data does not match the mechanism, such as if the entire sensor mount assembly breaks off. Using SyncCANcoder over FusedCANcoder will not benefit from backlash compensation, as the CANcoder position is not continually fused in. SyncCANcoder requires the configuration of several Feedback config group items, shown below. Java /* Configure CANcoder to zero the magnet appropriately */ CANcoderConfiguration cc_cfg = new CANcoderConfiguration (); cc_cfg . MagnetSensor . AbsoluteSensorRange = AbsoluteSensorRangeValue . Signed_PlusMinusHalf ; cc_cfg . MagnetSensor . SensorDirection = SensorDirectionValue . CounterClockwise_Positive ; cc_cfg . MagnetSensor . MagnetOffset = 0.4 ; m_cc . getConfigurator (). apply ( cc_cfg ); TalonFXConfiguration fx_cfg = new TalonFXConfiguration (); fx_cfg . Feedback . FeedbackRemoteSensorID = m_cc . getDeviceID (); fx_cfg . Feedback . FeedbackSensorSource = FeedbackSensorSourceValue . SyncCANcoder ; fx_cfg . Feedback . SensorToMechanismRatio = 1.0 ; fx_cfg . Feedback . RotorToSensorRatio = 12.8 ; m_fx . getConfigurator (). apply ( fx_cfg ); C++ /* Configure CANcoder to zero the magnet appropriately */ configs :: CANcoderConfiguration cc_cfg {}; cc_cfg . MagnetSensor . AbsoluteSensorRange = signals :: AbsoluteSensorRangeValue :: Signed_PlusMinusHalf ; cc_cfg . MagnetSensor . SensorDirection = signals :: SensorDirectionValue :: CounterClockwise_Positive ; cc_cfg . MagnetSensor . MagnetOffset = 0.4 ; m_cc . GetConfigurator (). Apply ( cc_cfg ); configs :: TalonFXConfiguration fx_cfg {}; fx_cfg . Feedback . FeedbackRemoteSensorID = m_cc . GetDeviceID (); fx_cfg . Feedback . FeedbackSensorSource = signals :: FeedbackSensorSourceValue :: SyncCANcoder ; fx_cfg . Feedback . SensorToMechanismRatio = 1.0 ; fx_cfg . Feedback . RotorToSensorRatio = 12.8 ; m_fx . GetConfigurator (). Apply ( fx_cfg ); Python cc_cfg = configs . CANcoderConfiguration () cc_cfg . magnet_sensor . absolute_sensor_range = signals . AbsoluteSensorRangeValue . SIGNED_PLUS_MINUS_HALF cc_cfg . magnet_sensor . sensor_direction = signals . SensorDirectionValue . COUNTER_CLOCKWISE_POSITIVE cc_cfg . magnet_sensor . magnet_offset = 0.4 self . cc . configurator . apply ( cc_cfg ) fx_cfg = configs . TalonFXConfiguration () fx_cfg . feedback . feedback_remote_sensor_id = self . cc . device_id fx_cfg . feedback . feedback_sensor_source = signals . FeedbackSensorSourceValue . SYNC_CANCODER fx_cfg . feedback . sensor_to_mechanism_ratio = 1.0 fx_cfg . feedback . rotor_to_sensor_ratio = 12.8 self . fx . configurator . apply ( fx_cfg )",
- "content_preview": "TalonFX Remote Sensors The TalonFX supports various remote sensors. Remote sensors allow onboard closed-loop functionality at rates faster than a traditional robot processor (~1Khz) by reading the remote sensor directly from the CAN bus."
+ "content": "Pigeon 2.0 Pigeon 2.0 is the next evolution in the family of Pigeon IMUs. With no on-boot calibration or temperature calibration required and dramatic improvement to drift, the Pigeon is the easiest IMU to use yet. Pigeon 2 Troubleshooting Store Page CAD and purchase instructions. https://store.ctr-electronics.com/pigeon-2/ Hardware User Manual Wiring and mount instructions in PDF format. https://store.ctr-electronics.com/content/user-manual/Pigeon2%20User’s%20Guide.pdf Status Light Reference Important If the status lights do not exactly match any of the blink codes below, the device may be alternating between multiple blink codes (most commonly between good and bad CAN). Blink Codes Animation (Click to play) LED State Cause Possible Fix LEDs Off No Power Provide 12V to Red/Black leads. Blinking Alternating Red Pigeon 2 does not have valid CAN. Ensure good connections between CANH and CANL (Yellow and Green) & robot controller is on. Blinking Alternating Orange Pigeon 2 detects CAN but does not see Phoenix running on the robot controller. If Phoenix is running on the robot controller, ensure good connection between the controller and this device. Otherwise, deploy a robot program that uses Phoenix. Blinking Simultaneous Orange Pigeon 2 detects CAN and sees the robot is disabled. Phoenix is running in robot controller and Pigeon 2 has good CAN connection to robot controller. Blinking Alternating Green Pigeon 2 detects CAN and sees the robot is enabled. Alternate Red/Orange Damaged Hardware. Use Tuner X Self Test to confirm the LEDs and that the hardware fault is set, then contact CTRE Single LED alternates Green/Orange Pigeon 2 in bootloader. Field-upgrade device in Tuner X. Mount Calibration It’s recommended to perform a mount calibration when placement of the Pigeon 2.0 has been finalized. This can be done via the Calibration page in Tuner X.",
+ "content_preview": "Pigeon 2.0 Pigeon 2.0 is the next evolution in the family of Pigeon IMUs. With no on-boot calibration or temperature calibration required and dramatic improvement to drift, the Pigeon is the easiest IMU to use yet. Pigeon 2 Troubleshooting Store Page CAD and purchase instructions."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-swerve/creating-your-project.html",
- "title": "Creating your Project",
- "section": "Phoenix Tuner X",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/api-usage/status-signals.html",
+ "title": "Status Signals",
+ "section": "API Reference",
"language": "All",
- "content": "Creating your Project Wheel Radius (inches) The radius can be found by measuring the width of the module wheel, then dividing that by 2. FL to FR distance (inches) This is the distance between the center of the front-left module, and the center of the front-right module. FL to BL distance (inches) This is the distance between the center of the front-left module, and the center of the back-left module. Module Type The type of swerve module, such as WCP Swerve X standard, flipped gear, or flipped belt. Users not using any of the supported modules should select Custom instead. Drive Ratio This is the gearing ratio between the output shaft of the motor and the module wheel. Swerve X users can find that information here . Steer Ratio (Custom) This is the gearing ratio between the output shaft of the steering motor and the azimuth gear. For the Custom module type, users must calculate this based on their gearing themselves, or consult their manufacturer. Import Project Import an existing Tuner X swerve project save file. New Project Create a new project based on the settings configured. Users should configure the settings applicable for their robot and click New Project once they are done. Tip Throughout the application is various tooltips, that when you hover on them, provide instructions. If you are unsure on what something means, try hovering on it! Wizard Options Once a project is open, a couple of options are exposed at the top-right. In order from left to right: Factory default all devices Open the swerve settings menu Export project Exit project",
- "content_preview": "Creating your Project Wheel Radius (inches) The radius can be found by measuring the width of the module wheel, then dividing that by 2. FL to FR distance (inches) This is the distance between the center of the front-left module, and the center of the front-right module."
+ "content": "Status Signals Signals represent live data reported by a device; these can be yaw, position, etc. To make use of the live data, users need to know the value, timestamp, latency, units, and error condition of the data. Additionally, users may need to synchronize with fresh data to minimize latency. Tip A Signal Logging API is available for logging received signals. This can be useful for any form of post analysis, including diagnosing issues after a match or using WPILib SysId . StatusSignal The StatusSignal ( Java , C++ , Python ) is a signal object that provides APIs to address all of the requirements listed above. The device object provides getters for all available signals. Each getter returns a StatusSignal that is typed appropriately for the signal. Note The device getters return a cached StatusSignal . As a result, frequently calling the getter does not influence RAM performance. Java // fetch with refresh var supplyVoltageSignal = m_device . getSupplyVoltage (); // fetch WITHOUT refresh var supplyVoltageSignal = m_device . getSupplyVoltage ( false ); C++ // fetch with refresh auto & supplyVoltageSignal = m_device . GetSupplyVoltage (); // fetch WITHOUT refresh auto & supplyVoltageSignal = m_device . GetSupplyVoltage ( false ); Python # fetch with refresh supply_voltage_signal = self . device . get_supply_voltage () # fetch WITHOUT refresh supply_voltage_signal = self . device . get_supply_voltage ( False ) The value of the signal can be retrieved from the StatusSignal by calling getValue() . Java // get the value as a unit type var supplyVoltage = supplyVoltageSignal . getValue (); // convert the unit type to our desired units double supplyVoltageVolts = supplyVoltage . in ( Volts ); // alternatively, get the value as a double in the documented canonical units double supplyVoltage = supplyVoltageSignal . getValueAsDouble (); C++ // get the value as a unit type auto supplyVoltage = supplyVoltageSignal . GetValue (); // pull out the underlying value from the unit type double supplyVoltageVolts = supplyVoltage . value (); // alternatively, get the value as a double in the documented canonical units double supplyVoltage = supplyVoltageSignal . GetValueAsDouble (); Python supply_voltage = supply_voltage_signal . value Note Phoenix 6 utilizes the Java units library and C++ units library when applicable. Using the Java units library may increase GC overhead. The StatusCode ( Java , C++ , Python ) of the signal can be retrieved by calling getStatus() . This can be used to determine if the device is not present on the CAN bus. Note If a status signal is not available on the CAN bus, an error will be reported to the Driver Station. Refreshing the Signal Value The device StatusSignal getters implicitly refresh the cached signal values by default. However, if the user application caches the StatusSignal object or passes in false to the device signal getters, the refresh() method must be called to fetch fresh data. Multiple signals can be refreshed in one call using BaseStatusSignal.refreshAll() ( Java , C++ , Python ), which can improve performance compared to individual refreshes. Tip The refresh() method can be method-chained. As a result, you can call refresh() and getValue() on one line. Java // refresh the supply voltage signal supplyVoltageSignal . refresh (); // refresh the position and velocity signals BaseStatusSignal . refreshAll ( positionSignal , velocitySignal ); // refresh an array or List of differential signals BaseStatusSignal [] signals = { diffPositionSignal , diffVelocitySignal }; BaseStatusSignal . refreshAll ( signals ); C++ // refresh the supply voltage signal supplyVoltageSignal . Refresh (); // refresh the position and velocity signals BaseStatusSignal :: RefreshAll ( positionSignal , velocitySignal ); // refresh a std::span of differential signals std :: vector < BaseStatusSignal *> signals { & diffPositionSignal , & diffVelocitySignal }; BaseStatusSignal :: RefreshAll ( signals ); Python # refresh the supply voltage signal supply_voltage_signal . refresh () # refresh the position and velocity signals BaseStatusSignal . refresh_all ( position_signal , velocity_signal ) # refresh a list of differential signals signals = [ diff_position_signal , diff_velocity_signal ] BaseStatusSignal . refresh_all ( signals ) Waiting for Signal Updates Instead of using the latest value, the user can instead opt to synchronously wait for a signal update. StatusSignal provides a waitForUpdate(timeoutSec) method that will block the current robot loop until the signal is retrieved or the timeout has been exceeded. This replaces the need to call refresh() on cached StatusSignal objects. Tip If you want to zero your sensors, you can use this API to ensure the set operation has completed before continuing program flow. Tip The waitForUpdate() method can be method-chained. As a result, you can call waitForUpdate() and getValue() on one line. Java // wait up to 1 robot loop iteration (20ms) for fresh data supplyVoltageSignal . waitForUpdate ( 0.020 ); C++ // wait up to 1 robot loop iteration (20ms) for fresh data supplyVoltageSignal . WaitForUpdate ( 20 _ms ); Python # wait up to 1 robot loop iteration (20ms) for fresh data supply_voltage_signal . wait_for_update ( 0.020 ) Changing Update Frequency All signals can have their update frequency configured via the setUpdateFrequency() method. Additionally, the update frequency of multiple signals can be specified at once using BaseStatusSignal.setUpdateFrequencyForAll() ( Java , C++ , Python ). Warning Increasing signal frequency will also increase CAN bus utilization, which can cause indeterminate behavior at high utilization rates (>90%). This is less of a concern when using CANivore, which uses the higher-bandwidth CAN FD bus. Java // disable supply voltage reporting (0 Hz) supplyVoltageSignal . setUpdateFrequency ( 0 ); // speed up position and velocity reporting to 200 Hz BaseStatusSignal . setUpdateFrequencyForAll ( 200 , positionSignal , velocitySignal ); // speed up array or List of differential signals to 100 Hz BaseStatusSignal [] signals = { diffPositionSignal , diffVelocitySignal }; BaseStatusSignal . setUpdateFrequencyForAll ( 100 , signals ); C++ // disable supply voltage reporting (0 Hz) supplyVoltageSignal . SetUpdateFrequency ( 0 _Hz ); // speed up position and velocity reporting to 200 Hz BaseStatusSignal :: SetUpdateFrequencyForAll ( 200 _Hz , positionSignal , velocitySignal ); // speed up std::span of differential signals to 100 Hz std :: vector < BaseStatusSignal *> signals { & diffPositionSignal , & diffVelocitySignal }; BaseStatusSignal :: SetUpdateFrequencyForAll ( 100 _Hz , signals ); Python # disable supply voltage reporting (0 Hz) supply_voltage_signal . set_update_frequency ( 0 ) # speed up position and velocity reporting to 200 Hz BaseStatusSignal . set_update_frequency_for_all ( 200 , position_signal , velocity_signal ) # speed up list of differential signals to 100 Hz signals = [ diff_position_signal , diff_velocity_signal ] BaseStatusSignal . set_update_frequency_for_all ( 100 , signals ) When different update frequencies are specified for signals that share a status frame, the highest update frequency of all the relevant signals will be applied to the entire frame. Users can get a signal’s applied update frequency using the getAppliedUpdateFrequency() method. Signal update frequencies are automatically reapplied by the robot program on device reset. Optimizing Bus Utilization For users that wish to disable or slow down every unused status signal for their devices to reduce bus utilization, device objects have an optimizeBusUtilization() method ( Java , C++ , Python ). Additionally, multiple devices can be optimized at once using ParentDevice.optimizeBusUtilizationForAll() ( Java , C++ , Python ). When optimizing the bus utilization for devices, all status signals that have not been given an update frequency using setUpdateFrequency() will be disabled or slowed down. This results in an opt-in model for status signals, maximizing the reduction in bus utilization. Tip The update frequency of optimized signals can be specified ( Java , C++ , Python ), where 0 Hz completely disables the signals. The default update frequency is 4 Hz, ensuring that all signals are available when using Signal Logging . Warning When using followers, the leader motor must keep the DutyCycle , MotorVoltage , and TorqueCurrent status signals enabled. Additionally, remote sensors must keep related status signals enabled (such as position and velocity). Java m_pigeon . optimizeBusUtilization (); ParentDevice . optimizeBusUtilizationForAll ( m_leftMotor , m_rightMotor , m_cancoder ); C++ m_pigeon . OptimizeBusUtilization (); hardware :: ParentDevice :: OptimizeBusUtilizationForAll ( m_leftMotor , m_rightMotor , m_cancoder ); Python self . pigeon . optimize_bus_utilization () hardware . ParentDevice . optimize_bus_utilization_for_all ( self . left_motor , self . right_motor , self . cancoder ) Resetting All to Default The update frequencies of all status signals for a device can be reset to the defaults by calling resetSignalFrequencies() on the device ( Java , C++ , Python ). Additionally, multiple devices can be reset at once using ParentDevice.resetSignalFrequenciesForAll() ( Java , C++ , Python ). Since devices typically maintain their configured status signal update frequencies until they are power cycled, this can be useful to restore everything to the defaults before reconfiguring update frequencies. Java m_pigeon . resetSignalFrequencies (); ParentDevice . resetSignalFrequenciesForAll ( m_leftMotor , m_rightMotor , m_cancoder ); C++ m_pigeon . ResetSignalFrequencies (); hardware :: ParentDevice :: ResetSignalFrequenciesForAll ( m_leftMotor , m_rightMotor , m_cancoder ); Python self . pigeon . reset_signal_frequencies () hardware . ParentDevice . reset_signal_frequencies_for_all ( self . left_motor , self . right_motor , self . cancoder ) Timestamps StatusSignals can have multiple timestamps associated from them, as there are often multiple sources of time. Users can call getTimestamp() ( Java , C++ , Python ) to return the “best” timestamp determined by Phoenix. getAllTimestamps() can also be used to return all timestamps associated with a given StatusSignal , which returns a collection of Timestamp ( Java , C++ , Python ) objects. The Timestamp objects can be used to perform latency compensation math. CANivore Timesync Important CANivore Timesync requires the devices or the CANivore to be Pro licensed . When using CANivore , the attached CAN devices will automatically synchronize their time bases. This allows devices to sample and publish their signals in a synchronized manner. Users can synchronously wait for these signals to update using BaseStatusSignal.waitForAll() ( Java , C++ , Python ). Tip waitForAll() with a timeout of zero matches the behavior of refreshAll() , performing a non-blocking refresh on all signals passed in. Because the devices are synchronized, time-critical signals are sampled and published on the same schedule. This combined with the waitForAll() routine means applications can considerably reduce the latency of the timesync signals. This is particularly useful for multi-device mechanisms, such as swerve odometry. Note When using a non-zero timeout, the signals passed into waitForAll() should have the same update frequency for synchronous data acquisition. This can be done by calling setUpdateFrequency() or by referring to the API documentation. The diagram below demonstrates the benefits of using timesync to synchronously acquire signals from multiple devices. Check the API documentation for information on whether a status signal supports CANivore Timesync. Java var talonFXPositionSignal = m_talonFX . getPosition ( false ); var cancoderPositionSignal = m_cancoder . getPosition ( false ); var pigeon2YawSignal = m_pigeon2 . getYaw ( false ); BaseStatusSignal . waitForAll ( 0.020 , talonFXPositionSignal , cancoderPositionSignal , pigeon2YawSignal ); // can also send down an array or List of signals BaseStatusSignal [] signals = { talonFXPositionSignal , cancoderPositionSignal , pigeon2YawSignal }; BaseStatusSignal . waitForAll ( 0.020 , signals ); C++ auto & talonFXPositionSignal = m_talonFX . GetPosition ( false ); auto & cancoderPositionSignal = m_cancoder . GetPosition ( false ); auto & pigeon2YawSignal = m_pigeon2 . GetYaw ( false ); BaseStatusSignal :: WaitForAll ( 20 _ms , talonFXPositionSignal , cancoderPositionSignal , pigeon2YawSignal ); // can also send down a std::span of signal pointers std :: vector < BaseStatusSignal *> signals { & talonFXPositionSignal , & cancoderPositionSignal , & pigeon2YawSignal }; BaseStatusSignal :: WaitForAll ( 20 _ms , signals ); Python talonfx_position_signal = self . talonfx . get_position ( False ) cancoder_position_signal = self . cancoder . get_position ( False ) pigeon2_yaw_signal = self . pigeon2 . get_yaw ( False ) BaseStatusSignal . wait_for_all ( 0.020 , talonfx_position_signal , cancoder_position_signal , pigeon2_yaw_signal ) # can also send down a list of signals signals = [ talonfx_position_signal , cancoder_position_signal , pigeon2_yaw_signal ] BaseStatusSignal . wait_for_all ( 0.020 , signals ) Latency Compensation Users can perform latency compensation using BaseStatusSignal.getLatencyCompensatedValue() ( Java , C++ , Python ). Important getLatencyCompensatedValue() does not automatically refresh the signals. As a result, the user must ensure the signal and signalSlope parameters are refreshed before retrieving a compensated value. Java double compensatedTurns = BaseStatusSignal . getLatencyCompensatedValue ( m_motor . getPosition (), m_motor . getVelocity () ); C++ auto compensatedTurns = BaseStatusSignal :: GetLatencyCompensatedValue ( m_motor . GetPosition (), m_motor . GetVelocity () ); Python compensated_turns = BaseStatusSignal . get_latency_compensated_value ( self . motor . get_position (), self . motor . get_velocity () ) StatusSignalCollection The StatusSignalCollection ( Java , C++ , Python ) class provides a lightweight wrapper around a list of status signals on a common network. This simplifies the process of refreshing or waiting on multiple status signals. Java final StatusSignalCollection signals = new StatusSignalCollection (); // register all the signals we want to refresh (on the same network) signals . addSignals ( m_talonFX . getPosition ( false ), m_cancoder . getPosition ( false ), m_pigeon2 . getYaw ( false ) ); // set all the signals to a 200 Hz update frequency signals . setUpdateFrequencyForAll ( Hertz . of ( 200 )); // now wait on all the signals in the collection signals . waitForAll ( 0.010 ); C++ StatusSignalCollection signals {}; // register all the signals we want to refresh (on the same network) signals . AddSignals ( m_talonFX . GetPosition ( false ), m_cancoder . GetPosition ( false ), m_pigeon2 . GetYaw ( false ) ); // set all the signals to a 200 Hz update frequency signals . SetUpdateFrequencyForAll ( 200 _Hz ); // now wait on all the signals in the collection signals . WaitForAll ( 10 _ms ); Python self . signals = StatusSignalCollection () # register all the signals we want to refresh (on the same network) self . signals . add_signals ( self . talonfx . get_position ( False ), self . cancoder . get_position ( False ), self . pigeon2 . get_yaw ( False ) ) # set all the signals to a 200 Hz update frequency self . signals . set_update_frequency_for_all ( 200.0 ) # now wait on all the signals in the collection self . signals . wait_for_all ( 0.010 ) SignalMeasurement All StatusSignal objects have a getDataCopy() method that returns a new SignalMeasurement ( Java , C++ ) object. SignalMeasurement is a Passive Data Structure that provides all the information about a signal at the time of the getDataCopy() call, which can be useful for data logging. Warning getDataCopy() returns a new SignalMeasurement object every call. Java users should avoid using this API in RAM-constrained applications.",
+ "content_preview": "Status Signals Signals represent live data reported by a device; these can be yaw, position, etc. To make use of the live data, users need to know the value, timestamp, latency, units, and error condition of the data. Additionally, users may need to synchronize with fresh data to minimize latency."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/application-notes/update-frequency-impact.html",
- "title": "Factors that Impact Odometry",
- "section": "Application Notes",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/hardware-reference/candi/index.html",
+ "title": "CANdi™",
+ "section": "General",
"language": "All",
- "content": "Factors that Impact Odometry Authored by Cory Often we’ve been asked what the impact higher frequencies, time synchronization, and synchronous API have on critical robot features, such as drivetrain odometry. This devblog will go into detail on the theoretical and practical impact they have. Note This doesn’t cover all the factors that impact odometry, but it includes some of the major ones that contribute significantly to odometry error. Update Frequencies Update frequency has a direct impact on the accuracy of your localization through odometry, as it is an integration problem. The less frequent the odometry is called, the more time error can accrue before being updated to the present state of the robot. This can be seen graphically, the desmos session below shows the error in position, and how the error decreases as the update frequency increases. https://www.desmos.com/calculator/vdgebi9s4t Note This desmos graph shows a Forward Euler discretization of a simple odometry case. The odometry solution provided by WPILib are discretized using the Pose Exponential, which is more accurate than Forward Euler. Discretization error at 50 Hz Discretization error at 250 Hz Synchronous API and Time Synchronization Synchronous API and Time Synchronization will further improve the performance of odometry. The two do this by reducing the overall latency of the signals and reducing the random distribution of latency involved in each signal. Further explanation of this is available in the Time Synchronization . Latency reduces the accuracy of the data being used in the odometry, and with lower accuracy going into the odometry, the result will be less accurate as well ( garbage in, garbage out concept). We can add this into our desmos session, including the effect of latency and variable latency to our error calculation. https://www.desmos.com/calculator/rytssjj158 Discretization error at 50 Hz with latency Discretization error at 250 Hz with latency Practical Results With the theory out of the way, we can see what kind of impact this has on the odometry of a real robot performing real maneuvers. We took a swerve drive robot and, using our Swerve API, drove it around the office. Attached on the robot is a limelight pointing straight up to our ceiling, which has April Tags at regular points. This allows the Limelight to know the absolute position of the robot throughout the motion. The Limelight is configured for a high resolution capture to reduce the error of its pose estimation, at the cost of less frequent pose calculations. This is acceptable, because as the robot performs its maneuvers, it will come to a rest at key points, and when it’s at rest, we can do our comparisons between the Limelight pose and the dead-reckoning from the odometry. As we performed the maneuvers, we logged the pose of the robot as reported by the odometry and the Limelight for use in playback. The “real” robot pose is the odometry-driven pose, and the ghost is the Limelight reported pose. It can be assumed the limelight pose is the “true” pose while the robot is at rest. Note The Limelight pose measurements are not latency-compensated, so they will lag behind the odometry pose. The focus of this experiment is to see the difference in pose between the two methods while the robot is relatively still, so that this lag due to latency is not a factor. The same maneuver was teleop-driven under the following circumstances, with the results below: CANivore CAN bus at 250 Hz (top left, measured at 45% CAN bus utilization) CANivore CAN bus at 50 Hz (top right, measured at 16% CAN bus utilization) RIO CAN bus at 250 Hz (bottom left, measured at 88% CAN bus utilization) RIO CAN bus at 50 Hz (bottom right, measured at 45% CAN bus utilization) Final States CANivore 250 Hz end position CANivore 50 Hz end position RIO 250 Hz end position RIO 50 Hz end position As can be seen, going from the RIO bus to the CANivore bus, or from 50 Hz to 250 Hz improves the accuracy of the odometry, and by a noticeable amount. Based on this, utilizing faster update frequencies and time synchronization from the CANivore should result in more accurate odometry, even for the “short” movements as shown in the gif. Data on the pose location is available for download: OdometryData.xlsx . After-Test Data Roughly 2 weeks after this initial data was collected and the blog post written, we went back and re-verified the data for the CANivore 250 Hz and RIO 250 Hz cases to further test the impact of time synchronization. These tests were ran in autonomous a total of 20 times (10 for CANivore, 10 for RIO), measuring the error of the odometry against the Limelight data. The results are below: RIO CANivore 0.52 0.14 0.33 0.28 0.47 0.56 0.23 0.22 0.51 0.32 0.23 0.18 0.22 0.30 0.59 0.26 0.15 0.33 0.11 0.25 This resulted in the following average and standard deviation of error: RIO CANivore Average 0.336 0.284 Standard Deviation 0.173 0.114",
- "content_preview": "Factors that Impact Odometry Authored by Cory Often we’ve been asked what the impact higher frequencies, time synchronization, and synchronous API have on critical robot features, such as drivetrain odometry. This devblog will go into detail on the theoretical and practical impact they have."
+ "content": "CANdi™ The CTR Electronics’ CANdi™ branded device seamlessly integrates digital signals into existing CAN bus networks, simplifying wiring and allowing multiple devices to share and utilize valuable input data. CANdi™ enables CAN interopability with sensors such as: PWM encoders, Quadrature encoders, beam break sensors, and limit switches. Store Page CAD and purchase instructions. https://store.ctr-electronics.com/products/candi Status Light Reference Important If the status lights do not exactly match any of the blink codes below, the device may be alternating between multiple blink codes (most commonly between good and bad CAN). Blink Codes Animation (Click to play) LED State Cause Possible Fix LEDs Off No Power Provide 12V to V+ and V- inputs. Blinking Alternating Red CANdi™ does not have valid CAN. Ensure good connections between CANH and CANL (Yellow and Green) & robot controller is on. Blinking Alternating Green CANdi™ has a good CAN connection. Alternate Red/Orange Damaged Hardware. Use Tuner X Self Test to confirm the LEDs and that the hardware fault is set, then contact CTRE Single LED alternates Green/Orange CANdi™ in bootloader. Field-upgrade device in Tuner X.",
+ "content_preview": "CANdi™ The CTR Electronics’ CANdi™ branded device seamlessly integrates digital signals into existing CAN bus networks, simplifying wiring and allowing multiple devices to share and utilize valuable input data."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/hardware-reference/talonfxs/index.html",
- "title": "Talon FXS",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/licensing/licensing.html",
+ "title": "Device Licensing",
"section": "General",
"language": "All",
- "content": "Talon FXS Talon FXS is a versatile motor controller compatible with the CTRE software ecosystem. This standalone device seamlessly supports both brushless and brushed motors, offering unparalleled flexibility and performance. The Talon FXS supports the CTR Electronics Minion and other third-party motors. Talon FXS also employs methods to reduce hall sensor velocity measurement noise and phase delay - a common problem with similar standalone motor controllers. Phoenix Pro users also benefit with “Advanced Hall Support” which can increase motor peak efficiency as high as an additional 2% percentage points and further reduce velocity measurement noise, making it ideal for velocity closed loop modes. Store Page CAD and purchase instructions. https://store.ctr-electronics.com/products/talon-fxs Supported Motors CTR Electronics Minion Third-party NEO Third-party NEO 550 Third-party NEO Vortex with Solo Adapter Most 3rd party brushed motors Status Light Reference Important If the status lights do not exactly match any of the blink codes below, the device may be alternating between multiple blink codes (most commonly between good and bad CAN). Blink Codes Disabled Codes Animation (Click to play) LED State Cause Possible Fix LEDs Off No Power Provide 12V to Red/Black leads. Blinking Alternating Red Talon FXS does not have a valid CAN/PWM signal. Ensure good connections between CANH and CANL (Yellow and Green) & robot controller is on. Blinking Alternating Orange TalonFXS detects CAN but does not see Phoenix running on the robot controller. If Phoenix is running on the robot controller, ensure good connection between the controller and this device. Otherwise, deploy a robot program that uses Phoenix. Blinking Simultaneous Orange Talon FXS has valid CAN signal and is disabled. Phoenix is running in robot controller and Talon FXS has good CAN connection to robot controller. If robot is enabled, ensure a control request is being sent to the Talon FXS. Enabled Codes Both Solid Orange Talon FXS enabled with neutral output. Blinking Simultaneous Red Talon FXS driving in reverse. Rate of blink corresponds to duty cycle applied. Blinking Simultaneous Green Talon FXS driving forward. Rate of blink corresponds to duty cycle applied. Offset Alternating Red/Off Talon FXS limited (hard or soft limit). Direction of offset determines forward/reverse limit. Special Codes Offset Orange/Off Talon FXS in thermal cutoff or temperature measurement is missing. Please see \"Troubleshooting Fault LED\" for potential solutions. Alternate Red/Green Talon FXS driven with Pro-only command while unlicensed. Use non-Pro-only command, or license device for Pro. Alternate Orange/Green Talon FXS driven with no motor selected in motor arrangement. Configure the Talon FXS with the attached motor. Alternate Red/Orange Damaged Hardware. Use Tuner X Self Test to confirm the LEDs and that the hardware fault is set, then contact CTRE Single LED alternates Green/Orange Talon FXS in bootloader. Field-upgrade device in Tuner X. Troubleshooting Fault LED A Talon FXS thermal fault, as indicated with an “Offset Orange/Off” blink code, can be triggered for a variety of reasons. The following list can be used to help identify the reason and a potential solution. Brushless Motor JST is disconnected or damaged. Plug-in the motor JST cable into the JST port and ensure the cable is not damaged. Motor arrangement is incorrect. Please select the correct motor in configs . Talon FXS or motor has reached thermal cut-off. Allow time for the device to cool and consider configuring a Stator Current limit . Brushed Motor Motor arrangement is incorrect. Please select one of the brushed options in configs . Talon FXS has reached thermal cut-off. Allow time for the device to cool and consider configuring a Stator Current limit .",
- "content_preview": "Talon FXS Talon FXS is a versatile motor controller compatible with the CTRE software ecosystem. This standalone device seamlessly supports both brushless and brushed motors, offering unparalleled flexibility and performance."
+ "content": "Device Licensing Note Users utilizing season pass must attach a team number before continuing. See Attaching a Team Number to Season Pass for more information. All Phoenix 6 supported devices support device licensing. Additionally, CANivore is supported for licensing. When a CANivore is licensed, all devices on that bus are Pro enabled without additional activation. Important All license activation and verification features are only available in Phoenix Tuner X . Phoenix Tuner v1 does not support licensing actions. Purchasing a License Licenses can be purchased in the licensing section on the CTR Electronics store. Click here to purchase a license. Once a license has been purchased, you will receive an email confirmation confirming your purchase. Once this email is received, the license should be visible in the list of licenses in Tuner X. Activating a License Licenses are activated by first clicking on the LIC icon in the bottom right corner of the device card. This will open up a screen which displays a list of currently attached licenses for that device. Click on the Activate a new license button on the bottom of the popup. A list of purchased (but unattached) license seats are shown here. Click on the license you would like to redeem and press the Activate Selected License button to confirm redemption of that seat. Warning Users should be aware that license activation is permanent and irreversible Once the activation is complete, the license will be downloaded to the device. In the event that Tuner X disconnects from the internet or from the robot before this completes, the license is still activated and available for download the next time Tuner X is connected to the internet/robot. Batch Activating Licenses Tuner X also supports batch activating licenses from the Devices page. The user can either select devices by their checkbox (in the top right corner of their respective card) or by selecting the checkmark icon in the top right. Tip Selecting a device using their checkbox and clicking the checkmark in the top right will select all devices of the same models Step 1 in the above image selects all devices of the same models selected (or all devices if no device is currently check-boxed). Step 2 in the above image opens the batch licensing dialog. Once the dialog is opened, select a license from the dropdown at the top of the popup. The first list contains devices that will be batch licensed, while the second list contains devices that are ineligible due to one of the following: Device is not running Phoenix 6 firmware that supports licensing Device does not support the selected license Device is already licensed with the selected license The License devices button at the bottom of the popup shows the number of device licenses that will be applied and the number of seats currently available. After confirming that everything looks correct, press the License devices button to apply the licenses. Activating a License without a Robot Devices that have been seen by Tuner X at least once will be available in Device History . This can be useful for licensing a device when disconnected from the robot. Verifying Activation State An icon displaying the license state of your device is located in the bottom right of the device card. The below table can be used to determine your device license state for troubleshooting. State Image Description Licensed Device is licensed for the current version of the Phoenix 6 API. CANivore contains Licenses CANivore contains at least one bus license, which it will use to remote-license all compliant CAN devices. Pro Licensing Error Device is licensed and there was an error communicating license state. Licensing Error Device is not licensed and there was an error communicating license state. Not Licensed Device is not licensed for this version of the Phoenix 6 API. Licensing Not Supported Icon not present Device does not support licensing or is using an incompatible firmware for device licensing. Additionally, users can perform a Self Test to verify that the device has a valid license. Troubleshooting Did you activate a license for this device? Clicking on the icon will show licenses that are attached to this device Is the latest diagnostic server running? Check the version at the bottom of Tuner X’s devices page. Latest version details can be found in the changelog under the latest Phoenix-6/Libs version. Confirm the vendordep in your robot project is the latest version. Alternatively, you can deploy the temporary diagnostic server . Is the latest Phoenix 6 firmware flashed onto the device? FRC Only : If using Season Pass, is the roboRIO configured with the correct team number ?",
+ "content_preview": "Device Licensing Note Users utilizing season pass must attach a team number before continuing. See Attaching a Team Number to Season Pass for more information. All Phoenix 6 supported devices support device licensing. Additionally, CANivore is supported for licensing."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/application-notes/tuner-evolution.html",
- "title": "Tuner and an evolution in configuration",
- "section": "Application Notes",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/installation/installation.html",
+ "title": "Installing Phoenix 6",
+ "section": "General",
"language": "All",
- "content": "Tuner and an evolution in configuration Authored by Dalton Since the introduction of the CTRE Toolsuite (pre-2018), we at CTR have strived to provide intuitive means of configuring and utilizing our products. In 2018, we launched Phoenix Tuner (now lovingly referred to as Tuner v1). Tuner v1 introduced features like: batch firmware upgrading like devices, diagnostic server deployment, self tests, plotting and control. With Tuner, and by extension diagnostics, we have several primary objectives: Ease of debugging (exposed via self test). Seamless setup experience. Support and integrate our extension feature-set. Tuner v1 was and is great, but we wanted to do more. In the 2023 season, we introduced Tuner X. Introducing Tuner X The goal we had with Tuner X development was to refine and enhance the existing Tuner v1 feature set. We introduced Android support , improved batch upgrading, improved highlighting of duplicate devices, automatic firmware downloads (no more downloading CRFs!), improved self test and licensing support. With Tuner X, users can: Configure their device’s name & ID Blink a device, which is useful for identifying where the device is on the robot. Firmware update all devices to the latest version available (no more CRF downloads). Control individual motors with their Android phone, or on Windows. Plot various signals such as velocity, position and yaw. Self test their v6 device, which provides a marked up self test of the device. Important Tuner X does not require v6 and can be used with v5 flashed devices. For a full list of features, check out the v6 documentation . Introducing a new iteration of Tuner X Some of you may have noticed that your version of Tuner X has changed recently. We’ve been working on several key improvements to the application that should dramatically improve the user experience. While this blog will highlight some of those, it’s best to just try out the new Tuner yourself. Note Feedback is welcome and can be provided by emailing feedback @ ctr-electronics . com . Improved connection diagnostics Tuner requires a running diagnostic server to work. Typically, this is installed through a robot program utilizing one of our devices. Alternatively, this program is temporarily run using a button in Settings . We’ve improved the disconnection status card to contain information about the ping of the target and diagnostic state of the device. This 3 step check looks for the following: Ping of the target. Is diagnostics (or a robot program with diagnostics) running? Are there any devices reported? To summarize, if a user is not seeing devices in Tuner but checks 1 and 2 are good, then the next recommendation is to check the LED status of the device. We have an extensive list of status LEDs that indicate if the device is detected on a CAN bus, or other problems. This list can be found on the corresponding device page in the docs. For example, look at the CANcoder LED table . Redesigned device overview The device overview page has been redesigned to improve usability of plot, control and configuration. It’s never been easier to tune your closed-loop gains directly in Tuner! Bug squashing and usability improvements This list is by no means exhaustive, but provides a good idea of the changes between 2023.X and 2024 versions of Tuner X. Firmware selection now has a dropdown for year, allowing you to flash older year firmware Dramatically improved startup and navigation performance Dramatically improved plotting performance Dramatically improved commands timing out on Android Tuner Enable/Disable button colors have been adjusted to be more clear Fixed “connection blipping” on Android Tuner Fixed control sometimes stuttering and causing the device to disable Fixed licensing sometimes fail to load on Android Tuner Fixed SSH credentials popup not appearing sometimes Fixed lag when entering into various entries Fixed memory leak when plotting for long periods of time Fixed situation where the application would shutdown uncleanly and lose settings Fixed various clipping of icons, text and labels Fixed issue where CANivore USB toggle would be unable to enable or disable Fixed firmware flashing on Raspberry Pi Fixed temporary diagnostic deployment on non-RIO platforms Slows down CANivore polling, which improves Rio CPU performance when Tuner is open What’s next? We have a couple of exciting improvements to Tuner on our radar, keep an eye out on our changelog . Tuner X can be downloaded via the Microsoft Store and the Google Play Store .",
- "content_preview": "Tuner and an evolution in configuration Authored by Dalton Since the introduction of the CTRE Toolsuite (pre-2018), we at CTR have strived to provide intuitive means of configuring and utilizing our products. In 2018, we launched Phoenix Tuner (now lovingly referred to as Tuner v1)."
+ "content": "Installing Phoenix 6 Installing Phoenix 6 (FRC) Click here to learn about installing the Phoenix 6 library for FRC. This explains adding the Phoenix vendordep into your robot project. Installing Phoenix 6 (FRC) Installing Phoenix 6 (non-FRC) Click here to learn about installing the Phoenix 6 library outside of FRC. This explains including our APT repository and applicable binaries. Installing Phoenix 6 (non-FRC)",
+ "content_preview": "Installing Phoenix 6 Installing Phoenix 6 (FRC) Click here to learn about installing the Phoenix 6 library for FRC. This explains adding the Phoenix vendordep into your robot project."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/licensing/what-is-licensing.html",
- "title": "What is Licensing",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/migration/migration-guide/configuration-guide.html",
+ "title": "Configuration",
"section": "General",
"language": "All",
- "content": "What is Licensing All supported Phoenix 6 devices can freely use the Phoenix 6 API. Licensing a device can enable additional features that can enhance performance or user experience. A breakdown of the features offered by licensing your device is available at Feature Breakdown . Licenses will enable Phoenix Pro features on hardware when the device firmware and API versions match the license year. For example, a 2023 license enables Pro when used with 23.X firmware and 23.X Phoenix 6 API. A 2024 license would enable Pro features when used with 24.X firmware and 24.X API. Note Using Phoenix 6 simulation with Pro features does not require licensing. Types of Licenses Season Pass (FRC Teams Only) A single purchase that allows FRC Teams to license the team’s devices for Phoenix Pro. Season Pass generated licenses are equivalent to Single Device licenses but are tied to the team number assigned to the Season Pass after purchase. Season Pass Licensing Single Device A single device license can be activated and installed individually per device. Each purchase licenses exactly one compatible CAN bus device to use Phoenix Pro features. This may be preferred for systems with a small number of devices that need to utilize Pro features, or when used in a benchtop application. Device Licensing CANivore Bus This license is activated and installed onto the CANivore, and enables Phoenix Pro features for every CAN-connected device. This means that every compatible device that is attached to a licensed CANivore via CAN bus will be able to use Phoenix Pro features. This license type is advantageous if there are a large number of devices and eliminates the need to license additional devices in the event of a replacement. Device Licensing",
- "content_preview": "What is Licensing All supported Phoenix 6 devices can freely use the Phoenix 6 API. Licensing a device can enable additional features that can enhance performance or user experience. A breakdown of the features offered by licensing your device is available at Feature Breakdown ."
+ "content": "Configuration Phoenix 6 simplifies the configuration process through the use of device-specific Configuration classes, as well as configuration groups. Note For more information about configuration in Phoenix 6, see Configuration . Applying Configs v5 Java // set slot 0 gains // 50 ms timeout on each config call m_motor . config_kF ( 0 , 0.05 , 50 ); m_motor . config_kP ( 0 , 0.046 , 50 ); m_motor . config_kI ( 0 , 0.0002 , 50 ); m_motor . config_kD ( 0 , 0.42 , 50 ); C++ // set slot 0 gains // 50 ms timeout on each config call m_motor . Config_kF ( 0 , 0.05 , 50 ); m_motor . Config_kP ( 0 , 0.046 , 50 ); m_motor . Config_kI ( 0 , 0.0002 , 50 ); m_motor . Config_kD ( 0 , 0.42 , 50 ); v6 Java var talonFXConfigs = new TalonFXConfiguration (); // set slot 0 gains and leave every other config factory-default var slot0Configs = talonFXConfigs . Slot0 ; slot0Configs . kV = 0.12 ; slot0Configs . kP = 0.11 ; slot0Configs . kI = 0.5 ; slot0Configs . kD = 0.001 ; // apply all configs, 50 ms total timeout m_talonFX . getConfigurator (). apply ( talonFXConfigs , 0.050 ); C++ configs :: TalonFXConfiguration talonFXConfigs {}; // set slot 0 gains and leave every other config factory-default configs :: Slot0Configs & slot0Configs = talonFXConfigs . Slot0 ; slot0Configs . kV = 0.12 ; slot0Configs . kP = 0.11 ; slot0Configs . kI = 0.5 ; slot0Configs . kD = 0.001 ; // apply all configs, 50 ms total timeout m_talonFX . GetConfigurator (). Apply ( talonFXConfigs , 50 _ms ); Factory Defaulting Configs v5 Java // user must remember to explicitly factory default if they configure devices in code m_motor . configFactoryDefault (); C++ // user must remember to explicitly factory default if they configure devices in code m_motor . ConfigFactoryDefault (); v6 Java // Any unmodified configs in a configuration object are *automatically* factory-defaulted. // As a result, factory-defaulting before applying configs is *unnecessary* when using a // full device configuration object, such as TalonFXConfiguration. // Users can perform a full factory default by passing a new device configuration object. m_motor . getConfigurator (). apply ( new TalonFXConfiguration ()); C++ // Any unmodified configs in a configuration object are *automatically* factory-defaulted; // As a result, factory-defaulting before applying configs is *unnecessary* when using a // full device configuration object, such as TalonFXConfiguration. // Users can perform a full factory default by passing a new device configuration object. m_motor . GetConfigurator (). Apply ( configs :: TalonFXConfiguration {}); Retrieving Configs v5 Java // a limited number of configs have configGet* methods; // for example, you can get the supply current limits var supplyCurLim = new SupplyCurrentLimitConfiguration (); m_motor . configGetSupplyCurrentLimit ( supplyCurLim ); C++ // a limited number of configs have ConfigGet* methods; // for example, you can get the supply current limits SupplyCurrentLimitConfiguration supplyCurLim {}; m_motor . ConfigGetSupplyCurrentLimit ( supplyCurLim ); v6 Java var fx_cfg = new TalonFXConfiguration (); // fetch *all* configs currently applied to the device m_motor . getConfigurator (). refresh ( fx_cfg ); C++ configs :: TalonFXConfiguration fx_cfg {}; // fetch *all* configs currently applied to the device m_motor . GetConfigurator (). Refresh ( fx_cfg );",
+ "content_preview": "Configuration Phoenix 6 simplifies the configuration process through the use of device-specific Configuration classes, as well as configuration groups. Note For more information about configuration in Phoenix 6, see Configuration ."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/canivore/canivore-api.html",
- "title": "CANivore API",
- "section": "CANivore",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/application-notes/devblog.html",
+ "title": "Development Blog",
+ "section": "Application Notes",
"language": "All",
- "content": "CANivore API All device constructors have an overload that takes a CANBus object ( Java , C++ , Python ). The native roboRIO CAN bus can be constructed using CANBus.roboRIO() . Otherwise, the CANBus constructor takes a string identifier. This identifier can be * to select the first available CANivore, or it can be a CANivore’s name or serial number. On non-FRC Linux systems, this string can also be a SocketCAN interface. Note If there are multiple CANivores with the same name, the system will use the first CANivore found. If no CAN bus string is passed into the constructor, or the CAN bus string is empty, the behavior is platform-dependent: roboRIO: use the roboRIO native CAN bus Windows: use the first CANivore found non-FRC Linux: use SocketCAN interface can0 Java final TalonFX fx_default = new TalonFX ( 0 ); // On roboRIO, this constructs a TalonFX on the RIO native CAN bus final TalonFX fx_rio = new TalonFX ( 1 , CANBus . roboRIO ()); // This also constructs a TalonFX on the RIO native CAN bus final TalonFX fx_drivebase = new TalonFX ( 0 , new CANBus ( \"Drivebase\" )); // This constructs a TalonFX on the CANivore bus named \"Drivebase\" final CANcoder cc_elevator = new CANcoder ( 0 , new CANBus ( \"Elevator\" )); // This constructs a CANcoder on the CANivore bus named \"Elevator\" C++ (Header) hardware :: TalonFX fx_default { 0 }; // On roboRIO, this constructs a TalonFX on the RIO native CAN bus hardware :: TalonFX fx_rio { 1 , CANBus :: RoboRIO ()}; // This also constructs a TalonFX on the RIO native CAN bus hardware :: TalonFX fx_drivebase { 0 , CANBus { \"Drivebase\" }}; // This constructs a TalonFX on the CANivore bus named \"Drivebase\" hardware :: CANcoder cc_elevator { 0 , CANBus { \"Elevator\" }}; // This constructs a CANcoder on the CANivore bus named \"Elevator\" Python self . _fx_default = hardware . TalonFX ( 0 ) # On roboRIO, this constructs a TalonFX on the RIO native CAN bus self . _fx_rio = hardware . TalonFX ( 1 , CANBus . roborio ()) # This also constructs a TalonFX on the RIO native CAN bus self . _fx_drivebase = hardware . TalonFX ( 0 , CANBus ( \"Drivebase\" )) # This constructs a TalonFX on the CANivore bus named \"Drivebase\" self . _cc_elevator = hardware . CANcoder ( 0 , CANBus ( \"Elevator\" )) # This constructs a CANcoder on the CANivore bus named \"Elevator\" The CANBus API can also be used to retrieve information about any given CAN bus, such as the bus utilization. Java // create a CAN bus for the CANivore named drivetrain final CANBus canbus = new CANBus ( \"drivetrain\" ); // construct a TalonFX on the CAN bus final TalonFX fx = new TalonFX ( 0 , canbus ); // retrieve bus utilization for the CAN bus CANBusStatus canInfo = canbus . getStatus (); float busUtil = canInfo . BusUtilization ; if ( busUtil > 0.8 ) { System . out . println ( \"CAN bus utilization is greater than 80%!\" ); } C++ // create a CAN bus for the CANivore named drivetrain CANBus canbus { \"drivetrain\" }; // construct a TalonFX on the CAN bus hardware :: TalonFX fx { 0 , canbus }; // retrieve bus utilization for the CANivore named drivetrain CANBus :: CANBusStatus canInfo = canbus . GetStatus (); float busUtil = canInfo . BusUtilization ; if ( busUtil > 0.8 ) { std :: cout << \"CAN bus utilization is greater than 80%!\" << std :: endl ; } Python # create a CAN bus for the CANivore named drivetrain self . _canbus = CANBus ( \"drivetrain\" ) # construct a TalonFX on the CAN bus self . _fx = hardware . TalonFX ( 0 , self . _canbus ) # retrieve bus utilization for the CANivore named drivetrain can_info = self . _canbus . get_status () bus_util = can_info . bus_utilization if bus_util > 0.8 : print ( \"CAN bus utilization is greater than 80%!\" ) CANivore Status Prints When working with CANivore CAN buses in a robot program, Phoenix prints some messages to report the state of the CANivore connection. These messages can be useful to debug connection issues (bad USB vs bad CAN) or report bugs to CTR Electronics. Connection Messages Message Connection Status CANbus Failed to Connect Could not connect to a CANivore with the given name or serial number CANbus Connected Successfully found and connected to the CANivore with the given name or serial number CANbus Disconnected Detected that a CANivore USB device has been disconnected CANivore Bring-up Messages (Linux only) Message Bring-up Status CANbus Failed Bring-up Found and connected to the CANivore, but could not configure the device or start the network CANbus Successfully Started Successfully configured the CANivore and started the network Network State Messages Message Network State CANbus Network Down Linux: The SocketCAN network has been deactivated, USB-to-CAN activity has stopped Windows: Could not open the communication channels for USB-to-CAN traffic CANbus Network Up Linux: The SocketCAN network has been activated, USB-to-CAN activity has resumed Windows: Successfully opened the communication channels for USB-to-CAN traffic",
- "content_preview": "CANivore API All device constructors have an overload that takes a CANBus object ( Java , C++ , Python ). The native roboRIO CAN bus can be constructed using CANBus.roboRIO() . Otherwise, the CANBus constructor takes a string identifier."
+ "content": "Development Blog Welcome to the development blog. Here, we will highlight various features of CTR Electronics devices and how they can be utilized in specific applications. Note This list may move in the future. Latency and Frequency Tuner and an evolution in configuration Factors that Impact Odometry Tuning CANrange",
+ "content_preview": "Development Blog Welcome to the development blog. Here, we will highlight various features of CTR Electronics devices and how they can be utilized in specific applications. Note This list may move in the future."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/canivore/canivore-intro.html",
- "title": "CANivore Intro",
- "section": "CANivore",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-swerve/index.html",
+ "title": "Swerve Project Generator",
+ "section": "Phoenix Tuner X",
"language": "All",
- "content": "CANivore Intro The CANivore is a multipurpose USB-to-CAN FD device. The CANivore: Adds a secondary CAN FD bus to the roboRIO CAN FD improves upon CAN with increased device bandwidth and transfer speed. Allows the control of CTR Electronics devices on Supported Devices . Important Details on licensing your CANivore is available on the licensing page. Initial Setup Setting up a CANivore for robot projects and desktop development. CANivore Setup API Usage Using the CANivore with devices in API. CANivore API Hardware-Attached Simulation Using a CANivore with hardware devices in a desktop environment. Hardware-Attached Simulation Advanced Configuration Advanced configuration options for the CANivore. Advanced Configuration Status Light Reference Blink Codes STAT Codes Animation (Click to play) LED State Cause Possible Fix LED Off No Power Provide 12V to V+/V-, or plug in USB. Red Double-Blink Device powered through V+/V-, but no USB. Plug in USB and ensure the robot controller is powered on. Red Fast-Strobe USB plugged in, but no USB communication. Ensure robot controller is fully booted and enumerated USB. Then consider replacing the USB cable. Orange Double-Blink Good USB connection, CAN streaming disabled. V+/V- is NOT powered. Ensure a robot program using Phoenix is running. Orange Fast-Strobe Good USB connection, CAN streaming disabled. V+/V- is powered. Ensure a robot program using Phoenix is running. Green Double-Blink Good USB connection, CAN streaming enabled. V+/V- is NOT powered. Green Fast-Strobe Good USB connection, CAN streaming enabled. V+/V- is powered. Alternate Red/Orange Damaged Hardware. Contact CTRE Support. Alternate Orange/Green CANivore in bootloader. Field-upgrade device in Tuner X. Wi-Fi Codes LED Off Wi-Fi is disabled. Enable the ESP32. Green Blink Wi-Fi is enabled, or ESP32 custom application is allowed to use Wi-Fi. BT Codes LED Off Bluetooth is disabled. Enable the ESP32. Green Blink Bluetooth is enabled, or ESP32 custom application is allowed to use Bluetooth. CAN Codes Solid Red Voltage too low for CAN bus. Ensure device is receiving 5 V over USB and optionally 12 V over V+/V-. Red Double-Blink No CAN communication. CAN termination is disabled. Ensure good connections on CANH and CANL (Yellow and Green), all connected devices support CAN FD, and the bus is properly terminated with two 120-Ω resistors, one on each end. Red Fast-Strobe No CAN communication. CAN termination is enabled. Ensure good connections on CANH and CANL (Yellow and Green), all connected devices support CAN FD, and the bus is properly terminated with a 120-Ω resistor on the other end. Orange Double-Blink Reserved for CAN 2.0B legacy mode. CAN termination is disabled. Orange Fast-Strobe Reserved for CAN 2.0B legacy mode. CAN termination is enabled. Green Double-Blink CAN FD is active. CAN termination is disabled. Green Fast-Strobe CAN FD is active. CAN termination is enabled.",
- "content_preview": "CANivore Intro The CANivore is a multipurpose USB-to-CAN FD device. The CANivore: Adds a secondary CAN FD bus to the roboRIO CAN FD improves upon CAN with increased device bandwidth and transfer speed. Allows the control of CTR Electronics devices on Supported Devices ."
+ "content": "Swerve Project Generator Important Full swerve project generation is only available for FRC users. However, non-FRC users can still generate the constants file. Under the Mechanisms page in Tuner X is the Swerve Project Generator. This utility guides the user through configuring their modules, verifying their drivetrain, encoder inverts, drivetrain inverts and more. Note The generated swerve project utilizes the Swerve API . Swerve Requirements Creating your Project Configuring Modules Validating the Drivetrain Generating the Project Swerve Next Steps",
+ "content_preview": "Swerve Project Generator Important Full swerve project generation is only available for FRC users. However, non-FRC users can still generate the constants file. Under the Mechanisms page in Tuner X is the Swerve Project Generator."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/migration/migration-guide/closed-loop-guide.html",
- "title": "Closed",
- "section": "General",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-history.html",
+ "title": "Tuner History",
+ "section": "Phoenix Tuner X",
"language": "All",
- "content": "Closed-Loop Control Phoenix 6 enhances the experience of using onboard closed-loop control through the use of standardized units and a variety of control output types. Note For more information about closed-loop control in Phoenix 6, see Closed-Loop Overview . Closed-Loop Setpoints Phoenix 6 uses canonical units for closed-loop setpoints. Note This calculator assumes the RotorToSensorRatio and SensorToMechanismRatio configs are both set to 1. If this is not the case in your robot program, divide the resulting values by both ratios. Setpoint Conversion Name Value Units Formula Position Original \\(\\mathrm{raw\\_units}\\) \\(x_{\\mathrm{old}}\\) New \\(\\mathrm{rotations}\\) \\(x_{\\mathrm{new}}=x_{\\mathrm{old}} \\cdot \\frac{1}{2048} \\frac{\\mathrm{rot}}{\\mathrm{raw\\_unit}}\\) Velocity Original \\(\\frac{\\mathrm{raw\\_units}}{\\mathrm{100ms}}\\) \\(v_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{rot}}{\\mathrm{second}}\\) \\(v_{\\mathrm{new}}=v_{\\mathrm{old}} \\cdot \\frac{1}{2048} \\frac{\\mathrm{rot}}{\\mathrm{raw\\_unit}} \\cdot 10 \\frac{\\mathrm{100ms}}{\\mathrm{second}} \\) Acceleration Original \\(\\frac{\\mathrm{raw\\_units}}{\\mathrm{100ms} \\cdot \\mathrm{second}}\\) \\(a_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{rot}}{\\mathrm{second}^2}\\) \\(a_{\\mathrm{new}}=a_{\\mathrm{old}} \\cdot \\frac{1}{2048} \\frac{\\mathrm{rot}}{\\mathrm{raw\\_unit}} \\cdot 10 \\frac{\\mathrm{100ms}}{\\mathrm{second}} \\) Closed-Loop Gains Position without Voltage Comp Phoenix 5 ControlMode.Position with voltage compensation disabled maps to the Phoenix 6 PositionDutyCycle control request. Note This calculator assumes the RotorToSensorRatio and SensorToMechanismRatio configs are both set to 1. If this is not the case in your robot program, multiply the resulting gains by both ratios. Position without Voltage Compensation Name Value Units Formula kP Original \\(\\frac{\\mathrm{raw\\_output}}{\\mathrm{unit}}\\) \\(kP_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{duty\\_cycle}}{\\mathrm{rot}}\\) \\(kP_{\\mathrm{new}}=kP_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}}\\) kI Original \\(\\frac{\\mathrm{raw\\_output}}{\\mathrm{unit} \\cdot \\mathrm{millisecond}}\\) \\(kI_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{duty\\_cycle}}{\\mathrm{rot} \\cdot \\mathrm{second}}\\) \\(kI_{\\mathrm{new}}=kI_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot 1000 \\frac{\\mathrm{millisecond}}{\\mathrm{second}}\\) kD Original \\(\\frac{\\mathrm{raw\\_output}}{\\mathrm{unit} / \\mathrm{millisecond}}\\) \\(kD_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{duty\\_cycle}}{\\mathrm{rot} / \\mathrm{second}}\\) \\(kD_{\\mathrm{new}}=kD_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot \\frac{1}{1000} \\frac{\\mathrm{second}}{\\mathrm{millisecond}}\\) Position with Voltage Comp Phoenix 5 ControlMode.Position with voltage compensation enabled has been replaced with the Phoenix 6 PositionVoltage control request, which directly controls voltage. Note This calculator assumes the RotorToSensorRatio and SensorToMechanismRatio configs are both set to 1. If this is not the case in your robot program, multiply the resulting gains by both ratios. Position with Voltage Compensation Voltage Compensation Value: Name Value Units Formula kP Original \\(\\frac{\\mathrm{\\mathrm{raw\\_output}}}{\\mathrm{unit}}\\) \\(kP_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{V}}{\\mathrm{rot}}\\) \\(kP_{\\mathrm{new}}=kP_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot \\mathrm{V\\_comp} \\frac{\\mathrm{V}}{\\mathrm{duty\\_cycle}}\\) kI Original \\(\\frac{\\mathrm{\\mathrm{raw\\_output}}}{\\mathrm{unit} \\cdot \\mathrm{millisecond}}\\) \\(kI_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{V}}{\\mathrm{rot} \\cdot \\mathrm{second}}\\) \\(kI_{\\mathrm{new}}=kI_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot 1000 \\frac{\\mathrm{millisecond}}{\\mathrm{second}} \\cdot \\mathrm{V\\_comp} \\frac{\\mathrm{V}}{\\mathrm{duty\\_cycle}}\\) kD Original \\(\\frac{\\mathrm{\\mathrm{raw\\_output}}}{\\mathrm{unit} / \\mathrm{millisecond}}\\) \\(kD_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{V}}{\\mathrm{rot} / \\mathrm{second}}\\) \\(kD_{\\mathrm{new}}=kD_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot \\frac{1}{1000} \\frac{\\mathrm{second}}{\\mathrm{millisecond}} \\cdot \\mathrm{V\\_comp} \\frac{\\mathrm{V}}{\\mathrm{duty\\_cycle}}\\) Velocity without Voltage Comp Phoenix 5 ControlMode.Velocity with voltage compensation disabled maps to the Phoenix 6 VelocityDutyCycle control request. Additionally, kF from Phoenix 5 has been replaced with kV in Phoenix 6. Note This calculator assumes the RotorToSensorRatio and SensorToMechanismRatio configs are both set to 1. If this is not the case in your robot program, multiply the resulting gains by both ratios. Velocity without Voltage Compensation Name Value Units Formula kP Original \\(\\frac{\\mathrm{raw\\_output}}{\\mathrm{unit} / \\mathrm{100ms}}\\) \\(kP_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{duty\\_cycle}}{\\mathrm{rot} / \\mathrm{sec}}\\) \\(kP_{\\mathrm{new}}=kP_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot \\frac{1}{10} \\frac{\\mathrm{sec}}{\\mathrm{100ms}}\\) kI Original \\(\\frac{\\mathrm{raw\\_output}}{(\\mathrm{unit} / \\mathrm{100ms}) \\cdot \\mathrm{millisecond}}\\) \\(kI_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{duty\\_cycle}}{\\mathrm{rot}}\\) \\(kI_{\\mathrm{new}}=kI_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot 1000 \\frac{\\mathrm{millisecond}}{\\mathrm{second}} \\cdot \\frac{1}{10} \\frac{\\mathrm{sec}}{\\mathrm{100ms}}\\) kD Original \\(\\frac{\\mathrm{raw\\_output}}{(\\mathrm{unit} / \\mathrm{100ms}) / \\mathrm{millisecond}}\\) \\(kD_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{duty\\_cycle}}{\\mathrm{rot} / \\mathrm{second}^{2}}\\) \\(kD_{\\mathrm{new}}=kD_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot \\frac{1}{1000} \\frac{\\mathrm{second}}{\\mathrm{millisecond}} \\cdot \\frac{1}{10} \\frac{\\mathrm{sec}}{\\mathrm{100ms}}\\) kF kV Original \\(\\frac{\\mathrm{raw\\_output}}{\\mathrm{unit} / \\mathrm{100millisecond}}\\) \\(kF_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{duty\\_cycle}}{\\mathrm{rot} / \\mathrm{second}}\\) \\(kV_{\\mathrm{new}}=kF_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot \\frac{1}{10} \\frac{\\mathrm{second}}{\\mathrm{100ms}}\\) Velocity with Voltage Comp Phoenix 5 ControlMode.Velocity with voltage compensation enabled has been replaced with the Phoenix 6 VelocityVoltage control request, which directly controls voltage. Additionally, kF from Phoenix 5 has been replaced with kV in Phoenix 6. Note This calculator assumes the RotorToSensorRatio and SensorToMechanismRatio configs are both set to 1. If this is not the case in your robot program, multiply the resulting gains by both ratios. Velocity with Voltage Compensation Voltage Compensation Value: Name Value Units Formula kP Original \\(\\frac{\\mathrm{\\mathrm{raw\\_output}}}{\\mathrm{unit} / \\mathrm{100ms}}\\) \\(kP_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{V}}{\\mathrm{rot} / \\mathrm{sec}}\\) \\(kP_{\\mathrm{new}}=kP_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot \\frac{1}{10} \\frac{\\mathrm{second}}{\\mathrm{100ms}} \\cdot \\mathrm{V\\_comp} \\frac{\\mathrm{V}}{\\mathrm{duty\\_cycle}}\\) kI Original \\(\\frac{\\mathrm{\\mathrm{raw\\_output}}}{(\\mathrm{unit} / \\mathrm{100ms}) \\cdot \\mathrm{millisecond}}\\) \\(kI_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{V}}{\\mathrm{rot}}\\) \\(kI_{\\mathrm{new}}=kI_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot 1000 \\frac{\\mathrm{millisecond}}{\\mathrm{second}} \\cdot \\frac{1}{10} \\frac{\\mathrm{second}}{\\mathrm{100ms}} \\cdot \\mathrm{V\\_comp} \\frac{\\mathrm{V}}{\\mathrm{duty\\_cycle}}\\) kD Original \\(\\frac{\\mathrm{\\mathrm{raw\\_output}}}{(\\mathrm{unit} / \\mathrm{100ms}) / \\mathrm{millisecond}}\\) \\(kD_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{V}}{\\mathrm{rot} / \\mathrm{second}^{2}}\\) \\(kD_{\\mathrm{new}}=kD_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot \\frac{1}{1000} \\frac{\\mathrm{second}}{\\mathrm{millisecond}} \\cdot \\frac{1}{10} \\frac{\\mathrm{second}}{\\mathrm{100ms}} \\cdot \\mathrm{V\\_comp} \\frac{\\mathrm{V}}{\\mathrm{duty\\_cycle}}\\) kF kV Original \\(\\frac{\\mathrm{\\mathrm{raw\\_output}}}{\\mathrm{unit} / \\mathrm{100ms}}\\) \\(kF_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{V}}{\\mathrm{rot} / \\mathrm{second}}\\) \\(kV_{\\mathrm{new}}=kF_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot \\frac{1}{10} \\frac{\\mathrm{second}}{\\mathrm{100ms}} \\cdot \\mathrm{V\\_comp} \\frac{\\mathrm{V}}{\\mathrm{duty\\_cycle}}\\) Using Closed-Loop Control v5 Java // robot init, set slot 0 gains m_motor . config_kF ( 0 , 0.05 , 50 ); m_motor . config_kP ( 0 , 0.046 , 50 ); m_motor . config_kI ( 0 , 0.0002 , 50 ); m_motor . config_kD ( 0 , 4.2 , 50 ); // enable voltage compensation m_motor . configVoltageComSaturation ( 12 ); m_motor . enableVoltageCompensation ( true ); // periodic, run velocity control with slot 0 configs, // target velocity of 50 rps (10240 ticks/100ms) m_motor . selectProfileSlot ( 0 , 0 ); m_motor . set ( ControlMode . Velocity , 10240 ); C++ // robot init, set slot 0 gains m_motor . Config_kF ( 0 , 0.05 , 50 ); m_motor . Config_kP ( 0 , 0.046 , 50 ); m_motor . Config_kI ( 0 , 0.0002 , 50 ); m_motor . Config_kD ( 0 , 4.2 , 50 ); // enable voltage compensation m_motor . ConfigVoltageComSaturation ( 12 ); m_motor . EnableVoltageCompensation ( true ); // periodic, run velocity control with slot 0 configs, // target velocity of 50 rps (10240 ticks/100ms) m_motor . SelectProfileSlot ( 0 , 0 ); m_motor . Set ( ControlMode :: Velocity , 10240 ); v6 Java // class member variable final VelocityVoltage m_velocity = new VelocityVoltage ( 0 ); // robot init, set slot 0 gains var slot0Configs = new Slot0Configs (); slot0Configs . kV = 0.12 ; slot0Configs . kP = 0.11 ; slot0Configs . kI = 0.48 ; slot0Configs . kD = 0.01 ; m_talonFX . getConfigurator (). apply ( slot0Configs , 0.050 ); // periodic, run velocity control with slot 0 configs, // target velocity of 50 rps m_velocity . Slot = 0 ; m_motor . setControl ( m_velocity . withVelocity ( 50 )); C++ // class member variable controls :: VelocityVoltage m_velocity { 0 _tps }; // robot init, set slot 0 gains configs :: Slot0Configs slot0Configs {}; slot0Configs . kV = 0.12 ; slot0Configs . kP = 0.11 ; slot0Configs . kI = 0.48 ; slot0Configs . kD = 0.01 ; m_talonFX . GetConfigurator (). Apply ( slot0Configs , 50 _ms ); // periodic, run velocity control with slot 0 configs, // target velocity of 50 rps m_velocity . Slot = 0 ; m_motor . SetControl ( m_velocity . WithVelocity ( 50 _tps )); Motion Magic® v5 Java // robot init, set slot 0 gains m_motor . config_kF ( 0 , 0.05 , 50 ); // PID runs on position m_motor . config_kP ( 0 , 0.2 , 50 ); m_motor . config_kI ( 0 , 0 , 50 ); m_motor . config_kD ( 0 , 4.2 , 50 ); // set Motion Magic settings m_motor . configMotionCruiseVelocity ( 16384 ); // 80 rps = 16384 ticks/100ms cruise velocity m_motor . configMotionAcceleration ( 32768 ); // 160 rps/s = 32768 ticks/100ms/s acceleration m_motor . configMotionSCurveStrength ( 3 ); // s-curve smoothing strength of 3 // enable voltage compensation m_motor . configVoltageComSaturation ( 12 ); m_motor . enableVoltageCompensation ( true ); // periodic, run Motion Magic with slot 0 configs m_motor . selectProfileSlot ( 0 , 0 ); // target position of 200 rotations (409600 ticks) // add 0.02 (2%) arbitrary feedforward to overcome friction m_motor . set ( ControlMode . MotionMagic , 409600 , DemandType . ArbitraryFeedforward , 0.02 ); C++ // robot init, set slot 0 gains m_motor . Config_kF ( 0 , 0.05 , 50 ); // PID runs on position m_motor . Config_kP ( 0 , 0.2 , 50 ); m_motor . Config_kI ( 0 , 0 , 50 ); m_motor . Config_kD ( 0 , 4.2 , 50 ); // set Motion Magic settings m_motor . ConfigMotionCruiseVelocity ( 16384 ); // 80 rps = 16384 ticks/100ms cruise velocity m_motor . ConfigMotionAcceleration ( 32768 ); // 160 rps/s = 32768 ticks/100ms/s acceleration m_motor . ConfigMotionSCurveStrength ( 3 ); // s-curve smoothing strength of 3 // enable voltage compensation m_motor . ConfigVoltageComSaturation ( 12 ); m_motor . EnableVoltageCompensation ( true ); // periodic, run Motion Magic with slot 0 configs m_motor . SelectProfileSlot ( 0 , 0 ); // target position of 200 rotations (409600 ticks) // add 0.02 (2%) arbitrary feedforward to overcome friction m_motor . Set ( ControlMode :: MotionMagic , 409600 , DemandType :: ArbitraryFeedforward , 0.02 ); v6 Note The Motion Magic® S-Curve Strength has been replaced with jerk control in Phoenix 6. Java // class member variable final MotionMagicVoltage m_motmag = new MotionMagicVoltage ( 0 ); // robot init var talonFXConfigs = new TalonFXConfiguration (); // set slot 0 gains var slot0Configs = talonFXConfigs . Slot0Configs ; slot0Configs . kS = 0.24 ; // add 0.24 V to overcome friction slot0Configs . kV = 0.12 ; // apply 12 V for a target velocity of 100 rps // PID runs on position slot0Configs . kP = 4.8 ; slot0Configs . kI = 0 ; slot0Configs . kD = 0.1 ; // set Motion Magic settings var motionMagicConfigs = talonFXConfigs . MotionMagicConfigs ; motionMagicConfigs . MotionMagicCruiseVelocity = 80 ; // 80 rps cruise velocity motionMagicConfigs . MotionMagicAcceleration = 160 ; // 160 rps/s acceleration (0.5 seconds) motionMagicConfigs . MotionMagicJerk = 1600 ; // 1600 rps/s^2 jerk (0.1 seconds) m_talonFX . getConfigurator (). apply ( talonFXConfigs , 0.050 ); // periodic, run Motion Magic with slot 0 configs, // target position of 200 rotations m_motmag . Slot = 0 ; m_motor . setControl ( m_motmag . withPosition ( 200 )); C++ // class member variable controls :: MotionMagicVoltage m_motmag { 0 _tr }; // robot init configs :: TalonFXConfiguration talonFXConfigs {}; // set slot 0 gains auto & slot0Configs = talonFXConfigs . Slot0Configs ; slot0Configs . kS = 0.24 ; // add 0.24 V to overcome friction slot0Configs . kV = 0.12 ; // apply 12 V for a target velocity of 100 rps // PID runs on position slot0Configs . kP = 4.8 ; slot0Configs . kI = 0 ; slot0Configs . kD = 0.1 ; // set Motion Magic settings auto & motionMagicConfigs = talonFXConfigs . MotionMagicConfigs ; motionMagicConfigs . MotionMagicCruiseVelocity = 80 ; // 80 rps cruise velocity motionMagicConfigs . MotionMagicAcceleration = 160 ; // 160 rps/s acceleration (0.5 seconds) motionMagicConfigs . MotionMagicJerk = 1600 ; // 1600 rps/s^2 jerk (0.1 seconds) m_talonFX . GetConfigurator (). Apply ( talonFXConfigs , 50 _ms ); // periodic, run Motion Magic with slot 0 configs, // target position of 200 rotations m_motmag . Slot = 0 ; m_motor . SetControl ( m_motmag . WithPosition ( 200 _tr )); Motion Profiling Closed-loop control requests have been expanded to support motion profiles generated by the robot controller. Java // class member variable final PositionVoltage m_position = new PositionVoltage ( 0 ); // Trapezoid profile with max velocity 80 rps, max accel 160 rps/s final TrapezoidProfile m_profile = new TrapezoidProfile ( new TrapezoidProfile . Constraints ( 80 , 160 ) ); // Final target of 200 rot, 0 rps TrapezoidProfile . State m_goal = new TrapezoidProfile . State ( 200 , 0 ); TrapezoidProfile . State m_setpoint = new TrapezoidProfile . State (); // robot init, set slot 0 gains var slot0Configs = new Slot0Configs (); slot0Configs . kS = 0.24 ; // add 0.24 V to overcome friction slot0Configs . kV = 0.12 ; // apply 12 V for a target velocity of 100 rps slot0Configs . kP = 4.8 ; slot0Configs . kI = 0 ; slot0Configs . kD = 0.1 ; m_talonFX . getConfigurator (). apply ( Slot0Configs , 0.050 ); // periodic, update the profile setpoint for 20 ms loop time m_setpoint = m_profile . calculate ( 0.020 , m_setpoint , m_goal ); // apply the setpoint to the control request m_position . Position = m_setpoint . position ; m_position . Velocity = m_setpoint . velocity ; m_motor . setControl ( m_position ); C++ // class member variable controls :: PositionVoltage m_position { 0 _tr }; // Trapezoid profile with max velocity 80 rps, max accel 160 rps/s frc :: TrapezoidProfile < units :: turns > m_profile {{ 80 _tps , 160 _tr_per_s_sq }}; // Final target of 200 rot, 0 rps frc :: TrapezoidProfile < units :: turns >:: State m_goal { 200 _tr , 0 _tps }; frc :: TrapezoidProfile < units :: turns >:: State m_setpoint {}; // robot init, set slot 0 gains configs :: Slot0Configs slot0Configs {}; slot0Configs . kS = 0.24 ; // add 0.24 V to overcome friction slot0Configs . kV = 0.12 ; // apply 12 V for a target velocity of 100 rps slot0Configs . kP = 4.8 ; slot0Configs . kI = 0 ; slot0Configs . kD = 0.1 ; m_talonFX . GetConfigurator (). Apply ( slot0Configs , 50 _ms ); // periodic, update the profile setpoint for 20 ms loop time m_setpoint = m_profile . Calculate ( 20 _ms , m_setpoint , m_goal ); // apply the setpoint to the control request m_position . Position = m_setpoint . position ; m_position . Velocity = m_setpoint . velocity ; m_motor . SetControl ( m_position );",
- "content_preview": "Closed-Loop Control Phoenix 6 enhances the experience of using onboard closed-loop control through the use of standardized units and a variety of control output types. Note For more information about closed-loop control in Phoenix 6, see Closed-Loop Overview ."
+ "content": "Tuner History Tuner history provides insight on past connected devices and robot networks. Device History allows teams to view previously connected devices and license them without being directly connected to them. Users may wish to license previously connected devices due to a lack of internet connection while being connected to them. Network History indicates a list of past connected robot networks. Users can access a list of past devices connected to Tuner X and license them via the Device History page. This is accessible from the left-hand sidebar. This list is not automatically refreshed, but users can refresh it by pressing the refresh icon in the top-right of the page. Licensing from Device History Users can activate a license for a disconnected device by clicking on the device in the Grid. Then, select the “PRO” icon at the bottom right of the device card. From there, the user can activate a license for the device like normal. Once the device license has been activated, the user still needs to connect Tuner X to the robot to transfer the activated license to the device. The “PRO” icon may be replaced with a greyed “LIC” icon in the following situations: The device is on Phoenix 5 firmware and actively connected to Tuner X The device is not a Phoenix 6 compatible device Users who license an eligible Phoenix 6 device running Phoenix 5 firmware must update the device firmware to v6 compatible firmware to utilize licensed features.",
+ "content_preview": "Tuner History Tuner history provides insight on past connected devices and robot networks. Device History allows teams to view previously connected devices and license them without being directly connected to them."
},
{
"url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-swerve/swerve-system-requirements.html",
@@ -436,84 +420,76 @@
"content_preview": "Swerve Requirements The swerve project creator and swerve API have several limitations. These limitations are in place to maximize performance and improve maintainability. Only Phoenix 6 supported hardware (e.g. Talon FX, Talon FXS, CANcoder, CANdi™, Pigeon 2.0)."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-elev/calibration-and-limits.html",
- "title": "Calibration and Limits",
- "section": "Phoenix Tuner X",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/mechanisms/differential/differential-setup.html",
+ "title": "Differential Mechanism Setup",
+ "section": "API Reference",
"language": "All",
- "content": "Calibration and Limits Tuner will have the user perform a calibration routine, during which the user will bring the elevator to it’s lowest position, and then manually raise the elevator to it’s top-most position. This routine will automatically determine the bounds of the elevator and motor inverts. To begin, select Open Wizard . This will open the Elevator Calibation popup. Manually bring the elevator to it’s bottom-most position. This will act as it’s zero. Once this is done, press Zero Elevator . Press the arrow on the bottom-right of the popup to navigate to the next step in the wizard. Bring the elevator to it’s top-most position. Once this is done, press Stop Tracking . Go ahead and exit the popup. The Calibration Results will be populated with it’s results. Homing Limits The generated Elevator subsystem includes a function for performing a current-based homing routine to zero the elevator. However, the calibration and limits interface provides the ability to configure hardware or remote limits.",
- "content_preview": "Calibration and Limits Tuner will have the user perform a calibration routine, during which the user will bring the elevator to it’s lowest position, and then manually raise the elevator to it’s top-most position."
+ "content": "Differential Mechanism Setup The differential mechanism APIs are constructed by setting up a DifferentialMotorConstants ( Java , C++ , Python ) object. This includes necessary information such as the leader motor controllers’ IDs, gear ratio on the differential, and alignment of the two sides of the mechanism. Important The differential mechanism only manages one motor controller from each gearbox. Any other motors in the gearboxes should be separately constructed and configured to follow their corresponding leader. Defining the Motor Constants An example configuration for a differential wrist is shown below with the following setup: A 3:1 gear ratio on the average axis An additional 2:1 gear ratio on the differential axis The two motor directions are aligned (ignoring inverts) Closed-loop control and relevant status signals run at 200 Hz Warning Many of these constants, including the PID gains, are specific to this example and will not work on your mechanism. Java // Average axis gains typically go in Slot 0 private static final Slot0Configs averageGains = new Slot0Configs () . withKP ( 20 ). withKI ( 0 ). withKD ( 0.1 ) . withKG ( 0.2 ). withKS ( 0.1 ). withKV ( 0.36 ). withKA ( 0 ) . withGravityType ( GravityTypeValue . Arm_Cosine ); // Difference axis gains typically go in Slot 1 private static final Slot1Configs differenceGains = new Slot1Configs () . withKP ( 30 ). withKI ( 0 ). withKD ( 0.1 ) . withKS ( 0.1 ). withKV ( 0.72 ); private static final double kAverageGearRatio = 3.0 ; private static final double kDifferenceGearRatio = 2.0 ; // Initial configs for the differential leader and follower motor controllers. // Some configs will be overwritten; check the `with*InitialConfigs()` API documentation. private static final TalonFXConfiguration leaderInitialConfigs = new TalonFXConfiguration () . withMotorOutput ( new MotorOutputConfigs () . withNeutralMode ( NeutralModeValue . Brake ) ) . withCurrentLimits ( new CurrentLimitsConfigs () . withStatorCurrentLimit ( Amps . of ( 80 )) . withStatorCurrentLimitEnable ( true ) ) . withFeedback ( new FeedbackConfigs () . withSensorToMechanismRatio ( kAverageGearRatio ) ) . withClosedLoopGeneral ( new ClosedLoopGeneralConfigs () // differential wrist is continuous on the difference axis . withDifferentialContinuousWrap ( true ) ) . withSlot0 ( averageGains ) . withSlot1 ( differenceGains ) . withMotionMagic ( new MotionMagicConfigs () . withMotionMagicCruiseVelocity ( 80 ) . withMotionMagicAcceleration ( 320 ) ); private static final TalonFXConfiguration followerInitialConfigs = new TalonFXConfiguration () . withFeedback ( new FeedbackConfigs () . withSensorToMechanismRatio ( kAverageGearRatio ) ); // CAN bus that the devices are located on; // All mechanism devices must share the same CAN bus private static final CANBus kCANBus = new CANBus ( \"canivore\" , \"./logs/example.hoot\" ); private static final DifferentialMotorConstants < TalonFXConfiguration > differentialConstants = new DifferentialMotorConstants < TalonFXConfiguration > () . withCANBusName ( kCANBus . getName ()) . withLeaderId ( 0 ) . withFollowerId ( 1 ) . withAlignment ( MotorAlignmentValue . Aligned ) . withSensorToDifferentialRatio ( kDifferenceGearRatio ) . withClosedLoopRate ( 200.0 ) . withLeaderInitialConfigs ( leaderInitialConfigs ) . withFollowerInitialConfigs ( followerInitialConfigs ) . withFollowerUsesCommonLeaderConfigs ( true ); C++ // Average axis gains typically go in Slot 0 static constexpr configs :: Slot0Configs averageGains = configs :: Slot0Configs {} . WithKP ( 10 ). WithKI ( 0 ). WithKD ( 0.1 ) . WithKG ( 0.2 ). WithKS ( 0.1 ). WithKV ( 0.12 ). WithKA ( 0 ) . WithGravityType ( signals :: GravityTypeValue :: Arm_Cosine ); // Difference axis gains typically go in Slot 1 static constexpr configs :: Slot1Configs differenceGains = configs :: Slot1Configs {} . WithKP ( 10 ). WithKI ( 0 ). WithKD ( 0.1 ) . WithKS ( 0.1 ). WithKV ( 0.12 ); static constexpr units :: scalar_t kAverageGearRatio = 3.0 ; static constexpr units :: scalar_t kDifferenceGearRatio = 2.0 ; // Initial configs for the differential leader and follower motor controllers. // Some configs will be overwritten; check the `With*InitialConfigs()` API documentation. static constexpr configs :: TalonFXConfiguration leaderInitialConfigs = configs :: TalonFXConfiguration {} . WithMotorOutput ( configs :: MotorOutputConfigs {} . WithNeutralMode ( signals :: NeutralModeValue :: Brake ) ) . WithCurrentLimits ( configs :: CurrentLimitsConfigs {} . WithStatorCurrentLimit ( 80 _A ) . WithStatorCurrentLimitEnable ( true ) ) . WithFeedback ( configs :: FeedbackConfigs {} . WithSensorToMechanismRatio ( kAverageGearRatio ) ) . withClosedLoopGeneral ( configs :: ClosedLoopGeneralConfigs {} // differential wrist is continuous on the difference axis . WithDifferentialContinuousWrap ( true ) ) . WithSlot0 ( averageGains ) . WithSlot1 ( differenceGains ) . WithMotionMagic ( configs :: MotionMagicConfigs {} . WithMotionMagicCruiseVelocity ( 80 _tps ) . WithMotionMagicAcceleration ( 320 _tr_per_s_sq ) ); static constexpr configs :: TalonFXConfiguration followerInitialConfigs = configs :: TalonFXConfiguration {} . WithFeedback ( configs :: FeedbackConfigs {} . WithSensorToMechanismRatio ( kAverageGearRatio ) ); // CAN bus that the devices are located on; // All mechanism devices must share the same CAN bus static constexpr std :: string_view kCANBusName = \"canivore\" ; static inline CANBus kCANBus { kCANBusName , \"./logs/example.hoot\" }; static constexpr mechanisms :: DifferentialMotorConstants differentialConstants = mechanisms :: DifferentialMotorConstants < configs :: TalonFXConfiguration > {} . WithCANBusName ( kCANBusName ) . WithLeaderId ( 0 ) . WithFollowerId ( 1 ) . WithAlignment ( signals :: MotorAlignmentValue :: Opposed ) . WithSensorToDifferentialRatio ( kDifferenceGearRatio ) . WithClosedLoopRate ( 200 _Hz ) . WithLeaderInitialConfigs ( leaderInitialConfigs ) . WithFollowerInitialConfigs ( followerInitialConfigs ) . WithFollowerUsesCommonLeaderConfigs ( true ); Python # Average axis gains typically go in Slot 0 _average_gains = ( configs . Slot0Configs () . with_k_p ( 10 ) . with_k_i ( 0 ) . with_k_d ( 0.1 ) . with_k_g ( 0.2 ) . with_k_s ( 0.1 ) . with_k_v ( 0.12 ) . with_k_a ( 0 ) . with_gravity_type ( signals . GravityTypeValue . ARM_COSINE ) ) # Difference axis gains typically go in Slot 1 _difference_gains = ( configs . Slot1Configs () . with_k_p ( 10 ) . with_k_i ( 0 ) . with_k_d ( 0.1 ) . with_k_s ( 0.1 ) . with_k_v ( 0.12 ) ) _average_gear_ratio = 3.0 _difference_gear_ratio = 2.0 # Initial configs for the differential leader and follower motor controllers. # Some configs will be overwritten; check the `with*InitialConfigs()` API documentation. _leader_initial_configs = ( configs . TalonFXConfiguration () . with_motor_output ( configs . MotorOutputConfigs () . with_neutral_mode ( signals . NeutralModeValue . BRAKE ) ) . with_current_limits ( configs . CurrentLimitsConfigs () . with_stator_current_limit ( 80.0 ) . with_stator_current_limit_enable ( True ) ) . with_feedback ( configs . FeedbackConfigs () . with_sensor_to_mechanism_ratio ( _average_gear_ratio ) ) . with_closed_loop_general ( configs . ClosedLoopGeneralConfigs () # differential wrist is continuous on the difference axis . with_differential_continuous_wrap ( True ) ) . with_slot0 ( _average_gains ) . with_slot1 ( _difference_gains ) . with_motion_magic ( configs . MotionMagicConfigs () . with_motion_magic_cruise_velocity ( 80 ) . with_motion_magic_acceleration ( 320 ) ) ) _follower_initial_configs = ( configs . TalonFXConfiguration () . with_feedback ( configs . FeedbackConfigs () . with_sensor_to_mechanism_ratio ( _average_gear_ratio ) ) ) # CAN bus that the devices are located on; # All mechanism devices must share the same CAN bus self . _canbus = CANBus ( \"canivore\" , \"./logs/example.hoot\" ) self . _differential_constants : mechanisms . DifferentialMotorConstants [ configs . TalonFXConfiguration ] = ( mechanisms . DifferentialMotorConstants () . with_can_bus_name ( _canbus . name ) . with_leader_id ( 0 ) . with_follower_id ( 1 ) . with_alignment ( signals . MotorAlignmentValue . OPPOSED ) . with_sensor_to_differential_ratio ( _difference_gear_ratio ) . with_closed_loop_rate ( 200.0 ) . with_leader_initial_configs ( _leader_initial_configs ) . with_follower_initial_configs ( _follower_initial_configs ) . with_follower_uses_common_leader_configs ( True ) ) Building the Mechanism The differential motor constants can then be used to construct the DifferentialMechanism ( Java , C++ , Python ) or SimpleDifferentialMechanism ( Java , C++ , Python ). The mechanisms have constructor overloads to provide a remote sensor for the Difference axis, such as the yaw of a Pigeon 2. DifferentialMechanism Java // Construct the mechanism, Difference axis uses half the difference between the motors private final DifferentialMechanism < TalonFX > diffMech = new DifferentialMechanism < TalonFX > ( TalonFX :: new , differentialConstants ); // Construct the mechanism, Difference axis uses the yaw of a Pigeon 2 private final Pigeon2 pigeon2 = new Pigeon2 ( 0 , kCANBus ); private final DifferentialMechanism < TalonFX > diffMech = new DifferentialMechanism < TalonFX > ( TalonFX :: new , differentialConstants , pigeon2 , DifferentialPigeon2Source . Yaw ); C++ // Construct the mechanism, Difference axis uses half the difference between the motors mechanisms :: DifferentialMechanism < hardware :: TalonFX > diffMech { differentialConstants }; // Construct the mechanism, Difference axis uses the yaw of a Pigeon 2 hardware :: Pigeon2 pigeon2 { 0 , kCANBus }; mechanisms :: DifferentialMechanism < hardware :: TalonFX > diffMech { differentialConstants , pigeon2 , mechanisms :: DifferentialPigeon2Source :: Yaw }; Python # Construct the mechanism, Difference axis uses half the difference between the motors self . _diff_mech : mechanisms . DifferentialMechanism [ hardware . TalonFX ] = ( mechanisms . DifferentialMechanism ( hardware . TalonFX , self . _differential_constants ) ) # Construct the mechanism, Difference axis uses the yaw of a Pigeon 2 self . _pigeon2 = hardware . Pigeon2 ( 0 , self . _canbus ) self . _diff_mech : mechanisms . DifferentialMechanism [ hardware . TalonFX ] = ( mechanisms . DifferentialMechanism ( hardware . TalonFX , self . _differential_constants self . _pigeon2 , mechanisms . DifferentialPigeon2Source . YAW ) ) SimpleDifferentialMechanism Java // Construct the mechanism, Difference axis uses half the difference between the motors private final SimpleDifferentialMechanism < TalonFX > diffMech = new SimpleDifferentialMechanism < TalonFX > ( TalonFX :: new , differentialConstants ); // Construct the mechanism, Difference axis uses the yaw of a Pigeon 2 private final Pigeon2 pigeon2 = new Pigeon2 ( 0 , kCANBus ); private final SimpleDifferentialMechanism < TalonFX > diffMech = new SimpleDifferentialMechanism < TalonFX > ( TalonFX :: new , differentialConstants , pigeon2 , DifferentialPigeon2Source . Yaw ); C++ // Construct the mechanism, Difference axis uses half the difference between the motors mechanisms :: SimpleDifferentialMechanism < hardware :: TalonFX > diffMech { differentialConstants }; // Construct the mechanism, Difference axis uses the yaw of a Pigeon 2 hardware :: Pigeon2 pigeon2 { 0 , kCANBus }; mechanisms :: SimpleDifferentialMechanism < hardware :: TalonFX > diffMech { differentialConstants , pigeon2 , mechanisms :: DifferentialPigeon2Source :: Yaw }; Python # Construct the mechanism, Difference axis uses half the difference between the motors self . _diff_mech : mechanisms . SimpleDifferentialMechanism [ hardware . TalonFX ] = ( mechanisms . SimpleDifferentialMechanism ( hardware . TalonFX , self . _differential_constants ) ) # Construct the mechanism, Difference axis uses the yaw of a Pigeon 2 self . _pigeon2 = hardware . Pigeon2 ( 0 , self . _canbus ) self . _diff_mech : mechanisms . SimpleDifferentialMechanism [ hardware . TalonFX ] = ( mechanisms . SimpleDifferentialMechanism ( hardware . TalonFX , self . _differential_constants self . _pigeon2 , mechanisms . DifferentialPigeon2Source . YAW ) )",
+ "content_preview": "Differential Mechanism Setup The differential mechanism APIs are constructed by setting up a DifferentialMotorConstants ( Java , C++ , Python ) object. This includes necessary information such as the leader motor controllers’ IDs, gear ratio on the differential, and alignment of the two sides of..."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/configs.html",
- "title": "Tuner Configs",
- "section": "Phoenix Tuner X",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/mechanisms/differential/differential-tuning.html",
+ "title": "Tuning a Differential Mechanism",
+ "section": "API Reference",
"language": "All",
- "content": "Tuner Configs Tip Devices can also be configured in code . Configs can be viewed, modified, backed-up, restored, and factory-defaulted via the Configs tab in Phoenix Tuner X. To apply a modified config, press the apply button (download icon) on the top button bar. Additional options are available by clicking on the 3-dots icon on the top button bar.",
- "content_preview": "Tuner Configs Tip Devices can also be configured in code . Configs can be viewed, modified, backed-up, restored, and factory-defaulted via the Configs tab in Phoenix Tuner X. To apply a modified config, press the apply button (download icon) on the top button bar."
+ "content": "Tuning a Differential Mechanism Important This article assumes that you are familiar with tuning PID on a single-axis mechanism. For more information, see Closed-Loop Overview . Because a differential mechanism has two axes of motion , it also needs separate sets of PID gains for the Average axis (typically Slot 0) and the Difference axis (typically Slot 1). These gains are applied to the differential leader . Warning Tuner X only supports the SimpleDifferentialMechanism control requests. For full functionality while tuning, use the DifferentialMechanism API for control and Tuner X for plotting and configs. See Using the Differential Mechanism API for more information. Tuning a differential mechanism typically involves three steps: Step 1: Weak kP on the Difference Axis When tuning closed-loop control on a differential mechanism, it is important that the axis not being tuned is roughly held in place. Failing to do so can result in damage to the mechanism at stronger gains. To accomplish this, a relatively weak kP can be applied to the Difference axis prior to tuning the average axis. In Tuner X, select the differential leader motor controller. The DifferentialPosition signal can be plotted to determine a reasonable setpoint. From there, apply an open-loop request to the average axis and a position closed-loop request to the Difference axis (for SimpleDifferentialMechanism , use the Differential{OutputType} requests). Adjust the kP on the Difference axis until there is a reasonable amount of resistance to motion without oscillation. Important Do not try to optimize the feedforward and PID gains on the Difference axis at this point. Step 2: Feedforwards and PID on the Average Axis With the Difference axis now roughly held in place, the focus shifts to tuning the feedforwards and PID on the Average axis. The tuning process on the average axis is roughly the same as with a single-axis mechanism (such as a single gearbox elevator) with two changes: Use the DifferentialAveragePosition and DifferentialAverageVelocity signals instead of the Position and Velocity signals. Use the differential mechanism or control requests (such as DifferentialMotionMagicVoltage ) when tuning, keeping the Difference axis held in place. As is the case with a single-axis mechanism, many gains on the Average axis scale with the RotorToSensorRatio and SensorToMechanismRatio . For example, a Kraken X60 differential mechanism with 3:1 gearing on the average axis would have a kV of around 0.36 V/rps ( \\(K_{v\\_avg} = 3.0 * 0.12\\) V/rps) on the Average axis. Step 3: Feedforwards and PID on the Difference Axis Finally, the feedforwards and PID gains on the Difference axis can be fully tuned: Use the DifferentialDifferencePosition and DifferentialDifferenceVelocity signals instead of the Position and Velocity signals. Use the Differential closed-loop signals (such as DifferentialClosedLoopReference ) instead of the regular closed-loop signals. Use the differential mechanism or control requests (such as DifferentialMotionMagicVoltage ) when tuning, keeping the Average axis held in place. Note For some mechanisms like a two-gearbox elevator, these gains may be left on the weaker side, as the focus is on the Average axis. At a SensorToDifferentialRatio of 1.0, the scale of the gains on the Difference axis will be similar to those on the Average axis. For example, a Kraken X60 differential mechanism with 1:1 gearing on all axes will have a kV of around 0.12 V/rps on both the Average axis and the Differential axis. However, note that kG is often 0 on the Difference axis. Otherwise, the gains on the Difference axis scale with the SensorToDifferentialRatio relative to the Average axis . For example, a 3:1 gearing on the Average axis and an additional 2:1 gearing on the Difference axis would result in a kV of around 0.72 V/rps ( \\(K_{v\\_diff} = 2.0 * K_{v\\_avg}\\) ) on the Difference axis.",
+ "content_preview": "Tuning a Differential Mechanism Important This article assumes that you are familiar with tuning PID on a single-axis mechanism. For more information, see Closed-Loop Overview ."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/licensing/team-licensing.html",
- "title": "Season Pass Licensing",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/migration/migration-guide/control-requests-guide.html",
+ "title": "Control Requests",
"section": "General",
"language": "All",
- "content": "Season Pass Licensing Tip Season Pass can be purchased at https://store.ctr-electronics.com/ . Season Pass is a single, cost-effective purchase that allows FRC teams to license compatible devices with Phoenix Pro for the entire season. The steps for licensing devices with season pass are as follows. Purchase a season pass at https://store.ctr-electronics.com/ . Wait for the email that says your license is ready (typically 1-2 minutes). Follow the below steps for attaching a team number to your license. Once a team number has been attached, licensing process is the same as Device Licensing . When is a Season Pass Applicable? Season Pass licenses enable Pro features when the licensed device is: Used on a roboRIO configured for the team number assigned to the Season Pass Season Pass licensing will work on the roboRIO regardless of whether the device is on the native CAN Bus or a CANivore Bus. Used for Hardware Attached Simulation (a CANivore connected to PC) with a WPILib robot project Used on a roboRIO configured for one of the reserved “Off-Season Demo” team numbers (currently team numbers 9970 - 9999) Attaching a Team Number to Season Pass Warning Attaching a team number to a season pass is permanent. On the Profile page of Tuner X, click on the license to attach a team for. Enter the team number in the box below the list of licenses. Click Assign Team . A prompt will appear asking the user to confirm the entered team number. Note Note that the robot must be configured for the assigned team number. An invalid team number on the robot will result in the device not appearing as Pro licensed. How many devices can I activate? A season pass contains 100 individual device licenses. In the event that a team needs more licenses, contact us at support @ ctr-electronics . com .",
- "content_preview": "Season Pass Licensing Tip Season Pass can be purchased at https://store.ctr-electronics.com/ . Season Pass is a single, cost-effective purchase that allows FRC teams to license compatible devices with Phoenix Pro for the entire season."
- },
- {
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/mechanisms/swerve/swerve-requests.html",
- "title": "Swerve Requests",
- "section": "API Reference",
- "language": "All",
- "content": "Swerve Requests Controlling the drivetrain is done by calling setControl(SwerveRequest) ( Java , C++ , Python ) periodically, which takes a given SwerveRequest ( Java , C++ , Python ). There are multiple pre-defined SwerveRequest implementations that cover the majority of use cases. In some advanced scenarios, users can also define their own. Applying a Request Requests are instantiated once and then mutated using various withX functions. In the example below, a FieldCentric ( Java , C++ , Python ) request is created and given values from a joystick. Java private double MaxSpeed = TunerConstants . kSpeedAt12Volts . in ( MetersPerSecond ); private double MaxAngularRate = RotationsPerSecond . of ( 0.75 ). in ( RadiansPerSecond ); private final SwerveRequest . FieldCentric m_driveRequest = new SwerveRequest . FieldCentric () . withDeadband ( MaxSpeed * 0.1 ). withRotationalDeadband ( MaxAngularRate * 0.1 ) // Add a 10% deadband . withDriveRequestType ( DriveRequestType . OpenLoopVoltage ) . withSteerRequestType ( SteerRequestType . Position ); private final XboxController m_joystick = new XboxController ( 0 ); public final TunerSwerveDrivetrain drivetrain = TunerConstants . createDrivetrain (); @Override public void teleopPeriodic () { // Note that X is defined as forward according to WPILib convention, // and Y is defined as to the left according to WPILib convention. drivetrain . setControl ( m_driveRequest . withVelocityX ( - joystick . getLeftY () * MaxSpeed ) . withVelocityY ( - joystick . getLeftX () * MaxSpeed ) . withRotationalRate ( - joystick . getRightX () * MaxAngularRate ) ); } C++ private : units :: meters_per_second_t MaxSpeed = TunerConstants :: kSpeedAt12Volts ; units :: radians_per_second_t MaxAngularRate = 0.75 _tps ; swerve :: requests :: FieldCentric m_driveRequest = swerve :: requests :: FieldCentric {} . WithDeadband ( MaxSpeed * 0.1 ). WithRotationalDeadband ( MaxAngularRate * 0.1 ) // Add a 10% deadband . WithDriveRequestType ( swerve :: DriveRequestType :: OpenLoopVoltage ) . WithSteerRequestType ( swerve :: SteerRequestType :: Position ); frc :: XboxController m_joystick { 0 }; public : TunerSwerveDrivetrain drivetrain { TunerConstants :: CreateDrivetrain ()}; void TeleopPeriodic () override { // Note that X is defined as forward according to WPILib convention, // and Y is defined as to the left according to WPILib convention. drivetrain . SetControl ( m_driveRequest . WithVelocityX ( - joystick . GetLeftY () * MaxSpeed ) . WithVelocityY ( - joystick . GetLeftX () * MaxSpeed ) . WithRotationalRate ( - joystick . GetRightX () * MaxAngularRate ) ); } Python self . _max_speed = ( TunerConstants . speed_at_12_volts ) self . _max_angular_rate = rotationsToRadians ( 0.75 ) self . _drive_request = ( swerve . requests . FieldCentric () . with_deadband ( self . _max_speed * 0.1 ) . with_rotational_deadband ( self . _max_angular_rate * 0.1 ) # Add a 10% deadband . with_drive_request_type ( swerve . SwerveModule . DriveRequestType . OPEN_LOOP_VOLTAGE ) . with_steer_request_type ( swerve . SwerveModule . SteerRequestType . POSITION ) ) self . _joystick = XboxController ( 0 ) self . drivetrain = TunerConstants . create_drivetrain () def teleopPeriodic (): # Note that X is defined as forward according to WPILib convention, # and Y is defined as to the left according to WPILib convention. self . drivetrain . set_control ( self . _drive_request . with_velocity_x ( - self . _joystick . getLeftY () * self . _max_speed ) . with_velocity_y ( - self . _joystick . getLeftX () * self . _max_speed ) . with_rotational_rate ( - self . _joystick . getRightX () * self . _max_angular_rate ) ) Command-Based When using the command-based CommandSwerveDrivetrain generated by Tuner X, the applyRequest(Supplier) method can instead be used to get a command that periodically applies the SwerveRequest returned by the lambda. Java private double MaxSpeed = TunerConstants . kSpeedAt12Volts . in ( MetersPerSecond ); private double MaxAngularRate = RotationsPerSecond . of ( 0.75 ). in ( RadiansPerSecond ); private final SwerveRequest . FieldCentric m_driveRequest = new SwerveRequest . FieldCentric () . withDeadband ( MaxSpeed * 0.1 ). withRotationalDeadband ( MaxAngularRate * 0.1 ) // Add a 10% deadband . withDriveRequestType ( DriveRequestType . OpenLoopVoltage ) . withSteerRequestType ( SteerRequestType . Position ); private final CommandXboxController m_joystick = new CommandXboxController ( 0 ); public final CommandSwerveDrivetrain drivetrain = TunerConstants . createDrivetrain (); public void configureBindings () { // Note that X is defined as forward according to WPILib convention, // and Y is defined as to the left according to WPILib convention. drivetrain . setDefaultCommand ( // Drivetrain will execute this command periodically drivetrain . applyRequest (() -> m_driveRequest . withVelocityX ( - joystick . getLeftY () * MaxSpeed ) . withVelocityY ( - joystick . getLeftX () * MaxSpeed ) . withRotationalRate ( - joystick . getRightX () * MaxAngularRate ) ) ); // Idle while the robot is disabled. This ensures the configured // neutral mode is applied to the drive motors while disabled. final var idle = new SwerveRequest . Idle (); RobotModeTriggers . disabled (). whileTrue ( drivetrain . applyRequest (() -> idle ). ignoringDisable ( true ) ); } C++ private : units :: meters_per_second_t MaxSpeed = TunerConstants :: kSpeedAt12Volts ; units :: radians_per_second_t MaxAngularRate = 0.75 _tps ; swerve :: requests :: FieldCentric m_driveRequest = swerve :: requests :: FieldCentric {} . WithDeadband ( MaxSpeed * 0.1 ). WithRotationalDeadband ( MaxAngularRate * 0.1 ) // Add a 10% deadband . WithDriveRequestType ( swerve :: DriveRequestType :: OpenLoopVoltage ) . WithSteerRequestType ( swerve :: SteerRequestType :: Position ); frc :: XboxController m_joystick { 0 }; public : subsystems :: CommandSwerveDrivetrain drivetrain { TunerConstants :: CreateDrivetrain ()}; void ConfigureBindings () { // Note that X is defined as forward according to WPILib convention, // and Y is defined as to the left according to WPILib convention. drivetrain . SetDefaultCommand ( // Drivetrain will execute this command periodically drivetrain . ApplyRequest ([ this ]() -> auto && { return m_driveRequest . WithVelocityX ( - joystick . GetLeftY () * MaxSpeed ) . WithVelocityY ( - joystick . GetLeftX () * MaxSpeed ) . WithRotationalRate ( - joystick . GetRightX () * MaxAngularRate ); }) ); // Idle while the robot is disabled. This ensures the configured // neutral mode is applied to the drive motors while disabled. frc2 :: RobotModeTriggers :: Disabled (). WhileTrue ( drivetrain . ApplyRequest ([] { return swerve :: requests :: Idle {}; }). IgnoringDisable ( true ) ); } Python self . _max_speed = ( TunerConstants . speed_at_12_volts ) self . _max_angular_rate = rotationsToRadians ( 0.75 ) self . _drive_request = ( swerve . requests . FieldCentric () . with_deadband ( self . _max_speed * 0.1 ) . with_rotational_deadband ( self . _max_angular_rate * 0.1 ) # Add a 10% deadband . with_drive_request_type ( swerve . SwerveModule . DriveRequestType . OPEN_LOOP_VOLTAGE ) . with_steer_request_type ( swerve . SwerveModule . SteerRequestType . POSITION ) ) self . _joystick = CommandXboxController ( 0 ) self . drivetrain = TunerConstants . create_drivetrain () def configureButtonBindings () -> None : # Note that X is defined as forward according to WPILib convention, # and Y is defined as to the left according to WPILib convention. self . drivetrain . setDefaultCommand ( # Drivetrain will execute this command periodically self . drivetrain . apply_request ( lambda : ( self . _drive_request . with_velocity_x ( - self . _joystick . getLeftY () * self . _max_speed ) # Drive forward with negative Y (forward) . with_velocity_y ( - self . _joystick . getLeftX () * self . _max_speed ) # Drive left with negative X (left) . with_rotational_rate ( - self . _joystick . getRightX () * self . _max_angular_rate ) # Drive counterclockwise with negative X (left) ) ) ) # Idle while the robot is disabled. This ensures the configured # neutral mode is applied to the drive motors while disabled. idle = swerve . requests . Idle () Trigger ( DriverStation . isDisabled ) . whileTrue ( self . drivetrain . apply_request ( lambda : idle ) . ignoringDisable ( True ) ) Custom Swerve Requests In many cases, advanced control logic can live in the command applying the swerve request. For example, path following is typically implemented using a WPILib Command factory in the subsystem. Most path planning libraries generate the path setpoints in the main robot loop, and PID on the Pose2d must be run inline with setpoint generation. However, there are some advanced cases where it is beneficial to run some of the control logic at the higher update frequency of the odometry thread. To accomplish that, users can define custom swerve requests by implementing the SwerveRequest interface. In a custom swerve request, the control logic lives in the apply(...) method, which is called by the odometry thread. Important Custom swerve requests can have a performance cost compared to the native implementations. Additionally, the apply(...) method must be fast to avoid blocking odometry updates. Swerve Requests with Composition To maximize performance and minimize duplicate code, most custom swerve requests should be built on top of existing ones. For example, the built-in FieldCentricFacingAngle ( Java , C++ , Python ) request uses a regular FieldCentric request under the hood, as demonstrated below. Java private final FieldCentric m_fieldCentric = new FieldCentric (); @Override public StatusCode apply ( SwerveControlParameters parameters , SwerveModule , ? , ?> ... modulesToApply ) { Rotation2d angleToFace = TargetDirection ; if ( ForwardPerspective == ForwardPerspectiveValue . OperatorPerspective ) { /* If we're operator perspective, rotate the direction we want to face by the angle */ angleToFace = angleToFace . rotateBy ( parameters . operatorForwardDirection ); } double toApplyOmega = TargetRateFeedforward + HeadingController . calculate ( parameters . currentPose . getRotation (). getRadians (), angleToFace . getRadians (), parameters . timestamp ); /* The rest of the logic is the same as FieldCentric, so * set up and call FieldCentric's apply() method */ return m_fieldCentric . withVelocityX ( VelocityX ) . withVelocityY ( VelocityY ) . withRotationalRate ( toApplyOmega ) . withDeadband ( Deadband ) . withRotationalDeadband ( RotationalDeadband ) . withCenterOfRotation ( CenterOfRotation ) . withDriveRequestType ( DriveRequestType ) . withSteerRequestType ( SteerRequestType ) . withDesaturateWheelSpeeds ( DesaturateWheelSpeeds ) . withForwardPerspective ( ForwardPerspective ) . apply ( parameters , modulesToApply ); } C++ ctre :: phoenix :: StatusCode Apply ( swerve :: requests :: SwerveRequest :: ControlParameters const & parameters , std :: span < std :: unique_ptr < swerve :: impl :: SwerveModuleImpl > const > modulesToApply ) override { swerve :: Rotation2d angleToFace = TargetDirection ; if ( ForwardPerspective == swerve :: requests :: ForwardPerspectiveValue :: OperatorPerspective ) { /* If we're operator perspective, rotate the direction we want to face by the angle */ angleToFace = angleToFace . RotateBy ( parameters . operatorForwardDirection ); } units :: radians_per_second_t toApplyOmega = TargetRateFeedforward + units :: radians_per_second_t { HeadingController . Calculate ( parameters . currentPose . Rotation (). Radians (). value (), angleToFace . Radians (). value (), parameters . timestamp )}; /* The rest of the logic is the same as FieldCentric, so * set up and call FieldCentric's Apply() method */ return swerve :: requests :: FieldCentric {} . WithVelocityX ( VelocityX ) . WithVelocityY ( VelocityY ) . WithRotationalRate ( toApplyOmega ) . WithDeadband ( Deadband ) . WithRotationalDeadband ( RotationalDeadband ) . WithCenterOfRotation ( CenterOfRotation ) . WithDriveRequestType ( DriveRequestType ) . WithSteerRequestType ( SteerRequestType ) . WithDesaturateWheelSpeeds ( DesaturateWheelSpeeds ) . WithForwardPerspective ( ForwardPerspective ) . Apply ( parameters , modulesToApply ); } Python def __init__ ( self ): # ... self . __field_centric = FieldCentric () def apply ( self , parameters : swerve . SwerveControlParameters , modules_to_apply : list [ swerve . SwerveModule ] ) -> StatusCode : angle_to_face = self . target_direction if self . forward_perspective is swerve . requests . ForwardPerspectiveValue . OPERATOR_PERSPECTIVE : # If we're operator perspective, rotate the direction we want to face by the angle angle_to_face = angle_to_face . rotateBy ( parameters . operator_forward_direction ) to_apply_omega = self . target_rate_feedforward + self . heading_controller . calculate ( parameters . current_pose . rotation () . radians (), angle_to_face . radians (), parameters . timestamp ) # The rest of the logic is the same as FieldCentric, so # set up and call FieldCentric's apply() method return ( self . __field_centric . with_velocity_x ( self . velocity_x ) . with_velocity_y ( self . velocity_y ) . with_rotational_rate ( to_apply_omega ) . with_deadband ( self . deadband ) . with_rotational_deadband ( self . rotational_deadband ) . with_center_of_rotation ( self . center_of_rotation ) . with_drive_request_type ( self . drive_request_type ) . with_steer_request_type ( self . steer_request_type ) . with_desaturate_wheel_speeds ( self . desaturate_wheel_speeds ) . with_forward_perspective ( self . forward_perspective ) . apply ( parameters , modules_to_apply ) ) Swerve Requests with Module Targets In a few cases, none of the existing swerve request implementations may be suitable for the desired request. For example, there is no built-in swerve request that directly accepts an array of SwerveModuleState instances. In that situation, the custom swerve request can call apply(SwerveModule.ModuleRequest) ( Java , C++ , Python ) on each SwerveModule instance provided to the apply(...) method. Note, however, that this can negatively impact performance of the robot, both in terms of loop times and control accuracy, compared to reusing the built-in requests. As a result, we recommend converting to supported types, such as ChassisSpeeds , and reusing existing swerve requests, such as ApplyFieldSpeeds ( Java , C++ , Python ), whenever possible. Warning We recommend against using a custom swerve request for the WPILib SwerveControllerCommand , as it does not follow modern WPILib best practices. Instead, the command can be reimplemented as a command factory using ApplyFieldSpeeds to maximize performance. Java public class ApplyModuleStates implements SwerveRequest { public SwerveModuleState [] ModuleStates = new SwerveModuleState [ 0 ] ; @Override public StatusCode apply ( SwerveControlParameters parameters , SwerveModule , ? , ?> ... modulesToApply ) { var moduleRequest = new SwerveModule . ModuleRequest () . withUpdatePeriod ( parameters . updatePeriod ); for ( int i = 0 ; i < modulesToApply . length && i < ModuleStates . length ; ++ i ) { /* apply the SwerveModuleState to the module */ modulesToApply [ i ] . apply ( moduleRequest . withState ( ModuleStates [ i ] )); } } } C++ struct ApplyModuleStates : public swerve :: requests :: SwerveRequest { std :: vector < SwerveModuleState > ModuleStates ; ctre :: phoenix :: StatusCode Apply ( swerve :: requests :: SwerveRequest :: ControlParameters const & parameters , std :: span < std :: unique_ptr < swerve :: impl :: SwerveModuleImpl > const > modulesToApply ) override { auto moduleRequest = impl :: SwerveModuleImpl :: ModuleRequest {} . WithUpdatePeriod ( parameters . updatePeriod ); for ( size_t i = 0 ; i < modulesToApply . size () && i < ModuleStates . size (); ++ i ) { /* apply the SwerveModuleState to the module */ modulesToApply [ i ] -> Apply ( moduleRequest . WithState ( ModuleStates [ i ])); } } }; Python class ApplyModuleStates ( swerve . requests . SwerveRequest ): def __init__ ( self ): self . module_states : list [ SwerveModuleState ] = [] def apply ( self , parameters : swerve . SwerveControlParameters , modules_to_apply : list [ swerve . SwerveModule ] ) -> StatusCode : module_request = ( SwerveModule . ModuleRequest () . with_update_period ( parameters . update_period ) ) for ( module , state ) in zip ( modules_to_apply , self . module_states ): # apply the SwerveModuleState to the module module . apply ( module_request . with_state ( state )) } } Swerve Requests with Direct Control Swerve modules by default have some built-in control optimizations and support a limited set of control types. However, for something like the built-in SysId swerve requests, such high-level control may not be desirable. As a result, SwerveModule also has apply(ControlRequest drive, ControlRequest steer) ( Java , C++ , Python ) to directly apply control requests to the drive and steer motors. For example, the built-in SysIdSwerveSteerGains ( Java , C++ , Python ) request directly applies a CoastOut to the drive motor and a VoltageOut to the steer motor. Important We recommend against using this strategy in competition code, as it does not benefit from the built-in control optimizations. Java private final CoastOut m_driveRequest = new CoastOut (); private final VoltageOut m_steerRequest = new VoltageOut ( 0 ); @Override public StatusCode apply ( SwerveControlParameters parameters , SwerveModule , ? , ?> ... modulesToApply ) { for ( int i = 0 ; i < modulesToApply . length ; ++ i ) { /* directly apply the control requests to the drive and steer motors */ modulesToApply [ i ] . apply ( m_driveRequest , m_steerRequest . withOutput ( VoltsToApply )); } return StatusCode . OK ; } C++ ctre :: phoenix :: StatusCode Apply ( SwerveRequest :: ControlParameters const & parameters , std :: span < std :: unique_ptr < impl :: SwerveModuleImpl > const > modulesToApply ) override { for ( size_t i = 0 ; i < modulesToApply . size (); ++ i ) { /* directly apply the control requests to the drive and steer motors */ modulesToApply [ i ] -> Apply ( controls :: CoastOut {}, controls :: VoltageOut { VoltsToApply }); } return ctre :: phoenix :: StatusCode :: OK ; } Python def __init__ ( self ): # ... self . __drive_request = CoastOut () self . __steer_request = VoltageOut ( 0 ) def apply ( self , parameters : swerve . SwerveControlParameters , modules_to_apply : list [ swerve . SwerveModule ] ) -> StatusCode : for module in modules_to_apply : # directly apply the control requests to the drive and steer motors module . apply ( self . __drive_request , self . __steer_request . with_output ( self . volts_to_apply ) ) return StatusCode . OK",
- "content_preview": "Swerve Requests Controlling the drivetrain is done by calling setControl(SwerveRequest) ( Java , C++ , Python ) periodically, which takes a given SwerveRequest ( Java , C++ , Python ). There are multiple pre-defined SwerveRequest implementations that cover the majority of use cases."
+ "content": "Control Requests Phoenix 6 provides an extensive list of flexible control modes through the use of strongly-typed control requests. Note For more information about control requests in Phoenix 6, see Control Requests . Using Control Requests v5 Java // robot init, set voltage compensation to 12 V m_motor . configVoltageComSaturation ( 12 ); m_motor . enableVoltageCompensation ( true ); // main robot code, command 12 V output m_motor . set ( ControlMode . PercentOutput , 1.0 ); C++ // robot init, set voltage compensation to 12 V m_motor . ConfigVoltageComSaturation ( 12 ); m_motor . EnableVoltageCompensation ( true ); // main robot code, command 12 V output m_motor . Set ( ControlMode :: PercentOutput , 1.0 ); v6 Java // class member variable final VoltageOut m_request = new VoltageOut ( 0 ); // main robot code, command 12 V output m_motor . setControl ( m_request . withOutput ( 12.0 )); // the control request `with` methods also accept unit types m_motor . setControl ( m_request . withOutput ( Volts . of ( 12.0 ))); C++ // class member variable controls :: VoltageOut m_request { 0 _V }; // main robot code, command 12 V output m_motor . SetControl ( m_request . WithOutput ( 12 _V )); Follower Motors v5 Java // robot init, set m_follower to follow m_leader m_follower . follow ( m_leader ); // m_follower should NOT oppose m_leader m_follower . setInverted ( TalonFXInvertType . FollowMaster ); // set m_strictFollower to follow m_leader m_strictFollower . follow ( m_leader ); // set m_strictFollower to ignore m_leader invert and use its own m_strictFollower . setInverted ( TalonFXInvertType . CounterClockwise ); // main robot code, command 100% output for m_leader m_leader . set ( ControlMode . PercentOutput , 1.0 ); // - m_follower and m_strictFollower will also run at 100% output // - m_follower will follow m_leader's invert, while m_strictFollower // ignores it and uses its own // NOTE: if set(), neutralOutput(), or disable() is ever called on // the followers, they will stop following C++ // robot init, set m_follower to follow m_leader m_follower . Follow ( m_leader ); // m_follower should NOT oppose m_leader m_follower . SetInverted ( TalonFXInvertType :: FollowMaster ); // set m_strictFollower to follow m_leader m_strictFollower . Follow ( m_leader ); // set m_strictFollower to ignore m_leader invert and use its own m_strictFollower . SetInverted ( TalonFXInvertType :: CounterClockwise ); // main robot code, command 100% output for m_leader m_leader . Set ( ControlMode :: PercentOutput , 1.0 ); // - m_follower and m_strictFollower will also run at 100% output // - m_follower will follow m_leader's invert, while m_strictFollower // ignores it and uses its own // NOTE: if Set(), NeutralOutput(), or Disable() is ever called on // the followers, they will stop following v6 Java // class member variables final DutyCycleOut m_request = new DutyCycleOut ( 0 ); // robot init, set m_follower to follow m_leader // m_follower should NOT oppose leader m_follower . setControl ( new Follower ( m_leader . getDeviceID (), MotorAlignmentValue . Aligned )); // set m_strictFollower to strict-follow m_leader // strict followers ignore the leader's invert and use their own m_strictFollower . setControl ( new StrictFollower ( m_leader . getDeviceID ())); // main robot code, command 100% output for m_leader m_motor . setControl ( m_request . withOutput ( 1.0 )); // - m_follower and m_strictFollower will also run at 100% output // - m_follower will follow m_leader's invert, while m_strictFollower // ignores it and uses its own C++ // class member variables controls :: DutyCycleOut m_request { 0 }; // robot init, set m_follower to follow m_leader // m_follower should NOT oppose leader m_follower . SetControl ( controls :: Follower { m_leader . GetDeviceID (), false }); // set m_strictFollower to strict-follow m_leader // strict followers ignore the leader's invert and use their own m_strictFollower . SetControl ( controls :: StrictFollower { m_leader . GetDeviceID ()}); // main robot code, command 100% output for m_leader m_motor . SetControl ( m_request . WithOutput ( 1.0 )); // - m_follower and m_strictFollower will also run at 100% output // - m_follower will follow m_leader's invert, while m_strictFollower // ignores it and uses its own Changing Update Frequency (Control Frame Period) v5 Java // slow down the Control 3 frame (general control) to 50 Hz (20ms) m_talonFX . setControlFramePeriod ( ControlFrame . Control_3_General , 20 ); C++ // slow down the Control 3 frame (general control) to 50 Hz (20ms) m_talonFX . SetControlFramePeriod ( ControlFrame :: Control_3_General , 20 ); v6 Java // class member variables final DutyCycleOut m_request = new DutyCycleOut ( 0 ); // slow down the control request to 50 Hz m_request . UpdateFreqHz = 50 ; C++ // class member variables controls :: DutyCycleOut m_request { 0 }; // slow down the control request to 50 Hz m_request . UpdateFreqHz = 50 _Hz ; Tip UpdateFreqHz can be set to 0 Hz to synchronously one-shot the control request. In this case, users must ensure the control request is sent periodically in their robot code. Therefore, we recommend users call setControl no slower than 20 Hz (50 ms) when the control is one-shot. Control Types In Phoenix 6, voltage compensation has been replaced with the ability to directly specify the control output type . All control output types are supported in open-loop and closed-loop control requests. Open-loop Control Requests Phoenix 5 Phoenix 6 PercentOutput DutyCycleOut PercentOutput + Voltage Compensation VoltageOut Phoenix 5 does not support torque control TorqueCurrentFOC (requires Pro) Current closed-loop This has been deprecated in Phoenix 6. Users looking to control torque should use TorqueCurrentFOC (requires Pro) Users looking to limit current should use supply and stator current limits Closed-loop Control Requests Phoenix 5 Phoenix 6 Position PositionDutyCycle Velocity VelocityDutyCycle MotionMagic MotionMagicDutyCycle Closed-loop + Voltage Compensation {ClosedLoop}Voltage Closed-loop + Torque Control (not supported in Phoenix 5) {ClosedLoop}TorqueCurrentFOC (requires Pro)",
+ "content_preview": "Control Requests Phoenix 6 provides an extensive list of flexible control modes through the use of strongly-typed control requests. Note For more information about control requests in Phoenix 6, see Control Requests ."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/wpilib-integration/index.html",
- "title": "WPILib Integration",
- "section": "API Reference",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/controlling-devices.html",
+ "title": "Controlling Devices",
+ "section": "Phoenix Tuner X",
"language": "All",
- "content": "WPILib Integration Phoenix 6 API used as part of WPILib robot projects provides implementations of common WPILib interfaces that FRC teams use. Note While the Python API does support simulation, it currently does not hook into WPILib’s simulation GUI at this time. MotorController Integration Motor Safety Simulation Annotation Logging with Epilogue SysId Integration Advantages of SignalLogger over DataLog Plumbing & Running SysId Unit Testing",
- "content_preview": "WPILib Integration Phoenix 6 API used as part of WPILib robot projects provides implementations of common WPILib interfaces that FRC teams use. Note While the Python API does support simulation, it currently does not hook into WPILib’s simulation GUI at this time."
+ "content": "Controlling Devices Tuner X can be used to directly control devices outside a robot program. When combined with Plotting , it can be an excellent tool for calculating closed loop gains or isolating mechanical issues. Devices can be controlled by clicking on the red “DISABLED” button, switching it to “ENABLED”. Important FRC users must enable the robot in Driver Station while using Tuner X control. During this time, the output can be adjusted using the sliders or the text entries below it. Control modes can be changed using the dropdown below the disable/enable button. FRC Locked The “lock” icon next to the “DISABLED” button indicates that this device is FRC locked. This means the FRC Driver Station must also be enabled for the device to actuate. For more information, see FRC Lock .",
+ "content_preview": "Controlling Devices Tuner X can be used to directly control devices outside a robot program. When combined with Plotting , it can be an excellent tool for calculating closed loop gains or isolating mechanical issues."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/mechanisms/differential/differential-overview.html",
- "title": "Differential Overview",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/device-specific/index.html",
+ "title": "Device API",
"section": "API Reference",
"language": "All",
- "content": "Differential Overview Phoenix 6 has two robust differential mechanism APIs taking advantage of the motor controllers’ onboard differential controls. These APIs greatly simplify the setup and usage of common differential mechanisms, ranging from a differential wrist to a two-gearbox elevator without mechanical linkage. What is a Differential Mechanism? A differential mechanism has two axes of motion, where the position along each axis is determined by two motors in separate gearboxes: Driving both motors in a common direction causes the mechanism to move forward/reverse, up/down, etc. This is the Average axis : position is determined by the average of the two motors’ positions. Driving the motors in opposing directions causes the mechanism to twist or rotate left/right. This is the Difference axis : rotation is determined by half the difference of the two motors’ positions. As an example, a differential drivetrain has a few motors on each side of the robot. Driving both sides of the robot in the “forward” direction causes the robot to move forward. However, driving the left side “forward” and the right side “reverse” causes the robot to turn right. Another example is a two-gearbox elevator without mechanical linkage between the two sides. If the two sides of the elevator are not driven together, the elevator carriage twists, potentially breaking if the twist is too extreme. As a result, the elevator can be treated as a differential mechanism that always targets a difference of 0. In a more advanced setup, a remote sensor can be used on the Difference axis as an absolute sensor source. For example, a differential drivetrain can use the yaw of a Pigeon 2 to target an absolute heading. Differential Leader and Follower In a differential mechanism, one of the motor controllers is selected as the “differential leader”, while the other is selected as the “differential follower”. The leader is responsible for running all closed-loop calculations and applies Average + Difference to its output. The follower reports its position and velocity to the leader and applies Average - Difference to its output. The selection of the leader and follower motor controllers is only important when using a remote sensor on the difference axis. For example, consider a differential drivetrain using the yaw of a Pigeon 2. The Pigeon 2 is counter-clockwise positive, so the robot should rotate counter-clockwise (left) from a positive output on the Difference axis. This occurs when the right side drives forward (positive) and the left side drives reverse (negative). As a result, the right motor controller should be selected as the leader. Hardware Requirements All differential mechanism APIs require at least 2 Talon FX or Talon FXS motor controllers, one on each side of the mechanism. Optionally, a remote CANcoder, CANdi, or Pigeon 2 can be used on the Difference axis as an absolute sensor source. Note Both motor controllers must be of the same type. Overview of the API There are two differential mechanism APIs: DifferentialMechanism ( Java , C++ , Python ) Requires Phoenix Pro and CANivore . Full functionality, including full support for feedforwards and custom motion profiles. Difference axis supports open-loop control and position/velocity closed-loop control. Supports all control output type. SimpleDifferentialMechanism ( Java , C++ , Python ) Free and supports CAN 2.0. Limited functionality. Difference axis only supports position closed-loop control. Only supports Duty Cycle and Voltage control output types. Both types of mechanism are constructed using a DifferentialMotorConstants ( Java , C++ , Python ) object. Usage of these classes is available in the following articles in this section. Differential Mechanism Setup Using the Differential Mechanism API Tuning a Differential Mechanism",
- "content_preview": "Differential Overview Phoenix 6 has two robust differential mechanism APIs taking advantage of the motor controllers’ onboard differential controls. These APIs greatly simplify the setup and usage of common differential mechanisms, ranging from a differential wrist to a two-gearbox elevator without..."
+ "content": "Device API This section is intended to highlight any device-specific API functionality. This include features such as the TalonFX + CANcoder fusion, details on using TalonFX Control Requests , and more. TalonFX Introduction to TalonFX Control Open-Loop Control Closed-Loop Overview Basic PID and Profiling Motion Magic® Controls TalonFX Remote Sensors",
+ "content_preview": "Device API This section is intended to highlight any device-specific API functionality. This include features such as the TalonFX + CANcoder fusion, details on using TalonFX Control Requests , and more."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/api-usage/control-requests.html",
- "title": "Control Requests",
- "section": "API Reference",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/migration/new-to-phoenix.html",
+ "title": "Phoenix 6 Features",
+ "section": "General",
"language": "All",
- "content": "Control Requests Control Requests represent the output of a device. A list of control requests can be found in the API docs ( Java , C++ , Python ). Note Phoenix 6 utilizes the C++ units library and, optionally, the Java units library when applicable. Using the Java units library may increase GC overhead. Applying a Control Request Control requests can be applied by calling setControl() on the device object. setControl() returns a StatusCode ( Java , C++ , Python ) enum that represents success state. A successful request will return StatusCode.OK . Java // Command m_motor to 100% of duty cycle m_motor . setControl ( new DutyCycleOut ( 1.0 )); C++ // Command m_motor to 100% of duty cycle m_motor . SetControl ( controls :: DutyCycleOut { 1.0 }); Python # Command m_motor to 100% of duty cycle self . motor . set_control ( controls . DutyCycleOut ( 1.0 )) Modifying a Control Request Control requests are mutable, so they can be saved in a member variable and reused. For example, DutyCycleOut ( Java , C++ , Python ) has an Output member variable that can be manipulated, thus changing the output DutyCycle (proportion of supply voltage). Note Java users should reuse control requests to prevent excessive invocation of the Garbage Collector. Java final DutyCycleOut m_motorRequest = new DutyCycleOut ( 0.0 ); m_motorRequest . Output = 1.0 ; m_motor . setControl ( m_motorRequest ); C++ controls :: DutyCycleOut m_motorRequest { 0.0 }; m_motorRequest . Output = 1.0 ; m_motor . SetControl ( m_motorRequest ); Python self . motor_request = controls . DutyCycleOut ( 0.0 ) self . motor_request . output = 1.0 self . motor . set_control ( self . motor_request ) Method Chaining API Control requests also supports modification using method chaining. This can be useful for mutating multiple values of a control request. In Java, this can also be used to provide a unit type. Java // initialize torque current FOC request with 0 amps final TorqueCurrentFOC m_motorRequest = new TorqueCurrentFOC ( 0 ); // mutate request with output of 10 amps and max duty cycle 0.5 m_motor . setControl ( m_motorRequest . withOutput ( Amps . of ( 10 )). withMaxAbsDutyCycle ( 0.5 )); C++ // initialize torque current FOC request with 0 amps controls :: TorqueCurrentFOC m_motorRequest { 0 _A }; // mutate request with output of 10 amps and max duty cycle 0.5 m_motor . SetControl ( m_motorRequest . WithOutput ( 10 _A ). WithMaxAbsDutyCycle ( 0.5 )); Python # initialize torque current FOC request with 0 amps self . motor_request = controls . TorqueCurrentFOC ( 0 ) # mutate request with output of 10 amps and max duty cycle 0.5 self . motor . set_control ( self . motor_request . with_output ( 10 ) . with_max_abs_duty_cycle ( 0.5 )) Changing Update Frequency Control requests are automatically transmitted at a fixed update frequency. This update frequency can be modified by changing the UpdateFreqHz ( Java , C++ , Python ) field of the control request before sending it to the device. Java // create a duty cycle request final DutyCycleOut m_motorRequest = new DutyCycleOut ( 0 ); // reduce the update frequency to 50 Hz m_motorRequest . UpdateFreqHz = 50 ; C++ // create a duty cycle request controls :: DutyCycleOut m_motorRequest { 0 }; // reduce the update frequency to 50 Hz m_motorRequest . UpdateFreqHz = 50 ; Python # create a duty cycle request self . motor_request = controls . DutyCycleOut ( 0 ) # reduce the update frequency to 50 Hz self . motor_request . update_freq_hz = 50 Tip UpdateFreqHz can be set to 0 Hz to synchronously one-shot the control request. In this case, users must ensure the control request is sent periodically in their robot code. Therefore, we recommend users call setControl no slower than 20 Hz (50 ms) when the control is one-shot.",
- "content_preview": "Control Requests Control Requests represent the output of a device. A list of control requests can be found in the API docs ( Java , C++ , Python ). Note Phoenix 6 utilizes the C++ units library and, optionally, the Java units library when applicable."
+ "content": "Phoenix 6 Features Phoenix 6 currently offers the following features and will further expand. Phoenix 6 The following features are available for free in the Phoenix 6 API. Comprehensive API Device signal getters return a StatusSignal object, expanding the functionality of status signals. Control devices with an extensive list of flexible, strongly-typed control request objects . Canonical Units Uses the popular C++ units library and standardizes on SI units. Provides overloads using the Java units library . Signals are documented with the unit type and the minimum and maximum values. Improved Device Control New and improved control output types and closed-loop configuration. Improved Motion Magic® with jerk control and support for modifying the profile on the fly. New Motion Magic® Expo control to use an exponential profile following system dynamics, reducing both overshoot and time to target. Kalman-based algorithms to reduce latency while maintaining smooth data. Swerve API High-performance Swerve API using synchronous, latency-compensated odometry. Eliminate the boilerplate from copying swerve template code. Supported in Java, C++, and Python. Minimized GC impact in Java and Python using native C++ implementation. Improved odometry performance with CANivore and Phoenix Pro. Tuner X Swerve Project Generator gets swerve drive up and running quickly. Built-in high-fidelity simulation support. Enhanced Support for CAN FD Improved CAN FD framing further reduces any CAN bus utilization issues. Larger CAN frames allow for the addition of more advanced features. New Tuner X Self Tests and Plotting Detailed and resolute self tests to improve debugging. Plot signals at the configured signal update frequency. Combine multiple signal axes together and customize display of signal plots. Free High-Fidelity Simulation Simulation closely follows the behavior of real hardware. Write unit-tests for your robot code, and make sure the robot works before deploying. Continuous Wrap Mode Takes the shortest path for continuous mechanisms. Ideal for mechanisms such as Swerve Drive Steer. Phoenix Pro Certain Phoenix 6 features require the device or CANivore to be Pro licensed . The list of features that require licensing is available below. Field Oriented Control (FOC) ~15% increase in peak power. Increased torque output; faster acceleration and higher speeds under load. Greater efficiency; the motor draws less current for the same output power, increasing battery life. Support for direct torque control . Time Base Synchronization Using CANivore Timesync , signals from all devices are sampled and published to the CAN bus at the same time. API can synchronously wait for data from multiple devices on a CANivore to arrive. Device timestamps captured when the signal is sampled provides best possible latency compensation. Fused CANcoder Fuse a CANcoder with the motor’s internal rotor, getting absolute data all the time while using the fast internal sensor for closed looping. Real-Time High-Fidelity Signal Logger Log all status signals from every device with timestamps from CAN. Data captured as it arrives at the full update rate of the status signals. Improved sensitivity and accuracy of system identification with WPILib SysId . Automatically starts logging on a roboRIO 1 with a USB flash drive or a roboRIO 2. Support for custom user signals alongside auto-captured data. Efficient hoot logging format minimizes disk space and CPU usage. Export to multiple formats including WPILOG and MCAP. Free users can export a limited set of signals . Replay Hoot Logs Rerun your robot program in simulation using status signals and custom signals from a hoot log generated by the robot. No architecture changes necessary for automatic replay of device status signals. Robot automatically enables in the correct mode and runs through all maneuvers in the hoot log. Test code changes such as odometry improvements or failure condition detection and handling. Support for step timing and changing the speed of playback. Feature Breakdown A full comparison of features between the free Phoenix 6 API and Phoenix Pro is shown below. Feature Phoenix 6 (rio) Phoenix 6 + Pro (rio) Phoenix 6 (CANivore) Phoenix 6 + Pro (CANivore) Canonical Units x x x x Improved Bus Utilization x x x x CANcoder Always Absolute x x x x Kalman-based Velocity x x x x Synchronous Wait for Data x x x x System Timestamps x x x x Limited Signal Logger Export x x x x Explicit Control Requests x x x x Motion Magic® x x x x Motion Magic® Velocity x x x x Motion Magic® Expo x x x x Continuous Wrap Mode x x x x Simple Differential Control x x x x Improved Self-Test Snapshot x x x x Improved Tuner X Plotting x x x x CANivore Timestamps x x CAN FD x x Field Oriented Control (FOC) x x Fused CANcoder + TalonFX x x Sync CANcoder + Talon FX x x Full Signal Logger Export x x Hoot Log Replay x x Time-Synced Signal Publishing x Device Timestamps x Dynamic Motion Magic® x Full Differential Control x Swerve API + ++ ++ +++ Note + The Swerve API is freely available; however, performance improves when used on a CANivore bus and further improves when used with Pro devices. For more information, see Factors that Impact Odometry .",
+ "content_preview": "Phoenix 6 Features Phoenix 6 currently offers the following features and will further expand. Phoenix 6 The following features are available for free in the Phoenix 6 API. Comprehensive API Device signal getters return a StatusSignal object, expanding the functionality of status signals."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/mechanisms/index.html",
- "title": "Mechanisms",
- "section": "API Reference",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-swerve/creating-your-project.html",
+ "title": "Creating your Project",
+ "section": "Phoenix Tuner X",
"language": "All",
- "content": "Mechanisms This section serves to provide API usage of mechanisms supported by Phoenix 6. Swerve Documentation on the Phoenix 6 Swerve API Swerve Overview Differential Documentation on the Phoenix 6 Differential Mechanism APIs Differential Overview Generating Mechanisms Mechanisms such as swerve or an elevator can be generated using Tuner X , greatly simplifying the setup process and eliminating many sources of error. Additionally, the corvus CLI tool can be used from a terminal to generate a mechanism from a JSON specification, including a full swerve project. corvus can be downloaded from the CLI Tools download page . To view a list of available commands, run corvus either with no parameters or with --help . As an example, to generate an example Elevator subsystem for a Java robot program, run: ./corvus json elevator \"Elevator.json\" ./corvus elevator java -i \"Elevator.json\" -o \"src/main/java/frc/robot/subsystems/Elevator.java\"",
- "content_preview": "Mechanisms This section serves to provide API usage of mechanisms supported by Phoenix 6. Swerve Documentation on the Phoenix 6 Swerve API Swerve Overview Differential Documentation on the Phoenix 6 Differential Mechanism APIs Differential Overview Generating Mechanisms Mechanisms such as swerve or..."
+ "content": "Creating your Project Wheel Radius (inches) The radius can be found by measuring the width of the module wheel, then dividing that by 2. FL to FR distance (inches) This is the distance between the center of the front-left module, and the center of the front-right module. FL to BL distance (inches) This is the distance between the center of the front-left module, and the center of the back-left module. Module Type The type of swerve module, such as WCP Swerve X standard, flipped gear, or flipped belt. Users not using any of the supported modules should select Custom instead. Drive Ratio This is the gearing ratio between the output shaft of the motor and the module wheel. Swerve X users can find that information here . Steer Ratio (Custom) This is the gearing ratio between the output shaft of the steering motor and the azimuth gear. For the Custom module type, users must calculate this based on their gearing themselves, or consult their manufacturer. Import Project Import an existing Tuner X swerve project save file. New Project Create a new project based on the settings configured. Users should configure the settings applicable for their robot and click New Project once they are done. Tip Throughout the application is various tooltips, that when you hover on them, provide instructions. If you are unsure on what something means, try hovering on it! Wizard Options Once a project is open, a couple of options are exposed at the top-right. In order from left to right: Factory default all devices Open the swerve settings menu Export project Exit project",
+ "content_preview": "Creating your Project Wheel Radius (inches) The radius can be found by measuring the width of the module wheel, then dividing that by 2. FL to FR distance (inches) This is the distance between the center of the front-left module, and the center of the front-right module."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/mechanisms/swerve/swerve-overview.html",
- "title": "Swerve Overview",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/api-usage/signal-logging.html",
+ "title": "Signal Logging",
"section": "API Reference",
"language": "All",
- "content": "Swerve Overview Important Some swerve features, such as simulation support, are only available for FRC users. Phoenix 6 incorporates a high performance swerve API supported in Java, C++, and Python. This API simplifies the boilerplate necessary for swerve and maximizes performance. Tip Tuner X supports a swerve project creator that greatly simplifies the setup process and eliminates common error cases. Small API surface, easily debuggable Build robot characteristics using SwerveModuleConstants ( Java , C++ , Python ) and SwerveDrivetrainConstants ( Java , C++ , Python ). Integrates cleanly into the WPILib command-based framework using CommandSwerveDrivetrain (from our examples or Tuner X). Provide a lambda to telemetrize directly in the odometry loop using registerTelemetry(...) ( Java , C++ , Python ). Extensible and powerful control of the drivetrain via SwerveRequest ( Java , C++ , Python ). Built-in requests tuned for both autonomous and teleoperated robot-centric, field-centric and field-centric facing angle control. Supports common scenarios such as X brake (point all modules toward the center of the robot). Simulation Test your autonomous paths and pose estimation without a physical robot. Simply call updateSimState(...) ( Java , C++ , Python ) in simulationPeriodic() or on a separate thread. Performance Odometry is updated synchronously with the motor controllers. Odometry is received as fast as possible using a separate thread. Control is run inline with odometry updates. Combine with Phoenix Pro and a CANivore with timesync for improved performance. Tip Simulation boilerplate is automatically handled when generating a robot project using Tuner X. Hardware Requirements Utilizing the swerve API requires that the robot drivetrain is composed of supported Phoenix 6 devices. At a minimum, these requirements are: 4 Talon FX or Talon FXS drive motor controllers 4 Talon FX or Talon FXS steer motor controllers 1 Pigeon 2.0 4 encoders (must all be one of the following) 4 CANcoders 4 PWM absolute encoders connected to at least 2 CANdi 4 PWM absolute encoders connected to their corresponding steer Talon FXS Note All drive motor controllers must be of the same type, and all steer motor controllers must be of the same type. However, the drive and steer motor controllers can be different types from each other. For example, you can utilize 4 Talon FXS connected to a Minion for steer and 4 Kraken X60 for drive. Overview of the API Simple usage is comprised of 5 core APIs: SwerveDrivetrainConstants ( Java , C++ , Python ) This class handles characteristics of the robot that are not module specific. e.g. CAN bus, Pigeon 2 ID, whether FD is enabled or not. SwerveModuleConstantsFactory ( Java , C++ , Python ) Factory class with common constants used to instantiate SwerveModuleConstants for each module on the robot. SwerveModuleConstants ( Java , C++ , Python ) Represents the characteristics for a given module. SwerveDrivetrain ( Java , C++ , Python ) Created using SwerveDrivetrainConstants and a SwerveModuleConstants for each module, this is used to control the swerve drivetrain. SwerveRequest ( Java , C++ , Python ) Controls the drivetrain, such as driving field-centric. Usage of these classes is available in the following articles in this section. Swerve Builder API Swerve Requests Swerve Simulation Using the Swerve Drivetrain",
- "content_preview": "Swerve Overview Important Some swerve features, such as simulation support, are only available for FRC users. Phoenix 6 incorporates a high performance swerve API supported in Java, C++, and Python. This API simplifies the boilerplate necessary for swerve and maximizes performance."
+ "content": "Signal Logging Note Information on how to retrieve and convert hoot files to compatible formats can be found in Extracting Signal Logs . Phoenix 6 comes with a real-time, high-fidelity signal logger. This can be useful for any form of post analysis, including diagnosing issues after a match or using WPILib SysId . The Phoenix 6 signal logger provides the following advantages over alternatives: All status signals are captured automatically with their timestamps from CAN . Status signals are captured as they arrive at their configured update frequency. Logging is not affected by the timing of the main robot loop or Java GC, significantly improving the sensitivity and accuracy of system identification. Custom user signals can be logged alongside the automatically captured status signals on the same timebase . The highly efficient hoot file format minimizes the size of the log files and the CPU usage of the logger. The signal logging API is available through static functions in the SignalLogger ( Java , C++ , Python ) class. Signal logging is enabled by default on a roboRIO 1 with a USB flash drive or a roboRIO 2, where logging is started by any of the following (whichever occurs first): The robot is enabled. It has been at least 5 seconds since program startup (allowing for calls to setPath ), and the Driver Station is connected to the robot. Users can disable this behavior with SignalLogger.enableAutoLogging(false) ( Java , C++ , Python ). Tip Device status signals can also be viewed live in the Tuner X Plotting page . Setting Log Path The logging directory can optionally be changed using SignalLogger.setPath() ( Java , C++ , Python ). If the specified directory does not exist, SignalLogger.setPath() will return an error code. Setting the path while logging will restart the log. The below example sets the logging path to a ctre-logs folder on the first USB drive found. Java SignalLogger . setPath ( \"/media/sda1/ctre-logs/\" ); C++ SignalLogger :: SetPath ( \"/media/sda1/ctre-logs/\" ); Python SignalLogger . set_path ( \"/media/sda1/ctre-logs/\" ) Note Each CAN bus gets its own dedicated log file. All logs will be placed in a subfolder named after the date and time of the start of the program. Start/Stop Logging The signal logger can be started and stopped using the start() and stop() functions ( Java , C++ , Python ). Java SignalLogger . start (); SignalLogger . stop (); C++ SignalLogger :: Start (); SignalLogger :: Stop (); Python SignalLogger . start () SignalLogger . stop () Writing Custom Signals Users can write custom signals to the currently opened logs by utilizing the write*() functions. An example application of this is logging your swerve odometry data. The integer and floating-point write*() functions can optionally be supplied a units string to log alongside the data. Additionally, all write*() functions support an optional latency parameter that is subtracted from the current time to get the latency-adjusted timestamp of the signal. This can be useful for logging high-latency data, such as vision measurements. Tip In a WPILib robot project, custom data types can be logged using Struct and Protobuf. Additionally, Java robot projects can take advantage of Epilogue integration . Java // Log the odometry pose SignalLogger . writeStruct ( \"odometry\" , Pose2d . struct , pose ); // Log the odometry period with units of \"seconds\" SignalLogger . writeDouble ( \"odom period\" , state . OdometryPeriod , \"seconds\" ); // Log the camera pose with calculated latency SignalLogger . writeStruct ( \"camera pose\" , Pose2d . struct , camPose , Timer . getTimestamp () - camRes . getTimestampSeconds () ); C++ // Log the odometry pose SignalLogger :: WriteStruct < frc :: Pose2d > ( \"odometry\" , pose ); // Log the odometry period with units of \"seconds\" SignalLogger :: WriteDouble ( \"odom period\" , state . OdometryPeriod , \"seconds\" ); // Log the camera pose with calculated latency SignalLogger :: WriteStruct < frc :: Pose2d > ( \"camera pose\" , camPose , frc :: Timer :: GetTimestamp () - camRes . GetTimestamp () ); Python # Log the odometry pose SignalLogger . write_struct ( \"odometry\" , Pose2d , pose ) # Log the odometry period with units of \"seconds\" SignalLogger . write_double ( \"odom period\" , state . odometry_period , \"seconds\" ) # Log the camera pose with calculated latency SignalLogger . write_struct ( \"camera pose\" , Pose2d , cam_pose , Timer . getTimestamp () - cam_res . getTimestamp () ) Free Signals Any log that contains a pro-licensed device will export all signals. Otherwise, the following status signals and all custom signals can be exported for free. Click here to view free signals Common Signals VersionMajor VersionMinor VersionBugfix VersionBuild IsProLicensed SupplyVoltage Fault_UnlicensedFeatureInUse Fault_BootDuringEnable Fault_Hardware Fault_Undervoltage Talon FX SupplyCurrent StatorCurrent MotorVoltage Position Velocity DeviceEnable RobotEnable ConnectedMotor Fault_DeviceTemp Fault_ProcTemp Fault_RemoteSensorDataInvalid Fault_StaticBrakeDisabled Fault_BridgeBrownout Fault_RotorFault1 Fault_RotorFault2 Talon FXS SupplyCurrent StatorCurrent MotorVoltage Position Velocity DeviceEnable RobotEnable ConnectedMotor Fault_DeviceTemp Fault_ProcTemp Fault_RemoteSensorDataInvalid Fault_StaticBrakeDisabled Fault_BridgeBrownout Fault_HallSensorMissing Fault_DriveDisabledHallSensor Fault_MotorTempSensorMissing Fault_MotorTempSensorTooHot Fault_MotorArrangementNotSelected CANcoder Position Velocity Pigeon 2.0 Yaw AngularVelocityZWorld NoMotionEnabled NoMotionCount UpTime CANrange DistanceMeters ProximityDetected SignalStrength CANdi™ Pin1State Pin2State S1Closed S2Closed QuadPosition QuadVelocity Pwm1_Position Pwm1_Velocity Pwm2_Position Pwm2_Velocity Overcurrent Fault_5V CANdle® OutputCurrent DeviceTemp MaxSimultaneousAnimationCount Fault_Overvoltage Fault_5VTooHigh Fault_5VTooLow Fault_Thermal Fault_SoftwareFuse Fault_ShortCircuit Low Storage Space Behavior If the target drive (i.e. flash drive or roboRIO internal storage) reaches 50 MB free space, old logs will be deleted, and a warning will be printed. If the target drive reaches 5 MB of free space, logging will be stopped, and an error will be printed. Logging cannot be resumed until more disk space is made available. An example error that may occur if the free space limit is reached is shown below. [phoenix] Signal Logger: Available disk space (3 MB) below 5 MB, stopping log Converting Signal Logs Signal logs can be converted to other common file formats such as WPILOG or MCAP using the Tuner X Log Extractor . Additionally, the owlet CLI tool can be used from a terminal, including on platforms not supported by Tuner X. owlet can be downloaded from the CLI Tools download page . To view a list of available commands, run owlet either with no parameters or with --help . As an example, to convert a hoot file to WPILOG, run: ./owlet -f wpilog \"input.hoot\" \"output.wpilog\"",
+ "content_preview": "Signal Logging Note Information on how to retrieve and convert hoot files to compatible formats can be found in Extracting Signal Logs . Phoenix 6 comes with a real-time, high-fidelity signal logger."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/hardware-reference/candi/index.html",
- "title": "CANdi™",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/support.html",
+ "title": "Support",
"section": "General",
"language": "All",
- "content": "CANdi™ The CTR Electronics’ CANdi™ branded device seamlessly integrates digital signals into existing CAN bus networks, simplifying wiring and allowing multiple devices to share and utilize valuable input data. CANdi™ enables CAN interopability with sensors such as: PWM encoders, Quadrature encoders, beam break sensors, and limit switches. Store Page CAD and purchase instructions. https://store.ctr-electronics.com/products/candi Status Light Reference Important If the status lights do not exactly match any of the blink codes below, the device may be alternating between multiple blink codes (most commonly between good and bad CAN). Blink Codes Animation (Click to play) LED State Cause Possible Fix LEDs Off No Power Provide 12V to V+ and V- inputs. Blinking Alternating Red CANdi™ does not have valid CAN. Ensure good connections between CANH and CANL (Yellow and Green) & robot controller is on. Blinking Alternating Green CANdi™ has a good CAN connection. Alternate Red/Orange Damaged Hardware. Use Tuner X Self Test to confirm the LEDs and that the hardware fault is set, then contact CTRE Single LED alternates Green/Orange CANdi™ in bootloader. Field-upgrade device in Tuner X.",
- "content_preview": "CANdi™ The CTR Electronics’ CANdi™ branded device seamlessly integrates digital signals into existing CAN bus networks, simplifying wiring and allowing multiple devices to share and utilize valuable input data."
+ "content": "Support CTR Electronics prides itself on excellent customer service. Our contact information can be found on our website .",
+ "content_preview": "Support CTR Electronics prides itself on excellent customer service. Our contact information can be found on our website ."
},
{
"url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-swerve/generating-running-project.html",
@@ -524,84 +500,148 @@
"content_preview": "Generating the Project The robot program can be generated by clicking the Generate Project button. This will open a prompt asking for the team number for the generated project, followed by the programming language."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/pigeon-cal.html",
- "title": "Pigeon 2.0 Calibration",
- "section": "Pigeon 2",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/canivore/canivore-intro.html",
+ "title": "CANivore Intro",
+ "section": "CANivore",
"language": "All",
- "content": "Pigeon 2.0 Calibration It is recommended that calibration is performed once the Pigeon 2.0 has been mounted to the robot. Calibration will calculate the optimal offsets to apply to ensure that Pose, Pitch and Yaw is 0 when the robot is considered “flat”. Users can access the calibration menu by clicking on the Pigeon 2.0 in Devices and clicking Calibration in the top right. Read through the on-screen instructions and click Begin Mount Calibration .",
- "content_preview": "Pigeon 2.0 Calibration It is recommended that calibration is performed once the Pigeon 2.0 has been mounted to the robot. Calibration will calculate the optimal offsets to apply to ensure that Pose, Pitch and Yaw is 0 when the robot is considered “flat”."
+ "content": "CANivore Intro The CANivore is a multipurpose USB-to-CAN FD device. The CANivore: Adds a secondary CAN FD bus to the roboRIO CAN FD improves upon CAN with increased device bandwidth and transfer speed. Allows the control of CTR Electronics devices on Supported Devices . Important Details on licensing your CANivore is available on the licensing page. Initial Setup Setting up a CANivore for robot projects and desktop development. CANivore Setup API Usage Using the CANivore with devices in API. CANivore API Hardware-Attached Simulation Using a CANivore with hardware devices in a desktop environment. Hardware-Attached Simulation Advanced Configuration Advanced configuration options for the CANivore. Advanced Configuration Status Light Reference Blink Codes STAT Codes Animation (Click to play) LED State Cause Possible Fix LED Off No Power Provide 12V to V+/V-, or plug in USB. Red Double-Blink Device powered through V+/V-, but no USB. Plug in USB and ensure the robot controller is powered on. Red Fast-Strobe USB plugged in, but no USB communication. Ensure robot controller is fully booted and enumerated USB. Then consider replacing the USB cable. Orange Double-Blink Good USB connection, CAN streaming disabled. V+/V- is NOT powered. Ensure a robot program using Phoenix is running. Orange Fast-Strobe Good USB connection, CAN streaming disabled. V+/V- is powered. Ensure a robot program using Phoenix is running. Green Double-Blink Good USB connection, CAN streaming enabled. V+/V- is NOT powered. Green Fast-Strobe Good USB connection, CAN streaming enabled. V+/V- is powered. Alternate Red/Orange Damaged Hardware. Contact CTRE Support. Alternate Orange/Green CANivore in bootloader. Field-upgrade device in Tuner X. Wi-Fi Codes LED Off Wi-Fi is disabled. Enable the ESP32. Green Blink Wi-Fi is enabled, or ESP32 custom application is allowed to use Wi-Fi. BT Codes LED Off Bluetooth is disabled. Enable the ESP32. Green Blink Bluetooth is enabled, or ESP32 custom application is allowed to use Bluetooth. CAN Codes Solid Red Voltage too low for CAN bus. Ensure device is receiving 5 V over USB and optionally 12 V over V+/V-. Red Double-Blink No CAN communication. CAN termination is disabled. Ensure good connections on CANH and CANL (Yellow and Green), all connected devices support CAN FD, and the bus is properly terminated with two 120-Ω resistors, one on each end. Red Fast-Strobe No CAN communication. CAN termination is enabled. Ensure good connections on CANH and CANL (Yellow and Green), all connected devices support CAN FD, and the bus is properly terminated with a 120-Ω resistor on the other end. Orange Double-Blink Reserved for CAN 2.0B legacy mode. CAN termination is disabled. Orange Fast-Strobe Reserved for CAN 2.0B legacy mode. CAN termination is enabled. Green Double-Blink CAN FD is active. CAN termination is disabled. Green Fast-Strobe CAN FD is active. CAN termination is enabled.",
+ "content_preview": "CANivore Intro The CANivore is a multipurpose USB-to-CAN FD device. The CANivore: Adds a secondary CAN FD bus to the roboRIO CAN FD improves upon CAN with increased device bandwidth and transfer speed. Allows the control of CTR Electronics devices on Supported Devices ."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/migration/migration-guide/api-structure-guide.html",
- "title": "API Structure",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/canivore/canivore-setup.html",
+ "title": "CANivore Setup",
+ "section": "CANivore",
+ "language": "All",
+ "content": "CANivore Setup Installing for roboRIO Note Phoenix Tuner X requires a 2023 roboRIO image or newer to configure the CANivore. No additional steps are required. The roboRIO comes with the canivore-usb kernel module pre-installed. Installing for Linux (non-FRC) See CANivore Installation for information on setting up your Linux system for use with a CANivore. Viewing Attached CANivores Attached CANivores can be viewed in Phoenix Tuner X by selecting the CANivores page from the left-hand sidebar. You can specify the target system in the Target IP or Team # text box. Note The Phoenix Diagnostic Server must be running on the target system to use the CANivores page. Tip If you are connecting to CANivores on your local Windows machine, you can enable the CANivore USB toggle and set the target IP to localhost . This runs a diagnostic server within Tuner X so you do not need to run a robot project to communicate with CANivores. Field Upgrading CANivores A CANivore can be field updated using Phoenix Tuner X . Click or tap on the listed CANivore card to open the device details page. The CANivore can then be field upgraded via the dropdown or by manually selected a file: Phoenix Tuner X also allows the user to batch field upgrade CANivores from the list of CANivores in the same manner as batch field upgrading devices . Renaming CANivores CANivores can be given custom names for use within a robot program. This can be configured through Phoenix Tuner X on the specified device card.",
+ "content_preview": "CANivore Setup Installing for roboRIO Note Phoenix Tuner X requires a 2023 roboRIO image or newer to configure the CANivore. No additional steps are required. The roboRIO comes with the canivore-usb kernel module pre-installed."
+ },
+ {
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/troubleshooting/running-diagnostics.html",
+ "title": "Running the Diagnostic Server",
"section": "General",
"language": "All",
- "content": "API Structure Phoenix 6 uses a separate, simpler set of namespaces and packages from Phoenix 5. Note For more information about the structure of Phoenix 6, see API Overview . v5 Java // Phoenix 5 is in the com.ctre.phoenix.* packages import com.ctre.phoenix.motorcontrol.can.WPI_TalonFX ; import com.ctre.phoenix.motorcontrol.TalonFXConfiguration ; import com.ctre.phoenix.motorcontrol.TalonFXControlMode ; import com.ctre.phoenix.motorcontrol.TalonFXInvertType ; import com.ctre.phoenix.motorcontrol.TalonFXSimCollection ; import com.ctre.phoenix.sensors.CANCoderConfiguration ; import com.ctre.phoenix.sensors.WPI_CANCoder ; // WPI_* for WPILib integration final WPI_TalonFX m_talonFX = new WPI_TalonFX ( 0 ); final WPI_CANCoder m_cancoder = new WPI_CANCoder ( 0 ); final TalonFXSimCollection m_talonFXSim = m_talonFX . getSimCollection (); final TalonFXConfiguration m_talonConfig = new TalonFXConfiguration (); final CANCoderConfiguration m_cancoderConfig = new CANCoderConfiguration (); TalonFXInvertType m_talonFXInverted = TalonFXInvertType . CounterClockwise ; m_talonFX . set ( TalonFXControlMode . PercentOutput , 0 ); C++ // Phoenix 5 is in the ctre/phoenix headers #include \"ctre/phoenix/motorcontrol/can/WPI_TalonFX.h\" #include \"ctre/phoenix/sensors/WPI_CANCoder.h\" // Phoenix 5 uses the ctre::phoenix namespace using namespace ctre :: phoenix ; // WPI_* for WPILib integration motorcontrol :: can :: WPI_TalonFX m_talonFX { 0 }; sensors :: WPI_CANCoder m_cancoder { 0 }; motorcontrol :: TalonFXSimCollection & m_talonFXSim { m_talonFX . GetSimCollection ()}; motorcontrol :: TalonFXConfiguration m_talonConfig {}; sensors :: CANCoderConfiguration m_cancoderConfig {}; motorcontrol :: TalonFXInvertType m_talonFXInverted { motorcontrol :: TalonFXInvertType :: CounterClockwise }; m_talonFX . Set ( motorcontrol :: TalonFXControlMode :: PercentOutput , 0 ); v6 Java // Phoenix 6 is in the com.ctre.phoenix6.* packages import com.ctre.phoenix6.configs.CANcoderConfiguration ; import com.ctre.phoenix6.configs.TalonFXConfiguration ; import com.ctre.phoenix6.controls.DutyCycleOut ; import com.ctre.phoenix6.hardware.CANcoder ; import com.ctre.phoenix6.hardware.TalonFX ; import com.ctre.phoenix6.signals.InvertedValue ; import com.ctre.phoenix6.sim.TalonFXSimState ; // All hardware classes already have WPILib integration final TalonFX m_talonFX = new TalonFX ( 0 ); final CANcoder m_cancoder = new CANcoder ( 0 ); final TalonFXSimState m_talonFXSim = m_talonFX . getSimState (); final DutyCycleOut m_talonFXOut = new DutyCycleOut ( 0 ); final TalonFXConfiguration m_talonFXConfig = new TalonFXConfiguration (); final CANcoderConfiguration m_cancoderConfig = new CANcoderConfiguration (); InvertedValue m_talonFXInverted = InvertedValue . CounterClockwise_Positive ; m_talonFX . setControl ( m_talonFXOut ); C++ // Phoenix 6 is in the ctre/phoenix6 headers #include \"ctre/phoenix6/CANcoder.hpp\" #include \"ctre/phoenix6/TalonFX.hpp\" // Phoenix 6 uses the ctre::phoenix6 namespace using namespace ctre :: phoenix6 ; // now types are organized cleanly by namespace // All hardware classes already have WPILib integration hardware :: TalonFX m_talonFX { 0 }; hardware :: CANcoder m_cancoder { 0 }; sim :: TalonFXSimState & m_talonFXSim { m_talonFX . GetSimState ()}; controls :: DutyCycleOut m_talonFXOut { 0 }; configs :: TalonFXConfiguration m_talonFXConfig {}; configs :: CANcoderConfiguration m_cancoderConfig {}; signals :: InvertedValue m_talonFXInverted { signals :: InvertedValue :: CounterClockwise_Positive }; m_talonFX . SetControl ( m_talonFXOut );",
- "content_preview": "API Structure Phoenix 6 uses a separate, simpler set of namespaces and packages from Phoenix 5. Note For more information about the structure of Phoenix 6, see API Overview ."
+ "content": "Running the Diagnostic Server Phoenix Tuner utilizes an on-device HTTP server called Phoenix Diagnostic Server to communicate with the device. The user can run the diagnostic server through one of two ways. 1: Deploying a Robot Program Phoenix Diagnostics will automatically run assuming you have instantiated a CTR Electronics device in your robot program. This can be as simple as having a motor declared somewhere in your program. Note The ID of the device does not need to be valid to run diagnostics. Java private TalonFX m_motor = new TalonFX ( 0 ); C++ hardware :: TalonFX m_talonFX { 0 }; When the program runs, it will print text to the console similar to the below Note WPILib users will see this text in the Driver Station or RioLog [phoenix] Starting Standalone Diagnostics Server (23.1.0-Jun 2 2023,23:17:09) [phoenix-diagnostics] Server 2023.1.0 (Jun 2 2023, 23:17:56) running on port: 1250 2: Running Temporary Diagnostic Server Alternatively, users can run a temporary diagnostic server in Tuner X. The temporary diagnostic server will only run until the next reboot of the target system. Note Temporary diagnostic server can be deployed on supported non-FRC platforms . However, devices will only enumerate if the CAN bus interface is can0 .",
+ "content_preview": "Running the Diagnostic Server Phoenix Tuner utilizes an on-device HTTP server called Phoenix Diagnostic Server to communicate with the device. The user can run the diagnostic server through one of two ways."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/mechanisms/differential/differential-tuning.html",
- "title": "Tuning a Differential Mechanism",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/mechanisms/swerve/using-swerve-api.html",
+ "title": "Using the Swerve Drivetrain",
"section": "API Reference",
"language": "All",
- "content": "Tuning a Differential Mechanism Important This article assumes that you are familiar with tuning PID on a single-axis mechanism. For more information, see Closed-Loop Overview . Because a differential mechanism has two axes of motion , it also needs separate sets of PID gains for the Average axis (typically Slot 0) and the Difference axis (typically Slot 1). These gains are applied to the differential leader . Warning Tuner X only supports the SimpleDifferentialMechanism control requests. For full functionality while tuning, use the DifferentialMechanism API for control and Tuner X for plotting and configs. See Using the Differential Mechanism API for more information. Tuning a differential mechanism typically involves three steps: Step 1: Weak kP on the Difference Axis When tuning closed-loop control on a differential mechanism, it is important that the axis not being tuned is roughly held in place. Failing to do so can result in damage to the mechanism at stronger gains. To accomplish this, a relatively weak kP can be applied to the Difference axis prior to tuning the average axis. In Tuner X, select the differential leader motor controller. The DifferentialPosition signal can be plotted to determine a reasonable setpoint. From there, apply an open-loop request to the average axis and a position closed-loop request to the Difference axis (for SimpleDifferentialMechanism , use the Differential{OutputType} requests). Adjust the kP on the Difference axis until there is a reasonable amount of resistance to motion without oscillation. Important Do not try to optimize the feedforward and PID gains on the Difference axis at this point. Step 2: Feedforwards and PID on the Average Axis With the Difference axis now roughly held in place, the focus shifts to tuning the feedforwards and PID on the Average axis. The tuning process on the average axis is roughly the same as with a single-axis mechanism (such as a single gearbox elevator) with two changes: Use the DifferentialAveragePosition and DifferentialAverageVelocity signals instead of the Position and Velocity signals. Use the differential mechanism or control requests (such as DifferentialMotionMagicVoltage ) when tuning, keeping the Difference axis held in place. As is the case with a single-axis mechanism, many gains on the Average axis scale with the RotorToSensorRatio and SensorToMechanismRatio . For example, a Kraken X60 differential mechanism with 3:1 gearing on the average axis would have a kV of around 0.36 V/rps ( \\(K_{v\\_avg} = 3.0 * 0.12\\) V/rps) on the Average axis. Step 3: Feedforwards and PID on the Difference Axis Finally, the feedforwards and PID gains on the Difference axis can be fully tuned: Use the DifferentialDifferencePosition and DifferentialDifferenceVelocity signals instead of the Position and Velocity signals. Use the Differential closed-loop signals (such as DifferentialClosedLoopReference ) instead of the regular closed-loop signals. Use the differential mechanism or control requests (such as DifferentialMotionMagicVoltage ) when tuning, keeping the Average axis held in place. Note For some mechanisms like a two-gearbox elevator, these gains may be left on the weaker side, as the focus is on the Average axis. At a SensorToDifferentialRatio of 1.0, the scale of the gains on the Difference axis will be similar to those on the Average axis. For example, a Kraken X60 differential mechanism with 1:1 gearing on all axes will have a kV of around 0.12 V/rps on both the Average axis and the Differential axis. However, note that kG is often 0 on the Difference axis. Otherwise, the gains on the Difference axis scale with the SensorToDifferentialRatio relative to the Average axis . For example, a 3:1 gearing on the Average axis and an additional 2:1 gearing on the Difference axis would result in a kV of around 0.72 V/rps ( \\(K_{v\\_diff} = 2.0 * K_{v\\_avg}\\) ) on the Difference axis.",
- "content_preview": "Tuning a Differential Mechanism Important This article assumes that you are familiar with tuning PID on a single-axis mechanism. For more information, see Closed-Loop Overview ."
+ "content": "Using the Swerve Drivetrain In addition to control and simulation , the SwerveDrivetrain ( Java , C++ , Python ) has many other APIs to manage the built-in pose estimator, telemetry, and more. Changing Neutral Mode The neutral mode of the drive motors can be reconfigured at runtime using configNeutralMode ( Java , C++ , Python ). Tip The neutral mode can be applied on construction by modifying the DriveMotorInitialConfigs and SteerMotorInitialConfigs provided to the SwerveModuleConstants . Java // The drivetrain was constructed in brake mode, switch to coast drivetrain . configNeutralMode ( NeutralModeValue . Coast ); C++ // The drivetrain was constructed in brake mode, switch to coast drivetrain . ConfigNeutralMode ( signals :: NeutralModeValue :: Coast ); Python # The drivetrain was constructed in brake mode, switch to coast self . drivetrain . config_neutral_mode ( signals . NeutralModeValue . COAST ) Using Field-Centric Control In field-centric control, the robot is driven using velocities relative to the field. This makes it so the forward direction is constant (typically away from the driver) regardless of the robot orientation. There are two common field coordinate systems: Blue Alliance Perspective (which are absolute coordinates) and Operator Perspective (which are alliance-relative coordinates). For field-centric control to behave correctly, the drivetrain needs to know which directions the driver and the robot are facing. Setting the Operator Perspective The OperatorPerspective is typically used during teleop control, ensuring that forward (+X) is always away from the driver. The forward direction for OperatorPerspective can be set using setOperatorPerspectiveForward ( Java , C++ , Python ). This tells the drivetrain which direction the driver is facing. Looking at the Blue Alliance Perspective coordinates, facing away from the blue alliance is a heading of 0 degrees . On the other hand, the red alliance is flipped, so facing away from the red alliance is a heading of 180 degrees . Important When using CommandSwerveDrivetrain from our examples or Tuner X, this is already handled by the subsystem. Java /* Blue alliance sees forward as 0 degrees (toward red alliance wall) */ private static final Rotation2d kBlueAlliancePerspectiveRotation = Rotation2d . kZero ; /* Red alliance sees forward as 180 degrees (toward blue alliance wall) */ private static final Rotation2d kRedAlliancePerspectiveRotation = Rotation2d . k180deg ; /* Keep track if we've ever applied the operator perspective before or not */ private boolean m_hasAppliedOperatorPerspective = false ; @Override public void periodic () { // Periodically try to apply the operator perspective // if we haven't yet or if we're currently disabled. if ( ! m_hasAppliedOperatorPerspective || DriverStation . isDisabled ()) { DriverStation . getAlliance (). ifPresent ( allianceColor -> { setOperatorPerspectiveForward ( allianceColor == Alliance . Red ? kRedAlliancePerspectiveRotation : kBlueAlliancePerspectiveRotation ); m_hasAppliedOperatorPerspective = true ; }); } } C++ /* Blue alliance sees forward as 0 degrees (toward red alliance wall) */ static constexpr frc :: Rotation2d kBlueAlliancePerspectiveRotation { 0 _deg }; /* Red alliance sees forward as 180 degrees (toward blue alliance wall) */ static constexpr frc :: Rotation2d kRedAlliancePerspectiveRotation { 180 _deg }; /* Keep track if we've ever applied the operator perspective before or not */ bool m_hasAppliedOperatorPerspective = false ; void Periodic () override { // Periodically try to apply the operator perspective // if we haven't yet or if we're currently disabled. if ( ! m_hasAppliedOperatorPerspective || frc :: DriverStation :: IsDisabled ()) { auto const allianceColor = frc :: DriverStation :: GetAlliance (); if ( allianceColor ) { SetOperatorPerspectiveForward ( * allianceColor == frc :: DriverStation :: Alliance :: kRed ? kRedAlliancePerspectiveRotation : kBlueAlliancePerspectiveRotation ); m_hasAppliedOperatorPerspective = true ; } } } Python _BLUE_ALLIANCE_PERSPECTIVE_ROTATION = Rotation2d . fromDegrees ( 0 ) \"\"\"Blue alliance sees forward as 0 degrees (toward red alliance wall)\"\"\" _RED_ALLIANCE_PERSPECTIVE_ROTATION = Rotation2d . fromDegrees ( 180 ) \"\"\"Red alliance sees forward as 180 degrees (toward blue alliance wall)\"\"\" def __init__ ( self , ... ): # ... self . _has_applied_operator_perspective = False \"\"\"Keep track if we've ever applied the operator perspective before or not\"\"\" def periodic ( self ): # Periodically try to apply the operator perspective # if we haven't yet or if we're currently disabled. if not self . _has_applied_operator_perspective or DriverStation . isDisabled (): alliance_color = DriverStation . getAlliance () if alliance_color is not None : self . set_operator_perspective_forward ( self . _RED_ALLIANCE_PERSPECTIVE_ROTATION if alliance_color == DriverStation . Alliance . kRed else self . _BLUE_ALLIANCE_PERSPECTIVE_ROTATION ) self . _has_applied_operator_perspective = True Setting the Robot Heading After setting the operator perspective, the drivetrain also needs to know which direction the robot is facing. Note Many path planning libraries automatically reset the full pose of the robot, including heading, at the start of the path. Current Direction is Forward If the robot is currently facing the driver’s forward direction, call seedFieldCentric() ( Java , C++ , Python ) to reset the heading. Tip The Tuner X generated swerve project and our examples bind seedFieldCentric() to the left bumper. Java // Reset the field-centric heading on left bumper press. joystick . leftBumper (). onTrue ( drivetrain . runOnce ( drivetrain :: seedFieldCentric )); C++ // reset the field-centric heading on left bumper press joystick . LeftBumper (). OnTrue ( drivetrain . RunOnce ([ this ] { drivetrain . SeedFieldCentric (); })); Python # reset the field-centric heading on left bumper press self . _joystick . leftBumper () . onTrue ( self . drivetrain . runOnce ( self . drivetrain . seed_field_centric ) ) Angle Relative to Forward If the robot is facing some other angle relative to the driver’s forward direction, call seedFieldCentric(Rotation2d) ( Java , C++ , Python ) with the relative angle. For example, if the robot is facing left, then pass in an angle of +90 degrees (counter-clockwise). Tip The Tuner X generated swerve project calls seedFieldCentric(Rotation2d) at the start of the default autonomous command. Java // Reset the field-centric heading so the robot is facing left (90 deg CCW) drivetrain . seedFieldCentric ( Rotation2d . kCCW_90deg ); C++ // Reset the field-centric heading so the robot is facing left (+90 deg) drivetrain . SeedFieldCentric ( frc :: Rotation2d { 90 _deg }); Python # Reset the field-centric heading so the robot is facing left (+90 deg) self . drivetrain . seed_field_centric ( Rotation2d . fromDegrees ( 90 )) Blue Alliance Heading or Pose When using a path planning library such as PathPlanner or Choreo , the paths often operate using the BlueAlliancePerspective and reset the robot’s pose at the start of the path. Vision libraries similarly often operate using a BlueAlliancePerspective heading or pose. The robot’s heading can be reset to a BlueAlliancePerspective heading using resetRotation(Rotation2d) ( Java , C++ , Python ), and the pose can be reset using resetPose(Pose2d) ( Java , C++ , Python ). Tip PathPlanner and Choreo can call resetPose automatically at the start of the autonomous path. Java // Reset the robot's heading to 0 deg (away from the Blue Alliance wall) drivetrain . resetRotation ( Rotation2d . kZero ); // Reset the robot's pose to the initial pose of the autonomous path drivetrain . resetPose ( initialPose ); C++ // Reset the robot's heading to 0 deg (away from the Blue Alliance wall) drivetrain . ResetRotation ( frc :: Rotation2d {}); // Reset the robot's pose to the initial pose of the autonomous path drivetrain . ResetPose ( initialPose ); Python # Reset the robot's heading to 0 deg (away from the Blue Alliance wall) drivetrain . reset_rotation ( Rotation2d ()) # Reset the robot's pose to the initial pose of the autonomous path self . drivetrain . reset_pose ( initial_pose ) Odometry and State The SwerveDrivetrain has a built-in pose estimator running on a separate odometry thread (250 Hz on CANivore, 100 Hz on roboRIO). This significantly improves the accuracy and consistency of odometry and robot pose estimation. Information about the robot’s state can be retrieved using getState() ( Java , C++ , Python ), and a thread-safe copy can be retrieved using getStateCopy() ( Java , Python ). This returns a SwerveDriveState ( Java , C++ , Python ) instance that includes information such as the pose estimate, module states, and chassis speeds. Java var state = drivetrain . getState (); // pull out the pose estimate and chassis speeds Pose2d pose = state . Pose ; ChassisSpeeds speeds = state . Speeds ; C++ auto state = drivetrain . GetState (); // pull out the pose estimate and chassis speeds frc :: Pose2d pose = state . Pose ; frc :: ChassisSpeeds speeds = state . Speeds ; Python state = self . drivetrain . get_state () # pull out the pose estimate and chassis speeds pose = state . pose speeds = state . speeds Drivetrain Telemetry The state of the drivetrain can also be telemeterized inline with odometry updates, ensuring that all information is captured in logs. A telemetry function that accepts the latest SwerveDriveState as a parameter can be registered using registerTelemetry ( Java , C++ , Python ). Tip The Tuner X generated swerve project and our examples have a Telemetry class that is already registered with the drivetrain. Java public Robot () { drivetrain . registerTelemetry ( this :: telemeterize ); } /** Accept the swerve drive state and telemeterize it to SignalLogger. */ public void telemeterize ( SwerveDriveState state ) { SignalLogger . writeStruct ( \"DriveState/Pose\" , Pose2d . struct , state . Pose ); SignalLogger . writeStruct ( \"DriveState/Speeds\" , ChassisSpeeds . struct , state . Speeds ); SignalLogger . writeStructArray ( \"DriveState/ModuleStates\" , SwerveModuleState . struct , state . ModuleStates ); SignalLogger . writeStructArray ( \"DriveState/ModuleTargets\" , SwerveModuleState . struct , state . ModuleTargets ); SignalLogger . writeStructArray ( \"DriveState/ModulePositions\" , SwerveModulePosition . struct , state . ModulePositions ); SignalLogger . writeDouble ( \"DriveState/OdometryPeriod\" , state . OdometryPeriod , \"seconds\" ); SignalLogger . writeInteger ( \"DriveState/FailedDaqs\" , state . FailedDaqs ); } C++ Robot () { drivetrain . RegisterTelemetry ( [ this ]( auto const & state ) { Telemeterize ( state ); } ); } /** Accept the swerve drive state and telemeterize it to SignalLogger. */ void Telemeterize ( subsystems :: TunerSwerveDrivetrain :: SwerveDriveState const & state ) { SignalLogger :: WriteStruct ( \"DriveState/Pose\" , state . Pose ); SignalLogger :: WriteStruct ( \"DriveState/Speeds\" , state . Speeds ); SignalLogger :: WriteStructArray < frc :: SwerveModuleState > ( \"DriveState/ModuleStates\" , state . ModuleStates ); SignalLogger :: WriteStructArray < frc :: SwerveModuleState > ( \"DriveState/ModuleTargets\" , state . ModuleTargets ); SignalLogger :: WriteStructArray < frc :: SwerveModulePosition > ( \"DriveState/ModulePositions\" , state . ModulePositions ); SignalLogger :: WriteValue ( \"DriveState/OdometryPeriod\" , state . OdometryPeriod ); SignalLogger :: WriteInteger ( \"DriveState/FailedDaqs\" , state . FailedDaqs ); } Python def __init__ ( self ): # ... self . drivetrain . register_telemetry ( self . telemeterize ) def telemeterize ( self , state : swerve . SwerveDrivetrain . SwerveDriveState ): \"\"\" Accept the swerve drive state and telemeterize it to SignalLogger. \"\"\" SignalLogger . write_struct ( \"DriveState/Pose\" , Pose2d , state . pose ) SignalLogger . write_struct ( \"DriveState/Speeds\" , ChassisSpeeds , state . speeds ) SignalLogger . write_struct_array ( \"DriveState/ModuleStates\" , SwerveModuleState , state . module_states ) SignalLogger . write_struct_array ( \"DriveState/ModuleTargets\" , SwerveModuleState , state . module_targets ) SignalLogger . write_struct_array ( \"DriveState/ModulePositions\" , SwerveModulePosition , state . module_positions ) SignalLogger . write_double ( \"DriveState/OdometryPeriod\" , state . odometry_period , \"seconds\" ) SignalLogger . write_integer ( \"DriveState/FailedDaqs\" , state . failed_daqs )",
+ "content_preview": "Using the Swerve Drivetrain In addition to control and simulation , the SwerveDrivetrain ( Java , C++ , Python ) has many other APIs to manage the built-in pose estimator, telemetry, and more."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-history.html",
- "title": "Tuner History",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/wpilib-integration/index.html",
+ "title": "WPILib Integration",
+ "section": "API Reference",
+ "language": "All",
+ "content": "WPILib Integration Phoenix 6 API used as part of WPILib robot projects provides implementations of common WPILib interfaces that FRC teams use. Note While the Python API does support simulation, it currently does not hook into WPILib’s simulation GUI at this time. MotorController Integration Motor Safety Simulation Annotation Logging with Epilogue SysId Integration Advantages of SignalLogger over DataLog Plumbing & Running SysId Unit Testing",
+ "content_preview": "WPILib Integration Phoenix 6 API used as part of WPILib robot projects provides implementations of common WPILib interfaces that FRC teams use. Note While the Python API does support simulation, it currently does not hook into WPILib’s simulation GUI at this time."
+ },
+ {
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/api-usage/actuator-limits.html",
+ "title": "Actuator Limits",
+ "section": "API Reference",
+ "language": "All",
+ "content": "Actuator Limits CTR Electronics actuators, such as the TalonFX, support various kinds of hardware and software limits. Note The TalonFX + Kraken X60 does not support hardware limit switches. Instead, control request limit overrides can be used, or a CANcoder/CANdi™/CANrange can be used as a remote limit switch . Documentation on wiring limit switches can be found here . Retrieving Limit Switch State The state of the forward or reverse limit switch can be retrieved from the API via getForwardLimit() and getReverseLimit() . Additionally, the state of the forward or reverse soft limit can be retrieved from the API via getFault_ForwardSoftLimit() and getFault_ReverseSoftLimit() . Java var forwardLimit = m_motor . getForwardLimit (); if ( forwardLimit . getValue () == ForwardLimitValue . ClosedToGround ) { // do action when forward limit is closed } var forwardSoftLimit = m_motor . getFault_ForwardSoftLimit (); if ( forwardSoftLimit . getValue ()) { // do action when forward soft limit is reached } C++ auto & forwardLimit = m_motor . GetForwardLimit (); if ( forwardLimit . GetValue () == signals :: ForwardLimitValue :: ClosedToGround ) { // do action when forward limit is closed } auto & forwardSoftLimit = m_motor . GetFault_ForwardSoftLimit (); if ( forwardSoftLimit . GetValue ()) { // do action when forward soft limit is reached } Python forward_limit = self . motor . get_forward_limit () if forward_limit . value is signals . ForwardLimitValue . CLOSED_TO_GROUND : # do action when forward limit is closed forward_soft_limit = self . motor . get_fault_forward_soft_limit () if forward_soft_limit . value : # do action when forward soft limit is reached Control Request Limits Many control requests support overriding the limit switch values using LimitForwardMotion and LimitReverseMotion parameters ( Java , C++ , Python ). These allow users to use other limit switch sensors connected to the robot controller. Java final DigitalInput m_forwardLimit = new DigitalInput ( 0 ); final DigitalInput m_reverseLimit = new DigitalInput ( 1 ); final DutyCycleOut m_dutyCycle = new DutyCycleOut ( 0.0 ); m_motor . setControl ( m_dutyCycle . withOutput ( 0.5 ) . withLimitForwardMotion ( m_forwardLimit . get ()) . withLimitReverseMotion ( m_reverseLimit . get ()) ); C++ frc :: DigitalInput m_forwardLimit { 0 }; frc :: DigitalInput m_reverseLimit { 1 }; controls :: DutyCycleOut m_dutyCycle { 0.0 }; m_motor . SetControl ( m_dutyCycle . WithOutput ( 0.5 ) . WithLimitForwardMotion ( m_forwardLimit . Get ()) . WithLimitReverseMotion ( m_reverseLimit . Get ()) ); Python self . forward_limit = wpilib . DigitalInput ( 0 ) self . reverse_limit = wpilib . DigitalInput ( 1 ) self . duty_cycle = controls . DutyCycleOut ( 0.0 ) self . motor . set_control ( self . duty_cycle . with_output ( 0.5 ) . with_limit_forward_motion ( self . forward_limit . get ()) . with_limit_reverse_motion ( self . reverse_limit . get ()) ) Remote Limit Switches Supported devices (TalonFX, CANifier, CANcoder, CANdi™, CANrange) can be utilized as a remote limit switch, disabling actuator outputs when triggers. When utilizing a CANcoder as a remote limit, the limit will trigger when the magnet strength changes from BAD (red) to ADEQUATE (orange) or GOOD (green). When utilizing a CANrange as a remote limit, the limit will trigger when the proximity detect is tripped following the ProximityParamsConfigs ( Java , C++ , Python ). When utilizing a CANdi™ as a remote limit, the limit will trigger when the S1Closed or S2Closed signal is true. The remote limit switch can be selected using the LimitSource and LimitRemoteSensorID configs. Java var limitConfigs = new HardwareLimitSwitchConfigs (); limitConfigs . ForwardLimitSource = ForwardLimitSourceValue . RemoteCANcoder ; limitConfigs . ForwardLimitRemoteSensorID = m_cancoder . getDeviceID (); m_motor . getConfigurator (). apply ( limitConfigs ); C++ configs :: HardwareLimitSwitchConfigs limitConfigs {}; limitConfigs . ForwardLimitSource = signals :: ForwardLimitSourceValue :: RemoteCANcoder ; limitConfigs . ForwardLimitRemoteSensorID = m_cancoder . GetDeviceID (); m_motor . GetConfigurator (). Apply ( limitConfigs ); Python limit_configs = configs . HardwareLimitSwitchConfigs () limit_configs . forward_limit_source = signals . ForwardLimitSourceValue . REMOTE_CANCODER limit_configs . forward_limit_remote_sensor_id = self . cancoder . device_id self . motor . configurator . apply ( limit_configs )",
+ "content_preview": "Actuator Limits CTR Electronics actuators, such as the TalonFX, support various kinds of hardware and software limits. Note The TalonFX + Kraken X60 does not support hardware limit switches."
+ },
+ {
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-elev/setup.html",
+ "title": "Setup",
"section": "Phoenix Tuner X",
"language": "All",
- "content": "Tuner History Tuner history provides insight on past connected devices and robot networks. Device History allows teams to view previously connected devices and license them without being directly connected to them. Users may wish to license previously connected devices due to a lack of internet connection while being connected to them. Network History indicates a list of past connected robot networks. Users can access a list of past devices connected to Tuner X and license them via the Device History page. This is accessible from the left-hand sidebar. This list is not automatically refreshed, but users can refresh it by pressing the refresh icon in the top-right of the page. Licensing from Device History Users can activate a license for a disconnected device by clicking on the device in the Grid. Then, select the “PRO” icon at the bottom right of the device card. From there, the user can activate a license for the device like normal. Once the device license has been activated, the user still needs to connect Tuner X to the robot to transfer the activated license to the device. The “PRO” icon may be replaced with a greyed “LIC” icon in the following situations: The device is on Phoenix 5 firmware and actively connected to Tuner X The device is not a Phoenix 6 compatible device Users who license an eligible Phoenix 6 device running Phoenix 5 firmware must update the device firmware to v6 compatible firmware to utilize licensed features.",
- "content_preview": "Tuner History Tuner history provides insight on past connected devices and robot networks. Device History allows teams to view previously connected devices and license them without being directly connected to them."
+ "content": "Setup Prerequisites The elevator generator and generated project make a few assumption. To determine if the elevator generator is the best fit for your mechanism, consult the following checklist. The elevator is a single or two gearbox mechanism. All gearboxes on the elevator consist of the same gearing. All gearboxes on the elevator have the same number of motors. If using two gearboxes, ensure that the orientation of the gearbox’s are identical. All motors on the elevator are identical. Initial Configuration Setup is done with two steps. 1. Choosing your Gearbox The supported gearbox types are Single and Dual . 2. Configuration A typical elevator is composed of a number of motors, driving a gearbox which spins a spool that drives the elevator up or down. The generated Elevator subsystem will automatically handle conversion between raw mechanism rotations to linear units (inches, meters, feet, etc). To handle this scenario, certain constants cannot be automatically determined. Users will need to input the Drum Radius (in) , Gear Ratio , and Num Motors . Once this has been done, a list of motor controllers will be populated per gearbox.",
+ "content_preview": "Setup Prerequisites The elevator generator and generated project make a few assumption. To determine if the elevator generator is the best fit for your mechanism, consult the following checklist. The elevator is a single or two gearbox mechanism."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/migration/canbus-utilization.html",
- "title": "CAN Bus Utilization",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/installation/installation-frc.html",
+ "title": "Installing Phoenix 6 (FRC)",
"section": "General",
"language": "All",
- "content": "CAN Bus Utilization CTR Electronics goes through great efforts to make our products efficient on CAN bus bandwidth. This article highlights the average default bus utilization of supported Phoenix 6 devices. Users should keep total CAN bus utilization below 90% to prevent any unexpected behavior. Information on changing the default CAN update frequency is available in the status signal and control request documentation. Note Using Phoenix API will automatically start up a diagnostic server which adds a constant 0-5% total CAN bus utilization. Device Phoenix 5 (CAN 2.0) Phoenix 6 (CAN 2.0) Phoenix 5 (CAN FD) Phoenix 6 (CAN FD) [ 1 ] CANcoder 1.8% 1.7% 0.9% 0.9% CANdi N/A 2.5% N/A 1.1% CANdle [ 2 ] 1.5% 0.4% 0.7% 0.2% CANrange N/A 2.0% N/A 1.0% Talon FX 4.7% 4.1% 2.0% 1.8% Talon FXS N/A 4.2% N/A 1.8% Pigeon 2 5.5% 3.1% 2.5% 1.3% [ 1 ] Phoenix 6 devices on CAN FD also increase the default update frequency of many status signals to 100 Hz. [ 2 ] CANdle’s device utilization is without LED usage. Animating or setting LEDs will increase bus utilization.",
- "content_preview": "CAN Bus Utilization CTR Electronics goes through great efforts to make our products efficient on CAN bus bandwidth. This article highlights the average default bus utilization of supported Phoenix 6 devices. Users should keep total CAN bus utilization below 90% to prevent any unexpected behavior."
+ "content": "Installing Phoenix 6 (FRC) Java/C++ Offline Download the Phoenix Framework Installer Navigate through the installer, ensuring applicable options are selected Apply the vendordep via WPILib VS Code Adding Offline Libraries Online Users can install Phoenix without an installer using WPILib’s Install New Libraries functionality in VS Code. This requires the user to have an installation of WPILib on their machine. To begin, open WPILib VS Code and click on the WPILib icon in the top right. Then type Manage Vendor Libraries and click on the menu option that appears. Click Install new libraries (online) and a textbox should appear. Follow the remaining instructions below on pasting the correct link into the textbox. Paste the following URL in WPILib VS Code Install new libraries (online) : https://maven.ctr-electronics.com/release/com/ctre/phoenix6/latest/Phoenix6-frc2026-latest.json Additionally, v5 can safely installed alongside it by installing the v5 vendordep. https://maven.ctr-electronics.com/release/com/ctre/phoenix/Phoenix5-frc2026-latest.json Alternatively, the Hoot Replay version of the vendordep can be installed, as well as the v5 Replay-compatible vendordep: https://maven.ctr-electronics.com/release/com/ctre/phoenix6/latest/Phoenix6-replay-frc2026-latest.json https://maven.ctr-electronics.com/release/com/ctre/phoenix/Phoenix5-replay-frc2026-latest.json Important Users utilizing only v5 devices still need the v6 vendordep added to their robot project. Python First, make sure to install RobotPy . From there, installation of Phoenix 6 is available through PyPI . py -3 -m pip install phoenix6 LabVIEW Download the Phoenix Offline Installer from the Latest GitHub Release , and install it on the computer (with the LabVIEW component checked). This will put the Phoenix LabVIEW VIs into the “WPI Robotics Library -> Third Party -> CTRE” pallette for LabVIEW development. To deploy robot projects with Phoenix, you need to first download the Phoenix Libraries to the roboRIO. This can be done one of two ways: Phoenix Tuner X under “Settings -> FRC Advanced -> Install LabVIEW” LabVIEW under “Tools -> FIRST Robotics Tools -> Download CTRE Phoenix Libs”. After the libraries are downloaded, hard deploy (run as startup) a LabVIEW program and restart the roboRIO.",
+ "content_preview": "Installing Phoenix 6 (FRC) Java/C++ Offline Download the Phoenix Framework Installer Navigate through the installer, ensuring applicable options are selected Apply the vendordep via WPILib VS Code Adding Offline Libraries Online Users can install Phoenix without an installer using WPILib’s Install..."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/migration/migration-guide/status-signals-guide.html",
- "title": "Status Signals",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/device-specific/talonfx/basic-pid-control.html",
+ "title": "Basic PID and Profiling",
+ "section": "TalonFX",
+ "language": "All",
+ "content": "Basic PID and Profiling The Talon FX supports basic PID control and motion profiling for position and velocity. Note For more information on feedback and feedforward gains, see Closed-Loop Overview . Position Control A Position closed loop can be used to target a specified motor position (in rotations). Position closed loop is currently supported for all base control output types . The units of the output are determined by the control output type. In a Position closed loop, the gains should be configured as follows: \\(K_g\\) - output to overcome gravity (output) \\(K_s\\) - Velocity Sign: unused; Closed-Loop Sign: output to overcome static friction (output) \\(K_v\\) - unused, as there is no target velocity \\(K_a\\) - unused, as there is no target acceleration \\(K_p\\) - output per unit of error in position (output/rotation) \\(K_i\\) - output per unit of integrated error in position (output/(rotation*s)) \\(K_d\\) - output per unit of error derivative in position (output/rps) Java // in init function, set slot 0 gains var slot0Configs = new Slot0Configs (); slot0Configs . kP = 2.4 ; // An error of 1 rotation results in 2.4 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0.1 ; // A velocity of 1 rps results in 0.1 V output m_talonFX . getConfigurator (). apply ( slot0Configs ); C++ // in init function, set slot 0 gains configs :: Slot0Configs slot0Configs {}; slot0Configs . kP = 2.4 ; // An error of 1 rotation results in 2.4 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0.1 ; // A velocity of 1 rps results in 0.1 V output m_talonFX . GetConfigurator (). Apply ( slot0Configs ); Python # in init function, set slot 0 gains slot0_configs = configs . Slot0Configs () slot0_configs . k_p = 2.4 # An error of 1 rotation results in 2.4 V output slot0_configs . k_i = 0 # no output for integrated error slot0_configs . k_d = 0.1 # A velocity of 1 rps results in 0.1 V output self . talonfx . configurator . apply ( slot0_configs ) Once the gains are configured, the Position closed loop control request can be sent to the TalonFX. The control request object has an optional feedforward term that can be used to add an arbitrary value to the output, which can be useful to account for the effects of gravity or friction. Java // create a position closed-loop request, voltage output, slot 0 configs final PositionVoltage m_request = new PositionVoltage ( 0 ). withSlot ( 0 ); // set position to 10 rotations m_talonFX . setControl ( m_request . withPosition ( 10 )); C++ // create a position closed-loop request, voltage output, slot 0 configs controls :: PositionVoltage m_request = controls :: PositionVoltage { 0 _tr }. WithSlot ( 0 ); // set position to 10 rotations m_talonFX . SetControl ( m_request . WithPosition ( 10 _tr )); Python # create a position closed-loop request, voltage output, slot 0 configs self . request = controls . PositionVoltage ( 0 ) . with_slot ( 0 ) # set position to 10 rotations self . talonfx . set_control ( self . request . with_position ( 10 )) Velocity Control A Velocity closed loop can be used to maintain a target velocity (in rotations per second). This can be useful for controlling flywheels, where a velocity needs to be maintained for accurate shooting. Velocity closed loop is currently supported for all base control output types . The units of the output are determined by the control output type. In a Velocity closed loop, the gains should be configured as follows: \\(K_g\\) - output to overcome gravity (output) \\(K_s\\) - output to overcome static friction (output) \\(K_v\\) - output per unit of requested velocity (output/rps) \\(K_a\\) - unused, as there is no target acceleration \\(K_p\\) - output per unit of error in velocity (output/rps) \\(K_i\\) - output per unit of integrated error in velocity (output/rotation) \\(K_d\\) - output per unit of error derivative in velocity (output/(rps/s)) Java // in init function, set slot 0 gains var slot0Configs = new Slot0Configs (); slot0Configs . kS = 0.1 ; // Add 0.1 V output to overcome static friction slot0Configs . kV = 0.12 ; // A velocity target of 1 rps results in 0.12 V output slot0Configs . kP = 0.11 ; // An error of 1 rps results in 0.11 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0 ; // no output for error derivative m_talonFX . getConfigurator (). apply ( slot0Configs ); C++ // in init function, set slot 0 gains configs :: Slot0Configs slot0Configs {}; slot0Configs . kS = 0.1 ; // Add 0.1 V output to overcome static friction slot0Configs . kV = 0.12 ; // A velocity target of 1 rps results in 0.12 V output slot0Configs . kP = 0.11 ; // An error of 1 rps results in 0.11 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0 ; // no output for error derivative m_talonFX . GetConfigurator (). Apply ( slot0Configs ); Python slot0_configs = configs . Slot0Configs () slot0_configs . k_s = 0.1 # Add 0.1 V output to overcome static friction slot0_configs . k_v = 0.12 # A velocity target of 1 rps results in 0.12 V output slot0_configs . k_p = 0.11 # An error of 1 rps results in 0.11 V output slot0_configs . k_i = 0 # no output for integrated error slot0_configs . k_d = 0 # no output for error derivative self . talonfx . configurator . apply ( slot0_configs ) Once the gains are configured, the Velocity closed loop control request can be sent to the TalonFX. The control request object has an optional feedforward term that can be used to add an arbitrary value to the output, which can be useful to account for the effects of gravity. Java // create a velocity closed-loop request, voltage output, slot 0 configs final VelocityVoltage m_request = new VelocityVoltage ( 0 ). withSlot ( 0 ); // set velocity to 8 rps, add 0.5 V to overcome gravity m_talonFX . setControl ( m_request . withVelocity ( 8 ). withFeedForward ( 0.5 )); C++ // create a velocity closed-loop request, voltage output, slot 0 configs controls :: VelocityVoltage m_request = controls :: VelocityVoltage { 0 _tps }. WithSlot ( 0 ); // set velocity to 8 rps, add 0.5 V to overcome gravity m_talonFX . SetControl ( m_request . WithVelocity ( 8 _tps ). WithFeedForward ( 0.5 _V )); Python # create a velocity closed-loop request, voltage output, slot 0 configs self . request = controls . VelocityVoltage ( 0 ) . with_slot ( 0 ) # set velocity to 8 rps, add 0.5 V to overcome gravity self . talonfx . set_control ( self . request . with_velocity ( 8 ) . with_feed_forward ( 0.5 )) Motion Profiling The Position and Velocity closed-loop requests can be used to run a motion profile generated by the robot controller. Tip The Talon FX supports several onboard motion profiles using Motion Magic® . Position In a Position motion profile, the gains should be configured as follows: \\(K_g\\) - output to overcome gravity (output) \\(K_s\\) - output to overcome static friction (output) \\(K_v\\) - output per unit of requested velocity (output/rps) \\(K_a\\) - unused, as there is no target acceleration \\(K_p\\) - output per unit of error in position (output/rotation) \\(K_i\\) - output per unit of integrated error in position (output/(rotation*s)) \\(K_d\\) - output per unit of error derivative in position (output/rps) Java // in init function, set slot 0 gains var slot0Configs = new Slot0Configs (); slot0Configs . kS = 0.25 ; // Add 0.25 V output to overcome static friction slot0Configs . kV = 0.12 ; // A velocity target of 1 rps results in 0.12 V output slot0Configs . kP = 4.8 ; // A position error of 2.5 rotations results in 12 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0.1 ; // A velocity error of 1 rps results in 0.1 V output m_talonFX . getConfigurator (). apply ( slot0Configs ); C++ // in init function, set slot 0 gains configs :: Slot0Configs slot0Configs {}; slot0Configs . kS = 0.25 ; // Add 0.25 V output to overcome static friction slot0Configs . kV = 0.12 ; // A velocity target of 1 rps results in 0.12 V output slot0Configs . kP = 4.8 ; // A position error of 2.5 rotations results in 12 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0.1 ; // A velocity error of 1 rps results in 0.1 V output m_talonFX . GetConfigurator (). Apply ( slot0Configs ); Python # in init function, set slot 0 gains slot0_configs = configs . Slot0Configs () slot0_configs . k_s = 0.25 # Add 0.25 V output to overcome static friction slot0_configs . k_v = 0.12 # A velocity target of 1 rps results in 0.12 V output slot0_configs . k_p = 4.8 # A position error of 2.5 rotations results in 12 V output slot0_configs . k_i = 0 # no output for integrated error slot0_configs . k_d = 0.1 # A velocity error of 1 rps results in 0.1 V output self . talonfx . configurator . apply ( slot0_configs ) Once the gains are configured, the Position closed-loop control request can be sent to the TalonFX. The Velocity parameter is used to specify the current setpoint velocity of the motion profile. The control request object has an optional feedforward term that can be used to add an arbitrary value to the output, which can be useful to account for the effects of gravity or friction. Java // Trapezoid profile with max velocity 80 rps, max accel 160 rps/s final TrapezoidProfile m_profile = new TrapezoidProfile ( new TrapezoidProfile . Constraints ( 80 , 160 ) ); // Final target of 200 rot, 0 rps TrapezoidProfile . State m_goal = new TrapezoidProfile . State ( 200 , 0 ); TrapezoidProfile . State m_setpoint = new TrapezoidProfile . State (); // create a position closed-loop request, voltage output, slot 0 configs final PositionVoltage m_request = new PositionVoltage ( 0 ). withSlot ( 0 ); // calculate the next profile setpoint m_setpoint = m_profile . calculate ( 0.020 , m_setpoint , m_goal ); // send the request to the device m_request . Position = m_setpoint . position ; m_request . Velocity = m_setpoint . velocity ; m_talonFX . setControl ( m_request ); C++ // Trapezoid profile with max velocity 80 rps, max accel 160 rps/s frc :: TrapezoidProfile < units :: turn_t > m_profile {{ 80 _tps , 160 _tr_per_s_sq }}; // Final target of 200 rot, 0 rps frc :: TrapezoidProfile < units :: turn_t >:: State m_goal { 200 _tr , 0 _tps }; frc :: TrapezoidProfile < units :: turn_t >:: State m_setpoint {}; // create a position closed-loop request, voltage output, slot 0 configs controls :: PositionVoltage m_request = controls :: PositionVoltage { 0 _tr }. WithSlot ( 0 ); // calculate the next profile setpoint m_setpoint = m_profile . Calculate ( 20 _ms , m_setpoint , m_goal ); // send the request to the device m_request . Position = m_setpoint . position ; m_request . Velocity = m_setpoint . velocity ; m_talonFX . SetControl ( m_request ); Python # Trapezoid profile with max velocity 80 rps, max accel 160 rps/s self . profile = TrapezoidProfile ( TrapezoidProfile . Constraints ( 80 , 160 ) ) # Final target of 200 rot, 0 rps self . goal = TrapezoidProfile . State ( 200 , 0 ) self . setpoint = TrapezoidProfile . State () # create a position closed-loop request, voltage output, slot 0 configs self . request = controls . PositionVoltage ( 0 ) . with_slot ( 0 ) # calculate the next profile setpoint self . setpoint = self . profile . calculate ( 0.020 , self . setpoint , self . goal ) # send the request to the device self . request . position = self . setpoint . position self . request . velocity = self . setpoint . velocity self . talonfx . set_control ( self . request ) Velocity In a Velocity motion profile, the gains should be configured as follows: \\(K_g\\) - output to overcome gravity (output) \\(K_s\\) - output to overcome static friction (output) \\(K_v\\) - output per unit of requested velocity (output/rps) \\(K_a\\) - output per unit of requested acceleration (output/(rps/s)) \\(K_p\\) - output per unit of error in velocity (output/rps) \\(K_i\\) - output per unit of integrated error in velocity (output/rotation) \\(K_d\\) - output per unit of error derivative in velocity (output/(rps/s)) Java // in init function, set slot 0 gains var slot0Configs = new Slot0Configs (); slot0Configs . kS = 0.25 ; // Add 0.25 V output to overcome static friction slot0Configs . kV = 0.12 ; // A velocity target of 1 rps results in 0.12 V output slot0Configs . kA = 0.01 ; // An acceleration of 1 rps/s requires 0.01 V output slot0Configs . kP = 0.11 ; // An error of 1 rps results in 0.11 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0 ; // no output for error derivative m_talonFX . getConfigurator (). apply ( slot0Configs ); C++ // in init function, set slot 0 gains configs :: Slot0Configs slot0Configs {}; slot0Configs . kS = 0.25 ; // Add 0.25 V output to overcome static friction slot0Configs . kV = 0.12 ; // A velocity target of 1 rps results in 0.12 V output slot0Configs . kA = 0.01 ; // An acceleration of 1 rps/s requires 0.01 V output slot0Configs . kP = 0.11 ; // An error of 1 rps results in 0.11 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0 ; // no output for error derivative m_talonFX . GetConfigurator (). Apply ( slot0Configs ); Python # in init function, set slot 0 gains slot0_configs = configs . Slot0Configs () slot0_configs . k_s = 0.25 # Add 0.25 V output to overcome static friction slot0_configs . k_v = 0.12 # A velocity target of 1 rps results in 0.12 V output slot0_configs . k_a = 0.01 # An acceleration of 1 rps/s requires 0.01 V output slot0_configs . k_p = 0.11 # An error of 1 rps results in 0.11 V output slot0_configs . k_i = 0 # no output for integrated error slot0_configs . k_d = 0 # no output for error derivative self . talonfx . configurator . apply ( slot0_configs ) Once the gains are configured, the Velocity closed-loop control request can be sent to the TalonFX. The Acceleration parameter is used to specify the current setpoint acceleration of the motion profile. The control request object has an optional feedforward term that can be used to add an arbitrary value to the output, which can be useful to account for the effects of gravity or friction. Java // Trapezoid profile with max acceleration 400 rot/s^2, max jerk 4000 rot/s^3 final TrapezoidProfile m_profile = new TrapezoidProfile ( new TrapezoidProfile . Constraints ( 400 , 4000 ) ); // Final target of 80 rps, 0 rps/s TrapezoidProfile . State m_goal = new TrapezoidProfile . State ( 80 , 0 ); TrapezoidProfile . State m_setpoint = new TrapezoidProfile . State (); // create a velocity closed-loop request, voltage output, slot 0 configs final VelocityVoltage m_request = new VelocityVoltage ( 0 ). withSlot ( 0 ); // calculate the next profile setpoint m_setpoint = m_profile . calculate ( 0.020 , m_setpoint , m_goal ); // send the request to the device // note: \"position\" is velocity, and \"velocity\" is acceleration m_request . Velocity = m_setpoint . position ; m_request . Acceleration = m_setpoint . velocity ; m_talonFX . setControl ( m_request ); C++ // Trapezoid profile with max acceleration 400 rot/s^2, max jerk 4000 rot/s^3 frc :: TrapezoidProfile < units :: turns_per_second_t > m_profile {{ 400 _tr_per_s_sq , 4000 _tr_per_s_cu }}; // Final target of 80 rps, 0 rot/s^2 frc :: TrapezoidProfile < units :: turns_per_second_t >:: State m_goal { 80 _tps , 0 _tr_per_s_sq }; frc :: TrapezoidProfile < units :: turns_per_second_t >:: State m_setpoint {}; // create a velocity closed-loop request, voltage output, slot 0 configs controls :: VelocityVoltage m_request = controls :: VelocityVoltage { 0 _tps }. WithSlot ( 0 ); // calculate the next profile setpoint m_setpoint = m_profile . Calculate ( 20 _ms , m_setpoint , m_goal ); // send the request to the device // note: \"position\" is velocity, and \"velocity\" is acceleration m_positionControl . Velocity = m_setpoint . position ; m_positionControl . Acceleration = m_setpoint . velocity ; m_talonFX . SetControl ( m_request ); Python # Trapezoid profile with max acceleration 400 rot/s^2, max jerk 4000 rot/s^3 self . profile = TrapezoidProfile ( TrapezoidProfile . Constraints ( 400 , 4000 ) ) # Final target of 80 rps, 0 rot/s^2 self . goal = TrapezoidProfile . State ( 80 , 0 ) self . setpoint = TrapezoidProfile . State () # create a velocity closed-loop request, voltage output, slot 0 configs self . request = controls . VelocityVoltage ( 0 ) . with_slot ( 0 ) # calculate the next profile setpoint self . setpoint = self . profile . calculate ( 0.020 , self . setpoint , self . goal ) # send the request to the device # note: \"position\" is velocity, and \"velocity\" is acceleration self . request . velocity = self . setpoint . position self . request . acceleration = self . setpoint . velocity self . talonfx . set_control ( self . request )",
+ "content_preview": "Basic PID and Profiling The Talon FX supports basic PID control and motion profiling for position and velocity. Note For more information on feedback and feedforward gains, see Closed-Loop Overview ."
+ },
+ {
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/migration/migration-guide/closed-loop-guide.html",
+ "title": "Closed",
"section": "General",
"language": "All",
- "content": "Status Signals Phoenix 6 expands the functionality of status signals with the introduction of the StatusSignal ( Java , C++ ). Note For more information about status signals in Phoenix 6, see Status Signals . Using Status Signals v5 Java // get latest TalonFX selected sensor position // units are encoder ticks int sensorPos = m_talonFX . getSelectedSensorPosition (); // latency is unknown // cannot synchronously wait for new data C++ // get latest TalonFX selected sensor position // units are encoder ticks int sensorPos = m_talonFX . GetSelectedSensorPosition (); // latency is unknown // cannot synchronously wait for new data v6 Java // acquire a refreshed TalonFX rotor position signal var rotorPosSignal = m_talonFX . getRotorPosition (); // because we are calling getRotorPosition() every loop, // we do not need to call refresh() //rotorPosSignal.refresh(); // retrieve position value that we just refreshed // units are rotations, uses the units library var rotorPos = rotorPosSignal . getValue (); // the units library can be bypassed using getValueAsDouble() double rotorPosRotations = rotorPosSignal . getValueAsDouble (); // get latency of the signal var rotorPosLatency = rotorPosSignal . getTimestamp (). getLatency (); // synchronously wait 20 ms for new data rotorPosSignal . waitForUpdate ( 0.020 ); C++ // acquire a refreshed TalonFX rotor position signal auto & rotorPosSignal = m_talonFX . GetRotorPosition (); // because we are calling GetRotorPosition() every loop, // we do not need to call Refresh() //rotorPosSignal.Refresh(); // retrieve position value that we just refreshed // units are rotations, uses the units library auto rotorPos = rotorPosSignal . GetValue (); // get latency of the signal auto rotorPosLatency = rotorPosSignal . GetTimestamp (). GetLatency (); // synchronously wait 20 ms for new data rotorPosSignal . WaitForUpdate ( 20 _ms ); Changing Update Frequency (Status Frame Period) v5 Java // slow down the Status 2 frame (selected sensor data) to 5 Hz (200ms) m_talonFX . setStatusFramePeriod ( StatusFrameEnhanced . Status_2_Feedback0 , 200 ); C++ // slow down the Status 2 frame (selected sensor data) to 5 Hz (200ms) m_talonFX . SetStatusFramePeriod ( StatusFrameEnhanced :: Status_2_Feedback0 , 200 ); v6 Java // slow down the position signal to 5 Hz m_talonFX . getPosition (). setUpdateFrequency ( 5 ); C++ // slow down the position signal to 5 Hz m_talonFX . GetPosition (). SetUpdateFrequency ( 5 _Hz ); Note When different update frequencies are specified for signals that share a status frame, the highest update frequency of all the relevant signals will be applied to the entire frame. Users can get a signal’s applied update frequency using the getAppliedUpdateFrequency() method. Common Signals Several status signals have changed name or form in Phoenix 6. General Signals Phoenix 5 Phoenix 6 BusVoltage SupplyVoltage Faults / StickyFaults (fills an object) Fault_* / StickyFault_* (individual faults) FirmwareVersion Version Talon FX Signals Phoenix 5 Phoenix 6 MotorOutputPercent DutyCycle StatorCurrent StatorCurrent (motoring +, braking -), TorqueCurrent (forward +, reverse -) Inverted (true/false; matches setInverted ) AppliedRotorPolarity (CCW+/CW+; typically matches Inverted config, affected by follower features) SelectedSensorPosition / SelectedSensorVelocity Position / Velocity IntegratedSensor* (in SensorCollection ) Rotor* ActiveTrajectory* (only Motion Magic® and the Motion Profile Executor) ClosedLoopReference* (all closed-loop control requests) IsFwdLimitSwitchClosed / IsRevLimitSwitchClosed (true/false) GetForwardLimit / GetReverseLimit (Open/Closed) CANcoder Signals Phoenix 5 Phoenix 6 MagnetFieldStrength MagnetHealth Pigeon 2 Signals Note Many Pigeon 2 signal getters in Phoenix 5 fill an array, such as YawPitchRoll . In Phoenix 6, these signals have been broken up into their individual components, such as Yaw , Pitch , and Roll . Phoenix 5 Phoenix 6 RawGyro AngularVelocity* 6dQuaternion Quat* BiasedAccelerometer Acceleration* BiasedMagnetometer MagneticField* RawMagnetometer RawMagneticField*",
- "content_preview": "Status Signals Phoenix 6 expands the functionality of status signals with the introduction of the StatusSignal ( Java , C++ ). Note For more information about status signals in Phoenix 6, see Status Signals ."
+ "content": "Closed-Loop Control Phoenix 6 enhances the experience of using onboard closed-loop control through the use of standardized units and a variety of control output types. Note For more information about closed-loop control in Phoenix 6, see Closed-Loop Overview . Closed-Loop Setpoints Phoenix 6 uses canonical units for closed-loop setpoints. Note This calculator assumes the RotorToSensorRatio and SensorToMechanismRatio configs are both set to 1. If this is not the case in your robot program, divide the resulting values by both ratios. Setpoint Conversion Name Value Units Formula Position Original \\(\\mathrm{raw\\_units}\\) \\(x_{\\mathrm{old}}\\) New \\(\\mathrm{rotations}\\) \\(x_{\\mathrm{new}}=x_{\\mathrm{old}} \\cdot \\frac{1}{2048} \\frac{\\mathrm{rot}}{\\mathrm{raw\\_unit}}\\) Velocity Original \\(\\frac{\\mathrm{raw\\_units}}{\\mathrm{100ms}}\\) \\(v_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{rot}}{\\mathrm{second}}\\) \\(v_{\\mathrm{new}}=v_{\\mathrm{old}} \\cdot \\frac{1}{2048} \\frac{\\mathrm{rot}}{\\mathrm{raw\\_unit}} \\cdot 10 \\frac{\\mathrm{100ms}}{\\mathrm{second}} \\) Acceleration Original \\(\\frac{\\mathrm{raw\\_units}}{\\mathrm{100ms} \\cdot \\mathrm{second}}\\) \\(a_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{rot}}{\\mathrm{second}^2}\\) \\(a_{\\mathrm{new}}=a_{\\mathrm{old}} \\cdot \\frac{1}{2048} \\frac{\\mathrm{rot}}{\\mathrm{raw\\_unit}} \\cdot 10 \\frac{\\mathrm{100ms}}{\\mathrm{second}} \\) Closed-Loop Gains Position without Voltage Comp Phoenix 5 ControlMode.Position with voltage compensation disabled maps to the Phoenix 6 PositionDutyCycle control request. Note This calculator assumes the RotorToSensorRatio and SensorToMechanismRatio configs are both set to 1. If this is not the case in your robot program, multiply the resulting gains by both ratios. Position without Voltage Compensation Name Value Units Formula kP Original \\(\\frac{\\mathrm{raw\\_output}}{\\mathrm{unit}}\\) \\(kP_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{duty\\_cycle}}{\\mathrm{rot}}\\) \\(kP_{\\mathrm{new}}=kP_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}}\\) kI Original \\(\\frac{\\mathrm{raw\\_output}}{\\mathrm{unit} \\cdot \\mathrm{millisecond}}\\) \\(kI_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{duty\\_cycle}}{\\mathrm{rot} \\cdot \\mathrm{second}}\\) \\(kI_{\\mathrm{new}}=kI_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot 1000 \\frac{\\mathrm{millisecond}}{\\mathrm{second}}\\) kD Original \\(\\frac{\\mathrm{raw\\_output}}{\\mathrm{unit} / \\mathrm{millisecond}}\\) \\(kD_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{duty\\_cycle}}{\\mathrm{rot} / \\mathrm{second}}\\) \\(kD_{\\mathrm{new}}=kD_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot \\frac{1}{1000} \\frac{\\mathrm{second}}{\\mathrm{millisecond}}\\) Position with Voltage Comp Phoenix 5 ControlMode.Position with voltage compensation enabled has been replaced with the Phoenix 6 PositionVoltage control request, which directly controls voltage. Note This calculator assumes the RotorToSensorRatio and SensorToMechanismRatio configs are both set to 1. If this is not the case in your robot program, multiply the resulting gains by both ratios. Position with Voltage Compensation Voltage Compensation Value: Name Value Units Formula kP Original \\(\\frac{\\mathrm{\\mathrm{raw\\_output}}}{\\mathrm{unit}}\\) \\(kP_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{V}}{\\mathrm{rot}}\\) \\(kP_{\\mathrm{new}}=kP_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot \\mathrm{V\\_comp} \\frac{\\mathrm{V}}{\\mathrm{duty\\_cycle}}\\) kI Original \\(\\frac{\\mathrm{\\mathrm{raw\\_output}}}{\\mathrm{unit} \\cdot \\mathrm{millisecond}}\\) \\(kI_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{V}}{\\mathrm{rot} \\cdot \\mathrm{second}}\\) \\(kI_{\\mathrm{new}}=kI_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot 1000 \\frac{\\mathrm{millisecond}}{\\mathrm{second}} \\cdot \\mathrm{V\\_comp} \\frac{\\mathrm{V}}{\\mathrm{duty\\_cycle}}\\) kD Original \\(\\frac{\\mathrm{\\mathrm{raw\\_output}}}{\\mathrm{unit} / \\mathrm{millisecond}}\\) \\(kD_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{V}}{\\mathrm{rot} / \\mathrm{second}}\\) \\(kD_{\\mathrm{new}}=kD_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot \\frac{1}{1000} \\frac{\\mathrm{second}}{\\mathrm{millisecond}} \\cdot \\mathrm{V\\_comp} \\frac{\\mathrm{V}}{\\mathrm{duty\\_cycle}}\\) Velocity without Voltage Comp Phoenix 5 ControlMode.Velocity with voltage compensation disabled maps to the Phoenix 6 VelocityDutyCycle control request. Additionally, kF from Phoenix 5 has been replaced with kV in Phoenix 6. Note This calculator assumes the RotorToSensorRatio and SensorToMechanismRatio configs are both set to 1. If this is not the case in your robot program, multiply the resulting gains by both ratios. Velocity without Voltage Compensation Name Value Units Formula kP Original \\(\\frac{\\mathrm{raw\\_output}}{\\mathrm{unit} / \\mathrm{100ms}}\\) \\(kP_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{duty\\_cycle}}{\\mathrm{rot} / \\mathrm{sec}}\\) \\(kP_{\\mathrm{new}}=kP_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot \\frac{1}{10} \\frac{\\mathrm{sec}}{\\mathrm{100ms}}\\) kI Original \\(\\frac{\\mathrm{raw\\_output}}{(\\mathrm{unit} / \\mathrm{100ms}) \\cdot \\mathrm{millisecond}}\\) \\(kI_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{duty\\_cycle}}{\\mathrm{rot}}\\) \\(kI_{\\mathrm{new}}=kI_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot 1000 \\frac{\\mathrm{millisecond}}{\\mathrm{second}} \\cdot \\frac{1}{10} \\frac{\\mathrm{sec}}{\\mathrm{100ms}}\\) kD Original \\(\\frac{\\mathrm{raw\\_output}}{(\\mathrm{unit} / \\mathrm{100ms}) / \\mathrm{millisecond}}\\) \\(kD_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{duty\\_cycle}}{\\mathrm{rot} / \\mathrm{second}^{2}}\\) \\(kD_{\\mathrm{new}}=kD_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot \\frac{1}{1000} \\frac{\\mathrm{second}}{\\mathrm{millisecond}} \\cdot \\frac{1}{10} \\frac{\\mathrm{sec}}{\\mathrm{100ms}}\\) kF kV Original \\(\\frac{\\mathrm{raw\\_output}}{\\mathrm{unit} / \\mathrm{100millisecond}}\\) \\(kF_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{duty\\_cycle}}{\\mathrm{rot} / \\mathrm{second}}\\) \\(kV_{\\mathrm{new}}=kF_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot \\frac{1}{10} \\frac{\\mathrm{second}}{\\mathrm{100ms}}\\) Velocity with Voltage Comp Phoenix 5 ControlMode.Velocity with voltage compensation enabled has been replaced with the Phoenix 6 VelocityVoltage control request, which directly controls voltage. Additionally, kF from Phoenix 5 has been replaced with kV in Phoenix 6. Note This calculator assumes the RotorToSensorRatio and SensorToMechanismRatio configs are both set to 1. If this is not the case in your robot program, multiply the resulting gains by both ratios. Velocity with Voltage Compensation Voltage Compensation Value: Name Value Units Formula kP Original \\(\\frac{\\mathrm{\\mathrm{raw\\_output}}}{\\mathrm{unit} / \\mathrm{100ms}}\\) \\(kP_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{V}}{\\mathrm{rot} / \\mathrm{sec}}\\) \\(kP_{\\mathrm{new}}=kP_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot \\frac{1}{10} \\frac{\\mathrm{second}}{\\mathrm{100ms}} \\cdot \\mathrm{V\\_comp} \\frac{\\mathrm{V}}{\\mathrm{duty\\_cycle}}\\) kI Original \\(\\frac{\\mathrm{\\mathrm{raw\\_output}}}{(\\mathrm{unit} / \\mathrm{100ms}) \\cdot \\mathrm{millisecond}}\\) \\(kI_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{V}}{\\mathrm{rot}}\\) \\(kI_{\\mathrm{new}}=kI_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot 1000 \\frac{\\mathrm{millisecond}}{\\mathrm{second}} \\cdot \\frac{1}{10} \\frac{\\mathrm{second}}{\\mathrm{100ms}} \\cdot \\mathrm{V\\_comp} \\frac{\\mathrm{V}}{\\mathrm{duty\\_cycle}}\\) kD Original \\(\\frac{\\mathrm{\\mathrm{raw\\_output}}}{(\\mathrm{unit} / \\mathrm{100ms}) / \\mathrm{millisecond}}\\) \\(kD_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{V}}{\\mathrm{rot} / \\mathrm{second}^{2}}\\) \\(kD_{\\mathrm{new}}=kD_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot \\frac{1}{1000} \\frac{\\mathrm{second}}{\\mathrm{millisecond}} \\cdot \\frac{1}{10} \\frac{\\mathrm{second}}{\\mathrm{100ms}} \\cdot \\mathrm{V\\_comp} \\frac{\\mathrm{V}}{\\mathrm{duty\\_cycle}}\\) kF kV Original \\(\\frac{\\mathrm{\\mathrm{raw\\_output}}}{\\mathrm{unit} / \\mathrm{100ms}}\\) \\(kF_{\\mathrm{old}}\\) New \\(\\frac{\\mathrm{V}}{\\mathrm{rot} / \\mathrm{second}}\\) \\(kV_{\\mathrm{new}}=kF_{\\mathrm{old}} \\cdot 2048 \\frac{\\mathrm{unit}}{\\mathrm{rot}} \\cdot \\frac{1}{1023} \\frac{\\mathrm{duty\\_cycle}}{\\mathrm{raw\\_output}} \\cdot \\frac{1}{10} \\frac{\\mathrm{second}}{\\mathrm{100ms}} \\cdot \\mathrm{V\\_comp} \\frac{\\mathrm{V}}{\\mathrm{duty\\_cycle}}\\) Using Closed-Loop Control v5 Java // robot init, set slot 0 gains m_motor . config_kF ( 0 , 0.05 , 50 ); m_motor . config_kP ( 0 , 0.046 , 50 ); m_motor . config_kI ( 0 , 0.0002 , 50 ); m_motor . config_kD ( 0 , 4.2 , 50 ); // enable voltage compensation m_motor . configVoltageComSaturation ( 12 ); m_motor . enableVoltageCompensation ( true ); // periodic, run velocity control with slot 0 configs, // target velocity of 50 rps (10240 ticks/100ms) m_motor . selectProfileSlot ( 0 , 0 ); m_motor . set ( ControlMode . Velocity , 10240 ); C++ // robot init, set slot 0 gains m_motor . Config_kF ( 0 , 0.05 , 50 ); m_motor . Config_kP ( 0 , 0.046 , 50 ); m_motor . Config_kI ( 0 , 0.0002 , 50 ); m_motor . Config_kD ( 0 , 4.2 , 50 ); // enable voltage compensation m_motor . ConfigVoltageComSaturation ( 12 ); m_motor . EnableVoltageCompensation ( true ); // periodic, run velocity control with slot 0 configs, // target velocity of 50 rps (10240 ticks/100ms) m_motor . SelectProfileSlot ( 0 , 0 ); m_motor . Set ( ControlMode :: Velocity , 10240 ); v6 Java // class member variable final VelocityVoltage m_velocity = new VelocityVoltage ( 0 ); // robot init, set slot 0 gains var slot0Configs = new Slot0Configs (); slot0Configs . kV = 0.12 ; slot0Configs . kP = 0.11 ; slot0Configs . kI = 0.48 ; slot0Configs . kD = 0.01 ; m_talonFX . getConfigurator (). apply ( slot0Configs , 0.050 ); // periodic, run velocity control with slot 0 configs, // target velocity of 50 rps m_velocity . Slot = 0 ; m_motor . setControl ( m_velocity . withVelocity ( 50 )); C++ // class member variable controls :: VelocityVoltage m_velocity { 0 _tps }; // robot init, set slot 0 gains configs :: Slot0Configs slot0Configs {}; slot0Configs . kV = 0.12 ; slot0Configs . kP = 0.11 ; slot0Configs . kI = 0.48 ; slot0Configs . kD = 0.01 ; m_talonFX . GetConfigurator (). Apply ( slot0Configs , 50 _ms ); // periodic, run velocity control with slot 0 configs, // target velocity of 50 rps m_velocity . Slot = 0 ; m_motor . SetControl ( m_velocity . WithVelocity ( 50 _tps )); Motion Magic® v5 Java // robot init, set slot 0 gains m_motor . config_kF ( 0 , 0.05 , 50 ); // PID runs on position m_motor . config_kP ( 0 , 0.2 , 50 ); m_motor . config_kI ( 0 , 0 , 50 ); m_motor . config_kD ( 0 , 4.2 , 50 ); // set Motion Magic settings m_motor . configMotionCruiseVelocity ( 16384 ); // 80 rps = 16384 ticks/100ms cruise velocity m_motor . configMotionAcceleration ( 32768 ); // 160 rps/s = 32768 ticks/100ms/s acceleration m_motor . configMotionSCurveStrength ( 3 ); // s-curve smoothing strength of 3 // enable voltage compensation m_motor . configVoltageComSaturation ( 12 ); m_motor . enableVoltageCompensation ( true ); // periodic, run Motion Magic with slot 0 configs m_motor . selectProfileSlot ( 0 , 0 ); // target position of 200 rotations (409600 ticks) // add 0.02 (2%) arbitrary feedforward to overcome friction m_motor . set ( ControlMode . MotionMagic , 409600 , DemandType . ArbitraryFeedforward , 0.02 ); C++ // robot init, set slot 0 gains m_motor . Config_kF ( 0 , 0.05 , 50 ); // PID runs on position m_motor . Config_kP ( 0 , 0.2 , 50 ); m_motor . Config_kI ( 0 , 0 , 50 ); m_motor . Config_kD ( 0 , 4.2 , 50 ); // set Motion Magic settings m_motor . ConfigMotionCruiseVelocity ( 16384 ); // 80 rps = 16384 ticks/100ms cruise velocity m_motor . ConfigMotionAcceleration ( 32768 ); // 160 rps/s = 32768 ticks/100ms/s acceleration m_motor . ConfigMotionSCurveStrength ( 3 ); // s-curve smoothing strength of 3 // enable voltage compensation m_motor . ConfigVoltageComSaturation ( 12 ); m_motor . EnableVoltageCompensation ( true ); // periodic, run Motion Magic with slot 0 configs m_motor . SelectProfileSlot ( 0 , 0 ); // target position of 200 rotations (409600 ticks) // add 0.02 (2%) arbitrary feedforward to overcome friction m_motor . Set ( ControlMode :: MotionMagic , 409600 , DemandType :: ArbitraryFeedforward , 0.02 ); v6 Note The Motion Magic® S-Curve Strength has been replaced with jerk control in Phoenix 6. Java // class member variable final MotionMagicVoltage m_motmag = new MotionMagicVoltage ( 0 ); // robot init var talonFXConfigs = new TalonFXConfiguration (); // set slot 0 gains var slot0Configs = talonFXConfigs . Slot0Configs ; slot0Configs . kS = 0.24 ; // add 0.24 V to overcome friction slot0Configs . kV = 0.12 ; // apply 12 V for a target velocity of 100 rps // PID runs on position slot0Configs . kP = 4.8 ; slot0Configs . kI = 0 ; slot0Configs . kD = 0.1 ; // set Motion Magic settings var motionMagicConfigs = talonFXConfigs . MotionMagicConfigs ; motionMagicConfigs . MotionMagicCruiseVelocity = 80 ; // 80 rps cruise velocity motionMagicConfigs . MotionMagicAcceleration = 160 ; // 160 rps/s acceleration (0.5 seconds) motionMagicConfigs . MotionMagicJerk = 1600 ; // 1600 rps/s^2 jerk (0.1 seconds) m_talonFX . getConfigurator (). apply ( talonFXConfigs , 0.050 ); // periodic, run Motion Magic with slot 0 configs, // target position of 200 rotations m_motmag . Slot = 0 ; m_motor . setControl ( m_motmag . withPosition ( 200 )); C++ // class member variable controls :: MotionMagicVoltage m_motmag { 0 _tr }; // robot init configs :: TalonFXConfiguration talonFXConfigs {}; // set slot 0 gains auto & slot0Configs = talonFXConfigs . Slot0Configs ; slot0Configs . kS = 0.24 ; // add 0.24 V to overcome friction slot0Configs . kV = 0.12 ; // apply 12 V for a target velocity of 100 rps // PID runs on position slot0Configs . kP = 4.8 ; slot0Configs . kI = 0 ; slot0Configs . kD = 0.1 ; // set Motion Magic settings auto & motionMagicConfigs = talonFXConfigs . MotionMagicConfigs ; motionMagicConfigs . MotionMagicCruiseVelocity = 80 ; // 80 rps cruise velocity motionMagicConfigs . MotionMagicAcceleration = 160 ; // 160 rps/s acceleration (0.5 seconds) motionMagicConfigs . MotionMagicJerk = 1600 ; // 1600 rps/s^2 jerk (0.1 seconds) m_talonFX . GetConfigurator (). Apply ( talonFXConfigs , 50 _ms ); // periodic, run Motion Magic with slot 0 configs, // target position of 200 rotations m_motmag . Slot = 0 ; m_motor . SetControl ( m_motmag . WithPosition ( 200 _tr )); Motion Profiling Closed-loop control requests have been expanded to support motion profiles generated by the robot controller. Java // class member variable final PositionVoltage m_position = new PositionVoltage ( 0 ); // Trapezoid profile with max velocity 80 rps, max accel 160 rps/s final TrapezoidProfile m_profile = new TrapezoidProfile ( new TrapezoidProfile . Constraints ( 80 , 160 ) ); // Final target of 200 rot, 0 rps TrapezoidProfile . State m_goal = new TrapezoidProfile . State ( 200 , 0 ); TrapezoidProfile . State m_setpoint = new TrapezoidProfile . State (); // robot init, set slot 0 gains var slot0Configs = new Slot0Configs (); slot0Configs . kS = 0.24 ; // add 0.24 V to overcome friction slot0Configs . kV = 0.12 ; // apply 12 V for a target velocity of 100 rps slot0Configs . kP = 4.8 ; slot0Configs . kI = 0 ; slot0Configs . kD = 0.1 ; m_talonFX . getConfigurator (). apply ( Slot0Configs , 0.050 ); // periodic, update the profile setpoint for 20 ms loop time m_setpoint = m_profile . calculate ( 0.020 , m_setpoint , m_goal ); // apply the setpoint to the control request m_position . Position = m_setpoint . position ; m_position . Velocity = m_setpoint . velocity ; m_motor . setControl ( m_position ); C++ // class member variable controls :: PositionVoltage m_position { 0 _tr }; // Trapezoid profile with max velocity 80 rps, max accel 160 rps/s frc :: TrapezoidProfile < units :: turns > m_profile {{ 80 _tps , 160 _tr_per_s_sq }}; // Final target of 200 rot, 0 rps frc :: TrapezoidProfile < units :: turns >:: State m_goal { 200 _tr , 0 _tps }; frc :: TrapezoidProfile < units :: turns >:: State m_setpoint {}; // robot init, set slot 0 gains configs :: Slot0Configs slot0Configs {}; slot0Configs . kS = 0.24 ; // add 0.24 V to overcome friction slot0Configs . kV = 0.12 ; // apply 12 V for a target velocity of 100 rps slot0Configs . kP = 4.8 ; slot0Configs . kI = 0 ; slot0Configs . kD = 0.1 ; m_talonFX . GetConfigurator (). Apply ( slot0Configs , 50 _ms ); // periodic, update the profile setpoint for 20 ms loop time m_setpoint = m_profile . Calculate ( 20 _ms , m_setpoint , m_goal ); // apply the setpoint to the control request m_position . Position = m_setpoint . position ; m_position . Velocity = m_setpoint . velocity ; m_motor . SetControl ( m_position );",
+ "content_preview": "Closed-Loop Control Phoenix 6 enhances the experience of using onboard closed-loop control through the use of standardized units and a variety of control output types. Note For more information about closed-loop control in Phoenix 6, see Closed-Loop Overview ."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/api-usage/faults.html",
- "title": "Device Faults",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/wpilib-integration/motorcontroller-integration.html",
+ "title": "MotorController Integration",
"section": "API Reference",
"language": "All",
- "content": "Device Faults “Faults” are status indicators on CTR Electronics CAN devices that indicate a certain behavior or event has occurred. Faults do not directly affect the behavior of a device; instead, they indicate the device’s current status and highlight potential issues. Faults are stored in two fashions. There are “live” faults, which are reported in real-time, and “sticky” faults, which assert persistently and stay asserted until they are manually cleared (like trouble codes in a vehicle). Sticky Faults can be cleared by clicking the Clear Faults button in Phoenix Tuner X, or by calling clearStickyFaults() on the device in the robot program. A regular fault can only be cleared when the offending problem has been resolved. Using API to Retrieve Faults Faults can also be retrieved in API using the getFault_*() (regular) or getStickyFault_*() (sticky) methods on the device object. This can be useful for diagnostics or error handling. Java var faulted = m_cancoder . getFault_BadMagnet (). getValue (); if ( faulted ) { // do action when bad magnet fault is set } C++ auto faulted = m_cancoder . GetFault_BadMagnet (). GetValue (); if ( faulted ) { // do action when bad magnet fault is set } Python faulted = self . cancoder . get_fault_bad_magnet () . value if faulted : # do action when bad magnet fault is set A list of possible faults can be found in the API documentation for each device. Using API to Clear Sticky Faults Sticky faults can be cleared in API using the clearStickyFaults() method on the device objects. Additionally, individual sticky faults may be cleared using the clearStickyFault_*() APIs. Note Clearing sticky faults is a blocking operation and should not be run in a periodic loop. Java // clear the undervoltage sticky fault m_cancoder . clearStickyFault_Undervoltage (); C++ // clear the undervoltage sticky fault m_cancoder . ClearStickyFault_Undervoltage (); Python # clear the undervoltage sticky fault self . cancoder . clear_sticky_fault_undervoltage ()",
- "content_preview": "Device Faults “Faults” are status indicators on CTR Electronics CAN devices that indicate a certain behavior or event has occurred. Faults do not directly affect the behavior of a device; instead, they indicate the device’s current status and highlight potential issues."
+ "content": "MotorController Integration Phoenix 6 motor controller classes such as TalonFX ( Java , C++ , Python ) implement many APIs from the MotorController ( Java , C++ ) interface. This allows Phoenix 6 motor controllers to more easily be used in WPILib drivetrain classes such as DifferentialDrive . Java // instantiate motor controllers final TalonFX m_motorLeft = new TalonFX ( 0 ); final TalonFX m_motorRight = new TalonFX ( 1 ); // create DifferentialDrive object for robot control final DifferentialDrive m_diffDrive = new DifferentialDrive ( m_motorLeft :: set , m_motorRight :: set ); // instantiate joystick final XboxController m_driverJoy = new XboxController ( 0 ); public void teleopPeriodic () { var forward = - m_driverJoy . getLeftY (); var rot = - m_driverJoy . getRightX (); m_diffDrive . arcadeDrive ( forward , rot ); } C++ (Source) void Robot::TeleopPeriodic () { auto forward = - m_driverJoy . GetLeftY (); auto rot = - m_driverJoy . GetRightX (); m_diffDrive . ArcadeDrive ( forward , rot ); } C++ (Header) // instantiate motor controllers hardware :: TalonFX m_motorLeft { 0 }; hardware :: TalonFX m_motorRight { 1 }; // create differentialdrive object for robot control frc :: DifferentialDrive m_diffDrive { [ this ]( double output ) { m_motorLeft . Set ( output ); }, [ this ]( double output ) { m_motorRight . Set ( output ); } }; // instantiate joystick frc :: XboxController m_driverJoy { 0 }; Python def __init__ ( self ): # instantiate motor controllers self . motor_left = hardware . TalonFX ( 0 ) self . motor_right = hardware . TalonFX ( 1 ) # create DifferentialDrive object for robot control self . diff_drive = wpilib . drive . DifferentialDrive ( self . motor_left . set , self . motor_right . set ) # instantiate joystick self . driver_joy = wpilib . XboxController ( 0 ) def teleopPeriodic ( self ): forward = - self . driver_joy . getLeftY () rot = - self . driver_joy . getRightX () self . diff_drive . arcadeDrive ( forward , rot ) Motor Safety CTR Electronics supported actuators implement WPILib Motor Safety . In additional to the normal enable signal of CTR Electronics actuators, Motor Safety will automatically disable the device according to the WPILib Motor Safety implementation. Simulation It’s recommended that users set supply voltage to RobotController.getBatteryVoltage() ( Java , C++ ) to take advantage of WPILib’s BatterySim ( Java , C++ ) API. Additionally, the simulated device state is shown in the simulation Other Devices menu.",
+ "content_preview": "MotorController Integration Phoenix 6 motor controller classes such as TalonFX ( Java , C++ , Python ) implement many APIs from the MotorController ( Java , C++ ) interface. This allows Phoenix 6 motor controllers to more easily be used in WPILib drivetrain classes such as DifferentialDrive ."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/examples/quickstart.html",
- "title": "Open",
- "section": "API Reference",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-elev/calibration-and-limits.html",
+ "title": "Calibration and Limits",
+ "section": "Phoenix Tuner X",
"language": "All",
- "content": "Open-Loop Quickstart The below example showcases controlling a four-motor drivetrain. Declaring Motor Controllers The TalonFX motor controller constructor ( Java , C++ , Python ) requires a device ID (int) and an optional CAN bus (string). Note The name of the native roboRIO CAN bus is rio . This is also the default CAN bus on the roboRIO when none is specified. Java public class Robot extends TimedRobot { private static final CANBus kCANBus = new CANBus ( \"canivore\" ); private final TalonFX m_leftLeader = new TalonFX ( 0 , kCANBus ); private final TalonFX m_rightLeader = new TalonFX ( 1 , kCANBus ); private final TalonFX m_leftFollower = new TalonFX ( 2 , kCANBus ); private final TalonFX m_rightFollower = new TalonFX ( 3 , kCANBus ); } C++ (Header) class Robot : public frc :: TimedRobot { private : static constexpr ctre :: phoenix6 :: CANBus kCANBus { \"canivore\" }; ctre :: phoenix6 :: hardware :: TalonFX m_leftLeader { 0 , kCANBus }; ctre :: phoenix6 :: hardware :: TalonFX m_rightLeader { 1 , kCANBus }; ctre :: phoenix6 :: hardware :: TalonFX m_leftFollower { 2 , kCANBus }; ctre :: phoenix6 :: hardware :: TalonFX m_rightFollower { 3 , kCANBus }; } Configure Followers & Inverts In a traditional robot drivetrain, there are two motors attached to each horizontal side of the drivetrain. This setup typically (unless mechanically inverted) causes the right side to rotate in an opposite direction when given the same voltage. Java public Robot () { // start with factory-default configs var currentConfigs = new MotorOutputConfigs (); // The left motor is CCW+ currentConfigs . Inverted = InvertedValue . CounterClockwise_Positive ; m_leftLeader . getConfigurator (). apply ( currentConfigs ); // The right motor is CW+ currentConfigs . Inverted = InvertedValue . Clockwise_Positive ; m_rightLeader . getConfigurator (). apply ( currentConfigs ); // Ensure our followers are following their respective leader m_leftFollower . setControl ( new Follower ( m_leftLeader . getDeviceID (), MotorAlignmentValue . Aligned )); m_rightFollower . setControl ( new Follower ( m_rightLeader . getDeviceID (), MotorAlignmentValue . Aligned )); } C++ (Source) #include \"Robot.h\" using namespace ctre :: phoenix6 ; Robot :: Robot () { // start with factory-default configs configs :: MotorOutputConfigs currentConfigs {}; // The left motor is CCW+ currentConfigs . Inverted = signals :: InvertedValue :: CounterClockwise_Positive ; m_leftLeader . GetConfigurator (). Apply ( currentConfigs ); // The right motor is CW+ currentConfigs . Inverted = signals :: InvertedValue :: Clockwise_Positive ; m_rightLeader . GetConfigurator (). Apply ( currentConfigs ); // Ensure the followers are following their respective leader m_leftFollower . SetControl ( controls :: Follower { m_leftLeader . GetDeviceID (), false }); m_rightFollower . SetControl ( controls :: Follower { m_rightLeader . GetDeviceID (), false }); } Full Example Java public class Robot extends TimedRobot { private static final CANBus kCANBus = new CANBus ( \"canivore\" ); private final TalonFX m_leftLeader = new TalonFX ( 0 , kCANBus ); private final TalonFX m_rightLeader = new TalonFX ( 1 , kCANBus ); private final TalonFX m_leftFollower = new TalonFX ( 2 , kCANBus ); private final TalonFX m_rightFollower = new TalonFX ( 3 , kCANBus ); private final DutyCycleOut m_leftOut = new DutyCycleOut ( 0 ); private final DutyCycleOut m_rightOut = new DutyCycleOut ( 0 ); private final XboxController m_driverJoy = new XboxController ( 0 ); public Robot () { // start with factory-default configs var currentConfigs = new MotorOutputConfigs (); // The left motor is CCW+ currentConfigs . Inverted = InvertedValue . CounterClockwise_Positive ; m_leftLeader . getConfigurator (). apply ( currentConfigs ); // The right motor is CW+ currentConfigs . Inverted = InvertedValue . Clockwise_Positive ; m_rightLeader . getConfigurator (). apply ( currentConfigs ); // Ensure our followers are following their respective leader m_leftFollower . setControl ( new Follower ( m_leftLeader . getDeviceID (), MotorAlignmentValue . Aligned )); m_rightFollower . setControl ( new Follower ( m_rightLeader . getDeviceID (), MotorAlignmentValue . Aligned )); } @Override public void teleopPeriodic () { // retrieve joystick inputs var fwd = - m_driverJoy . getLeftY (); var rot = m_driverJoy . getRightX (); // modify control requests m_leftOut . Output = fwd + rot ; m_rightOut . Output = fwd - rot ; // send control requests m_leftLeader . setControl ( m_leftOut ); m_rightLeader . setControl ( m_rightOut ); } } C++ (Source) #include \"Robot.h\" using namespace ctre :: phoenix6 ; Robot :: Robot () { // start with factory-default configs configs :: MotorOutputConfigs currentConfigs {}; // The left motor is CCW+ currentConfigs . Inverted = signals :: InvertedValue :: CounterClockwise_Positive ; m_leftLeader . GetConfigurator (). Apply ( currentConfigs ); // The right motor is CW+ currentConfigs . Inverted = signals :: InvertedValue :: Clockwise_Positive ; m_rightLeader . GetConfigurator (). Apply ( currentConfigs ); // Ensure the followers are following their respective leader m_leftFollower . SetControl ( controls :: Follower { m_leftLeader . GetDeviceID (), false }); m_rightFollower . SetControl ( controls :: Follower { m_rightLeader . GetDeviceID (), false }); } void Robot :: TeleopPeriodic () { // retrieve joystick inputs auto fwd = - m_driverJoy . GetLeftY (); auto rot = m_driverJoy . GetRightX (); // modify control requests m_leftOut . Output = fwd + rot ; m_rightOut . Output = fwd - rot ; // send control requests m_leftLeader . SetControl ( m_leftOut ); m_rightLeader . SetControl ( m_rightOut ); } C++ (Header) private : static constexpr ctre :: phoenix6 :: CANBus kCANBus { \"canivore\" }; ctre :: phoenix6 :: hardware :: TalonFX m_leftLeader { 0 , kCANBus }; ctre :: phoenix6 :: hardware :: TalonFX m_rightLeader { 1 , kCANBus }; ctre :: phoenix6 :: hardware :: TalonFX m_leftFollower { 2 , kCANBus }; ctre :: phoenix6 :: hardware :: TalonFX m_rightFollower { 3 , kCANBus }; ctre :: phoenix6 :: controls :: DutyCycleOut m_leftOut { 0 }; ctre :: phoenix6 :: controls :: DutyCycleOut m_rightOut { 0 }; frc :: XboxController m_driverJoy { 0 };",
- "content_preview": "Open-Loop Quickstart The below example showcases controlling a four-motor drivetrain. Declaring Motor Controllers The TalonFX motor controller constructor ( Java , C++ , Python ) requires a device ID (int) and an optional CAN bus (string). Note The name of the native roboRIO CAN bus is rio ."
+ "content": "Calibration and Limits Tuner will have the user perform a calibration routine, during which the user will bring the elevator to it’s lowest position, and then manually raise the elevator to it’s top-most position. This routine will automatically determine the bounds of the elevator and motor inverts. To begin, select Open Wizard . This will open the Elevator Calibation popup. Manually bring the elevator to it’s bottom-most position. This will act as it’s zero. Once this is done, press Zero Elevator . Press the arrow on the bottom-right of the popup to navigate to the next step in the wizard. Bring the elevator to it’s top-most position. Once this is done, press Stop Tracking . Go ahead and exit the popup. The Calibration Results will be populated with it’s results. Homing Limits The generated Elevator subsystem includes a function for performing a current-based homing routine to zero the elevator. However, the calibration and limits interface provides the ability to configure hardware or remote limits.",
+ "content_preview": "Calibration and Limits Tuner will have the user perform a calibration routine, during which the user will bring the elevator to it’s lowest position, and then manually raise the elevator to it’s top-most position."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/api-usage/configuration.html",
- "title": "Configuration",
- "section": "API Reference",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/migration/migration-guide/index.html",
+ "title": "API Migration",
+ "section": "General",
"language": "All",
- "content": "Configuration Devices support persistent settings through the use of “configs”. Tip Configs can also be configured using Phoenix Tuner X. See Tuner Configs for more information. Configuration Objects There are device-specific Configuration classes that group configuration data of devices in a meaningful way. These classes are Passive Data Structures . One example is TalonFXConfiguration , which has subgroups of configs such as MotorOutputConfigs . The configs can be modified through public member variables of the Configuration object. The complete list of configuration objects can be found in the API documentation ( Java , C++ , Python ). Note Phoenix 6 utilizes the C++ units library and, optionally, the Java units library when applicable. Using the Java units library may increase GC overhead. Java var talonFXConfigs = new TalonFXConfiguration (); C++ configs :: TalonFXConfiguration talonFXConfigs {}; Python talonfx_configs = configs . TalonFXConfiguration () Modifying Configurations Configuration objects are mutable, so they can be saved in a member variable and reused. Additionally, configuration objects support modification using method chaining. This can be useful for constructing them as a class member variable or at compile time. In Java, this can also be used to provide unit types. Configuration and config group objects can also be cloned, making it easy to share common configs across device configurations. Java final TalonFXConfiguration commonConfigs = new TalonFXConfiguration () . withMotorOutput ( new MotorOutputConfigs () . withNeutralMode ( NeutralModeValue . Brake ) ) . withCurrentLimits ( new CurrentLimitsConfigs () . withStatorCurrentLimit ( Amps . of ( 120 )) . withStatorCurrentLimitEnable ( true ) ); /* create a copy with a different invert */ final TalonFXConfiguration leaderConfigs = commonConfigs . clone () . withMotorOutput ( commonConfigs . MotorOutput . clone () . withInverted ( InvertedValue . Clockwise_Positive ) ); C++ static constexpr configs :: TalonFXConfiguration commonConfigs = configs :: TalonFXConfiguration {} . WithMotorOutput ( configs :: MotorOutputConfigs {} . WithNeutralMode ( signals :: NeutralModeValue :: Brake ) ) . WithCurrentLimits ( configs :: CurrentLimitsConfigs {} . WithStatorCurrentLimit ( 120 _A ) . WithStatorCurrentLimitEnable ( true ) ); /* create a copy with a different invert */ configs :: TalonFXConfiguration leaderConfigs = configs :: TalonFXConfiguration { commonConfigs } . WithMotorOutput ( configs :: MotorOutputConfigs { commonConfigs . MotorOutput } . WithInverted ( signals :: InvertedValue :: Clockwise_Positive ) ); Python self . _common_configs = ( configs . TalonFXConfiguration () . with_motor_output ( configs . MotorOutputConfigs () . with_neutral_mode ( signals . NeutralModeValue . BRAKE ) ) . with_current_limits ( configs . CurrentLimitsConfigs () . with_stator_current_limit ( 120.0 ) . with_stator_current_limit_enable ( True ) ) ) # create a copy with a different invert self . _leader_configs = ( copy . deepcopy ( self . _common_configs ) . with_motor_output ( copy . deepcopy ( self . _common_configs . motor_output ) . with_inverted ( signals . InvertedValue . CLOCKWISE_POSITIVE ) ) ) Future Proofing Configs There is a corner case with configs where the device may have firmware with newer configs that didn’t exist when the version of the API was built. To account for this problem, device Configuration objects have a FutureProofConfigs ( Java , C++ , Python ) field. Configurator API Device objects have a getConfigurator() method that returns a device-specific Configurator object. The Configurator is used to retrieve, apply, and factory default the configs of a device. Note The getConfigurator() routine can be called frequently without any performance implications. The device-specific configurators have type-specific overloads that allow for the widest variety of device-compatible configs. As a result, the caller can pass the entire device Configuration object or just the relevant subgroup of configs to the Configurator API. Java var talonFXConfigurator = m_talonFX . getConfigurator (); C++ auto & talonFXConfigurator = m_talonFX . GetConfigurator (); Python talonfx_configurator = self . talonfx . configurator Reading Configs To read configs stored in a device, use the refresh() method to update a Configuration object. The example below demonstrates retrieving a full TalonFXConfiguration ( Java , C++ , Python ) object from a TalonFX device. Warning refresh() is a blocking API call that waits on the device to respond. Calling refresh() periodically may slow down the execution time of the periodic function, as it will always wait up to DefaultTimeoutSeconds ( Java , C++ , Python ) for the response when no timeout parameter is specified. Java var talonFXConfigurator = m_talonFX . getConfigurator (); var talonFXConfigs = new TalonFXConfiguration (); // optional timeout (in seconds) as a second optional parameter talonFXConfigurator . refresh ( talonFXConfigs ); C++ auto & talonFXConfigurator = m_talonFX . GetConfigurator (); configs :: TalonFXConfiguration talonFXConfigs {}; // optional timeout (in seconds) as a second optional parameter talonFXConfigurator . Refresh ( talonFXConfigs ); Python talonfx_configurator = self . talonfx . configurator talonfx_configs = configs . TalonFXConfiguration () # optional timeout (in seconds) as a second optional parameter talonfx_configurator . refresh ( talonfx_configs ) Applying Configs Configs can be applied to a device by calling apply() on the Configurator with a Configuration object. Warning apply() is a blocking API call that waits on the device to respond. Calling apply() periodically may slow down the execution time of the periodic function, as it will always wait up to DefaultTimeoutSeconds ( Java , C++ , Python ) for the response when no timeout parameter is specified. Java var talonFXConfigurator = m_talonFX . getConfigurator (); var motorConfigs = new MotorOutputConfigs (); // set invert to CW+ and apply config change motorConfigs . Inverted = InvertedValue . Clockwise_Positive ; talonFXConfigurator . apply ( motorConfigs ); C++ auto & talonFXConfigurator = m_talonFX . GetConfigurator (); configs :: MotorOutputConfigs motorConfigs {}; // set invert to CW+ and apply config change motorConfigs . Inverted = signals :: InvertedValue :: Clockwise_Positive ; talonFXConfigurator . Apply ( motorConfigs ); Python talonfx_configurator = self . talonfx . configurator motor_configs = configs . MotorOutputConfigs () # set invert to CW+ and apply config change motor_configs . inverted = signals . InvertValue . CLOCKWISE_POSITIVE talonfx_configurator . apply ( motor_configs ) Tip To modify a single configuration value without affecting the other configs, users can call refresh() after constructing the config object, or users can cache the config object and reuse it for future calls to apply() . Factory Default A newly-created Configuration object contains the default configuration values of a device. As a result, it is unnecessary to factory default a device before applying a modified device Configuration object. A device’s configs can be explicitly restored to the factory defaults by passing a newly-created Configuration object to the device Configurator . Java m_talonFX . getConfigurator (). apply ( new TalonFXConfiguration ()); C++ m_talonFX . GetConfigurator (). Apply ( configs :: TalonFXConfiguration {}); Python self . talonfx . configurator . apply ( configs . TalonFXConfiguration ())",
- "content_preview": "Configuration Devices support persistent settings through the use of “configs”. Tip Configs can also be configured using Phoenix Tuner X. See Tuner Configs for more information."
+ "content": "API Migration This section serves as a “cheat sheet” of commonly-used functions in Phoenix 5 and their equivalents in Phoenix 6. API Structure General structure of the Phoenix 6 namespaces and packages Configuration Configuring device configs in robot code Status Signals Using status signals to retrieve sensor data from devices Control Requests Using control requests to control the functionality of actuators, such as the TalonFX Closed-Loop Control Configuring and using closed-loop control requests Feature Replacements Other features replaced or improved upon in Phoenix 6",
+ "content_preview": "API Migration This section serves as a “cheat sheet” of commonly-used functions in Phoenix 5 and their equivalents in Phoenix 6. API Structure General structure of the Phoenix 6 namespaces and packages Configuration Configuring device configs in robot code Status Signals Using status signals to..."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/index.html",
- "title": "Phoenix Tuner X",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/canivore/canivore-hardware-attached.html",
+ "title": "Hardware",
+ "section": "CANivore",
+ "language": "All",
+ "content": "Hardware-Attached Simulation CANivore supports hardware-attached simulation when used in an FRC robot program . This allows a CANivore to be used with real devices on supported host operating systems. The below video showcases controlling a real Falcon 500 in a robot program using hardware-attached simulation. To utilize hardware-attached simulation, ensure the CANivore is connected directly via USB to the machine running the simulation. All devices on the CANivore CAN Bus should be independently powered, as the CANivore does not provide power. In the robot program, the CANivore name or * must be specified in the device constructor. Important Any motors/actuators that have been connected to a roboRIO CAN Bus at any time must be factory defaulted due to them being FRC Locked . Factory defaulting can be done in Tuner X and should be done when the CANivore is not connected to a roboRIO. Java CANBus kCANBus = new CANbus ( \"mycanivore\" ); TalonFX m_motor = new TalonFX ( 0 , kCANBus ); C++ static constexpr ctre :: phoenix6 :: CANBus kCANBus { \"mycanivore\" }; ctre :: phoenix6 :: hardware :: TalonFX m_motor { 0 , kCANBus }; Python self . canbus = CANBus ( \"mycanivore\" ) self . motor = hardware . TalonFX ( 0 , self . canbus ) Java/C++ In VS Code, select the 3 dots in the top-right, then select Hardware Sim Robot Code A message in the console should appear that the CAN Bus is connected. ********** Robot program startup complete ********** [phoenix] CANbus Connected: uno (WinUSB, 2B189E633353385320202034383803FF) [phoenix] CANbus Network Up: uno (WinUSB, 2B189E633353385320202034383803FF) [phoenix] Library initialization is complete. Python Simulation can be started using python -m robotpy sim Users may notice the robot program is using simulated devices by default. This is the default behavior if the host platform supports simulation (see requirements for a full list of supported platforms). In order for the robot program to communicate with physical devices (on platforms that support both simulation and hardware), the CTR_TARGET environment variable must be set. Examples of this are shown below. Windows $env:CTR_TARGET = \"Hardware\" # Set the environment variable, which will persist for the duration of this powershell instance. Linux export CTR_TARGET = Hardware # Export the environment variable so it's persistent in the shell Or CTR_TARGET = Hardware python3 application.py # Set the environment variable only for the python call",
+ "content_preview": "Hardware-Attached Simulation CANivore supports hardware-attached simulation when used in an FRC robot program . This allows a CANivore to be used with real devices on supported host operating systems."
+ },
+ {
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/profile.html",
+ "title": "Profile Page",
"section": "Phoenix Tuner X",
"language": "All",
- "content": "Phoenix Tuner X What is Phoenix Tuner X? Phoenix Tuner X is the companion application allowing you to update, configure, analyze, and control your devices. Phoenix Tuner X can be installed from the Microsoft Store , the Google Play Store , and the Apple App Store . Phoenix Tuner X supports Windows 10 (1903+), Windows 11, Android (8.0+), macOS (12.0+), and iOS (15.0+). Tip Many UI elements contain hover tooltips. That means the user can hover over them with their mouse for a text explanation of what they do. Connecting Tuner Device List Tuner History Device Details Tuner Configs Self Test Snapshot Controlling Devices Plotting Multi-device Plot & Control Profile Page Pigeon 2.0 Calibration Tools Swerve Project Generator Tuner Elevator Generator",
- "content_preview": "Phoenix Tuner X What is Phoenix Tuner X? Phoenix Tuner X is the companion application allowing you to update, configure, analyze, and control your devices. Phoenix Tuner X can be installed from the Microsoft Store , the Google Play Store , and the Apple App Store ."
+ "content": "Profile Page The profile page gets the user access to account management and is where team numbers are assigned to a season pass . This can be accessed by clicking the profile icon in the left-side menu. Users can use this page to see how many seats they’ve redeemed for their season pass, how many licenses are available, how many licenses have been redeemed, and much more. This is also where the user can manually log out of their account, by clicking LOGOUT in the top-right corner.",
+ "content_preview": "Profile Page The profile page gets the user access to account management and is where team numbers are assigned to a season pass . This can be accessed by clicking the profile icon in the left-side menu."
+ },
+ {
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/hardware-reference/candle/index.html",
+ "title": "CANdle®",
+ "section": "General",
+ "language": "All",
+ "content": "CANdle® The CTR Electronics’ CANdle® branded device makes it easy to control individually addressable LEDs over CAN. Combined with being a 5V high-efficiency DC voltage regulator, the CANdle® is a versatile addition to any robot. Store Page CAD and purchase instructions. https://store.ctr-electronics.com/products/candle Status Light Reference Important If the status lights do not exactly match any of the blink codes below, the device may be alternating between multiple blink codes (most commonly between good and bad CAN). Blink Codes Animation (Click to play) LED State Cause Possible Fix LEDs Off No power, or the StatusLedWhenActive config is set to Disabled. If you cannot communicate with the CANdle® and all LEDs are off, validate 12V on the Red/Black (+Vin/-Vin) leads. Blinking Red CANdle® does not have valid CAN or Pixel pulse train [1]. Ensure good connections between CANH and CANL (Yellow and Green) and the CAN bus or Pixel pulse train [1], and robot controller is on. Blinking Orange CANdle® has a good CAN or Pixel pulse train [1] connection but is not being controlled. If the robot program is trying to control the CANdle®, ensure good connection between the controller and this device. Blinking Green CANdle® has a good CAN or Pixel pulse train [1] connection and is being actively controlled. Rapid Red 5V too high fault. Check for a short between +Vout and 5V out. Blip Red Short circuit or software fuse fault. Use Tuner X to determine which fault is active. Check for shorts across the output leads, and reduce the current load on the CANdle® (max 6 A). Blip Orange Thermal fault. Allow CANdle® to cool. Consider disabling the onboard LEDs or reducing the current load on the CANdle®. Alternate Red/Orange Damaged Hardware. Use Tuner X Self Test to confirm the LEDs and that the hardware fault is set, then contact CTRE Alternate Orange/Green CANdle® in bootloader. Field-upgrade device in Tuner X. [1] CANdle can be directly controlled using a WS2812B-compliant pulse train from other devices, such as an Arduino. See the User’s Guide for more information.",
+ "content_preview": "CANdle® The CTR Electronics’ CANdle® branded device makes it easy to control individually addressable LEDs over CAN. Combined with being a 5V high-efficiency DC voltage regulator, the CANdle® is a versatile addition to any robot. Store Page CAD and purchase instructions."
+ },
+ {
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-elev/tuning.html",
+ "title": "Tuning your Elevator",
+ "section": "Phoenix Tuner X",
+ "language": "All",
+ "content": "Tuning your Elevator In the third step of the elevator wizard, the user is guided through creating setpoints and calculating closed-loop gains. Tip Check out Basic PID and Profiling for information on how to tune a closed-loop position system. Elevator Setpoints Setpoints are configured in the Setpoints tab in the second column. Add a new setpoint Rename a setpoint Ensure setpoint names are unique, or there will be compile errors when you generate your Elevator. Run setpoint The robot must be ENABLED , or nothing will happen. Delete setpoint Closed-loop Gains Gains can be configured in the first column. While default gains have been calculated, it is highly recommended to just use this as a starting point. The elevator should be tuned in it’s final configuration, with any load that it may need to bear (holding a game piece). The control request used to command the elevator is MotionMagicVoltage ( Java , C++ , Python ), which allows the user to directly control velocity and acceleration for smooth travel.",
+ "content_preview": "Tuning your Elevator In the third step of the elevator wizard, the user is guided through creating setpoints and calculating closed-loop gains. Tip Check out Basic PID and Profiling for information on how to tune a closed-loop position system."
+ },
+ {
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/api-usage/index.html",
+ "title": "General API Usage",
+ "section": "API Reference",
+ "language": "All",
+ "content": "General API Usage This section serves to provide general API usage for the Phoenix 6 API. For full details, please visit the API docs ( Java , C++ , Python ). Important While Phoenix 6 and Phoenix 5 devices may exist on the same CAN bus and same robot project, each robot project must use the API tied to the device firmware version. This means Phoenix 5 devices must use the Phoenix 5 API, and Phoenix 6 devices must use the Phoenix 6 API. There are three major components to the Phoenix 6 API: Configs Configs represent a persistent configuration for a device. For example, closed-loop gains. Configuration Control Requests Control Requests represent the output of a device, typically a motor controller. Control Requests Signals Signals represent data retrieved from a device. This can be velocity, position, yaw, pitch, roll, temperature, etc. Status Signals TalonFX Quickstart Quickstart on controlling a TalonFX with open loop control requests and a Joystick. Open-Loop Control API Overview Details a high level overview of what makes up the Phoenix 6 API. Configuration Describes configuring device configs via code. Control Requests Highlights using control requests to control the output of actuators such as the TalonFX. Status Signals Details using status signals to retrieve sensor data from devices. Signal Logging Information on the signal logging API used for capturing signal traffic on the bus. Hoot Replay Highlights playing back captured signals from a hoot log to test robot program changes. Device Faults Documents how faults are used to indicate device hardware status. Enabling Actuators Information on the FRC Lock safety feature and enabling actuators. Actuator Limits Documents how to retrieve and configure software and hardware actuator limits. Orchestra Information on playing music and sounds using the Orchestra API.",
+ "content_preview": "General API Usage This section serves to provide general API usage for the Phoenix 6 API. For full details, please visit the API docs ( Java , C++ , Python )."
},
{
"url": "https://v6.docs.ctr-electronics.com/en/stable/docs/hardware-reference/cancoder/index.html",
@@ -612,92 +652,76 @@
"content_preview": "CANcoder The CANcoder is the next evolution in the line of CTRE magnetic encoder products. As its name implies, this product is a rotary magnetic encoder that communicates over the CAN bus."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/device-specific/index.html",
- "title": "Device API",
- "section": "API Reference",
- "language": "All",
- "content": "Device API This section is intended to highlight any device-specific API functionality. This include features such as the TalonFX + CANcoder fusion, details on using TalonFX Control Requests , and more. TalonFX Introduction to TalonFX Control Open-Loop Control Closed-Loop Overview Basic PID and Profiling Motion Magic® Controls TalonFX Remote Sensors",
- "content_preview": "Device API This section is intended to highlight any device-specific API functionality. This include features such as the TalonFX + CANcoder fusion, details on using TalonFX Control Requests , and more."
- },
- {
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/troubleshooting/canbus-troubleshooting.html",
- "title": "CAN Bus Troubleshooting",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/hardware-reference/canrange/index.html",
+ "title": "CANrange",
"section": "General",
"language": "All",
- "content": "CAN Bus Troubleshooting There are typically two failure modes that must be resolved: There are same-model devices on the bus with the same device ID (devices have a default device ID of ‘0’). CAN bus is not wired correctly or robustly During hardware validation, you will likely have to isolate each device to assign a unique device ID. Note CTRE software has the ability to resolve device ID conflicts without device isolation, and CAN bus is capable of reporting the health of the CAN bus (see Driver Station lightening tab). However, the problem is when both root-causes are occurring at the same time, this can confuse students who have no experience with CAN bus systems. Note Many teams will pre-assign and update devices (Talon SRXs for example) long before the robot takes form. This is also a great task for new students who need to start learning the control system (with the appropriate mentor oversight to ensure hardware does not get damaged). Identifying Duplicate IDs Tip Label the devices appropriately so there is no guessing which device ID is what. Don’t have a label maker? Use tape and/or Sharpie (sharpie marks can be removed with alcohol). Phoenix Tuner X will report when there are multiple devices of the same model with the same ID. This is shown when the device card is RED and there is a message in the middle of the device card. Users seeing this should iteratively configure IDs on the device(s). Check your wiring Specific wiring instructions can be found in the user manual of each product, but there are common steps that must be followed for all devices: If connectors are used for CAN bus, tug-test each individual crimped wire one at a time. Bad crimps/connection points are the most common cause of intermittent connection issues. Confirm red and black are not flipped. Confirm battery voltage is adequate (through Driver Station or through voltmeter). Manually inspect and confirm that green-connects-to-green and yellow-connects-to-yellow at every connection point. Flipping/mixing green and yellow is a common failure point during hardware bring up . Confirm breakers are installed in the PDP where appropriate. Measure resistance between CANH and CANL when system is not powered (should measure ~60Ω). If the measurement is 120Ω, then confirm both RIO and PDP are in circuit, and PDP jumper is in the correct location. LEDs are red - now what? We need to rule out same-ID versus bad-bus-wiring. There are two approaches: Approach 1 will help troubleshoot bad wiring and common IDs. Approach 2 will only be effective in troubleshooting common IDs, but this method is noteworthy because it is simple/quick (no wiring changes, just pull breakers). The specific instructions for changing device ID are in the next section. Review this if needed. Approach 1 (best) Physically connect CAN bus from roboRIO to one device only. Circumvent your wiring if need be. Power boot robot/bench setup. Open Phoenix Tuner X and wait for connection (roboRIO may take ~30 seconds to boot) Open the Devices page Confirm that CAN device appears Use Tuner X to change the device ID Label the new ID on the physical device Repeat this procedure for every device, one at a time If you find a particular device where communication is not possible, scrutinize device’s power and CAN connection to the system. Make the test setup so simple that the only failure mode possible is within the device itself. Note Typically, there must be two 120- \\(\\Omega\\) termination resistors at each end of the bus. CTR Electronics integrates termination resistors into the PDP and the CANivore. The roboRIO also has an integrated termination resistor. During bring-up, if you keep your harness short (such as the CAN pigtail leads from a single TalonFX) then a single resistor is adequate for testing purposes. Approach 2 (easier) Leave CAN bus wiring as is Pull breakers and PCM fuse from PDP Disconnect CAN bus pigtail from PDP Pick the first device to power up and restore breaker/fuse/pigtail so that only this CAN device is powered Power boot robot/bench setup Open Phoenix Tuner X and wait for connection (roboRIO may take ~30 seconds to boot) Open the Devices page Confirm that CAN device appears If device does not appear, scrutinize device’s power and CAN connection to the system Use Tuner X to change the device ID Label the new ID on the physical device Repeat this procedure for every device If you find a particular device or section of devices where communication is not possible, then the CAN bus wiring needs to be re-inspected. Remember to “flick” / “shake” / “jostle” the CAN wiring in various sections to attempt to reproduce red LED blips. This is a sure sign of loose contact points. If you are able to detect and change device ID on your devices individually, begin piecing your CAN bus together. Start with either roboRIO <—-> device <—> PDP, or CANivore <—-> device <—> 120 \\(\\Omega\\) resistor, to ensure termination exists at both ends. Then introduce the remaining devices until a failure is observed or until all devices are in-circuit. If introducing a new device creates a failure symptom, scrutinize that device by replacing it, inspecting common wires, and inspecting power. At the end of this section, all devices should appear (notwithstanding the above notes) and device LEDs should not be red. TalonFX and Pigeon2 typically blink orange when they are healthy and not controlled, and CANcoder rapid-blinks brightly. PDP may be orange or green depending on its sticky faults.",
- "content_preview": "CAN Bus Troubleshooting There are typically two failure modes that must be resolved: There are same-model devices on the bus with the same device ID (devices have a default device ID of ‘0’)."
+ "content": "CANrange CANrange is a CAN-enabled Time-of-Flight distance measurement sensor. This product uses laser measurements to calculate precise distance to a surface parallel to the sensor. Users can also configure the CANrange to act as a limit switch or beam break sensor in the Device Configs . Store Page CAD and purchase instructions. https://store.ctr-electronics.com/products/canrange Hardware User Manual https://ctre.download/files/user-manual/CANrange%20User’s%20Guide.pdf Status Light Reference Important If the status lights do not exactly match any of the blink codes below, the device may be alternating between multiple blink codes (most commonly between good and bad CAN). Blink Codes Animation (Click to play) LED State Cause Possible Fix LEDs Off No Power Provide 12V to V+ and V- inputs. Blinking Alternating Red CANrange does not have valid CAN. Ensure good connections between CANH and CANL (Yellow and Green) & robot controller is on. Blinking Alternating Orange CANrange has a good CAN connection. Measured distance is not within detection threshold. Blinking Alternating Green CANrange has a good CAN connection. Measured distance is within detection threshold. Alternate Red/Orange Damaged Hardware. Use Tuner X Self Test to confirm the LEDs and that the hardware fault is set, then contact CTRE Single LED alternates Green/Orange CANrange in bootloader. Field-upgrade device in Tuner X. The rate at which the LED is blinking can be used as a rough indicator of measured distance. For example, the below LED shows that the detected distance is close to the CANrange. Animation (Click to play)",
+ "content_preview": "CANrange CANrange is a CAN-enabled Time-of-Flight distance measurement sensor. This product uses laser measurements to calculate precise distance to a surface parallel to the sensor. Users can also configure the CANrange to act as a limit switch or beam break sensor in the Device Configs ."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/licensing/licensing.html",
- "title": "Device Licensing",
- "section": "General",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/device-specific/talonfx/motion-magic.html",
+ "title": "Motion Magic® Controls",
+ "section": "TalonFX",
"language": "All",
- "content": "Device Licensing Note Users utilizing season pass must attach a team number before continuing. See Attaching a Team Number to Season Pass for more information. All Phoenix 6 supported devices support device licensing. Additionally, CANivore is supported for licensing. When a CANivore is licensed, all devices on that bus are Pro enabled without additional activation. Important All license activation and verification features are only available in Phoenix Tuner X . Phoenix Tuner v1 does not support licensing actions. Purchasing a License Licenses can be purchased in the licensing section on the CTR Electronics store. Click here to purchase a license. Once a license has been purchased, you will receive an email confirmation confirming your purchase. Once this email is received, the license should be visible in the list of licenses in Tuner X. Activating a License Licenses are activated by first clicking on the LIC icon in the bottom right corner of the device card. This will open up a screen which displays a list of currently attached licenses for that device. Click on the Activate a new license button on the bottom of the popup. A list of purchased (but unattached) license seats are shown here. Click on the license you would like to redeem and press the Activate Selected License button to confirm redemption of that seat. Warning Users should be aware that license activation is permanent and irreversible Once the activation is complete, the license will be downloaded to the device. In the event that Tuner X disconnects from the internet or from the robot before this completes, the license is still activated and available for download the next time Tuner X is connected to the internet/robot. Batch Activating Licenses Tuner X also supports batch activating licenses from the Devices page. The user can either select devices by their checkbox (in the top right corner of their respective card) or by selecting the checkmark icon in the top right. Tip Selecting a device using their checkbox and clicking the checkmark in the top right will select all devices of the same models Step 1 in the above image selects all devices of the same models selected (or all devices if no device is currently check-boxed). Step 2 in the above image opens the batch licensing dialog. Once the dialog is opened, select a license from the dropdown at the top of the popup. The first list contains devices that will be batch licensed, while the second list contains devices that are ineligible due to one of the following: Device is not running Phoenix 6 firmware that supports licensing Device does not support the selected license Device is already licensed with the selected license The License devices button at the bottom of the popup shows the number of device licenses that will be applied and the number of seats currently available. After confirming that everything looks correct, press the License devices button to apply the licenses. Activating a License without a Robot Devices that have been seen by Tuner X at least once will be available in Device History . This can be useful for licensing a device when disconnected from the robot. Verifying Activation State An icon displaying the license state of your device is located in the bottom right of the device card. The below table can be used to determine your device license state for troubleshooting. State Image Description Licensed Device is licensed for the current version of the Phoenix 6 API. CANivore contains Licenses CANivore contains at least one bus license, which it will use to remote-license all compliant CAN devices. Pro Licensing Error Device is licensed and there was an error communicating license state. Licensing Error Device is not licensed and there was an error communicating license state. Not Licensed Device is not licensed for this version of the Phoenix 6 API. Licensing Not Supported Icon not present Device does not support licensing or is using an incompatible firmware for device licensing. Additionally, users can perform a Self Test to verify that the device has a valid license. Troubleshooting Did you activate a license for this device? Clicking on the icon will show licenses that are attached to this device Is the latest diagnostic server running? Check the version at the bottom of Tuner X’s devices page. Latest version details can be found in the changelog under the latest Phoenix-6/Libs version. Confirm the vendordep in your robot project is the latest version. Alternatively, you can deploy the temporary diagnostic server . Is the latest Phoenix 6 firmware flashed onto the device? FRC Only : If using Season Pass, is the roboRIO configured with the correct team number ?",
- "content_preview": "Device Licensing Note Users utilizing season pass must attach a team number before continuing. See Attaching a Team Number to Season Pass for more information. All Phoenix 6 supported devices support device licensing. Additionally, CANivore is supported for licensing."
+ "content": "Motion Magic® Controls In addition to basic PID control, the Talon FX also supports onboard motion profiling using Motion Magic® controls. Note For more information on feedback and feedforward gains, see Closed-Loop Overview . Motion Magic® Motion Magic® is a control mode that provides the benefit of Motion Profiling without needing to generate motion profile trajectory points. When using Motion Magic®, the motor will move to a target position using a motion profile, while honoring the user specified acceleration, maximum velocity (cruise velocity), and optional jerk. The benefits of this control mode over “simple” PID position closed-looping are: Control of the mechanism throughout the entire motion (as opposed to racing to the end target position) Control of the mechanism’s inertia to ensure smooth transitions between setpoints Improved repeatability despite changes in battery load Improved repeatability despite changes in motor load After gain/settings are determined, the robot controller only needs to periodically set the target position. There is no general requirement to “wait for the profile to finish”. However, the robot application can poll the sensor position and determine when the motion is finished if need be. Motion Magic® functions by generating a trapezoidal/S-Curve velocity profile that does not exceed the specified cruise velocity, acceleration, or jerk. This is done automatically by the motor controller. Note If the remaining sensor distance to travel is small, the velocity may not reach cruise velocity as this would overshoot the target position. This is often referred to as a “triangle profile”. If the Motion Magic® jerk is set to a nonzero value, the generated velocity profile is no longer trapezoidal, but instead is a continuous S-Curve (corner points are smoothed). An S-Curve profile has the following advantaged over a trapezoidal profile: Reducing oscillation of the mechanism. Maneuver is more deliberate and reproducible. Note The jerk control feature, by its nature, will increase the amount of time a movement requires. This can be compensated for by increasing the configured acceleration value. The following parameters must be set when controlling using Motion Magic® Cruise Velocity - peak/cruising velocity of the motion Acceleration - controls acceleration and deceleration rates during the beginning and end of motion Jerk (optional) - controls jerk, which is the derivative of acceleration Using Motion Magic® in API Motion Magic® is currently supported for all base control output types . The units of the output are determined by the control output type. The Motion Magic® jerk, acceleration, and cruise velocity can be configured in code using a MotionMagicConfigs ( Java , C++ , Python ) object. In Motion Magic®, the gains should be configured as follows: \\(K_g\\) - output to overcome gravity (output) \\(K_s\\) - output to overcome static friction (output) \\(K_v\\) - output per unit of target velocity (output/rps) \\(K_a\\) - output per unit of target acceleration (output/(rps/s)) \\(K_p\\) - output per unit of error in position (output/rotation) \\(K_i\\) - output per unit of integrated error in position (output/(rotation*s)) \\(K_d\\) - output per unit of error in velocity (output/rps) Java // in init function var talonFXConfigs = new TalonFXConfiguration (); // set slot 0 gains var slot0Configs = talonFXConfigs . Slot0 ; slot0Configs . kS = 0.25 ; // Add 0.25 V output to overcome static friction slot0Configs . kV = 0.12 ; // A velocity target of 1 rps results in 0.12 V output slot0Configs . kA = 0.01 ; // An acceleration of 1 rps/s requires 0.01 V output slot0Configs . kP = 4.8 ; // A position error of 2.5 rotations results in 12 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0.1 ; // A velocity error of 1 rps results in 0.1 V output // set Motion Magic settings var motionMagicConfigs = talonFXConfigs . MotionMagic ; motionMagicConfigs . MotionMagicCruiseVelocity = 80 ; // Target cruise velocity of 80 rps motionMagicConfigs . MotionMagicAcceleration = 160 ; // Target acceleration of 160 rps/s (0.5 seconds) motionMagicConfigs . MotionMagicJerk = 1600 ; // Target jerk of 1600 rps/s/s (0.1 seconds) m_talonFX . getConfigurator (). apply ( talonFXConfigs ); C++ // in init function configs :: TalonFXConfiguration talonFXConfigs {}; // set slot 0 gains auto & slot0Configs = talonFXConfigs . Slot0 ; slot0Configs . kS = 0.25 ; // Add 0.25 V output to overcome static friction slot0Configs . kV = 0.12 ; // A velocity target of 1 rps results in 0.12 V output slot0Configs . kA = 0.01 ; // An acceleration of 1 rps/s requires 0.01 V output slot0Configs . kP = 4.8 ; // A position error of 2.5 rotations results in 12 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0.1 ; // A velocity error of 1 rps results in 0.1 V output // set Motion Magic settings auto & motionMagicConfigs = talonFXConfigs . MotionMagic ; motionMagicConfigs . MotionMagicCruiseVelocity = 80 ; // Target cruise velocity of 80 rps motionMagicConfigs . MotionMagicAcceleration = 160 ; // Target acceleration of 160 rps/s (0.5 seconds) motionMagicConfigs . MotionMagicJerk = 1600 ; // Target jerk of 1600 rps/s/s (0.1 seconds) m_talonFX . GetConfigurator (). Apply ( talonFXConfigs ); Python # in init function talonfx_configs = configs . TalonFXConfiguration () # set slot 0 gains slot0_configs = talonfx_configs . slot0 slot0_configs . k_s = 0.25 # Add 0.25 V output to overcome static friction slot0_configs . k_v = 0.12 # A velocity target of 1 rps results in 0.12 V output slot0_configs . k_a = 0.01 # An acceleration of 1 rps/s requires 0.01 V output slot0_configs . k_p = 4.8 # A position error of 2.5 rotations results in 12 V output slot0_configs . k_i = 0 # no output for integrated error slot0_configs . k_d = 0.1 # A velocity error of 1 rps results in 0.1 V output # set Motion Magic settings motion_magic_configs = talonfx_configs . motion_magic motion_magic_configs . motion_magic_cruise_velocity = 80 # Target cruise velocity of 80 rps motion_magic_configs . motion_magic_acceleration = 160 # Target acceleration of 160 rps/s (0.5 seconds) motion_magic_configs . motion_magic_jerk = 1600 # Target jerk of 1600 rps/s/s (0.1 seconds) self . talonfx . configurator . apply ( talonfx_configs ) Tip Motion Magic® supports modifying cruise velocity, acceleration, and jerk on the fly (requires firmware version 24.0.6.0 or newer). Once the gains are configured, the Motion Magic® request can be sent to the TalonFX. The control request object has an optional feedforward term that can be used to add an arbitrary value to the output, which can be useful to account for the effects of gravity. Java // create a Motion Magic request, voltage output final MotionMagicVoltage m_request = new MotionMagicVoltage ( 0 ); // set target position to 100 rotations m_talonFX . setControl ( m_request . withPosition ( 100 )); C++ // create a Motion Magic request, voltage output controls :: MotionMagicVoltage m_request { 0 _tr }; // set target position to 100 rotations m_talonFX . SetControl ( m_request . WithPosition ( 100 _tr )); Python # create a Motion Magic request, voltage output self . request = controls . MotionMagicVoltage ( 0 ) # set target position to 100 rotations self . talonfx . set_control ( self . request . with_position ( 100 )) Dynamic Motion Magic® Important This feature requires the device to be Pro licensed and on a CANivore . When unlicensed, the TalonFX will disable control output and trip the UnlicensedFeatureInUse fault. When using a Pro-licensed Talon FX connected to a CANivore, Dynamic Motion Magic® can be used, allowing for the cruise velocity, acceleration, and jerk to be modified directly in the control request during motion. This can be used to set up different values for acceleration vs deceleration or to speed up and slow down the profile on the fly. The gain slots are configured in the same way as a regular Motion Magic® request. However, the cruise velocity, acceleration, and jerk parameters are set up in the control request, not the Motion Magic® config group. Once the gains are configured, the Dynamic Motion Magic® request can be sent to the TalonFX. The control request object has an optional feedforward term that can be used to add an arbitrary value to the output, which can be useful to account for the effects of gravity. Java // create a Dynamic Motion Magic request, voltage output // default velocity of 80 rps, acceleration of 400 rot/s^2, and jerk of 4000 rot/s^3 final DynamicMotionMagicVoltage m_request = new DynamicMotionMagicVoltage ( 0 , 80 , 400 ). withJerk ( 4000 ); if ( m_joy . getAButton ()) { // while the joystick A button is held, use a slower profile m_request . Velocity = 40 ; // rps m_request . Acceleration = 80 ; // rot/s^2 m_request . Jerk = 400 ; // rot/s^3 } else { // otherwise use a faster profile m_request . Velocity = 80 ; // rps m_request . Acceleration = 400 ; // rot/s^2 m_request . Jerk = 4000 ; // rot/s^3 } // set target position to 100 rotations m_talonFX . setControl ( m_request . withPosition ( 100 )); C++ // create a Dynamic Motion Magic request, voltage output // default velocity of 80 rps, acceleration of 400 rot/s^2, and jerk of 4000 rot/s^3 controls :: DynamicMotionMagicVoltage m_request = controls :: DynamicMotionMagicVoltage { 0 _tr , 80 _tps , 400 _tr_per_s_sq } . WithJerk ( 4000 _tr_per_s_cu ); if ( m_joy . GetAButton ()) { // while the joystick A button is held, use a slower profile m_request . Velocity = 40 _tps ; m_request . Acceleration = 80 _tr_per_s_sq ; m_request . Jerk = 400 _tr_per_s_cu ; } else { // otherwise use a faster profile m_request . Velocity = 80 _tps ; m_request . Acceleration = 400 _tr_per_s_sq ; m_request . Jerk = 4000 _tr_per_s_cu ; } // set target position to 100 rotations m_talonFX . SetControl ( m_request . WithPosition ( 100 _tr )); Python # create a Dynamic Motion Magic request, voltage output # default velocity of 80 rps, acceleration of 400 rot/s^2, and jerk of 4000 rot/s^3 self . request = controls . DynamicMotionMagicVoltage ( 0 , 80 , 400 , 4000 ) if self . joy . getAButton (): # while the joystick A button is held, use a slower profile self . request . velocity = 40 # rps self . request . acceleration = 80 # rot/s^2 self . request . jerk = 400 # rot/s^3 else : # otherwise use a faster profile self . request . velocity = 80 # rps self . request . acceleration = 400 # rot/s^2 self . request . jerk = 4000 # rot/s^3 # set target position to 100 rotations self . talonfx . set_control ( self . request . with_position ( 100 )) Motion Magic® Velocity The Talon FX also supports onboard velocity motion profiling using Motion Magic® Velocity. When using Motion Magic® Velocity, the motor will ramp to a target velocity using a trapezoidal acceleration profile that honors the specified acceleration and optional jerk. The benefits of this control mode over “simple” PID velocity closed-looping are: Control of the mechanism throughout the entire motion (as opposed to racing to the end target velocity) Control of the mechanism’s inertia to ensure smooth transitions between setpoints Improved repeatability despite changes in battery load Improved repeatability despite changes in motor load After gain/settings are determined, the robot controller only needs to periodically set the target velocity. The following parameters must be set when controlling using Motion Magic® Velocity Acceleration - controls acceleration and deceleration rates during the beginning and end of motion Jerk (optional) - controls jerk, which is the derivative of acceleration Using Motion Magic® Velocity in API Motion Magic® Velocity is currently supported for all base control output types . The units of the output are determined by the control output type. The Motion Magic® Velocity jerk and acceleration can be configured in code using a MotionMagicConfigs ( Java , C++ , Python ) object. In Motion Magic® Velocity, the gains should be configured as follows: \\(K_g\\) - output to overcome gravity (output) \\(K_s\\) - output to overcome static friction (output) \\(K_v\\) - output per unit of target velocity (output/rps) \\(K_a\\) - output per unit of target acceleration (output/(rps/s)) \\(K_p\\) - output per unit of error in velocity (output/rps) \\(K_i\\) - output per unit of integrated error in velocity (output/rotation) \\(K_d\\) - output per unit of error derivative in velocity (output/(rps/s)) Java // in init function var talonFXConfigs = new TalonFXConfiguration (); // set slot 0 gains var slot0Configs = talonFXConfigs . Slot0 ; slot0Configs . kS = 0.25 ; // Add 0.25 V output to overcome static friction slot0Configs . kV = 0.12 ; // A velocity target of 1 rps results in 0.12 V output slot0Configs . kA = 0.01 ; // An acceleration of 1 rps/s requires 0.01 V output slot0Configs . kP = 0.11 ; // An error of 1 rps results in 0.11 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0 ; // no output for error derivative // set Motion Magic Velocity settings var motionMagicConfigs = talonFXConfigs . MotionMagic ; motionMagicConfigs . MotionMagicAcceleration = 400 ; // Target acceleration of 400 rps/s (0.25 seconds to max) motionMagicConfigs . MotionMagicJerk = 4000 ; // Target jerk of 4000 rps/s/s (0.1 seconds) m_talonFX . getConfigurator (). apply ( talonFXConfigs ); C++ // in init function configs :: TalonFXConfiguration talonFXConfigs {}; // set slot 0 gains auto & slot0Configs = talonFXConfigs . Slot0 ; slot0Configs . kS = 0.25 ; // Add 0.25 V output to overcome static friction slot0Configs . kV = 0.12 ; // A velocity target of 1 rps results in 0.12 V output slot0Configs . kA = 0.01 ; // An acceleration of 1 rps/s requires 0.01 V output slot0Configs . kP = 0.11 ; // An error of 1 rps results in 0.11 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0 ; // no output for error derivative // set Motion Magic Velocity settings auto & motionMagicConfigs = talonFXConfigs . MotionMagic ; motionMagicConfigs . MotionMagicAcceleration = 400 ; // Target acceleration of 400 rps/s (0.25 seconds to max) motionMagicConfigs . MotionMagicJerk = 4000 ; // Target jerk of 4000 rps/s/s (0.1 seconds) m_talonFX . GetConfigurator (). Apply ( talonFXConfigs ); Python # in init function talonfx_configs = configs . TalonFXConfiguration () # set slot 0 gains slot0_configs = talonfx_configs . slot0 slot0_configs . k_s = 0.25 # Add 0.25 V output to overcome static friction slot0_configs . k_v = 0.12 # A velocity target of 1 rps results in 0.12 V output slot0_configs . k_a = 0.01 # An acceleration of 1 rps/s requires 0.01 V output slot0_configs . k_p = 0.11 # An error of 1 rps results in 0.11 V output slot0_configs . k_i = 0 # no output for integrated error slot0_configs . k_d = 0 # no output for error derivative # set Motion Magic Velocity settings motion_magic_configs = talonfx_configs . motion_magic motion_magic_configs . motion_magic_acceleration = 400 # Target acceleration of 400 rps/s (0.25 seconds to max) motion_magic_configs . motion_magic_jerk = 4000 # Target jerk of 4000 rps/s/s (0.1 seconds) self . talonfx . configurator . apply ( talonfx_configs ) Tip Motion Magic® Velocity supports modifying acceleration and jerk on the fly (requires firmware version 24.0.6.0 or newer). Once the gains are configured, the Motion Magic® Velocity request can be sent to the TalonFX. The Motion Magic® Velocity request has an Acceleration parameter that can be used to override the profile acceleration during motion. If the Acceleration parameter is left 0, the acceleration config will be used instead. The control request object also has an optional feedforward term that can be used to add an arbitrary value to the output, which can be useful to account for the effects of gravity. Java // create a Motion Magic Velocity request, voltage output final MotionMagicVelocityVoltage m_request = new MotionMagicVelocityVoltage ( 0 ); if ( m_joy . getAButton ()) { // while the joystick A button is held, use a slower acceleration m_request . Acceleration = 100 ; // rot/s^2 } else { // otherwise, fall back to the config m_request . Acceleration = 0 ; } // set target velocity to 80 rps m_talonFX . setControl ( m_request . withVelocity ( 80 )); C++ // create a Motion Magic Velocity request, voltage output controls :: MotionMagicVelocityVoltage m_request { 0 _tps }; if ( m_joy . GetAButton ()) { // while the joystick A button is held, use a slower acceleration m_request . Acceleration = 100 _tr_per_s_sq ; } else { // otherwise, fall back to the config m_request . Acceleration = 0 _tr_per_s_sq ; } // set target velocity to 80 rps m_talonFX . SetControl ( m_request . WithVelocity ( 80 _tps )); Python # create a Motion Magic Velocity request, voltage output self . request = controls . MotionMagicVelocityVoltage ( 0 ) if self . joy . getAButton (): # while the joystick A button is held, use a slower acceleration self . request . acceleration = 100 # rot/s^2 else : # otherwise, fall back to the config self . request . acceleration = 0 # set target velocity to 80 rps self . talonfx . set_control ( self . request . with_velocity ( 80 )) Motion Magic® Expo Whereas traditional Motion Magic® generates a trapezoidal or S-Curve profile, Motion Magic® Expo generates an exponential profile. This allows the profile to best match the system dynamics, reducing both overshoot and time to target compared to a trapezoidal profile. Motion Magic® Expo uses the kV and kA characteristics of the system, as well as an optional cruise velocity. The Motion Magic® Expo kV and kA configs are separate from the slot gain configs, as they may use different units and have different behaviors. The Motion Magic® Expo kV represents the voltage required to maintain a given velocity and is in units of Volts/rps. Dividing the supply voltage by kV results in the maximum velocity of the profile. As a result, when supply voltage is fixed, a higher profile kV results in a lower profile velocity . Unlike with gain slots, it is safer to start from a higher kV than what is ideal. The Motion Magic® Expo kA represents the voltage required to apply a given acceleration and is in units of Volts/(rps/s). Dividing the supply voltage by kA results in the maximum acceleration of the profile from 0. As a result, when supply voltage is fixed, a higher profile kA results in a lower profile acceleration . Unlike with gain slots, it is safer to start from a higher kA than what is ideal. If the Motion Magic® cruise velocity is set to a non-zero value, the profile will only accelerate up to the cruise velocity. Otherwise, the profile will accelerate towards the maximum possible velocity based on the profile kV. The following parameters must be set when controlling using Motion Magic® Expo: Expo kV - voltage required to maintain a given velocity, in V/rps Expo kA - voltage required to apply a given acceleration, in V/(rps/s) Cruise Velocity (optional) - peak velocity of the profile; set to 0 to target the system’s max velocity Using Motion Magic® Expo in API Motion Magic® Expo is currently supported for all base control output types . The units of the output are determined by the control output type. The Motion Magic® Expo kV, kA, and cruise velocity can be configured in code using a MotionMagicConfigs ( Java , C++ , Python ) object. Important Unlike the gain slots, the MotionMagicExpo_kV and MotionMagicExpo_kA configs are always in output units of Volts. In Motion Magic® Expo, the gains should be configured as follows: \\(K_g\\) - output to overcome gravity (output) \\(K_s\\) - output to overcome static friction (output) \\(K_v\\) - output per unit of target velocity (output/rps) \\(K_a\\) - output per unit of target acceleration (output/(rps/s)) \\(K_p\\) - output per unit of error in position (output/rotation) \\(K_i\\) - output per unit of integrated error in position (output/(rotation*s)) \\(K_d\\) - output per unit of error in velocity (output/rps) Java // in init function var talonFXConfigs = new TalonFXConfiguration (); // set slot 0 gains var slot0Configs = talonFXConfigs . Slot0 ; slot0Configs . kS = 0.25 ; // Add 0.25 V output to overcome static friction slot0Configs . kV = 0.12 ; // A velocity target of 1 rps results in 0.12 V output slot0Configs . kA = 0.01 ; // An acceleration of 1 rps/s requires 0.01 V output slot0Configs . kP = 4.8 ; // A position error of 2.5 rotations results in 12 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0.1 ; // A velocity error of 1 rps results in 0.1 V output // set Motion Magic Expo settings var motionMagicConfigs = talonFXConfigs . MotionMagic ; motionMagicConfigs . MotionMagicCruiseVelocity = 0 ; // Unlimited cruise velocity motionMagicConfigs . MotionMagicExpo_kV = 0.12 ; // kV is around 0.12 V/rps motionMagicConfigs . MotionMagicExpo_kA = 0.1 ; // Use a slower kA of 0.1 V/(rps/s) m_talonFX . getConfigurator (). apply ( talonFXConfigs ); C++ // in init function configs :: TalonFXConfiguration talonFXConfigs {}; // set slot 0 gains auto & slot0Configs = talonFXConfigs . Slot0 ; slot0Configs . kS = 0.25 ; // Add 0.25 V output to overcome static friction slot0Configs . kV = 0.12 ; // A velocity target of 1 rps results in 0.12 V output slot0Configs . kA = 0.01 ; // An acceleration of 1 rps/s requires 0.01 V output slot0Configs . kP = 4.8 ; // A position error of 2.5 rotations results in 12 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0.1 ; // A velocity error of 1 rps results in 0.1 V output // set Motion Magic Expo settings auto & motionMagicConfigs = talonFXConfigs . MotionMagic ; motionMagicConfigs . MotionMagicCruiseVelocity = 0 ; // Unlimited cruise velocity motionMagicConfigs . MotionMagicExpo_kV = 0.12 ; // kV is around 0.12 V/rps motionMagicConfigs . MotionMagicExpo_kA = 0.1 ; // Use a slower kA of 0.1 V/(rps/s) m_talonFX . GetConfigurator (). Apply ( talonFXConfigs ); Python # in init function talonfx_configs = configs . TalonFXConfiguration () # set slot 0 gains slot0_configs = talonfx_configs . slot0 slot0_configs . k_s = 0.25 # Add 0.25 V output to overcome static friction slot0_configs . k_v = 0.12 # A velocity target of 1 rps results in 0.12 V output slot0_configs . k_a = 0.01 # An acceleration of 1 rps/s requires 0.01 V output slot0_configs . k_p = 4.8 # A position error of 2.5 rotations results in 12 V output slot0_configs . k_i = 0 # no output for integrated error slot0_configs . k_d = 0.1 # A velocity error of 1 rps results in 0.1 V output # set Motion Magic Expo settings motion_magic_configs = talonfx_configs . motion_magic motion_magic_configs . motion_magic_cruise_velocity = 0 # Unlimited cruise velocity motion_magic_configs . motion_magic_expo_k_v = 0.12 # kV is around 0.12 V/rps motion_magic_configs . motion_magic_expo_k_a = 0.1 # Use a slower kA of 0.1 V/(rps/s) self . talonfx . configurator . apply ( talonfx_configs ) Tip Motion Magic® Expo supports modifying cruise velocity, kV, and kA on the fly. Once the gains are configured, the Motion Magic® Expo request can be sent to the TalonFX. The control request object has an optional feedforward term that can be used to add an arbitrary value to the output, which can be useful to account for the effects of gravity. Java // create a Motion Magic Expo request, voltage output final MotionMagicExpoVoltage m_request = new MotionMagicExpoVoltage ( 0 ) // set target position to 100 rotations m_talonFX . setControl ( m_request . withPosition ( 100 )); C++ // create a Motion Magic Expo request, voltage output controls :: MotionMagicExpoVoltage m_request { 0 _tr } // set target position to 100 rotations m_talonFX . SetControl ( m_request . WithPosition ( 100 _tr )); Python # create a Motion Magic Expo request, voltage output self . request = controls . MotionMagicExpoVoltage ( 0 ) # set target position to 100 rotations self . talonfx . set_control ( self . request . with_position ( 100 )) Dynamic Motion Magic® Expo Important This feature requires the device to be Pro licensed and on a CANivore . When unlicensed, the TalonFX will disable control output and trip the UnlicensedFeatureInUse fault. When using a Pro-licensed Talon FX connected to a CANivore, Dynamic Motion Magic® Expo can be used, allowing for the cruise velocity, Expo kV, and Expo kA to be modified directly in the control request during motion. This can be used to set up different values for forward vs reverse or to speed up and slow down the profile on the fly. The gain slots are configured in the same way as a regular Motion Magic® Expo request. However, the cruise velocity, Expo kV, and Expo kA parameters are set up in the control request, not the Motion Magic® config group. Once the gains are configured, the Dynamic Motion Magic® Expo request can be sent to the TalonFX. The control request object has an optional feedforward term that can be used to add an arbitrary value to the output, which can be useful to account for the effects of gravity. Java // create a Dynamic Motion Magic Expo request, voltage output // default Expo kV of 0.12 V/rps and kA of 0.1 V/(rot/s^2), unlimited cruise velocity final DynamicMotionMagicExpoVoltage m_request = new DynamicMotionMagicExpoVoltage ( 0 , 0.12 , 0.1 ); if ( m_joy . getAButton ()) { // while the joystick A button is held, use a slower profile // cap the cruise velocity and weaken acceleration (larger kA) m_request . Velocity = 40 ; // rps m_request . kA = 0.2 ; // V/(rot/s^2) } else { // otherwise use a faster profile m_request . Velocity = 0 ; // rps, 0 is unlimited m_request . kA = 0.1 ; // V/(rot/s^2) } // set target position to 100 rotations m_talonFX . setControl ( m_request . withPosition ( 100 )); C++ // create a Dynamic Motion Magic Expo request, voltage output // default Expo kV of 0.12 V/rps and kA of 0.1 V/(rot/s^2), unlimited cruise velocity controls :: DynamicMotionMagicExpoVoltage m_request { 0 _tr , 0.12 _V / 1 _tr_per_s , 0.1 _V / 1 _tr_per_s_sq }; if ( m_joy . GetAButton ()) { // while the joystick A button is held, use a slower profile // cap the cruise velocity and weaken acceleration (larger kA) m_request . Velocity = 40 _tps ; m_request . kA = 0.2 _V / 1 _tr_per_s_sq ; } else { // otherwise use a faster profile m_request . Velocity = 0 _tps ; // 0 is unlimited m_request . kA = 0.1 _V / 1 _tr_per_s_sq ; } // set target position to 100 rotations m_talonFX . SetControl ( m_request . WithPosition ( 100 _tr )); Python # create a Dynamic Motion Magic Expo request, voltage output # default Expo kV of 0.12 V/rps and kA of 0.1 V/(rot/s^2), unlimited cruise velocity self . request = controls . DynamicMotionMagicExpoVoltage ( 0 , 0.12 , 0.1 ) if self . joy . getAButton (): # while the joystick A button is held, use a slower profile # cap the cruise velocity and weaken acceleration (larger kA) self . request . velocity = 40 # rps self . request . k_a = 0.2 # V/(rot/s^2) else : # otherwise use a faster profile self . request . velocity = 0 # rps, 0 is unlimited self . request . k_a = 0.1 # V/(rot/s^2) # set target position to 100 rotations self . talonfx . set_control ( self . request . with_position ( 100 ))",
+ "content_preview": "Motion Magic® Controls In addition to basic PID control, the Talon FX also supports onboard motion profiling using Motion Magic® controls. Note For more information on feedback and feedforward gains, see Closed-Loop Overview ."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/mechanisms/swerve/swerve-builder-api.html",
- "title": "Swerve Builder API",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/mechanisms/differential/differential-overview.html",
+ "title": "Differential Overview",
"section": "API Reference",
"language": "All",
- "content": "Swerve Builder API To simplify the API surface, both builder and factory paradigms are used. Users create a SwerveDrivetrain by first defining the global drivetrain characteristics and then each module characteristics. Note Phoenix 6 supports the Java units library when applicable. Defining Drivetrain Characteristics Drivetrain, in this instance, refers to the SwerveDrivetrainConstants class ( Java , C++ , Python ). This class defines characteristics that are not tied to the swerve modules, such as the CAN bus or Pigeon 2 device ID. Note All devices in the swerve drivetrain must be on the same CAN bus. Users can optionally provide a configuration object to apply custom configs to the Pigeon 2, such as mount orientation. Leaving the configuration object null will skip applying configs to the Pigeon 2. Defining Module Characteristics The typical FRC drivetrain includes 4 identical modules. To simplify module creation, the SwerveModuleConstantsFactory ( Java , C++ , Python ) class is used to set up constants common across all modules, such as the drive/steer gear ratios and the wheel radius. Some extra steps may be required to determine some constants, described below. CouplingGearRatio The ratio at which the output wheel rotates when the azimuth spins. In a traditional swerve module, this is the inverse of the 1st stage of the drive motor. To manually determine the coupling ratio, lock the drive wheel in-place, then rotate the azimuth three times. Observe the number of rotations reported by the drive motor. The coupling ratio will be \\(driveRotations / 3\\) , or \\(driveRotations / azimuthRotations\\) . SlipCurrent This is the amount of stator current the drive motors can apply without slippage. Follow the instructions in Preventing Wheel Slip to find the slip current of the drivetrain. DriveMotorInitialConfigs / SteerMotorInitialConfigs / EncoderInitialConfigs An initial configuration object that can be used to apply custom configs to the backing devices for each swerve module. This is useful for situations such as applying supply current limits. Building the Swerve Module Constants SwerveModuleConstants ( Java , C++ , Python ) can be created from the previous SwerveModuleConstantsFactory . A typical swerve drivetrain consists of four identical modules: Front Left, Front Right, Back Left, Back Right. While these modules can be instantiated directly (only really useful if the modules have different physical characteristics), the modules can also be created by calling createModuleConstants(...) with the aforementioned factory. Note The X and Y position of the modules is measured from the center point of the robot along the X and Y axes, respectively. These values use the same coordinate system as Translation2d ( Java , C++ , Python ), where forward is positive X and left is positive Y. Building the SwerveDrivetrain SwerveDrivetrain ( Java , C++ , Python ) is the class that handles odometry, configuration and control of the drivetrain. The constructor for this class takes the previous SwerveDrivetrainConstants and a list of SwerveModuleConstants . Utilization of SwerveDrivetrain consists of SwerveRequests that define the state of the drivetrain. For full details of using SwerveRequests to control your swerve, see Swerve Requests . Full Example Note CommandSwerveDrivetrain is a version created by the Tuner X Swerve Project Generator that implements Subsystem ( Java , C++ , Python ) for easy command-based integration. Java 1 // Generated by the 2026 Tuner X Swerve Project Generator 2 // https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-swerve/index.html 3 public class TunerConstants { 4 // Both sets of gains need to be tuned to your individual robot. 5 6 // The steer motor uses any SwerveModule.SteerRequestType control request with the 7 // output type specified by SwerveModuleConstants.SteerMotorClosedLoopOutput 8 private static final Slot0Configs steerGains = new Slot0Configs () 9 . withKP ( 100 ). withKI ( 0 ). withKD ( 0.5 ) 10 . withKS ( 0.1 ). withKV ( 1.91 ). withKA ( 0 ) 11 . withStaticFeedforwardSign ( StaticFeedforwardSignValue . UseClosedLoopSign ); 12 // When using closed-loop control, the drive motor uses the control 13 // output type specified by SwerveModuleConstants.DriveMotorClosedLoopOutput 14 private static final Slot0Configs driveGains = new Slot0Configs () 15 . withKP ( 0.1 ). withKI ( 0 ). withKD ( 0 ) 16 . withKS ( 0 ). withKV ( 0.124 ); 17 18 // The closed-loop output type to use for the steer motors; 19 // This affects the PID/FF gains for the steer motors 20 private static final ClosedLoopOutputType kSteerClosedLoopOutput = ClosedLoopOutputType . Voltage ; 21 // The closed-loop output type to use for the drive motors; 22 // This affects the PID/FF gains for the drive motors 23 private static final ClosedLoopOutputType kDriveClosedLoopOutput = ClosedLoopOutputType . Voltage ; 24 25 // The type of motor used for the drive motor 26 private static final DriveMotorArrangement kDriveMotorType = DriveMotorArrangement . TalonFX_Integrated ; 27 // The type of motor used for the steer motor 28 private static final SteerMotorArrangement kSteerMotorType = SteerMotorArrangement . TalonFX_Integrated ; 29 30 // The remote sensor feedback type to use for the steer motors; 31 // When not Pro-licensed, Fused*/Sync* automatically fall back to Remote* 32 private static final SteerFeedbackType kSteerFeedbackType = SteerFeedbackType . FusedCANcoder ; 33 34 // The stator current at which the wheels start to slip; 35 // This needs to be tuned to your individual robot 36 private static final Current kSlipCurrent = Amps . of ( 120 ); 37 38 // Initial configs for the drive and steer motors and the azimuth encoder; these cannot be null. 39 // Some configs will be overwritten; check the `with*InitialConfigs()` API documentation. 40 private static final TalonFXConfiguration driveInitialConfigs = new TalonFXConfiguration () 41 . withCurrentLimits ( 42 new CurrentLimitsConfigs () 43 // Default supply current limit is 70 A, but it can be lowered to avoid brownouts. 44 // Supply current limits can be larger than the breaker current rating. 45 . withSupplyCurrentLimit ( Amps . of ( 70 )) 46 . withSupplyCurrentLimitEnable ( true ) 47 ); 48 private static final TalonFXConfiguration steerInitialConfigs = new TalonFXConfiguration () 49 . withCurrentLimits ( 50 new CurrentLimitsConfigs () 51 // Swerve azimuth does not require much torque output, so we can set a relatively low 52 // stator current limit to help avoid brownouts without impacting performance. 53 . withStatorCurrentLimit ( Amps . of ( 60 )) 54 . withStatorCurrentLimitEnable ( true ) 55 ); 56 private static final CANcoderConfiguration encoderInitialConfigs = new CANcoderConfiguration (); 57 // Configs for the Pigeon 2; leave this null to skip applying Pigeon 2 configs 58 private static final Pigeon2Configuration pigeonConfigs = null ; 59 60 // CAN bus that the devices are located on; 61 // All swerve devices must share the same CAN bus 62 public static final CANBus kCANBus = new CANBus ( \"canivore\" , \"./logs/example.hoot\" ); 63 64 // Measured robot speed (m/s) at 12 V applied output; 65 // This is NOT the desired max robot speed - see MaxSpeed in RobotContainer instead; 66 // This needs to be tuned to your individual robot 67 public static final LinearVelocity kSpeedAt12Volts = MetersPerSecond . of ( 4.54 ); 68 69 // Every 1 rotation of the azimuth results in kCoupleRatio drive motor turns; 70 // This may need to be tuned to your individual robot 71 private static final double kCoupleRatio = 3.8181818181818183 ; 72 73 private static final double kDriveGearRatio = 7.363636363636365 ; 74 private static final double kSteerGearRatio = 15.42857142857143 ; 75 private static final Distance kWheelRadius = Inches . of ( 2.167 ); 76 77 private static final boolean kInvertLeftSide = false ; 78 private static final boolean kInvertRightSide = true ; 79 80 private static final int kPigeonId = 1 ; 81 82 // These are only used for simulation 83 private static final MomentOfInertia kSteerInertia = KilogramSquareMeters . of ( 0.01 ); 84 private static final MomentOfInertia kDriveInertia = KilogramSquareMeters . of ( 0.035 ); 85 // Simulated voltage necessary to overcome friction 86 private static final Voltage kSteerFrictionVoltage = Volts . of ( 0.2 ); 87 private static final Voltage kDriveFrictionVoltage = Volts . of ( 0.2 ); 88 89 public static final SwerveDrivetrainConstants DrivetrainConstants = new SwerveDrivetrainConstants () 90 . withCANBusName ( kCANBus . getName ()) 91 . withPigeon2Id ( kPigeonId ) 92 . withPigeon2Configs ( pigeonConfigs ); 93 94 private static final SwerveModuleConstantsFactory < TalonFXConfiguration , TalonFXConfiguration , CANcoderConfiguration > ConstantCreator = 95 new SwerveModuleConstantsFactory < TalonFXConfiguration , TalonFXConfiguration , CANcoderConfiguration > () 96 . withDriveMotorGearRatio ( kDriveGearRatio ) 97 . withSteerMotorGearRatio ( kSteerGearRatio ) 98 . withCouplingGearRatio ( kCoupleRatio ) 99 . withWheelRadius ( kWheelRadius ) 100 . withSteerMotorGains ( steerGains ) 101 . withDriveMotorGains ( driveGains ) 102 . withSteerMotorClosedLoopOutput ( kSteerClosedLoopOutput ) 103 . withDriveMotorClosedLoopOutput ( kDriveClosedLoopOutput ) 104 . withSlipCurrent ( kSlipCurrent ) 105 . withSpeedAt12Volts ( kSpeedAt12Volts ) 106 . withDriveMotorType ( kDriveMotorType ) 107 . withSteerMotorType ( kSteerMotorType ) 108 . withFeedbackSource ( kSteerFeedbackType ) 109 . withDriveMotorInitialConfigs ( driveInitialConfigs ) 110 . withSteerMotorInitialConfigs ( steerInitialConfigs ) 111 . withEncoderInitialConfigs ( encoderInitialConfigs ) 112 . withSteerInertia ( kSteerInertia ) 113 . withDriveInertia ( kDriveInertia ) 114 . withSteerFrictionVoltage ( kSteerFrictionVoltage ) 115 . withDriveFrictionVoltage ( kDriveFrictionVoltage ); 116 117 118 // Front Left 119 private static final int kFrontLeftDriveMotorId = 3 ; 120 private static final int kFrontLeftSteerMotorId = 2 ; 121 private static final int kFrontLeftEncoderId = 1 ; 122 private static final Angle kFrontLeftEncoderOffset = Rotations . of ( 0.15234375 ); 123 private static final boolean kFrontLeftSteerMotorInverted = true ; 124 private static final boolean kFrontLeftEncoderInverted = false ; 125 126 private static final Distance kFrontLeftXPos = Inches . of ( 10 ); 127 private static final Distance kFrontLeftYPos = Inches . of ( 10 ); 128 129 // Front Right 130 private static final int kFrontRightDriveMotorId = 1 ; 131 private static final int kFrontRightSteerMotorId = 0 ; 132 private static final int kFrontRightEncoderId = 0 ; 133 private static final Angle kFrontRightEncoderOffset = Rotations . of ( - 0.4873046875 ); 134 private static final boolean kFrontRightSteerMotorInverted = true ; 135 private static final boolean kFrontRightEncoderInverted = false ; 136 137 private static final Distance kFrontRightXPos = Inches . of ( 10 ); 138 private static final Distance kFrontRightYPos = Inches . of ( - 10 ); 139 140 // Back Left 141 private static final int kBackLeftDriveMotorId = 7 ; 142 private static final int kBackLeftSteerMotorId = 6 ; 143 private static final int kBackLeftEncoderId = 3 ; 144 private static final Angle kBackLeftEncoderOffset = Rotations . of ( - 0.219482421875 ); 145 private static final boolean kBackLeftSteerMotorInverted = true ; 146 private static final boolean kBackLeftEncoderInverted = false ; 147 148 private static final Distance kBackLeftXPos = Inches . of ( - 10 ); 149 private static final Distance kBackLeftYPos = Inches . of ( 10 ); 150 151 // Back Right 152 private static final int kBackRightDriveMotorId = 5 ; 153 private static final int kBackRightSteerMotorId = 4 ; 154 private static final int kBackRightEncoderId = 2 ; 155 private static final Angle kBackRightEncoderOffset = Rotations . of ( 0.17236328125 ); 156 private static final boolean kBackRightSteerMotorInverted = true ; 157 private static final boolean kBackRightEncoderInverted = false ; 158 159 private static final Distance kBackRightXPos = Inches . of ( - 10 ); 160 private static final Distance kBackRightYPos = Inches . of ( - 10 ); 161 162 163 public static final SwerveModuleConstants < TalonFXConfiguration , TalonFXConfiguration , CANcoderConfiguration > FrontLeft = 164 ConstantCreator . createModuleConstants ( 165 kFrontLeftSteerMotorId , kFrontLeftDriveMotorId , kFrontLeftEncoderId , kFrontLeftEncoderOffset , 166 kFrontLeftXPos , kFrontLeftYPos , kInvertLeftSide , kFrontLeftSteerMotorInverted , kFrontLeftEncoderInverted 167 ); 168 public static final SwerveModuleConstants < TalonFXConfiguration , TalonFXConfiguration , CANcoderConfiguration > FrontRight = 169 ConstantCreator . createModuleConstants ( 170 kFrontRightSteerMotorId , kFrontRightDriveMotorId , kFrontRightEncoderId , kFrontRightEncoderOffset , 171 kFrontRightXPos , kFrontRightYPos , kInvertRightSide , kFrontRightSteerMotorInverted , kFrontRightEncoderInverted 172 ); 173 public static final SwerveModuleConstants < TalonFXConfiguration , TalonFXConfiguration , CANcoderConfiguration > BackLeft = 174 ConstantCreator . createModuleConstants ( 175 kBackLeftSteerMotorId , kBackLeftDriveMotorId , kBackLeftEncoderId , kBackLeftEncoderOffset , 176 kBackLeftXPos , kBackLeftYPos , kInvertLeftSide , kBackLeftSteerMotorInverted , kBackLeftEncoderInverted 177 ); 178 public static final SwerveModuleConstants < TalonFXConfiguration , TalonFXConfiguration , CANcoderConfiguration > BackRight = 179 ConstantCreator . createModuleConstants ( 180 kBackRightSteerMotorId , kBackRightDriveMotorId , kBackRightEncoderId , kBackRightEncoderOffset , 181 kBackRightXPos , kBackRightYPos , kInvertRightSide , kBackRightSteerMotorInverted , kBackRightEncoderInverted 182 ); 183 184 /** 185 * Creates a CommandSwerveDrivetrain instance. 186 * This should only be called once in your robot program,. 187 */ 188 public static CommandSwerveDrivetrain createDrivetrain () { 189 return new CommandSwerveDrivetrain ( 190 DrivetrainConstants , FrontLeft , FrontRight , BackLeft , BackRight 191 ); 192 } 193 194 195 /** 196 * Swerve Drive class utilizing CTR Electronics' Phoenix 6 API with the selected device types. 197 */ 198 public static class TunerSwerveDrivetrain extends SwerveDrivetrain < TalonFX , TalonFX , CANcoder > { 199 /** 200 * Constructs a CTRE SwerveDrivetrain using the specified constants. 201 * 202 * This constructs the underlying hardware devices, so users should not construct 203 * the devices themselves. If they need the devices, they can access them through 204 * getters in the classes. 205 * 206 * @param drivetrainConstants Drivetrain-wide constants for the swerve drive 207 * @param modules Constants for each specific module 208 */ 209 public TunerSwerveDrivetrain ( 210 SwerveDrivetrainConstants drivetrainConstants , 211 SwerveModuleConstants , ? , ?> ... modules 212 ) { 213 super ( 214 TalonFX :: new , TalonFX :: new , CANcoder :: new , 215 drivetrainConstants , modules 216 ); 217 } 218 219 /** 220 * Constructs a CTRE SwerveDrivetrain using the specified constants. 221 *
222 * This constructs the underlying hardware devices, so users should not construct 223 * the devices themselves. If they need the devices, they can access them through 224 * getters in the classes. 225 * 226 * @param drivetrainConstants Drivetrain-wide constants for the swerve drive 227 * @param odometryUpdateFrequency The frequency to run the odometry loop. If 228 * unspecified or set to 0 Hz, this is 250 Hz on 229 * CAN FD, and 100 Hz on CAN 2.0. 230 * @param modules Constants for each specific module 231 */ 232 public TunerSwerveDrivetrain ( 233 SwerveDrivetrainConstants drivetrainConstants , 234 double odometryUpdateFrequency , 235 SwerveModuleConstants , ? , ?> ... modules 236 ) { 237 super ( 238 TalonFX :: new , TalonFX :: new , CANcoder :: new , 239 drivetrainConstants , odometryUpdateFrequency , modules 240 ); 241 } 242 243 /** 244 * Constructs a CTRE SwerveDrivetrain using the specified constants. 245 *
246 * This constructs the underlying hardware devices, so users should not construct 247 * the devices themselves. If they need the devices, they can access them through 248 * getters in the classes. 249 * 250 * @param drivetrainConstants Drivetrain-wide constants for the swerve drive 251 * @param odometryUpdateFrequency The frequency to run the odometry loop. If 252 * unspecified or set to 0 Hz, this is 250 Hz on 253 * CAN FD, and 100 Hz on CAN 2.0. 254 * @param odometryStandardDeviation The standard deviation for odometry calculation 255 * in the form [x, y, theta]ᵀ, with units in meters 256 * and radians 257 * @param visionStandardDeviation The standard deviation for vision calculation 258 * in the form [x, y, theta]ᵀ, with units in meters 259 * and radians 260 * @param modules Constants for each specific module 261 */ 262 public TunerSwerveDrivetrain ( 263 SwerveDrivetrainConstants drivetrainConstants , 264 double odometryUpdateFrequency , 265 Matrix < N3 , N1 > odometryStandardDeviation , 266 Matrix < N3 , N1 > visionStandardDeviation , 267 SwerveModuleConstants , ? , ?> ... modules 268 ) { 269 super ( 270 TalonFX :: new , TalonFX :: new , CANcoder :: new , 271 drivetrainConstants , odometryUpdateFrequency , 272 odometryStandardDeviation , visionStandardDeviation , modules 273 ); 274 } 275 } 276 } C++ (Header) 1 #include \"ctre/phoenix6/swerve/SwerveDrivetrain.hpp\" 2 #include \"ctre/phoenix6/CANcoder.hpp\" 3 #include \"ctre/phoenix6/TalonFX.hpp\" 4 5 using namespace ctre :: phoenix6 ; 6 7 namespace subsystems { 8 /* Forward declaration */ 9 class CommandSwerveDrivetrain ; 10 } 11 12 // Generated by the 2026 Tuner X Swerve Project Generator 13 // https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-swerve/index.html 14 class TunerConstants { 15 // Both sets of gains need to be tuned to your individual robot. 16 17 // The steer motor uses any SwerveModule.SteerRequestType control request with the 18 // output type specified by SwerveModuleConstants::SteerMotorClosedLoopOutput 19 static constexpr configs :: Slot0Configs steerGains = configs :: Slot0Configs {} 20 . WithKP ( 100 ). WithKI ( 0 ). WithKD ( 0.5 ) 21 . WithKS ( 0.1 ). WithKV ( 1.91 ). WithKA ( 0 ) 22 . WithStaticFeedforwardSign ( signals :: StaticFeedforwardSignValue :: UseClosedLoopSign ); 23 // When using closed-loop control, the drive motor uses the control 24 // output type specified by SwerveModuleConstants::DriveMotorClosedLoopOutput 25 static constexpr configs :: Slot0Configs driveGains = configs :: Slot0Configs {} 26 . WithKP ( 0.1 ). WithKI ( 0 ). WithKD ( 0 ) 27 . WithKS ( 0 ). WithKV ( 0.124 ); 28 29 // The closed-loop output type to use for the steer motors; 30 // This affects the PID/FF gains for the steer motors 31 static constexpr swerve :: ClosedLoopOutputType kSteerClosedLoopOutput = swerve :: ClosedLoopOutputType :: Voltage ; 32 // The closed-loop output type to use for the drive motors; 33 // This affects the PID/FF gains for the drive motors 34 static constexpr swerve :: ClosedLoopOutputType kDriveClosedLoopOutput = swerve :: ClosedLoopOutputType :: Voltage ; 35 36 // The type of motor used for the drive motor 37 static constexpr swerve :: DriveMotorArrangement kDriveMotorType = swerve :: DriveMotorArrangement :: TalonFX_Integrated ; 38 // The type of motor used for the steer motor 39 static constexpr swerve :: SteerMotorArrangement kSteerMotorType = swerve :: SteerMotorArrangement :: TalonFX_Integrated ; 40 41 // The remote sensor feedback type to use for the steer motors; 42 // When not Pro-licensed, Fused*/Sync* automatically fall back to Remote* 43 static constexpr swerve :: SteerFeedbackType kSteerFeedbackType = swerve :: SteerFeedbackType :: FusedCANcoder ; 44 45 // The stator current at which the wheels start to slip; 46 // This needs to be tuned to your individual robot 47 static constexpr units :: ampere_t kSlipCurrent = 120 _A ; 48 49 // Initial configs for the drive and steer motors and the azimuth encoder; these cannot be null. 50 // Some configs will be overwritten; check the `With*InitialConfigs()` API documentation. 51 static constexpr configs :: TalonFXConfiguration driveInitialConfigs = configs :: TalonFXConfiguration {} 52 . WithCurrentLimits ( 53 configs :: CurrentLimitsConfigs {} 54 // Default supply current limit is 70 A, but it can be lowered to avoid brownouts. 55 // Supply current limits can be larger than the breaker current rating. 56 . WithSupplyCurrentLimit ( 70 _A ) 57 . WithSupplyCurrentLimitEnable ( true ) 58 ); 59 static constexpr configs :: TalonFXConfiguration steerInitialConfigs = configs :: TalonFXConfiguration {} 60 . WithCurrentLimits ( 61 configs :: CurrentLimitsConfigs {} 62 // Swerve azimuth does not require much torque output, so we can set a relatively low 63 // stator current limit to help avoid brownouts without impacting performance. 64 . WithStatorCurrentLimit ( 60 _A ) 65 . WithStatorCurrentLimitEnable ( true ) 66 ); 67 static constexpr configs :: CANcoderConfiguration encoderInitialConfigs {}; 68 // Configs for the Pigeon 2; leave this nullopt to skip applying Pigeon 2 configs 69 static constexpr std :: optional < configs :: Pigeon2Configuration > pigeonConfigs = std :: nullopt ; 70 71 static constexpr std :: string_view kCANBusName = \"canivore\" ; 72 73 public : 74 // CAN bus that the devices are located on; 75 // All swerve devices must share the same CAN bus 76 static inline const CANBus kCANBus { kCANBusName , \"./logs/example.hoot\" }; 77 78 // Measured robot speed (m/s) at 12 V applied output; 79 // This is NOT the desired max robot speed - see MaxSpeed in RobotContainer instead; 80 // This needs to be tuned to your individual robot 81 static constexpr units :: meters_per_second_t kSpeedAt12Volts = 4.54 _mps ; 82 83 private : 84 // Every 1 rotation of the azimuth results in kCoupleRatio drive motor turns; 85 // This may need to be tuned to your individual robot 86 static constexpr units :: scalar_t kCoupleRatio = 3.8181818181818183 ; 87 88 static constexpr units :: scalar_t kDriveGearRatio = 7.363636363636365 ; 89 static constexpr units :: scalar_t kSteerGearRatio = 15.42857142857143 ; 90 static constexpr units :: inch_t kWheelRadius = 2.167 _in ; 91 92 static constexpr bool kInvertLeftSide = false ; 93 static constexpr bool kInvertRightSide = true ; 94 95 static constexpr int kPigeonId = 1 ; 96 97 // These are only used for simulation 98 static constexpr units :: kilogram_square_meter_t kSteerInertia = 0.01 _kg_sq_m ; 99 static constexpr units :: kilogram_square_meter_t kDriveInertia = 0.035 _kg_sq_m ; 100 // Simulated voltage necessary to overcome friction 101 static constexpr units :: volt_t kSteerFrictionVoltage = 0.2 _V ; 102 static constexpr units :: volt_t kDriveFrictionVoltage = 0.2 _V ; 103 104 public : 105 static constexpr swerve :: SwerveDrivetrainConstants DrivetrainConstants = swerve :: SwerveDrivetrainConstants {} 106 . WithCANBusName ( kCANBusName ) 107 . WithPigeon2Id ( kPigeonId ) 108 . WithPigeon2Configs ( pigeonConfigs ); 109 110 private : 111 static constexpr swerve :: SwerveModuleConstantsFactory ConstantCreator = 112 swerve :: SwerveModuleConstantsFactory < configs :: TalonFXConfiguration , configs :: TalonFXConfiguration , configs :: CANcoderConfiguration > {} 113 . WithDriveMotorGearRatio ( kDriveGearRatio ) 114 . WithSteerMotorGearRatio ( kSteerGearRatio ) 115 . WithCouplingGearRatio ( kCoupleRatio ) 116 . WithWheelRadius ( kWheelRadius ) 117 . WithSteerMotorGains ( steerGains ) 118 . WithDriveMotorGains ( driveGains ) 119 . WithSteerMotorClosedLoopOutput ( kSteerClosedLoopOutput ) 120 . WithDriveMotorClosedLoopOutput ( kDriveClosedLoopOutput ) 121 . WithSlipCurrent ( kSlipCurrent ) 122 . WithSpeedAt12Volts ( kSpeedAt12Volts ) 123 . WithDriveMotorType ( kDriveMotorType ) 124 . WithSteerMotorType ( kSteerMotorType ) 125 . WithFeedbackSource ( kSteerFeedbackType ) 126 . WithDriveMotorInitialConfigs ( driveInitialConfigs ) 127 . WithSteerMotorInitialConfigs ( steerInitialConfigs ) 128 . WithEncoderInitialConfigs ( encoderInitialConfigs ) 129 . WithSteerInertia ( kSteerInertia ) 130 . WithDriveInertia ( kDriveInertia ) 131 . WithSteerFrictionVoltage ( kSteerFrictionVoltage ) 132 . WithDriveFrictionVoltage ( kDriveFrictionVoltage ); 133 134 135 // Front Left 136 static constexpr int kFrontLeftDriveMotorId = 3 ; 137 static constexpr int kFrontLeftSteerMotorId = 2 ; 138 static constexpr int kFrontLeftEncoderId = 1 ; 139 static constexpr units :: turn_t kFrontLeftEncoderOffset = 0.15234375 _tr ; 140 static constexpr bool kFrontLeftSteerMotorInverted = true ; 141 static constexpr bool kFrontLeftEncoderInverted = false ; 142 143 static constexpr units :: inch_t kFrontLeftXPos = 10 _in ; 144 static constexpr units :: inch_t kFrontLeftYPos = 10 _in ; 145 146 // Front Right 147 static constexpr int kFrontRightDriveMotorId = 1 ; 148 static constexpr int kFrontRightSteerMotorId = 0 ; 149 static constexpr int kFrontRightEncoderId = 0 ; 150 static constexpr units :: turn_t kFrontRightEncoderOffset = -0.4873046875 _tr ; 151 static constexpr bool kFrontRightSteerMotorInverted = true ; 152 static constexpr bool kFrontRightEncoderInverted = false ; 153 154 static constexpr units :: inch_t kFrontRightXPos = 10 _in ; 155 static constexpr units :: inch_t kFrontRightYPos = -10 _in ; 156 157 // Back Left 158 static constexpr int kBackLeftDriveMotorId = 7 ; 159 static constexpr int kBackLeftSteerMotorId = 6 ; 160 static constexpr int kBackLeftEncoderId = 3 ; 161 static constexpr units :: turn_t kBackLeftEncoderOffset = -0.219482421875 _tr ; 162 static constexpr bool kBackLeftSteerMotorInverted = true ; 163 static constexpr bool kBackLeftEncoderInverted = false ; 164 165 static constexpr units :: inch_t kBackLeftXPos = -10 _in ; 166 static constexpr units :: inch_t kBackLeftYPos = 10 _in ; 167 168 // Back Right 169 static constexpr int kBackRightDriveMotorId = 5 ; 170 static constexpr int kBackRightSteerMotorId = 4 ; 171 static constexpr int kBackRightEncoderId = 2 ; 172 static constexpr units :: turn_t kBackRightEncoderOffset = 0.17236328125 _tr ; 173 static constexpr bool kBackRightSteerMotorInverted = true ; 174 static constexpr bool kBackRightEncoderInverted = false ; 175 176 static constexpr units :: inch_t kBackRightXPos = -10 _in ; 177 static constexpr units :: inch_t kBackRightYPos = -10 _in ; 178 179 180 public : 181 static constexpr swerve :: SwerveModuleConstants FrontLeft = ConstantCreator . CreateModuleConstants ( 182 kFrontLeftSteerMotorId , kFrontLeftDriveMotorId , kFrontLeftEncoderId , kFrontLeftEncoderOffset , 183 kFrontLeftXPos , kFrontLeftYPos , kInvertLeftSide , kFrontLeftSteerMotorInverted , kFrontLeftEncoderInverted ); 184 static constexpr swerve :: SwerveModuleConstants FrontRight = ConstantCreator . CreateModuleConstants ( 185 kFrontRightSteerMotorId , kFrontRightDriveMotorId , kFrontRightEncoderId , kFrontRightEncoderOffset , 186 kFrontRightXPos , kFrontRightYPos , kInvertRightSide , kFrontRightSteerMotorInverted , kFrontRightEncoderInverted ); 187 static constexpr swerve :: SwerveModuleConstants BackLeft = ConstantCreator . CreateModuleConstants ( 188 kBackLeftSteerMotorId , kBackLeftDriveMotorId , kBackLeftEncoderId , kBackLeftEncoderOffset , 189 kBackLeftXPos , kBackLeftYPos , kInvertLeftSide , kBackLeftSteerMotorInverted , kBackLeftEncoderInverted ); 190 static constexpr swerve :: SwerveModuleConstants BackRight = ConstantCreator . CreateModuleConstants ( 191 kBackRightSteerMotorId , kBackRightDriveMotorId , kBackRightEncoderId , kBackRightEncoderOffset , 192 kBackRightXPos , kBackRightYPos , kInvertRightSide , kBackRightSteerMotorInverted , kBackRightEncoderInverted ); 193 194 /** 195 * Creates a CommandSwerveDrivetrain instance. 196 * This should only be called once in your robot program. 197 */ 198 static subsystems :: CommandSwerveDrivetrain CreateDrivetrain (); 199 }; 200 201 202 /** 203 * \\brief Swerve Drive class utilizing CTR Electronics' Phoenix 6 API with the selected device types. 204 */ 205 class TunerSwerveDrivetrain : public swerve :: SwerveDrivetrain < hardware :: TalonFX , hardware :: TalonFX , hardware :: CANcoder > { 206 public : 207 using SwerveModuleConstants = swerve :: SwerveModuleConstants < configs :: TalonFXConfiguration , configs :: TalonFXConfiguration , configs :: CANcoderConfiguration > ; 208 209 /** 210 * \\brief Constructs a CTRE SwerveDrivetrain using the specified constants. 211 * 212 * This constructs the underlying hardware devices, so users should not construct 213 * the devices themselves. If they need the devices, they can access them 214 * through getters in the classes. 215 * 216 * \\param drivetrainConstants Drivetrain-wide constants for the swerve drive 217 * \\param modules Constants for each specific module 218 */ 219 template < std :: same_as < SwerveModuleConstants > ... ModuleConstants > 220 TunerSwerveDrivetrain ( swerve :: SwerveDrivetrainConstants const & driveTrainConstants , ModuleConstants const & ... modules ) : 221 SwerveDrivetrain { driveTrainConstants , modules ...} 222 {} 223 224 /** 225 * \\brief Constructs a CTRE SwerveDrivetrain using the specified constants. 226 * 227 * This constructs the underlying hardware devices, so users should not construct 228 * the devices themselves. If they need the devices, they can access them 229 * through getters in the classes. 230 * 231 * \\param drivetrainConstants Drivetrain-wide constants for the swerve drive 232 * \\param odometryUpdateFrequency The frequency to run the odometry loop. If 233 * unspecified or set to 0 Hz, this is 250 Hz on 234 * CAN FD, and 100 Hz on CAN 2.0. 235 * \\param modules Constants for each specific module 236 */ 237 template < std :: same_as < SwerveModuleConstants > ... ModuleConstants > 238 TunerSwerveDrivetrain ( 239 swerve :: SwerveDrivetrainConstants const & driveTrainConstants , 240 units :: hertz_t odometryUpdateFrequency , 241 ModuleConstants const & ... modules 242 ) : 243 SwerveDrivetrain { driveTrainConstants , odometryUpdateFrequency , modules ...} 244 {} 245 246 /** 247 * \\brief Constructs a CTRE SwerveDrivetrain using the specified constants. 248 * 249 * This constructs the underlying hardware devices, so users should not construct 250 * the devices themselves. If they need the devices, they can access them 251 * through getters in the classes. 252 * 253 * \\param drivetrainConstants Drivetrain-wide constants for the swerve drive 254 * \\param odometryUpdateFrequency The frequency to run the odometry loop. If 255 * unspecified or set to 0 Hz, this is 250 Hz on 256 * CAN FD, and 100 Hz on CAN 2.0. 257 * \\param odometryStandardDeviation The standard deviation for odometry calculation 258 * in the form [x, y, theta]ᵀ, with units in meters 259 * and radians 260 * \\param visionStandardDeviation The standard deviation for vision calculation 261 * in the form [x, y, theta]ᵀ, with units in meters 262 * and radians 263 * \\param modules Constants for each specific module 264 */ 265 template < std :: same_as < SwerveModuleConstants > ... ModuleConstants > 266 TunerSwerveDrivetrain ( 267 swerve :: SwerveDrivetrainConstants const & driveTrainConstants , 268 units :: hertz_t odometryUpdateFrequency , 269 std :: array < double , 3 > const & odometryStandardDeviation , 270 std :: array < double , 3 > const & visionStandardDeviation , 271 ModuleConstants const & ... modules 272 ) : 273 SwerveDrivetrain { 274 driveTrainConstants , odometryUpdateFrequency , 275 odometryStandardDeviation , visionStandardDeviation , modules ... 276 } 277 {} 278 }; C++ (Source) 1 #include \"generated/TunerConstants.h\" 2 #include \"subsystems/CommandSwerveDrivetrain.h\" 3 4 subsystems :: CommandSwerveDrivetrain TunerConstants::CreateDrivetrain () 5 { 6 return { DrivetrainConstants , FrontLeft , FrontRight , BackLeft , BackRight }; 7 } Python 1 from subsystems.command_swerve_drivetrain import CommandSwerveDrivetrain 2 3 4 class TunerConstants : 5 \"\"\" 6 Generated by the 2026 Tuner X Swerve Project Generator 7 https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-swerve/index.html 8 \"\"\" 9 10 # Both sets of gains need to be tuned to your individual robot 11 12 # The steer motor uses any SwerveModule.SteerRequestType control request with the 13 # output type specified by SwerveModuleConstants.SteerMotorClosedLoopOutput 14 _steer_gains = ( 15 configs . Slot0Configs () 16 . with_k_p ( 100 ) 17 . with_k_i ( 0 ) 18 . with_k_d ( 0.5 ) 19 . with_k_s ( 0.1 ) 20 . with_k_v ( 1.91 ) 21 . with_k_a ( 0 ) 22 . with_static_feedforward_sign ( 23 signals . StaticFeedforwardSignValue . USE_CLOSED_LOOP_SIGN 24 ) 25 ) 26 # When using closed-loop control, the drive motor uses the control 27 # output type specified by SwerveModuleConstants.DriveMotorClosedLoopOutput 28 _drive_gains = ( 29 configs . Slot0Configs () 30 . with_k_p ( 0.1 ) 31 . with_k_i ( 0 ) 32 . with_k_d ( 0 ) 33 . with_k_s ( 0 ) 34 . with_k_v ( 0.124 ) 35 ) 36 37 # The closed-loop output type to use for the steer motors; 38 # This affects the PID/FF gains for the steer motors 39 _steer_closed_loop_output = swerve . ClosedLoopOutputType . VOLTAGE 40 # The closed-loop output type to use for the drive motors; 41 # This affects the PID/FF gains for the drive motors 42 _drive_closed_loop_output = swerve . ClosedLoopOutputType . VOLTAGE 43 44 # The type of motor used for the drive motor 45 _drive_motor_type = swerve . DriveMotorArrangement . TALON_FX_INTEGRATED 46 # The type of motor used for the steer motor 47 _steer_motor_type = swerve . SteerMotorArrangement . TALON_FX_INTEGRATED 48 49 # The remote sensor feedback type to use for the steer motors; 50 # When not Pro-licensed, Fused*/Sync* automatically fall back to Remote* 51 _steer_feedback_type = swerve . SteerFeedbackType . FUSED_CANCODER 52 53 # The stator current at which the wheels start to slip; 54 # This needs to be tuned to your individual robot 55 _slip_current : units . ampere = 120.0 56 57 # Initial configs for the drive and steer motors and the azimuth encoder; these cannot be null. 58 # Some configs will be overwritten; check the `with_*_initial_configs()` API documentation. 59 _drive_initial_configs = configs . TalonFXConfiguration () . with_current_limits ( 60 configs . CurrentLimitsConfigs () 61 # Default supply current limit is 70 A, but it can be lowered to avoid brownouts. 62 # Supply current limits can be larger than the breaker current rating. 63 . with_supply_current_limit ( 70.0 ) 64 . with_supply_current_limit_enable ( True ) 65 ) 66 _steer_initial_configs = configs . TalonFXConfiguration () . with_current_limits ( 67 configs . CurrentLimitsConfigs () 68 # Swerve azimuth does not require much torque output, so we can set a relatively low 69 # stator current limit to help avoid brownouts without impacting performance. 70 . with_stator_current_limit ( 60.0 ) 71 . with_stator_current_limit_enable ( True ) 72 ) 73 _encoder_initial_configs = configs . CANcoderConfiguration () 74 # Configs for the Pigeon 2; leave this None to skip applying Pigeon 2 configs 75 _pigeon_configs : configs . Pigeon2Configuration | None = None 76 77 # CAN bus that the devices are located on; 78 # All swerve devices must share the same CAN bus 79 canbus = CANBus ( \"canivore\" , \"./logs/example.hoot\" ) 80 81 # Measured robot speed (m/s) at 12 V applied output; 82 # This is NOT the desired max robot speed - see _max_speed in RobotContainer instead; 83 # This needs to be tuned to your individual robot 84 speed_at_12_volts : units . meters_per_second = 4.54 85 86 # Every 1 rotation of the azimuth results in _couple_ratio drive motor turns; 87 # This may need to be tuned to your individual robot 88 _couple_ratio = 3.8181818181818183 89 90 _drive_gear_ratio = 7.363636363636365 91 _steer_gear_ratio = 15.42857142857143 92 _wheel_radius : units . meter = inchesToMeters ( 2.167 ) 93 94 _invert_left_side = False 95 _invert_right_side = True 96 97 _pigeon_id = 1 98 99 # These are only used for simulation 100 _steer_inertia : units . kilogram_square_meter = 0.01 101 _drive_inertia : units . kilogram_square_meter = 0.035 102 # Simulated voltage necessary to overcome friction 103 _steer_friction_voltage : units . volt = 0.2 104 _drive_friction_voltage : units . volt = 0.2 105 106 drivetrain_constants = ( 107 swerve . SwerveDrivetrainConstants () 108 . with_can_bus_name ( canbus . name ) 109 . with_pigeon2_id ( _pigeon_id ) 110 . with_pigeon2_configs ( _pigeon_configs ) 111 ) 112 113 _constants_creator : swerve . SwerveModuleConstantsFactory [ 114 configs . TalonFXConfiguration , 115 configs . TalonFXConfiguration , 116 configs . CANcoderConfiguration , 117 ] = ( 118 swerve . SwerveModuleConstantsFactory () 119 . with_drive_motor_gear_ratio ( _drive_gear_ratio ) 120 . with_steer_motor_gear_ratio ( _steer_gear_ratio ) 121 . with_coupling_gear_ratio ( _couple_ratio ) 122 . with_wheel_radius ( _wheel_radius ) 123 . with_steer_motor_gains ( _steer_gains ) 124 . with_drive_motor_gains ( _drive_gains ) 125 . with_steer_motor_closed_loop_output ( _steer_closed_loop_output ) 126 . with_drive_motor_closed_loop_output ( _drive_closed_loop_output ) 127 . with_slip_current ( _slip_current ) 128 . with_speed_at12_volts ( speed_at_12_volts ) 129 . with_drive_motor_type ( _drive_motor_type ) 130 . with_steer_motor_type ( _steer_motor_type ) 131 . with_feedback_source ( _steer_feedback_type ) 132 . with_drive_motor_initial_configs ( _drive_initial_configs ) 133 . with_steer_motor_initial_configs ( _steer_initial_configs ) 134 . with_encoder_initial_configs ( _encoder_initial_configs ) 135 . with_steer_inertia ( _steer_inertia ) 136 . with_drive_inertia ( _drive_inertia ) 137 . with_steer_friction_voltage ( _steer_friction_voltage ) 138 . with_drive_friction_voltage ( _drive_friction_voltage ) 139 ) 140 141 142 # Front Left 143 _front_left_drive_motor_id = 3 144 _front_left_steer_motor_id = 2 145 _front_left_encoder_id = 1 146 _front_left_encoder_offset : units . rotation = 0.15234375 147 _front_left_steer_motor_inverted = True 148 _front_left_encoder_inverted = False 149 150 _front_left_x_pos : units . meter = inchesToMeters ( 10 ) 151 _front_left_y_pos : units . meter = inchesToMeters ( 10 ) 152 153 # Front Right 154 _front_right_drive_motor_id = 1 155 _front_right_steer_motor_id = 0 156 _front_right_encoder_id = 0 157 _front_right_encoder_offset : units . rotation = - 0.4873046875 158 _front_right_steer_motor_inverted = True 159 _front_right_encoder_inverted = False 160 161 _front_right_x_pos : units . meter = inchesToMeters ( 10 ) 162 _front_right_y_pos : units . meter = inchesToMeters ( - 10 ) 163 164 # Back Left 165 _back_left_drive_motor_id = 7 166 _back_left_steer_motor_id = 6 167 _back_left_encoder_id = 3 168 _back_left_encoder_offset : units . rotation = - 0.219482421875 169 _back_left_steer_motor_inverted = True 170 _back_left_encoder_inverted = False 171 172 _back_left_x_pos : units . meter = inchesToMeters ( - 10 ) 173 _back_left_y_pos : units . meter = inchesToMeters ( 10 ) 174 175 # Back Right 176 _back_right_drive_motor_id = 5 177 _back_right_steer_motor_id = 4 178 _back_right_encoder_id = 2 179 _back_right_encoder_offset : units . rotation = 0.17236328125 180 _back_right_steer_motor_inverted = True 181 _back_right_encoder_inverted = False 182 183 _back_right_x_pos : units . meter = inchesToMeters ( - 10 ) 184 _back_right_y_pos : units . meter = inchesToMeters ( - 10 ) 185 186 187 front_left = _constants_creator . create_module_constants ( 188 _front_left_steer_motor_id , 189 _front_left_drive_motor_id , 190 _front_left_encoder_id , 191 _front_left_encoder_offset , 192 _front_left_x_pos , 193 _front_left_y_pos , 194 _invert_left_side , 195 _front_left_steer_motor_inverted , 196 _front_left_encoder_inverted , 197 ) 198 front_right = _constants_creator . create_module_constants ( 199 _front_right_steer_motor_id , 200 _front_right_drive_motor_id , 201 _front_right_encoder_id , 202 _front_right_encoder_offset , 203 _front_right_x_pos , 204 _front_right_y_pos , 205 _invert_right_side , 206 _front_right_steer_motor_inverted , 207 _front_right_encoder_inverted , 208 ) 209 back_left = _constants_creator . create_module_constants ( 210 _back_left_steer_motor_id , 211 _back_left_drive_motor_id , 212 _back_left_encoder_id , 213 _back_left_encoder_offset , 214 _back_left_x_pos , 215 _back_left_y_pos , 216 _invert_left_side , 217 _back_left_steer_motor_inverted , 218 _back_left_encoder_inverted , 219 ) 220 back_right = _constants_creator . create_module_constants ( 221 _back_right_steer_motor_id , 222 _back_right_drive_motor_id , 223 _back_right_encoder_id , 224 _back_right_encoder_offset , 225 _back_right_x_pos , 226 _back_right_y_pos , 227 _invert_right_side , 228 _back_right_steer_motor_inverted , 229 _back_right_encoder_inverted , 230 ) 231 232 @classmethod 233 def create_drivetrain ( cls ) -> \"CommandSwerveDrivetrain\" : 234 \"\"\" 235 Creates a CommandSwerveDrivetrain instance. 236 This should only be called once in your robot program. 237 \"\"\" 238 from subsystems.command_swerve_drivetrain import CommandSwerveDrivetrain 239 240 return CommandSwerveDrivetrain ( 241 cls . drivetrain_constants , 242 [ 243 cls . front_left , 244 cls . front_right , 245 cls . back_left , 246 cls . back_right , 247 ], 248 ) 249 250 251 class TunerSwerveDrivetrain ( 252 swerve . SwerveDrivetrain [ hardware . TalonFX , hardware . TalonFX , hardware . CANcoder ] 253 ): 254 \"\"\"Swerve Drive class utilizing CTR Electronics' Phoenix 6 API with the selected device types.\"\"\" 255 256 @overload 257 def __init__ ( 258 self , 259 drivetrain_constants : swerve . SwerveDrivetrainConstants , 260 modules : list [ swerve . SwerveModuleConstants ], 261 / , 262 ) -> None : 263 \"\"\" 264 Constructs a CTRE SwerveDrivetrain using the specified constants. 265 266 This constructs the underlying hardware devices, so users should not construct 267 the devices themselves. If they need the devices, they can access them through 268 getters in the classes. 269 270 :param drivetrain_constants: Drivetrain-wide constants for the swerve drive 271 :type drivetrain_constants: swerve.SwerveDrivetrainConstants 272 :param modules: Constants for each specific module 273 :type modules: list[swerve.SwerveModuleConstants] 274 \"\"\" 275 ... 276 277 @overload 278 def __init__ ( 279 self , 280 drivetrain_constants : swerve . SwerveDrivetrainConstants , 281 odometry_update_frequency : units . hertz , 282 modules : list [ swerve . SwerveModuleConstants ], 283 / , 284 ) -> None : 285 \"\"\" 286 Constructs a CTRE SwerveDrivetrain using the specified constants. 287 288 This constructs the underlying hardware devices, so users should not construct 289 the devices themselves. If they need the devices, they can access them through 290 getters in the classes. 291 292 :param drivetrain_constants: Drivetrain-wide constants for the swerve drive 293 :type drivetrain_constants: swerve.SwerveDrivetrainConstants 294 :param odometry_update_frequency: The frequency to run the odometry loop. If 295 unspecified or set to 0 Hz, this is 250 Hz on 296 CAN FD, and 100 Hz on CAN 2.0. 297 :type odometry_update_frequency: units.hertz 298 :param modules: Constants for each specific module 299 :type modules: list[swerve.SwerveModuleConstants] 300 \"\"\" 301 ... 302 303 @overload 304 def __init__ ( 305 self , 306 drivetrain_constants : swerve . SwerveDrivetrainConstants , 307 odometry_update_frequency : units . hertz , 308 odometry_standard_deviation : tuple [ float , float , float ], 309 vision_standard_deviation : tuple [ float , float , float ], 310 modules : list [ swerve . SwerveModuleConstants ], 311 / , 312 ) -> None : 313 \"\"\" 314 Constructs a CTRE SwerveDrivetrain using the specified constants. 315 316 This constructs the underlying hardware devices, so users should not construct 317 the devices themselves. If they need the devices, they can access them through 318 getters in the classes. 319 320 :param drivetrain_constants: Drivetrain-wide constants for the swerve drive 321 :type drivetrain_constants: swerve.SwerveDrivetrainConstants 322 :param odometry_update_frequency: The frequency to run the odometry loop. If 323 unspecified or set to 0 Hz, this is 250 Hz on 324 CAN FD, and 100 Hz on CAN 2.0. 325 :type odometry_update_frequency: units.hertz 326 :param odometry_standard_deviation: The standard deviation for odometry calculation 327 in the form [x, y, theta]ᵀ, with units in meters 328 and radians 329 :type odometry_standard_deviation: tuple[float, float, float] 330 :param vision_standard_deviation: The standard deviation for vision calculation 331 in the form [x, y, theta]ᵀ, with units in meters 332 and radians 333 :type vision_standard_deviation: tuple[float, float, float] 334 :param modules: Constants for each specific module 335 :type modules: list[swerve.SwerveModuleConstants] 336 \"\"\" 337 ... 338 339 @overload 340 def __init__ ( 341 self , 342 drivetrain_constants : swerve . SwerveDrivetrainConstants , 343 arg0 : None , 344 arg1 : None , 345 arg2 : None , 346 arg3 : None , 347 / , 348 ) -> None : ... 349 350 def __init__ ( 351 self , 352 drivetrain_constants : swerve . SwerveDrivetrainConstants , 353 arg0 = None , 354 arg1 = None , 355 arg2 = None , 356 arg3 = None , 357 ): 358 swerve . SwerveDrivetrain . __init__ ( 359 self , 360 hardware . TalonFX , 361 hardware . TalonFX , 362 hardware . CANcoder , 363 drivetrain_constants , 364 arg0 , 365 arg1 , 366 arg2 , 367 arg3 , 368 )",
- "content_preview": "Swerve Builder API To simplify the API surface, both builder and factory paradigms are used. Users create a SwerveDrivetrain by first defining the global drivetrain characteristics and then each module characteristics. Note Phoenix 6 supports the Java units library when applicable."
+ "content": "Differential Overview Phoenix 6 has two robust differential mechanism APIs taking advantage of the motor controllers’ onboard differential controls. These APIs greatly simplify the setup and usage of common differential mechanisms, ranging from a differential wrist to a two-gearbox elevator without mechanical linkage. What is a Differential Mechanism? A differential mechanism has two axes of motion, where the position along each axis is determined by two motors in separate gearboxes: Driving both motors in a common direction causes the mechanism to move forward/reverse, up/down, etc. This is the Average axis : position is determined by the average of the two motors’ positions. Driving the motors in opposing directions causes the mechanism to twist or rotate left/right. This is the Difference axis : rotation is determined by half the difference of the two motors’ positions. As an example, a differential drivetrain has a few motors on each side of the robot. Driving both sides of the robot in the “forward” direction causes the robot to move forward. However, driving the left side “forward” and the right side “reverse” causes the robot to turn right. Another example is a two-gearbox elevator without mechanical linkage between the two sides. If the two sides of the elevator are not driven together, the elevator carriage twists, potentially breaking if the twist is too extreme. As a result, the elevator can be treated as a differential mechanism that always targets a difference of 0. In a more advanced setup, a remote sensor can be used on the Difference axis as an absolute sensor source. For example, a differential drivetrain can use the yaw of a Pigeon 2 to target an absolute heading. Differential Leader and Follower In a differential mechanism, one of the motor controllers is selected as the “differential leader”, while the other is selected as the “differential follower”. The leader is responsible for running all closed-loop calculations and applies Average + Difference to its output. The follower reports its position and velocity to the leader and applies Average - Difference to its output. The selection of the leader and follower motor controllers is only important when using a remote sensor on the difference axis. For example, consider a differential drivetrain using the yaw of a Pigeon 2. The Pigeon 2 is counter-clockwise positive, so the robot should rotate counter-clockwise (left) from a positive output on the Difference axis. This occurs when the right side drives forward (positive) and the left side drives reverse (negative). As a result, the right motor controller should be selected as the leader. Hardware Requirements All differential mechanism APIs require at least 2 Talon FX or Talon FXS motor controllers, one on each side of the mechanism. Optionally, a remote CANcoder, CANdi, or Pigeon 2 can be used on the Difference axis as an absolute sensor source. Note Both motor controllers must be of the same type. Overview of the API There are two differential mechanism APIs: DifferentialMechanism ( Java , C++ , Python ) Requires Phoenix Pro and CANivore . Full functionality, including full support for feedforwards and custom motion profiles. Difference axis supports open-loop control and position/velocity closed-loop control. Supports all control output type. SimpleDifferentialMechanism ( Java , C++ , Python ) Free and supports CAN 2.0. Limited functionality. Difference axis only supports position closed-loop control. Only supports Duty Cycle and Voltage control output types. Both types of mechanism are constructed using a DifferentialMotorConstants ( Java , C++ , Python ) object. Usage of these classes is available in the following articles in this section. Differential Mechanism Setup Using the Differential Mechanism API Tuning a Differential Mechanism",
+ "content_preview": "Differential Overview Phoenix 6 has two robust differential mechanism APIs taking advantage of the motor controllers’ onboard differential controls. These APIs greatly simplify the setup and usage of common differential mechanisms, ranging from a differential wrist to a two-gearbox elevator without..."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/migration/migration-guide/control-requests-guide.html",
- "title": "Control Requests",
- "section": "General",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/mechanisms/index.html",
+ "title": "Mechanisms",
+ "section": "API Reference",
"language": "All",
- "content": "Control Requests Phoenix 6 provides an extensive list of flexible control modes through the use of strongly-typed control requests. Note For more information about control requests in Phoenix 6, see Control Requests . Using Control Requests v5 Java // robot init, set voltage compensation to 12 V m_motor . configVoltageComSaturation ( 12 ); m_motor . enableVoltageCompensation ( true ); // main robot code, command 12 V output m_motor . set ( ControlMode . PercentOutput , 1.0 ); C++ // robot init, set voltage compensation to 12 V m_motor . ConfigVoltageComSaturation ( 12 ); m_motor . EnableVoltageCompensation ( true ); // main robot code, command 12 V output m_motor . Set ( ControlMode :: PercentOutput , 1.0 ); v6 Java // class member variable final VoltageOut m_request = new VoltageOut ( 0 ); // main robot code, command 12 V output m_motor . setControl ( m_request . withOutput ( 12.0 )); // the control request `with` methods also accept unit types m_motor . setControl ( m_request . withOutput ( Volts . of ( 12.0 ))); C++ // class member variable controls :: VoltageOut m_request { 0 _V }; // main robot code, command 12 V output m_motor . SetControl ( m_request . WithOutput ( 12 _V )); Follower Motors v5 Java // robot init, set m_follower to follow m_leader m_follower . follow ( m_leader ); // m_follower should NOT oppose m_leader m_follower . setInverted ( TalonFXInvertType . FollowMaster ); // set m_strictFollower to follow m_leader m_strictFollower . follow ( m_leader ); // set m_strictFollower to ignore m_leader invert and use its own m_strictFollower . setInverted ( TalonFXInvertType . CounterClockwise ); // main robot code, command 100% output for m_leader m_leader . set ( ControlMode . PercentOutput , 1.0 ); // - m_follower and m_strictFollower will also run at 100% output // - m_follower will follow m_leader's invert, while m_strictFollower // ignores it and uses its own // NOTE: if set(), neutralOutput(), or disable() is ever called on // the followers, they will stop following C++ // robot init, set m_follower to follow m_leader m_follower . Follow ( m_leader ); // m_follower should NOT oppose m_leader m_follower . SetInverted ( TalonFXInvertType :: FollowMaster ); // set m_strictFollower to follow m_leader m_strictFollower . Follow ( m_leader ); // set m_strictFollower to ignore m_leader invert and use its own m_strictFollower . SetInverted ( TalonFXInvertType :: CounterClockwise ); // main robot code, command 100% output for m_leader m_leader . Set ( ControlMode :: PercentOutput , 1.0 ); // - m_follower and m_strictFollower will also run at 100% output // - m_follower will follow m_leader's invert, while m_strictFollower // ignores it and uses its own // NOTE: if Set(), NeutralOutput(), or Disable() is ever called on // the followers, they will stop following v6 Java // class member variables final DutyCycleOut m_request = new DutyCycleOut ( 0 ); // robot init, set m_follower to follow m_leader // m_follower should NOT oppose leader m_follower . setControl ( new Follower ( m_leader . getDeviceID (), MotorAlignmentValue . Aligned )); // set m_strictFollower to strict-follow m_leader // strict followers ignore the leader's invert and use their own m_strictFollower . setControl ( new StrictFollower ( m_leader . getDeviceID ())); // main robot code, command 100% output for m_leader m_motor . setControl ( m_request . withOutput ( 1.0 )); // - m_follower and m_strictFollower will also run at 100% output // - m_follower will follow m_leader's invert, while m_strictFollower // ignores it and uses its own C++ // class member variables controls :: DutyCycleOut m_request { 0 }; // robot init, set m_follower to follow m_leader // m_follower should NOT oppose leader m_follower . SetControl ( controls :: Follower { m_leader . GetDeviceID (), false }); // set m_strictFollower to strict-follow m_leader // strict followers ignore the leader's invert and use their own m_strictFollower . SetControl ( controls :: StrictFollower { m_leader . GetDeviceID ()}); // main robot code, command 100% output for m_leader m_motor . SetControl ( m_request . WithOutput ( 1.0 )); // - m_follower and m_strictFollower will also run at 100% output // - m_follower will follow m_leader's invert, while m_strictFollower // ignores it and uses its own Changing Update Frequency (Control Frame Period) v5 Java // slow down the Control 3 frame (general control) to 50 Hz (20ms) m_talonFX . setControlFramePeriod ( ControlFrame . Control_3_General , 20 ); C++ // slow down the Control 3 frame (general control) to 50 Hz (20ms) m_talonFX . SetControlFramePeriod ( ControlFrame :: Control_3_General , 20 ); v6 Java // class member variables final DutyCycleOut m_request = new DutyCycleOut ( 0 ); // slow down the control request to 50 Hz m_request . UpdateFreqHz = 50 ; C++ // class member variables controls :: DutyCycleOut m_request { 0 }; // slow down the control request to 50 Hz m_request . UpdateFreqHz = 50 _Hz ; Tip UpdateFreqHz can be set to 0 Hz to synchronously one-shot the control request. In this case, users must ensure the control request is sent periodically in their robot code. Therefore, we recommend users call setControl no slower than 20 Hz (50 ms) when the control is one-shot. Control Types In Phoenix 6, voltage compensation has been replaced with the ability to directly specify the control output type . All control output types are supported in open-loop and closed-loop control requests. Open-loop Control Requests Phoenix 5 Phoenix 6 PercentOutput DutyCycleOut PercentOutput + Voltage Compensation VoltageOut Phoenix 5 does not support torque control TorqueCurrentFOC (requires Pro) Current closed-loop This has been deprecated in Phoenix 6. Users looking to control torque should use TorqueCurrentFOC (requires Pro) Users looking to limit current should use supply and stator current limits Closed-loop Control Requests Phoenix 5 Phoenix 6 Position PositionDutyCycle Velocity VelocityDutyCycle MotionMagic MotionMagicDutyCycle Closed-loop + Voltage Compensation {ClosedLoop}Voltage Closed-loop + Torque Control (not supported in Phoenix 5) {ClosedLoop}TorqueCurrentFOC (requires Pro)",
- "content_preview": "Control Requests Phoenix 6 provides an extensive list of flexible control modes through the use of strongly-typed control requests. Note For more information about control requests in Phoenix 6, see Control Requests ."
+ "content": "Mechanisms This section serves to provide API usage of mechanisms supported by Phoenix 6. Swerve Documentation on the Phoenix 6 Swerve API Swerve Overview Differential Documentation on the Phoenix 6 Differential Mechanism APIs Differential Overview Generating Mechanisms Mechanisms such as swerve or an elevator can be generated using Tuner X , greatly simplifying the setup process and eliminating many sources of error. Additionally, the corvus CLI tool can be used from a terminal to generate a mechanism from a JSON specification, including a full swerve project. corvus can be downloaded from the CLI Tools download page . To view a list of available commands, run corvus either with no parameters or with --help . As an example, to generate an example Elevator subsystem for a Java robot program, run: ./corvus json elevator \"Elevator.json\" ./corvus elevator java -i \"Elevator.json\" -o \"src/main/java/frc/robot/subsystems/Elevator.java\"",
+ "content_preview": "Mechanisms This section serves to provide API usage of mechanisms supported by Phoenix 6. Swerve Documentation on the Phoenix 6 Swerve API Swerve Overview Differential Documentation on the Phoenix 6 Differential Mechanism APIs Differential Overview Generating Mechanisms Mechanisms such as swerve or..."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/api-usage/orchestra.html",
- "title": "Orchestra",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/simulation/index.html",
+ "title": "Simulation",
"section": "API Reference",
"language": "All",
- "content": "Orchestra Compatible motors powered by Talon FX have the ability to produce audible output through the MusicTone ( Java , C++ , Python ) control request. The Orchestra API extends this ability and orchestrates multiple motors to play music. To get started, construct an Orchestra ( Java , C++ , Python ) object with an instrument and CHRP. Ensure that addInstrument() and loadMusic() are not called periodically, as they are blocking functions. Note For information on converting MIDI to CHRP, see CHRP Converter . Java Orchestra m_orchestra = new Orchestra (); // Add a single device to the orchestra m_orchestra . addInstrument ( m_motor ); // Attempt to load the chrp var status = m_orchestra . loadMusic ( \"track.chrp\" ); if ( ! status . isOK ()) { // log error } C++ Orchestra m_orchestra ; // Add a single device to the orchestra m_orchestra . addInstrument ( m_motor ); // Attempt to load the chrp auto status = m_orchestra . loadMusic ( \"track.chrp\" ); if ( ! status . IsOK ()) { // log error } Python self . orchestra = Orchestra () self . orchestra . add_instrument ( self . motor ); status = self . orchestra . load_music ( \"track.chrp\" ) if not status . is_ok (): # log error Once the track has been loaded, play/pause/stop can be used to manage the track. play() only needs to be called once. Java m_orchestra . play (); C++ m_orchestra . Play (); Python self . orchestra . play () Playback While Disabled (FRC) Playback can be safely enabled during robot disable by enabling the Allow Music Dur Disable ( Java , C++ , Python ) config.",
- "content_preview": "Orchestra Compatible motors powered by Talon FX have the ability to produce audible output through the MusicTone ( Java , C++ , Python ) control request. The Orchestra API extends this ability and orchestrates multiple motors to play music."
+ "content": "Simulation Phoenix 6 supports comprehensive simulation support. All hardware features are available in simulation, including configs, control requests, simulated CAN bus timing, and Phoenix Tuner X support. Introduction to Simulation",
+ "content_preview": "Simulation Phoenix 6 supports comprehensive simulation support. All hardware features are available in simulation, including configs, control requests, simulated CAN bus timing, and Phoenix Tuner X support. Introduction to Simulation"
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/hardware-reference/pigeon2/index.html",
- "title": "Pigeon 2.0",
- "section": "Pigeon 2",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/application-notes/tuner-evolution.html",
+ "title": "Tuner and an evolution in configuration",
+ "section": "Application Notes",
"language": "All",
- "content": "Pigeon 2.0 Pigeon 2.0 is the next evolution in the family of Pigeon IMUs. With no on-boot calibration or temperature calibration required and dramatic improvement to drift, the Pigeon is the easiest IMU to use yet. Pigeon 2 Troubleshooting Store Page CAD and purchase instructions. https://store.ctr-electronics.com/pigeon-2/ Hardware User Manual Wiring and mount instructions in PDF format. https://store.ctr-electronics.com/content/user-manual/Pigeon2%20User’s%20Guide.pdf Status Light Reference Important If the status lights do not exactly match any of the blink codes below, the device may be alternating between multiple blink codes (most commonly between good and bad CAN). Blink Codes Animation (Click to play) LED State Cause Possible Fix LEDs Off No Power Provide 12V to Red/Black leads. Blinking Alternating Red Pigeon 2 does not have valid CAN. Ensure good connections between CANH and CANL (Yellow and Green) & robot controller is on. Blinking Alternating Orange Pigeon 2 detects CAN but does not see Phoenix running on the robot controller. If Phoenix is running on the robot controller, ensure good connection between the controller and this device. Otherwise, deploy a robot program that uses Phoenix. Blinking Simultaneous Orange Pigeon 2 detects CAN and sees the robot is disabled. Phoenix is running in robot controller and Pigeon 2 has good CAN connection to robot controller. Blinking Alternating Green Pigeon 2 detects CAN and sees the robot is enabled. Alternate Red/Orange Damaged Hardware. Use Tuner X Self Test to confirm the LEDs and that the hardware fault is set, then contact CTRE Single LED alternates Green/Orange Pigeon 2 in bootloader. Field-upgrade device in Tuner X. Mount Calibration It’s recommended to perform a mount calibration when placement of the Pigeon 2.0 has been finalized. This can be done via the Calibration page in Tuner X.",
- "content_preview": "Pigeon 2.0 Pigeon 2.0 is the next evolution in the family of Pigeon IMUs. With no on-boot calibration or temperature calibration required and dramatic improvement to drift, the Pigeon is the easiest IMU to use yet. Pigeon 2 Troubleshooting Store Page CAD and purchase instructions."
+ "content": "Tuner and an evolution in configuration Authored by Dalton Since the introduction of the CTRE Toolsuite (pre-2018), we at CTR have strived to provide intuitive means of configuring and utilizing our products. In 2018, we launched Phoenix Tuner (now lovingly referred to as Tuner v1). Tuner v1 introduced features like: batch firmware upgrading like devices, diagnostic server deployment, self tests, plotting and control. With Tuner, and by extension diagnostics, we have several primary objectives: Ease of debugging (exposed via self test). Seamless setup experience. Support and integrate our extension feature-set. Tuner v1 was and is great, but we wanted to do more. In the 2023 season, we introduced Tuner X. Introducing Tuner X The goal we had with Tuner X development was to refine and enhance the existing Tuner v1 feature set. We introduced Android support , improved batch upgrading, improved highlighting of duplicate devices, automatic firmware downloads (no more downloading CRFs!), improved self test and licensing support. With Tuner X, users can: Configure their device’s name & ID Blink a device, which is useful for identifying where the device is on the robot. Firmware update all devices to the latest version available (no more CRF downloads). Control individual motors with their Android phone, or on Windows. Plot various signals such as velocity, position and yaw. Self test their v6 device, which provides a marked up self test of the device. Important Tuner X does not require v6 and can be used with v5 flashed devices. For a full list of features, check out the v6 documentation . Introducing a new iteration of Tuner X Some of you may have noticed that your version of Tuner X has changed recently. We’ve been working on several key improvements to the application that should dramatically improve the user experience. While this blog will highlight some of those, it’s best to just try out the new Tuner yourself. Note Feedback is welcome and can be provided by emailing feedback @ ctr-electronics . com . Improved connection diagnostics Tuner requires a running diagnostic server to work. Typically, this is installed through a robot program utilizing one of our devices. Alternatively, this program is temporarily run using a button in Settings . We’ve improved the disconnection status card to contain information about the ping of the target and diagnostic state of the device. This 3 step check looks for the following: Ping of the target. Is diagnostics (or a robot program with diagnostics) running? Are there any devices reported? To summarize, if a user is not seeing devices in Tuner but checks 1 and 2 are good, then the next recommendation is to check the LED status of the device. We have an extensive list of status LEDs that indicate if the device is detected on a CAN bus, or other problems. This list can be found on the corresponding device page in the docs. For example, look at the CANcoder LED table . Redesigned device overview The device overview page has been redesigned to improve usability of plot, control and configuration. It’s never been easier to tune your closed-loop gains directly in Tuner! Bug squashing and usability improvements This list is by no means exhaustive, but provides a good idea of the changes between 2023.X and 2024 versions of Tuner X. Firmware selection now has a dropdown for year, allowing you to flash older year firmware Dramatically improved startup and navigation performance Dramatically improved plotting performance Dramatically improved commands timing out on Android Tuner Enable/Disable button colors have been adjusted to be more clear Fixed “connection blipping” on Android Tuner Fixed control sometimes stuttering and causing the device to disable Fixed licensing sometimes fail to load on Android Tuner Fixed SSH credentials popup not appearing sometimes Fixed lag when entering into various entries Fixed memory leak when plotting for long periods of time Fixed situation where the application would shutdown uncleanly and lose settings Fixed various clipping of icons, text and labels Fixed issue where CANivore USB toggle would be unable to enable or disable Fixed firmware flashing on Raspberry Pi Fixed temporary diagnostic deployment on non-RIO platforms Slows down CANivore polling, which improves Rio CPU performance when Tuner is open What’s next? We have a couple of exciting improvements to Tuner on our radar, keep an eye out on our changelog . Tuner X can be downloaded via the Microsoft Store and the Google Play Store .",
+ "content_preview": "Tuner and an evolution in configuration Authored by Dalton Since the introduction of the CTRE Toolsuite (pre-2018), we at CTR have strived to provide intuitive means of configuring and utilizing our products. In 2018, we launched Phoenix Tuner (now lovingly referred to as Tuner v1)."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/device-details-page.html",
- "title": "Device Details",
- "section": "Phoenix Tuner X",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/yearly-changes/yearly-changelog.html",
+ "title": "New for 2026",
+ "section": "General",
"language": "All",
- "content": "Device Details The Device Details page can be accessed by clicking on the device card (or clicking on View more details… when in grid view). This view allows you to access detailed device actions such as: Device Details (Name, ID, Firmware Version, Model, Serial No, etc.) Blinking LEDs Field Upgrading Licensing Details (by clicking on the LIC/PRO icon) Configs Control Self Tests Plotting Pigeon 2 Mount Calibration Blinking All CTR Electronics devices can be blinked (rapidly flash the LEDs). This can be useful for handling whenever you have duplicate devices using the same ID on the CAN bus. Verifying Device Details This screen highlights information such as (1) Device Name, (2) Device Model, (3) Firmware Version. Tip Clicking in the blank space outside the detail frames will bring the user back to the devices page. Configuring Name & IDs All devices can have their Name and ID configured via their respective textbox. IDs are limited to the range of 0 to 62 (inclusive). After inputting the ID or name, press the Set button to save the changes to the device. Field-Upgrade Firmware Version Tuner X has improved firmware upgrading functionality by automatically downloading and caching firmware. Upon initial Tuner X launch, the latest firmware for all devices will automatically be downloaded in the background (takes <10s on most internet connections). The individual device page allows you to select specific firmware versions for your device via the firmware dropdown. Batch firmware can also be completed via the batch field upgrade pop-up . Important Users should ensure they select Phoenix 6 firmware when using Phoenix 6 API, and Phoenix 5 firmware when using Phoenix 5 API. A single robot project may use both APIs simultaneously. Users can switch between firmware release years by selecting from the dropdown above the firmware selection. Note The toggle between firmware years only affects the firmware versions downloaded by Tuner X. Files selected using the “Browse” button are not affected.",
- "content_preview": "Device Details The Device Details page can be accessed by clicking on the device card (or clicking on View more details… when in grid view). This view allows you to access detailed device actions such as: Device Details (Name, ID, Firmware Version, Model, Serial No, etc.) Blinking LEDs Field..."
+ "content": "New for 2026 Engineering never stops, and neither do we. At CTR Electronics, we are constantly analyzing and reflecting on our software, documentation and hardware integration. This past year, we’ve made immense investments in our differential stack, data analysis with Tuner, data logging and replay pipeline and much more. We are proud to present our new for 2026 changelog! Firmware for the 2026 release of Phoenix 6 can be found by selecting “2026” in the firmware selection menu. The API vendordep for 2026 is available under https://maven.ctr-electronics.com/release/com/ctre/phoenix6/latest/Phoenix6-frc2026-latest.json . Users will need to update both firmware and API to make use of these features. Note This changelog is intended to highlight the major additions to the Phoenix 6 ecosystem. For a detailed list of changes and bug fixes, visit the API changelog . API Breaking Changes The new (int id, String canbus) constructors are now deprecated and will be removed in 2027. Use the new (int id, CANBus canbus) constructors instead. This change is intended to prepare users for 2027, where an explicit CAN bus declaration is necessary. The minimum supported C++ version is now C++ 20, and the minimum Linux requirement is Ubuntu 22.04 / Debian Bookworm. Linux ARM32 is no longer supported. C++: Improved robot project compilation times. This results in the following breaking changes: The ctre/phoenix6/configs/Configs.hpp header has been split into separate files. In generated swerve projects, TunerConstants.h must now explicitly include the motor controllers and encoder used. DifferentialMechanism and SimpleDifferentialMechanism have been reworked to more closely align with the swerve API. The behavior of the difference axis has also been adjusted. Signal Logger Improvements & Behavior Changes Signal logger has had quite a few changes over the off-season. Signal Logger now has support for Protobuf and WPILib Structs , arbitrarily sized user signals, and annotation-based logging as an Epilogue Backend . The behavior of the signal logger auto-start functionality has also been changed in response to user feedback to make the experience consistent between at-home testing and events. Signal Logging auto start is only enabled on the roboRIO 1 if a flash drive is present, and otherwise enabled by default on the roboRIO 2. Signal logging is started by any of the following (whichever occurs first): The robot is enabled It has been at least 5 seconds since program startup (allowing for calls to setPath ), and the Driver Station is connected to the robot. Additionally, OptimizeBusUtilization() now defaults to setting optimized signals to 4 Hz instead of 0 Hz, preserving data for hoot logs. Users can still get the old behavior by explicitly passing in 0 Hz. Miscellaneous Changes Logs from the same robot program instance / FRC match will now be grouped into a subdirectory named after the match and the date/time of the start of the match. The list of Free Signals (including custom user signals) is no longer limited to WPILOG export and can now be exported to MCAP. Hoot Replay Improvements Hoot replay has been enhanced to simplify use cases beyond status signal playback. A HootAutoReplay API has been added to easily register custom “input” signals for logging on hardware and playback in replay. This also enables easy replay of the robot FPGA timestamp and joystick information. private final Camera camera = new Camera (...); private PoseEstimate cameraPoseEst = new PoseEstimate (); private final HootAutoReplay autoReplay = new HootAutoReplay () . withTimestampReplay () . withJoystickReplay () . withStruct ( \"CameraPoseEst/pose\" , Pose2d . struct , /* getter lambda returns the value to log */ () -> cameraPoseEst . pose , /* setter lambda applies the value from the log */ val -> cameraPoseEst . pose = val . value ) . withDouble ( \"CameraPoseEst/timestamp\" , () -> cameraPoseEst . timestamp , val -> cameraPoseEst . timestamp = val . value ); private void fetchInputs () { cameraPoseEst = camera . getPoseEstimate (); } @Override public void robotPeriodic () { if ( ! Utils . isReplay ()) { fetchInputs (); } autoReplay . update (); } We highly recommend reading over the improved hoot replay documentation for understanding how to integrate hoot replay into your robot program. Additionally, the Signal Logger is now supported in Hoot Replay with the following behavior: Signal Logger is always enabled during replay. As a result, SignalLogger::Start() and SignalLogger::Stop() are ignored. The replayed log will always be written to a replay__/ subfolder next to the original log. All custom signals written during replay will be automatically placed under hoot_replay/ in the log. The replayed log also contains all status signals and custom signals from the original log, excluding those starting with hoot_replay/ . This can be useful to log the new outputs after making changes to program logic. Investments in Differential Our differential API has seen a variety of changes and feature additions that should enhance and expand the capabilities of teams building differential systems (such as Differential Wrists or Elevators). SimpleDifferentialMechanism and DifferentialMechanism have been reworked to more closely align with swerve. Both APIs now take a DifferentialMotorConstants object on construction, internally construct and configure the motor controllers (with initial configs objects), and provide useful APIs such as getAveragePosition() and setPosition(avg, diff) . Support has also been added for MotionMagicExpo and MotionMagicVelocity on the average axis. Additionally, configs have been added to control the behavior of continuous wrapping and gear ratios on the difference axis. This also comes with a breaking change in behavior on the differential axis: the full differential output is now added/subtracted from each motor, and the difference axis uses half the difference in position/velocity between the two motors. This effectively means that PID gains do not change, but setpoints on the difference axis must be halved. User documentation for the differential API can be found here . We highly recommend users read over the full changelog for a full list changes to the differential API. Improvements to Swerve Swerve has seen a number of enhancements. We’ve added a LinearPath API that generates a linear path between two poses with constant velocity and acceleration limits. A WheelForceCalculator has also been added that calculates the wheel force feedforwards to apply for the given target robot accelerations or change in ChassisSpeeds , based on the robot mass and MOI. The generated swerve project has been updated to include a default “drive straight” autonomous command that slowly drives forward for 5 seconds. Additionally, the SeedFieldCentric(Rotation2d) overload has been added to reset the heading of the robot to the given operator-perspective heading. The generated swerve project has also been updated to include a simple drive-straight auton and log to SignalLogger using the new WPILib Struct and Struct array support. Additional Language Support for C# C# has received some much needed updates that bring it to feature parity with the other supported languages. C# fits the middle-ground for users looking for type safety without using an unmanaged language like C++. Check out the installation instructions on installing the nuget. Additional Utility Functions We’ve added a couple new utility functions, specifically for interacting with status signals. Added StatusSignal::IsNear(target, tolerance) utility function that checks whether the signal is near a target value given tolerance. Added StatusSignalCollection , a lightweight List wrapper that provides waitForAll / refreshAll /etc. This can be used to easily register status signals from multiple classes for a single refreshAll call. Java: Added List overloads to waitForAll / refreshAll /etc. C++: Replaced the std::vector and std::array overloads with a std::span overload for APIs such as WaitForAll / RefreshAll / OptimizeBusUtilization /etc. Enhancements to Error Reporting Some changes have been made to the API to improve error reporting. StatusSignal WaitForAll() and RefreshAll() now report errors for all the status signals involved that errored. StatusSignal WaitForAll() and RefreshAll() now propagate an InvalidNetwork error to all the provided status signals. Talon FX Improvements Improvements to follower have been made, specifically in regards to when the leader active request is Voltage-based. Follower and DifferentialFollower now follow MotorVoltage instead of DutyCycle when the leader is running a voltage control output type. Follower now follows the leader’s coast/brake. Additionally, Dynamic Motion Magic® Expo control requests have been added. Furthermore, the GravityArmPositionOffset config has also been added to offset the position used for arm kG calculations (within (-0.25, 0.25) rot). Miscellaneous Changes Added support for simple gain scheduling in position PID closed-loop control based on closed-loop error. For more information, see the API documentation of ClosedLoopGeneralConfigs::GainSchedErrorThreshold , ClosedLoopGeneralConfigs::GainSchedKpBehavior , and SlotConfigs::GainSchedBehavior . Added MotionMagicAtTarget status signal, which returns whether the motion profile has completed (equivalent to checking that MotionMagicIsRunning , the ClosedLoopReference is the final target, and the ClosedLoopReferenceSlope is 0). Kraken X44 Improvements to FOC have been made to improve peak performance. Additionally, Kraken X44 simulation support has been added via TalonFXSimState::SetMotorType . Talon FXS Our non-FRC customers can now utilize generic sensored (with hall effects) BLDC motors up to 24V on Talon FXS. This enables use cases for industrial motors in automation. Take a look at the Custom Brushless Motor configs for more information on how to configure this. Phoenix Tuner X Tuner has seen a number of new feature additions, bug fixes, and general improvements. Elevator Mechanism Support Under the Mechanisms page in Tuner X is the Elevator Generator. This utility guides the user through determining the necessary constants and configurations for a working Elevator subsystem. Take a read through the relevant documentation pages for information on how this works! Multi-device Control & Plot One of the most asked features of Phoenix Tuner has been the ability to control and plot multiple devices simultaneously. We are proud to announce that this feature will now be available for the 2026 season. This page can be found in the left-hand sidebar. Integrated Log Analysis (Beta) Hoot logs can now be directly plotted in Tuner. This feature is marked as beta as we continue to refine and improve the process. This page is available by going to Tools (magic wand in the sidebar) and then clicking on Log Analyzer (Beta) . Feedback is welcome and can be submitted by sending us an email ! CANdle Animations Preview The traditional control interface has been replaced with a CANdle animations tab, directly in Tuner. This allows you to have a real-time preview of the state of your LED strip and also includes generation functionality. The built-in generator will take your current CANdle animations and solid colors, and create a subsystem that you can implement in your robot program. Miscellaneous Improvements Beyond the big ticket feature adds is loads of smaller enhancements throughout the application. Reworked the flyout to be minimized by default and show a flat list of icons. Reworked configs to be a nested menu. This improves the performance and makes it more reusable across the application. Added support for extracting and deleting folders on the Hoot Extractor page. Added batch licensing to device history.",
+ "content_preview": "New for 2026 Engineering never stops, and neither do we. At CTR Electronics, we are constantly analyzing and reflecting on our software, documentation and hardware integration."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/device-specific/talonfx/closed-loop-requests.html",
- "title": "Closed",
- "section": "TalonFX",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/wpilib-integration/sysid-integration/plumbing-and-running-sysid.html",
+ "title": "Plumbing & Running SysId",
+ "section": "API Reference",
"language": "All",
- "content": "Closed-Loop Overview Closed-loop control typically refers to control of a motor that relies on sensor data to adjust based on error. Systems/mechanisms that rely on maintaining a certain position or velocity achieve this state using closed-loop control. This is achieved by feedback (PID) and feedforward control. Closed-loop control can be performed on the robot controller or on the individual motor controllers. The benefits of onboard closed-loop control are that there is no sensor latency, and the closed-loop controller has a 1 kHz update frequency. This can result in a more responsive output compared to running the closed-loop on the robot controller. Since closed-loop control changes based on the dynamics of the system (velocity, mass, CoG, etc.), closed-loop relies on PID and feedforward parameters. These parameters are configured either via Tuner Configs or in code . The parameters can be determined using System Identification (such as with WPILib SysId ) or through manual tuning . Manual tuning typically follows this process: Set all gains to zero. Determine \\(K_g\\) if using an elevator or arm . Select the appropriate Static Feedforward Sign for your closed-loop type. Increase \\(K_s\\) until just before the motor moves. If using velocity setpoints, increase \\(K_v\\) until the output velocity closely matches the velocity setpoints. Increase \\(K_p\\) until the output starts to oscillate around the setpoint. Increase \\(K_d\\) as much as possible without introducing jittering to the response. All closed-loop control requests follow the naming pattern {ClosedLoopMode}{ControlOutputType} . For example, the VelocityVoltage control request performs a velocity closed-loop using voltage output. Choosing Output Type The choice of control output type can affect the reproducibility and stability of the closed-loop control. DutyCycle has the benefit of being the simplest control output type, as it is unaffected by voltage and current measurements. However, because DutyCycle represents a proportion of the supply voltage, changes in battery voltage can affect the reproducibility of the control request. Voltage control output takes into account the supply voltage to ensure its voltage output remains consistent. As a result, Voltage control often results in more stable and reproducible behavior compared to DutyCycle control, so Voltage control is often preferred. A disadvantage with both DutyCycle and Voltage control output types is that they control acceleration indirectly and require a velocity feedforward \\(K_v\\) to hold a constant velocity. On the other hand, torque-based control output types, such as TorqueCurrentFOC, directly control acceleration , which has several advantages: Since the torque request is directly proportional to acceleration, \\(K_v\\) is generally unnecessary. A torque output of 0 corresponds to a constant velocity, assuming no external forces. \\(K_a\\) can be tuned independently of all the other closed-loop gains by comparing the measured acceleration with the requested acceleration. Because the output is in units of torque, the units of the gains more closely match those of forces in the real world. As a result, torque-based control output types offer more stable and reproducible behavior that can be easier to tune compared to the other control output types. Gain Slots It may be useful to switch between presets of gains in a motor controller, so the TalonFX supports multiple gain slots. All closed-loop control requests have a member variable Slot that can be assigned an integer ID to select the set of gains used by the closed-loop. The gain slots can be configured in code using Slot*Configs ( Java , C++ , Python ) objects. Gravity Feedforward The gravity feedforward \\(K_g\\) is the output necessary to overcome gravity, in units of the control output type . Phoenix 6 supports the two most common use cases for \\(K_g\\) —elevators and arms—using the GravityType config in the gain slots. Elevator/Static For systems with a constant gravity component, such as an elevator, \\(K_g\\) adds a constant value to the closed-loop output. To find \\(K_g\\) , determine the output necessary to hold the elevator at a constant height in open-loop control. Arm/Cosine For systems with an angular gravity component, such as an arm, the output of \\(K_g\\) is dependent on the cosine of the angle between the arm and horizontal. The value of \\(K_g\\) can be found by determining the output necessary to hold the arm horizontally forward. Since the arm \\(K_g\\) uses the angle of the arm relative to horizontal, the Talon FX often requires an absolute sensor whose position is 1:1 with the arm, and the sensor offset and ratios must be configured. When using an absolute sensor, such as a CANcoder, the sensor offset must be configured such that a position of 0 represents the arm being held horizontally forward. From there, the RotorToSensor ratio must be configured to the ratio between the absolute sensor and the Talon FX rotor. Some arm mechanisms have a center of gravity that is offset from the zero position. The GravityArmPositionOffset config can be adjusted to account for this offset within ±0.25 rotations. Static Feedforward Sign The static feedforward \\(K_s\\) is the output needed to overcome the system’s static friction, in units of the control output type . Because friction always opposes the direction of motion, the sign of \\(K_s\\) also depends on the direction of motion. Phoenix 6 provides two possible methods of determining this signage using the StaticFeedforwardSign config in the gain slots. Velocity Sign By default, the signage of \\(K_s\\) is determined by the signage of the velocity setpoint. In other words, if the velocity setpoint is positive, then the output of \\(K_s\\) is positive; if the velocity setpoint is negative, then \\(K_s\\) is negative. This option is always used when running velocity closed loops, and it is recommended for Motion Magic® controls and motion-profiled position closed loops. Closed-Loop Sign When using a position closed-loop controller, signage of \\(K_s\\) can instead be determined by the sign of the closed-loop error. For example, if the position error (target - measured) is positive, then the output of \\(K_s\\) is positive; if the error is negative, then \\(K_s\\) is negative. This option is typically used when a velocity setpoint is otherwise not available, such as when running unprofiled position closed loops. Important When using the sign of closed-loop error for \\(K_s\\) , it is important that the selected \\(K_s\\) value is not too large. Otherwise, the motor output may dither or oscillate when near the closed-loop target. Converting from Meters In some applications, it may be useful to translate between meters and rotations. This can be done using the following equation: \\[rotations = \\frac{meters}{2 \\pi \\cdot wheelRadius} \\cdot gearRatio\\] where meters is the target in meters, wheelRadius is the radius of the wheel in meters, and gearRatio is the gear ratio between the output shaft and the wheel. This equation also works with converting velocity from m/s to rps or acceleration from m/s² to rps/s. Continuous Mechanism Wrap A continuous mechanism is a mechanism with unlimited travel in any direction, and whose rotational position can be represented with multiple unique position values. Some examples of continuous mechanisms are swerve drive steer mechanisms or turrets (without cable management). ContinuousWrap ( Java , C++ , Python ) is a mode of closed loop operation that enables the Talon to take the “shortest path” to a target position for a continuous mechanism. It does this by assuming that the mechanism is continuous within 1 rotation. For example, if a Talon is currently at 2.1 rotations, it knows this is equivalent to every position that is exactly 1.0 rotations away from each other (3.1, 1.1, 0.1, -0.9, etc.). If that Talon is then commanded to a position of 0.8 rotations, instead of driving backwards 1.3 rotations or forwards 0.7 rotations, it will drive backwards 0.3 rotations to a target of 1.8 rotations. Note The ContinuousWrap config only affects the closed loop operation. Other signals such as Position are unaffected by this config. In order to use this feature, the FeedbackConfigs ( Java , C++ , Python ) ratio configs must be configured so that the mechanism is properly described. An example is provided below, where there is a continuous mechanism with a 12.8:1 speed reduction between the rotor and mechanism.",
- "content_preview": "Closed-Loop Overview Closed-loop control typically refers to control of a motor that relies on sensor data to adjust based on error. Systems/mechanisms that rely on maintaining a certain position or velocity achieve this state using closed-loop control."
+ "content": "Plumbing & Running SysId For the purpose of this documentation, we focus on the integration of Phoenix 6 and WPILib’s SysId API to characterize common mechanisms. Detailed documentation on the SysId routines can be found here . To get started, users must construct a SysIdRoutine that defines a Config and Mechanism . The Config constructor allows the users to define the voltage ramp rate, dynamic step voltage, characterization timeout, and a lambda that accepts the SysIdRoutineLog.State for logging. The lambda needs to be overridden to log the State string using the Phoenix 6 Signal Logger . The Mechanism constructor takes a lambda accepts a Measure . This lambda is used to apply the voltage request to the motors during characterization, which can be done using a VoltageOut request. The second argument to the constructor is a logging callback; this is left null when using the Signal Logger, as all signals are logged automatically. The last parameter is a reference to this Subsystem . Putting this all together results in the example shown below. Java private final TalonFX m_motor = new TalonFX ( 0 ); private final VoltageOut m_voltReq = new VoltageOut ( 0.0 ); private final SysIdRoutine m_sysIdRoutine = new SysIdRoutine ( new SysIdRoutine . Config ( null , // Use default ramp rate (1 V/s) Volts . of ( 4 ), // Reduce dynamic step voltage to 4 to prevent brownout null , // Use default timeout (10 s) // Log state with Phoenix SignalLogger class ( state ) -> SignalLogger . writeString ( \"state\" , state . toString ()) ), new SysIdRoutine . Mechanism ( ( volts ) -> m_motor . setControl ( m_voltReq . withOutput ( volts . in ( Volts ))), null , this ) ); C++ hardware :: TalonFX m_motor { 0 }; controls :: VoltageOut m_voltReq { 0 _V }; frc2 :: sysid :: SysIdRoutine m_sysIdRoutine { frc2 :: sysid :: Config { std :: nullopt , // Use default ramp rate (1 V/s) 4 _V , // Reduce dynamic step voltage to 4 to prevent brownout std :: nullopt , // Use default timeout (10 s) // Log state with Phoenix SignalLogger class []( frc :: sysid :: State state ) { SignalLogger :: WriteString ( \"state\" , frc :: sysid :: SysIdRoutineLog :: StateEnumToString ( state )); } }, frc2 :: sysid :: Mechanism { [ this ]( units :: volt_t volts ) { m_motor . SetControl ( m_voltReq . WithOutput ( volts )); }, []( auto ) {}, this } }; Python self . motor = hardware . TalonFX ( 0 ) self . voltage_req = controls . VoltageOut ( 0 ) self . sys_id_routine = SysIdRoutine ( SysIdRoutine . Config ( # Use default ramp rate (1 V/s) and timeout (10 s) # Reduce dynamic voltage to 4 to prevent brownout stepVoltage = 4.0 , # Log state with Phoenix SignalLogger class recordState = lambda state : SignalLogger . write_string ( \"state\" , SysIdRoutineLog . stateEnumToString ( state )) ), SysIdRoutine . Mechanism ( lambda volts : self . motor . set_control ( self . voltage_req . with_output ( volts )), lambda log : None , self ) ) Now that the routine has been plumbed, the characterization commands need to be exposed from the subsystem. Java public Command sysIdQuasistatic ( SysIdRoutine . Direction direction ) { return m_sysIdRoutine . quasistatic ( direction ); } public Command sysIdDynamic ( SysIdRoutine . Direction direction ) { return m_sysIdRoutine . dynamic ( direction ); } C++ frc2 :: CommandPtr SysIdQuasistatic ( frc2 :: sysid :: Direction direction ) { return m_sysIdRoutine . Quasistatic ( direction ); } frc2 :: CommandPtr SysIdDynamic ( frc2 :: sysid :: Direction direction ) { return m_sysIdRoutine . Dynamic ( direction ); } Python def sys_id_quasistatic ( self , direction : SysIdRoutine . Direction ) -> Command : return self . sys_id_routine . quasistatic ( direction ) def sys_id_dynamic ( self , direction : SysIdRoutine . Direction ) -> Command : return self . sys_id_routine . dynamic ( direction ) From there, the program can bind buttons to these commands in RobotContainer . Java m_joystick . leftBumper (). onTrue ( Commands . runOnce ( SignalLogger :: start )); m_joystick . rightBumper (). onTrue ( Commands . runOnce ( SignalLogger :: stop )); /* * Joystick Y = quasistatic forward * Joystick A = quasistatic reverse * Joystick B = dynamic forward * Joystick X = dyanmic reverse */ m_joystick . y (). whileTrue ( m_mechanism . sysIdQuasistatic ( SysIdRoutine . Direction . kForward )); m_joystick . a (). whileTrue ( m_mechanism . sysIdQuasistatic ( SysIdRoutine . Direction . kReverse )); m_joystick . b (). whileTrue ( m_mechanism . sysIdDynamic ( SysIdRoutine . Direction . kForward )); m_joystick . x (). whileTrue ( m_mechanism . sysIdDynamic ( SysIdRoutine . Direction . kReverse )); C++ m_joystick . LeftBumper (). OnTrue ( frc2 :: cmd :: RunOnce ( SignalLogger :: Start )); m_joystick . RightBumper (). OnTrue ( frc2 :: cmd :: RunOnce ( SignalLogger :: Stop )); /* * Joystick Y = quasistatic forward * Joystick A = quasistatic reverse * Joystick B = dynamic forward * Joystick X = dynamic reverse */ m_joystick . Y (). WhileTrue ( m_mechanism . SysIdQuasistatic ( frc2 :: sysid :: Direction :: kForward )); m_joystick . A (). WhileTrue ( m_mechanism . SysIdQuasistatic ( frc2 :: sysid :: Direction :: kReverse )); m_joystick . B (). WhileTrue ( m_mechanism . SysIdDynamic ( frc2 :: sysid :: Direction :: kForward )); m_joystick . X (). WhileTrue ( m_mechanism . SysIdDynamic ( frc2 :: sysid :: Direction :: kReverse )); Python self . joystick . leftBumper () . onTrue ( cmd . runOnce ( SignalLogger . start )) self . joystick . rightBumper () . onTrue ( cmd . runOnce ( SignalLogger . stop )) # Joystick Y = quasistatic forward # Joystick A = quasistatic reverse # Joystick B = dynamic forward # Joystick X = dynamic reverse self . joystick . y () . whileTrue ( self . mechanism . sys_id_quasistatic ( SysIdRoutine . Direction . kForward )) self . joystick . a () . whileTrue ( self . mechanism . sys_id_quasistatic ( SysIdRoutine . Direction . kReverse )) self . joystick . b () . whileTrue ( self . mechanism . sys_id_dynamic ( SysIdRoutine . Direction . kForward )) self . joystick . x () . whileTrue ( self . mechanism . sys_id_dynamic ( SysIdRoutine . Direction . kReverse )) All four tests must be run and captured in a single log file. As a result, it is important that the user starts the Signal Logger before running the tests and stops the Signal Logger after all tests have been completed. This will ensure the log is not cluttered with data from other actions such as driving the robot to an open area. Note Consult the WPILib documentation for additional details on mechanism characterization. Before Characterization There are a couple of important things to consider before running the characterization tests. Characterization Can Be Dangerous: Danger Always use caution when mechanisms are moving and ensure that the robot can be disabled swiftly at any time! Since characterization applies a scaling (quasistatic) or constant (dynamic) voltage to the motor, it can very easily hit a wall (drivetrain) or break the mechanism (elevator) if unprepared. Ensure that the ramp rate is set appropriately and adequate space is given (15m recommended for drivetrain) for the tests. Ensure Adequate Space If the mechanism is continuous (swerve azimuth or a flywheel), then this is not an issue. However, mechanisms such as a drivetrain or elevator have a limited degree of movement. Ensure the configuration parameters match what is possible, and be prepared to disable the robot early. Only Run Each Test Once Limitations of the SysId desktop utility prevent multiple of the same tests to be properly analyzed. Ensure each test is run exactly once. Running Characterization The quasistatic test will slowly ramp up voltage until the button has been released or a timeout has been hit. It is always safe to end the tests early, but at least ~3-5 seconds of data is necessary. Ensure ramp rate is configured such that this can be accomplished. The dynamic test will immediately run the mechanism at the target voltage. This voltage may need to be adjusted if there is not sufficient room for the test. With the routines configured and buttons set up, the characterization tests can be performed. To keep things simple and debuggable, perform tests in the following order. Quasistatic forward Quasistatic reverse Dynamic forward Dynamic reverse Ensure each test is ran once, and only once. If a test is accidentally started multiple times, stop and restart the Signal Logger and try again. Once you have a log with all the tests, you can use Tuner X or the owlet CLI tool to extract the hoot log to WPILOG . The exported WPILOG can then be loaded into SysId for analysis using the Talon FX Position , Velocity , and MotorVoltage signals. Important We recommend users do not use third-party tools to export a hoot log to WPILOG. Doing so may result in a lossy conversion that impacts the quality of the SysId analysis. This is particularly true in simulation, where a lossy export can result in SysId failing to analyze the data.",
+ "content_preview": "Plumbing & Running SysId For the purpose of this documentation, we focus on the integration of Phoenix 6 and WPILib’s SysId API to characterize common mechanisms. Detailed documentation on the SysId routines can be found here ."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-elev/index.html",
- "title": "Tuner Elevator Generator",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tools/chrp-converter.html",
+ "title": "CHRP Converter",
"section": "Phoenix Tuner X",
"language": "All",
- "content": "Tuner Elevator Generator Important The generated Elevator subsystem assumes WPILib command based, but can trivially be adopted for non-FRC by removing the WPILib references. Under the Mechanisms page in Tuner X is the Elevator Generator. This utility guides the user through determining the necessary constants and configurations for a working Elevator subsystem. Setup Calibration and Limits Tuning your Elevator Generation",
- "content_preview": "Tuner Elevator Generator Important The generated Elevator subsystem assumes WPILib command based, but can trivially be adopted for non-FRC by removing the WPILib references. Under the Mechanisms page in Tuner X is the Elevator Generator."
- },
- {
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/canivore/canivore-config.html",
- "title": "Advanced Configuration",
- "section": "CANivore",
- "language": "All",
- "content": "Advanced Configuration The CANivore provides additional configuration options for advanced users. CAN Bus Termination The CANivore has a 120 \\(\\Omega\\) programmable resister for terminating the CAN bus. The resistor can be configured using the CAN Bus Termination toggle in the CANivore device card in Phoenix Tuner X. Warning A CAN bus requires two termination resistors, one at each extreme end. If only one is present, communication over CAN may fail. caniv - CANivore CLI caniv is a Command-line Interface (CLI) to interact with CANivores outside of Phoenix Tuner X. Note Unlike the CANivores page in Phoenix Tuner X, caniv does not require a running Phoenix Diagnostic Server. On Linux systems (including the roboRIO), caniv can be found at /usr/local/bin . On Windows systems, the program is in the Phoenix Tuner X application cache directory, which can be opened by opening the Diagnostic Log page and clicking the left folder icon in the top right: To view a list of available commands, run caniv either with no parameters or with --help .",
- "content_preview": "Advanced Configuration The CANivore provides additional configuration options for advanced users. CAN Bus Termination The CANivore has a 120 \\(\\Omega\\) programmable resister for terminating the CAN bus."
+ "content": "CHRP Converter Orchestra uses CHRP files to play music using compatible Talon FX motors. Tuner offers the ability to convert MIDI soundtracks to compatible CHRPs. Simply follow the on-screen instructions and press Import MIDI .",
+ "content_preview": "CHRP Converter Orchestra uses CHRP files to play music using compatible Talon FX motors. Tuner offers the ability to convert MIDI soundtracks to compatible CHRPs. Simply follow the on-screen instructions and press Import MIDI ."
},
{
"url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/wpilib-integration/epilogue-integration.html",
@@ -708,140 +732,116 @@
"content_preview": "Annotation Logging with Epilogue In a WPILib Java robot project, the HootEpilogueBackend ( Java ) can be used to integrate the Phoenix 6 Signal Logger with Epilogue . This makes it easy to register custom signals for logging using Java annotations."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/hardware-reference/canrange/index.html",
- "title": "CANrange",
- "section": "General",
- "language": "All",
- "content": "CANrange CANrange is a CAN-enabled Time-of-Flight distance measurement sensor. This product uses laser measurements to calculate precise distance to a surface parallel to the sensor. Users can also configure the CANrange to act as a limit switch or beam break sensor in the Device Configs . Store Page CAD and purchase instructions. https://store.ctr-electronics.com/products/canrange Hardware User Manual https://ctre.download/files/user-manual/CANrange%20User’s%20Guide.pdf Status Light Reference Important If the status lights do not exactly match any of the blink codes below, the device may be alternating between multiple blink codes (most commonly between good and bad CAN). Blink Codes Animation (Click to play) LED State Cause Possible Fix LEDs Off No Power Provide 12V to V+ and V- inputs. Blinking Alternating Red CANrange does not have valid CAN. Ensure good connections between CANH and CANL (Yellow and Green) & robot controller is on. Blinking Alternating Orange CANrange has a good CAN connection. Measured distance is not within detection threshold. Blinking Alternating Green CANrange has a good CAN connection. Measured distance is within detection threshold. Alternate Red/Orange Damaged Hardware. Use Tuner X Self Test to confirm the LEDs and that the hardware fault is set, then contact CTRE Single LED alternates Green/Orange CANrange in bootloader. Field-upgrade device in Tuner X. The rate at which the LED is blinking can be used as a rough indicator of measured distance. For example, the below LED shows that the detected distance is close to the CANrange. Animation (Click to play)",
- "content_preview": "CANrange CANrange is a CAN-enabled Time-of-Flight distance measurement sensor. This product uses laser measurements to calculate precise distance to a surface parallel to the sensor. Users can also configure the CANrange to act as a limit switch or beam break sensor in the Device Configs ."
- },
- {
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/api-usage/index.html",
- "title": "General API Usage",
- "section": "API Reference",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tools/log-extractor.html",
+ "title": "Extracting Signal Logs",
+ "section": "Phoenix Tuner X",
"language": "All",
- "content": "General API Usage This section serves to provide general API usage for the Phoenix 6 API. For full details, please visit the API docs ( Java , C++ , Python ). Important While Phoenix 6 and Phoenix 5 devices may exist on the same CAN bus and same robot project, each robot project must use the API tied to the device firmware version. This means Phoenix 5 devices must use the Phoenix 5 API, and Phoenix 6 devices must use the Phoenix 6 API. There are three major components to the Phoenix 6 API: Configs Configs represent a persistent configuration for a device. For example, closed-loop gains. Configuration Control Requests Control Requests represent the output of a device, typically a motor controller. Control Requests Signals Signals represent data retrieved from a device. This can be velocity, position, yaw, pitch, roll, temperature, etc. Status Signals TalonFX Quickstart Quickstart on controlling a TalonFX with open loop control requests and a Joystick. Open-Loop Control API Overview Details a high level overview of what makes up the Phoenix 6 API. Configuration Describes configuring device configs via code. Control Requests Highlights using control requests to control the output of actuators such as the TalonFX. Status Signals Details using status signals to retrieve sensor data from devices. Signal Logging Information on the signal logging API used for capturing signal traffic on the bus. Hoot Replay Highlights playing back captured signals from a hoot log to test robot program changes. Device Faults Documents how faults are used to indicate device hardware status. Enabling Actuators Information on the FRC Lock safety feature and enabling actuators. Actuator Limits Documents how to retrieve and configure software and hardware actuator limits. Orchestra Information on playing music and sounds using the Orchestra API.",
- "content_preview": "General API Usage This section serves to provide general API usage for the Phoenix 6 API. For full details, please visit the API docs ( Java , C++ , Python )."
+ "content": "Extracting Signal Logs Tip Information on how to use the signal logger API can be found in the corresponding API article . Tuner X offers in-app functionality to retrieve, manage, and convert hoot logs to compatible formats. CTRE hoot logs can be retrieved utilizing the file explorer on the left of the application. The file explorer offers the functionality to download and delete logs on a remote target. On the right side, the Convert tab can be used to import and convert hoot logs to available formats. Once a hoot log has been downloaded, it is automatically placed in the conversion queue to the right. Logs can also be manually imported or removed using the two buttons at the top-right of the conversion queue. If a log file is reported as unlicensed, users can perform a Deep Scan of the log if they believe it should contain pro-licensed devices. By default, Tuner will only scan the first few megabytes of the log for pro-licensed devices to save time during scan and export. Filtering for Signals Since hoot logs can contain a massive amount of data, users may want to trim the exported log file. Tuner supports simple search and regex filtering of signals in a hoot . Filters are optional and configured on a per-log basis. Note If no signals are selected, all signals will be exported during conversion. In the below picture, regex is used to select only the MotorVoltage , Position , and Velocity signals for TalonFX-11 . Important If the hoot log does not contain any Pro-licensed devices, a limited set of signals may be exported for free. Converting After adding hoot logs to the queue, select the output directory and one of the output types. Then, click the Convert button to begin the conversion process. This may take some time depending on the output format, the size of the hoot file, and the number of selected signals. Common Issues Problem: When converting, I get hoot log API version too old, cannot export its signals Solution: This may happen if your hoot file was generated using an old version of Phoenix. Update your Phoenix installation (by updating your vendordep in Installing Phoenix 6 ) and recreate your log file. If the log file recorded is critical, reach out to support @ ctr-electronics . com . Problem: When converting, I get Could not read to end of input file Solution: This occurs when the converter encounters bad data. This typically occurs when the robot is turned off in the middle of writing to the log. Users can often ignore this error message, although the last few seconds of data may be lost. To avoid this issue, run SignalLogger.stop() before shutting down the robot program.",
+ "content_preview": "Extracting Signal Logs Tip Information on how to use the signal logger API can be found in the corresponding API article . Tuner X offers in-app functionality to retrieve, manage, and convert hoot logs to compatible formats."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/device-specific/talonfx/motion-magic.html",
- "title": "Motion Magic® Controls",
- "section": "TalonFX",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-elev/index.html",
+ "title": "Tuner Elevator Generator",
+ "section": "Phoenix Tuner X",
"language": "All",
- "content": "Motion Magic® Controls In addition to basic PID control, the Talon FX also supports onboard motion profiling using Motion Magic® controls. Note For more information on feedback and feedforward gains, see Closed-Loop Overview . Motion Magic® Motion Magic® is a control mode that provides the benefit of Motion Profiling without needing to generate motion profile trajectory points. When using Motion Magic®, the motor will move to a target position using a motion profile, while honoring the user specified acceleration, maximum velocity (cruise velocity), and optional jerk. The benefits of this control mode over “simple” PID position closed-looping are: Control of the mechanism throughout the entire motion (as opposed to racing to the end target position) Control of the mechanism’s inertia to ensure smooth transitions between setpoints Improved repeatability despite changes in battery load Improved repeatability despite changes in motor load After gain/settings are determined, the robot controller only needs to periodically set the target position. There is no general requirement to “wait for the profile to finish”. However, the robot application can poll the sensor position and determine when the motion is finished if need be. Motion Magic® functions by generating a trapezoidal/S-Curve velocity profile that does not exceed the specified cruise velocity, acceleration, or jerk. This is done automatically by the motor controller. Note If the remaining sensor distance to travel is small, the velocity may not reach cruise velocity as this would overshoot the target position. This is often referred to as a “triangle profile”. If the Motion Magic® jerk is set to a nonzero value, the generated velocity profile is no longer trapezoidal, but instead is a continuous S-Curve (corner points are smoothed). An S-Curve profile has the following advantaged over a trapezoidal profile: Reducing oscillation of the mechanism. Maneuver is more deliberate and reproducible. Note The jerk control feature, by its nature, will increase the amount of time a movement requires. This can be compensated for by increasing the configured acceleration value. The following parameters must be set when controlling using Motion Magic® Cruise Velocity - peak/cruising velocity of the motion Acceleration - controls acceleration and deceleration rates during the beginning and end of motion Jerk (optional) - controls jerk, which is the derivative of acceleration Using Motion Magic® in API Motion Magic® is currently supported for all base control output types . The units of the output are determined by the control output type. The Motion Magic® jerk, acceleration, and cruise velocity can be configured in code using a MotionMagicConfigs ( Java , C++ , Python ) object. In Motion Magic®, the gains should be configured as follows: \\(K_g\\) - output to overcome gravity (output) \\(K_s\\) - output to overcome static friction (output) \\(K_v\\) - output per unit of target velocity (output/rps) \\(K_a\\) - output per unit of target acceleration (output/(rps/s)) \\(K_p\\) - output per unit of error in position (output/rotation) \\(K_i\\) - output per unit of integrated error in position (output/(rotation*s)) \\(K_d\\) - output per unit of error in velocity (output/rps) Java // in init function var talonFXConfigs = new TalonFXConfiguration (); // set slot 0 gains var slot0Configs = talonFXConfigs . Slot0 ; slot0Configs . kS = 0.25 ; // Add 0.25 V output to overcome static friction slot0Configs . kV = 0.12 ; // A velocity target of 1 rps results in 0.12 V output slot0Configs . kA = 0.01 ; // An acceleration of 1 rps/s requires 0.01 V output slot0Configs . kP = 4.8 ; // A position error of 2.5 rotations results in 12 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0.1 ; // A velocity error of 1 rps results in 0.1 V output // set Motion Magic settings var motionMagicConfigs = talonFXConfigs . MotionMagic ; motionMagicConfigs . MotionMagicCruiseVelocity = 80 ; // Target cruise velocity of 80 rps motionMagicConfigs . MotionMagicAcceleration = 160 ; // Target acceleration of 160 rps/s (0.5 seconds) motionMagicConfigs . MotionMagicJerk = 1600 ; // Target jerk of 1600 rps/s/s (0.1 seconds) m_talonFX . getConfigurator (). apply ( talonFXConfigs ); C++ // in init function configs :: TalonFXConfiguration talonFXConfigs {}; // set slot 0 gains auto & slot0Configs = talonFXConfigs . Slot0 ; slot0Configs . kS = 0.25 ; // Add 0.25 V output to overcome static friction slot0Configs . kV = 0.12 ; // A velocity target of 1 rps results in 0.12 V output slot0Configs . kA = 0.01 ; // An acceleration of 1 rps/s requires 0.01 V output slot0Configs . kP = 4.8 ; // A position error of 2.5 rotations results in 12 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0.1 ; // A velocity error of 1 rps results in 0.1 V output // set Motion Magic settings auto & motionMagicConfigs = talonFXConfigs . MotionMagic ; motionMagicConfigs . MotionMagicCruiseVelocity = 80 ; // Target cruise velocity of 80 rps motionMagicConfigs . MotionMagicAcceleration = 160 ; // Target acceleration of 160 rps/s (0.5 seconds) motionMagicConfigs . MotionMagicJerk = 1600 ; // Target jerk of 1600 rps/s/s (0.1 seconds) m_talonFX . GetConfigurator (). Apply ( talonFXConfigs ); Python # in init function talonfx_configs = configs . TalonFXConfiguration () # set slot 0 gains slot0_configs = talonfx_configs . slot0 slot0_configs . k_s = 0.25 # Add 0.25 V output to overcome static friction slot0_configs . k_v = 0.12 # A velocity target of 1 rps results in 0.12 V output slot0_configs . k_a = 0.01 # An acceleration of 1 rps/s requires 0.01 V output slot0_configs . k_p = 4.8 # A position error of 2.5 rotations results in 12 V output slot0_configs . k_i = 0 # no output for integrated error slot0_configs . k_d = 0.1 # A velocity error of 1 rps results in 0.1 V output # set Motion Magic settings motion_magic_configs = talonfx_configs . motion_magic motion_magic_configs . motion_magic_cruise_velocity = 80 # Target cruise velocity of 80 rps motion_magic_configs . motion_magic_acceleration = 160 # Target acceleration of 160 rps/s (0.5 seconds) motion_magic_configs . motion_magic_jerk = 1600 # Target jerk of 1600 rps/s/s (0.1 seconds) self . talonfx . configurator . apply ( talonfx_configs ) Tip Motion Magic® supports modifying cruise velocity, acceleration, and jerk on the fly (requires firmware version 24.0.6.0 or newer). Once the gains are configured, the Motion Magic® request can be sent to the TalonFX. The control request object has an optional feedforward term that can be used to add an arbitrary value to the output, which can be useful to account for the effects of gravity. Java // create a Motion Magic request, voltage output final MotionMagicVoltage m_request = new MotionMagicVoltage ( 0 ); // set target position to 100 rotations m_talonFX . setControl ( m_request . withPosition ( 100 )); C++ // create a Motion Magic request, voltage output controls :: MotionMagicVoltage m_request { 0 _tr }; // set target position to 100 rotations m_talonFX . SetControl ( m_request . WithPosition ( 100 _tr )); Python # create a Motion Magic request, voltage output self . request = controls . MotionMagicVoltage ( 0 ) # set target position to 100 rotations self . talonfx . set_control ( self . request . with_position ( 100 )) Dynamic Motion Magic® Important This feature requires the device to be Pro licensed and on a CANivore . When unlicensed, the TalonFX will disable control output and trip the UnlicensedFeatureInUse fault. When using a Pro-licensed Talon FX connected to a CANivore, Dynamic Motion Magic® can be used, allowing for the cruise velocity, acceleration, and jerk to be modified directly in the control request during motion. This can be used to set up different values for acceleration vs deceleration or to speed up and slow down the profile on the fly. The gain slots are configured in the same way as a regular Motion Magic® request. However, the cruise velocity, acceleration, and jerk parameters are set up in the control request, not the Motion Magic® config group. Once the gains are configured, the Dynamic Motion Magic® request can be sent to the TalonFX. The control request object has an optional feedforward term that can be used to add an arbitrary value to the output, which can be useful to account for the effects of gravity. Java // create a Dynamic Motion Magic request, voltage output // default velocity of 80 rps, acceleration of 400 rot/s^2, and jerk of 4000 rot/s^3 final DynamicMotionMagicVoltage m_request = new DynamicMotionMagicVoltage ( 0 , 80 , 400 ). withJerk ( 4000 ); if ( m_joy . getAButton ()) { // while the joystick A button is held, use a slower profile m_request . Velocity = 40 ; // rps m_request . Acceleration = 80 ; // rot/s^2 m_request . Jerk = 400 ; // rot/s^3 } else { // otherwise use a faster profile m_request . Velocity = 80 ; // rps m_request . Acceleration = 400 ; // rot/s^2 m_request . Jerk = 4000 ; // rot/s^3 } // set target position to 100 rotations m_talonFX . setControl ( m_request . withPosition ( 100 )); C++ // create a Dynamic Motion Magic request, voltage output // default velocity of 80 rps, acceleration of 400 rot/s^2, and jerk of 4000 rot/s^3 controls :: DynamicMotionMagicVoltage m_request = controls :: DynamicMotionMagicVoltage { 0 _tr , 80 _tps , 400 _tr_per_s_sq } . WithJerk ( 4000 _tr_per_s_cu ); if ( m_joy . GetAButton ()) { // while the joystick A button is held, use a slower profile m_request . Velocity = 40 _tps ; m_request . Acceleration = 80 _tr_per_s_sq ; m_request . Jerk = 400 _tr_per_s_cu ; } else { // otherwise use a faster profile m_request . Velocity = 80 _tps ; m_request . Acceleration = 400 _tr_per_s_sq ; m_request . Jerk = 4000 _tr_per_s_cu ; } // set target position to 100 rotations m_talonFX . SetControl ( m_request . WithPosition ( 100 _tr )); Python # create a Dynamic Motion Magic request, voltage output # default velocity of 80 rps, acceleration of 400 rot/s^2, and jerk of 4000 rot/s^3 self . request = controls . DynamicMotionMagicVoltage ( 0 , 80 , 400 , 4000 ) if self . joy . getAButton (): # while the joystick A button is held, use a slower profile self . request . velocity = 40 # rps self . request . acceleration = 80 # rot/s^2 self . request . jerk = 400 # rot/s^3 else : # otherwise use a faster profile self . request . velocity = 80 # rps self . request . acceleration = 400 # rot/s^2 self . request . jerk = 4000 # rot/s^3 # set target position to 100 rotations self . talonfx . set_control ( self . request . with_position ( 100 )) Motion Magic® Velocity The Talon FX also supports onboard velocity motion profiling using Motion Magic® Velocity. When using Motion Magic® Velocity, the motor will ramp to a target velocity using a trapezoidal acceleration profile that honors the specified acceleration and optional jerk. The benefits of this control mode over “simple” PID velocity closed-looping are: Control of the mechanism throughout the entire motion (as opposed to racing to the end target velocity) Control of the mechanism’s inertia to ensure smooth transitions between setpoints Improved repeatability despite changes in battery load Improved repeatability despite changes in motor load After gain/settings are determined, the robot controller only needs to periodically set the target velocity. The following parameters must be set when controlling using Motion Magic® Velocity Acceleration - controls acceleration and deceleration rates during the beginning and end of motion Jerk (optional) - controls jerk, which is the derivative of acceleration Using Motion Magic® Velocity in API Motion Magic® Velocity is currently supported for all base control output types . The units of the output are determined by the control output type. The Motion Magic® Velocity jerk and acceleration can be configured in code using a MotionMagicConfigs ( Java , C++ , Python ) object. In Motion Magic® Velocity, the gains should be configured as follows: \\(K_g\\) - output to overcome gravity (output) \\(K_s\\) - output to overcome static friction (output) \\(K_v\\) - output per unit of target velocity (output/rps) \\(K_a\\) - output per unit of target acceleration (output/(rps/s)) \\(K_p\\) - output per unit of error in velocity (output/rps) \\(K_i\\) - output per unit of integrated error in velocity (output/rotation) \\(K_d\\) - output per unit of error derivative in velocity (output/(rps/s)) Java // in init function var talonFXConfigs = new TalonFXConfiguration (); // set slot 0 gains var slot0Configs = talonFXConfigs . Slot0 ; slot0Configs . kS = 0.25 ; // Add 0.25 V output to overcome static friction slot0Configs . kV = 0.12 ; // A velocity target of 1 rps results in 0.12 V output slot0Configs . kA = 0.01 ; // An acceleration of 1 rps/s requires 0.01 V output slot0Configs . kP = 0.11 ; // An error of 1 rps results in 0.11 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0 ; // no output for error derivative // set Motion Magic Velocity settings var motionMagicConfigs = talonFXConfigs . MotionMagic ; motionMagicConfigs . MotionMagicAcceleration = 400 ; // Target acceleration of 400 rps/s (0.25 seconds to max) motionMagicConfigs . MotionMagicJerk = 4000 ; // Target jerk of 4000 rps/s/s (0.1 seconds) m_talonFX . getConfigurator (). apply ( talonFXConfigs ); C++ // in init function configs :: TalonFXConfiguration talonFXConfigs {}; // set slot 0 gains auto & slot0Configs = talonFXConfigs . Slot0 ; slot0Configs . kS = 0.25 ; // Add 0.25 V output to overcome static friction slot0Configs . kV = 0.12 ; // A velocity target of 1 rps results in 0.12 V output slot0Configs . kA = 0.01 ; // An acceleration of 1 rps/s requires 0.01 V output slot0Configs . kP = 0.11 ; // An error of 1 rps results in 0.11 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0 ; // no output for error derivative // set Motion Magic Velocity settings auto & motionMagicConfigs = talonFXConfigs . MotionMagic ; motionMagicConfigs . MotionMagicAcceleration = 400 ; // Target acceleration of 400 rps/s (0.25 seconds to max) motionMagicConfigs . MotionMagicJerk = 4000 ; // Target jerk of 4000 rps/s/s (0.1 seconds) m_talonFX . GetConfigurator (). Apply ( talonFXConfigs ); Python # in init function talonfx_configs = configs . TalonFXConfiguration () # set slot 0 gains slot0_configs = talonfx_configs . slot0 slot0_configs . k_s = 0.25 # Add 0.25 V output to overcome static friction slot0_configs . k_v = 0.12 # A velocity target of 1 rps results in 0.12 V output slot0_configs . k_a = 0.01 # An acceleration of 1 rps/s requires 0.01 V output slot0_configs . k_p = 0.11 # An error of 1 rps results in 0.11 V output slot0_configs . k_i = 0 # no output for integrated error slot0_configs . k_d = 0 # no output for error derivative # set Motion Magic Velocity settings motion_magic_configs = talonfx_configs . motion_magic motion_magic_configs . motion_magic_acceleration = 400 # Target acceleration of 400 rps/s (0.25 seconds to max) motion_magic_configs . motion_magic_jerk = 4000 # Target jerk of 4000 rps/s/s (0.1 seconds) self . talonfx . configurator . apply ( talonfx_configs ) Tip Motion Magic® Velocity supports modifying acceleration and jerk on the fly (requires firmware version 24.0.6.0 or newer). Once the gains are configured, the Motion Magic® Velocity request can be sent to the TalonFX. The Motion Magic® Velocity request has an Acceleration parameter that can be used to override the profile acceleration during motion. If the Acceleration parameter is left 0, the acceleration config will be used instead. The control request object also has an optional feedforward term that can be used to add an arbitrary value to the output, which can be useful to account for the effects of gravity. Java // create a Motion Magic Velocity request, voltage output final MotionMagicVelocityVoltage m_request = new MotionMagicVelocityVoltage ( 0 ); if ( m_joy . getAButton ()) { // while the joystick A button is held, use a slower acceleration m_request . Acceleration = 100 ; // rot/s^2 } else { // otherwise, fall back to the config m_request . Acceleration = 0 ; } // set target velocity to 80 rps m_talonFX . setControl ( m_request . withVelocity ( 80 )); C++ // create a Motion Magic Velocity request, voltage output controls :: MotionMagicVelocityVoltage m_request { 0 _tps }; if ( m_joy . GetAButton ()) { // while the joystick A button is held, use a slower acceleration m_request . Acceleration = 100 _tr_per_s_sq ; } else { // otherwise, fall back to the config m_request . Acceleration = 0 _tr_per_s_sq ; } // set target velocity to 80 rps m_talonFX . SetControl ( m_request . WithVelocity ( 80 _tps )); Python # create a Motion Magic Velocity request, voltage output self . request = controls . MotionMagicVelocityVoltage ( 0 ) if self . joy . getAButton (): # while the joystick A button is held, use a slower acceleration self . request . acceleration = 100 # rot/s^2 else : # otherwise, fall back to the config self . request . acceleration = 0 # set target velocity to 80 rps self . talonfx . set_control ( self . request . with_velocity ( 80 )) Motion Magic® Expo Whereas traditional Motion Magic® generates a trapezoidal or S-Curve profile, Motion Magic® Expo generates an exponential profile. This allows the profile to best match the system dynamics, reducing both overshoot and time to target compared to a trapezoidal profile. Motion Magic® Expo uses the kV and kA characteristics of the system, as well as an optional cruise velocity. The Motion Magic® Expo kV and kA configs are separate from the slot gain configs, as they may use different units and have different behaviors. The Motion Magic® Expo kV represents the voltage required to maintain a given velocity and is in units of Volts/rps. Dividing the supply voltage by kV results in the maximum velocity of the profile. As a result, when supply voltage is fixed, a higher profile kV results in a lower profile velocity . Unlike with gain slots, it is safer to start from a higher kV than what is ideal. The Motion Magic® Expo kA represents the voltage required to apply a given acceleration and is in units of Volts/(rps/s). Dividing the supply voltage by kA results in the maximum acceleration of the profile from 0. As a result, when supply voltage is fixed, a higher profile kA results in a lower profile acceleration . Unlike with gain slots, it is safer to start from a higher kA than what is ideal. If the Motion Magic® cruise velocity is set to a non-zero value, the profile will only accelerate up to the cruise velocity. Otherwise, the profile will accelerate towards the maximum possible velocity based on the profile kV. The following parameters must be set when controlling using Motion Magic® Expo: Expo kV - voltage required to maintain a given velocity, in V/rps Expo kA - voltage required to apply a given acceleration, in V/(rps/s) Cruise Velocity (optional) - peak velocity of the profile; set to 0 to target the system’s max velocity Using Motion Magic® Expo in API Motion Magic® Expo is currently supported for all base control output types . The units of the output are determined by the control output type. The Motion Magic® Expo kV, kA, and cruise velocity can be configured in code using a MotionMagicConfigs ( Java , C++ , Python ) object. Important Unlike the gain slots, the MotionMagicExpo_kV and MotionMagicExpo_kA configs are always in output units of Volts. In Motion Magic® Expo, the gains should be configured as follows: \\(K_g\\) - output to overcome gravity (output) \\(K_s\\) - output to overcome static friction (output) \\(K_v\\) - output per unit of target velocity (output/rps) \\(K_a\\) - output per unit of target acceleration (output/(rps/s)) \\(K_p\\) - output per unit of error in position (output/rotation) \\(K_i\\) - output per unit of integrated error in position (output/(rotation*s)) \\(K_d\\) - output per unit of error in velocity (output/rps) Java // in init function var talonFXConfigs = new TalonFXConfiguration (); // set slot 0 gains var slot0Configs = talonFXConfigs . Slot0 ; slot0Configs . kS = 0.25 ; // Add 0.25 V output to overcome static friction slot0Configs . kV = 0.12 ; // A velocity target of 1 rps results in 0.12 V output slot0Configs . kA = 0.01 ; // An acceleration of 1 rps/s requires 0.01 V output slot0Configs . kP = 4.8 ; // A position error of 2.5 rotations results in 12 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0.1 ; // A velocity error of 1 rps results in 0.1 V output // set Motion Magic Expo settings var motionMagicConfigs = talonFXConfigs . MotionMagic ; motionMagicConfigs . MotionMagicCruiseVelocity = 0 ; // Unlimited cruise velocity motionMagicConfigs . MotionMagicExpo_kV = 0.12 ; // kV is around 0.12 V/rps motionMagicConfigs . MotionMagicExpo_kA = 0.1 ; // Use a slower kA of 0.1 V/(rps/s) m_talonFX . getConfigurator (). apply ( talonFXConfigs ); C++ // in init function configs :: TalonFXConfiguration talonFXConfigs {}; // set slot 0 gains auto & slot0Configs = talonFXConfigs . Slot0 ; slot0Configs . kS = 0.25 ; // Add 0.25 V output to overcome static friction slot0Configs . kV = 0.12 ; // A velocity target of 1 rps results in 0.12 V output slot0Configs . kA = 0.01 ; // An acceleration of 1 rps/s requires 0.01 V output slot0Configs . kP = 4.8 ; // A position error of 2.5 rotations results in 12 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0.1 ; // A velocity error of 1 rps results in 0.1 V output // set Motion Magic Expo settings auto & motionMagicConfigs = talonFXConfigs . MotionMagic ; motionMagicConfigs . MotionMagicCruiseVelocity = 0 ; // Unlimited cruise velocity motionMagicConfigs . MotionMagicExpo_kV = 0.12 ; // kV is around 0.12 V/rps motionMagicConfigs . MotionMagicExpo_kA = 0.1 ; // Use a slower kA of 0.1 V/(rps/s) m_talonFX . GetConfigurator (). Apply ( talonFXConfigs ); Python # in init function talonfx_configs = configs . TalonFXConfiguration () # set slot 0 gains slot0_configs = talonfx_configs . slot0 slot0_configs . k_s = 0.25 # Add 0.25 V output to overcome static friction slot0_configs . k_v = 0.12 # A velocity target of 1 rps results in 0.12 V output slot0_configs . k_a = 0.01 # An acceleration of 1 rps/s requires 0.01 V output slot0_configs . k_p = 4.8 # A position error of 2.5 rotations results in 12 V output slot0_configs . k_i = 0 # no output for integrated error slot0_configs . k_d = 0.1 # A velocity error of 1 rps results in 0.1 V output # set Motion Magic Expo settings motion_magic_configs = talonfx_configs . motion_magic motion_magic_configs . motion_magic_cruise_velocity = 0 # Unlimited cruise velocity motion_magic_configs . motion_magic_expo_k_v = 0.12 # kV is around 0.12 V/rps motion_magic_configs . motion_magic_expo_k_a = 0.1 # Use a slower kA of 0.1 V/(rps/s) self . talonfx . configurator . apply ( talonfx_configs ) Tip Motion Magic® Expo supports modifying cruise velocity, kV, and kA on the fly. Once the gains are configured, the Motion Magic® Expo request can be sent to the TalonFX. The control request object has an optional feedforward term that can be used to add an arbitrary value to the output, which can be useful to account for the effects of gravity. Java // create a Motion Magic Expo request, voltage output final MotionMagicExpoVoltage m_request = new MotionMagicExpoVoltage ( 0 ) // set target position to 100 rotations m_talonFX . setControl ( m_request . withPosition ( 100 )); C++ // create a Motion Magic Expo request, voltage output controls :: MotionMagicExpoVoltage m_request { 0 _tr } // set target position to 100 rotations m_talonFX . SetControl ( m_request . WithPosition ( 100 _tr )); Python # create a Motion Magic Expo request, voltage output self . request = controls . MotionMagicExpoVoltage ( 0 ) # set target position to 100 rotations self . talonfx . set_control ( self . request . with_position ( 100 )) Dynamic Motion Magic® Expo Important This feature requires the device to be Pro licensed and on a CANivore . When unlicensed, the TalonFX will disable control output and trip the UnlicensedFeatureInUse fault. When using a Pro-licensed Talon FX connected to a CANivore, Dynamic Motion Magic® Expo can be used, allowing for the cruise velocity, Expo kV, and Expo kA to be modified directly in the control request during motion. This can be used to set up different values for forward vs reverse or to speed up and slow down the profile on the fly. The gain slots are configured in the same way as a regular Motion Magic® Expo request. However, the cruise velocity, Expo kV, and Expo kA parameters are set up in the control request, not the Motion Magic® config group. Once the gains are configured, the Dynamic Motion Magic® Expo request can be sent to the TalonFX. The control request object has an optional feedforward term that can be used to add an arbitrary value to the output, which can be useful to account for the effects of gravity. Java // create a Dynamic Motion Magic Expo request, voltage output // default Expo kV of 0.12 V/rps and kA of 0.1 V/(rot/s^2), unlimited cruise velocity final DynamicMotionMagicExpoVoltage m_request = new DynamicMotionMagicExpoVoltage ( 0 , 0.12 , 0.1 ); if ( m_joy . getAButton ()) { // while the joystick A button is held, use a slower profile // cap the cruise velocity and weaken acceleration (larger kA) m_request . Velocity = 40 ; // rps m_request . kA = 0.2 ; // V/(rot/s^2) } else { // otherwise use a faster profile m_request . Velocity = 0 ; // rps, 0 is unlimited m_request . kA = 0.1 ; // V/(rot/s^2) } // set target position to 100 rotations m_talonFX . setControl ( m_request . withPosition ( 100 )); C++ // create a Dynamic Motion Magic Expo request, voltage output // default Expo kV of 0.12 V/rps and kA of 0.1 V/(rot/s^2), unlimited cruise velocity controls :: DynamicMotionMagicExpoVoltage m_request { 0 _tr , 0.12 _V / 1 _tr_per_s , 0.1 _V / 1 _tr_per_s_sq }; if ( m_joy . GetAButton ()) { // while the joystick A button is held, use a slower profile // cap the cruise velocity and weaken acceleration (larger kA) m_request . Velocity = 40 _tps ; m_request . kA = 0.2 _V / 1 _tr_per_s_sq ; } else { // otherwise use a faster profile m_request . Velocity = 0 _tps ; // 0 is unlimited m_request . kA = 0.1 _V / 1 _tr_per_s_sq ; } // set target position to 100 rotations m_talonFX . SetControl ( m_request . WithPosition ( 100 _tr )); Python # create a Dynamic Motion Magic Expo request, voltage output # default Expo kV of 0.12 V/rps and kA of 0.1 V/(rot/s^2), unlimited cruise velocity self . request = controls . DynamicMotionMagicExpoVoltage ( 0 , 0.12 , 0.1 ) if self . joy . getAButton (): # while the joystick A button is held, use a slower profile # cap the cruise velocity and weaken acceleration (larger kA) self . request . velocity = 40 # rps self . request . k_a = 0.2 # V/(rot/s^2) else : # otherwise use a faster profile self . request . velocity = 0 # rps, 0 is unlimited self . request . k_a = 0.1 # V/(rot/s^2) # set target position to 100 rotations self . talonfx . set_control ( self . request . with_position ( 100 ))",
- "content_preview": "Motion Magic® Controls In addition to basic PID control, the Talon FX also supports onboard motion profiling using Motion Magic® controls. Note For more information on feedback and feedforward gains, see Closed-Loop Overview ."
+ "content": "Tuner Elevator Generator Important The generated Elevator subsystem assumes WPILib command based, but can trivially be adopted for non-FRC by removing the WPILib references. Under the Mechanisms page in Tuner X is the Elevator Generator. This utility guides the user through determining the necessary constants and configurations for a working Elevator subsystem. Setup Calibration and Limits Tuning your Elevator Generation",
+ "content_preview": "Tuner Elevator Generator Important The generated Elevator subsystem assumes WPILib command based, but can trivially be adopted for non-FRC by removing the WPILib references. Under the Mechanisms page in Tuner X is the Elevator Generator."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/mechanisms/differential/using-differential-mech.html",
- "title": "Using the Differential Mechanism API",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/api-usage/faults.html",
+ "title": "Device Faults",
"section": "API Reference",
"language": "All",
- "content": "Using the Differential Mechanism API DifferentialMechanism ( Java , C++ , Python ) and SimpleDifferentialMechanism ( Java , C++ , Python ) automatically set up and applies all configs to the two provided motor controllers on construction. Additionally, the mechanisms provide functions to control the mechanism, pull out relevant signals, and automatically detect and disable on fault conditions. Tip The configs applied to the motor controllers, including invert and neutral mode, can be adjusted using the LeaderInitialConfigs and FollowerInitialConfigs . For more information, see Differential Mechanism Setup . Running Control Requests The differential mechanism can be controlled by calling setControl with one of the supported control requests. DifferentialMechanism The full DifferentialMechanism (requires Phoenix Pro and CANivore ) accepts two separate control requests. The first is run on the Average axis, while the second is run on the Difference axis. Both control requests must use the same control output type . All control output types are supported, as are all parameters within the two provided control requests. However, note that Motion Magic® is not supported on the Difference axis. Note Common parameters such as UseTimesync only need to be set on the Average axis control request. Java // Run Motion Magic on the Average axis, Position on the Difference axis final MotionMagicVoltage avgRequest = new MotionMagicVoltage ( 0 ). withSlot ( 0 ); final PositionVoltage diffRequest = new PositionVoltage ( 0 ). withSlot ( 1 ); // Apply a TrapezoidProfile to the Difference axis for smoother motion final TrapezoidProfile diffProfile = new TrapezoidProfile ( new TrapezoidProfile . Constraints ( 80 , 320 ) ); TrapezoidProfile . State diffSetpoint = new TrapezoidProfile . State (); TrapezoidProfile . State diffGoal = new TrapezoidProfile . State (); // Update the Difference setpoint and control the mechanism diffGoal = new TrapezoidProfile . State ( 5.0 , 0.0 ); diffSetpoint = diffProfile . calculate ( 0.020 , diffSetpoint , diffGoal ); diffMech . setControl ( avgRequest . withPosition ( 10.0 ), diffRequest . withPosition ( diffSetpoint . position ) . withVelocity ( diffSetpoint . velocity ) ); C++ // Run Motion Magic on the Average axis, Position on the Difference axis controls :: MotionMagicVoltage avgRequest = controls :: MotionMagicVoltage { 0 _tr }. WithSlot ( 0 ); controls :: PositionVoltage diffRequest = controls :: PositionVoltage { 0 _tr }. WithSlot ( 1 ); // Apply a TrapezoidProfile to the Difference axis for smoother motion frc :: TrapezoidProfile < units :: turn_t > diffProfile {{ 80 _tps , 320 _tr_per_s_sq }}; frc :: TrapezoidProfile < units :: turn_t >:: State diffSetpoint {}; frc :: TrapezoidProfile < units :: turn_t >:: State diffGoal {}; /* Update the Difference setpoint and control the mechanism */ diffGoal = frc :: TrapezoidProfile < units :: turn_t >:: State { 5 _tr , 0 _tps }; diffSetpoint = diffProfile . Calculate ( 20 _ms , diffSetpoint , diffGoal ); diffMech . SetControl ( avgRequest . WithPosition ( 10 _tr ), diffRequest . WithPosition ( diffSetpoint . position ) . WithVelocity ( diffSetpoint . velocity ) ); Python # Run Motion Magic on the Average axis, Position on the Difference axis self . _avg_request = controls . MotionMagicVoltage ( 0 ) . with_slot ( 0 ) self . _diff_request = controls . PositionVoltage ( 0 ) . with_slot ( 1 ) # Apply a TrapezoidProfile to the Difference axis for smoother motion self . _diff_profile = TrapezoidProfile ( TrapezoidProfile . Constraints ( 80 , 320 ) ) self . _diff_setpoint = TrapezoidProfile . State () self . _diff_goal = TrapezoidProfile . State () # Update the Difference setpoint and control the mechanism self . _diff_goal = TrapezoidProfile . State ( 5.0 , 0.0 ) self . _diff_setpoint = self . _diff_profile . calculate ( 0.020 , self . _diff_setpoint , self . _diff_goal ) self . _diff_mech . set_control ( self . _avg_request . with_position ( 10.0 ), self . _diff_request . with_position ( self . _diff_setpoint . position ) . with_velocity ( self . _diff_setpoint . velocity ) ) SimpleDifferentialMechanism The limited SimpleDifferentialMechanism accepts a single Differential* control request. These control requests contain a limited number of parameters compared to the non-differential requests. For example, DifferentialPositionVoltage does not support FeedForward or Velocity . Additionally, all of these control requests run position PID on the Difference axis, and only Duty Cycle and Voltage control output types are supported. The Average* parameters specify the targets for the Average axis, while the Differential* parameters specify the targets for the Difference axis. Java // Run Motion Magic on the Average axis, unprofiled Position on the Difference axis // Defaults to Slot 0 on Average, Slot 1 on Differential final DifferentialMotionMagicVoltage request = new DifferentialMotionMagicVoltage ( 0 , 0 ); // Apply the control to the mechanism simpleDiffMech . setControl ( request . withAveragePosition ( 10.0 ) . withDifferentialPosition ( 5.0 ) ); C++ // Run Motion Magic on the Average axis, unprofiled Position on the Difference axis // Defaults to Slot 0 on Average, Slot 1 on Differential controls :: DifferentialMotionMagicVoltage request { 0 _tr , 0 _tr }; // Apply the control to the mechanism simpleDiffMech . SetControl ( request . WithAveragePosition ( 10 _tr ) . WithDifferentialPosition ( 5 _tr ) ); Python # Run Motion Magic on the Average axis, unprofiled Position on the Difference axis # Defaults to Slot 0 on Average, Slot 1 on Differential self . _request = controls . DifferentialMotionMagicVoltage ( 0 , 0 ) # Apply the control to the mechanism self . _simple_diff_mech . set_control ( self . _request . with_average_position ( 10.0 ) . with_differential_position ( 5.0 ) ) Disabling Output The differential mechanism also provides a few easy ways to disable output: setNeutralOut() applies the configured neutral mode (brake or coast) to both motors. setCoastOut() forces both motors to coast by applying a CoastOut request. setStaticBrake() forces both motors to brake by applying a StaticBrake request. Java // Disable mechanism output using the configured neutral mode diffMech . setNeutralOut (); C++ // Disable mechanism output using the configured neutral mode diffMech . SetNeutralOut (); Python # Disable mechanism output using the configured neutral mode self . _diff_mech . set_neutral_out () Set Neutral Mode and Position The neutral mode of the mechanism can be changed after construction by calling configNeutralMode ( Java , C++ , Python ). Tip The neutral mode can be applied on construction by modifying the LeaderInitialConfigs provided to the DifferentialMotorConstants . Additionally, the position of the mechanism can be reset using setPosition ( Java , C++ , Python ), which accepts the new Average axis and Difference axis positions (default 0). This can be useful for recalibrating the mechanism position. Note that any remote sensor on the Difference axis will not be affected by setPosition and can be adjusted separately. Java // The mechanism was constructed in brake mode, switch to coast diffMech . configNeutralMode ( NeutralModeValue . Coast ); // Reset the mechanism positions to 0 diffMech . setPosition ( Rotations . of ( 0 ), Rotations . of ( 0 )); C++ // The mechanism was constructed in brake mode, switch to coast diffMech . ConfigNeutralMode ( signals :: NeutralModeValue :: Coast ); // Reset the mechanism positions to 0 diffMech . SetPosition ( 0 _tr , 0 _tr ); Python # The mechanism was constructed in brake mode, switch to coast self . _diff_mech . config_neutral_mode ( signals . NeutralModeValue . COAST ) # Reset the mechanism positions to 0 self . _diff_mech . set_position ( 0.0 , 0.0 ) Fetching Status Signals Status signals relevant to the mechanism, such as the AveragePosition and DifferentialPosition , can be directly fetched from the mechanism. For other status signals, such as supply current, the differential leader and follower motor controllers can be pulled out from the mechanism using getLeader() and getFollower() . Java // Pull out some StatusSignals from the mechanism var avgPosition = diffMech . getAveragePosition (); var diffPosition = diffMech . getDifferentialPosition (); var avgClosedLoopRef = diffMech . getAverageClosedLoopReference (); var diffClosedLoopRef = diffMech . getDifferentialClosedLoopReference (); // Also pull out supply and torque current from both motors var leaderSupplyCurrent = diffMech . getLeader (). getSupplyCurrent (); var leaderTorqueCurrent = diffMech . getLeader (). getTorqueCurrent (); var followerSupplyCurrent = diffMech . getFollower (). getSupplyCurrent (); var followerTorqueCurrent = diffMech . getFollower (). getTorqueCurrent (); C++ // Pull out some StatusSignals from the mechanism auto & avgPosition = diffMech . GetAveragePosition (); auto & diffPosition = diffMech . GetDifferentialPosition (); auto & avgClosedLoopRef = diffMech . GetAverageClosedLoopReference (); auto & diffClosedLoopRef = diffMech . GetDifferentialClosedLoopReference (); // Also pull out supply and torque current from both motors auto & leaderSupplyCurrent = diffMech . GetLeader (). GetSupplyCurrent (); auto & leaderTorqueCurrent = diffMech . GetLeader (). GetTorqueCurrent (); auto & followerSupplyCurrent = diffMech . GetFollower (). GetSupplyCurrent (); auto & followerTorqueCurrent = diffMech . GetFollower (). GetTorqueCurrent (); Python # Pull out some StatusSignals from the mechanism avg_position = self . _diff_mech . get_average_position () diff_position = self . _diff_mech . get_differential_position () avg_closed_loop_ref = self . _diff_mech . get_average_closed_loop_reference () diff_closed_loop_ref = self . _diff_mech . get_differential_closed_loop_reference () # Also pull out supply and torque current from both motors leader_supply_current = self . _diff_mech . leader . get_supply_current () leader_torque_current = self . _diff_mech . leader . get_torque_current () follower_supply_current = self . _diff_mech . follower . get_supply_current () follower_torque_current = self . _diff_mech . follower . get_torque_current () Automatic Fault Protection The differential mechanisms provide an optional periodic() ( Java , C++ , Python ) function that, when called periodically, automatically detects dangerous fault conditions and disables the mechanism to prevent damage. The state of the mechanism can be fetched using getMechanismState() ( Java , C++ , Python ). Note This section only applies when calling periodic() . Some faults temporarily disable the mechanism until they are naturally resolved. For example, the mechanism temporarily disables when one of the motor controllers briefly disconnects from CAN and re-enables once it returns. isDisabled() (or MechanismState.Disabled ) can be used to check if the mechanism is disabled, and getDisabledReason() ( Java , C++ , Python ) reports the reason for the disable. Other faults are higher in severity and require user action before the mechanism can safely re-enable. For example, the mechanism disables when one of the motor controllers is power cycled, as its position is likely invalid and requires a call to setPosition . requiresUserAction() (or MechanismState.RequiresUserAction ) can be used to check for these fault conditions, and getRequiresUserReason() ( Java , C++ , Python ) reports the reason that user action is required. Once the mechanism has been confirmed to be in a safe state, the mechanism can be re-enabled using clearUserRequirement() ( Java , C++ , Python ). Note isDisabled() also reports true when requiresUserAction() reports true. Java // If we encounter a critical fault condition, schedule a // WPILib Command to recalibrate our zero and re-enable new Trigger ( diffMech :: requiresUserAction ). onTrue ( calibrateZero (). finallyDo ( diffMech :: clearUserRequirement ) ); C++ // If we encounter a critical fault condition, schedule a // WPILib Command to recalibrate our zero and re-enable frc2 :: Trigger {[ this ] { return diffMech . RequiresUserAction (); }}. OnTrue ( CalibrateZero (). FinallyDo ([ this ] { diffMech . ClearUserRequirement (); }) ); Python # If we encounter a critical fault condition, schedule a # WPILib Command to recalibrate our zero and re-enable Trigger ( self . _diff_mech . requires_user_action ) . onTrue ( self . calibrate_zero () . finallyDo ( self . _diff_mech . clear_user_requirement ) )",
- "content_preview": "Using the Differential Mechanism API DifferentialMechanism ( Java , C++ , Python ) and SimpleDifferentialMechanism ( Java , C++ , Python ) automatically set up and applies all configs to the two provided motor controllers on construction."
+ "content": "Device Faults “Faults” are status indicators on CTR Electronics CAN devices that indicate a certain behavior or event has occurred. Faults do not directly affect the behavior of a device; instead, they indicate the device’s current status and highlight potential issues. Faults are stored in two fashions. There are “live” faults, which are reported in real-time, and “sticky” faults, which assert persistently and stay asserted until they are manually cleared (like trouble codes in a vehicle). Sticky Faults can be cleared by clicking the Clear Faults button in Phoenix Tuner X, or by calling clearStickyFaults() on the device in the robot program. A regular fault can only be cleared when the offending problem has been resolved. Using API to Retrieve Faults Faults can also be retrieved in API using the getFault_*() (regular) or getStickyFault_*() (sticky) methods on the device object. This can be useful for diagnostics or error handling. Java var faulted = m_cancoder . getFault_BadMagnet (). getValue (); if ( faulted ) { // do action when bad magnet fault is set } C++ auto faulted = m_cancoder . GetFault_BadMagnet (). GetValue (); if ( faulted ) { // do action when bad magnet fault is set } Python faulted = self . cancoder . get_fault_bad_magnet () . value if faulted : # do action when bad magnet fault is set A list of possible faults can be found in the API documentation for each device. Using API to Clear Sticky Faults Sticky faults can be cleared in API using the clearStickyFaults() method on the device objects. Additionally, individual sticky faults may be cleared using the clearStickyFault_*() APIs. Note Clearing sticky faults is a blocking operation and should not be run in a periodic loop. Java // clear the undervoltage sticky fault m_cancoder . clearStickyFault_Undervoltage (); C++ // clear the undervoltage sticky fault m_cancoder . ClearStickyFault_Undervoltage (); Python # clear the undervoltage sticky fault self . cancoder . clear_sticky_fault_undervoltage ()",
+ "content_preview": "Device Faults “Faults” are status indicators on CTR Electronics CAN devices that indicate a certain behavior or event has occurred. Faults do not directly affect the behavior of a device; instead, they indicate the device’s current status and highlight potential issues."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/migration/migration-guide/index.html",
- "title": "API Migration",
- "section": "General",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tools.html",
+ "title": "Tools",
+ "section": "Phoenix Tuner X",
"language": "All",
- "content": "API Migration This section serves as a “cheat sheet” of commonly-used functions in Phoenix 5 and their equivalents in Phoenix 6. API Structure General structure of the Phoenix 6 namespaces and packages Configuration Configuring device configs in robot code Status Signals Using status signals to retrieve sensor data from devices Control Requests Using control requests to control the functionality of actuators, such as the TalonFX Closed-Loop Control Configuring and using closed-loop control requests Feature Replacements Other features replaced or improved upon in Phoenix 6",
- "content_preview": "API Migration This section serves as a “cheat sheet” of commonly-used functions in Phoenix 5 and their equivalents in Phoenix 6. API Structure General structure of the Phoenix 6 namespaces and packages Configuration Configuring device configs in robot code Status Signals Using status signals to..."
+ "content": "Tools Tuner offers additional, miscellaneous functionality in the form of tool pages. Extracting Signal Logs Log Analysis (Beta) CHRP Converter",
+ "content_preview": "Tools Tuner offers additional, miscellaneous functionality in the form of tool pages. Extracting Signal Logs Log Analysis (Beta) CHRP Converter"
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/installation/requirements.html",
- "title": "Requirements",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/migration/migration-guide/feature-replacements-guide.html",
+ "title": "Feature Replacements",
"section": "General",
"language": "All",
- "content": "Requirements This document explains the requirements to use Phoenix 6. Supported Devices Phoenix 6 supports the following devices: CANcoder CANdi™ CANdle® CANrange Pigeon 2.0 Talon FX ( Falcon 500 , Kraken X60 , Kraken X44 ) Talon FXS CAN Bus Requirements Phoenix 6 devices are supported on the below CAN bus adapters. FRC roboRIO CANivore non-FRC On Linux systems, any SocketCAN capable adapter will work, but the CANivore is highly recommended. CANivore offers additional functionality over other SocketCAN adapters. On Windows systems, you will need a CANivore to communicate with hardware. System Requirements Phoenix 6 supports a plethora of languages and operating systems. The table found below details which languages are supported on what platforms. FRC Targets Supported Languages Supports CANivore Supports High-Fidelity Simulation NI roboRIO Java, C++, Python Yes n/a Windows 10/11 x86-64 Java, C++, Python Yes Yes Linux x86-64 (desktop) [ 1 ] and ARM64 [ 2 ] Java, C++, Python Yes Yes macOS Java, C++, Python No Yes non-FRC Targets Supported Languages Supports CANivore Supports High-Fidelity Simulation Windows 10/11 x86-64 C#, Python Yes Yes Linux x86-64 (desktop) [ 1 ] and ARM64 [ 2 ] C++, C#, Python Yes Yes (C# and Python only) macOS (Simulation Only) C#, Python No Yes [ 1 ] ( 1 , 2 ) Supported Linux x86-64 (desktop) targets: Ubuntu 22.04 or newer Debian Bookworm or newer [ 2 ] ( 1 , 2 ) Supported Linux ARM64 targets: Raspberry Pi NVIDIA Jetson (Jetpack 6 or newer) Ubuntu 22.04 or newer Debian Bookworm or newer",
- "content_preview": "Requirements This document explains the requirements to use Phoenix 6. Supported Devices Phoenix 6 supports the following devices: CANcoder CANdi™ CANdle® CANrange Pigeon 2.0 Talon FX ( Falcon 500 , Kraken X60 , Kraken X44 ) Talon FXS CAN Bus Requirements Phoenix 6 devices are supported on the..."
+ "content": "Feature Replacements In addition to the changes shown in the other sections, several other Phoenix 5 features have been replaced or improved upon in Phoenix 6. Motor Invert In Phoenix 6, motor invert is now a persistent config ( Java , C++ ) instead of a control signal. Warning Since invert is a persistent config, getting and setting motor inverts are now blocking API calls. We recommend that users only set the invert once at program startup. Neutral Mode In Phoenix 6, Neutral mode is now available in API as a config ( Java , C++ ). Many control requests also have the ability to override the neutral mode to either force braking ( Java , C++ ) or force coasting ( Java , C++ ). Nominal Output The Talon FX forward and reverse Nominal Output configs have been removed in Phoenix 6. The typical use case of the nominal output configs is to overcome friction in closed-loop control modes, which can now be achieved using the kS feedforward parameter ( Java , C++ ). Sensor Phase The Talon FX setSensorPhase() method has been removed in Phoenix 6. The Talon FX integrated sensor is always in phase, so the method does nothing in Phoenix 5. When using a remote sensor, you can invert the remote sensor to bring it in phase with the Talon FX. Sensor Initialization Strategy The Talon FX and CANcoder sensors are always initialized to their absolute position in Phoenix 6. Clear Position on Limit In Phoenix 5, users could configure the TalonFX to clear its sensor position (i.e. set to 0) when a limit switch is triggered. In Phoenix 6, this feature has been improved to allow users to specify the applied sensor position when a limit switch is triggered. This can be configured using the *LimitAutosetPositionValue configs ( Java , C++ ). Velocity Measurement Period/Window In Phoenix 6, the velocity rolling average window in Talon FX and CANcoder has been replaced with a Kalman filter, resulting in a less noisy velocity signal with a minimal impact on latency (~1 ms). As a result, the velocity measurement period/window configs are no longer necessary in Phoenix 6 and have been removed. Integral Zone and Max Integral Accumulator Phoenix 6 automatically prevents integral windup in closed-loop controls. As a result, the Integral Zone and Max Integral Accumulator configs are no longer necessary and have been removed. CANcoder Sensor Coefficient and Units In Phoenix 6, CANcoder does not support setting a custom sensor coefficient, unit string, and sensor time base. Instead, the CANcoder uses canonical units of rotations and rotations per second using the C++ units library . Features to Be Implemented The following Phoenix 5 features are not implemented in the current release of Phoenix 6 but are planned to be implemented in the future. Feature Status CANdle Support Normal priority Features Omitted The following Phoenix 5 features have been omitted from Phoenix 6. While there are no plans for these features to be added, if there is customer demand for these features, they may be considered for addition in the future. Feedback is welcome at feedback @ ctr-electronics . com . Motion Profile Executor Control requests have been improved to cover many of the use cases of the Motion Profile Executor. Allowable Closed-Loop Error",
+ "content_preview": "Feature Replacements In addition to the changes shown in the other sections, several other Phoenix 5 features have been replaced or improved upon in Phoenix 6. Motor Invert In Phoenix 6, motor invert is now a persistent config ( Java , C++ ) instead of a control signal."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/api-usage/actuator-limits.html",
- "title": "Actuator Limits",
- "section": "API Reference",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/device-list.html",
+ "title": "Device List",
+ "section": "Phoenix Tuner X",
"language": "All",
- "content": "Actuator Limits CTR Electronics actuators, such as the TalonFX, support various kinds of hardware and software limits. Note The TalonFX + Kraken X60 does not support hardware limit switches. Instead, control request limit overrides can be used, or a CANcoder/CANdi™/CANrange can be used as a remote limit switch . Documentation on wiring limit switches can be found here . Retrieving Limit Switch State The state of the forward or reverse limit switch can be retrieved from the API via getForwardLimit() and getReverseLimit() . Additionally, the state of the forward or reverse soft limit can be retrieved from the API via getFault_ForwardSoftLimit() and getFault_ReverseSoftLimit() . Java var forwardLimit = m_motor . getForwardLimit (); if ( forwardLimit . getValue () == ForwardLimitValue . ClosedToGround ) { // do action when forward limit is closed } var forwardSoftLimit = m_motor . getFault_ForwardSoftLimit (); if ( forwardSoftLimit . getValue ()) { // do action when forward soft limit is reached } C++ auto & forwardLimit = m_motor . GetForwardLimit (); if ( forwardLimit . GetValue () == signals :: ForwardLimitValue :: ClosedToGround ) { // do action when forward limit is closed } auto & forwardSoftLimit = m_motor . GetFault_ForwardSoftLimit (); if ( forwardSoftLimit . GetValue ()) { // do action when forward soft limit is reached } Python forward_limit = self . motor . get_forward_limit () if forward_limit . value is signals . ForwardLimitValue . CLOSED_TO_GROUND : # do action when forward limit is closed forward_soft_limit = self . motor . get_fault_forward_soft_limit () if forward_soft_limit . value : # do action when forward soft limit is reached Control Request Limits Many control requests support overriding the limit switch values using LimitForwardMotion and LimitReverseMotion parameters ( Java , C++ , Python ). These allow users to use other limit switch sensors connected to the robot controller. Java final DigitalInput m_forwardLimit = new DigitalInput ( 0 ); final DigitalInput m_reverseLimit = new DigitalInput ( 1 ); final DutyCycleOut m_dutyCycle = new DutyCycleOut ( 0.0 ); m_motor . setControl ( m_dutyCycle . withOutput ( 0.5 ) . withLimitForwardMotion ( m_forwardLimit . get ()) . withLimitReverseMotion ( m_reverseLimit . get ()) ); C++ frc :: DigitalInput m_forwardLimit { 0 }; frc :: DigitalInput m_reverseLimit { 1 }; controls :: DutyCycleOut m_dutyCycle { 0.0 }; m_motor . SetControl ( m_dutyCycle . WithOutput ( 0.5 ) . WithLimitForwardMotion ( m_forwardLimit . Get ()) . WithLimitReverseMotion ( m_reverseLimit . Get ()) ); Python self . forward_limit = wpilib . DigitalInput ( 0 ) self . reverse_limit = wpilib . DigitalInput ( 1 ) self . duty_cycle = controls . DutyCycleOut ( 0.0 ) self . motor . set_control ( self . duty_cycle . with_output ( 0.5 ) . with_limit_forward_motion ( self . forward_limit . get ()) . with_limit_reverse_motion ( self . reverse_limit . get ()) ) Remote Limit Switches Supported devices (TalonFX, CANifier, CANcoder, CANdi™, CANrange) can be utilized as a remote limit switch, disabling actuator outputs when triggers. When utilizing a CANcoder as a remote limit, the limit will trigger when the magnet strength changes from BAD (red) to ADEQUATE (orange) or GOOD (green). When utilizing a CANrange as a remote limit, the limit will trigger when the proximity detect is tripped following the ProximityParamsConfigs ( Java , C++ , Python ). When utilizing a CANdi™ as a remote limit, the limit will trigger when the S1Closed or S2Closed signal is true. The remote limit switch can be selected using the LimitSource and LimitRemoteSensorID configs. Java var limitConfigs = new HardwareLimitSwitchConfigs (); limitConfigs . ForwardLimitSource = ForwardLimitSourceValue . RemoteCANcoder ; limitConfigs . ForwardLimitRemoteSensorID = m_cancoder . getDeviceID (); m_motor . getConfigurator (). apply ( limitConfigs ); C++ configs :: HardwareLimitSwitchConfigs limitConfigs {}; limitConfigs . ForwardLimitSource = signals :: ForwardLimitSourceValue :: RemoteCANcoder ; limitConfigs . ForwardLimitRemoteSensorID = m_cancoder . GetDeviceID (); m_motor . GetConfigurator (). Apply ( limitConfigs ); Python limit_configs = configs . HardwareLimitSwitchConfigs () limit_configs . forward_limit_source = signals . ForwardLimitSourceValue . REMOTE_CANCODER limit_configs . forward_limit_remote_sensor_id = self . cancoder . device_id self . motor . configurator . apply ( limit_configs )",
- "content_preview": "Actuator Limits CTR Electronics actuators, such as the TalonFX, support various kinds of hardware and software limits. Note The TalonFX + Kraken X60 does not support hardware limit switches."
+ "content": "Device List Card Layout Grid Layout The Devices page is the first page that is shown to the user upon launching the application. The Devices page by default shows a grid of cards, but can be changed to a flat grid view (similar to Phoenix Tuner v1) by clicking on the 4 grid square icon located in the top right corner (not available in Android Tuner X). Card Colors The color of the device cards is helpful as a visual indicator of device state. The meaning of the card color is also shown as text underneath the device title. Color Description Green Device has latest firmware. Purple Device has an unexpected/beta firmware version. Yellow A new firmware version is available. Check the changelog to determine if the new version matters to your application Red Device has a duplicate ID. Blue Failed to retrieve list of available firmware. Clipboard Options & Licensing Phoenix Tuner X provides icons at the bottom right of each card that will allow the user to copy to the clipboard the device details, configs and Self Test. This can be useful for support requests and additional debugging. Devices that support CAN FD are shown via a CAN FD icon in the bottom right of the card. Note The CAN FD icon does not indicate that the device is currently on a CAN FD bus, merely that it supports CAN FD. The other major icon in the bottom right of the device card is the licensing indicator. This showcases the licensing states and when clicked, will open the licensing dialog. Batch Field Upgrade Phoenix Tuner X allows the user to batch field upgrade from the Devices page. The user can either select devices by their checkbox (in the top right corner of their respective card) or by selecting the checkmark icon in the top right. Tip Selecting a device using their checkbox and clicking the checkmark in the top right will select all devices of the same models Step 1 in the above image selects all devices of the same models selected (or all devices if no device is currently check-boxed). Step 2 in the above image opens the field-upgrade dialog. Once the dialog is opened, information detailing the device name, model, ID, and firmware version is presented. There is a year selector in the top-left corner to select the firmware version year. Once the correct firmware year is selected user can begin the upgrade progress by selecting Update to latest . If the user does not want to use the latest firmware version, the Custom year selection allows for the selection of a specific firmware version for each device model. Tip Generally, users should update their devices to the latest available firmware version. If manually selecting a CRF is important, the firmware files are available for download on our GitHub Repo . Important While the user can cancel firmware upgrading using the “X” button in the top-right, this will not cancel the current device in progress. It will finish upgrading the current device and will not upgrade subsequent devices. Typical Tuner X behavior will resume once the current device finishes flashing. Batch Licensing See Batch Activating Licenses",
+ "content_preview": "Device List Card Layout Grid Layout The Devices page is the first page that is shown to the user upon launching the application. The Devices page by default shows a grid of cards, but can be changed to a flat grid view (similar to Phoenix Tuner v1) by clicking on the 4 grid square icon located in..."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/wpilib-integration/unit-testing.html",
- "title": "Unit Testing",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/api-usage/control-requests.html",
+ "title": "Control Requests",
"section": "API Reference",
"language": "All",
- "content": "Unit Testing High-fidelity simulation with CTR Electronics devices can be used for unit testing robot applications. When writing unit tests, the regular device APIs should be used to control devices and read status signals. Just like in simulation, the device SimState API can be used to update the simulated state of the device. Additionally, users must ensure the robot is enabled prior to controlling actuators. This can be accomplished in WPILib by calling DriverStationSim.setEnabled(true) ( Java , C++ ), followed by DriverStation.notifyNewData() to apply the change ( Java , C++ ). Important There may be a short delay between enabling the robot and the simulated actuators being enabled. Unit tests should delay for ~100ms after constructing all devices and enabling the robot to account for this delay. In unit tests, users should utilize the StatusSignal.waitForUpdate() and BaseStatusSignal.waitForAll() APIs to wait for fresh data after sending a control request or modifying the simulated device state. Important There may be a short delay between sending a control request and the simulated device applying the control. Unit tests should delay for ~20ms after sending a control request to account for this delay. Below is an example unit test that verifies the robot is enabled and verifies that the device responds to a control request. Java public class TalonFXTest implements AutoCloseable { static final double DELTA = 1e-3 ; // acceptable deviation range static final double kGearRatio = 10.0 ; TalonFX m_fx ; TalonFXSimState m_fxSim ; DCMotorSim m_motorSim ; @Override public void close () { /* destroy our TalonFX object */ m_fx . close (); } @BeforeEach public void constructDevices () { assert HAL . initialize ( 500 , 0 ); /* create the TalonFX */ m_fx = new TalonFX ( 0 ); m_fxSim = m_fx . getSimState (); /* create the simulated DC motor */ var gearbox = DCMotor . getKrakenX60Foc ( 1 ); m_motorSim = new DCMotorSim ( LinearSystemId . createDCMotorSystem ( gearbox , 0.001 , kGearRatio ), gearbox ); /* enable the robot */ DriverStationSim . setEnabled ( true ); DriverStationSim . notifyNewData (); /* delay ~100ms so the devices can start up and enable */ Timer . delay ( 0.100 ); } @AfterEach void shutdown () { close (); } @Test public void robotIsEnabled () { /* verify that the robot is enabled */ assertTrue ( DriverStation . isEnabled ()); } @Test public void motorDrives () { /* set the voltage supplied by the battery */ m_fxSim . setSupplyVoltage ( RobotController . getBatteryVoltage ()); var dutyCycle = m_fx . getDutyCycle (); /* wait for a fresh duty cycle signal */ dutyCycle . waitForUpdate ( 0.100 ); /* verify that the motor output is zero */ assertEquals ( dutyCycle . getValueAsDouble (), 0.0 , DELTA ); /* request 100% output */ m_fx . setControl ( new DutyCycleOut ( 1.0 )); /* wait for the control to apply and the motor to accelerate */ for ( int i = 0 ; i < 10 ; ++ i ) { Timer . delay ( 0.020 ); m_motorSim . setInputVoltage ( m_fxSim . getMotorVoltage ()); m_motorSim . update ( 0.020 ); m_fxSim . setRawRotorPosition ( m_motorSim . getAngularPosition (). times ( kGearRatio )); m_fxSim . setRotorVelocity ( m_motorSim . getAngularVelocity (). times ( kGearRatio )); } /* wait for a new duty cycle signal */ dutyCycle . waitForUpdate ( 0.100 ); /* verify that the motor output is 1.0 */ assertEquals ( dutyCycle . getValueAsDouble (), 1.0 , DELTA ); } } C++ class TalonFXTest : public testing :: Test { protected : static constexpr double kGearRatio = 10.0 ; /* create the TalonFX */ hardware :: TalonFX m_fx { 0 }; sim :: TalonFXSimState & m_fxSim { m_fx . GetSimState ()}; /* create the simulated DC motor */ frc :: sim :: DCMotorSim m_motorSim { frc :: LinearSystemId :: DCMotorSystem { frc :: DCMotor :: KrakenX60FOC ( 1 ), 0.001 _kg_sq_m , kGearRatio }, frc :: DCMotor :: KrakenX60FOC ( 1 ) }; void SetUp () override { /* enable the robot */ frc :: sim :: DriverStationSim :: SetEnabled ( true ); frc :: sim :: DriverStationSim :: NotifyNewData (); /* delay ~100ms so the devices can start up and enable */ std :: this_thread :: sleep_for ( std :: chrono :: milliseconds { 100 }); } }; TEST_F ( TalonFXTest , RobotIsEnabled ) { /* verify that the robot is enabled */ EXPECT_TRUE ( frc :: DriverStation :: IsEnabled ()); } TEST_F ( TalonFXTest , MotorDrives ) { /* set the voltage supplied by the battery */ m_fxSim . SetSupplyVoltage ( frc :: RobotController :: GetBatteryVoltage ()); auto & dutyCycle = m_fx . GetDutyCycle (); /* wait for a fresh duty cycle signal */ dutyCycle . WaitForUpdate ( 100 _ms ); /* verify that the motor output is zero */ EXPECT_DOUBLE_EQ ( dutyCycle . GetValue (), 0.0 ); /* request 100% output */ m_fx . SetControl ( controls :: DutyCycleOut { 1.0 }); /* wait for the control to apply and the motor to accelerate */ for ( int i = 0 ; i < 10 ; ++ i ) { std :: this_thread :: sleep_for ( std :: chrono :: milliseconds { 20 }); m_motorSim . SetInputVoltage ( m_fxSim . GetMotorVoltage ()); m_motorSim . Update ( 20 _ms ); m_fxSim . SetRawRotorPosition ( kGearRatio * m_motorSim . GetAngularPosition ()); m_fxSim . SetRotorVelocity ( kGearRatio * m_motorSim . GetAngularVelocity ()); } /* wait for a new duty cycle signal */ dutyCycle . WaitForUpdate ( 100 _ms ); /* verify that the motor output is 1.0 */ EXPECT_DOUBLE_EQ ( dutyCycle . GetValue (), 1.0 ); }",
- "content_preview": "Unit Testing High-fidelity simulation with CTR Electronics devices can be used for unit testing robot applications. When writing unit tests, the regular device APIs should be used to control devices and read status signals."
- },
- {
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/migration/migration-guide/configuration-guide.html",
- "title": "Configuration",
- "section": "General",
- "language": "All",
- "content": "Configuration Phoenix 6 simplifies the configuration process through the use of device-specific Configuration classes, as well as configuration groups. Note For more information about configuration in Phoenix 6, see Configuration . Applying Configs v5 Java // set slot 0 gains // 50 ms timeout on each config call m_motor . config_kF ( 0 , 0.05 , 50 ); m_motor . config_kP ( 0 , 0.046 , 50 ); m_motor . config_kI ( 0 , 0.0002 , 50 ); m_motor . config_kD ( 0 , 0.42 , 50 ); C++ // set slot 0 gains // 50 ms timeout on each config call m_motor . Config_kF ( 0 , 0.05 , 50 ); m_motor . Config_kP ( 0 , 0.046 , 50 ); m_motor . Config_kI ( 0 , 0.0002 , 50 ); m_motor . Config_kD ( 0 , 0.42 , 50 ); v6 Java var talonFXConfigs = new TalonFXConfiguration (); // set slot 0 gains and leave every other config factory-default var slot0Configs = talonFXConfigs . Slot0 ; slot0Configs . kV = 0.12 ; slot0Configs . kP = 0.11 ; slot0Configs . kI = 0.5 ; slot0Configs . kD = 0.001 ; // apply all configs, 50 ms total timeout m_talonFX . getConfigurator (). apply ( talonFXConfigs , 0.050 ); C++ configs :: TalonFXConfiguration talonFXConfigs {}; // set slot 0 gains and leave every other config factory-default configs :: Slot0Configs & slot0Configs = talonFXConfigs . Slot0 ; slot0Configs . kV = 0.12 ; slot0Configs . kP = 0.11 ; slot0Configs . kI = 0.5 ; slot0Configs . kD = 0.001 ; // apply all configs, 50 ms total timeout m_talonFX . GetConfigurator (). Apply ( talonFXConfigs , 50 _ms ); Factory Defaulting Configs v5 Java // user must remember to explicitly factory default if they configure devices in code m_motor . configFactoryDefault (); C++ // user must remember to explicitly factory default if they configure devices in code m_motor . ConfigFactoryDefault (); v6 Java // Any unmodified configs in a configuration object are *automatically* factory-defaulted. // As a result, factory-defaulting before applying configs is *unnecessary* when using a // full device configuration object, such as TalonFXConfiguration. // Users can perform a full factory default by passing a new device configuration object. m_motor . getConfigurator (). apply ( new TalonFXConfiguration ()); C++ // Any unmodified configs in a configuration object are *automatically* factory-defaulted; // As a result, factory-defaulting before applying configs is *unnecessary* when using a // full device configuration object, such as TalonFXConfiguration. // Users can perform a full factory default by passing a new device configuration object. m_motor . GetConfigurator (). Apply ( configs :: TalonFXConfiguration {}); Retrieving Configs v5 Java // a limited number of configs have configGet* methods; // for example, you can get the supply current limits var supplyCurLim = new SupplyCurrentLimitConfiguration (); m_motor . configGetSupplyCurrentLimit ( supplyCurLim ); C++ // a limited number of configs have ConfigGet* methods; // for example, you can get the supply current limits SupplyCurrentLimitConfiguration supplyCurLim {}; m_motor . ConfigGetSupplyCurrentLimit ( supplyCurLim ); v6 Java var fx_cfg = new TalonFXConfiguration (); // fetch *all* configs currently applied to the device m_motor . getConfigurator (). refresh ( fx_cfg ); C++ configs :: TalonFXConfiguration fx_cfg {}; // fetch *all* configs currently applied to the device m_motor . GetConfigurator (). Refresh ( fx_cfg );",
- "content_preview": "Configuration Phoenix 6 simplifies the configuration process through the use of device-specific Configuration classes, as well as configuration groups. Note For more information about configuration in Phoenix 6, see Configuration ."
+ "content": "Control Requests Control Requests represent the output of a device. A list of control requests can be found in the API docs ( Java , C++ , Python ). Note Phoenix 6 utilizes the C++ units library and, optionally, the Java units library when applicable. Using the Java units library may increase GC overhead. Applying a Control Request Control requests can be applied by calling setControl() on the device object. setControl() returns a StatusCode ( Java , C++ , Python ) enum that represents success state. A successful request will return StatusCode.OK . Java // Command m_motor to 100% of duty cycle m_motor . setControl ( new DutyCycleOut ( 1.0 )); C++ // Command m_motor to 100% of duty cycle m_motor . SetControl ( controls :: DutyCycleOut { 1.0 }); Python # Command m_motor to 100% of duty cycle self . motor . set_control ( controls . DutyCycleOut ( 1.0 )) Modifying a Control Request Control requests are mutable, so they can be saved in a member variable and reused. For example, DutyCycleOut ( Java , C++ , Python ) has an Output member variable that can be manipulated, thus changing the output DutyCycle (proportion of supply voltage). Note Java users should reuse control requests to prevent excessive invocation of the Garbage Collector. Java final DutyCycleOut m_motorRequest = new DutyCycleOut ( 0.0 ); m_motorRequest . Output = 1.0 ; m_motor . setControl ( m_motorRequest ); C++ controls :: DutyCycleOut m_motorRequest { 0.0 }; m_motorRequest . Output = 1.0 ; m_motor . SetControl ( m_motorRequest ); Python self . motor_request = controls . DutyCycleOut ( 0.0 ) self . motor_request . output = 1.0 self . motor . set_control ( self . motor_request ) Method Chaining API Control requests also supports modification using method chaining. This can be useful for mutating multiple values of a control request. In Java, this can also be used to provide a unit type. Java // initialize torque current FOC request with 0 amps final TorqueCurrentFOC m_motorRequest = new TorqueCurrentFOC ( 0 ); // mutate request with output of 10 amps and max duty cycle 0.5 m_motor . setControl ( m_motorRequest . withOutput ( Amps . of ( 10 )). withMaxAbsDutyCycle ( 0.5 )); C++ // initialize torque current FOC request with 0 amps controls :: TorqueCurrentFOC m_motorRequest { 0 _A }; // mutate request with output of 10 amps and max duty cycle 0.5 m_motor . SetControl ( m_motorRequest . WithOutput ( 10 _A ). WithMaxAbsDutyCycle ( 0.5 )); Python # initialize torque current FOC request with 0 amps self . motor_request = controls . TorqueCurrentFOC ( 0 ) # mutate request with output of 10 amps and max duty cycle 0.5 self . motor . set_control ( self . motor_request . with_output ( 10 ) . with_max_abs_duty_cycle ( 0.5 )) Changing Update Frequency Control requests are automatically transmitted at a fixed update frequency. This update frequency can be modified by changing the UpdateFreqHz ( Java , C++ , Python ) field of the control request before sending it to the device. Java // create a duty cycle request final DutyCycleOut m_motorRequest = new DutyCycleOut ( 0 ); // reduce the update frequency to 50 Hz m_motorRequest . UpdateFreqHz = 50 ; C++ // create a duty cycle request controls :: DutyCycleOut m_motorRequest { 0 }; // reduce the update frequency to 50 Hz m_motorRequest . UpdateFreqHz = 50 ; Python # create a duty cycle request self . motor_request = controls . DutyCycleOut ( 0 ) # reduce the update frequency to 50 Hz self . motor_request . update_freq_hz = 50 Tip UpdateFreqHz can be set to 0 Hz to synchronously one-shot the control request. In this case, users must ensure the control request is sent periodically in their robot code. Therefore, we recommend users call setControl no slower than 20 Hz (50 ms) when the control is one-shot.",
+ "content_preview": "Control Requests Control Requests represent the output of a device. A list of control requests can be found in the API docs ( Java , C++ , Python ). Note Phoenix 6 utilizes the C++ units library and, optionally, the Java units library when applicable."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/controlling-devices.html",
- "title": "Controlling Devices",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-elev/generation.html",
+ "title": "Generation",
"section": "Phoenix Tuner X",
"language": "All",
- "content": "Controlling Devices Tuner X can be used to directly control devices outside a robot program. When combined with Plotting , it can be an excellent tool for calculating closed loop gains or isolating mechanical issues. Devices can be controlled by clicking on the red “DISABLED” button, switching it to “ENABLED”. Important FRC users must enable the robot in Driver Station while using Tuner X control. During this time, the output can be adjusted using the sliders or the text entries below it. Control modes can be changed using the dropdown below the disable/enable button. FRC Locked The “lock” icon next to the “DISABLED” button indicates that this device is FRC locked. This means the FRC Driver Station must also be enabled for the device to actuate. For more information, see FRC Lock .",
- "content_preview": "Controlling Devices Tuner X can be used to directly control devices outside a robot program. When combined with Plotting , it can be an excellent tool for calculating closed loop gains or isolating mechanical issues."
+ "content": "Generation The subsystem is generated directly into an existing robot project . Select Browser and navigate to the root of a robot project. Then, press Generate . Open the robot project in WPILib VS Code.",
+ "content_preview": "Generation The subsystem is generated directly into an existing robot project . Select Browser and navigate to the root of a robot project. Then, press Generate . Open the robot project in WPILib VS Code."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/wpilib-integration/motorcontroller-integration.html",
- "title": "MotorController Integration",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/mechanisms/swerve/swerve-simulation.html",
+ "title": "Swerve Simulation",
"section": "API Reference",
"language": "All",
- "content": "MotorController Integration Phoenix 6 motor controller classes such as TalonFX ( Java , C++ , Python ) implement many APIs from the MotorController ( Java , C++ ) interface. This allows Phoenix 6 motor controllers to more easily be used in WPILib drivetrain classes such as DifferentialDrive . Java // instantiate motor controllers final TalonFX m_motorLeft = new TalonFX ( 0 ); final TalonFX m_motorRight = new TalonFX ( 1 ); // create DifferentialDrive object for robot control final DifferentialDrive m_diffDrive = new DifferentialDrive ( m_motorLeft :: set , m_motorRight :: set ); // instantiate joystick final XboxController m_driverJoy = new XboxController ( 0 ); public void teleopPeriodic () { var forward = - m_driverJoy . getLeftY (); var rot = - m_driverJoy . getRightX (); m_diffDrive . arcadeDrive ( forward , rot ); } C++ (Source) void Robot::TeleopPeriodic () { auto forward = - m_driverJoy . GetLeftY (); auto rot = - m_driverJoy . GetRightX (); m_diffDrive . ArcadeDrive ( forward , rot ); } C++ (Header) // instantiate motor controllers hardware :: TalonFX m_motorLeft { 0 }; hardware :: TalonFX m_motorRight { 1 }; // create differentialdrive object for robot control frc :: DifferentialDrive m_diffDrive { [ this ]( double output ) { m_motorLeft . Set ( output ); }, [ this ]( double output ) { m_motorRight . Set ( output ); } }; // instantiate joystick frc :: XboxController m_driverJoy { 0 }; Python def __init__ ( self ): # instantiate motor controllers self . motor_left = hardware . TalonFX ( 0 ) self . motor_right = hardware . TalonFX ( 1 ) # create DifferentialDrive object for robot control self . diff_drive = wpilib . drive . DifferentialDrive ( self . motor_left . set , self . motor_right . set ) # instantiate joystick self . driver_joy = wpilib . XboxController ( 0 ) def teleopPeriodic ( self ): forward = - self . driver_joy . getLeftY () rot = - self . driver_joy . getRightX () self . diff_drive . arcadeDrive ( forward , rot ) Motor Safety CTR Electronics supported actuators implement WPILib Motor Safety . In additional to the normal enable signal of CTR Electronics actuators, Motor Safety will automatically disable the device according to the WPILib Motor Safety implementation. Simulation It’s recommended that users set supply voltage to RobotController.getBatteryVoltage() ( Java , C++ ) to take advantage of WPILib’s BatterySim ( Java , C++ ) API. Additionally, the simulated device state is shown in the simulation Other Devices menu.",
- "content_preview": "MotorController Integration Phoenix 6 motor controller classes such as TalonFX ( Java , C++ , Python ) implement many APIs from the MotorController ( Java , C++ ) interface. This allows Phoenix 6 motor controllers to more easily be used in WPILib drivetrain classes such as DifferentialDrive ."
+ "content": "Swerve Simulation Important Swerve simulation is only supported for FRC users. The API supports a functionality focused simulation. This means that the simulation API assumes that the swerve drive is perfect (no scrub and no wheel slip). Additionally, it assumes a constant drive motor inertia regardless of the type of motion. To update the simulated swerve robot state, ensure drivetrain.updateSimState(...) ( Java , C++ , Python ) is called in simulationPeriodic() . The typical update rate of a robot project is 20 ms (0.020 seconds), and RobotController.getBatteryVoltage() ( Java , C++ , Python ) can be used to get the simulated battery voltage. The behavior of the simulated drivetrain can be improved to more closely match hardware by running the simulation logic at a faster update rate, such as by using a WPILib Notifier as demonstrated below. Important When using CommandSwerveDrivetrain from our examples or Tuner X, this is already handled by the subsystem. Java private static final double kSimLoopPeriod = 0.004 ; // 4 ms private Notifier m_simNotifier = null ; private double m_lastSimTime ; @Override public void simulationInit () { m_lastSimTime = Utils . getCurrentTimeSeconds (); /* Run simulation at a faster rate so PID gains behave more reasonably */ m_simNotifier = new Notifier (() -> { final double currentTime = Utils . getCurrentTimeSeconds (); double deltaTime = currentTime - m_lastSimTime ; m_lastSimTime = currentTime ; /* Use the measured time delta, get battery voltage from WPILib */ drivetrain . updateSimState ( deltaTime , RobotController . getBatteryVoltage ()); }); m_simNotifier . startPeriodic ( kSimLoopPeriod ); } C++ private : static constexpr units :: second_t kSimLoopPeriod = 4 _ms ; std :: unique_ptr < frc :: Notifier > m_simNotifier ; units :: second_t m_lastSimTime ; public : void SimulationInit () override { m_lastSimTime = utils :: GetCurrentTime (); /* Run simulation at a faster rate so PID gains behave more reasonably */ m_simNotifier = std :: make_unique < frc :: Notifier > ([ this ] { units :: second_t const currentTime = utils :: GetCurrentTime (); auto const deltaTime = currentTime - m_lastSimTime ; m_lastSimTime = currentTime ; /* Use the measured time delta, get battery voltage from WPILib */ drivetrain . UpdateSimState ( deltaTime , frc :: RobotController :: GetBatteryVoltage ()); }); m_simNotifier -> StartPeriodic ( kSimLoopPeriod ); } Python _SIM_LOOP_PERIOD : units . second = 0.004 # 4 ms def __init__ ( self ): self . _sim_notifier : Notifier | None = None self . _last_sim_time : units . second = 0.0 # ... def simulationInit ( self ): def _sim_periodic (): current_time = utils . get_current_time_seconds () delta_time = current_time - self . _last_sim_time self . _last_sim_time = current_time # Use the measured time delta, get battery voltage from WPILib self . drivetrain . update_sim_state ( delta_time , RobotController . getBatteryVoltage ()) # Run simulation at a faster rate so PID gains behave more reasonably self . _last_sim_time = utils . get_current_time_seconds () self . _sim_notifier = Notifier ( _sim_periodic ) self . _sim_notifier . startPeriodic ( self . _SIM_LOOP_PERIOD ) Simulation FAQ Q: My robot does not move in simulation A: Verify that all gains are non-zero and that the steer/drive inertia is non-zero. Q: My robot drifts a bit when driving while rotating A: Azimuth inertia and control latency is simulated. As a result, simulated swerve modules match the behavior of hardware in lagging behind the module targets, which can be improved by tuning the steer PID gains.",
+ "content_preview": "Swerve Simulation Important Swerve simulation is only supported for FRC users. The API supports a functionality focused simulation. This means that the simulation API assumes that the swerve drive is perfect (no scrub and no wheel slip)."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/self-test.html",
- "title": "Self Test Snapshot",
- "section": "Phoenix Tuner X",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/migration/migration-guide/status-signals-guide.html",
+ "title": "Status Signals",
+ "section": "General",
"language": "All",
- "content": "Self Test Snapshot Self Test Snapshot is a diagnostic feature of all supported devices that will show the immediate state of the device. This is extremely useful for troubleshooting and ensuring the device is working properly. Phoenix 6 with Phoenix Tuner X improves upon Self Test by showing the information in clean tables, animations and detailed units. Self Test also includes four buttons: Refresh , Clear Faults , Copy Self Test , and Share to Support . Refresh will refresh the Self Test information, Clear faults` will blink the device and clear any faults on the device. Copy Self Test will copy the Self Test information to your clipboard. Share to Support will open the default email client with an email to CTR Electronics support. Viewing Status LEDs Phoenix 6 devices report status LEDs as an animated GIF in Phoenix Tuner X. This can be useful for diagnosing a device when it’s buried in a robot.",
- "content_preview": "Self Test Snapshot Self Test Snapshot is a diagnostic feature of all supported devices that will show the immediate state of the device. This is extremely useful for troubleshooting and ensuring the device is working properly."
+ "content": "Status Signals Phoenix 6 expands the functionality of status signals with the introduction of the StatusSignal ( Java , C++ ). Note For more information about status signals in Phoenix 6, see Status Signals . Using Status Signals v5 Java // get latest TalonFX selected sensor position // units are encoder ticks int sensorPos = m_talonFX . getSelectedSensorPosition (); // latency is unknown // cannot synchronously wait for new data C++ // get latest TalonFX selected sensor position // units are encoder ticks int sensorPos = m_talonFX . GetSelectedSensorPosition (); // latency is unknown // cannot synchronously wait for new data v6 Java // acquire a refreshed TalonFX rotor position signal var rotorPosSignal = m_talonFX . getRotorPosition (); // because we are calling getRotorPosition() every loop, // we do not need to call refresh() //rotorPosSignal.refresh(); // retrieve position value that we just refreshed // units are rotations, uses the units library var rotorPos = rotorPosSignal . getValue (); // the units library can be bypassed using getValueAsDouble() double rotorPosRotations = rotorPosSignal . getValueAsDouble (); // get latency of the signal var rotorPosLatency = rotorPosSignal . getTimestamp (). getLatency (); // synchronously wait 20 ms for new data rotorPosSignal . waitForUpdate ( 0.020 ); C++ // acquire a refreshed TalonFX rotor position signal auto & rotorPosSignal = m_talonFX . GetRotorPosition (); // because we are calling GetRotorPosition() every loop, // we do not need to call Refresh() //rotorPosSignal.Refresh(); // retrieve position value that we just refreshed // units are rotations, uses the units library auto rotorPos = rotorPosSignal . GetValue (); // get latency of the signal auto rotorPosLatency = rotorPosSignal . GetTimestamp (). GetLatency (); // synchronously wait 20 ms for new data rotorPosSignal . WaitForUpdate ( 20 _ms ); Changing Update Frequency (Status Frame Period) v5 Java // slow down the Status 2 frame (selected sensor data) to 5 Hz (200ms) m_talonFX . setStatusFramePeriod ( StatusFrameEnhanced . Status_2_Feedback0 , 200 ); C++ // slow down the Status 2 frame (selected sensor data) to 5 Hz (200ms) m_talonFX . SetStatusFramePeriod ( StatusFrameEnhanced :: Status_2_Feedback0 , 200 ); v6 Java // slow down the position signal to 5 Hz m_talonFX . getPosition (). setUpdateFrequency ( 5 ); C++ // slow down the position signal to 5 Hz m_talonFX . GetPosition (). SetUpdateFrequency ( 5 _Hz ); Note When different update frequencies are specified for signals that share a status frame, the highest update frequency of all the relevant signals will be applied to the entire frame. Users can get a signal’s applied update frequency using the getAppliedUpdateFrequency() method. Common Signals Several status signals have changed name or form in Phoenix 6. General Signals Phoenix 5 Phoenix 6 BusVoltage SupplyVoltage Faults / StickyFaults (fills an object) Fault_* / StickyFault_* (individual faults) FirmwareVersion Version Talon FX Signals Phoenix 5 Phoenix 6 MotorOutputPercent DutyCycle StatorCurrent StatorCurrent (motoring +, braking -), TorqueCurrent (forward +, reverse -) Inverted (true/false; matches setInverted ) AppliedRotorPolarity (CCW+/CW+; typically matches Inverted config, affected by follower features) SelectedSensorPosition / SelectedSensorVelocity Position / Velocity IntegratedSensor* (in SensorCollection ) Rotor* ActiveTrajectory* (only Motion Magic® and the Motion Profile Executor) ClosedLoopReference* (all closed-loop control requests) IsFwdLimitSwitchClosed / IsRevLimitSwitchClosed (true/false) GetForwardLimit / GetReverseLimit (Open/Closed) CANcoder Signals Phoenix 5 Phoenix 6 MagnetFieldStrength MagnetHealth Pigeon 2 Signals Note Many Pigeon 2 signal getters in Phoenix 5 fill an array, such as YawPitchRoll . In Phoenix 6, these signals have been broken up into their individual components, such as Yaw , Pitch , and Roll . Phoenix 5 Phoenix 6 RawGyro AngularVelocity* 6dQuaternion Quat* BiasedAccelerometer Acceleration* BiasedMagnetometer MagneticField* RawMagnetometer RawMagneticField*",
+ "content_preview": "Status Signals Phoenix 6 expands the functionality of status signals with the introduction of the StatusSignal ( Java , C++ ). Note For more information about status signals in Phoenix 6, see Status Signals ."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/canivore/canivore-setup.html",
- "title": "CANivore Setup",
- "section": "CANivore",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/device-specific/talonfx/index.html",
+ "title": "TalonFX",
+ "section": "TalonFX",
"language": "All",
- "content": "CANivore Setup Installing for roboRIO Note Phoenix Tuner X requires a 2023 roboRIO image or newer to configure the CANivore. No additional steps are required. The roboRIO comes with the canivore-usb kernel module pre-installed. Installing for Linux (non-FRC) See CANivore Installation for information on setting up your Linux system for use with a CANivore. Viewing Attached CANivores Attached CANivores can be viewed in Phoenix Tuner X by selecting the CANivores page from the left-hand sidebar. You can specify the target system in the Target IP or Team # text box. Note The Phoenix Diagnostic Server must be running on the target system to use the CANivores page. Tip If you are connecting to CANivores on your local Windows machine, you can enable the CANivore USB toggle and set the target IP to localhost . This runs a diagnostic server within Tuner X so you do not need to run a robot project to communicate with CANivores. Field Upgrading CANivores A CANivore can be field updated using Phoenix Tuner X . Click or tap on the listed CANivore card to open the device details page. The CANivore can then be field upgraded via the dropdown or by manually selected a file: Phoenix Tuner X also allows the user to batch field upgrade CANivores from the list of CANivores in the same manner as batch field upgrading devices . Renaming CANivores CANivores can be given custom names for use within a robot program. This can be configured through Phoenix Tuner X on the specified device card.",
- "content_preview": "CANivore Setup Installing for roboRIO Note Phoenix Tuner X requires a 2023 roboRIO image or newer to configure the CANivore. No additional steps are required. The roboRIO comes with the canivore-usb kernel module pre-installed."
+ "content": "TalonFX Introduction to TalonFX Control Open-Loop Control Closed-Loop Overview Basic PID and Profiling Motion Magic® Controls TalonFX Remote Sensors",
+ "content_preview": "TalonFX Introduction to TalonFX Control Open-Loop Control Closed-Loop Overview Basic PID and Profiling Motion Magic® Controls TalonFX Remote Sensors"
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-elev/tuning.html",
- "title": "Tuning your Elevator",
- "section": "Phoenix Tuner X",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/mechanisms/swerve/swerve-overview.html",
+ "title": "Swerve Overview",
+ "section": "API Reference",
"language": "All",
- "content": "Tuning your Elevator In the third step of the elevator wizard, the user is guided through creating setpoints and calculating closed-loop gains. Tip Check out Basic PID and Profiling for information on how to tune a closed-loop position system. Elevator Setpoints Setpoints are configured in the Setpoints tab in the second column. Add a new setpoint Rename a setpoint Ensure setpoint names are unique, or there will be compile errors when you generate your Elevator. Run setpoint The robot must be ENABLED , or nothing will happen. Delete setpoint Closed-loop Gains Gains can be configured in the first column. While default gains have been calculated, it is highly recommended to just use this as a starting point. The elevator should be tuned in it’s final configuration, with any load that it may need to bear (holding a game piece). The control request used to command the elevator is MotionMagicVoltage ( Java , C++ , Python ), which allows the user to directly control velocity and acceleration for smooth travel.",
- "content_preview": "Tuning your Elevator In the third step of the elevator wizard, the user is guided through creating setpoints and calculating closed-loop gains. Tip Check out Basic PID and Profiling for information on how to tune a closed-loop position system."
+ "content": "Swerve Overview Important Some swerve features, such as simulation support, are only available for FRC users. Phoenix 6 incorporates a high performance swerve API supported in Java, C++, and Python. This API simplifies the boilerplate necessary for swerve and maximizes performance. Tip Tuner X supports a swerve project creator that greatly simplifies the setup process and eliminates common error cases. Small API surface, easily debuggable Build robot characteristics using SwerveModuleConstants ( Java , C++ , Python ) and SwerveDrivetrainConstants ( Java , C++ , Python ). Integrates cleanly into the WPILib command-based framework using CommandSwerveDrivetrain (from our examples or Tuner X). Provide a lambda to telemetrize directly in the odometry loop using registerTelemetry(...) ( Java , C++ , Python ). Extensible and powerful control of the drivetrain via SwerveRequest ( Java , C++ , Python ). Built-in requests tuned for both autonomous and teleoperated robot-centric, field-centric and field-centric facing angle control. Supports common scenarios such as X brake (point all modules toward the center of the robot). Simulation Test your autonomous paths and pose estimation without a physical robot. Simply call updateSimState(...) ( Java , C++ , Python ) in simulationPeriodic() or on a separate thread. Performance Odometry is updated synchronously with the motor controllers. Odometry is received as fast as possible using a separate thread. Control is run inline with odometry updates. Combine with Phoenix Pro and a CANivore with timesync for improved performance. Tip Simulation boilerplate is automatically handled when generating a robot project using Tuner X. Hardware Requirements Utilizing the swerve API requires that the robot drivetrain is composed of supported Phoenix 6 devices. At a minimum, these requirements are: 4 Talon FX or Talon FXS drive motor controllers 4 Talon FX or Talon FXS steer motor controllers 1 Pigeon 2.0 4 encoders (must all be one of the following) 4 CANcoders 4 PWM absolute encoders connected to at least 2 CANdi 4 PWM absolute encoders connected to their corresponding steer Talon FXS Note All drive motor controllers must be of the same type, and all steer motor controllers must be of the same type. However, the drive and steer motor controllers can be different types from each other. For example, you can utilize 4 Talon FXS connected to a Minion for steer and 4 Kraken X60 for drive. Overview of the API Simple usage is comprised of 5 core APIs: SwerveDrivetrainConstants ( Java , C++ , Python ) This class handles characteristics of the robot that are not module specific. e.g. CAN bus, Pigeon 2 ID, whether FD is enabled or not. SwerveModuleConstantsFactory ( Java , C++ , Python ) Factory class with common constants used to instantiate SwerveModuleConstants for each module on the robot. SwerveModuleConstants ( Java , C++ , Python ) Represents the characteristics for a given module. SwerveDrivetrain ( Java , C++ , Python ) Created using SwerveDrivetrainConstants and a SwerveModuleConstants for each module, this is used to control the swerve drivetrain. SwerveRequest ( Java , C++ , Python ) Controls the drivetrain, such as driving field-centric. Usage of these classes is available in the following articles in this section. Swerve Builder API Swerve Requests Swerve Simulation Using the Swerve Drivetrain",
+ "content_preview": "Swerve Overview Important Some swerve features, such as simulation support, are only available for FRC users. Phoenix 6 incorporates a high performance swerve API supported in Java, C++, and Python. This API simplifies the boilerplate necessary for swerve and maximizes performance."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/plotting.html",
- "title": "Plotting",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/device-details-page.html",
+ "title": "Device Details",
"section": "Phoenix Tuner X",
"language": "All",
- "content": "Plotting Phoenix Tuner X supports an accurate and highly performant real-time plotter. Users can plot and manipulate multiple signals simultaneously. Plotter has undergone strenuous stress testing to ensure hours of plotting operation. This can be used in conjunction with configs and control for tasks like tuning PID loops. Tuner supports plotting signals as they arrive, ensuring that every visible point is a signal update that has been sent by a device. Users can zoom in and hover over points for a tooltip highlighting the exact value of the datapoint. Adding a Signal Signals can be added from the right-side menu. Manipulating the Plot Horizontal Stretch Vertical Stretch Panning Box Selection The plot can be manipulated in a variety of ways: Click + Drag to pan around the plot Scroll over the X-axis or the plot to horizontally stretch the timescale Shift + Scroll over a signal’s Y-axis to vertically stretch that signal Shift + Scroll over the plot to vertically stretch all visible signals’ Y-axis Ctrl + Drag to pan across all signals’ Y-axis Additional Customizations The topbar of plotter contains a variety of options used for controlling data collection. Play : Play/Pauses the plotter. Points are not collected when the plotter is paused. Horizontal Tracking : Whether the horizontal axis should be locked to the most recent point. This automatically turns off when pan operations occur. Users can click this checkbox to keep their current Y-axis min/max but resume seeking at the beginning. Timespan : How long points should be kept before being discarded. Increasing this value will result in increased memory usage. Update Rate : How often to fetch points from the diagnostic server. Update rates larger than 100ms may result in lost points and lower update rates will result in increased CPU utilization. Export : Export currently visible signals to CSV. Clear Point : Remove all points and reset the plot back to its default state. Reset Zoom : Keep current points but reset horizontal and vertical zoom to its defaults A plot customization tab is available at the bottom of the device view. This tab allows users to group signals together (create a group with the Plus icon, and then drag signals over the group name), customize the color of the signal, explicitly set min/max, etc. Additionally, statistics for a signal can be viewed under Statistics .",
- "content_preview": "Plotting Phoenix Tuner X supports an accurate and highly performant real-time plotter. Users can plot and manipulate multiple signals simultaneously. Plotter has undergone strenuous stress testing to ensure hours of plotting operation."
- },
- {
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/device-specific/talonfx/basic-pid-control.html",
- "title": "Basic PID and Profiling",
- "section": "TalonFX",
- "language": "All",
- "content": "Basic PID and Profiling The Talon FX supports basic PID control and motion profiling for position and velocity. Note For more information on feedback and feedforward gains, see Closed-Loop Overview . Position Control A Position closed loop can be used to target a specified motor position (in rotations). Position closed loop is currently supported for all base control output types . The units of the output are determined by the control output type. In a Position closed loop, the gains should be configured as follows: \\(K_g\\) - output to overcome gravity (output) \\(K_s\\) - Velocity Sign: unused; Closed-Loop Sign: output to overcome static friction (output) \\(K_v\\) - unused, as there is no target velocity \\(K_a\\) - unused, as there is no target acceleration \\(K_p\\) - output per unit of error in position (output/rotation) \\(K_i\\) - output per unit of integrated error in position (output/(rotation*s)) \\(K_d\\) - output per unit of error derivative in position (output/rps) Java // in init function, set slot 0 gains var slot0Configs = new Slot0Configs (); slot0Configs . kP = 2.4 ; // An error of 1 rotation results in 2.4 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0.1 ; // A velocity of 1 rps results in 0.1 V output m_talonFX . getConfigurator (). apply ( slot0Configs ); C++ // in init function, set slot 0 gains configs :: Slot0Configs slot0Configs {}; slot0Configs . kP = 2.4 ; // An error of 1 rotation results in 2.4 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0.1 ; // A velocity of 1 rps results in 0.1 V output m_talonFX . GetConfigurator (). Apply ( slot0Configs ); Python # in init function, set slot 0 gains slot0_configs = configs . Slot0Configs () slot0_configs . k_p = 2.4 # An error of 1 rotation results in 2.4 V output slot0_configs . k_i = 0 # no output for integrated error slot0_configs . k_d = 0.1 # A velocity of 1 rps results in 0.1 V output self . talonfx . configurator . apply ( slot0_configs ) Once the gains are configured, the Position closed loop control request can be sent to the TalonFX. The control request object has an optional feedforward term that can be used to add an arbitrary value to the output, which can be useful to account for the effects of gravity or friction. Java // create a position closed-loop request, voltage output, slot 0 configs final PositionVoltage m_request = new PositionVoltage ( 0 ). withSlot ( 0 ); // set position to 10 rotations m_talonFX . setControl ( m_request . withPosition ( 10 )); C++ // create a position closed-loop request, voltage output, slot 0 configs controls :: PositionVoltage m_request = controls :: PositionVoltage { 0 _tr }. WithSlot ( 0 ); // set position to 10 rotations m_talonFX . SetControl ( m_request . WithPosition ( 10 _tr )); Python # create a position closed-loop request, voltage output, slot 0 configs self . request = controls . PositionVoltage ( 0 ) . with_slot ( 0 ) # set position to 10 rotations self . talonfx . set_control ( self . request . with_position ( 10 )) Velocity Control A Velocity closed loop can be used to maintain a target velocity (in rotations per second). This can be useful for controlling flywheels, where a velocity needs to be maintained for accurate shooting. Velocity closed loop is currently supported for all base control output types . The units of the output are determined by the control output type. In a Velocity closed loop, the gains should be configured as follows: \\(K_g\\) - output to overcome gravity (output) \\(K_s\\) - output to overcome static friction (output) \\(K_v\\) - output per unit of requested velocity (output/rps) \\(K_a\\) - unused, as there is no target acceleration \\(K_p\\) - output per unit of error in velocity (output/rps) \\(K_i\\) - output per unit of integrated error in velocity (output/rotation) \\(K_d\\) - output per unit of error derivative in velocity (output/(rps/s)) Java // in init function, set slot 0 gains var slot0Configs = new Slot0Configs (); slot0Configs . kS = 0.1 ; // Add 0.1 V output to overcome static friction slot0Configs . kV = 0.12 ; // A velocity target of 1 rps results in 0.12 V output slot0Configs . kP = 0.11 ; // An error of 1 rps results in 0.11 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0 ; // no output for error derivative m_talonFX . getConfigurator (). apply ( slot0Configs ); C++ // in init function, set slot 0 gains configs :: Slot0Configs slot0Configs {}; slot0Configs . kS = 0.1 ; // Add 0.1 V output to overcome static friction slot0Configs . kV = 0.12 ; // A velocity target of 1 rps results in 0.12 V output slot0Configs . kP = 0.11 ; // An error of 1 rps results in 0.11 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0 ; // no output for error derivative m_talonFX . GetConfigurator (). Apply ( slot0Configs ); Python slot0_configs = configs . Slot0Configs () slot0_configs . k_s = 0.1 # Add 0.1 V output to overcome static friction slot0_configs . k_v = 0.12 # A velocity target of 1 rps results in 0.12 V output slot0_configs . k_p = 0.11 # An error of 1 rps results in 0.11 V output slot0_configs . k_i = 0 # no output for integrated error slot0_configs . k_d = 0 # no output for error derivative self . talonfx . configurator . apply ( slot0_configs ) Once the gains are configured, the Velocity closed loop control request can be sent to the TalonFX. The control request object has an optional feedforward term that can be used to add an arbitrary value to the output, which can be useful to account for the effects of gravity. Java // create a velocity closed-loop request, voltage output, slot 0 configs final VelocityVoltage m_request = new VelocityVoltage ( 0 ). withSlot ( 0 ); // set velocity to 8 rps, add 0.5 V to overcome gravity m_talonFX . setControl ( m_request . withVelocity ( 8 ). withFeedForward ( 0.5 )); C++ // create a velocity closed-loop request, voltage output, slot 0 configs controls :: VelocityVoltage m_request = controls :: VelocityVoltage { 0 _tps }. WithSlot ( 0 ); // set velocity to 8 rps, add 0.5 V to overcome gravity m_talonFX . SetControl ( m_request . WithVelocity ( 8 _tps ). WithFeedForward ( 0.5 _V )); Python # create a velocity closed-loop request, voltage output, slot 0 configs self . request = controls . VelocityVoltage ( 0 ) . with_slot ( 0 ) # set velocity to 8 rps, add 0.5 V to overcome gravity self . talonfx . set_control ( self . request . with_velocity ( 8 ) . with_feed_forward ( 0.5 )) Motion Profiling The Position and Velocity closed-loop requests can be used to run a motion profile generated by the robot controller. Tip The Talon FX supports several onboard motion profiles using Motion Magic® . Position In a Position motion profile, the gains should be configured as follows: \\(K_g\\) - output to overcome gravity (output) \\(K_s\\) - output to overcome static friction (output) \\(K_v\\) - output per unit of requested velocity (output/rps) \\(K_a\\) - unused, as there is no target acceleration \\(K_p\\) - output per unit of error in position (output/rotation) \\(K_i\\) - output per unit of integrated error in position (output/(rotation*s)) \\(K_d\\) - output per unit of error derivative in position (output/rps) Java // in init function, set slot 0 gains var slot0Configs = new Slot0Configs (); slot0Configs . kS = 0.25 ; // Add 0.25 V output to overcome static friction slot0Configs . kV = 0.12 ; // A velocity target of 1 rps results in 0.12 V output slot0Configs . kP = 4.8 ; // A position error of 2.5 rotations results in 12 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0.1 ; // A velocity error of 1 rps results in 0.1 V output m_talonFX . getConfigurator (). apply ( slot0Configs ); C++ // in init function, set slot 0 gains configs :: Slot0Configs slot0Configs {}; slot0Configs . kS = 0.25 ; // Add 0.25 V output to overcome static friction slot0Configs . kV = 0.12 ; // A velocity target of 1 rps results in 0.12 V output slot0Configs . kP = 4.8 ; // A position error of 2.5 rotations results in 12 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0.1 ; // A velocity error of 1 rps results in 0.1 V output m_talonFX . GetConfigurator (). Apply ( slot0Configs ); Python # in init function, set slot 0 gains slot0_configs = configs . Slot0Configs () slot0_configs . k_s = 0.25 # Add 0.25 V output to overcome static friction slot0_configs . k_v = 0.12 # A velocity target of 1 rps results in 0.12 V output slot0_configs . k_p = 4.8 # A position error of 2.5 rotations results in 12 V output slot0_configs . k_i = 0 # no output for integrated error slot0_configs . k_d = 0.1 # A velocity error of 1 rps results in 0.1 V output self . talonfx . configurator . apply ( slot0_configs ) Once the gains are configured, the Position closed-loop control request can be sent to the TalonFX. The Velocity parameter is used to specify the current setpoint velocity of the motion profile. The control request object has an optional feedforward term that can be used to add an arbitrary value to the output, which can be useful to account for the effects of gravity or friction. Java // Trapezoid profile with max velocity 80 rps, max accel 160 rps/s final TrapezoidProfile m_profile = new TrapezoidProfile ( new TrapezoidProfile . Constraints ( 80 , 160 ) ); // Final target of 200 rot, 0 rps TrapezoidProfile . State m_goal = new TrapezoidProfile . State ( 200 , 0 ); TrapezoidProfile . State m_setpoint = new TrapezoidProfile . State (); // create a position closed-loop request, voltage output, slot 0 configs final PositionVoltage m_request = new PositionVoltage ( 0 ). withSlot ( 0 ); // calculate the next profile setpoint m_setpoint = m_profile . calculate ( 0.020 , m_setpoint , m_goal ); // send the request to the device m_request . Position = m_setpoint . position ; m_request . Velocity = m_setpoint . velocity ; m_talonFX . setControl ( m_request ); C++ // Trapezoid profile with max velocity 80 rps, max accel 160 rps/s frc :: TrapezoidProfile < units :: turn_t > m_profile {{ 80 _tps , 160 _tr_per_s_sq }}; // Final target of 200 rot, 0 rps frc :: TrapezoidProfile < units :: turn_t >:: State m_goal { 200 _tr , 0 _tps }; frc :: TrapezoidProfile < units :: turn_t >:: State m_setpoint {}; // create a position closed-loop request, voltage output, slot 0 configs controls :: PositionVoltage m_request = controls :: PositionVoltage { 0 _tr }. WithSlot ( 0 ); // calculate the next profile setpoint m_setpoint = m_profile . Calculate ( 20 _ms , m_setpoint , m_goal ); // send the request to the device m_request . Position = m_setpoint . position ; m_request . Velocity = m_setpoint . velocity ; m_talonFX . SetControl ( m_request ); Python # Trapezoid profile with max velocity 80 rps, max accel 160 rps/s self . profile = TrapezoidProfile ( TrapezoidProfile . Constraints ( 80 , 160 ) ) # Final target of 200 rot, 0 rps self . goal = TrapezoidProfile . State ( 200 , 0 ) self . setpoint = TrapezoidProfile . State () # create a position closed-loop request, voltage output, slot 0 configs self . request = controls . PositionVoltage ( 0 ) . with_slot ( 0 ) # calculate the next profile setpoint self . setpoint = self . profile . calculate ( 0.020 , self . setpoint , self . goal ) # send the request to the device self . request . position = self . setpoint . position self . request . velocity = self . setpoint . velocity self . talonfx . set_control ( self . request ) Velocity In a Velocity motion profile, the gains should be configured as follows: \\(K_g\\) - output to overcome gravity (output) \\(K_s\\) - output to overcome static friction (output) \\(K_v\\) - output per unit of requested velocity (output/rps) \\(K_a\\) - output per unit of requested acceleration (output/(rps/s)) \\(K_p\\) - output per unit of error in velocity (output/rps) \\(K_i\\) - output per unit of integrated error in velocity (output/rotation) \\(K_d\\) - output per unit of error derivative in velocity (output/(rps/s)) Java // in init function, set slot 0 gains var slot0Configs = new Slot0Configs (); slot0Configs . kS = 0.25 ; // Add 0.25 V output to overcome static friction slot0Configs . kV = 0.12 ; // A velocity target of 1 rps results in 0.12 V output slot0Configs . kA = 0.01 ; // An acceleration of 1 rps/s requires 0.01 V output slot0Configs . kP = 0.11 ; // An error of 1 rps results in 0.11 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0 ; // no output for error derivative m_talonFX . getConfigurator (). apply ( slot0Configs ); C++ // in init function, set slot 0 gains configs :: Slot0Configs slot0Configs {}; slot0Configs . kS = 0.25 ; // Add 0.25 V output to overcome static friction slot0Configs . kV = 0.12 ; // A velocity target of 1 rps results in 0.12 V output slot0Configs . kA = 0.01 ; // An acceleration of 1 rps/s requires 0.01 V output slot0Configs . kP = 0.11 ; // An error of 1 rps results in 0.11 V output slot0Configs . kI = 0 ; // no output for integrated error slot0Configs . kD = 0 ; // no output for error derivative m_talonFX . GetConfigurator (). Apply ( slot0Configs ); Python # in init function, set slot 0 gains slot0_configs = configs . Slot0Configs () slot0_configs . k_s = 0.25 # Add 0.25 V output to overcome static friction slot0_configs . k_v = 0.12 # A velocity target of 1 rps results in 0.12 V output slot0_configs . k_a = 0.01 # An acceleration of 1 rps/s requires 0.01 V output slot0_configs . k_p = 0.11 # An error of 1 rps results in 0.11 V output slot0_configs . k_i = 0 # no output for integrated error slot0_configs . k_d = 0 # no output for error derivative self . talonfx . configurator . apply ( slot0_configs ) Once the gains are configured, the Velocity closed-loop control request can be sent to the TalonFX. The Acceleration parameter is used to specify the current setpoint acceleration of the motion profile. The control request object has an optional feedforward term that can be used to add an arbitrary value to the output, which can be useful to account for the effects of gravity or friction. Java // Trapezoid profile with max acceleration 400 rot/s^2, max jerk 4000 rot/s^3 final TrapezoidProfile m_profile = new TrapezoidProfile ( new TrapezoidProfile . Constraints ( 400 , 4000 ) ); // Final target of 80 rps, 0 rps/s TrapezoidProfile . State m_goal = new TrapezoidProfile . State ( 80 , 0 ); TrapezoidProfile . State m_setpoint = new TrapezoidProfile . State (); // create a velocity closed-loop request, voltage output, slot 0 configs final VelocityVoltage m_request = new VelocityVoltage ( 0 ). withSlot ( 0 ); // calculate the next profile setpoint m_setpoint = m_profile . calculate ( 0.020 , m_setpoint , m_goal ); // send the request to the device // note: \"position\" is velocity, and \"velocity\" is acceleration m_request . Velocity = m_setpoint . position ; m_request . Acceleration = m_setpoint . velocity ; m_talonFX . setControl ( m_request ); C++ // Trapezoid profile with max acceleration 400 rot/s^2, max jerk 4000 rot/s^3 frc :: TrapezoidProfile < units :: turns_per_second_t > m_profile {{ 400 _tr_per_s_sq , 4000 _tr_per_s_cu }}; // Final target of 80 rps, 0 rot/s^2 frc :: TrapezoidProfile < units :: turns_per_second_t >:: State m_goal { 80 _tps , 0 _tr_per_s_sq }; frc :: TrapezoidProfile < units :: turns_per_second_t >:: State m_setpoint {}; // create a velocity closed-loop request, voltage output, slot 0 configs controls :: VelocityVoltage m_request = controls :: VelocityVoltage { 0 _tps }. WithSlot ( 0 ); // calculate the next profile setpoint m_setpoint = m_profile . Calculate ( 20 _ms , m_setpoint , m_goal ); // send the request to the device // note: \"position\" is velocity, and \"velocity\" is acceleration m_positionControl . Velocity = m_setpoint . position ; m_positionControl . Acceleration = m_setpoint . velocity ; m_talonFX . SetControl ( m_request ); Python # Trapezoid profile with max acceleration 400 rot/s^2, max jerk 4000 rot/s^3 self . profile = TrapezoidProfile ( TrapezoidProfile . Constraints ( 400 , 4000 ) ) # Final target of 80 rps, 0 rot/s^2 self . goal = TrapezoidProfile . State ( 80 , 0 ) self . setpoint = TrapezoidProfile . State () # create a velocity closed-loop request, voltage output, slot 0 configs self . request = controls . VelocityVoltage ( 0 ) . with_slot ( 0 ) # calculate the next profile setpoint self . setpoint = self . profile . calculate ( 0.020 , self . setpoint , self . goal ) # send the request to the device # note: \"position\" is velocity, and \"velocity\" is acceleration self . request . velocity = self . setpoint . position self . request . acceleration = self . setpoint . velocity self . talonfx . set_control ( self . request )",
- "content_preview": "Basic PID and Profiling The Talon FX supports basic PID control and motion profiling for position and velocity. Note For more information on feedback and feedforward gains, see Closed-Loop Overview ."
+ "content": "Device Details The Device Details page can be accessed by clicking on the device card (or clicking on View more details… when in grid view). This view allows you to access detailed device actions such as: Device Details (Name, ID, Firmware Version, Model, Serial No, etc.) Blinking LEDs Field Upgrading Licensing Details (by clicking on the LIC/PRO icon) Configs Control Self Tests Plotting Pigeon 2 Mount Calibration Blinking All CTR Electronics devices can be blinked (rapidly flash the LEDs). This can be useful for handling whenever you have duplicate devices using the same ID on the CAN bus. Verifying Device Details This screen highlights information such as (1) Device Name, (2) Device Model, (3) Firmware Version. Tip Clicking in the blank space outside the detail frames will bring the user back to the devices page. Configuring Name & IDs All devices can have their Name and ID configured via their respective textbox. IDs are limited to the range of 0 to 62 (inclusive). After inputting the ID or name, press the Set button to save the changes to the device. Field-Upgrade Firmware Version Tuner X has improved firmware upgrading functionality by automatically downloading and caching firmware. Upon initial Tuner X launch, the latest firmware for all devices will automatically be downloaded in the background (takes <10s on most internet connections). The individual device page allows you to select specific firmware versions for your device via the firmware dropdown. Batch firmware can also be completed via the batch field upgrade pop-up . Important Users should ensure they select Phoenix 6 firmware when using Phoenix 6 API, and Phoenix 5 firmware when using Phoenix 5 API. A single robot project may use both APIs simultaneously. Users can switch between firmware release years by selecting from the dropdown above the firmware selection. Note The toggle between firmware years only affects the firmware versions downloaded by Tuner X. Files selected using the “Browse” button are not affected.",
+ "content_preview": "Device Details The Device Details page can be accessed by clicking on the device card (or clicking on View more details… when in grid view). This view allows you to access detailed device actions such as: Device Details (Name, ID, Firmware Version, Model, Serial No, etc.) Blinking LEDs Field..."
},
{
- "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/api-usage/signal-logging.html",
- "title": "Signal Logging",
- "section": "API Reference",
+ "url": "https://v6.docs.ctr-electronics.com/en/stable/docs/installation/requirements.html",
+ "title": "Requirements",
+ "section": "General",
"language": "All",
- "content": "Signal Logging Note Information on how to retrieve and convert hoot files to compatible formats can be found in Extracting Signal Logs . Phoenix 6 comes with a real-time, high-fidelity signal logger. This can be useful for any form of post analysis, including diagnosing issues after a match or using WPILib SysId . The Phoenix 6 signal logger provides the following advantages over alternatives: All status signals are captured automatically with their timestamps from CAN . Status signals are captured as they arrive at their configured update frequency. Logging is not affected by the timing of the main robot loop or Java GC, significantly improving the sensitivity and accuracy of system identification. Custom user signals can be logged alongside the automatically captured status signals on the same timebase . The highly efficient hoot file format minimizes the size of the log files and the CPU usage of the logger. The signal logging API is available through static functions in the SignalLogger ( Java , C++ , Python ) class. Signal logging is enabled by default on a roboRIO 1 with a USB flash drive or a roboRIO 2, where logging is started by any of the following (whichever occurs first): The robot is enabled. It has been at least 5 seconds since program startup (allowing for calls to setPath ), and the Driver Station is connected to the robot. Users can disable this behavior with SignalLogger.enableAutoLogging(false) ( Java , C++ , Python ). Tip Device status signals can also be viewed live in the Tuner X Plotting page . Setting Log Path The logging directory can optionally be changed using SignalLogger.setPath() ( Java , C++ , Python ). If the specified directory does not exist, SignalLogger.setPath() will return an error code. Setting the path while logging will restart the log. The below example sets the logging path to a ctre-logs folder on the first USB drive found. Java SignalLogger . setPath ( \"/media/sda1/ctre-logs/\" ); C++ SignalLogger :: SetPath ( \"/media/sda1/ctre-logs/\" ); Python SignalLogger . set_path ( \"/media/sda1/ctre-logs/\" ) Note Each CAN bus gets its own dedicated log file. All logs will be placed in a subfolder named after the date and time of the start of the program. Start/Stop Logging The signal logger can be started and stopped using the start() and stop() functions ( Java , C++ , Python ). Java SignalLogger . start (); SignalLogger . stop (); C++ SignalLogger :: Start (); SignalLogger :: Stop (); Python SignalLogger . start () SignalLogger . stop () Writing Custom Signals Users can write custom signals to the currently opened logs by utilizing the write*() functions. An example application of this is logging your swerve odometry data. The integer and floating-point write*() functions can optionally be supplied a units string to log alongside the data. Additionally, all write*() functions support an optional latency parameter that is subtracted from the current time to get the latency-adjusted timestamp of the signal. This can be useful for logging high-latency data, such as vision measurements. Tip In a WPILib robot project, custom data types can be logged using Struct and Protobuf. Additionally, Java robot projects can take advantage of Epilogue integration . Java // Log the odometry pose SignalLogger . writeStruct ( \"odometry\" , Pose2d . struct , pose ); // Log the odometry period with units of \"seconds\" SignalLogger . writeDouble ( \"odom period\" , state . OdometryPeriod , \"seconds\" ); // Log the camera pose with calculated latency SignalLogger . writeStruct ( \"camera pose\" , Pose2d . struct , camPose , Timer . getTimestamp () - camRes . getTimestampSeconds () ); C++ // Log the odometry pose SignalLogger :: WriteStruct < frc :: Pose2d > ( \"odometry\" , pose ); // Log the odometry period with units of \"seconds\" SignalLogger :: WriteDouble ( \"odom period\" , state . OdometryPeriod , \"seconds\" ); // Log the camera pose with calculated latency SignalLogger :: WriteStruct < frc :: Pose2d > ( \"camera pose\" , camPose , frc :: Timer :: GetTimestamp () - camRes . GetTimestamp () ); Python # Log the odometry pose SignalLogger . write_struct ( \"odometry\" , Pose2d , pose ) # Log the odometry period with units of \"seconds\" SignalLogger . write_double ( \"odom period\" , state . odometry_period , \"seconds\" ) # Log the camera pose with calculated latency SignalLogger . write_struct ( \"camera pose\" , Pose2d , cam_pose , Timer . getTimestamp () - cam_res . getTimestamp () ) Free Signals Any log that contains a pro-licensed device will export all signals. Otherwise, the following status signals and all custom signals can be exported for free. Click here to view free signals Common Signals VersionMajor VersionMinor VersionBugfix VersionBuild IsProLicensed SupplyVoltage Fault_UnlicensedFeatureInUse Fault_BootDuringEnable Fault_Hardware Fault_Undervoltage Talon FX SupplyCurrent StatorCurrent MotorVoltage Position Velocity DeviceEnable RobotEnable ConnectedMotor Fault_DeviceTemp Fault_ProcTemp Fault_RemoteSensorDataInvalid Fault_StaticBrakeDisabled Fault_BridgeBrownout Talon FXS SupplyCurrent StatorCurrent MotorVoltage Position Velocity DeviceEnable RobotEnable ConnectedMotor Fault_DeviceTemp Fault_ProcTemp Fault_RemoteSensorDataInvalid Fault_StaticBrakeDisabled Fault_BridgeBrownout Fault_HallSensorMissing Fault_DriveDisabledHallSensor Fault_MotorTempSensorMissing Fault_MotorTempSensorTooHot Fault_MotorArrangementNotSelected CANcoder Position Velocity Pigeon 2.0 Yaw AngularVelocityZWorld CANrange DistanceMeters ProximityDetected SignalStrength CANdi™ Pin1State Pin2State S1Closed S2Closed QuadPosition QuadVelocity Pwm1_Position Pwm1_Velocity Pwm2_Position Pwm2_Velocity Overcurrent Fault_5V CANdle® OutputCurrent DeviceTemp MaxSimultaneousAnimationCount Fault_Overvoltage Fault_5VTooHigh Fault_5VTooLow Fault_Thermal Fault_SoftwareFuse Fault_ShortCircuit Low Storage Space Behavior If the target drive (i.e. flash drive or roboRIO internal storage) reaches 50 MB free space, old logs will be deleted, and a warning will be printed. If the target drive reaches 5 MB of free space, logging will be stopped, and an error will be printed. Logging cannot be resumed until more disk space is made available. An example error that may occur if the free space limit is reached is shown below. [phoenix] Signal Logger: Available disk space (3 MB) below 5 MB, stopping log Converting Signal Logs Signal logs can be converted to other common file formats such as WPILOG or MCAP using the Tuner X Log Extractor . Additionally, the owlet CLI tool can be used from a terminal, including on platforms not supported by Tuner X. owlet can be downloaded from the CLI Tools download page . To view a list of available commands, run owlet either with no parameters or with --help . As an example, to convert a hoot file to WPILOG, run: ./owlet -f wpilog \"input.hoot\" \"output.wpilog\"",
- "content_preview": "Signal Logging Note Information on how to retrieve and convert hoot files to compatible formats can be found in Extracting Signal Logs . Phoenix 6 comes with a real-time, high-fidelity signal logger."
+ "content": "Requirements This document explains the requirements to use Phoenix 6. Supported Devices Phoenix 6 supports the following devices: CANcoder CANdi™ CANdle® CANrange Pigeon 2.0 Talon FX ( Falcon 500 , Kraken X60 , Kraken X44 ) Talon FXS CAN Bus Requirements Phoenix 6 devices are supported on the below CAN bus adapters. FRC roboRIO CANivore non-FRC On Linux systems, any SocketCAN capable adapter will work, but the CANivore is highly recommended. CANivore offers additional functionality over other SocketCAN adapters. On Windows systems, you will need a CANivore to communicate with hardware. System Requirements Phoenix 6 supports a plethora of languages and operating systems. The table found below details which languages are supported on what platforms. FRC Targets Supported Languages Supports CANivore Supports High-Fidelity Simulation NI roboRIO Java, C++, Python Yes n/a Windows 10/11 x86-64 Java, C++, Python Yes Yes Linux x86-64 (desktop) [ 1 ] and ARM64 [ 2 ] Java, C++, Python Yes Yes macOS Java, C++, Python No Yes non-FRC Targets Supported Languages Supports CANivore Supports High-Fidelity Simulation Windows 10/11 x86-64 C#, Python Yes Yes Linux x86-64 (desktop) [ 1 ] and ARM64 [ 2 ] C++, C#, Python Yes Yes (C# and Python only) macOS (Simulation Only) C#, Python No Yes [ 1 ] ( 1 , 2 ) Supported Linux x86-64 (desktop) targets: Ubuntu 22.04 or newer Debian Bookworm or newer [ 2 ] ( 1 , 2 ) Supported Linux ARM64 targets: Raspberry Pi NVIDIA Jetson (Jetpack 6 or newer) Ubuntu 22.04 or newer Debian Bookworm or newer",
+ "content_preview": "Requirements This document explains the requirements to use Phoenix 6. Supported Devices Phoenix 6 supports the following devices: CANcoder CANdi™ CANdle® CANrange Pigeon 2.0 Talon FX ( Falcon 500 , Kraken X60 , Kraken X44 ) Talon FXS CAN Bus Requirements Phoenix 6 devices are supported on the..."
},
{
"url": "https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/index.html",
diff --git a/src/wpilib_mcp/plugins/photonvision/data/index.json b/src/wpilib_mcp/plugins/photonvision/data/index.json
index deb0e9b..3ae5219 100644
--- a/src/wpilib_mcp/plugins/photonvision/data/index.json
+++ b/src/wpilib_mcp/plugins/photonvision/data/index.json
@@ -1,7 +1,7 @@
{
"vendor": "photonvision",
"version": "latest",
- "built_at": "2026-03-29T04:14:09.153706",
+ "built_at": "2026-04-22T17:54:55.697913",
"pages": [
{
"url": "https://docs.photonvision.org/en/latest/",
@@ -12,164 +12,28 @@
"content_preview": "```{image} assets/PhotonVision-Header-onWhite.png\n:alt: PhotonVision\n```\n\nWelcome to the official documentation of PhotonVision! PhotonVision is the free, fast, and easy-to-use vision processing solution for the _FIRST_ Robotics Competition."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/programming/photonlib/controlling-led.html",
- "title": "Controlling LEDs",
+ "url": "https://docs.photonvision.org/en/latest/docs/programming/photonlib/index.html",
+ "title": "PhotonLib: Robot Code Interface",
"section": "PhotonLib",
"language": "All",
- "content": "# Controlling LEDs\n\nYou can control the vision LEDs of supported hardware via PhotonLib using the `setLED()` method on a `PhotonCamera` instance. In Java and C++, an `VisionLEDMode` enum class is provided to choose values from. These values include, `kOff`, `kOn`, `kBlink`, and `kDefault`. `kDefault` uses the default LED value from the selected pipeline.\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n // Blink the LEDs.\n camera.setLED(VisionLEDMode.kBlink);\n\n .. code-block:: c++\n\n // Blink the LEDs.\n camera.SetLED(photonlib::VisionLEDMode::kBlink);\n\n .. code-block:: python\n\n # Coming Soon!\n```\n",
- "content_preview": "# Controlling LEDs\n\nYou can control the vision LEDs of supported hardware via PhotonLib using the `setLED()` method on a `PhotonCamera` instance. In Java and C++, an `VisionLEDMode` enum class is provided to choose values from. These values include, `kOff`, `kOn`, `kBlink`, and `kDefault`."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/troubleshooting/index.html",
- "title": "Troubleshooting",
- "section": "Troubleshooting",
- "language": "All",
- "content": "# Troubleshooting\n\n```{toctree}\n:maxdepth: 1\n\ncommon-errors\nlogging\ncamera-troubleshooting\nnetworking-troubleshooting\nunix-commands\n```\n",
- "content_preview": "# Troubleshooting\n\n```{toctree}\n:maxdepth: 1\n\ncommon-errors\nlogging\ncamera-troubleshooting\nnetworking-troubleshooting\nunix-commands\n```\n"
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/quick-start/index.html",
- "title": "Quick Start",
- "section": "Getting Started",
- "language": "All",
- "content": "# Quick Start\n\n```{toctree}\n:maxdepth: 2\n\ncommon-setups\nquick-install\nwiring\nnetworking\ncamera-matching\ncamera-calibration\ncamera-focusing\nquick-configure\n```\n",
- "content_preview": "# Quick Start\n\n```{toctree}\n:maxdepth: 2\n\ncommon-setups\nquick-install\nwiring\nnetworking\ncamera-matching\ncamera-calibration\ncamera-focusing\nquick-configure\n```\n"
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/integration/background.html",
- "title": "Vision - Robot Integration Background",
- "section": "Robot Integration",
- "language": "All",
- "content": "# Vision - Robot Integration Background\n\n## Vision Processing's Purpose\n\nEach year, the FRC game requires a fundamental operation: **Align the Robot to a Goal**.\n\nRegardless of whether that alignment point is for picking up gamepieces, or for scoring, fast and effective robots must be able to align to them quickly and repeatably.\n\nSoftware strategies can be used to help augment the ability of a human operator, or step in when a human operator is not allowed to control the robot.\n\n*Vision Processing* is one key *input* to these software strategies. However, the inputs your coprocessor provides must be interpreted and converted (ultimately) to motor voltage commands.\n\nThere are many valid strategies for doing this transformation. Picking a strategy is a balancing act between:\n\n> 1. Available team resources (time, programming skills, previous experience)\n> 2. Precision of alignment required\n> 3. Team willingness to take on risk\n\nSimple strategies are low-risk - they require comparatively little effort to implement and tune, but have hard limits on the complexity of motion they can control on the robot. Advanced methods allow for more complex and precise movement, but take more effort to implement and tune. For this reason, it is more risky to attempt to use them.\n",
- "content_preview": "# Vision - Robot Integration Background\n\n## Vision Processing's Purpose\n\nEach year, the FRC game requires a fundamental operation: **Align the Robot to a Goal**.\n\nRegardless of whether that alignment point is for picking up gamepieces, or for scoring, fast and effective robots must be able to align..."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/contributing/linting.html",
- "title": "Linting the PhotonVision Codebase",
- "section": "Contributing",
- "language": "All",
- "content": "# Linting the PhotonVision Codebase\n\n## Versions\n\n:::{note}\nIf you work on other projects that use different versions of the same linters as PhotonVision, you may find it beneficial to use a [venv](https://docs.python.org/3/library/venv.html) instead of installing the linters globally. This will allow you to have different versions of the same linter installed for different projects.\n:::\n\nThe correct versions for each linter can be found under the linting workflow located [here](https://github.com/PhotonVision/photonvision/tree/main/.github/workflows). For *doc8*, the version can be found in `docs/requirements.txt`. If you've linted, and are still unable to pass CI, please check the versions of your linters.\n\n## Frontend\n\n### Linting the frontend\n\nIn order to lint the frontend, run `pnpm -C photon-client lint && pnpm -C photon-client format`. This should be done from the base level of the repo.\n\n## Backend\n\n### wpiformat installation\n\nTo lint the backend, PhotonVision uses *wpiformat* and *spotless*. Spotless is included with gradle, which means installation is not needed. To install wpiformat, run `pipx install wpiformat`. To install a specific version, run `pipx install wpiformat==`.\n\n### Linting the backend\n\nTo lint, run `./gradlew spotlessApply` and `wpiformat`.\n\n## Documentation\n\n### doc8 installation\n\nTo install *doc8*, the python tool we use to lint our documentation, run `pipx install doc8`. To install a specific version, run `pipx install doc8==`.\n\n### Linting the documentation\n\nTo lint the documentation, run `doc8 docs` from the root level of the docs.\n\n## Alias\n\nThe following [alias](https://www.computerworld.com/article/1373210/how-to-use-aliases-in-linux-shell-commands.html) can be added to your shell config, which will allow you to lint the entirety of the PhotonVision project by running `pvLint`. The alias will work on Linux, macOS, Git Bash on Windows, and WSL.\n\n```sh\nalias pvLint='wpiformat -v && ./gradlew spotlessApply && pnpm -C photon-client lint && pnpm -C photon-client format && doc8 docs'\n```\n",
- "content_preview": "# Linting the PhotonVision Codebase\n\n## Versions\n\n:::{note}\nIf you work on other projects that use different versions of the same linters as PhotonVision, you may find it beneficial to use a [venv](https://docs.python.org/3/library/venv.html) instead of installing the linters globally."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/simulation/hardware-in-the-loop-sim.html",
- "title": "Hardware In The Loop Simulation",
- "section": "Simulation",
- "language": "All",
- "content": "# Hardware In The Loop Simulation\n\nHardware in the loop simulation is using a physical device, such as a supported co-processor running PhotonVision, to enhance simulation capabilities. This is useful for developing and validating code before the camera is attached to a robot, as well as reducing the work required to use WPILib simulation with PhotonVision.\n\nBefore continuing, ensure PhotonVision is installed on your device. Instructions can be found {ref}`here ` for all devices.\n\nYour coprocessor and computer running simulation will have to be connected to the same network, like a home router. Connecting the coprocessor directly to the computer will not work.\n\nTo simulate with hardware in the loop, a one-line change is required. From the PhotonVision UI, go to the sidebar and select the Settings option. Within the Networking settings, find \"Team Number/NetworkTables Server Address\".\n\nDuring normal robot operation, a team's number would be entered into this field so that the PhotonVision coprocessor connects to the roboRIO as a NT client. Instead, enter the IP address of your computer running the simulation here.\n\n:::{note}\nTo find the IP address of your Windows computer, open command prompt and run `ipconfig`.\n\n```console\nC:/Users/you>ipconfig\n\nWindows IP Configuration\n\nEthernet adapter Ethernet:\n\n Connection-specific DNS Suffix . : home\n Link-local IPv6 Address . . . . . : fe80::b41d:e861:ef01:9dba%10\n IPv4 Address. . . . . . . . . . . : 192.168.254.13\n Subnet Mask . . . . . . . . . . . : 255.255.255.0\n Default Gateway . . . . . . . . . : 192.168.254.254\n```\n\n:::\n\n```{image} images/coproc-client-to-desktop-sim.png\n\n```\n\nNo code changes are required, PhotonLib should function similarly to normal operation.\n\nNow launch simulation, and you should be able to see the PhotonVision table on your simulation's NetworkTables dashboard.\n\n```{image} images/hardware-in-the-loop-sim.png\n\n```\n",
- "content_preview": "# Hardware In The Loop Simulation\n\nHardware in the loop simulation is using a physical device, such as a supported co-processor running PhotonVision, to enhance simulation capabilities."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/simulation/index.html",
- "title": "Simulation",
- "section": "Simulation",
- "language": "All",
- "content": "# Simulation\n\n```{toctree}\n:maxdepth: 0\n:titlesonly: true\n\nsimulation-java\nsimulation-cpp\nsimulation-python\nhardware-in-the-loop-sim\n```\n",
- "content_preview": "# Simulation\n\n```{toctree}\n:maxdepth: 0\n:titlesonly: true\n\nsimulation-java\nsimulation-cpp\nsimulation-python\nhardware-in-the-loop-sim\n```\n"
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/reflectiveAndShape/3D.html",
- "title": "3D Tuning",
- "section": "Reflective & Shape Detection",
- "language": "All",
- "content": "# 3D Tuning\n\nIn 3D mode, the SolvePNP algorithm is used to compute the position and rotation of the AprilTag or other target relative to the robot. This requires your {ref}`camera to be calibrated ` which can be done through the cameras tab.\n\nThe target model dropdown is used to select the target model used to compute target position. This should match the target your camera will be tracking.\n\nIf solvePNP is working correctly, the target should be displayed as a small rectangle within the \"Target Location\" minimap. The X/Y/Angle reading will also be displayed in the \"Target Info\" card.\n\n```{raw} html\n\n \n Your browser does not support the video tag.\n \n\n \n Your browser does not support the video tag.\n \n```\n\n## Contour Simplification (Non-AprilTag)\n\n3D mode internally computes a polygon that approximates the target contour being tracked. This polygon is used to detect the extreme corners of the target. The contour simplification slider changes how far from the original contour the approximation is allowed to deviate. Note that the approximate polygon is drawn on the output image for tuning.\n",
- "content_preview": "# 3D Tuning\n\nIn 3D mode, the SolvePNP algorithm is used to compute the position and rotation of the AprilTag or other target relative to the robot. This requires your {ref}`camera to be calibrated ` which can be done through the cameras..."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/apriltag-pipelines/about-apriltags.html",
- "title": "About AprilTags",
- "section": "Getting Started",
- "language": "All",
- "content": "# About AprilTags\n\n```{image} images/pv-apriltag.png\n:align: center\n:scale: 20 %\n```\n\nAprilTags are a common type of visual fiducial marker. Visual fiducial markers are artificial landmarks added to a scene to allow \"localization\" (finding your current position) via images. In simpler terms, tags mark known points of reference that you can use to find your current location. They are similar to QR codes in which they encode information, however, they hold only a single number. By placing AprilTags in known locations around the field and detecting them using PhotonVision, you can easily get full field localization / pose estimation. Alternatively, you can use AprilTags the same way you used retroreflective tape, simply using them to turn to goal without any pose estimation.\n\nA more technical explanation can be found in the [WPILib documentation](https://docs.wpilib.org/en/latest/docs/software/vision-processing/apriltag/apriltag-intro.html).\n\n:::{note}\nYou can get FIRST's [official PDF of the targets used in 2026 here](https://firstfrc.blob.core.windows.net/frc2026/FieldAssets/2026-apriltag-images-user-guide.pdf).\n:::\n",
- "content_preview": "# About AprilTags\n\n```{image} images/pv-apriltag.png\n:align: center\n:scale: 20 %\n```\n\nAprilTags are a common type of visual fiducial marker. Visual fiducial markers are artificial landmarks added to a scene to allow \"localization\" (finding your current position) via images."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/troubleshooting/unix-commands.html",
- "title": "Useful Unix Commands",
- "section": "Troubleshooting",
- "language": "All",
- "content": "# Useful Unix Commands\n\n## Networking\n\n### SSH\n\n[SSH (Secure Shell)](https://www.mankier.com/1/ssh) is used to securely connect from a local to a remote system (ex. from a laptop to a coprocessor). Unlike other commands on this page, ssh is not Unix specific and can be done on Windows and MacOS from their respective terminals.\n\n:::{note}\nYou may see a warning similar to `The authenticity of host 'xxx' can't be established...` or `WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!`, in most cases this can be safely ignored if you have confirmed that you are connecting to the correct host over a secure connection, and the fingerprint will change when your operating system is reinstalled or PhotonVision's coprocessor image is re-flashed. This can also occur if you have multiple coprocessors with the same hostname on your network. You can read more about it [here](https://superuser.com/questions/421997/what-is-a-ssh-key-fingerprint-and-how-is-it-generated)\n:::\n\nExample:\n\n```\nssh pi@hostname\n```\n\nFor PhotonVision, the username will be `pi` and the password will be `raspberry`.\n\n### ip\n\nRun [ip address](https://www.mankier.com/8/ip) with your coprocessor connected to a monitor in order to see its IP address and other network configuration information.\n\nYour output might look something like this:\n\n```\n2: end1: mtu 1500 qdisc mq state UP group default qlen 1000\n link/ether de:9a:8f:7d:31:aa brd ff:ff:ff:ff:ff:ff\n inet 10.88.47.12/24 brd 10.88.47.255 scope global dynamic noprefixroute end1\n valid_lft 27367sec preferred_lft 27367sec\n```\n\nIn this example, the numbers following `inet` (10.88.47.12) are your IP address.\n\n### ping\n\n[ping](https://www.mankier.com/8/ping) is a command-line utility used to test the reachability of a host on an IP network. It also measures the round-trip time for messages sent from the originating host to a destination computer. It can be used to determine if a network interface is available, which can be helpful when debugging.\n\n## File Transfer\n\nAll files under `/opt/photonvision` are owned by the root user. This means that if you want to modify them, the commands to do so must be ran as sudo.\n\n### SCP\n\n[SCP (Secure Copy)](https://www.mankier.com/1/scp) is used to securely transfer files between local and remote systems.\n\nExample:\n\n```\nscp [file] pi@hostname:/path/to/destination\n```\n\n### SFTP\n\n[SFTP (SSH File Transfer Protocol)](https://www.mankier.com/1/sftp#) is another option for transferring files between local and remote systems.\n\n### Filezilla\n\n[Filezilla](https://filezilla-project.org/) is a GUI alternative to SCP and SFTP. It is available for Windows, MacOS, and Linux.\n\n## Miscellaneous\n\n### v4l2-ctl\n\n[v4l2-ctl](https://www.mankier.com/1/v4l2-ctl) is a command-line tool for controlling video devices.\n\nList available video devices (used to verify the device recognized a connected camera):\n\n```\nv4l2-ctl --list-devices\n```\n\nList supported formats and resolutions for a specific video device:\n\n```\nv4l2-ctl --list-formats-ext --device /path/to/video_device\n```\n\nList all video device's controls and their values:\n\n```\nv4l2-ctl --list-ctrls --device path/to/video_device\n```\n\n:::{note}\nThis command is especially useful in helping to debug when certain camera controls, like exposure, aren't behaving as expected. If you see an error in the logs similar to `WARNING 30: failed to set property [property name] (UsbCameraImpl.cpp:646)`, that means that PhotonVision is trying to use a control that doesn't exist or has a different name on your hardware. If you encounter this issue, please [file an issue](https://github.com/PhotonVision/photonvision/issues) with the necessary logs and output of the `v4l2-ctl --list-ctrls` command.\n:::\n\n### systemctl\n\n[systemctl](https://www.mankier.com/1/systemctl) is a command that controls the `systemd` system and service manager.\n\nStart PhotonVision:\n\n```\nsystemctl start photonvision\n```\n\nStop PhotonVision:\n\n```\nsystemctl stop photonvision\n```\n\nRestart PhotonVision:\n\n```\nsystemctl restart photonvision\n```\n\nCheck the status of PhotonVision:\n\n```\nsystemctl status photonvision\n```\n\n### journalctl\n\n[journalctl](https://www.mankier.com/1/journalctl) is a command that queries the systemd journal, which is a logging system used by many Linux distributions.\n\nView the PhotonVision logs:\n\n```\njournalctl --output cat -u photonvision\n```\n\nView the PhotonVision logs in real-time:\n\n```\njournalctl --output cat -u photonvision -f\n```\n\n`--output cat` is used to prevent journalctl from printing its own timestamps, because we log our own timestamps.\n\n### lsusb\n\n[lsusb](https://linux.die.net/man/8/lsusb) is a command that can be used to find all the USB buses on a device. When run with the `--tree` flag, it will give you more information on the available ports and connected devices. See the example below.\n\n```\nphoton@photonvision:~$ lsusb -t\n/: Bus 001.Port 001: Dev 001, Class=root_hub, Driver=xhci_hcd/4p, 480M\n |__ Port 001: Dev 002, If 0, Class=Video, Driver=uvcvideo, 480M\n |__ Port 001: Dev 002, If 1, Class=Video, Driver=uvcvideo, 480M\n |__ Port 001: Dev 002, If 2, Class=Audio, Driver=snd-usb-audio, 480M\n |__ Port 001: Dev 002, If 3, Class=Audio, Driver=snd-usb-audio, 480M\n |__ Port 002: Dev 003, If 0, Class=Video, Driver=uvcvideo, 480M\n |__ Port 002: Dev 003, If 1, Class=Video, Driver=uvcvideo, 480M\n/: Bus 002.Port 001: Dev 001, Class=root_hub, Driver=xhci_hcd/4p, 5000M\n |__ Port 003: Dev 002, If 0, Class=Vendor Specific Class, Driver=ax_usb_nic, 5000M\n/: Bus 003.Port 001: Dev 001, Class=root_hub, Driver=xhci-hcd/1p, 480M\n```\n\nThe most important information from this list is the bandwitdth. This is the last number we see, followed by an M (megabytes).\n\n### usbtop\n\n[usbtop](https://github.com/aguinet/usbtop) is a program that can be used to monitor traffic on your device's USB buses. To use it, run `sudo modprobe usbmon` then `sudo usbtop`. An example output can be found below.\n\n```\nBus ID 1 (Raw USB traffic, bus number 1)\tTo device\tFrom device\n Device ID 1 :\t\t\t 0.00 kb/s\t0.00 kb/s\n Device ID 2 :\t\t\t 141.71 kb/s\t23595.81 kb/s\n Device ID 3 :\t\t\t 0.13 kb/s\t0.13 kb/s\nBus ID 2 (Raw USB traffic, bus number 2)\tTo device\tFrom device\n Device ID 1 :\t\t\t 0.00 kb/s\t0.00 kb/s\n Device ID 2 :\t\t\t 450.42 kb/s\t17.45 kb/s\nBus ID 3 (Raw USB traffic, bus number 3)\tTo device\tFrom device\n Device ID 1 :\t\t\t 0.00 kb/s\t0.00 kb/s\n```\n\nThe above output can be used to debug USB bandwidth issues, by comparing the size of data being sent with the bandwidth limits (bandwidth limits can be found using lsusb).\n",
- "content_preview": "# Useful Unix Commands\n\n## Networking\n\n### SSH\n\n[SSH (Secure Shell)](https://www.mankier.com/1/ssh) is used to securely connect from a local to a remote system (ex. from a laptop to a coprocessor)."
+ "content": "# PhotonLib: Robot Code Interface\n\n```{toctree}\n:maxdepth: 1\n\nadding-vendordep\ngetting-target-data\nusing-target-data\nrobot-pose-estimator\ndriver-mode-pipeline-index\ncontrolling-led\nfps-limiter\n```\n",
+ "content_preview": "# PhotonLib: Robot Code Interface\n\n```{toctree}\n:maxdepth: 1\n\nadding-vendordep\ngetting-target-data\nusing-target-data\nrobot-pose-estimator\ndriver-mode-pipeline-index\ncontrolling-led\nfps-limiter\n```\n"
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/apriltag-pipelines/2D-tracking-tuning.html",
- "title": "2D AprilTag Tuning / Tracking",
+ "url": "https://docs.photonvision.org/en/latest/docs/apriltag-pipelines/3D-tracking.html",
+ "title": "3D Tracking",
"section": "AprilTag Detection",
"language": "All",
- "content": "# 2D AprilTag Tuning / Tracking\n\n## Tracking AprilTags\n\nBefore you get started tracking AprilTags, ensure that you have followed the previous sections on installation, wiring and networking. Next, open the Web UI, go to the top right card, and switch to the \"AprilTag\" or \"ArUco\" type. You should see a screen similar to the one below.\n\n```{image} images/apriltag.png\n:align: center\n```\n\nYou are now able to detect and track AprilTags in 2D (yaw, pitch, roll, etc.). In order to get 3D data from your AprilTags, please see {ref}`here. `\n\n## Tuning AprilTags\n\nAprilTag pipelines come with reasonable defaults to get you up and running with tracking. However, in order to optimize your performance and accuracy, you must tune your AprilTag pipeline using the settings below. Note that the settings below are different between the AprilTag and ArUco detectors but the concepts are the same.\n\n```{image} images/apriltag-tune.png\n:align: center\n:scale: 45 %\n```\n\n### Target Family\n\nTarget families are defined by two numbers (before and after the h). The first number is the number of bits the tag is able to encode (which means more tags are available in the respective family) and the second is the hamming distance. Hamming distance describes the ability for error correction while identifying tag ids. A high hamming distance generally means that it will be easier for a tag to be identified even if there are errors. However, as hamming distance increases, the number of available tags decreases.\n\nThe 2026 FRC game will be using 36h11 tags, which can be found [here](https://github.com/AprilRobotics/apriltag-imgs/tree/2bc821edb4eb7b408d13c6a590d326d8a9ec98f3/tag36h11).\n\n### Decimate\n\nDecimation (also known as down-sampling) is the process of reducing the sampling frequency of a signal (in our case, the image). Increasing decimate will lead to an increased detection rate while decreasing detection distance. We recommend keeping this at the default value.\n\n### Blur\n\nThis controls the sigma of Gaussian blur for tag detection. In clearer terms, increasing blur will make the image blurrier, decreasing it will make it closer to the original image. We strongly recommend that you keep blur to a minimum (0) due to it's high performance intensity unless you have an extremely noisy image.\n\n### Threads\n\nThreads refers to the threads within your coprocessor's CPU. The theoretical maximum is device dependent, but we recommend that users to stick to one less than the amount of CPU threads that your coprocessor has. Increasing threads will increase performance at the cost of increased CPU load, temperature increase, etc. It may take some experimentation to find the most optimal value for your system.\n\n### Refine Edges\n\nThe edges of the each polygon are adjusted to \"snap to\" high color differences surrounding it. It is recommended to use this in tandem with decimate as it can increase the quality of the initial estimate.\n\n### Pose Iterations\n\nPose iterations represents the amount of iterations done in order for the AprilTag algorithm to converge on its pose solution(s). A smaller number between 0-100 is recommended. A smaller amount of iterations cause a more noisy set of poses when looking at the tag straight on, while higher values much more consistently stick to a (potentially wrong) pair of poses. WPILib contains many useful filter classes in order to account for a noisy tag reading.\n\n### Max Error Bits\n\nMax error bits, also known as hamming distance, is the number of positions at which corresponding pieces of data / tag are different. Put more generally, this is the number of bits (think of these as squares in the tag) that need to be changed / corrected in the tag to correctly detect it. A higher value means that more tags will be detected while a lower value cuts out tags that could be \"questionable\" in terms of detection.\n\nWe recommend a value of 0 for the 16h5 and at most 3 for the 36h11 family.\n\n### Decision Margin Cutoff\n\nThe decision margin cutoff is how much “margin” the detector has left before it rejects a tag; increasing this rejects poorer tags. We recommend you keep this value around a 30.\n",
- "content_preview": "# 2D AprilTag Tuning / Tracking\n\n## Tracking AprilTags\n\nBefore you get started tracking AprilTags, ensure that you have followed the previous sections on installation, wiring and networking. Next, open the Web UI, go to the top right card, and switch to the \"AprilTag\" or \"ArUco\" type."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/simulation/simulation-java.html",
- "title": "Simulation Support in PhotonLib in Java",
- "section": "Simulation",
- "language": "All",
- "content": "# Simulation Support in PhotonLib in Java\n\n## What Is Simulated?\n\nSimulation is a powerful tool for validating robot code without access to a physical robot. Read more about [simulation in WPILib](https://docs.wpilib.org/en/stable/docs/software/wpilib-tools/robot-simulation/introduction.html).\n\nIn Java, PhotonLib can simulate cameras on the field and generate target data approximating what would be seen in reality. This simulation attempts to include the following:\n\n- Camera Properties\n - Field of Vision\n - Lens distortion\n - Image noise\n - Framerate\n - Latency\n- Target Data\n - Detected / minimum-area-rectangle corners\n - Center yaw/pitch\n - Contour image area percentage\n - Fiducial ID\n - Fiducial ambiguity\n - Fiducial solvePNP transform estimation\n- Camera Raw/Processed Streams (grayscale)\n\n:::{note}\nSimulation does NOT include the following:\n\n- Full physical camera/world simulation (targets are automatically thresholded)\n- Image Thresholding Process (camera gain, brightness, etc)\n- Pipeline switching\n- Snapshots\n :::\n\nThis scope was chosen to balance fidelity of the simulation with the ease of setup, in a way that would best benefit most teams.\n\n```{image} diagrams/SimArchitecture.drawio.svg\n:alt: A diagram comparing the architecture of a real PhotonVision process to a simulated\n: one.\n```\n\n## Drivetrain Simulation Prerequisite\n\nA prerequisite for simulating vision frames is knowing where the camera is on the field-- to utilize PhotonVision simulation, you'll need to supply the simulated robot pose periodically. This requires drivetrain simulation for your robot project if you want to generate camera frames as your robot moves around the field.\n\nReferences for using PhotonVision simulation with drivetrain simulation can be found in the [PhotonLib Java Examples](https://github.com/PhotonVision/photonvision/blob/2a6fa1b6ac81f239c59d724da5339f608897c510/photonlib-java-examples/README.md) for both a differential drivetrain and a swerve drive.\n\n:::{important}\nThe simulated drivetrain pose must be separate from the drivetrain estimated pose if a pose estimator is utilized.\n:::\n\n## Vision System Simulation\n\nA `VisionSystemSim` represents the simulated world for one or more cameras, and contains the vision targets they can see. It is constructed with a unique label:\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // A vision system sim labelled as \"main\" in NetworkTables\n VisionSystemSim visionSim = new VisionSystemSim(\"main\");\n```\n\nPhotonLib will use this label to put a `Field2d` widget on NetworkTables at `/VisionSystemSim-[label]/Sim Field`. This label does not need to match any camera name or pipeline name in PhotonVision.\n\nVision targets require a `TargetModel`, which describes the shape of the target. For AprilTags, PhotonLib provides `TargetModel.kAprilTag16h5` for the tags used in 2023, and `TargetModel.kAprilTag36h11` for the tags used starting in 2024. For other target shapes, convenience constructors exist for spheres, cuboids, and planar rectangles. For example, a planar rectangle can be created with:\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // A 0.5 x 0.25 meter rectangular target\n TargetModel targetModel = new TargetModel(0.5, 0.25);\n```\n\nThese `TargetModel` are paired with a target pose to create a `VisionTargetSim`. A `VisionTargetSim` is added to the `VisionSystemSim` to become visible to all of its cameras.\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // The pose of where the target is on the field.\n // Its rotation determines where \"forward\" or the target x-axis points.\n // Let's say this target is flat against the far wall center, facing the blue driver stations.\n Pose3d targetPose = new Pose3d(16, 4, 2, new Rotation3d(0, 0, Math.PI));\n // The given target model at the given pose\n VisionTargetSim visionTarget = new VisionTargetSim(targetPose, targetModel);\n\n // Add this vision target to the vision system simulation to make it visible\n visionSim.addVisionTargets(visionTarget);\n```\n\n:::{note}\nThe pose of a `VisionTargetSim` object can be updated to simulate moving targets. Note, however, that this will break latency simulation for that target.\n:::\n\nTo use simulated object detection, you must provide an objDetClassId (zero-indexed class ID) and confidence value. When you set objDetConf to -1, the simulation computes confidence based on the area of the target in the camera's field of view. To simulate a object detection model with one class (fuel, index 0) and specify confidence, you'd write:\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // arbitrary position on field\n final var targetPose = new Pose3d(new Translation3d(2, 0, 0), new Rotation3d());\n // Class id, zero-indexed\n final int classId = 0;\n // Confidence, between 0 and 1.\n final float conf = 0.67f;\n // 6 inch diameter ball\n final TargetModel ballModel = new TargetModel(Units.inchesToMeters(6));\n final var ballTargetSim = new VisionTargetSim(targetPose, ballModel, classId, conf);\n\n // Add this vision target to the vision system simulation to make it visible\n visionSim.addVisionTargets(visionTarget);\n```\n\nFor convenience, an `AprilTagFieldLayout` can also be added to automatically create a target for each of its AprilTags.\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // The layout of AprilTags which we want to add to the vision system\n AprilTagFieldLayout tagLayout = AprilTagFieldLayout.loadFromResource(AprilTagFields.kDefaultField.m_resourceFile);\n\n visionSim.addAprilTags(tagLayout);\n```\n\n:::{note}\nThe poses of the AprilTags from this layout depend on its current alliance origin (e.g. blue or red). If this origin is changed later, the targets will have to be cleared from the `VisionSystemSim` and re-added.\n:::\n\n## Camera Simulation\n\nNow that we have a simulation world with vision targets, we can add simulated cameras to view it.\n\nBefore adding a simulated camera, we need to define its properties. This is done with the `SimCameraProperties` class:\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // The simulated camera properties\n SimCameraProperties cameraProp = new SimCameraProperties();\n```\n\nBy default, this will create a 960 x 720 resolution camera with a 90 degree diagonal FOV(field-of-view) and no noise, distortion, or latency. If we want to change these properties, we can do so:\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // A 640 x 480 camera with a 100 degree diagonal FOV.\n cameraProp.setCalibration(640, 480, Rotation2d.fromDegrees(100));\n // Approximate detection noise with average and standard deviation error in pixels.\n cameraProp.setCalibError(0.25, 0.08);\n // Set the camera image capture framerate (Note: this is limited by robot loop rate).\n cameraProp.setFPS(20);\n // The average and standard deviation in milliseconds of image data latency.\n cameraProp.setAvgLatencyMs(35);\n cameraProp.setLatencyStdDevMs(5);\n```\n\nThese properties are used in a `PhotonCameraSim`, which handles generating captured frames of the field from the simulated camera's perspective, and calculating the target data which is sent to the `PhotonCamera` being simulated.\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // The PhotonCamera used in the real robot code.\n PhotonCamera camera = new PhotonCamera(\"cameraName\");\n\n // The simulation of this camera. Its values used in real robot code will be updated.\n PhotonCameraSim cameraSim = new PhotonCameraSim(camera, cameraProp);\n```\n\nThe `PhotonCameraSim` can now be added to the `VisionSystemSim`. We have to define a robot-to-camera transform, which describes where the camera is relative to the robot pose (this can be measured in CAD or by hand).\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // Our camera is mounted 0.1 meters forward and 0.5 meters up from the robot pose,\n // (Robot pose is considered the center of rotation at the floor level, or Z = 0)\n Translation3d robotToCameraTrl = new Translation3d(0.1, 0, 0.5);\n // and pitched 15 degrees up.\n Rotation3d robotToCameraRot = new Rotation3d(0, Math.toRadians(-15), 0);\n Transform3d robotToCamera = new Transform3d(robotToCameraTrl, robotToCameraRot);\n\n // Add this camera to the vision system simulation with the given robot-to-camera transform.\n visionSim.addCamera(cameraSim, robotToCamera);\n```\n\n:::{important}\nYou may add multiple cameras to one `VisionSystemSim`, but not one camera to multiple `VisionSystemSim`. All targets in the `VisionSystemSim` will be visible to all its cameras.\n:::\n\nIf the camera is mounted on a mobile mechanism (like a turret) this transform can be updated in a periodic loop.\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // The turret the camera is mounted on is rotated 5 degrees\n Rotation3d turretRotation = new Rotation3d(0, 0, Math.toRadians(5));\n robotToCamera = new Transform3d(\n robotToCameraTrl.rotateBy(turretRotation),\n robotToCameraRot.rotateBy(turretRotation));\n visionSim.adjustCamera(cameraSim, robotToCamera);\n```\n\n## Low-Resource Vision Simulation with Photonvision\n\nBy default, PhotonCameraSim renders two simulated camera streams using OpenCV:\n\n- Raw stream - The unprocessed camera view\n- Processed stream - The camera view with vision processing overlays\n\nThese streams are nice if you want to actually view the simulated images, but they can be computationally expensive. This may cause lag and reduced simulation performance on lower-powered computers.\nLightweight Configuration\n\nThe following configuration disables both streams while still allowing tag detection and pose simulation to work. It's not perfect, but it's much better performance-wise than the default configuration.\n\n.. code-block:: java\n\n // lightweight config version\n // var cameraProperties = new SimCameraProperties();\n // cameraSim = new PhotonCameraSim(camera, cameraProperties, aprilTagLayout);\n // cameraSim.enableRawStream(false); // disables raw image stream\n // cameraSim.enableProcessedStream(false); // disables processed image stream\n\n**Use Case**\n\nThis configuration is ideal for Chromebooks or low-spec machines where rendering the simulated camera images causes lag, but vision data is still desired for testing.\n\n**What Still Works**\n\n- AprilTag detection\n- Pose estimation\n- NetworkTables data publishing\n- Robot positioning and targeting\n\n**What's Disabled**\n\n- Visual camera stream rendering\n- Real-time visual debugging of camera output\n\n## Updating The Simulation World\n\nTo update the `VisionSystemSim`, we simply have to pass in the simulated robot pose periodically (in `simulationPeriodic()`).\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // Update with the simulated drivetrain pose. This should be called every loop in simulation.\n visionSim.update(robotPoseMeters);\n```\n\nTargets and cameras can be added and removed, and camera properties can be changed at any time.\n\n## Visualizing Results\n\nEach `VisionSystemSim` has its own built-in `Field2d` for displaying object poses in the simulation world such as the robot, simulated cameras, and actual/measured target poses.\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // Get the built-in Field2d used by this VisionSystemSim\n visionSim.getDebugField();\n```\n\n:::{figure} images/SimExampleField.png\n_A_ `VisionSystemSim`_'s internal_ `Field2d` _customized with target images and colors_\n:::\n\nA `PhotonCameraSim` can also draw and publish generated camera frames to a MJPEG stream similar to an actual PhotonVision process.\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // Enable the raw and processed streams. These are enabled by default.\n cameraSim.enableRawStream(true);\n cameraSim.enableProcessedStream(true);\n\n // Enable drawing a wireframe visualization of the field to the camera streams.\n // This is extremely resource-intensive and is disabled by default.\n cameraSim.enableDrawWireframe(true);\n```\n\nThese streams follow the port order mentioned in {ref}`docs/quick-start/networking:Camera Stream Ports`. For example, a single simulated camera will have its raw stream at `localhost:1181` and processed stream at `localhost:1182`, which can also be found in the CameraServer tab of Shuffleboard like a normal camera stream.\n\n:::{figure} images/SimExampleFrame.png\n_A frame from the processed stream of a simulated camera viewing some 2023 AprilTags with the field wireframe enabled_\n:::\n",
- "content_preview": "# Simulation Support in PhotonLib in Java\n\n## What Is Simulated?\n\nSimulation is a powerful tool for validating robot code without access to a physical robot."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/additional-resources/config.html",
- "title": "Filesystem Directory",
- "section": "Additional Resources",
- "language": "All",
- "content": "# Filesystem Directory\n\nPhotonVision stores and loads settings in the {code}`photonvision_config` directory, in the same folder as the PhotonVision JAR is stored. On supported hardware, this is in the {code}`/opt/photonvision` directory. The contents of this directory can be exported as a zip archive from the settings page of the interface, under \"export settings\". This export will contain everything detailed below. These settings can later be uploaded using \"import settings\", to restore configurations from previous backups.\n\n## Directory Structure\n\nThe directory structure is outlined below.\n\n```{image} images/configDir.png\n:alt: Config directory structure\n:width: 600\n```\n\n- calibImgs\n - Images saved from the last run of the calibration routine\n- cameras\n - Contains a subfolder for each camera. This folder contains the following files:\n - pipelines folder, which contains a {code}`json` file for each user-created pipeline.\n - config.json, which contains all camera-specific configuration. This includes FOV, pitch, current pipeline index, and calibration data\n - drivermode.json, which contains settings for the driver mode pipeline\n- imgSaves\n - Contains images saved with the input/output save commands.\n- logs\n - Contains timestamped logs in the format {code}`photonvision-YYYY-MM-D_HH-MM-SS.log`. These timestamps will likely be significantly behind the real time. Coprocessors on the robot have no way to get current time.\n- hardwareSettings.json\n - Contains hardware settings. Currently this includes only the LED brightness.\n- networkSettings.json\n - Contains network settings, including team number (or remote network tables address), static/dynamic settings, and hostname.\n\n## Importing and Exporting Settings\n\nThe entire settings directory can be exported as a ZIP archive from the settings page.\n\n```{raw} html\n\n \n Your browser does not support the video tag.\n \n```\n\nA variety of files can be imported back into PhotonVision:\n\n- ZIP Archive ({code}`.zip`)\n - Useful for restoring a full configuration from a different PhotonVision instance.\n- Single Config File\n - Currently-supported Files\n - {code}`hardwareConfig.json`\n - {code}`hardwareSettings.json`\n - {code}`networkSettings.json`\n - Useful for simple hardware or network configuration tasks without overwriting all settings.\n\n\n\n",
- "content_preview": "# Filesystem Directory\n\nPhotonVision stores and loads settings in the {code}`photonvision_config` directory, in the same folder as the PhotonVision JAR is stored. On supported hardware, this is in the {code}`/opt/photonvision` directory."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/contributing/design-descriptions/image-rotation.html",
- "title": "Calibration and Image Rotation",
- "section": "Contributing",
- "language": "All",
- "content": "# Calibration and Image Rotation\n\n## Rotating Points\n\nTo stay consistent with the OpenCV camera coordinate frame, we put the origin in the top left, with X right, Y down, and Z out (as required by the right-hand rule). Intuitively though, if I ask you to rotate an image 90 degrees clockwise though, you'd probably rotate it about -Z in this coordinate system. Just be aware of this inconsistency.\n\n\n\nIf we have any one point in any of those coordinate systems, we can transform it into any of the other ones using standard geometry libraries by performing relative transformations (like in this pseudocode):\n\n```\nTranslation2d tag_corner1 = new Translation2d();\nTranslation2d rotated = tag_corner1.relativeTo(ORIGIN_ROTATED_90_CCW);\n```\n\n## Image Distortion\n\nThe distortion coefficients for OPENCV8 is given in order `[k1 k2 p1 p2 k3 k4 k5 k6]`. Mrcal names these coefficients `[k_0 k_1, k_2, k_3, k_4, k_5, k_6, k_7]`.\n\n```{math}\n \\begin{align*}\n \\vec P &\\equiv \\frac{\\vec p_{xy}}{p_z} \\\\\n r &\\equiv \\left|\\vec P\\right| \\\\\n \\vec P_\\mathrm{radial} &\\equiv \\frac{ 1 + k_0 r^2 + k_1 r^4 + k_4 r^6}{ 1 + k_5 r^2 + k_6 r^4 + k_7 r^6} \\vec P \\\\\n \\vec P_\\mathrm{tangential} &\\equiv\n \\left[ \\begin{aligned}\n 2 k_2 P_0 P_1 &+ k_3 \\left(r^2 + 2 P_0^2 \\right) \\\\\n 2 k_3 P_0 P_1 &+ k_2 \\left(r^2 + 2 P_1^2 \\right)\n \\end{aligned}\\right] \\\\\n \\vec q &= \\vec f_{xy} \\left( \\vec P_\\mathrm{radial} + \\vec P_\\mathrm{tangential} \\right) + \\vec c_{xy}\n \\end{align*}\n```\n\nFrom this, we observe at `k_0, k_1, k_4, k_5, k_6, k_7` depend only on the norm of {math}`\\vec P`, and will be constant given a rotated image. However, `k_2` and `k_3` go with {math}`P_0 \\cdot P_1`, `k_3` with {math}`P_0^2`, and `k_2` with {math}`P_1^2`.\n\nLet's try a concrete example. With a 90 degree CCW rotation, we have {math}`P0=-P_{1\\mathrm{rotated}}` and {math}`P1=P_{0\\mathrm{rotated}}`. Let's substitute in\n\n```{math}\n \\begin{align*}\n \\left[ \\begin{aligned}\n 2 k_2 P_0 P_1 &+ k_3 \\left(r^2 + 2 P_0^2 \\right) \\\\\n 2 k_3 P_0 P_1 &+ k_2 \\left(r^2 + 2 P_1^2 \\right)\n \\end{aligned}\\right] &=\n \\left[ \\begin{aligned}\n 2 k_{2\\mathrm{rotated}} (-P_{1\\mathrm{rotated}}) P_{0\\mathrm{rotated}} &+ k_{3\\mathrm{rotated}} \\left(r^2 + 2 (-P_{1\\mathrm{rotated}})^2 \\right) \\\\\n 2 k_{3\\mathrm{rotated}} (-P_{1\\mathrm{rotated}}) P_{0\\mathrm{rotated}} &+ k_{2\\mathrm{rotated}} \\left(r^2 + 2 P_{0\\mathrm{rotated}}^2 \\right)\n \\end{aligned}\\right] \\\\\n &=\n \\left[ \\begin{aligned}\n -2 k_{2\\mathrm{rotated}} P_{1\\mathrm{rotated}} P_{0\\mathrm{rotated}} &+ k_{3\\mathrm{rotated}} \\left(r^2 + 2 P_{1\\mathrm{rotated}}^2 \\right) \\\\\n -2 k_{3\\mathrm{rotated}} P_{1\\mathrm{rotated}} P_{0\\mathrm{rotated}} &+ k_{2\\mathrm{rotated}} \\left(r^2 + 2 P_{0\\mathrm{rotated}}^2 \\right)\n \\end{aligned}\\right]\n \\end{align*}\n```\n\nBy inspection, this results in just applying another 90 degree rotation to the k2/k3 parameters. Proof is left as an exercise for the reader. Note that we can repeat this rotation to yield equations for tangential distortion for 180 and 270 degrees.\n\n```{math}\n k_2'=-k_3\n k_3'=k_2\n```\n",
- "content_preview": "# Calibration and Image Rotation\n\n## Rotating Points\n\nTo stay consistent with the OpenCV camera coordinate frame, we put the origin in the top left, with X right, Y down, and Z out (as required by the right-hand rule)."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/reflectiveAndShape/index.html",
- "title": "Colored Shape & Reflective",
- "section": "Reflective & Shape Detection",
- "language": "All",
- "content": "# Colored Shape & Reflective\n\n```{toctree}\n:maxdepth: 0\n:titlesonly: true\n\nthresholding\ncontour-filtering\n3D\n```\n",
- "content_preview": "# Colored Shape & Reflective\n\n```{toctree}\n:maxdepth: 0\n:titlesonly: true\n\nthresholding\ncontour-filtering\n3D\n```\n"
+ "content": "# 3D Tracking\n\n3D AprilTag tracking will allow you to track the real-world position and rotation of a tag relative to the camera's image sensor. This is useful for robot pose estimation and other applications like autonomous scoring. In order to use 3D tracking, you must first {ref}`calibrate your camera `. Once you have, you need to enable 3D mode in the UI and you will now be able to get 3D pose information from the tag! For information on getting and using this information in your code, see {ref}`the programming reference `.\n\n## Ambiguity\n\nTranslating from 2D to 3D using data from the calibration and the four tag corners can lead to \"pose ambiguity\", where it appears that the AprilTag pose is flipping between two different poses. You can read more about this issue [here](https://docs.wpilib.org/en/stable/docs/software/vision-processing/apriltag/apriltag-intro.html#d-to-3d-ambiguity). Ambiguity is calculated as the ratio of reprojection errors between two pose solutions (if they exist), where reprojection error is the error corresponding to the image distance between where the apriltag's corners are detected vs where we expect to see them based on the tag's estimated camera relative pose.\n\nThere are a few steps you can take to resolve/mitigate this issue:\n\n1. Mount cameras at oblique angles so it is less likely that the tag will be seen straight on.\n2. Use the {ref}`MultiTag system ` in order to combine the corners from multiple tags to get a more accurate and unambiguous pose.\n3. Reject all tag poses where the ambiguity ratio (available via PhotonLib) is greater than 0.2.\n",
+ "content_preview": "# 3D Tracking\n\n3D AprilTag tracking will allow you to track the real-world position and rotation of a tag relative to the camera's image sensor. This is useful for robot pose estimation and other applications like autonomous scoring."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/quick-start/camera-focusing.html",
- "title": "Camera Focusing",
+ "url": "https://docs.photonvision.org/en/latest/docs/quick-start/common-setups.html",
+ "title": "Common Hardware Setups",
"section": "Getting Started",
"language": "All",
- "content": "# Camera Focusing\n\n## Prepare Camera\n:::{warning}\nRefocusing your camera **will** make your calibration inaccurate, make sure to recalibrate after focusing.\n:::\nTo ensure that your camera is focused properly, mount it to a secure surface and ensure it does not move drastically. Point your camera at a detailed surface like a calibration board, and make sure that it not too close to the camera.\n\n## Using Focus Mode\n:::{important}\nWhen you enable Focus Mode, it will assign a *Score* to the current focus, this score depends on your environment and the lighting. This score cannot be compared to a focus score collected from other environments.\n:::\n- In the Cameras tab, turn on Focus Mode.\n- Rotate the lens on your camera to try and get the focus score as high as possible.\n- Once you cannot get a higher score, this indicates that your camera is fully focused and can be set in place using glue if desired.\n\n```{image} images/focusModeExample.png\n:scale: 50%\n```\n",
- "content_preview": "# Camera Focusing\n\n## Prepare Camera\n:::{warning}\nRefocusing your camera **will** make your calibration inaccurate, make sure to recalibrate after focusing.\n:::\nTo ensure that your camera is focused properly, mount it to a secure surface and ensure it does not move drastically."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/camera-specific-configuration/arducam-cameras.html",
- "title": "Arducam Cameras",
- "section": "Camera Configuration",
- "language": "All",
- "content": "# Arducam Cameras\n\n:::{warning}\nArducam Pivariety cameras are **incompatible** with PhotonVision as they require a custom camera library not compatible with PhotonVision.\n:::\n\nArducam cameras are supported for setups with multiple devices. This is possible because Arducam provides software that allows you to assign truly different device names to each camera. This feature is particularly useful in complex setups where multiple cameras are used simultaneously.\n\n## Setting Up Arducam Cameras\n\n1. **Download Arducam Software**: [Download and install the Arducam software from their official website.](https://docs.arducam.com/UVC-Camera/Serial-Number-Tool-Guide/)\n\n2. **Assign Device Names**: Use the Arducam software and Arducam [documentation](https://docs.arducam.com/UVC-Camera/Serial-Number-Tool-Guide/) to give each camera a unique device name. This will help in distinguishing between multiple cameras in your setup.\n\n## Steps to Configure in PhotonVision\n\n1. **Open PhotonVision Settings**: Navigate to the cameras page in PhotonVision.\n\n2. **Select Camera Model**: Select the proper camera. Use the Arducam model selector to specify the model of each Arducam camera connected to your system.\n\n3. **Save Settings**: Ensure that you save the settings after selecting the appropriate camera model for each device.\n\n```{image} images/setArducamModel.png\n:alt: The camera model can be selected from the Arducam model selector in the cameras tab\n:align: center\n```\n",
- "content_preview": "# Arducam Cameras\n\n:::{warning}\nArducam Pivariety cameras are **incompatible** with PhotonVision as they require a custom camera library not compatible with PhotonVision.\n:::\n\nArducam cameras are supported for setups with multiple devices."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/troubleshooting/logging.html",
- "title": "Logging",
- "section": "Troubleshooting",
- "language": "All",
- "content": "# Logging\n\n:::{note}\nLogging is very helpful when trying to debug issues within PhotonVision, as it allows us to see what is happening within the program after it is ran. Whenever reporting an issue to PhotonVision, we request that you include logs whenever possible.\n:::\n\nIn addition to storing logs in timestamped files in the config directory, PhotonVision streams logs to the web dashboard. These logs can be viewed later by pressing the \\` key. In this view, logs can be filtered by level or downloaded.\n\n:::{note}\nWhen the program first starts, it sends logs from startup to the client that first connects. This does not happen on subsequent connections.\n:::\n\n:::{note}\nLogs are stored inside the {code}`photonvision_config/logs` directory. Exporting the settings ZIP will also download all old logs for further review.\n:::\n\n```{raw} html\n\n \n Your browser does not support the video tag.\n \n```\n\nRobot mode transitions are also recorded in program logs. These transition messages look something like the two shown below, and show the contents of the [HAL Control Word](https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/hal/ControlWord.html) that the robot was in previously, and what it is now in. This includes:\n- Enabled state\n- Robot state (autonomous vs teleoperated)\n- If the robot e-stop is active\n\nIf the robot is connected to the FMS at an event, we will additionally print out:\n- Event name\n- Match type and number\n- Driver station position\n\n\n```\n[2025-04-19 19:52:08] [NetworkTables - NTDriverStation] [INFO] ROBOT TRANSITIONED MODES! From NtControlWord[m_enabled=true, m_autonomous=false, m_test=false, m_emergencyStop=false, m_fmsAttached=true, m_dsAttached=true] to NtControlWord[m_enabled=true, m_autonomous=false, m_test=true, m_emergencyStop=false, m_fmsAttached=true, m_dsAttached=true]\n\n[2025-04-19 19:52:09] [NetworkTables - NTDriverStation] [INFO] ROBOT TRANSITIONED MODES! From NtControlWord[m_enabled=true, m_autonomous=false, m_test=true, m_emergencyStop=false, m_fmsAttached=true, m_dsAttached=true] to NtControlWord[m_enabled=false, m_autonomous=false, m_test=false, m_emergencyStop=false, m_fmsAttached=false, m_dsAttached=false]\n[2025-04-19 19:52:19] [NetworkTables - NTDriverStation] [INFO] ROBOT TRANSITIONED MODES! From NtControlWord[m_enabled=false, m_autonomous=false, m_test=false, m_emergencyStop=false, m_fmsAttached=false, m_dsAttached=false] to NtControlWord[m_enabled=true, m_autonomous=true, m_test=false, m_emergencyStop=false, m_fmsAttached=true, m_dsAttached=true]\n```\n",
- "content_preview": "# Logging\n\n:::{note}\nLogging is very helpful when trying to debug issues within PhotonVision, as it allows us to see what is happening within the program after it is ran."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/contributing/design-descriptions/e2e-latency.html",
- "title": "Latency Characterization",
- "section": "Contributing",
- "language": "Java",
- "content": "# Latency Characterization\n\n\n## A primer on time\n\nEspecially starting around 2022 with AprilTags making localization easier, providing a way to know when a camera image was captured at became more important for localization.\nSince the [creation of USBFrameProvider](https://github.com/PhotonVision/photonvision/commit/f92bf670ded52b59a00352a4a49c277f01bae305), we used the time [provided by CSCore](https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/cscore/CvSink.html#grabFrame(org.opencv.core.Mat)) to tell when a camera image was captured at, but just keeping track of \"CSCore told us frame N was captured 104.21s after the Raspberry Pi turned on\" isn't very helpful. We can decompose this into asking:\n\n- At what time was a particular image captured at, in the coprocessor's timebase?\n- How do I convert a time in a coprocessor's timebase into the RoboRIO's timebase, so I can integrate the measurement with my other sensor measurements (like encoders)?\n\nThe first one seems easy - CSCore tells us the time, so just keep track of that? Should be easy. For the second, translating this time, as measured by the coprocessor's clock, into a timebase also used by user code on the RoboRIO, is actually a [fairly hard problem](time-sync.md) that involved reinventing [PTP](https://en.wikipedia.org/wiki/PTP).\n\nAnd on latency vs timestamps - PhotonVision has exposed a magic \"latency\" number since forever, but latency (as in, the time from image capture to acting on data) can be useful for benchmarking code, but robots actually want to answer \"what time was this image from, relative to \"?\n\n\n## CSCore's Frame Time\n\nWPILib's CSCore is a platform-agnostic wrapper around Windows, Linux, and MacOS camera APIs. On Linux, CSCore uses [Video4Linux](https://en.wikipedia.org/wiki/Video4Linux) to access USB Video Class (UVC) devices like webcams, as well as CSI cameras on some platforms. At a high level, CSCore's [Linux USB Camera driver](https://github.com/wpilibsuite/allwpilib/blob/17a03514bad6de195639634b3d57d5ac411d601e/cscore/src/main/native/linux/UsbCameraImpl.cpp) works by:\n\n- Opening a camera with `open`\n- Creating and `mmap`ing a handful of buffers V4L will fill with frame data into program memory\n- Asking V4L to start streaming\n- While the camera is running:\n - Wait for new frames\n - Dequeue one buffer\n - Call `SourceImpl::PutFrame`, which will copy the image out and convert as needed\n - Return the buffer to V4L to fill again\n\nPrior to https://github.com/wpilibsuite/allwpilib/pull/7609, CSCore used the [time it dequeued the buffer at](https://github.com/wpilibsuite/allwpilib/blob/17a03514bad6de195639634b3d57d5ac411d601e/cscore/src/main/native/linux/UsbCameraImpl.cpp#L559) as the image capture time. But this doesn't account for exposure time or latency introduced by the camera + USB stack + Linux itself.\n\nV4L does expose (with some [very heavy caveats](https://github.com/torvalds/linux/blob/fc033cf25e612e840e545f8d5ad2edd6ba613ed5/drivers/media/usb/uvc/uvc_video.c#L600) for some troublesome cameras) its best guess at the time an image was captured at via [buffer flags](https://www.kernel.org/doc/html/v4.9/media/uapi/v4l/buffer.html#buffer-flags). In my testing, all my cameras were able to provide timestamps with both these flags set:\n- `V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC`: The buffer timestamp has been taken from the CLOCK_MONOTONIC clock [...] accessible via `clock_gettime()`.\n- `V4L2_BUF_FLAG_TSTAMP_SRC_SOE`: Start Of Exposure. The buffer timestamp has been taken when the exposure of the frame has begun.\n\nI'm sure that we'll find a camera that doesn't play nice, because we can't have nice things :). But until then, using this timestamp gets us a free accuracy bump.\n\nOther things to note: This gets us an estimate at when the camera *started* collecting photons. The camera's sensor will remain collecting light for up to the total integration time, plus readout time for rolling shutter cameras.\n\n## Latency Testing\n\nHere, I've got a RoboRIO with an LED, an Orange Pi 5, and a network switch on a test bench. The LED is assumed to turn on basically instantly once we apply current, and based on DMA testing, the total time to switch a digital output on is on the order of 10uS. The RoboRIO is running a TimeSync Server, and the Orange Pi is running a TimeSync Client.\n\n### Test Setup\n\n\nShow RoboRIO Test Code \n\n```java\npackage frc.robot;\n\nimport org.photonvision.PhotonCamera;\n\nimport edu.wpi.first.wpilibj.DigitalOutput;\nimport edu.wpi.first.wpilibj.TimedRobot;\nimport edu.wpi.first.wpilibj.Timer;\nimport edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;\n\npublic class Robot extends TimedRobot {\n PhotonCamera camera;\n DigitalOutput light;\n\n @Override\n public void robotInit() {\n camera = new PhotonCamera(\"Arducam_OV9782_USB_Camera\");\n\n light = new DigitalOutput(0);\n light.set(false);\n }\n\n @Override\n public void robotPeriodic() {\n super.robotPeriodic();\n\n try {\n light.set(false);\n for (int i = 0; i < 50; i++) {\n Thread.sleep(20);\n camera.getAllUnreadResults();\n }\n\n var t1 = Timer.getFPGATimestamp();\n light.set(true);\n var t2 = Timer.getFPGATimestamp();\n\n\n for (int i = 0; i < 100; i++) {\n for (var result : camera.getAllUnreadResults()) {\n if (result.hasTargets()) {\n var t3 = result.getTimestampSeconds();\n var t1p5 = (t1 + t2) / 2;\n var error = t3-t1p5;\n SmartDashboard.putNumber(\"blink_error_ms\", error * 1000);\n return;\n }\n }\n\n Thread.sleep(20);\n }\n } catch (InterruptedException e) {\n e.printStackTrace();\n }\n }\n}\n```\n \n\nI've decreased camera exposure as much as possible (so we know with reasonable confidence that the image was collected right at the start of the exposure time reported by V4L), but we only get back new images at 60fps. So we don't know when between frame N and N+1 the LED turned on - just that sometime between now and 1/60th of a second a go, the LED turned on.\n\nThe test coprocessor was an Orange Pi 5 running a PhotonVision 2025 (Ubuntu 24.04 based) image, with an ArduCam OV9782 at 1280x800, 60fps, MJPG running a reflective pipeline.\n\n\n### Test Results\n\nThe videos above show the difference between when the RoboRIO turned the LED on and when PhotonVision first seeing a camera frame with the LED on, what I've called error and plotted in yellow with units of seconds. This error decreases when I use the frame time reported by V4L from a mean delta of 26 ms to a mean delta of 11 ms (below the maximum temporal resolution of my camera).\n\nOld CSCore:\n```{raw} html\n\n \n Your browser does not support the video tag.\n \n```\nCSCore using V4L frame time:\n```{raw} html\n\n \n Your browser does not support the video tag.\n \n```\n\nWith the camera capturing at 60fps, the time between successive frames is only ~16.7 ms, so I don't expect to be able to resolve anything smaller. Given sufficient time and with perfect latency compensation, and with more noise in the robot program to make sure we vary LED toggle times, I'd expect the error to converge to ~half the interval between frames - so being within this frame interval with CSCore updates is a very good sign.\n\n### Future Work\n\nThis test also makes no effort to isolate error from time synchronization from error introduced by frame time measurement - we're just interested in overall error. Future work could investigate the latency contribution\n",
- "content_preview": "# Latency Characterization\n\n\n## A primer on time\n\nEspecially starting around 2022 with AprilTags making localization easier, providing a way to know when a camera image was captured at became more important for localization.\nSince the [creation of..."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/advanced-installation/sw_install/other-coprocessors.html",
- "title": "Other Debian-Based Co-Processor Installation",
- "section": "General",
- "language": "All",
- "content": "# Other Debian-Based Co-Processor Installation\n\n:::{warning}\nWorking with unsupported coprocessors requires some level of \"know how\" of your system. The install script has only been tested on Debian/Raspberry Pi OS Buster and Ubuntu Bionic. If any issues arise with your specific OS, please open an issue on our [issues page](https://github.com/PhotonVision/photonvision/issues).\n:::\n\n:::{note}\nWe'd love to have your input! If you get PhotonVision working on another coprocessor, consider documenting your steps and submitting a [docs issue](https://github.com/PhotonVision/photonvision-docs/issues)., [pull request](https://github.com/PhotonVision/photonvision-docs/pulls) , or [ping us on Discord](https://discord.com/invite/wYxTwym). For example, Limelight and Romi install instructions came about because someone spent the time to figure it out, and did a writeup.\n:::\n\n## Installing PhotonVision\n\nWe provide an [install script](https://git.io/JJrEP) for other Debian-based systems (with `apt`) that will automatically install PhotonVision and make sure that it runs on startup.\n\n```bash\n$ wget https://git.io/JJrEP -O install.sh\n$ sudo chmod +x install.sh\n$ sudo ./install.sh\n$ sudo reboot now\n```\n\n:::{note}\nYour co-processor will require an Internet connection for this process to work correctly.\n:::\n\nFor installation on any other co-processors, we recommend reading the {ref}`advanced command line documentation `.\n\n## Updating PhotonVision\n\nPhotonVision can be updated by downloading the latest jar file, copying it onto the processor, and restarting the service.\n\nFor example, from another computer, run the following commands. Substitute the correct username for \"\\[user\\]\" ( Provided images use username \"pi\")\n\n```bash\n$ scp [jar name].jar [user]@photonvision.local:~/\n$ ssh [user]@photonvision.local\n$ sudo mv [jar name].jar /opt/photonvision/photonvision.jar\n$ sudo systemctl restart photonvision.service\n```\n",
- "content_preview": "# Other Debian-Based Co-Processor Installation\n\n:::{warning}\nWorking with unsupported coprocessors requires some level of \"know how\" of your system. The install script has only been tested on Debian/Raspberry Pi OS Buster and Ubuntu Bionic."
+ "content": "# Common Hardware Setups\n\nPhotonVision requires dedicated hardware, above and beyond a roboRIO. This page lists hardware that is frequently used with PhotonVision.\n\n## Coprocessors\n\n- Orange Pi 5 4GB\n - Supports up to 2 object detection streams, along with 2 AprilTag streams at 1280x800 (30fps).\n- Raspberry Pi 5 2GB\n - Supports up to 2 AprilTag streams at 1280x800 (30fps).\n\n:::{note}\nThe Orange Pi 5 is the only currently supported device for object detection.\n:::\n\n## SD Cards\n\n- 8GB or larger micro SD card\n\n:::{important}\nIndustrial grade SD cards from major manufacturers are recommended for robotics applications. For example: Sandisk SDSDQAF3-016G-I .\n:::\n\n## Cameras\n\nInnomaker and Arducam are common manufacturers of hardware designed specifically for vision processing.\n\n- AprilTag Detection\n - OV9281\n\n- Object Detection\n - OV9782\n\n- Driver Camera\n - OV9281\n - OV9782\n - Pi Camera Module V1 {ref}`(More setup info)`\n\nFeel free to get started with any color webcam you have sitting around.\n\n## Power\n\n- Pololu S13V30F5 Regulator\n- Redux Robotics Zinc-V Regulator\n\nSee {ref}`(Selecting Hardware)` for info on why these are recommended.\n",
+ "content_preview": "# Common Hardware Setups\n\nPhotonVision requires dedicated hardware, above and beyond a roboRIO. This page lists hardware that is frequently used with PhotonVision.\n\n## Coprocessors\n\n- Orange Pi 5 4GB\n - Supports up to 2 object detection streams, along with 2 AprilTag streams at 1280x800 (30fps).\n-..."
},
{
"url": "https://docs.photonvision.org/en/latest/docs/troubleshooting/camera-troubleshooting.html",
@@ -180,92 +44,44 @@
"content_preview": "# Camera Troubleshooting\n\n## Pi Cameras\n\nIf you haven't yet, please refer to {ref}`the Pi CSI Camera Configuration page ` for information on updating {code}`config.txt` for your use case."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/camera-specific-configuration/index.html",
- "title": "Camera-Specific Configuration - PhotonVision Docs",
- "section": "Camera Configuration",
- "language": "All",
- "content": "Camera-Specific Configuration Arducam Cameras Setting Up Arducam Cameras Steps to Configure in PhotonVision Pi Camera Configuration Background Updating config.txt Additional Information",
- "content_preview": "Camera-Specific Configuration Arducam Cameras Setting Up Arducam Cameras Steps to Configure in PhotonVision Pi Camera Configuration Background Updating config.txt Additional Information"
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/programming/photonlib/robot-pose-estimator.html",
- "title": "AprilTags and PhotonPoseEstimator",
+ "url": "https://docs.photonvision.org/en/latest/docs/programming/photonlib/fps-limiter.html",
+ "title": "FPS Limiter",
"section": "PhotonLib",
"language": "All",
- "content": "# AprilTags and PhotonPoseEstimator\n\n:::{note}\nFor more information on how to methods to get AprilTag data, look {ref}`here `.\n:::\n\nPhotonLib includes a `PhotonPoseEstimator` class, which allows you to combine the pose data from all tags in view in order to get a field relative pose. For each camera, a separate instance of the `PhotonPoseEstimator` class should be created.\n\n## Creating an `AprilTagFieldLayout`\n\n`AprilTagFieldLayout` is used to represent a layout of AprilTags within a space (field, shop at home, classroom, etc.). WPILib provides a JSON that describes the layout of AprilTags on the field which you can then use in the AprilTagFieldLayout constructor. You can also specify a custom layout.\n\nThe API documentation can be found in here: [Java](https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/apriltag/AprilTagFieldLayout.html), [C++](https://github.wpilib.org/allwpilib/docs/release/cpp/classfrc_1_1_april_tag_field_layout.html), and [Python](https://robotpy.readthedocs.io/projects/apriltag/en/stable/robotpy_apriltag/AprilTagFieldLayout.html#robotpy_apriltag.AprilTagFieldLayout).\n\n```{eval-rst}\n.. tab-set-code::\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-java-examples/poseest/src/main/java/frc/robot/Constants.java\n :language: java\n :lines: 48-49\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-cpp-examples/poseest/src/main/include/Constants.h\n :language: c++\n :lines: 46-47\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-python-examples/poseest/robot.py\n :language: python\n :lines: 46\n```\n\n## Defining the Robot to Camera `Transform3d`\n\nAnother necessary argument for creating a `PhotonPoseEstimator` is the `Transform3d` representing the robot-relative location and orientation of the camera. A `Transform3d` contains a `Translation3d` and a `Rotation3d`. The `Translation3d` is created in meters and the `Rotation3d` is created with radians. For more information on the coordinate system, please see the {ref}`Coordinate Systems ` documentation.\n\n```{eval-rst}\n.. tab-set-code::\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-java-examples/poseest/src/main/java/frc/robot/Constants.java\n :language: java\n :lines: 44-45\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-cpp-examples/poseest/src/main/include/Constants.h\n :language: c++\n :lines: 43-45\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-python-examples/poseest/robot.py\n :language: python\n :lines: 33-36\n```\n\n## Creating a `PhotonPoseEstimator`\n\nThe PhotonPoseEstimator has a constructor that takes an `AprilTagFieldLayout` (see above) and `Transform3d`.\n\n```{eval-rst}\n.. tab-set-code::\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-java-examples/poseest/src/main/java/frc/robot/Vision.java\n :language: java\n :lines: 63\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-cpp-examples/poseest/src/main/include/Vision.h\n :language: c++\n :lines: 149-150\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-python-examples/poseest/robot.py\n :language: python\n :lines: 45-48\n```\n\n## Using a `PhotonPoseEstimator`\n\nTo use your `PhotonPoseEstimator`, you must create a `PhotonCamera` and feed the results into your `PhotonPoseEstimator`. To do this, you must first set the name of your camera in Photon Client. From there you can define the camera in code.\n\n```{eval-rst}\n.. tab-set-code::\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-java-examples/poseest/src/main/java/frc/robot/Vision.java\n :language: java\n :lines: 62\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-cpp-examples/poseest/src/main/include/Vision.h\n :language: c++\n :lines: 151\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-python-examples/poseest/robot.py\n :language: python\n :lines: 44\n```\n\nWhen taking in a result from a `PhotonCamera`, PhotonPoseEstimator offers nine possible \"strategies\" for calculating a pose from a pipeline result in the form of methods that you can call, following the pattern `estimatePose`:\n\n- Coprocessor MultiTag (`estimateCoprocMultiTagPose`)\n - Calculates a new robot position estimate by combining all visible tag corners. Recommended for all teams as it will be the most accurate.\n - Must configure the AprilTagFieldLayout properly in the UI, please see {ref}`here ` for more information.\n- Lowest Ambiguity (`estimateLowestAmbiguityPose`)\n - Choose the Pose with the lowest ambiguity.\n- Closest to Camera Height (`estimateClosestToCameraHeightPose`)\n - Choose the Pose which is closest to the camera height.\n- Closest to Reference Pose (`estimateClosestToReferencePose`)\n - Choose the Pose which is closest to the pose that is passed into the function.\n- Average Best Targets (`estimateAverageBestTargetsPose`)\n - Choose the Pose which is the average of all the poses from each tag.\n- roboRio MultiTag (`estimateRioMultiTagPose`)\n - A slower, older version of Coprocessor MultiTag, not recommended for use.\n- PnP Distance Trig Solve (`estimatePnpDistanceTrigSolvePose`)\n - Use distance data from best visible tag to compute a Pose. This runs on the RoboRIO in order\n to access the robot's yaw heading, and MUST have addHeadingData called every frame so heading\n data is up-to-date. Based on a reference implementation by [FRC Team 6328 Mechanical Advantage](https://www.chiefdelphi.com/t/frc-6328-mechanical-advantage-2025-build-thread/477314/98).\n- Constrained SolvePnP (`estimateConstrainedSolvepnpPose`)\n - Solve a constrained version of the Perspective-n-Point problem with the robot's drivebase\n flat on the floor. This computation takes place on the RoboRIO, and should not take more than 2ms.\n This also requires addHeadingData to be called every frame so heading data is up to date.\n\nCalling one of the `estimatePose()` methods on your `PhotonPoseEstimator` will return an `Optional`, which will be empty if there are no detected tags, not enough detected tags (for multi-tag strategies), missing data (typically heading data), or if the internal solvers failed (this is a rare scenario). `EstimatedRobotPose` includes a `Pose3d` of the latest estimated pose (using the selected strategy) along with a `double` of the timestamp when the robot pose was estimated. The recommended way to use the estimatePose methods is to\n1. do estimation with one of MultiTag methods, check if the result is empty, then\n2. fallback to single tag estimation using a method like `estimateLowestAmbiguityPose`.\n\n```{eval-rst}\n.. tab-set-code::\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-java-examples/poseest/src/main/java/frc/robot/Vision.java\n :language: java\n :lines: 91-94\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-cpp-examples/poseest/src/main/include/Vision.h\n :language: c++\n :lines: 79-82\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-python-examples/poseest/robot.py\n :language: python\n :lines: 52-54\n```\n\nFor Constrained SolvePnP, it's recommended to do the previously mentioned steps, and then feed the pose (if it exists) into `estimateConstrainedSolvepnpPose`, and if the Constrained SolvePnP result is empty, simply feed the seed pose into your drivetrain pose estimator.\n\nOnce you have the `Optional`, you can check to see if there's an actual pose inside, and act accordingly. You should be updating your [drivetrain pose estimator](https://docs.wpilib.org/en/latest/docs/software/advanced-controls/state-space/state-space-pose-estimators.html) with the result from the `PhotonPoseEstimator` every loop using `addVisionMeasurement()`. For Java and C++, the examples pass a method from the drivetrain to a `Vision` object, with the parameter being called `estConsumer`. Python calls the drivetrain directly.\n\n```{eval-rst}\n.. tab-set-code::\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-java-examples/poseest/src/main/java/frc/robot/Robot.java\n :language: java\n :lines: 49\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-cpp-examples/poseest/src/main/include/Robot.h\n :language: c++\n :lines: 54-57\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-python-examples/poseest/robot.py\n :language: python\n :lines: 56-58\n```\n\n```{eval-rst}\n.. tab-set-code::\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-java-examples/poseest/src/main/java/frc/robot/Vision.java\n :language: java\n :lines: 89-115\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-cpp-examples/poseest/src/main/include/Vision.h\n :language: c++\n :lines: 77-100\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-python-examples/poseest/robot.py\n :language: python\n :lines: 51-54\n```\n\n## Complete Examples\n\nThe complete examples for the `PhotonPoseEstimator` can be found in the following locations:\n\n- [Java](https://github.com/PhotonVision/photonvision/tree/main/photonlib-java-examples/poseest)\n- [C++](https://github.com/PhotonVision/photonvision/tree/main/photonlib-cpp-examples/poseest)\n- [Python](https://github.com/PhotonVision/photonvision/tree/main/photonlib-python-examples/poseest)\n\n## Additional `PhotonPoseEstimator` Methods\n\nFor more information on the `PhotonPoseEstimator` class, please see the API documentation.\n\n- [Java Documentation](https://javadocs.photonvision.org/release/org/photonvision/PhotonPoseEstimator.html)\n- [C++ Documentation](https://cppdocs.photonvision.org/release/classphoton_1_1_photon_pose_estimator.html)\n- [Python Documentation](https://pydocs.photonvision.org/release/reference/photonPoseEstimator/)\n",
- "content_preview": "# AprilTags and PhotonPoseEstimator\n\n:::{note}\nFor more information on how to methods to get AprilTag data, look {ref}`here `.\n:::\n\nPhotonLib includes a `PhotonPoseEstimator` class, which allows you to combine the..."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/quick-start/quick-configure.html",
- "title": "Quick Configure",
- "section": "Getting Started",
- "language": "All",
- "content": "# Quick Configure\n\n## Settings to configure\n\n### Team number\n\nIn order for photonvision to connect to the roborio it needs to know your team number.\n\n### Camera Nickname\n\nYou **must** nickname your cameras in PhotonVision to ensure that every camera has a unique name. This is how you will identify cameras in robot code. The camera can be nicknamed using the edit button next to the camera name in the upper right of the Dashboard tab.\n\n```{image} images/editCameraName.png\n:align: center\n```\n\n## Pipeline Settings\n\n### AprilTag\n\nWhen using an Orange Pi 5 with an Arducam OV9281 teams will usually change the following settings. For more info on AprilTag settings please review {ref}`this`.\n\n- Resolution:\n - 1280x800\n- Decimate:\n - 2\n- Mode:\n - 3D\n- Exposure and Gain:\n - Adjust these to achieve good brightness without flicker and low motion blur. This may vary based on lighting conditions in your competition environment.\n- Enable MultiTag\n- Set arducam specific camera type selector to OV9281\n\n#### AprilTags and Motion Blur and Rolling Shutter\n\nWhen detecting AprilTags, it's important to minimize 'motion blur' as much as possible. Motion blur appears as visual streaking or smearing in the camera feed, resulting from the movement of either the camera or the object in focus. Reducing this effect is essential, as the robot is often in motion, and a clearer image allows for detecting as many tags as possible. This is not to be confused with {ref}`rolling shutter`.\n\n- Fixes\n - Lower your exposure as low as possible. Using gain and brightness to account for lack of brightness.\n- Other Options:\n - Don't use/rely on vision measurements while moving.\n\n```{image} images/motionblur.png\n:align: center\n```\n\n### Object Detection\n\nWhen using an Orange Pi 5 with an OV9782 teams will usually change the following settings. For more info on object detection settings please review {ref}`this`.\n\n- Resolution:\n - Resolutions higher than 640x640 may not result in any higher detection accuracy and may lower {ref}`performance`.\n- Confidence:\n - 0.75 - 0.95 Lower values are for detecting worn game pieces or less ideal game pieces. Higher for less worn, more ideal game pieces.\n- White Balance Temperature:\n - Adjust this to achieve better color accuracy. This may be needed to increase confidence.\n- Set arducam specific camera type selector to OV9782\n",
- "content_preview": "# Quick Configure\n\n## Settings to configure\n\n### Team number\n\nIn order for photonvision to connect to the roborio it needs to know your team number.\n\n### Camera Nickname\n\nYou **must** nickname your cameras in PhotonVision to ensure that every camera has a unique name."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/contributing/guidelines.html",
- "title": "Welcome!",
- "section": "Contributing",
- "language": "All",
- "content": "# Welcome!\n\nFirst and foremost, welcome to PhotonVision Development! We're pumped that you're interested to jump in, and help out!\n\nLike most things in FIRST, PhotonVision is reliant on the hard work of dedicated volunteers. It doesn't exist without you. You're joining in with a strong tradition of open-source software development.\n\nThis page talks a bit about how we develop PhotonVision, as a community. It applies to all repos and community aspects for PhotonVision.\n\n## Getting Started\n\nThe very first thing we'd recommend - get your computer set up to be able to build and run PhotonVision. [Docs for that are here](building-photon.md).\n\nThe two best ways to figure out what to do first:\n\n1. Take a look at [the main PhotonVision Repo's Issues](https://github.com/PhotonVision/photonvision/issues) - especially those marked `bug` or `good first issue`!\n2. Connect on [the Discord Server](https://discord.gg/wYxTwym) - Introduce yourself, and talk about what you're interested in!\n\nFrom there - assign yourself to an issue if you intend to work it. Create a Fork in github, and make and test your changes. Once passing builds, meeting your expectations, and passing CI, open a pull request back to the main repo.\n\n## Submitting A PR\n\nPull Requests are the mechanism used to ensure we only merge high-quality, reviewed, concrete, and organized changes to the codebase.\n\nThings that peer reviewers will look for include:\n\n* All CI checks are passing on the server.\n* Documentation - does the PR match the issue? Is the description detailed?\n* Cohesiveness - does the PR express a singular, related set of changes?\n* Architecture - is the code consistent with other code around it? Is it maintainable for the long term?\n* Testing - have unit tests been added as appropriate? Has the change been tested on real hardware?\n\nWork as you can to clean up any changes requested. Once all changes are addressed, the contain should get merged, and included in the next release! Horary!\n\n## General Developer Interaction\n\nThe main guiding principle: remember that we're all volunteers. While promptness is always appreciated (and occasionally required as release deadlines approach), it's important to remember each individual is only contributing when their personal schedule allows. Expect delays, prefer asynchronous communication, and be polite with reminders.\n\nWhile most of the community members are either FIRST robotics mentors or students, the PhotonVision development team is primarily focused on delivering high quality software. Mentorship can and does occur, but is not the primary goal. Members are expected to do their learning fairly independently.\n\nSeek to build trust in the quality of your work. Think carefully on your opinions before asking others to think about them too.\n\nBias toward action, and productionizable code. Limit the number of active PR's to help keep focus.\n\nFinally, be sure to embody the ethos of Gracious Professionalism in all your actions, on all platforms in the project. See more in our [code of conduct](https://github.com/PhotonVision/.github/blob/738dfcb792fdbfc2e8408c0135e389179fc483c0/codeofcoduct.md)\n\n### AI Usage\n\nCoding assistants driven by Large Language Models (\"AI\") are extremely powerful tools, and have been used on more than one occasion to accelerate development.\n\nPhotonVision still maintains a fundamental philosophy that the human submitting the pull request is responsible for the code and its behavior, regardless of the tools used to create it.\n\nThese tools can also generate a large volume of code changes, very rapidly. The above guidelines on PR quality still apply - large, undirected, or overly-scoped PR's are likely to be ignored, regardless of tooling used to generate them.\n\n### Violations\n\nWe're thankful that we've rarely experienced major issues in our community. In all cases, the project leads shall have the final decision making authority when dealing with violations of these guidelines.\n\n## Yearly Development Cycle\n\nPhotonVision's Development Cycle follows the same general flow as the FRC Build Season. Larger experiments get done over the summer, fall focuses on testing and \"production-readiness\", build season focuses on keeping all teams running smoothly.\n\nThe actual priorities also shift as developers have more or time to commit, or express interest in specific direction.\n\nPR's are absolutely always welcome. Just note depending on the scope and size, they may not be reviewed or merged immediately.\n\n## Project Governance\n\nThis project is jointly lead by Matt and Banks, who may be contacted on Discord. They serve as a \"Benevolent leader for life\" role, coordinating and approving architecture as needed, and curating the team of developers who have Pull Request review and merge responsibilities.\n",
- "content_preview": "# Welcome!\n\nFirst and foremost, welcome to PhotonVision Development! We're pumped that you're interested to jump in, and help out!\n\nLike most things in FIRST, PhotonVision is reliant on the hard work of dedicated volunteers. It doesn't exist without you."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/troubleshooting/networking-troubleshooting.html",
- "title": "Networking Troubleshooting",
- "section": "Troubleshooting",
- "language": "All",
- "content": "# Networking Troubleshooting\n\nBefore reading further, ensure that you follow all the recommendations {ref}`in our networking section `. You should follow these guidelines in order for PhotonVision to work properly; other networking setups are not officially supported.\n\n## Checklist\n\nA few issues make up the majority of support requests. Run through this checklist quickly to catch some common mistakes.\n\n- Is your camera connected to the robot's radio through a {ref}`network switch `?\n - Ethernet straight from a laptop to a coprocessor will not work (most likely), due to the unreliability of link-local connections.\n - Even if there's a switch between your laptop and coprocessor, you'll still want a radio or router in the loop somehow.\n - The FRC radio is the _only_ router we will officially support due to the innumerable variations between routers.\n- (Raspberry Pi, Orange Pi & Limelight only) have you flashed the correct image, and is it [up to date](https://github.com/PhotonVision/photonvision/releases/latest)?\n- Is your robot code using a **2026** version of WPILib, and is your coprocessor using the most up to date **2026** release?\n - 2022, 2023, 2024, 2025, and 2026 versions of either cannot be mix-and-matched!\n - Your PhotonVision version can be checked on the settings tab.\n- Is your team number correctly set on the settings tab?\n\n### photonvision.local Not Found\n\nUse [Angry IP Scanner](https://angryip.org/) and look for an IP that has port 5800 open. Then go to your web browser and do \\:5800.\n\nAlternatively, you can plug your coprocessor into a display, plug in a keyboard, and run `hostname -I` in the terminal. This should give you the IP Address of your coprocessor, then go to your web browser and do \\:5800.\n\nIf nothing shows up, ensure your coprocessor has power, and you are following all of our networking recommendations, feel free to {ref}`contact us ` and we will help you.\n\n### Can't Connect To Robot\n\nPlease check that:\n1\\. You don't have the NetworkTables Server on (toggleable in the settings tab). Turn this off when doing work on a robot.\n2\\. You have your team number set properly in the settings tab.\n3\\. Your camera name in the `PhotonCamera` constructor matches the name in the UI.\n4\\. You are using the 2026 version of WPILib and RoboRIO image.\n5\\. Your robot is on.\n\nIf all of the above are met and you still have issues, feel free to {ref}`contact us ` and provide the following information:\n\n- The WPILib version used by your robot code\n- PhotonLib vendor dependency version\n- PhotonVision version (from the UI)\n- Your settings exported from your coprocessor (if you're able to access it)\n- How your RoboRIO/coprocessor are networked together\n",
- "content_preview": "# Networking Troubleshooting\n\nBefore reading further, ensure that you follow all the recommendations {ref}`in our networking section `."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/calibration/calibration.html",
- "title": "Calibrating Your Camera",
- "section": "Camera Calibration",
- "language": "All",
- "content": "# Calibrating Your Camera\n\n:::{important}\nIn order to detect AprilTags and use 3D mode, your camera must be calibrated at the desired resolution! Inaccurate calibration will lead to poor performance.\n:::\n\nTo calibrate a camera, images of a ChArUco board (or chessboard) are taken. By comparing where the grid corners should be in object space (for example, a corner once every inch in an 8x6 grid) with where they appear in the camera image, we can find a least-squares estimate for intrinsic camera properties like focal lengths, center point, and distortion coefficients. For more on camera calibration, please review the [OpenCV documentation](https://docs.opencv.org/4.x/dc/dbb/tutorial_py_calibration.html).\n\n:::{warning}\nWhile any resolution can be calibrated, higher resolutions may be too performance-intensive for some coprocessors to handle. Therefore, we recommend experimenting to see what works best for your coprocessor.\n:::\n\n:::{note}\nThe calibration data collected during calibration is specific to each physical camera, as well as each individual resolution.\n:::\n\n## Calibration Tips\n\n:::{warning}\nThe usage of chessboards can result in bad calibration results if multiple similar images are taken. We strongly recommend that teams use ChArUco boards instead!\n:::\n\nAccurate camera calibration is required in order to get accurate pose measurements when using AprilTags and 3D mode. The tips below should help ensure success:\n\n01. Ensure the images you take have the target in different positions and angles, with as big of a difference between angles as possible. It is important to make sure the target overlay still lines up with the board while doing this. Tilt no more than 45 degrees.\n02. Use as big of a calibration target as your printer can print.\n03. Ensure that your printed pattern has enough white border around it.\n04. Ensure your camera stays in one position during the duration of the calibration.\n05. Make sure you get all 12 images from varying distances and angles.\n06. Take at least one image that covers the total image area, and generally ensure that you get even coverage of the lens with your image set.\n07. Have good lighting, having a diffusely lit target would be best (light specifically shining on the target without shadows).\n08. Ensure the calibration target is completely flat and does not bend or fold in any way. It should be mounted/taped down to something flat and then used for calibration, do not just hold it up.\n09. Avoid having targets that are parallel to the lens of the camera / straight on towards the camera as much as possible. You want angles and variations within your calibration images.\n\nFollowing the ideas above should help in getting an accurate calibration.\n\n## Calibrating using PhotonVision\n\n### 1. Navigate to the calibration section in the UI.\n\nThe Cameras tab of the UI houses PhotonVision's camera calibration tooling. It assists users with calibrating their cameras, as well as allows them to view previously calibrated resolutions. We support both ChArUco and chessboard calibrations.\n\n### 2. Print out the calibration target.\n\nIn the Camera Calibration tab, we'll print out the calibration target using the \"Download\" button. This should be printed on 8.5x11 printer paper. This page shows using an 8x8 ChArUco board (or chessboard depending on the selected calibration type).\n\n:::{warning}\nEnsure that there is no scaling applied during printing (it should be at 100%) and that the PDF is printed as is on regular printer paper. Check the square size with calipers or an accurate measuring device after printing to ensure squares are sized properly, and enter the true size of the square in the UI text box. For optimal results, various resources are available online to calibrate your specific printer if needed.\n:::\n\n### 3. Select calibration resolution and fill in appropriate target data.\n\nWe'll next select a resolution to calibrate and populate our pattern spacing, marker size, and board size. The provided chessboard and ChArUco board are an 8x8 grid of 1 inch square. The provided ChArUco board uses the 4x4 dictionary with a marker size of 0.75 inches (this board does not need the old OpenCV pattern selector selected). Printers are not perfect, and you need to measure your calibration target and enter the correct marker size (size of the ArUco marker) and pattern spacing (aka size of the black square) using calipers or similar. Finally, once our entered data is correct, we'll click \"start calibration.\"\n\n:::{warning} Old OpenCV Pattern selector. This should be used in the case that the calibration image is generated from a version of OpenCV before version 4.6.0. This would include targets created by calib.io. If this selector is not set correctly the calibration will be completely invalid. For more info view [this GitHub issue](https://github.com/opencv/opencv_contrib/issues/3291).\n:::\n\n:::{note}\nIf you have a [calib.io](https://calib.io/) ChArUco Target you will have to enter the paramaters of your target. For example if your target says \"9x12 | Checker Size: 30 mm | Marker Size: 22 mm | Dictionary: ArUco DICT 5x5\", you would have to set the board type to Dict_5x5_1000, the pattern spacing to 1.1811 in (30 mm converted to inches), the marker size 0.866142 in (22 mm converted to inches), the board width to 12 and the board height to 9. If you chose the wrong tag family the board wont be detected during calibration. If you swap the width and height your calibration will have a very high error.\n:::\n\n### 4. Take at calibration images from various angles.\n\nNow, we'll capture images of our board from various angles. It's important to check that the board overlay matches the board in your image. The further the overdrawn points are from the true position of the chessboard corners, the less accurate the final calibration will be. We'll want to capture enough images to cover the whole camera's FOV (with a minimum of 12). Once we've got our images, we'll click \"Finish calibration\" and wait for the calibration process to complete. If all goes well, the mean error and FOVs will be shown in the table on the right. The FOV should be close to the camera's specified FOV (usually found in a datasheet) usually within + or - 10 degrees. The mean error should also be low, usually less than 1 pixel.\n\n```{raw} html\n\n \n Your browser does not support the video tag.\n \n```\n\n## Accessing Calibration Images\n\nDetails about a particular calibration can be viewed by clicking on that resolution in the calibrations tab. This tab allows you to download raw calibration data, upload a previous calibration, and inspect details about calculated camera intrinsic.\n\n```{image} images/cal-details.png\n:alt: Captured calibration images\n:width: 600\n```\n\n:::{note}\nMore info on what these parameters mean can be found in [OpenCV's docs](https://docs.opencv.org/4.8.0/d4/d94/tutorial_camera_calibration.html)\n:::\n\n- Fx/Fy: Estimated camera focal length, in pixels\n- Fx/Cy: Estimated camera optical center, in pixels. This should be at about the center of the image\n- Distortion: OpenCV camera model distortion coefficients\n- FOV: calculated using estimated focal length and image size. Useful for gut-checking calibration results\n- Mean Err: Mean reprojection error, or distance between expected and observed chessboard cameras for the full calibration dataset\n\nBelow these outputs are the snapshots collected for calibration, along with a per-snapshot mean reprojection error. A snapshot with a larger reprojection error might indicate a bad snapshot, due to effects such as motion blur or misidentified chessboard corners.\n\nCalibration images can also be extracted from the downloaded JSON file using [this Python script](https://raw.githubusercontent.com/PhotonVision/photonvision/main/devTools/calibrationUtils.py). This script will unpack calibration images, and also generate a VNL file for use [with mrcal](https://mrcal.secretsauce.net/).\n\n```\npython3 /path/to/calibrationUtils.py path/to/photon_calibration.json /path/to/output/folder\n```\n\n```{image} images/unpacked-json.png\n:alt: Captured calibration images\n:width: 600\n```\n\n## Investigating Calibration Data with mrcal\n\n[mrcal](https://mrcal.secretsauce.net/tour.html) is a command-line tool for camera calibration and visualization. PhotonVision has the option to use the mrcal backend during camera calibration to estimate intrinsics. mrcal can also be used post-calibration to inspect snapshots and provide feedback. These steps will closely follow the [mrcal tour](https://mrcal.secretsauce.net/tour-initial-calibration.html) -- I'm aggregating commands and notes here, but the mrcal documentation is much more thorough.\n\nStart by [Installing mrcal](https://mrcal.secretsauce.net/install.html). Note that while mrcal *calibration* using PhotonVision is supported on all platforms, but investigation right now only works on Linux. Some users have also reported luck using [WSL 2 on Windows](https://learn.microsoft.com/en-us/windows/wsl/tutorials/gui-apps) as well. You may also need to install `feedgnuplot`. On Ubuntu systems, these commands should be run from a standalone terminal and *not* the one [built into vscode](https://github.com/ros2/ros2/issues/1406).\n\nLet's run `calibrationUtils.py` as described above, and then cd into the output folder. From here, you can follow the mrcal tour, just replacing the VNL filename and camera imager size as necessary. My camera calibration was at 1280x720, so I've set the XY limits to that below.\n\n```\n$ cd /path/to/output/folder\n$ ls\nmatt@photonvision:~/Documents/Downloads/2024-01-02_lifecam_1280$ ls\n corners.vnl img0.png img10.png img11.png img12.png img13.png img1.png\n img2.png img3.png img4.png img5.png img6.png img7.png img8.png\n img9.png cameramodel_0.cameramodel\n\n$ < corners.vnl \\\n vnl-filter -p x,y | \\\n feedgnuplot --domain --square --set 'xrange [0:1280] noextend' --set 'yrange [720:0] noextend'\n```\n\n```{image} images/mrcal-coverage.svg\n:alt: A diagram showing the locations of all detected chessboard corners.\n```\n\nAs you can see, we didn't do a fantastic job of covering our whole camera sensor -- there's a big gap across the whole right side, for example. We also only have 14 calibration images. We've also got our \"cameramodel\" file, which can be used by mrcal to display additional debug info.\n\nLet's inspect our reprojection error residuals. We expect their magnitudes and directions to be random -- if there's patterns in the colors shown, then our calibration probably doesn't fully explain our physical camera sensor.\n\n```\n$ mrcal-show-residuals --magnitudes --set 'cbrange [0:1.5]' ./camera-0.cameramodel\n$ mrcal-show-residuals --directions --unset key ./camera-0.cameramodel\n```\n\n```{image} images/residual-magnitudes.svg\n:alt: A diagram showing residual magnitudes\n```\n\n```{image} images/residual-directions.svg\n:alt: A diagram showing residual directions\n```\n\nClearly we don't have anywhere near enough data to draw any meaningful conclusions (yet). But for fun, let's dig into [camera uncertainty estimation](https://mrcal.secretsauce.net/tour-uncertainty.html). This diagram shows how expected projection error changes due to noise in calibration inputs. Lower projection error across a larger area of the sensor imply a better calibration that more fully covers the whole sensor. For my calibration data, you can tell the projection error isolines (lines of constant expected projection error) are skewed to the left, following my dataset (which was also skewed left).\n\n```\n$ mrcal-show-projection-uncertainty --unset key ./cameramodel_0.cameramodel\n```\n\n```{image} images/camera-uncertainty.svg\n:alt: A diagram showing camera uncertainty\n```\n",
- "content_preview": "# Calibrating Your Camera\n\n:::{important}\nIn order to detect AprilTags and use 3D mode, your camera must be calibrated at the desired resolution! Inaccurate calibration will lead to poor performance.\n:::\n\nTo calibrate a camera, images of a ChArUco board (or chessboard) are taken."
+ "content": "# FPS Limiter\n\n:::{warning}\nWhen using the FPS limiter, it's important to disable it before a match begins.\n:::\n\nThe FPS limiter can be used to lower the frames processed per second for a given camera. This is intended to be used for power-saving, particularly in the case of high FPS cameras with powerful coprocessors. The value passed to the function will indicate the frames per second that should be processed. A value of -1 should be passed to indicate that the FPS limiter should not restrict processing; this is the default behavior.\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n int limit = camera.getFPSLimit();\n\n camera.setFPSLimit(10);\n\n // This removes any previously set FPS limit.\n camera.setFPSLimit(-1);\n\n .. code-block:: c++\n\n int limit = camera.GetFPSLimit();\n\n camera.SetFPSLimit(10);\n\n // This removes any previously set FPS limit.\n camera.SetFPSLimit(-1);\n\n .. code-block:: python\n\n limit = camera.getFPSLimit()\n\n camera.setFPSLimit(10)\n\n # This removes any previously set FPS limit.\n camera.setFPSLimit(-1)\n```\n",
+ "content_preview": "# FPS Limiter\n\n:::{warning}\nWhen using the FPS limiter, it's important to disable it before a match begins.\n:::\n\nThe FPS limiter can be used to lower the frames processed per second for a given camera."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/troubleshooting/common-errors.html",
- "title": "Common Issues / Questions",
- "section": "Troubleshooting",
+ "url": "https://docs.photonvision.org/en/latest/docs/examples/aimingatatarget.html",
+ "title": "Aiming at a Target",
+ "section": "Code Examples",
"language": "All",
- "content": "# Common Issues / Questions\n\nThis page will grow as needed in order to cover commonly seen issues by teams. If this page doesn't help you and you need further assistance, feel free to {ref}`Contact Us`.\n\n## Known Issues\n\nAll known issues can be found on our [GitHub page](https://github.com/PhotonVision/photonvision/issues).\n\n### PS3Eye\n\nDue to an issue with Linux kernels, the drivers for the PS3Eye are no longer supported. If you would still like to use the PS3Eye, you can downgrade your kernel with the following command: `sudo CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt rpi-update 866751bfd023e72bd96a8225cf567e03c334ecc4`. Note: You must be connected to the internet to run the command.\n\n### LED Control\n\nThe logic for controlling LED mode when `multiple cameras are connected` is not fully fleshed out. In its current state, LED control is only enabled when a Pi Camera Module is not in driver mode—meaning a USB camera on its own is unable to control the LEDs.\n\nFor now, if you are using multiple cameras, it is recommended that teams set the value of the NetworkTables entry {code}`photonvision/ledMode` from the robot code to control LED state.\n\n## Commonly Seen Issues\n\n### Networking Issues\n\nPlease refer to our comprehensive {ref}`networking troubleshooting tips ` for debugging suggestions and possible causes.\n\n### Camera won't show up\n\nTry these steps to {ref}`troubleshoot your camera connection `.\n\nIf you are using a USB camera, it is possible your USB Camera isn't supported by CSCore and therefore won't work with PhotonVision.\n\n### Camera is consistently returning incorrect values when in 3D mode\n\nRead the tips on the {ref}`camera calibration page`, follow the advice there, and redo the calibration.\n\n### Not getting data from PhotonLib\n\n1. Ensure your coprocessor version and PhotonLib version match. This can be checked by the settings tab and examining the .json itself (respectively).\n2. Ensure that you have your team number set properly.\n3. Use Glass to verify that PhotonVision has connected to the NetworkTables server served by your robot. With Glass connected in client mode to your RoboRIO, we expect to see \"photonvision\" listed under the Clients tab of the NetworkTables Info pane.\n\n```{image} images/glass-connections.png\n:alt: Using Glass to check NT connections\n:width: 600\n```\n\n4. When creating a `PhotonCamera` in code, does the `cameraName` provided match the name in the upper-right card of the web interface? Glass can be used to verify the RoboRIO is receiving NetworkTables data by inspecting the `photonvision` subtable for your camera nickname.\n\n```{image} images/camera-subtable.png\n:alt: Using Glass to check camera publishing\n:width: 600\n```\n\n### Unable to download PhotonLib\n\nEnsure all of your network firewalls are disabled and you aren't on a school-network.\n\n### PhotonVision prompts for login on startup\n\nThis is normal. You don't need to connect a display to your Raspberry Pi to use PhotonVision, just navigate to the relevant webpage (ex. `photonvision.local:5800`) in order to see the dashboard.\n\n### Raspberry Pi enters into boot looping state when using PhotonVision\n\nThis is most commonly seen when your Pi doesn't have adequate power / is being undervolted. Ensure that your power supply is functioning properly.\n",
- "content_preview": "# Common Issues / Questions\n\nThis page will grow as needed in order to cover commonly seen issues by teams. If this page doesn't help you and you need further assistance, feel free to {ref}`Contact Us`.\n\n## Known Issues\n\nAll known issues can be found on our [GitHub..."
+ "content": "# Aiming at a Target\n\nThe following example is from the PhotonLib example repository ([Java](https://github.com/PhotonVision/photonvision/tree/main/photonlib-java-examples/aimattarget)).\n\n## Knowledge and Equipment Needed\n\n- A Robot\n- A camera mounted rigidly to the robot's frame, centered and pointed forward.\n- A coprocessor running PhotonVision with an AprilTag or ArUco 2D Pipeline.\n- [A printout of AprilTag 7](https://firstfrc.blob.core.windows.net/frc2026/FieldAssets/2026-apriltag-images-user-guide.pdf), mounted on a rigid and flat surface.\n\n## Code\n\nNow that you have properly set up your vision system and have tuned a pipeline, you can now aim your robot at an AprilTag using the data from PhotonVision. The _yaw_ of the target is the critical piece of data that will be needed first.\n\nYaw is reported to the roboRIO over Network Tables. PhotonLib, our vendor dependency, is the easiest way to access this data. The documentation for the Network Tables API can be found {ref}`here ` and the documentation for PhotonLib {ref}`here `.\n\nIn this example, while the operator holds a button down, the robot will turn towards the AprilTag using the P term of a PID loop. To learn more about how PID loops work, how WPILib implements them, and more, visit [Advanced Controls (PID)](https://docs.wpilib.org/en/stable/docs/software/advanced-control/introduction/index.html) and [PID Control in WPILib](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/controllers/pidcontroller.html#pid-control-in-wpilib).\n\n```{eval-rst}\n.. tab-set::\n :sync-group: code\n\n .. tab-item:: Java\n :sync: java\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/aimattarget/src/main/java/frc/robot/Robot.java\n :language: java\n :lines: 77-117\n :linenos:\n :lineno-start: 77\n\n .. tab-item:: C++ (Header)\n :sync: c++\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/aimattarget/src/main/include/Robot.h\n :language: c++\n :lines: 25-60\n :linenos:\n :lineno-start: 25\n\n .. tab-item:: C++ (Source)\n :sync: c++\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/aimattarget/src/main/cpp/Robot.cpp\n :language: c++\n :lines: 56-96\n :linenos:\n :lineno-start: 56\n\n .. tab-item:: Python\n :sync: python\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-python-examples/aimattarget/robot.py\n :language: python\n :lines: 46-70\n :linenos:\n :lineno-start: 46\n\n```\n",
+ "content_preview": "# Aiming at a Target\n\nThe following example is from the PhotonLib example repository ([Java](https://github.com/PhotonVision/photonvision/tree/main/photonlib-java-examples/aimattarget)).\n\n## Knowledge and Equipment Needed\n\n- A Robot\n- A camera mounted rigidly to the robot's frame, centered and..."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/hardware/index.html",
- "title": "Hardware Selection - PhotonVision Docs",
- "section": "Hardware Selection",
+ "url": "https://docs.photonvision.org/en/latest/docs/programming/photonlib/driver-mode-pipeline-index.html",
+ "title": "Driver Mode and Pipeline Index/Latency",
+ "section": "PhotonLib",
"language": "All",
- "content": "Hardware Selection Selecting Hardware Choosing a Coprocessor Choosing a Camera Performance Matrix Deploying on Custom Hardware Configuration LED Support Hardware Interaction Commands Known Camera FOV Device Name Branding Example",
- "content_preview": "Hardware Selection Selecting Hardware Choosing a Coprocessor Choosing a Camera Performance Matrix Deploying on Custom Hardware Configuration LED Support Hardware Interaction Commands Known Camera FOV Device Name Branding Example"
+ "content": "# Driver Mode and Pipeline Index/Latency\n\nAfter {ref}`creating a PhotonCamera `, one can toggle Driver Mode and change the Pipeline Index of the vision program from robot code.\n\n## Toggle Driver Mode\n\nYou can use the `setDriverMode()`/`SetDriverMode()` (Java and C++ respectively) to toggle driver mode from your robot program. Driver mode is an unfiltered / normal view of the camera to be used while driving the robot.\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // Set driver mode to on.\n camera.setDriverMode(true);\n\n .. code-block:: c++\n\n // Set driver mode to on.\n camera.SetDriverMode(true);\n\n .. code-block:: python\n\n # Coming Soon!\n```\n\n## Setting the Pipeline Index\n\nYou can use the `setPipelineIndex()`/`SetPipelineIndex()` (Java and C++ respectively) to dynamically change the vision pipeline from your robot program.\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // Change pipeline to 2\n camera.setPipelineIndex(2);\n\n .. code-block:: c++\n\n // Change pipeline to 2\n camera.SetPipelineIndex(2);\n\n .. code-block:: python\n\n # Coming Soon!\n```\n\n## Getting the Pipeline Latency\n\nYou can also get the pipeline latency from a pipeline result using the `getLatencyMillis()`/`GetLatency()` (Java and C++ respectively) methods on a `PhotonPipelineResult`.\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n // Get the pipeline latency.\n double latencySeconds = result.getLatencyMillis() / 1000.0;\n\n .. code-block:: c++\n\n // Get the pipeline latency.\n units::second_t latency = result.GetLatency();\n\n .. code-block:: python\n\n # Coming Soon!\n```\n\n:::{note}\nThe C++ version of PhotonLib returns the latency in a unit container. For more information on the Units library, see [here](https://docs.wpilib.org/en/stable/docs/software/basic-programming/cpp-units.html).\n:::\n",
+ "content_preview": "# Driver Mode and Pipeline Index/Latency\n\nAfter {ref}`creating a PhotonCamera `, one can toggle Driver Mode and change the Pipeline Index of the vision program from robot code.\n\n## Toggle Driver Mode\n\nYou can use the..."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/contributing/developer-docs/photonlib-backups.html",
- "title": "Photonlib Developer Docs",
+ "url": "https://docs.photonvision.org/en/latest/docs/contributing/design-descriptions/time-sync.html",
+ "title": "Time Synchronization Protocol Specification, Version 1.0",
"section": "Contributing",
"language": "All",
- "content": "# Photonlib Developer Docs\n\nOur maven server is located at https://maven.photonvision.org/#/. This server runs [Reposilite](https://hub.docker.com/r/dzikoysk/reposilite) in Docker, and uses Caddy for serving requests.\n\n\n## Backing up using Rsync\n\nThe Clarkson Open Source Institute at Clarkson University provides a mirror of our artifacts available [online](https://mirror.clarkson.edu/photonvision). Learn more about them at [their homepage](https://mirror.clarkson.edu/home).\n\nArtifacts from our Maven server can also be backed up locally to a folder called `photonlib-backup` using the following command, which excludes \"snapshots\" for space reasons:\n\n```\nrsync -avzrHy --no-perms --no-group --no-owner --ignore-errors --exclude \".~tmp~\" --exclude \"snapshots/org/photonvision/photontargeting*\" \\\n--exclude \"snapshots/org/photonvision/photonlib*\" maven.photonvision.org::reposilite-data \\\n/path/to/photonlib-backup\n```\n",
- "content_preview": "# Photonlib Developer Docs\n\nOur maven server is located at https://maven.photonvision.org/#/. This server runs [Reposilite](https://hub.docker.com/r/dzikoysk/reposilite) in Docker, and uses Caddy for serving requests.\n\n\n## Backing up using Rsync\n\nThe Clarkson Open Source Institute at Clarkson..."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/examples/poseest.html",
- "title": "Using WPILib Pose Estimation, Simulation, and PhotonVision Together",
- "section": "Code Examples",
- "language": "All",
- "content": "# Using WPILib Pose Estimation, Simulation, and PhotonVision Together\n\nThe following example comes from the PhotonLib example repository ([Java](https://github.com/PhotonVision/photonvision/tree/main/photonlib-java-examples/poseest)/[C++](https://github.com/PhotonVision/photonvision/tree/main/photonlib-cpp-examples/poseest)/[Python](https://github.com/PhotonVision/photonvision/tree/main/photonlib-python-examples/poseest)). Full code is available at that links.\n\n## Knowledge and Equipment Needed\n\n- Everything required in {ref}`Combining Aiming and Getting in Range `, plus some familiarity with WPILib pose estimation functionality.\n\n## Background\n\nThis example demonstrates integration of swerve drive control, a basic swerve physics simulation, and PhotonLib's simulated vision system functionality.\n\n## Walkthrough\n\n### Estimating Pose\n\nThe {code}`Drivetrain` class includes functionality to fuse multiple sensor readings together (including PhotonVision) into a best-guess of the pose on the field.\n\nPlease reference the [WPILib documentation](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/state-space/state-space-pose_state-estimators.html) on using the {code}`SwerveDrivePoseEstimator` class.\n\nWe use the current game's AprilTag Locations:\n\n```{eval-rst}\n.. tab-set-code::\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/poseest/src/main/java/frc/robot/Vision.java\n :language: java\n :lines: 68-68\n :linenos:\n :lineno-start: 68\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/poseest/src/main/include/Constants.h\n :language: c++\n :lines: 42-43\n :linenos:\n :lineno-start: 42\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-python-examples/poseest/robot.py\n :language: python\n :lines: 46-46\n :linenos:\n :lineno-start: 46\n\n```\n\n\n\nTo incorporate PhotonVision, we need to create a {code}`PhotonCamera`:\n\n\n```{eval-rst}\n.. tab-set-code::\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/poseest/src/main/java/frc/robot/Vision.java\n :language: java\n :lines: 57-57\n :linenos:\n :lineno-start: 57\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/poseest/src/main/include/Vision.h\n :language: c++\n :lines: 145-145\n :linenos:\n :lineno-start: 145\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-python-examples/poseest/robot.py\n :language: python\n :lines: 44-44\n :linenos:\n :lineno-start: 44\n```\n\nDuring periodic execution, we read back camera results. If we see AprilTags in the image, we calculate the camera-measured pose of the robot and pass it to the {code}`Drivetrain`.\n\n```{eval-rst}\n.. tab-set-code::\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/poseest/src/main/java/frc/robot/Robot.java\n :language: java\n :lines: 64-74\n :linenos:\n :lineno-start: 64\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/poseest/src/main/cpp/Robot.cpp\n :language: c++\n :lines: 38-46\n :linenos:\n :lineno-start: 38\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-python-examples/poseest/robot.py\n :language: python\n :lines: 54-56\n :linenos:\n :lineno-start: 54\n\n```\n\n### Simulating the Camera\n\nFirst, we create a new {code}`VisionSystemSim` to represent our camera and coprocessor running PhotonVision, and moving around our simulated field.\n\n```{eval-rst}\n.. tab-set-code::\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/poseest/src/main/java/frc/robot/Vision.java\n :language: java\n :lines: 65-69\n :linenos:\n :lineno-start: 65\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/poseest/src/main/include/Vision.h\n :language: c++\n :lines: 49-52\n :linenos:\n :lineno-start: 49\n\n .. code-block:: python\n\n # Coming Soon!\n\n```\n\nThen, we add configure the simulated vision system to match the camera system being simulated.\n\n```{eval-rst}\n.. tab-set-code::\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/poseest/src/main/java/frc/robot/Vision.java\n :language: java\n :lines: 69-82\n :linenos:\n :lineno-start: 69\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/poseest/src/main/include/Vision.h\n :language: c++\n :lines: 53-65\n :linenos:\n :lineno-start: 53\n\n .. code-block:: python\n\n # Coming Soon!\n```\n\n\n### Updating the Simulated Vision System\n\nDuring simulation, we periodically update the simulated vision system.\n\n```{eval-rst}\n.. tab-set-code::\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/poseest/src/main/java/frc/robot/Robot.java\n :language: java\n :lines: 114-132\n :linenos:\n :lineno-start: 114\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/poseest/src/main/cpp/Robot.cpp\n :language: c++\n :lines: 95-109\n :linenos:\n :lineno-start: 95\n\n .. code-block:: python\n\n # Coming Soon!\n```\n\nThe rest is done behind the scenes.\n\n```{raw} html\n\n \n Your browser does not support the video tag.\n \n```\n",
- "content_preview": "# Using WPILib Pose Estimation, Simulation, and PhotonVision Together\n\nThe following example comes from the PhotonLib example repository ([Java](https://github.com/PhotonVision/photonvision/tree/main/photonlib-java-examples/poseest)/[C++](https://github.com/PhotonVision/photonvision/tree/main/photon..."
+ "content": "# Time Synchronization Protocol Specification, Version 1.0\n\nProtocol Revision 1.0, 08/25/2024\n\n## Background\n\nIn a distributed compute environment like robots, time synchronization between computers is increasingly important. Currently, [NetworkTables Version 4.1](https://github.com/wpilibsuite/allwpilib/blob/main/ntcore/doc/networktables4.adoc) provides support for time synchronization of clients with the NetworkTables server using binary PING/PONG messages sent over WebSockets. This approach, while fundamentally the same as is described in this memo, has demonstrated some opportunities for improvement:\n\n- PING/PONG messages are processed in the same queue as other NetworkTables messages. Depending on the underlying implementation and processor speed, this can incur message processing delays and increase client-calculated Round-Trip Time (RTT), and cause messages to arrive at the server timestamped in the future.\n- Messages use WebSockets over TCP for their transport layer. We don't need the robustness guarantees of TCP as our connection is stateless.\n\nFor these reasons, a time synchronization solution separate from NetworkTables communication was desired. Architecture decisions made to address these issues are:\n\n- Use the User Datagram Protocol (UDP) transport layer, as we don't need the robustness guarantees afforded by TCP. As a Client, if a PING isn't replied to, we'll just try again at the start of the next PING window. As a bonus, we are free to use UDP port 5810 as NetworkTables only uses TCP Port 5810/5811 as of Version 4.1.\n- Use a separate thread from the current NetworkTables libUV runner.\n\n\n## Prior Art\n\nThe [NetworkTables 4.1 timestamp synchronization](https://github.com/wpilibsuite/allwpilib/blob/main/ntcore/doc/networktables4.adoc#timestamps) approach, an implementation of [Cristian's Algorithm](https://en.wikipedia.org/wiki/Cristian%27s_algorithm). We also implement Cristian’s Algorithm.\n\nThe [Precision Time Protocol](https://en.wikipedia.org/wiki/Precision_Time_Protocol#Synchronization) at it's core does something similar with Sync/Delay_Req/Delay_Resp. We do not have (guaranteed) access to hardware timestamping, but we utilize this PING/PONG pattern to estimate total round-trip time.\n\n\n## Roles\n\n```{graphviz}\ndigraph CristianAlgorithm {\n ratio=0.5;\n bgcolor=\"transparent\";\n\n node [\n fontcolor = \"#e6e6e6\",\n style = filled,\n color = \"#e6e6e6\",\n fillcolor = \"#333333\"\n fontsize=10;\n ]\n\n edge [\n color = \"#e6e6e6\",\n fontcolor = \"#e6e6e6\"\n fontsize=10;\n ]\n\n rankdir=LR;\n node [shape=box, style=filled, color=lightblue];\n\n user_send [label=\"User Sends T1\"];\n server_receive [label=\"Server Receives T1\"];\n server_send [label=\"Server Sends T2\"];\n user_receive [label=\"User Receives T2\"];\n user_compute [label=\"User Computes Time\"];\n\n user_send -> server_receive [label=\"T1 (Request)\"];\n server_receive -> server_send [label=\"T1 received by server\"];\n server_send -> user_receive [label=\"T2 sent by server\"];\n user_receive -> user_compute [label=\"T2 received by user\"];\n user_compute -> user_send [label=\"Computed Time: T3 = T2 + (deltaT2 - deltaT1)/2\"];\n}\n```\n\nTime Synchronization Protocol (TSP) participants can assume either a server role or a client role. The server role is responsible for listening for incoming time synchronization requests from clients and replying appropriately. The client role is responsible for sending \"Ping\" messages to the server and listening for \"Pong\" replies to estimate the offset between the server and client time bases.\n\nAll time values shall use units of microseconds. The epoch of the time base this is measured against is unspecified.\n\nClients shall periodically (e.g. every few seconds) send, in a manner that minimizes transmission delays, a **TSP Ping Message** that contains the client's current local time.\n\nWhen the server receives a **TSP Ping Message** from any client, it shall respond to the client, in a manner that minimizes transmission delays, with a **TSP Pong message** encoding a timestamp of its (the server's) current local time (in microseconds), and the client-provided data value.\n\nWhen the client receives a **TSP Pong Message** from the server, it shall verify that the `Client Local Time` corresponds to the currently in-flight TSP Ping message; if not, it shall drop this packet. The round trip time (RTT) shall be computed from the delta between the message's data value and the current local time. If the RTT is less than that from previous measurements, the client shall use the timestamp in the message plus ½ the RTT as the server time equivalent to the current local time, and use this equivalence to compute server time base timestamps from local time for future messages.\n\n## Transport\n\nCommunication between server and clients shall occur over the User Datagram Protocol (UDP) Port 5810.\n\n## Message Format\n\nThe message format forgoes CRCs (as these are provided by the Ethernet physical layer) or packet delineation (as our packets are assumed be under the network MTU). **TSP Ping** and **TSP Pong** messages shall be encoded in a manor compatible with a WPILib packed struct with respect to byte alignment and endianness.\n\n### TSP Ping\n\n| Offset | Format | Data | Notes |\n| ------ | ------ | ---- | ----- |\n| 0 | uint8 | Protocol version | This field shall always set to 1 (0b1) for TSP Version 1. |\n| 1 | uint8 | Message ID | This field shall always be set to 1 (0b1). |\n| 2 | uint64 | Client Local Time | The client's local time value, at the time this Ping message was sent. |\n\n### TSP Pong\n\n| Offset | Format | Data | Notes |\n| ------ | ------ | ---- | ----- |\n| 0 | uint8 | Protocol version | This field shall always set to 1 (0b1) for TSP Version 1.\n| 1 | uint8 | Message ID | This field shall always be set to 2 (0b2).\n| 2 | uint64 | Client Local Time | The client's local time value from the Ping message that this Pong is generated in response to.\n| 10 | uint64 | Server Local Time | The current time at the server, at the time this Pong message was sent.\n\n\n## Optional Protocol Extensions\n\nClients may publish statistics to NetworkTables. If they do, they shall publish to a key that is globally unique per participant in the Time Synchronization network. If a client implements this, it shall provide the following publishers:\n\n| Key | Type | Notes |\n| ------ | ------ | ---- |\n| offset_us | Integer | The time offset that, when added to the client's local clock, provides server time |\n| ping_tx_count | Integer | The total number of TSP Ping packets transmitted |\n| ping_rx_count | Integer | The total number of TSP Ping packets received |\n| pong_rx_time_us | Integer | The time, in client local time, that the last pong was received |\n| rtt2_us | Integer | The time in us from last complete (ping transmission to pong reception) |\n\nPhotonVision has chosen to publish to the sub-table `/photonvision/.timesync/{DEVICE_HOSTNAME}`. Future implementations of this protocol may decide to implement this as a structured data type.\n\n## Wireshark Dissector\n\n\n\nA [WireShark dissector](https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/devTools/photon.lua) created for Wireshark ~=4.6 can be used to inspect Time Syncronization messages. Copy the dissector to your Wireshark plugin directory (for me, that's `C:\\Users\\Me\\AppData\\Roaming\\Wireshark\\plugins`), and open the capture. Because TSP uses UDP Unicast, data must be collected on the coprocessor or robot processor using a command similar to:\n\n```\nsudo tcpdump -i any port 5810 -w tsp_capture.pcap\n```\n",
+ "content_preview": "# Time Synchronization Protocol Specification, Version 1.0\n\nProtocol Revision 1.0, 08/25/2024\n\n## Background\n\nIn a distributed compute environment like robots, time synchronization between computers is increasingly important."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/contributing/design-descriptions/index.html",
- "title": "Software Architecture Design Descriptions",
- "section": "Contributing",
+ "url": "https://docs.photonvision.org/en/latest/docs/pipelines/input.html",
+ "title": "Camera Tuning / Input",
+ "section": "Pipelines",
"language": "All",
- "content": "# Software Architecture Design Descriptions\n\n```{toctree}\n:maxdepth: 1\nimage-rotation\ntime-sync\ncamera-matching\ne2e-latency\n```\n",
- "content_preview": "# Software Architecture Design Descriptions\n\n```{toctree}\n:maxdepth: 1\nimage-rotation\ntime-sync\ncamera-matching\ne2e-latency\n```\n"
+ "content": "# Camera Tuning / Input\n\nPhotonVision's \"Input\" tab contains settings that affect the image captured by the currently selected camera. This includes camera exposure and brightness, as well as resolution and orientation.\n\n## Resolution\n\nResolution changes the resolution of the image captured. While higher resolutions are often more accurate than lower resolutions, they also run at a slower update rate.\n\nWhen using the reflective/colored shape pipeline, detection should be run as low of a resolution as possible as you are only trying to detect simple contours (essentially colored blobs).\n\nWhen using the AprilTag pipeline, you should try to use as high of a resolution as you can while still maintaining a reasonable FPS measurement. This is because higher resolution allows you to detect tags with higher accuracy and from larger distances.\n\n## Exposure and brightness\n\nCamera exposure and brightness control how bright the captured image will be, although they function differently. Camera exposure changes how long the camera shutter lets in light, which changes the overall brightness of the captured image. This is in contrast to brightness, which is a post-processing effect that boosts the overall brightness of the image at the cost of desaturating colors (making colors look less distinct).\n\n:::{important}\nFor all pipelines, exposure time should be set as low as possible while still allowing for the target to be reliably tracked. This allows for faster processing as decreasing exposure will increase your camera FPS.\n:::\n\nFor reflective pipelines, after adjusting exposure and brightness, the target should be lit green (or the color of the vision tracking LEDs used). The more distinct the color of the target, the more likely it will be tracked reliably.\n\n:::{note}\nUnlike with retroreflective tape, AprilTag tracking is not very dependent on lighting consistency. If you have trouble detecting tags due to low light, you may want to try increasing exposure, but this will likely decrease your achievable framerate.\n:::\n\n### AprilTags and Motion Blur\n\nFor AprilTag pipelines, your goal is to reduce the \"motion blur\" as much as possible. Motion blur is the visual streaking/smearing on the camera stream as a result of movement of the camera or object of focus. You want to mitigate this as much as possible because your robot is constantly moving and you want to be able to read as many tags as you possibly can. The possible solutions to this include:\n\n1. Cranking your exposure as low as it goes and increasing your gain/brightness. This will decrease the effects of motion blur and increase FPS.\n2. Using a global shutter (as opposed to rolling shutter) camera. This should eliminate most, if not all motion blur.\n3. Only rely on tags when not moving.\n\n```{image} images/motionblur.gif\n:align: center\n```\n\n## Orientation\n\nOrientation can be used to rotate the image prior to vision processing. This can be useful for cases where the camera is not oriented parallel to the ground. Do note that this operation can in some cases significantly reduce FPS.\n\n## Stream Resolution\n\nThis changes the resolution which is used to stream frames from PhotonVision. This does not change the resolution used to perform vision processing. This is useful to reduce bandwidth consumption on the field. In some high-resolution cases, decreasing stream resolution can increase processing FPS.\n",
+ "content_preview": "# Camera Tuning / Input\n\nPhotonVision's \"Input\" tab contains settings that affect the image captured by the currently selected camera. This includes camera exposure and brightness, as well as resolution and orientation.\n\n## Resolution\n\nResolution changes the resolution of the image captured."
},
{
"url": "https://docs.photonvision.org/en/latest/docs/advanced-installation/sw_install/romi.html",
@@ -275,54 +91,6 @@
"content": "# Romi Installation\n\nThe [Romi](https://docs.wpilib.org/en/latest/docs/romi-robot/index.html) is a small robot that can be controlled with the WPILib software. The main controller is a Raspberry Pi that must be imaged with [WPILibPi](https://docs.wpilib.org/en/latest/docs/romi-robot/imaging-romi.html) .\n\n## Installation\n\nThe WPILibPi image includes FRCVision, which reserves USB cameras; to use PhotonVision, we need to edit the `/home/pi/runCamera` script to disable it. First we will need to make the file system writeable; the easiest way to do this is to go to `10.0.0.2` and choose \"Writable\" at the top.\n\nSSH into the Raspberry Pi (using Windows command line, or a tool like [Putty](https://www.chiark.greenend.org.uk/~sgtatham/putty/) ) at the Romi's default address `10.0.0.2`. The default user is `pi`, and the password is `raspberry`.\n\n:::.. The following paragraph can be restored when WPILibPi becomes compatible with the current version of PhotonVision.\n:::.. Follow the process for installing PhotonVision on {ref}`\"Other Debian-Based Co-Processor Installation\" `. As it mentions, this will require an internet connection so connecting the Raspberry Pi to an internet-connected router via an Ethernet cable will be the easiest solution. The pi must remain writable while you are following these steps!\n\n:::..Temporary instructions explaining how to install the older version of PhotonVision on a Romi. Remove when no longer needed.\n:::{attention}\nThe version of WPILibPi for the Romi is 2023.2.1, which is not compatible with the current version of PhotonVision. **If you are using WPILibPi 2023.2.1 on your Romi, you must install PhotonVision v2023.4.2 or earlier!**\n\nTo install a compatible version of PhotonVision, enter these commands in the SSH terminal connected to the Raspberry Pi. This will download and run the install script, which will install PhotonVision on your Raspberry Pi and configure it to run at startup.\n\n```bash\n$ wget https://git.io/JJrEP -O install.sh\n$ sudo chmod +x install.sh\n$ sudo ./install.sh -v v2023.4.2\n```\nThe install script requires an internet connection, so connecting the Raspberry Pi to an internet-connected router via an Ethernet cable will be the easiest solution. The pi must remain writable while you are following these steps!\n:::\n:::..End of temporary instructions.\n\nNext, from the SSH terminal, run `sudo nano /home/pi/runCamera` then arrow down to the start of the exec line and press \"Enter\" to add a new line. Then add `#` before the exec command to comment it out. Then, arrow up to the new line and type `sleep 10000`. Hit \"Ctrl + O\" and then \"Enter\" to save the file. Finally press \"Ctrl + X\" to exit nano. Now, reboot the Romi by typing `sudo reboot now`.\n\n```{image} images/nano.png\n\n```\n\nAfter the Romi reboots, you should be able to open the PhotonVision UI at: [`http://10.0.0.2:5800/`](http://10.0.0.2:5800/). From here, you can adjust settings and configure {ref}`Pipelines `.\n\n:::{warning}\nIn order for settings, logs, etc. to be saved / take effect, ensure that PhotonVision is in writable mode.\n:::\n\n:::{attention}\nWhen using an older version of PhotonVision, the user interface and features may be different than what appears in the online documentation. The [Documentation](http://10.0.0.2:5800/#/docs) link in the User Interface will open a bundled version of the documentation that matches the PhotonVision version running on your coprocessor.\n:::\n",
"content_preview": "# Romi Installation\n\nThe [Romi](https://docs.wpilib.org/en/latest/docs/romi-robot/index.html) is a small robot that can be controlled with the WPILib software."
},
- {
- "url": "https://docs.photonvision.org/en/latest/docs/pipelines/output.html",
- "title": "Output",
- "section": "Pipelines",
- "language": "All",
- "content": "# Output\n\nThe output card contains sections for target manipulation and offset modes.\n\n## Target Manipulation\n\nIn this section, the Target Offset Point changes where the \"center\" of the target is. This can be useful if the pitch/yaw of the middle of the top edge of the target is desired, rather than the center of mass of the target. The \"top\"/\"bottom\"/\"left\"/\"right\" of the target are defined by the Target Orientation selection. For example, a 400x200px target in landscape mode would have the \"top\" offset point located at the middle of the uppermost long edge of the target, while in portrait mode the \"top\" offset point would be located in the middle of the topmost short edge (in this case, either the left or right sides).\n\nThis section also includes a switch to enable processing and sending multiple targets, up to 5, simultaneously. This information is available through PhotonLib. Note that the {code}`GetPitch`/{code}`GetYaw` methods will report the pitch/yaw of the \"best\" (lowest indexed) target.\n\n```{raw} html\n\n \n Your browser does not support the video tag.\n \n```\n\n## Robot Offset\n\nPhotonVision offers both single and dual point offset modes. In single point mode, the \"Take Point\" button will set the crosshair location to the center of the current \"best\" target.\n\nIn dual point mode, two snapshots are required. Take one snapshot with the target far away, and the other with the target closer. The position of the crosshair will be linearly interpolated between these two points based on the area of the current \"best\" target. This might be useful if single point is not accurate across the range of the tracking distance, or for significantly offset cameras.\n",
- "content_preview": "# Output\n\nThe output card contains sections for target manipulation and offset modes.\n\n## Target Manipulation\n\nIn this section, the Target Offset Point changes where the \"center\" of the target is."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/integration/index.html",
- "title": "Robot Integration - PhotonVision Docs",
- "section": "Robot Integration",
- "language": "All",
- "content": "Robot Integration Vision - Robot Integration Background Vision Processing’s Purpose Simple Strategies Knowledge and Equipment Needed Angle Alignment Adding Range Alignment Advanced Strategies Knowledge and Equipment Needed Robot Poses from the Camera Field-Relative Pose Estimation I have a Pose Estimate, Now What?",
- "content_preview": "Robot Integration Vision - Robot Integration Background Vision Processing’s Purpose Simple Strategies Knowledge and Equipment Needed Angle Alignment Adding Range Alignment Advanced Strategies Knowledge and Equipment Needed Robot Poses from the Camera Field-Relative Pose Estimation I have a Pose..."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/additional-resources/best-practices.html",
- "title": "Best Practices For Competition",
- "section": "Additional Resources",
- "language": "All",
- "content": "# Best Practices For Competition\n\n## Before Competition\n\n- Ensure you have spares of the relevant electronics if you can afford it (switch, coprocessor, cameras, etc.).\n- Stay on the latest version of PhotonVision until you have tested your full robot system to be functional.\n- Some time before the competition, lock down the version you are using and do not upgrade unless you encounter a critical bug.\n- Have a copy of the installation image for the version you are using on your programming laptop, in case re-imaging (without internet) is needed.\n- Extensively test at your home setup. Practice tuning from scratch under different lighting conditions.\n- Confirm you have followed all the recommendations under the {ref}`Networking` documentation (network switch and static IP).\n- Only use high quality ethernet cables that have been rigorously tested.\n\n## Camera Streaming\n- All camera streams are published under the NetworkTables table `CameraPublisher`.\n- The only subtable under `CameraPublisher` that will work for viewing a driver mode camera stream is the one that contains `Output` in the name.\n- To view a camera stream in a dashboard, drag the correct subtable from the NetworkTables tree into your dashboard.\n- Use the latest driver dashboard recommended by [WPILib](https://docs.wpilib.org/en/stable/docs/software/dashboards/dashboard-intro.html) on your driver station laptop.\n\n## During the Competition\n\n- Use the field calibration time given at the start of the event:\n - Bring your robot to the field at the allotted time.\n - Make sure the field has match-accurate lighting conditions active.\n - Turn on your robot and pull up the dashboard on your driver station.\n - Point your robot at the targets and ensure you get a consistent tracking (you hold one targets consistently, the ceiling lights aren't detected, etc.).\n - If you have problems with your pipeline, retune the pipeline following the {ref}`camera tuning ` documentation.\n - Move the robot close, far, angled, and around the field to ensure no extra targets are found.\n - Monitor camera feeds during a practice match to ensure everything is working correctly.\n- After field calibration, use the \"Export Settings\" button in the \"Settings\" page to create a backup.\n - Do this for each coprocessor on your robot that runs PhotonVision, and name your exports with meaningful names.\n - This will contain camera information/calibration, pipeline information, network settings, etc.\n - In the event of software/hardware failures (IE lost SD Card, broken device), you can then use the \"Import Settings\" button and select \"All Settings\" to restore your settings.\n - This effectively works as a snapshot of your PhotonVision data that can be restored at any point.\n- Before every match:\n - Check the ethernet and USB connectors are seated fully.\n - Close streaming dashboards when you don't need them to reduce bandwidth.\n- Stream at as low of a resolution as possible while still detecting AprilTags to stay within field bandwidth limits.\n",
- "content_preview": "# Best Practices For Competition\n\n## Before Competition\n\n- Ensure you have spares of the relevant electronics if you can afford it (switch, coprocessor, cameras, etc.).\n- Stay on the latest version of PhotonVision until you have tested your full robot system to be functional.\n- Some time before the..."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/apriltag-pipelines/index.html",
- "title": "AprilTag Detection",
- "section": "AprilTag Detection",
- "language": "All",
- "content": "# AprilTag Detection\n\n```{toctree}\nabout-apriltags\ndetector-types\n2D-tracking-tuning\n3D-tracking\nmultitag\ncoordinate-systems\n```\n",
- "content_preview": "# AprilTag Detection\n\n```{toctree}\nabout-apriltags\ndetector-types\n2D-tracking-tuning\n3D-tracking\nmultitag\ncoordinate-systems\n```\n"
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/contributing/building-docs.html",
- "title": "Building the PhotonVision Documentation",
- "section": "Contributing",
- "language": "All",
- "content": "# Building the PhotonVision Documentation\n\nTo build the PhotonVision documentation, you will require [Git](https://git-scm.com) and [Python 3.6 or greater](https://www.python.org).\n\n## Cloning the Documentation Repository\n\nDocumentation lives within the main PhotonVision repository within the `docs` sub-folder. If you are planning on contributing, it is recommended to create a fork of the [PhotonVision repository](https://github.com/PhotonVision/photonvision). To clone this fork, run the following command in a terminal window:\n\n`git clone https://github.com/[your username]/photonvision`\n\n## Installing Python Dependencies\n\nYou must install a set of Python dependencies in order to build the documentation. To do so, you can run the following command in the docs sub-folder:\n\n`~/photonvision/docs$ python -m pip install -r requirements.txt`\n\n## Building the Documentation\n\nIn order to build the documentation, you can run the following command in the docs sub-folder. This will automatically build docs every time a file changes, and serves them locally at `localhost:8000` by default.\n\n`~/photonvision/docs$ sphinx-autobuild --open-browser source source/_build/html`\n\n## Opening the Documentation\n\nThe built documentation is located at `docs/build/html/index.html` relative to the root project directory, or can be accessed via the local web server if using sphinx-autobuild.\n\n## Docs Builds on Pull Requests\n\nPre-merge builds of docs can be found at: `https://photonvision-docs--PRNUMBER.org.readthedocs.build/en/PRNUMBER/index.html`. These docs are republished on every commit to a pull request made to PhotonVision/photonvision-docs. For example, PR 325 would have pre-merge documentation published to `https://photonvision-docs--325.org.readthedocs.build/en/325/index.html`. Additionally, the pull request will have a link directly to the pre-release build of the docs. This build only runs when there is a change to files in the docs sub-folder.\n\n## Style Guide\n\nPhotonVision follows the frc-docs style guide which can be found [here](https://docs.wpilib.org/en/stable/docs/contributing/style-guide.html). In order to run the linter locally (which builds on doc8 and checks for compliance with the style guide), follow the instructions [on GitHub](https://github.com/wpilibsuite/ohnoyoudidnt).\n",
- "content_preview": "# Building the PhotonVision Documentation\n\nTo build the PhotonVision documentation, you will require [Git](https://git-scm.com) and [Python 3.6 or greater](https://www.python.org).\n\n## Cloning the Documentation Repository\n\nDocumentation lives within the main PhotonVision repository within the..."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/advanced-installation/sw_install/windows-pc.html",
- "title": "Windows PC Installation",
- "section": "General",
- "language": "All",
- "content": "# Windows PC Installation\n\nPhotonVision may be run on a Windows Desktop PC for basic testing and evaluation.\n\n:::{note}\nYou do not need to install PhotonVision on a Windows PC in order to access the webdashboard (assuming you are using an external coprocessor like a Raspberry Pi).\n:::\n\n## Install Bonjour\n\nBonjour provides more stable networking when using Windows PCs. Install [Bonjour here](https://support.apple.com/downloads/DL999/en_US/BonjourPSSetup.exe) before continuing to ensure a stable experience while using PhotonVision.\n\n## Installing Java\n\nPhotonVision requires a JDK installed and on the system path. **JDK 17 is needed.** You may already have it if you installed WPILib, but ensure that running `java -version` shows JDK 17. You will likely have to add WPILib's JDK to JAVA_HOME and the JDK's `bin` directory to PATH. If you do not have a JDK 17 install, [download and install it from here.](https://adoptium.net/temurin/releases?version=17)\n\n## Downloading the Latest Stable Release of PhotonVision\n\nGo to the [GitHub releases page](https://github.com/PhotonVision/photonvision/releases) and download the winx64.jar file.\n\n## Running PhotonVision\n\nTo run PhotonVision, open a terminal window of your choice and run the following command:\n\n```\n> java -jar C:\\path\\to\\photonvision\\NAME OF JAR FILE GOES HERE.jar\n```\n\nIf your computer has a compatible webcam connected, PhotonVision should startup without any error messages. If there are error messages, your webcam isn't supported or another issue has occurred. If it is the latter, please open an issue on the [PhotonVision issues page](https://github.com/PhotonVision/photonvision/issues).\n\n:::{warning}\nUsing an integrated laptop camera may cause issues when trying to run PhotonVision. If you are unable to run PhotonVision on a laptop with an integrated camera, try disabling the camera's driver in Windows Device Manager.\n:::\n\n## Accessing the PhotonVision Interface\n\nOnce the Java backend is up and running, you can access the main vision interface by navigating to `localhost:5800` inside your browser.\n",
- "content_preview": "# Windows PC Installation\n\nPhotonVision may be run on a Windows Desktop PC for basic testing and evaluation.\n\n:::{note}\nYou do not need to install PhotonVision on a Windows PC in order to access the webdashboard (assuming you are using an external coprocessor like a Raspberry Pi).\n:::\n\n## Install..."
- },
{
"url": "https://docs.photonvision.org/en/latest/docs/advanced-installation/sw_install/advanced-cmd.html",
"title": "Advanced Command Line Usage",
@@ -332,164 +100,108 @@
"content_preview": "# Advanced Command Line Usage\n\nPhotonVision exposes some command line options which may be useful for customizing execution on Debian-based installations.\n\n## Running a JAR File\n\nAssuming `java` has been installed, and the appropriate environment variables have been set upon installation (a package..."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/pipelines/index.html",
- "title": "Pipelines - PhotonVision Docs",
- "section": "Pipelines",
- "language": "All",
- "content": "Pipelines About Pipelines What is a pipeline? Types of Pipelines AprilTag / ArUco Object Detection Driver Mode Colored Shape Reflective Note About Multiple Cameras and Pipelines Pipeline Configuration AprilTag / ArUco Pipelines Object Detection Pipelines Reflective and Colored Shape Pipelines Camera Tuning / Input Resolution Exposure and brightness AprilTags and Motion Blur Orientation Stream Resolution Output Target Manipulation Robot Offset",
- "content_preview": "Pipelines About Pipelines What is a pipeline? Types of Pipelines AprilTag / ArUco Object Detection Driver Mode Colored Shape Reflective Note About Multiple Cameras and Pipelines Pipeline Configuration AprilTag / ArUco Pipelines Object Detection Pipelines Reflective and Colored Shape Pipelines..."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/quick-start/camera-calibration.html",
- "title": "Camera Calibration",
- "section": "Getting Started",
- "language": "All",
- "content": "# Camera Calibration\n\n:::{important}\nIn order to detect AprilTags and use 3D mode, your camera must be calibrated at the desired resolution! Inaccurate calibration will lead to poor performance.\n:::\n\nIf you’re not using cameras in 3D mode, calibration is optional, but it can still offer benefits. Calibrating cameras helps refine the pitch and yaw values, leading to more accurate positional data in every mode. {ref}`For a more in-depth view`.\n\n## Print the Calibration Target\n\n- Downloaded from our [demo site](https://demo.photonvision.org/#/cameras), or directly from your coprocessors cameras tab.\n- Use the ChArUco calibration board:\n - Board Type: ChAruCo\n - Tag Family: 4x4\n - Pattern Spacing: 1.00in\n - Marker Size: 0.75in\n - Board Height : 8\n - Board Width : 8\n\n## Prepare the Calibration Target\n\n- Measure Accurately: Use calipers to measure the actual size of the squares and markers. Accurate measurements are crucial for effective calibration.\n- Ensure Flatness: The calibration board must be perfectly flat, without any wrinkles or bends, to avoid introducing errors into the calibration process.\n\n## Calibrate your Camera\n\n- Take lots of photos: It's recommended to capture more than 50 images to properly calibrate your camera for accuracy. 12 is the bare minimum and may not provide good results.\n- Other Tips\n - Move the board not the camera.\n - Take photos of lots of angles: The more angles the more better (up to 45 deg).\n - A couple of up close images is good.\n - Cover the entire cameras fov.\n - Avoid images with the board facing straight towards the camera.\n",
- "content_preview": "# Camera Calibration\n\n:::{important}\nIn order to detect AprilTags and use 3D mode, your camera must be calibrated at the desired resolution! Inaccurate calibration will lead to poor performance.\n:::\n\nIf you’re not using cameras in 3D mode, calibration is optional, but it can still offer benefits."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/programming/photonlib/using-target-data.html",
- "title": "Using Target Data",
- "section": "PhotonLib",
- "language": "All",
- "content": "# Using Target Data\n\nA `PhotonUtils` class with helpful common calculations is included within `PhotonLib` to aid teams in using AprilTag data in order to get positional information on the field. This class contains two methods, `calculateDistanceToTargetMeters()`/`CalculateDistanceToTarget()` and `estimateTargetTranslation2d()`/`EstimateTargetTranslation()` (Java and C++ respectively).\n\n## Estimating Field Relative Pose with AprilTags\n\n`estimateFieldToRobotAprilTag(Transform3d cameraToTarget, Pose3d fieldRelativeTagPose, Transform3d cameraToRobot)` returns your robot's `Pose3d` on the field using the pose of the AprilTag relative to the camera, pose of the AprilTag relative to the field, and the transform from the camera to the origin of the robot.\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n // Calculate robot's field relative pose\n if (aprilTagFieldLayout.getTagPose(target.getFiducialId()).isPresent()) {\n Pose3d robotPose = PhotonUtils.estimateFieldToRobotAprilTag(target.getBestCameraToTarget(), aprilTagFieldLayout.getTagPose(target.getFiducialId()).get(), cameraToRobot);\n }\n .. code-block:: c++\n\n //TODO\n\n .. code-block:: python\n\n # Coming Soon!\n```\n\n## Estimating Field Relative Pose (Traditional)\n\nYou can get your robot's `Pose2D` on the field using various camera data, target yaw, gyro angle, target pose, and camera position. This method estimates the target's relative position using `estimateCameraToTargetTranslation` (which uses pitch and yaw to estimate range and heading), and the robot's gyro to estimate the rotation of the target.\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n // Calculate robot's field relative pose\n Pose2D robotPose = PhotonUtils.estimateFieldToRobot(\n kCameraHeight, kTargetHeight, kCameraPitch, kTargetPitch, Rotation2d.fromDegrees(-target.getYaw()), gyro.getRotation2d(), targetPose, cameraToRobot);\n\n .. code-block:: c++\n\n // Calculate robot's field relative pose\n frc::Pose2D robotPose = photonlib::EstimateFieldToRobot(\n kCameraHeight, kTargetHeight, kCameraPitch, kTargetPitch, frc::Rotation2d(units::degree_t(-target.GetYaw())), frc::Rotation2d(units::degree_t(gyro.GetRotation2d)), targetPose, cameraToRobot);\n\n .. code-block:: python\n\n # Coming Soon!\n\n```\n\n## Calculating Distance to Target\n\nIf your camera is at a fixed height on your robot and the height of the target is fixed, you can calculate the distance to the target based on your camera's pitch and the pitch to the target.\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // TODO\n\n .. code-block:: c++\n\n // TODO\n\n .. code-block:: python\n\n # Coming Soon!\n\n```\n\n:::{note}\nThe C++ version of PhotonLib uses the Units library. For more information, see [here](https://docs.wpilib.org/en/stable/docs/software/basic-programming/cpp-units.html).\n:::\n\n## Calculating Distance Between Two Poses\n\n`getDistanceToPose(Pose2d robotPose, Pose2d targetPose)` allows you to calculate the distance between two poses. This is useful when using AprilTags, given that there may not be an AprilTag directly on the target.\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n double distanceToTarget = PhotonUtils.getDistanceToPose(robotPose, targetPose);\n\n .. code-block:: c++\n\n //TODO\n\n .. code-block:: python\n\n # Coming Soon!\n```\n\n## Estimating Camera Translation to Target\n\nYou can get a [translation](https://docs.wpilib.org/en/latest/docs/software/advanced-controls/geometry/pose.html#translation) to the target based on the distance to the target (calculated above) and angle to the target (yaw).\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n // Calculate a translation from the camera to the target.\n Translation2d translation = PhotonUtils.estimateCameraToTargetTranslation(\n distanceMeters, Rotation2d.fromDegrees(-target.getYaw()));\n\n .. code-block:: c++\n\n // Calculate a translation from the camera to the target.\n frc::Translation2d translation = photonlib::PhotonUtils::EstimateCameraToTargetTranslation(\n distance, frc::Rotation2d(units::degree_t(-target.GetYaw())));\n\n .. code-block:: python\n\n # Coming Soon!\n\n```\n\n:::{note}\nWe are negating the yaw from the camera from CV (computer vision) conventions to standard mathematical conventions. In standard mathematical conventions, as you turn counter-clockwise, angles become more positive.\n:::\n\n## Getting the Yaw To a Pose\n\n`getYawToPose(Pose2d robotPose, Pose2d targetPose)` returns the `Rotation2d` between your robot and a target. This is useful when turning towards an arbitrary target on the field (ex. the center of the hub in 2022).\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n Rotation2d targetYaw = PhotonUtils.getYawToPose(robotPose, targetPose);\n .. code-block:: c++\n\n //TODO\n\n .. code-block:: python\n\n # Coming Soon!\n```\n",
- "content_preview": "# Using Target Data\n\nA `PhotonUtils` class with helpful common calculations is included within `PhotonLib` to aid teams in using AprilTag data in order to get positional information on the field."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/advanced-installation/sw_install/linux-pc.html",
- "title": "Linux PC Installation",
- "section": "General",
- "language": "All",
- "content": "# Linux PC Installation\n\nPhotonVision may be run on a Debian-based Linux Desktop PC for basic testing and evaluation.\n\n:::{note}\nYou do not need to install PhotonVision on a Windows PC in order to access the webdashboard (assuming you are using an external coprocessor like a Raspberry Pi).\n:::\n\n## Installing Java\n\nPhotonVision requires a JDK installed and on the system path. JDK 17 is needed (different versions will not work). If you don't have JDK 17 already, run the following to install it:\n\n```\n$ sudo apt-get install openjdk-17-jdk\n```\n\n:::{warning}\nUsing a JDK other than JDK17 will cause issues when running PhotonVision and is not supported.\n:::\n\n## Downloading the Latest Stable Release of PhotonVision\n\nGo to the [GitHub releases page](https://github.com/PhotonVision/photonvision/releases) and download the relevant .jar file for your coprocessor.\n\n:::{note}\nIf your coprocessor has a 64 bit ARM based CPU architecture (OrangePi, Raspberry Pi, etc.), download the LinuxArm64.jar file.\n\nIf your coprocessor has an 64 bit x86 based CPU architecture (Mini PC, laptop, etc.), download the Linuxx64.jar file.\n:::\n\n:::{warning}\nBe careful to pick the latest stable release. \"Draft\" or \"Pre-Release\" versions are not stable and often have bugs.\n:::\n\n## Running PhotonVision\n\nTo run PhotonVision, open a terminal window of your choice and run the following command:\n\n```\n$ java -jar /path/to/photonvision/photonvision-xxx.jar\n```\n\nIf your computer has a compatible webcam connected, PhotonVision should startup without any error messages. If there are error messages, your webcam isn't supported or another issue has occurred. If it is the latter, please open an issue on the [PhotonVision issues page](https://github.com/PhotonVision/photonvision/issues).\n\n## Accessing the PhotonVision Interface\n\nOnce the Java backend is up and running, you can access the main vision interface by navigating to `localhost:5800` inside your browser.\n",
- "content_preview": "# Linux PC Installation\n\nPhotonVision may be run on a Debian-based Linux Desktop PC for basic testing and evaluation.\n\n:::{note}\nYou do not need to install PhotonVision on a Windows PC in order to access the webdashboard (assuming you are using an external coprocessor like a Raspberry Pi).\n:::\n\n##..."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/objectDetection/index.html",
- "title": "Object Detection - PhotonVision Docs",
- "section": "Object Detection",
- "language": "All",
- "content": "Object Detection About Object Detection How does it work? Tracking Objects Tuning and Filtering Letterboxing Custom Models Training Custom Models Managing Custom Models Orange Pi 5 (and variants) Object Detection How it works Supported models Converting Custom Models Rubik Pi 3 Object Detection How it works Supported models Converting Custom Models Benchmarking",
- "content_preview": "Object Detection About Object Detection How does it work? Tracking Objects Tuning and Filtering Letterboxing Custom Models Training Custom Models Managing Custom Models Orange Pi 5 (and variants) Object Detection How it works Supported models Converting Custom Models Rubik Pi 3 Object Detection..."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/quick-start/camera-matching.html",
- "title": "Camera Matching",
- "section": "Getting Started",
- "language": "All",
- "content": "# Camera Matching\n\n## Activating and Deactivating Cameras\n\nWhen you first plug in a camera, it will be detected and added to the list of cameras with the \"Unassigned\" status, as shown below. You can press the \"Activate\" button to enable PhotonVision to use the camera.\n\n```{image} images/camera-matching/unassigned-camera.png\n:scale: 50%\n```\n\nIf a camera has been activated in the past, it will be listed as \"Deactivated\" in the camera list. You can press the \"Activate\" button to enable PhotonVision to use the camera.\n\n```{image} images/camera-matching/deactivated-camera.png\n:scale: 50%\n```\n\nOnce a camera is activated, it will be listed as \"Active\" in the camera list. You can press the \"Deactivate\" button to stop PhotonVision from using the camera.\n\n```{image} images/camera-matching/activated-camera.png\n:scale: 50%\n```\n\n## Deleting Cameras\n\nIf you want to remove a camera from the list, you can press the delete button. This will clear all settings for that particular camera, including the calibration data and any other settings you have configured. It is recommended to make a backup of the camera's settings before deleting it, as this action cannot be undone.\n\n## Matching Cameras\n\nWhen you plug in a camera, PhotonVision will attempt to match it to a previously configured camera based on the physical USB port it is connected to. If you plug another camera into that port, the cameras will have a \"Camera Mismatch\" status, indicating that the camera is not recognized as the one that was previously configured.\n\nAdditionally, pressing on the Details button will show you the details of the camera mismatch, allowing you to compare the current camera with the previously configured camera.\n\n```{image} images/camera-matching/camera-mismatch-details.png\n:scale: 50%\n```\n\n```{note}\nCamera matching is based on the USB ports on the device. If you unplug a camera and plug it into a different port, PhotonVision will attempt to use settings from the camera that was previously configured in that port, causing unexpected behavior.\n```\n\nTo resolve the camera mismatch, you should ensure each camera is plugged into the same port that you configured it in.\n",
- "content_preview": "# Camera Matching\n\n## Activating and Deactivating Cameras\n\nWhen you first plug in a camera, it will be detected and added to the list of cameras with the \"Unassigned\" status, as shown below."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/programming/photonlib/getting-target-data.html",
- "title": "Getting Target Data",
- "section": "PhotonLib",
- "language": "All",
- "content": "# Getting Target Data\n\n## Constructing a PhotonCamera\n\n### What is a PhotonCamera?\n\n`PhotonCamera` is a class in PhotonLib that allows a user to interact with one camera that is connected to hardware that is running PhotonVision. Through this class, users can retrieve yaw, pitch, roll, robot-relative pose, latency, and a wealth of other information.\n\nThe `PhotonCamera` class has two constructors: one that takes a `NetworkTable` and another that takes in the name of the network table that PhotonVision is broadcasting information over. For ease of use, it is recommended to use the latter. The name of the NetworkTable (for the string constructor) should be the same as the camera's nickname (from the PhotonVision UI).\n\n```{eval-rst}\n.. tab-set-code::\n\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/a3bcd3ac4f88acd4665371abc3073bdbe5effea8/photonlib-java-examples/src/main/java/org/photonlib/examples/aimattarget/Robot.java\n :language: java\n :lines: 51-52\n\n .. rli:: https://github.com/PhotonVision/photonvision/raw/a3bcd3ac4f88acd4665371abc3073bdbe5effea8/photonlib-cpp-examples/src/main/cpp/examples/aimattarget/include/Robot.h\n :language: c++\n :lines: 42-43\n\n .. code-block:: python\n\n # Change this to match the name of your camera as shown in the web ui\n self.camera = PhotonCamera(\"your_camera_name_here\")\n\n```\n\n:::{warning}\nTeams must have unique names for all of their cameras regardless of which coprocessor they are attached to.\n:::\n\n## Getting the Pipeline Result\n\n### What is a Photon Pipeline Result?\n\nA `PhotonPipelineResult` is a container that contains all information about currently detected targets from a `PhotonCamera`. You can retrieve the latest pipeline result using the PhotonCamera instance.\n\nUse the `getLatestResult()`/`GetLatestResult()` (Java and C++ respectively) to obtain the latest pipeline result. An advantage of using this method is that it returns a container with information that is guaranteed to be from the same timestamp. This is important if you are using this data for latency compensation or in an estimator.\n\n```{eval-rst}\n.. tab-set-code::\n\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/a3bcd3ac4f88acd4665371abc3073bdbe5effea8/photonlib-java-examples/src/main/java/org/photonlib/examples/aimattarget/Robot.java\n :language: java\n :lines: 79-80\n\n .. rli:: https://github.com/PhotonVision/photonvision/raw/a3bcd3ac4f88acd4665371abc3073bdbe5effea8/photonlib-cpp-examples/src/main/cpp/examples/aimattarget/cpp/Robot.cpp\n :language: c++\n :lines: 35-36\n\n .. code-block:: python\n\n # Query the latest result from PhotonVision\n result = self.camera.getLatestResult()\n\n\n```\n\n:::{note}\nUnlike other vision software solutions, using the latest result guarantees that all information is from the same timestamp. This is achievable because the PhotonVision backend sends a byte-packed string of data which is then deserialized by PhotonLib to get target data. For more information, check out the [PhotonLib source code](https://github.com/PhotonVision/photonvision/tree/main/photon-lib).\n:::\n\n## Checking for Existence of Targets\n\nEach pipeline result has a `hasTargets()`/`HasTargets()` (Java and C++ respectively) method to inform the user as to whether the result contains any targets.\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n // Check if the latest result has any targets.\n boolean hasTargets = result.hasTargets();\n\n .. code-block:: c++\n\n // Check if the latest result has any targets.\n bool hasTargets = result.HasTargets();\n\n .. code-block:: python\n\n # Check if the latest result has any targets.\n hasTargets = result.hasTargets()\n```\n\n:::{warning}\nIn Java/C++, You must _always_ check if the result has a target via `hasTargets()`/`HasTargets()` before getting targets or else you may get a null pointer exception. Further, you must use the same result in every subsequent call in that loop.\n:::\n\n## Getting a List of Targets\n\n### What is a Photon Tracked Target?\n\nA tracked target contains information about each target from a pipeline result. This information includes yaw, pitch, area, and robot relative pose.\n\nYou can get a list of tracked targets using the `getTargets()`/`GetTargets()` (Java and C++ respectively) method from a pipeline result.\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n // Get a list of currently tracked targets.\n List targets = result.getTargets();\n\n .. code-block:: c++\n\n // Get a list of currently tracked targets.\n wpi::ArrayRef targets = result.GetTargets();\n\n .. code-block:: python\n\n # Get a list of currently tracked targets.\n targets = result.getTargets()\n```\n\n## Getting the Best Target\n\nYou can get the {ref}`best target ` using `getBestTarget()`/`GetBestTarget()` (Java and C++ respectively) method from the pipeline result.\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n // Get the current best target.\n PhotonTrackedTarget target = result.getBestTarget();\n\n .. code-block:: c++\n\n // Get the current best target.\n photonlib::PhotonTrackedTarget target = result.GetBestTarget();\n\n\n .. code-block:: python\n\n # Coming Soon!\n\n```\n\n## Getting Data From A Target\n\n- double `getYaw()`/`GetYaw()`: The yaw of the target in degrees (positive left).\n- double `getPitch()`/`GetPitch()`: The pitch of the target in degrees (positive up).\n- double `getArea()`/`GetArea()`: The area (how much of the camera feed the bounding box takes up) as a percent (0-100).\n- double `getSkew()`/`GetSkew()`: The skew of the target in degrees (counter-clockwise positive).\n- double\\[\\] `getCorners()`/`GetCorners()`: The 4 corners of the minimum bounding box rectangle.\n- Transform2d `getCameraToTarget()`/`GetCameraToTarget()`: The camera to target transform. See [2d transform documentation here](https://docs.wpilib.org/en/latest/docs/software/advanced-controls/geometry/transformations.html#transform2d-and-twist2d).\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n // Get information from target.\n double yaw = target.getYaw();\n double pitch = target.getPitch();\n double area = target.getArea();\n double skew = target.getSkew();\n Transform2d pose = target.getCameraToTarget();\n List corners = target.getCorners();\n\n .. code-block:: c++\n\n // Get information from target.\n double yaw = target.GetYaw();\n double pitch = target.GetPitch();\n double area = target.GetArea();\n double skew = target.GetSkew();\n frc::Transform2d pose = target.GetCameraToTarget();\n wpi::SmallVector, 4> corners = target.GetCorners();\n\n .. code-block:: python\n\n # Get information from target.\n yaw = target.getYaw()\n pitch = target.getPitch()\n area = target.getArea()\n skew = target.getSkew()\n pose = target.getCameraToTarget()\n corners = target.getDetectedCorners()\n```\n\n## Getting AprilTag Data From A Target\n\n:::{note}\nAll of the data above (**except skew**) is available when using AprilTags.\n:::\n\n- int `getFiducialId()`/`GetFiducialId()`: The ID of the detected fiducial marker.\n- double `getPoseAmbiguity()`/`GetPoseAmbiguity()`: How ambiguous the pose of the target is (see below).\n- Transform3d `getBestCameraToTarget()`/`GetBestCameraToTarget()`: Get the transform that maps camera space (X = forward, Y = left, Z = up) to object/fiducial tag space (X forward, Y left, Z up) with the lowest reprojection error.\n- Transform3d `getAlternateCameraToTarget()`/`GetAlternateCameraToTarget()`: Get the transform that maps camera space (X = forward, Y = left, Z = up) to object/fiducial tag space (X forward, Y left, Z up) with the highest reprojection error.\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n // Get information from target.\n int targetID = target.getFiducialId();\n double poseAmbiguity = target.getPoseAmbiguity();\n Transform3d bestCameraToTarget = target.getBestCameraToTarget();\n Transform3d alternateCameraToTarget = target.getAlternateCameraToTarget();\n\n .. code-block:: c++\n\n // Get information from target.\n int targetID = target.GetFiducialId();\n double poseAmbiguity = target.GetPoseAmbiguity();\n frc::Transform3d bestCameraToTarget = target.getBestCameraToTarget();\n frc::Transform3d alternateCameraToTarget = target.getAlternateCameraToTarget();\n\n .. code-block:: python\n\n # Get information from target.\n targetID = target.getFiducialId()\n poseAmbiguity = target.getPoseAmbiguity()\n bestCameraToTarget = target.getBestCameraToTarget()\n alternateCameraToTarget = target.getAlternateCameraToTarget()\n```\n\n## Saving Pictures to File\n\nA `PhotonCamera` can save still images from the input or output video streams to file. This is useful for debugging what a camera is seeing while on the field and confirming targets are being identified properly.\n\nImages are stored within the PhotonVision configuration directory. Running the \"Export\" operation in the settings tab will download a .zip file which contains the image captures.\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // Capture pre-process camera stream image\n camera.takeInputSnapshot();\n\n // Capture post-process camera stream image\n camera.takeOutputSnapshot();\n\n .. code-block:: c++\n\n // Capture pre-process camera stream image\n camera.TakeInputSnapshot();\n\n // Capture post-process camera stream image\n camera.TakeOutputSnapshot();\n\n .. code-block:: python\n\n # Capture pre-process camera stream image\n camera.takeInputSnapshot()\n\n # Capture post-process camera stream image\n camera.takeOutputSnapshot()\n```\n\n:::{note}\nSaving images to file takes a bit of time and uses up disk space, so doing it frequently is not recommended. In general, the camera will save an image every 500ms. Calling these methods faster will not result in additional images. Consider tying image captures to a button press on the driver controller, or an appropriate point in an autonomous routine.\n:::\n",
- "content_preview": "# Getting Target Data\n\n## Constructing a PhotonCamera\n\n### What is a PhotonCamera?\n\n`PhotonCamera` is a class in PhotonLib that allows a user to interact with one camera that is connected to hardware that is running PhotonVision."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/index.html",
- "title": "Content",
- "section": "General",
+ "url": "https://docs.photonvision.org/en/latest/docs/troubleshooting/index.html",
+ "title": "Troubleshooting",
+ "section": "Troubleshooting",
"language": "All",
- "content": "```{image} assets/PhotonVision-Header-onWhite.png\n:alt: PhotonVision\n```\n\nWelcome to the official documentation of PhotonVision! PhotonVision is the free, fast, and easy-to-use vision processing solution for the _FIRST_ Robotics Competition. PhotonVision is designed to get vision working on your robot _quickly_, without the significant cost of other similar solutions. PhotonVision supports a variety of COTS hardware, including the Raspberry Pi 3, 4, and 5, the [SnakeEyes Pi hat](https://www.playingwithfusion.com/productview.php?pdid=133), and the Orange Pi 5.\n\n# Content\n\n```{eval-rst}\n.. grid:: 2\n\n .. grid-item-card:: Quick Start\n :link: docs/quick-start/index\n :link-type: doc\n\n Quick start to using Photonvision.\n\n .. grid-item-card:: Advanced Installation\n :link: docs/advanced-installation/index\n :link-type: doc\n\n Get started with installing PhotonVision on non-supported hardware.\n\n```\n\n```{eval-rst}\n.. grid:: 2\n\n .. grid-item-card:: Programming Reference and PhotonLib\n :link: docs/programming/index\n :link-type: doc\n\n Learn more about PhotonLib, our vendor dependency which makes it easier for teams to retrieve vision data, make various calculations, and more.\n\n .. grid-item-card:: Integration\n :link: docs/integration/index\n :link-type: doc\n\n Pick how to use vision processing results to control a physical robot.\n\n```\n\n```{eval-rst}\n.. grid:: 2\n\n .. grid-item-card:: Code Examples\n :link: docs/examples/index\n :link-type: doc\n\n View various step by step guides on how to use data from PhotonVision in your code, along with game-specific examples.\n\n .. grid-item-card:: Hardware\n :link: docs/hardware/index\n :link-type: doc\n\n Select appropriate hardware for high-quality and easy vision target detection.\n```\n\n```{eval-rst}\n.. grid:: 2\n\n .. grid-item-card:: Contributing\n :link: docs/contributing/index\n :link-type: doc\n\n Interested in helping with PhotonVision? Learn more about how to contribute to our main code base, documentation, and more.\n```\n\n# Source Code\n\nThe source code for all PhotonVision projects is available through our [GitHub organization](https://github.com/PhotonVision).\n\n- [PhotonVision](https://github.com/PhotonVision/photonvision)\n\n# Contact Us\n\nTo report a bug or submit a feature request in PhotonVision, please [submit an issue on the PhotonVision GitHub](https://github.com/PhotonVision/photonvision) or [contact the developers on Discord](https://discord.com/invite/KS76FrX).\n\nIf you find a problem in this documentation, please submit an issue on the [PhotonVision Documentation GitHub](https://github.com/PhotonVision/photonvision/tree/main/docs).\n\n# License\n\nPhotonVision is licensed under the [GNU GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html).\n\n```{toctree}\n:caption: Getting Started\n:hidden: true\n:maxdepth: 0\n\ndocs/description\ndocs/quick-start/index\ndocs/hardware/index\ndocs/advanced-installation/index\ndocs/camera-specific-configuration/index\n```\n\n```{toctree}\n:caption: Pipeline Tuning and Calibration\n:hidden: true\n:maxdepth: 0\n\ndocs/pipelines/index\ndocs/apriltag-pipelines/index\ndocs/reflectiveAndShape/index\ndocs/objectDetection/index\ndocs/driver-mode/index\ndocs/calibration/calibration\n```\n\n```{toctree}\n:caption: Programming Reference\n:hidden: true\n:maxdepth: 1\n\ndocs/programming/photonlib/index\ndocs/simulation/index\ndocs/integration/index\ndocs/examples/index\n```\n\n```{toctree}\n:caption: Additional Resources\n:hidden: true\n:maxdepth: 1\n\ndocs/troubleshooting/index\ndocs/additional-resources/best-practices\ndocs/additional-resources/config\ndocs/additional-resources/nt-api\ndocs/benchmarks/index\ndocs/contributing/index\n```\n\n```{toctree}\n:caption: API Documentation\n:hidden: true\n:maxdepth: 1\n\n Java \n\n C++ \n```\n",
- "content_preview": "```{image} assets/PhotonVision-Header-onWhite.png\n:alt: PhotonVision\n```\n\nWelcome to the official documentation of PhotonVision! PhotonVision is the free, fast, and easy-to-use vision processing solution for the _FIRST_ Robotics Competition."
+ "content": "# Troubleshooting\n\n```{toctree}\n:maxdepth: 1\n\ncommon-errors\nlogging\ncamera-troubleshooting\nnetworking-troubleshooting\nunix-commands\n```\n",
+ "content_preview": "# Troubleshooting\n\n```{toctree}\n:maxdepth: 1\n\ncommon-errors\nlogging\ncamera-troubleshooting\nnetworking-troubleshooting\nunix-commands\n```\n"
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/reflectiveAndShape/thresholding.html",
- "title": "Thresholding",
- "section": "Reflective & Shape Detection",
+ "url": "https://docs.photonvision.org/en/latest/docs/additional-resources/best-practices.html",
+ "title": "Best Practices For Competition",
+ "section": "Additional Resources",
"language": "All",
- "content": "# Thresholding\n\nFor colored shape detection, we want to tune our HSV thresholds such that only the goal color remains after the thresholding. The [HSV color representation](https://en.wikipedia.org/wiki/HSL_and_HSV) is similar to RGB in that it represents colors. However, HSV represents colors with hue, saturation and value components. Hue refers to the color, while saturation and value describe its richness and brightness.\n\nIn PhotonVision, HSV thresholds is available in the \"Threshold\" tab.\n\n```{raw} html\n\n \n Your browser does not support the video tag.\n \n```\n\n## Color Picker\n\nThe color picker can be used to quickly adjust HSV values. \"Set to average\" will set the HSV range to the color of the pixel selected, while \"shrink range\" and \"expand range\" will change the HSV threshold to include or exclude the selected pixel, respectively.\n\n```{raw} html\n\n \n Your browser does not support the video tag.\n \n```\n\n## Tuning Steps\n\nThe following steps were derived from FRC 254's 2016 Championship presentation on computer vision and allows you to accurately tune PhotonVision to track your target.\n\nIn order to properly capture the colors that you want, first turn your exposure low until you have a mostly dark image with the target still showing. A darker image ensures that you don't see things that aren't your target (ex. overhead lights). Be careful not to overexpose your image (you will be able to tell this if a target looks more cyan/white or equivalent instead of green when looking at it through the video feed) since that can give you poor results.\n\nFor HSV tuning, start with Hue, as it is the most important/differentiating factor when it comes to detecting color. You want to make the range for Hue as small as possible in order to get accurate tracking. Feel free to reference the chart below to help. After you have properly tuned Hue, tune for high saturation/color intensity (S), and then brightness (V). Using this method will decrease the likelihood that you need to calibrate on the field. Saturation and Value's upper bounds will often end up needing to be the maximum (255).\n\n```{image} images/hsl_top.png\n:alt: HSV chart\n:width: 600\n```\n",
- "content_preview": "# Thresholding\n\nFor colored shape detection, we want to tune our HSV thresholds such that only the goal color remains after the thresholding. The [HSV color representation](https://en.wikipedia.org/wiki/HSL_and_HSV) is similar to RGB in that it represents colors."
+ "content": "# Best Practices For Competition\n\n## Before Competition\n\n- Ensure you have spares of the relevant electronics if you can afford it (switch, coprocessor, cameras, etc.).\n- Stay on the latest version of PhotonVision until you have tested your full robot system to be functional.\n- Some time before the competition, lock down the version you are using and do not upgrade unless you encounter a critical bug.\n- Have a copy of the installation image for the version you are using on your programming laptop, in case re-imaging (without internet) is needed.\n- Extensively test at your home setup. Practice tuning from scratch under different lighting conditions.\n- Confirm you have followed all the recommendations under the {ref}`Networking` documentation (network switch and static IP).\n- Only use high quality ethernet cables that have been rigorously tested.\n\n## Camera Streaming\n- All camera streams are published under the NetworkTables table `CameraPublisher`.\n- The only subtable under `CameraPublisher` that will work for viewing a driver mode camera stream is the one that contains `Output` in the name.\n- To view a camera stream in a dashboard, drag the correct subtable from the NetworkTables tree into your dashboard.\n- Use the latest driver dashboard recommended by [WPILib](https://docs.wpilib.org/en/stable/docs/software/dashboards/dashboard-intro.html) on your driver station laptop.\n\n## During the Competition\n\n- Use the field calibration time given at the start of the event:\n - Bring your robot to the field at the allotted time.\n - Make sure the field has match-accurate lighting conditions active.\n - Turn on your robot and pull up the dashboard on your driver station.\n - Point your robot at the targets and ensure you get a consistent tracking (you hold one targets consistently, the ceiling lights aren't detected, etc.).\n - If you have problems with your pipeline, retune the pipeline following the {ref}`camera tuning ` documentation.\n - Move the robot close, far, angled, and around the field to ensure no extra targets are found.\n - Monitor camera feeds during a practice match to ensure everything is working correctly.\n- After field calibration, use the \"Export Settings\" button in the \"Settings\" page to create a backup.\n - Do this for each coprocessor on your robot that runs PhotonVision, and name your exports with meaningful names.\n - This will contain camera information/calibration, pipeline information, network settings, etc.\n - In the event of software/hardware failures (IE lost SD Card, broken device), you can then use the \"Import Settings\" button and select \"All Settings\" to restore your settings.\n - This effectively works as a snapshot of your PhotonVision data that can be restored at any point.\n- Before every match:\n - Check the ethernet and USB connectors are seated fully.\n - Close streaming dashboards when you don't need them to reduce bandwidth.\n- Stream at as low of a resolution as possible while still detecting AprilTags to stay within field bandwidth limits.\n",
+ "content_preview": "# Best Practices For Competition\n\n## Before Competition\n\n- Ensure you have spares of the relevant electronics if you can afford it (switch, coprocessor, cameras, etc.).\n- Stay on the latest version of PhotonVision until you have tested your full robot system to be functional.\n- Some time before the..."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/advanced-installation/sw_install/mac-os.html",
- "title": "Mac OS Installation",
- "section": "General",
+ "url": "https://docs.photonvision.org/en/latest/docs/calibration/calibration.html",
+ "title": "Calibrating Your Camera",
+ "section": "Camera Calibration",
"language": "All",
- "content": "# Mac OS Installation\n\n:::{warning}\nDue to current [cscore](https://github.com/wpilibsuite/allwpilib/tree/main/cscore) restrictions, the PhotonVision server backend may have issues running macOS.\n:::\n\n:::{note}\nYou do not need to install PhotonVision on a Mac in order to access the webdashboard (assuming you are using an external coprocessor like a Raspberry Pi).\n:::\n\nVERY Limited macOS support is available.\n\n## Installing Java\n\nPhotonVision requires a JDK installed and on the system path. JDK 17 is needed (different versions will not work). You may already have this if you have installed WPILib 2026+. If not, [download and install it from here](https://adoptium.net/temurin/releases?version=17).\n\n:::{warning}\nUsing a JDK other than JDK17 will cause issues when running PhotonVision and is not supported.\n:::\n\n## Downloading the Latest Stable Release of PhotonVision\n\nGo to the [GitHub releases page](https://github.com/PhotonVision/photonvision/releases) and download the relevant .jar file for your coprocessor.\n\n:::{note}\nIf you have an M Series Mac, download the macarm64.jar file.\n\nIf you have an Intel based Mac, download the macx64.jar file.\n:::\n\n:::{warning}\nBe careful to pick the latest stable release. \"Draft\" or \"Pre-Release\" versions are not stable and often have bugs.\n:::\n\n## Running PhotonVision\n\nTo run PhotonVision, open a terminal window of your choice and run the following command:\n\n```\n$ java -jar /path/to/photonvision/photonvision-xxx.jar\n```\n\n:::{warning}\nDue to current [cscore](https://github.com/wpilibsuite/allwpilib/tree/main/cscore) restrictions, the PhotonVision using test mode is all that is known to work currently.\n:::\n\n## Accessing the PhotonVision Interface\n\nOnce the Java backend is up and running, you can access the main vision interface by navigating to `localhost:5800` inside your browser.\n\n:::{warning}\nDue to current [cscore](https://github.com/wpilibsuite/allwpilib/tree/main/cscore) restrictions, it is unlikely any streams will open from real webcams.\n:::\n",
- "content_preview": "# Mac OS Installation\n\n:::{warning}\nDue to current [cscore](https://github.com/wpilibsuite/allwpilib/tree/main/cscore) restrictions, the PhotonVision server backend may have issues running macOS.\n:::\n\n:::{note}\nYou do not need to install PhotonVision on a Mac in order to access the webdashboard..."
+ "content": "# Calibrating Your Camera\n\n:::{important}\nIn order to detect AprilTags and use 3D mode, your camera must be calibrated at the desired resolution! Inaccurate calibration will lead to poor performance.\n:::\n\nTo calibrate a camera, images of a ChArUco board (or chessboard) are taken. By comparing where the grid corners should be in object space (for example, a corner once every inch in an 8x6 grid) with where they appear in the camera image, we can find a least-squares estimate for intrinsic camera properties like focal lengths, center point, and distortion coefficients. For more on camera calibration, please review the [OpenCV documentation](https://docs.opencv.org/4.x/dc/dbb/tutorial_py_calibration.html).\n\n:::{warning}\nWhile any resolution can be calibrated, higher resolutions may be too performance-intensive for some coprocessors to handle. Therefore, we recommend experimenting to see what works best for your coprocessor.\n:::\n\n:::{note}\nThe calibration data collected during calibration is specific to each physical camera, as well as each individual resolution.\n:::\n\n## Calibration Tips\n\n:::{warning}\nThe usage of chessboards can result in bad calibration results if multiple similar images are taken. We strongly recommend that teams use ChArUco boards instead!\n:::\n\nAccurate camera calibration is required in order to get accurate pose measurements when using AprilTags and 3D mode. The tips below should help ensure success:\n\n01. Ensure the images you take have the target in different positions and angles, with as big of a difference between angles as possible. It is important to make sure the target overlay still lines up with the board while doing this. Tilt no more than 45 degrees.\n02. Use as big of a calibration target as your printer can print.\n03. Ensure that your printed pattern has enough white border around it.\n04. Ensure your camera stays in one position during the duration of the calibration.\n05. Make sure you get all 12 images from varying distances and angles.\n06. Take at least one image that covers the total image area, and generally ensure that you get even coverage of the lens with your image set.\n07. Have good lighting, having a diffusely lit target would be best (light specifically shining on the target without shadows).\n08. Ensure the calibration target is completely flat and does not bend or fold in any way. It should be mounted/taped down to something flat and then used for calibration, do not just hold it up.\n09. Avoid having targets that are parallel to the lens of the camera / straight on towards the camera as much as possible. You want angles and variations within your calibration images.\n\nFollowing the ideas above should help in getting an accurate calibration.\n\n## Calibrating using PhotonVision\n\n### 1. Navigate to the calibration section in the UI.\n\nThe Cameras tab of the UI houses PhotonVision's camera calibration tooling. It assists users with calibrating their cameras, as well as allows them to view previously calibrated resolutions. We support both ChArUco and chessboard calibrations.\n\n### 2. Print out the calibration target.\n\nIn the Camera Calibration tab, we'll print out the calibration target using the \"Download\" button. This should be printed on 8.5x11 printer paper. This page shows using an 8x8 ChArUco board (or chessboard depending on the selected calibration type).\n\n:::{warning}\nEnsure that there is no scaling applied during printing (it should be at 100%) and that the PDF is printed as is on regular printer paper. Check the square size with calipers or an accurate measuring device after printing to ensure squares are sized properly, and enter the true size of the square in the UI text box. For optimal results, various resources are available online to calibrate your specific printer if needed.\n:::\n\n### 3. Select calibration resolution and fill in appropriate target data.\n\nWe'll next select a resolution to calibrate and populate our pattern spacing, marker size, and board size. The provided chessboard and ChArUco board are an 8x8 grid of 1 inch square. The provided ChArUco board uses the 4x4 dictionary with a marker size of 0.75 inches (this board does not need the old OpenCV pattern selector selected). Printers are not perfect, and you need to measure your calibration target and enter the correct marker size (size of the ArUco marker) and pattern spacing (aka size of the black square) using calipers or similar. Finally, once our entered data is correct, we'll click \"start calibration.\"\n\n:::{warning} Old OpenCV Pattern selector. This should be used in the case that the calibration image is generated from a version of OpenCV before version 4.6.0. This would include targets created by calib.io. If this selector is not set correctly the calibration will be completely invalid. For more info view [this GitHub issue](https://github.com/opencv/opencv_contrib/issues/3291).\n:::\n\n:::{note}\nIf you have a [calib.io](https://calib.io/) ChArUco Target you will have to enter the paramaters of your target. For example if your target says \"9x12 | Checker Size: 30 mm | Marker Size: 22 mm | Dictionary: ArUco DICT 5x5\", you would have to set the board type to Dict_5x5_1000, the pattern spacing to 1.1811 in (30 mm converted to inches), the marker size 0.866142 in (22 mm converted to inches), the board width to 12 and the board height to 9. If you chose the wrong tag family the board wont be detected during calibration. If you swap the width and height your calibration will have a very high error.\n:::\n\n### 4. Take at calibration images from various angles.\n\nNow, we'll capture images of our board from various angles. It's important to check that the board overlay matches the board in your image. The further the overdrawn points are from the true position of the chessboard corners, the less accurate the final calibration will be. We'll want to capture enough images to cover the whole camera's FOV (with a minimum of 12). Once we've got our images, we'll click \"Finish calibration\" and wait for the calibration process to complete. If all goes well, the mean error and FOVs will be shown in the table on the right. The FOV should be close to the camera's specified FOV (usually found in a datasheet) usually within + or - 10 degrees. The mean error should also be low, usually less than 1 pixel.\n\n```{raw} html\n\n \n Your browser does not support the video tag.\n \n```\n\n## Accessing Calibration Images\n\nDetails about a particular calibration can be viewed by clicking on that resolution in the calibrations tab. This tab allows you to download raw calibration data, upload a previous calibration, and inspect details about calculated camera intrinsic.\n\n```{image} images/cal-details.png\n:alt: Captured calibration images\n:width: 600\n```\n\n:::{note}\nMore info on what these parameters mean can be found in [OpenCV's docs](https://docs.opencv.org/4.8.0/d4/d94/tutorial_camera_calibration.html)\n:::\n\n- Fx/Fy: Estimated camera focal length, in pixels\n- Fx/Cy: Estimated camera optical center, in pixels. This should be at about the center of the image\n- Distortion: OpenCV camera model distortion coefficients\n- FOV: calculated using estimated focal length and image size. Useful for gut-checking calibration results\n- Mean Err: Mean reprojection error, or distance between expected and observed chessboard cameras for the full calibration dataset\n\nBelow these outputs are the snapshots collected for calibration, along with a per-snapshot mean reprojection error. A snapshot with a larger reprojection error might indicate a bad snapshot, due to effects such as motion blur or misidentified chessboard corners.\n\nCalibration images can also be extracted from the downloaded JSON file using [this Python script](https://raw.githubusercontent.com/PhotonVision/photonvision/main/devTools/calibrationUtils.py). This script will unpack calibration images, and also generate a VNL file for use [with mrcal](https://mrcal.secretsauce.net/).\n\n```\npython3 /path/to/calibrationUtils.py path/to/photon_calibration.json /path/to/output/folder\n```\n\n```{image} images/unpacked-json.png\n:alt: Captured calibration images\n:width: 600\n```\n\n## Investigating Calibration Data with mrcal\n\n[mrcal](https://mrcal.secretsauce.net/tour.html) is a command-line tool for camera calibration and visualization. PhotonVision has the option to use the mrcal backend during camera calibration to estimate intrinsics. mrcal can also be used post-calibration to inspect snapshots and provide feedback. These steps will closely follow the [mrcal tour](https://mrcal.secretsauce.net/tour-initial-calibration.html) -- I'm aggregating commands and notes here, but the mrcal documentation is much more thorough.\n\nStart by [Installing mrcal](https://mrcal.secretsauce.net/install.html). Note that while mrcal *calibration* using PhotonVision is supported on all platforms, but investigation right now only works on Linux. Some users have also reported luck using [WSL 2 on Windows](https://learn.microsoft.com/en-us/windows/wsl/tutorials/gui-apps) as well. You may also need to install `feedgnuplot`. On Ubuntu systems, these commands should be run from a standalone terminal and *not* the one [built into vscode](https://github.com/ros2/ros2/issues/1406).\n\nLet's run `calibrationUtils.py` as described above, and then cd into the output folder. From here, you can follow the mrcal tour, just replacing the VNL filename and camera imager size as necessary. My camera calibration was at 1280x720, so I've set the XY limits to that below.\n\n```\n$ cd /path/to/output/folder\n$ ls\nmatt@photonvision:~/Documents/Downloads/2024-01-02_lifecam_1280$ ls\n corners.vnl img0.png img10.png img11.png img12.png img13.png img1.png\n img2.png img3.png img4.png img5.png img6.png img7.png img8.png\n img9.png cameramodel_0.cameramodel\n\n$ < corners.vnl \\\n vnl-filter -p x,y | \\\n feedgnuplot --domain --square --set 'xrange [0:1280] noextend' --set 'yrange [720:0] noextend'\n```\n\n```{image} images/mrcal-coverage.svg\n:alt: A diagram showing the locations of all detected chessboard corners.\n```\n\nAs you can see, we didn't do a fantastic job of covering our whole camera sensor -- there's a big gap across the whole right side, for example. We also only have 14 calibration images. We've also got our \"cameramodel\" file, which can be used by mrcal to display additional debug info.\n\nLet's inspect our reprojection error residuals. We expect their magnitudes and directions to be random -- if there's patterns in the colors shown, then our calibration probably doesn't fully explain our physical camera sensor.\n\n```\n$ mrcal-show-residuals --magnitudes --set 'cbrange [0:1.5]' ./camera-0.cameramodel\n$ mrcal-show-residuals --directions --unset key ./camera-0.cameramodel\n```\n\n```{image} images/residual-magnitudes.svg\n:alt: A diagram showing residual magnitudes\n```\n\n```{image} images/residual-directions.svg\n:alt: A diagram showing residual directions\n```\n\nClearly we don't have anywhere near enough data to draw any meaningful conclusions (yet). But for fun, let's dig into [camera uncertainty estimation](https://mrcal.secretsauce.net/tour-uncertainty.html). This diagram shows how expected projection error changes due to noise in calibration inputs. Lower projection error across a larger area of the sensor imply a better calibration that more fully covers the whole sensor. For my calibration data, you can tell the projection error isolines (lines of constant expected projection error) are skewed to the left, following my dataset (which was also skewed left).\n\n```\n$ mrcal-show-projection-uncertainty --unset key ./cameramodel_0.cameramodel\n```\n\n```{image} images/camera-uncertainty.svg\n:alt: A diagram showing camera uncertainty\n```\n",
+ "content_preview": "# Calibrating Your Camera\n\n:::{important}\nIn order to detect AprilTags and use 3D mode, your camera must be calibrated at the desired resolution! Inaccurate calibration will lead to poor performance.\n:::\n\nTo calibrate a camera, images of a ChArUco board (or chessboard) are taken."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/pipelines/input.html",
- "title": "Camera Tuning / Input",
- "section": "Pipelines",
+ "url": "https://docs.photonvision.org/en/latest/docs/troubleshooting/logging.html",
+ "title": "Logging",
+ "section": "Troubleshooting",
"language": "All",
- "content": "# Camera Tuning / Input\n\nPhotonVision's \"Input\" tab contains settings that affect the image captured by the currently selected camera. This includes camera exposure and brightness, as well as resolution and orientation.\n\n## Resolution\n\nResolution changes the resolution of the image captured. While higher resolutions are often more accurate than lower resolutions, they also run at a slower update rate.\n\nWhen using the reflective/colored shape pipeline, detection should be run as low of a resolution as possible as you are only trying to detect simple contours (essentially colored blobs).\n\nWhen using the AprilTag pipeline, you should try to use as high of a resolution as you can while still maintaining a reasonable FPS measurement. This is because higher resolution allows you to detect tags with higher accuracy and from larger distances.\n\n## Exposure and brightness\n\nCamera exposure and brightness control how bright the captured image will be, although they function differently. Camera exposure changes how long the camera shutter lets in light, which changes the overall brightness of the captured image. This is in contrast to brightness, which is a post-processing effect that boosts the overall brightness of the image at the cost of desaturating colors (making colors look less distinct).\n\n:::{important}\nFor all pipelines, exposure time should be set as low as possible while still allowing for the target to be reliably tracked. This allows for faster processing as decreasing exposure will increase your camera FPS.\n:::\n\nFor reflective pipelines, after adjusting exposure and brightness, the target should be lit green (or the color of the vision tracking LEDs used). The more distinct the color of the target, the more likely it will be tracked reliably.\n\n:::{note}\nUnlike with retroreflective tape, AprilTag tracking is not very dependent on lighting consistency. If you have trouble detecting tags due to low light, you may want to try increasing exposure, but this will likely decrease your achievable framerate.\n:::\n\n### AprilTags and Motion Blur\n\nFor AprilTag pipelines, your goal is to reduce the \"motion blur\" as much as possible. Motion blur is the visual streaking/smearing on the camera stream as a result of movement of the camera or object of focus. You want to mitigate this as much as possible because your robot is constantly moving and you want to be able to read as many tags as you possibly can. The possible solutions to this include:\n\n1. Cranking your exposure as low as it goes and increasing your gain/brightness. This will decrease the effects of motion blur and increase FPS.\n2. Using a global shutter (as opposed to rolling shutter) camera. This should eliminate most, if not all motion blur.\n3. Only rely on tags when not moving.\n\n```{image} images/motionblur.gif\n:align: center\n```\n\n## Orientation\n\nOrientation can be used to rotate the image prior to vision processing. This can be useful for cases where the camera is not oriented parallel to the ground. Do note that this operation can in some cases significantly reduce FPS.\n\n## Stream Resolution\n\nThis changes the resolution which is used to stream frames from PhotonVision. This does not change the resolution used to perform vision processing. This is useful to reduce bandwidth consumption on the field. In some high-resolution cases, decreasing stream resolution can increase processing FPS.\n",
- "content_preview": "# Camera Tuning / Input\n\nPhotonVision's \"Input\" tab contains settings that affect the image captured by the currently selected camera. This includes camera exposure and brightness, as well as resolution and orientation.\n\n## Resolution\n\nResolution changes the resolution of the image captured."
+ "content": "# Logging\n\n:::{note}\nLogging is very helpful when trying to debug issues within PhotonVision, as it allows us to see what is happening within the program after it is ran. Whenever reporting an issue to PhotonVision, we request that you include logs whenever possible.\n:::\n\nIn addition to storing logs in timestamped files in the config directory, PhotonVision streams logs to the web dashboard. These logs can be viewed later by pressing the \\` key. In this view, logs can be filtered by level or downloaded.\n\n:::{note}\nWhen the program first starts, it sends logs from startup to the client that first connects. This does not happen on subsequent connections.\n:::\n\n:::{note}\nLogs are stored inside the {code}`photonvision_config/logs` directory. Exporting the settings ZIP will also download all old logs for further review.\n:::\n\n```{raw} html\n\n \n Your browser does not support the video tag.\n \n```\n\nRobot mode transitions are also recorded in program logs. These transition messages look something like the two shown below, and show the contents of the [HAL Control Word](https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/hal/ControlWord.html) that the robot was in previously, and what it is now in. This includes:\n- Enabled state\n- Robot state (autonomous vs teleoperated)\n- If the robot e-stop is active\n\nIf the robot is connected to the FMS at an event, we will additionally print out:\n- Event name\n- Match type and number\n- Driver station position\n\n\n```\n[2025-04-19 19:52:08] [NetworkTables - NTDriverStation] [INFO] ROBOT TRANSITIONED MODES! From NtControlWord[m_enabled=true, m_autonomous=false, m_test=false, m_emergencyStop=false, m_fmsAttached=true, m_dsAttached=true] to NtControlWord[m_enabled=true, m_autonomous=false, m_test=true, m_emergencyStop=false, m_fmsAttached=true, m_dsAttached=true]\n\n[2025-04-19 19:52:09] [NetworkTables - NTDriverStation] [INFO] ROBOT TRANSITIONED MODES! From NtControlWord[m_enabled=true, m_autonomous=false, m_test=true, m_emergencyStop=false, m_fmsAttached=true, m_dsAttached=true] to NtControlWord[m_enabled=false, m_autonomous=false, m_test=false, m_emergencyStop=false, m_fmsAttached=false, m_dsAttached=false]\n[2025-04-19 19:52:19] [NetworkTables - NTDriverStation] [INFO] ROBOT TRANSITIONED MODES! From NtControlWord[m_enabled=false, m_autonomous=false, m_test=false, m_emergencyStop=false, m_fmsAttached=false, m_dsAttached=false] to NtControlWord[m_enabled=true, m_autonomous=true, m_test=false, m_emergencyStop=false, m_fmsAttached=true, m_dsAttached=true]\n```\n",
+ "content_preview": "# Logging\n\n:::{note}\nLogging is very helpful when trying to debug issues within PhotonVision, as it allows us to see what is happening within the program after it is ran."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/contributing/building-photon.html",
- "title": "Build Instructions",
+ "url": "https://docs.photonvision.org/en/latest/docs/contributing/developer-docs/photonlib-backups.html",
+ "title": "Photonlib Developer Docs",
"section": "Contributing",
- "language": "Java",
- "content": "# Build Instructions\n\nThis section contains the build instructions from the source code available at [our GitHub page](https://github.com/PhotonVision/photonvision).\n\n## Development Setup\n\n### Prerequisites\n\n**Java Development Kit:**\n\n This project requires Java Development Kit (JDK) 17 to be compiled. This is the same Java version that comes with WPILib for 2026+. **Windows Users must use the JDK that ships with WPILib.** For other platforms, you can follow the instructions to install JDK 17 for your platform [here](https://bell-sw.com/pages/downloads/#jdk-17-lts).\n\n**Node JS:**\n\n The UI is written in Node JS. To compile the UI, Node 22 or later is required. To install Node JS, follow the instructions for your platform [on the official Node JS website](https://nodejs.org/en/download/).\n\n**pnpm:**\n\n [pnpm](https://pnpm.io/) is the package manager used to download dependencies for the UI. To install pnpm, follow [the instructions on the official pnpm website](https://pnpm.io/installation).\n\n**Cross-Compilation Toolchains (Optional):**\n\n If you plan to deploy PhotonVision to a coprocessor like a Raspberry Pi, you will need to install the appropriate cross-compilation toolchain for your platform. For `linuxarm64` devices, this can be accomplished by running `./gradlew installArm64Toolchain` in the root folder of the project.\n\n## Compiling Instructions\n\n### Getting the Source Code\n\nGet the source code from git:\n\n```bash\ngit clone https://github.com/PhotonVision/photonvision\n```\n\nor alternatively download the source code from GitHub and extract the zip:\n\n```{image} assets/git-download.png\n:alt: Download source code from git\n:width: 600\n```\n\n### Install Necessary Node JS Dependencies\n\nIn the photon-client directory:\n\n```bash\npnpm install\n```\n\n### Using hot reload on the UI\n\nIn the photon-client directory:\n\n```bash\npnpm run dev\n```\n\nThis allows you to make UI changes quickly without having to spend time rebuilding the jar. Hot reload is enabled, so changes that you make and save are reflected in the UI immediately. Running this command will give you the URL for accessing the UI, which is on a different port than normal. You must use the printed URL to use hot reload.\n\n### Build and Run PhotonVision\n\nTo compile and run the project, issue the following command in the root directory:\n\n```{eval-rst}\n.. tab-set::\n\n .. tab-item:: Linux\n :sync: linux\n\n ``./gradlew run``\n\n .. tab-item:: macOS\n :sync: macos\n\n ``./gradlew run``\n\n .. tab-item:: Windows (cmd)\n :sync: windows\n\n ``gradlew run``\n```\n\nRunning the following command under the root directory will build the jar under `photon-server/build/libs`:\n\n```{eval-rst}\n.. tab-set::\n\n .. tab-item:: Linux\n :sync: linux\n\n ``./gradlew shadowJar``\n\n .. tab-item:: macOS\n :sync: macos\n\n ``./gradlew shadowJar``\n\n .. tab-item:: Windows (cmd)\n :sync: windows\n\n ``gradlew shadowJar``\n```\n\n### Build and Run PhotonVision on a Raspberry Pi Coprocessor\n\nAs a convenience, the build has a built-in `deploy` command which builds, deploys, and starts the current source code on a coprocessor. It uses [deploy-utils](https://github.com/wpilibsuite/deploy-utils/blob/main/README.md), so it works very similarly to deploys on robot projects.\n\nAn architecture override is required to specify the deploy target's architecture.\n\n```{eval-rst}\n.. tab-set::\n\n .. tab-item:: Linux\n :sync: linux\n\n ``./gradlew clean``\n\n ``./gradlew deploy -PArchOverride=linuxarm64``\n\n .. tab-item:: macOS\n :sync: macos\n\n ``./gradlew clean``\n\n ``./gradlew deploy -PArchOverride=linuxarm64``\n\n .. tab-item:: Windows (cmd)\n :sync: windows\n\n ``gradlew clean``\n\n ``gradlew deploy -PArchOverride=linuxarm64``\n```\n\nThe `deploy` command is tested against Raspberry Pi coprocessors. Other similar coprocessors may work too.\n\n### Using PhotonLib Builds\n\nThe build process automatically generates a vendordep JSON of your local build at `photon-lib/build/generated/vendordeps/photonlib.json`.\n\nThe photonlib source can be published to your local maven repository after building:\n\n```{eval-rst}\n.. tab-set::\n\n .. tab-item:: Linux\n :sync: linux\n\n ``./gradlew publishToMavenLocal``\n\n .. tab-item:: macOS\n :sync: macos\n\n ``./gradlew publishToMavenLocal``\n\n .. tab-item:: Windows (cmd)\n :sync: windows\n\n ``gradlew publishToMavenLocal``\n```\n\nAfter adding the generated vendordep to your project, add the following to your project's `build.gradle` under the `plugins {}` block.\n\n```Java\nrepositories {\n mavenLocal()\n}\n```\n\n### Debugging PhotonVision Running on a CoProcessor\n\nWe can use Java's remote debug capabilities to run the PhotonVision JAR file on a Coprocessor, and attach a debugger running on a desktop/laptop to the process remotely. Set up a VSCode configuration in {code}`launch.json`\n\n```json\n{\n // Use IntelliSense to learn about possible attributes.\n // Hover to view descriptions of existing attributes.\n // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n \"version\": \"0.2.0\",\n \"configurations\": [\n {\n \"type\": \"java\",\n \"name\": \"Attach to CoProcessor\",\n \"request\": \"attach\",\n \"hostName\": \"photonvision.local\",\n \"port\": \"5801\",\n \"projectName\": \"photon-core\"\n },\n ]\n}\n```\n\nStop any existing instance of PhotonVision by running {code}`systemctl stop photonvision`.\n\nLaunch the program with the following additional argument to the JVM: {code}`java -jar -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5801 photonvision.jar` -- the JVM will wait for a debugger to connect before running `main`.\n\nOnce the program says it is listening on port 5801, launch the debug configuration in VSCode.\n\nThe program will wait for the VSCode debugger to attach before proceeding.\n\n\n## Running Tests\n\n### Running Default Tests\n\nMost unit tests [run as \"headless\" tests](https://docs.gradle.org/current/userguide/java_testing.html#test_filtering) (i.e have no UI component during the test) by default.\nTo run a test, pass the test name(s):\n\n```{eval-rst}\n.. tab-set::\n\n .. tab-item:: Linux\n :sync: linux\n\n ``./gradlew test --tests ``\n\n .. tab-item:: macOS\n :sync: macos\n\n ``./gradlew test --tests ``\n\n .. tab-item:: Windows (cmd)\n :sync: windows\n\n ``gradlew test --tests ``\n```\n\n### Debugging PhotonVision Tests Locally\n\nUnit tests can also be debugged through the ``test`` Gradle task for a specific subproject in VSCode, found in the Gradle tab:\n\n```{image} assets/vscode-gradle-tests.png\n:alt: An image showing how unit tests can be debugged in VSCode through the Gradle for Java extension.\n```\n\nHowever, this will run all tests in a subproject.\n\nSimilarly, a local instance of PhotonVision can be debugged in the same way using the Gradle ``run`` task. In both cases, additional arguments can be specified:\n\n```{image} assets/vscode-gradle-args.png\n:alt: An image showing how VSCode gradle tasks can specify additional arguments.\n```\n\n### Running Tests With UI\n\nBy default, tests are run with UI disabled so they are not obtrusive during a build. All tests should be useful when the UI is disabled. However, if a particular test would benefit from having UI access (i.e. for debugging info), the UI can be enabled by passing the `enableTestUi` project property to Gradle. This will run all tests by default, but the Gradle `--tests` option can be used to [filter for specific tests](https://docs.gradle.org/current/userguide/java_testing.html#test_filtering).\n\n```{eval-rst}\n.. tab-set::\n\n .. tab-item:: Linux\n :sync: linux\n\n ``./gradlew test -PenableTestUi``\n\n .. tab-item:: macOS\n :sync: macos\n\n ``./gradlew test -PenableTestUi``\n\n .. tab-item:: Windows (cmd)\n :sync: windows\n\n ``gradlew test -PenableTestUi``\n```\n\n### VSCode Test Runner Extension\n\nWith the VSCode [Extension Pack for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-pack), you can get the Test Runner for Java and Gradle for Java extensions. This lets you easily run specific tests through the IDE:\n\n```{image} assets/vscode-runner-tests.png\n:alt: An image showing how unit tests can be ran in VSCode through the Test Runner for Java extension.\n```\n\nTo correctly run PhotonVision tests this way, you must [delegate the tests to Gradle](https://code.visualstudio.com/docs/java/java-build#_delegate-tests-to-gradle). Debugging tests like this will [**not** currently](https://github.com/microsoft/build-server-for-gradle/issues/119) collect outputs.\n\n## Running examples\n\nYou can run one of the many built in examples straight from the command line, too! They contain a fully featured robot project, and some include simulation support. The projects can be found inside the photonlib-*-examples subdirectories for each language.\n\n### Running C++/Java\n\nPhotonLib must first be published to your local maven repository. This will also copy the generated vendordep json file into each example. After that, the simulateJava/simulateNative task can be used like a normal robot project. Robot simulation with attached debugger is technically possible by using simulateExternalJava and modifying the launch script it exports, though not yet supported.\n\n```\n~/photonvision$ ./gradlew publishToMavenLocal\n\n~/photonvision$ cd photonlib-java-examples\n~/photonvision/photonlib-java-examples$ ./gradlew :simulateJava\n\n~/photonvision$ cd photonlib-cpp-examples\n~/photonvision/photonlib-cpp-examples$ ./gradlew :simulateNative\n```\n\n### Running Python\n\nPhotonLibPy must first be built into a wheel.\n\n```\n> cd photon-lib/py\n> buildAndTest.bat\n```\n\nThen, you must enable using the development wheels. robotpy will use pip behind the scenes, and this bat file tells pip about your development artifacts.\n\nNote: This is best done in a virtual environment.\n\n```\n> enableUsingDevBuilds.bat\n```\n\nThen, run the examples:\n\n```\n> cd photonlib-python-examples\n> run.bat \n```\n\n### Downloading Pipeline Artifacts\n\nUsing the [GitHub CLI](https://cli.github.com/), we can download artifacts from pipelines by run ID and name:\n\n```\n~/photonvision$ gh run download 11759699679 -n jar-Linux\n```\n\n### MacOS Builds\n\nMacOS builds are not published to releases as MacOS is not an officially\nsupported platform. However, MacOS builds are still available from the MacOS\nbuild action, which can be found [here](https://github.com/PhotonVision/photonvision/actions/workflows/build.yml).\n\n### Forcing Object Detection in the UI\n\nIn order to force the Object Detection interface to be visible, it's necessary to hardcode the platform that `Platform.java` returns. This can be done by changing the function that detects the RK3588S/QCS6490 platform to always return true, and changing the `getCurrentPlatform()` function to always return the RK3588S/QCS6490 architecture.\nAlternatively, it's possible to modify the frontend code by changing all instances of `useSettingsStore().general.supportedBackends.length > 0` to `true`, which will force the card to render.\nMake sure to revert these changes before submitting a Pull Request.\n",
- "content_preview": "# Build Instructions\n\nThis section contains the build instructions from the source code available at [our GitHub page](https://github.com/PhotonVision/photonvision).\n\n## Development Setup\n\n### Prerequisites\n\n**Java Development Kit:**\n\n This project requires Java Development Kit (JDK) 17 to be..."
+ "language": "All",
+ "content": "# Photonlib Developer Docs\n\nOur maven server is located at https://maven.photonvision.org/#/. This server runs [Reposilite](https://hub.docker.com/r/dzikoysk/reposilite) in Docker, and uses Caddy for serving requests.\n\n\n## Backing up using Rsync\n\nThe Clarkson Open Source Institute at Clarkson University provides a mirror of our artifacts available [online](https://mirror.clarkson.edu/photonvision). Learn more about them at [their homepage](https://mirror.clarkson.edu/home).\n\nArtifacts from our Maven server can also be backed up locally to a folder called `photonlib-backup` using the following command, which excludes \"snapshots\" for space reasons:\n\n```\nrsync -avzrHy --no-perms --no-group --no-owner --ignore-errors --exclude \".~tmp~\" --exclude \"snapshots/org/photonvision/photontargeting*\" \\\n--exclude \"snapshots/org/photonvision/photonlib*\" maven.photonvision.org::reposilite-data \\\n/path/to/photonlib-backup\n```\n",
+ "content_preview": "# Photonlib Developer Docs\n\nOur maven server is located at https://maven.photonvision.org/#/. This server runs [Reposilite](https://hub.docker.com/r/dzikoysk/reposilite) in Docker, and uses Caddy for serving requests.\n\n\n## Backing up using Rsync\n\nThe Clarkson Open Source Institute at Clarkson..."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/examples/aimingatatarget.html",
- "title": "Aiming at a Target",
- "section": "Code Examples",
+ "url": "https://docs.photonvision.org/en/latest/docs/simulation/index.html",
+ "title": "Simulation",
+ "section": "Simulation",
"language": "All",
- "content": "# Aiming at a Target\n\nThe following example is from the PhotonLib example repository ([Java](https://github.com/PhotonVision/photonvision/tree/main/photonlib-java-examples/aimattarget)).\n\n## Knowledge and Equipment Needed\n\n- A Robot\n- A camera mounted rigidly to the robot's frame, centered and pointed forward.\n- A coprocessor running PhotonVision with an AprilTag or ArUco 2D Pipeline.\n- [A printout of AprilTag 7](https://firstfrc.blob.core.windows.net/frc2026/FieldAssets/2026-apriltag-images-user-guide.pdf), mounted on a rigid and flat surface.\n\n## Code\n\nNow that you have properly set up your vision system and have tuned a pipeline, you can now aim your robot at an AprilTag using the data from PhotonVision. The _yaw_ of the target is the critical piece of data that will be needed first.\n\nYaw is reported to the roboRIO over Network Tables. PhotonLib, our vendor dependency, is the easiest way to access this data. The documentation for the Network Tables API can be found {ref}`here ` and the documentation for PhotonLib {ref}`here `.\n\nIn this example, while the operator holds a button down, the robot will turn towards the AprilTag using the P term of a PID loop. To learn more about how PID loops work, how WPILib implements them, and more, visit [Advanced Controls (PID)](https://docs.wpilib.org/en/stable/docs/software/advanced-control/introduction/index.html) and [PID Control in WPILib](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/controllers/pidcontroller.html#pid-control-in-wpilib).\n\n```{eval-rst}\n.. tab-set::\n :sync-group: code\n\n .. tab-item:: Java\n :sync: java\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/aimattarget/src/main/java/frc/robot/Robot.java\n :language: java\n :lines: 77-117\n :linenos:\n :lineno-start: 77\n\n .. tab-item:: C++ (Header)\n :sync: c++\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/aimattarget/src/main/include/Robot.h\n :language: c++\n :lines: 25-60\n :linenos:\n :lineno-start: 25\n\n .. tab-item:: C++ (Source)\n :sync: c++\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/aimattarget/src/main/cpp/Robot.cpp\n :language: c++\n :lines: 56-96\n :linenos:\n :lineno-start: 56\n\n .. tab-item:: Python\n :sync: python\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-python-examples/aimattarget/robot.py\n :language: python\n :lines: 46-70\n :linenos:\n :lineno-start: 46\n\n```\n",
- "content_preview": "# Aiming at a Target\n\nThe following example is from the PhotonLib example repository ([Java](https://github.com/PhotonVision/photonvision/tree/main/photonlib-java-examples/aimattarget)).\n\n## Knowledge and Equipment Needed\n\n- A Robot\n- A camera mounted rigidly to the robot's frame, centered and..."
+ "content": "# Simulation\n\n```{toctree}\n:maxdepth: 0\n:titlesonly: true\n\nsimulation-java\nsimulation-cpp\nsimulation-python\nhardware-in-the-loop-sim\n```\n",
+ "content_preview": "# Simulation\n\n```{toctree}\n:maxdepth: 0\n:titlesonly: true\n\nsimulation-java\nsimulation-cpp\nsimulation-python\nhardware-in-the-loop-sim\n```\n"
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/apriltag-pipelines/3D-tracking.html",
- "title": "3D Tracking",
- "section": "AprilTag Detection",
+ "url": "https://docs.photonvision.org/en/latest/docs/contributing/design-descriptions/image-rotation.html",
+ "title": "Calibration and Image Rotation",
+ "section": "Contributing",
"language": "All",
- "content": "# 3D Tracking\n\n3D AprilTag tracking will allow you to track the real-world position and rotation of a tag relative to the camera's image sensor. This is useful for robot pose estimation and other applications like autonomous scoring. In order to use 3D tracking, you must first {ref}`calibrate your camera `. Once you have, you need to enable 3D mode in the UI and you will now be able to get 3D pose information from the tag! For information on getting and using this information in your code, see {ref}`the programming reference `.\n\n## Ambiguity\n\nTranslating from 2D to 3D using data from the calibration and the four tag corners can lead to \"pose ambiguity\", where it appears that the AprilTag pose is flipping between two different poses. You can read more about this issue [here](https://docs.wpilib.org/en/stable/docs/software/vision-processing/apriltag/apriltag-intro.html#d-to-3d-ambiguity). Ambiguity is calculated as the ratio of reprojection errors between two pose solutions (if they exist), where reprojection error is the error corresponding to the image distance between where the apriltag's corners are detected vs where we expect to see them based on the tag's estimated camera relative pose.\n\nThere are a few steps you can take to resolve/mitigate this issue:\n\n1. Mount cameras at oblique angles so it is less likely that the tag will be seen straight on.\n2. Use the {ref}`MultiTag system ` in order to combine the corners from multiple tags to get a more accurate and unambiguous pose.\n3. Reject all tag poses where the ambiguity ratio (available via PhotonLib) is greater than 0.2.\n",
- "content_preview": "# 3D Tracking\n\n3D AprilTag tracking will allow you to track the real-world position and rotation of a tag relative to the camera's image sensor. This is useful for robot pose estimation and other applications like autonomous scoring."
+ "content": "# Calibration and Image Rotation\n\n## Rotating Points\n\nTo stay consistent with the OpenCV camera coordinate frame, we put the origin in the top left, with X right, Y down, and Z out (as required by the right-hand rule). Intuitively though, if I ask you to rotate an image 90 degrees clockwise though, you'd probably rotate it about -Z in this coordinate system. Just be aware of this inconsistency.\n\n\n\nIf we have any one point in any of those coordinate systems, we can transform it into any of the other ones using standard geometry libraries by performing relative transformations (like in this pseudocode):\n\n```\nTranslation2d tag_corner1 = new Translation2d();\nTranslation2d rotated = tag_corner1.relativeTo(ORIGIN_ROTATED_90_CCW);\n```\n\n## Image Distortion\n\nThe distortion coefficients for OPENCV8 is given in order `[k1 k2 p1 p2 k3 k4 k5 k6]`. Mrcal names these coefficients `[k_0 k_1, k_2, k_3, k_4, k_5, k_6, k_7]`.\n\n```{math}\n \\begin{align*}\n \\vec P &\\equiv \\frac{\\vec p_{xy}}{p_z} \\\\\n r &\\equiv \\left|\\vec P\\right| \\\\\n \\vec P_\\mathrm{radial} &\\equiv \\frac{ 1 + k_0 r^2 + k_1 r^4 + k_4 r^6}{ 1 + k_5 r^2 + k_6 r^4 + k_7 r^6} \\vec P \\\\\n \\vec P_\\mathrm{tangential} &\\equiv\n \\left[ \\begin{aligned}\n 2 k_2 P_0 P_1 &+ k_3 \\left(r^2 + 2 P_0^2 \\right) \\\\\n 2 k_3 P_0 P_1 &+ k_2 \\left(r^2 + 2 P_1^2 \\right)\n \\end{aligned}\\right] \\\\\n \\vec q &= \\vec f_{xy} \\left( \\vec P_\\mathrm{radial} + \\vec P_\\mathrm{tangential} \\right) + \\vec c_{xy}\n \\end{align*}\n```\n\nFrom this, we observe at `k_0, k_1, k_4, k_5, k_6, k_7` depend only on the norm of {math}`\\vec P`, and will be constant given a rotated image. However, `k_2` and `k_3` go with {math}`P_0 \\cdot P_1`, `k_3` with {math}`P_0^2`, and `k_2` with {math}`P_1^2`.\n\nLet's try a concrete example. With a 90 degree CCW rotation, we have {math}`P0=-P_{1\\mathrm{rotated}}` and {math}`P1=P_{0\\mathrm{rotated}}`. Let's substitute in\n\n```{math}\n \\begin{align*}\n \\left[ \\begin{aligned}\n 2 k_2 P_0 P_1 &+ k_3 \\left(r^2 + 2 P_0^2 \\right) \\\\\n 2 k_3 P_0 P_1 &+ k_2 \\left(r^2 + 2 P_1^2 \\right)\n \\end{aligned}\\right] &=\n \\left[ \\begin{aligned}\n 2 k_{2\\mathrm{rotated}} (-P_{1\\mathrm{rotated}}) P_{0\\mathrm{rotated}} &+ k_{3\\mathrm{rotated}} \\left(r^2 + 2 (-P_{1\\mathrm{rotated}})^2 \\right) \\\\\n 2 k_{3\\mathrm{rotated}} (-P_{1\\mathrm{rotated}}) P_{0\\mathrm{rotated}} &+ k_{2\\mathrm{rotated}} \\left(r^2 + 2 P_{0\\mathrm{rotated}}^2 \\right)\n \\end{aligned}\\right] \\\\\n &=\n \\left[ \\begin{aligned}\n -2 k_{2\\mathrm{rotated}} P_{1\\mathrm{rotated}} P_{0\\mathrm{rotated}} &+ k_{3\\mathrm{rotated}} \\left(r^2 + 2 P_{1\\mathrm{rotated}}^2 \\right) \\\\\n -2 k_{3\\mathrm{rotated}} P_{1\\mathrm{rotated}} P_{0\\mathrm{rotated}} &+ k_{2\\mathrm{rotated}} \\left(r^2 + 2 P_{0\\mathrm{rotated}}^2 \\right)\n \\end{aligned}\\right]\n \\end{align*}\n```\n\nBy inspection, this results in just applying another 90 degree rotation to the k2/k3 parameters. Proof is left as an exercise for the reader. Note that we can repeat this rotation to yield equations for tangential distortion for 180 and 270 degrees.\n\n```{math}\n k_2'=-k_3\n k_3'=k_2\n```\n",
+ "content_preview": "# Calibration and Image Rotation\n\n## Rotating Points\n\nTo stay consistent with the OpenCV camera coordinate frame, we put the origin in the top left, with X right, Y down, and Z out (as required by the right-hand rule)."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/quick-start/wiring.html",
- "title": "Wiring",
+ "url": "https://docs.photonvision.org/en/latest/docs/apriltag-pipelines/about-apriltags.html",
+ "title": "About AprilTags",
"section": "Getting Started",
"language": "All",
- "content": "# Wiring\n\n## Coprocessor with regulator\n\n1. **IT IS STRONGLY RECOMMENDED** to use one of the recommended power regulators to prevent vision from cutting out from voltage drops while operating the robot. We recommend wiring the regulator directly to the power header pins using either of the two methods listed below or using a locking USB C cable.\n * Method 1: Soldering to GPIO Header Pins\n * Using 20 AWG or preferably 18 AWG wires, solder two wires from the regulator to the power header pins on the coprocessor and cover with heat-shrink tubing.\n * Method 2: Using a Wire-to-Board Connector\n * Using a wire-to-board connector with 20 AWG or preferably 18 AWG wires, connect two wires from the regulator to the power header pins on the coprocessor. To prevent the connector from becoming unseated, we recommend applying hot glue to the connector.\n\n2. Run an ethernet cable from your coprocessor to your network switch / radio.\n\n## Raspberry Pi and Orange Pi\n\nThis diagram shows how to use the recommended regulator to power a Raspberry Pi or Orange Pi.\n\n::::{tab-set}\n\n:::{tab-item} Orange Pi 5 Zinc V USB C\n\n```{image} images/OrangePiZincUSBC.png\n:alt: Wiring the opi5 to the pdp using the Redux Robotics Zinc V and usb c\n```\n\n:::\n\n:::{tab-item} Orange Pi 5 Zinc V\n\n```{image} images/OrangePiZinc.png\n:alt: Wiring the opi5 to the pdp using the Redux Robotics Zinc V\n```\n\n:::\n\n:::{tab-item} Orange Pi 5 Pololu S13V30F5\n\n```{image} images/OrangePiPololu.png\n:alt: Wiring the opi5 to the pdp using the Pololu S13V30F5\n```\n\n:::\n\n:::{tab-item} Orange Pi 5 Pololu S13V30F5 Pigtail\n\n```{image} images/OrangePiPololuPigtail.png\n:alt: Wiring the opi5 to the pdp using the Pololu S13V30F5 and a usb c pigtail\n```\n\n:::\n\n:::{tab-item} Raspberry Pi 5 Zinc V USB C\n\n```{image} images/RPiZincUSBC.png\n:alt: Wiring the RPI5 to the pdp using the Redux Robotics Zinc V and usb c\n```\n\n:::\n\n:::{tab-item} Raspberry Pi 5 Zinc V\n\n```{image} images/RPiZinc.png\n:alt: Wiring the RPI5 to the pdp using the Redux Robotics Zinc V\n```\n\n:::\n\n:::{tab-item} Raspberry Pi 5 Pololu S13V30F5\n\n```{image} images/RPiPololu.png\n:alt: Wiring the RPI5 to the pdp using the Pololu S13V30F5\n```\n\n:::\n\n:::{tab-item} Raspberry Pi 5 Pololu S13V30F5 Pigtail\n\n```{image} images/RPiPololuPigtail.png\n:alt: Wiring the RPI5 to the pdp using the Pololu S13V30F5 and a usb c pigtail\n```\n\n:::\n\n::::\n\nPigtails can be purchased from many sources we recommend [(USB C)](https://ctr-electronics.com/products/usb-type-c-wire-breakout?_pos=19&_sid=bf06b6a6b&_ss=r) [(Micro USB)](https://ctr-electronics.com/products/usb-micro-power-wire-breakout?pr_prod_strat=e5_desc&pr_rec_id=10bf36ce7&pr_rec_pid=7863771070637&pr_ref_pid=7863771103405&pr_seq=uniform)\n\n## RUBIK Pi\n\nThe RUBIK Pi has very different power requirements than the Orange Pi (or standard Raspberry Pi). In particular it requires 12V inputs, and has\na higher maximum power draw than those coprocessors. [First Rubik](https://first-rubik.github.io/docs/power/) has recommendations for both\non-robot and off-robot scenarios.\n\n## Limelight\n\nFollow the wiring instructions located in the [Limelight Documentation](https://docs.limelightvision.io/) for your Limelight model.\n\n## Coprocessor with Passive POE (Pi with SnakeEyes)\n\n1. Plug the [passive POE injector](https://www.revrobotics.com/rev-11-1210/) into the coprocessor and wire it to PDP/PDH (NOT the VRM).\n2. Add a breaker to relevant slot in your PDP/PDH\n3. Run an ethernet cable from the passive POE injector to your network switch / radio.\n\n## Off-Robot Wiring\n\nPlugging your coprocessor into the wall via a power brick will suffice for off robot wiring.\n\n:::{note}\nPlease make sure your chosen power supply can provide enough power for your coprocessor. Undervolting (where enough power isn't being supplied) can cause many issues.\n:::\n",
- "content_preview": "# Wiring\n\n## Coprocessor with regulator\n\n1. **IT IS STRONGLY RECOMMENDED** to use one of the recommended power regulators to prevent vision from cutting out from voltage drops while operating the robot."
+ "content": "# About AprilTags\n\n```{image} images/pv-apriltag.png\n:align: center\n:scale: 20 %\n```\n\nAprilTags are a common type of visual fiducial marker. Visual fiducial markers are artificial landmarks added to a scene to allow \"localization\" (finding your current position) via images. In simpler terms, tags mark known points of reference that you can use to find your current location. They are similar to QR codes in which they encode information, however, they hold only a single number. By placing AprilTags in known locations around the field and detecting them using PhotonVision, you can easily get full field localization / pose estimation. Alternatively, you can use AprilTags the same way you used retroreflective tape, simply using them to turn to goal without any pose estimation.\n\nA more technical explanation can be found in the [WPILib documentation](https://docs.wpilib.org/en/latest/docs/software/vision-processing/apriltag/apriltag-intro.html).\n\n:::{note}\nYou can get FIRST's [official PDF of the targets used in 2026 here](https://firstfrc.blob.core.windows.net/frc2026/FieldAssets/2026-apriltag-images-user-guide.pdf).\n:::\n",
+ "content_preview": "# About AprilTags\n\n```{image} images/pv-apriltag.png\n:align: center\n:scale: 20 %\n```\n\nAprilTags are a common type of visual fiducial marker. Visual fiducial markers are artificial landmarks added to a scene to allow \"localization\" (finding your current position) via images."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/apriltag-pipelines/detector-types.html",
- "title": "AprilTag Pipeline Types",
- "section": "AprilTag Detection",
+ "url": "https://docs.photonvision.org/en/latest/docs/camera-specific-configuration/picamconfig.html",
+ "title": "Pi Camera Configuration",
+ "section": "Camera Configuration",
"language": "All",
- "content": "# AprilTag Pipeline Types\n\nPhotonVision offers two different AprilTag pipeline types based on different implementations of the underlying algorithm. Each one has its advantages / disadvantages, which are detailed below.\n\n:::{note}\nNote that both of these pipeline types detect AprilTag markers and are just two different algorithms for doing so.\n:::\n\n## AprilTag\n\nThe AprilTag pipeline type is based on the [AprilTag](https://april.eecs.umich.edu/software/apriltag.html) library from the University of Michigan and we recommend it for most use cases. It is (to our understanding) most accurate pipeline type, but is also ~2x slower than ArUco. This was the pipeline type used by teams in the 2023 season and is well tested.\n\n## ArUco\n\nThe ArUco pipeline is based on the [ArUco](https://docs.opencv.org/4.8.0/d9/d6a/group__aruco.html) library implementation from OpenCV. It is ~2x higher fps and ~2x lower latency than the AprilTag pipeline type, but is less accurate. We recommend this pipeline type for teams that need to run at a higher framerate or have a lower powered device. This pipeline type was new for the 2024 season.\n",
- "content_preview": "# AprilTag Pipeline Types\n\nPhotonVision offers two different AprilTag pipeline types based on different implementations of the underlying algorithm. Each one has its advantages / disadvantages, which are detailed below.\n\n:::{note}\nNote that both of these pipeline types detect AprilTag markers and..."
+ "content": "# Pi Camera Configuration\n\nThis page covers specifics about the _Raspberry Pi_ CSI camera configuration.\n\n## Background\n\nThe Raspberry Pi CSI Camera port is routed through and processed by the GPU. Since the GPU boots before the CPU, it must be configured properly for the attached camera. Additionally, this configuration cannot be changed without rebooting.\n\nThe GPU is not always capable of detecting other cameras automatically. The file `/boot/config.txt` is parsed by the GPU at boot time to determine what camera, if any, is expected to be attached. This file must be updated for some cameras.\n\n:::{warning}\nIncorrect camera configuration will cause the camera to not be detected. It looks exactly the same as if the camera was unplugged.\n:::\n\n## Updating `config.txt`\n\nAfter flashing the pi image onto an SD card, open the `boot` segment in a file browser.\n\n:::{note}\nWindows may report \"There is a problem with this drive\". This should be ignored.\n:::\n\nLocate `config.txt` in the folder, and open it with your favorite text editor.\n\n```{image} images/bootConfigTxt.png\n\n```\n\nWithin the file, find this block of text:\n\n```\n##############################################################\n### PHOTONVISION CAM CONFIG\n### Comment/Uncomment to change which camera is supported\n### Picam V1, V2 or HQ: uncomment (remove leading # ) from camera_auto_detect=1,\n### and comment out all following lines\n### IMX290/327/OV9281/Any other cameras that require additional overlays:\n### Comment out (add a # ) to camera_auto_detect=1, and uncomment the line for\n### the sensor you're trying to user\n\ncameraAutoDetect=1\n\n# dtoverlay=imx290,clock-frequency=74250000\n# dtoverlay=imx290,clock-frequency=37125000\n# dtoverlay=imx378\n# dtoverlay=ov9281\n\n##############################################################\n```\n\nRemove the leading `#` character to uncomment the line associated with your camera. Add a `#` in front of other cameras.\n\n:::{warning}\nLeave lines outside the PhotonVision Camera Config block untouched. They are necessary for proper raspberry pi functionality.\n:::\n\nSave the file, close the editor, and eject the drive. The boot configuration should now be ready for your selected camera.\n\n## Additional Information\n\nSee [the libcamera documentation](https://github.com/raspberrypi/documentation/blob/679fab721855a3e8f17aa51819e5c2a7c447e98d/documentation/asciidoc/computers/camera/rpicam_configuration.adoc) for more details on configuring cameras.\n",
+ "content_preview": "# Pi Camera Configuration\n\nThis page covers specifics about the _Raspberry Pi_ CSI camera configuration.\n\n## Background\n\nThe Raspberry Pi CSI Camera port is routed through and processed by the GPU. Since the GPU boots before the CPU, it must be configured properly for the attached camera."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/integration/advancedStrategies.html",
- "title": "Advanced Strategies",
- "section": "Robot Integration",
+ "url": "https://docs.photonvision.org/en/latest/docs/apriltag-pipelines/index.html",
+ "title": "AprilTag Detection",
+ "section": "AprilTag Detection",
"language": "All",
- "content": "# Advanced Strategies\n\nAdvanced strategies for using vision processing results involve working with the robot's *pose* on the field.\n\nA *pose* is a combination an X/Y coordinate, and an angle describing where the robot's front is pointed. A pose is always considered *relative* to some fixed point on the field.\n\nWPILib provides a [Pose2d](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/geometry/pose.html) class to describe poses in software.\n\nPhotonVision can supply correcting information to keep estimates of *pose* accurate over a full match.\n\n## Knowledge and Equipment Needed\n\n- A Coprocessor running PhotonVision\n \\- Accurate camera calibration to support \"3D mode\" required\n- A Drivetrain with wheels and sensors\n \\- Sufficient sensors to measure wheel rotation\n \\- Capable of closed-loop velocity control\n- A gyroscope or IMU measuring actual robot heading\n- Experience using some path-planning library\n\n## Robot Poses from the Camera\n\nWhen using 3D mode in PhotonVision, an additional step is run to estimate the 3D position of camera, relative to one or more AprilTags.\n\nThis process does not produce a *unique* solution. There are multiple possible camera positions which might explain the image it observed. Additionally, the camera is rarely mounted in the exact center of a robot.\n\nFor these reasons, the 3D information must be filtered and transformed before they can describe the robot's pose.\n\nPhotonLib provides {ref}`a utility class to assist with this process on the roboRIO `. Alternatively, {ref}`a \"multi-tag\" strategy can do this process on the coprocessor. `.\n\n## Field-Relative Pose Estimation\n\nThe camera's guess of the robot pose generally should be *fused* with other sensor readings.\n\nWPILib provides [a set of pose estimation classes](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/state-space/state-space-pose-estimators.html) for doing this work.\n\n## I have a Pose Estimate, Now What?\n\n### Triggering Actions Automatically\n\nA simple way to use a pose estimate is to activate robot functions automatically when in the correct spot on the field.\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n Pose3d robotPose;\n boolean launcherSpinCmd;\n\n // ...\n\n if(robotPose.X() < 1.5){\n // Near blue alliance wall, start spinning the launcher wheel\n launcherSpinCmd = True;\n } else {\n // Far away, no need to run launcher.\n launcherSpinCmd = False;\n }\n\n // ...\n```\n\n### PathPlanning\n\nA common, but more complex usage of a pose estimate is an input to a path-following algorithm. Specifically, the pose estimate is used to correct for the robot straying off of the pre-defined path.\n\nSee the {ref}`Pose Estimation ` example for details on integrating this.\n",
- "content_preview": "# Advanced Strategies\n\nAdvanced strategies for using vision processing results involve working with the robot's *pose* on the field.\n\nA *pose* is a combination an X/Y coordinate, and an angle describing where the robot's front is pointed."
+ "content": "# AprilTag Detection\n\n```{toctree}\nabout-apriltags\ndetector-types\n2D-tracking-tuning\n3D-tracking\nmultitag\ncoordinate-systems\n```\n",
+ "content_preview": "# AprilTag Detection\n\n```{toctree}\nabout-apriltags\ndetector-types\n2D-tracking-tuning\n3D-tracking\nmultitag\ncoordinate-systems\n```\n"
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/objectDetection/rubik.html",
- "title": "Rubik Pi 3 Object Detection",
- "section": "Object Detection",
+ "url": "https://docs.photonvision.org/en/latest/docs/examples/index.html",
+ "title": "Code Examples - PhotonVision Docs",
+ "section": "Code Examples",
"language": "All",
- "content": "# Rubik Pi 3 Object Detection\n\n## How it works\n\nPhotonVision runs object detection on the Rubik Pi 3 by use of [TensorflowLite](https://github.com/tensorflow/tensorflow), and [this JNI code](https://github.com/PhotonVision/rubik_jni).\n\n## Supported models\n\nPhotonVision currently ONLY supports 640x640 Ultralytics YOLOv8 and YOLOv11 models trained and converted to `.tflite` format for QCS6490 SOCs! Other models require different post-processing code and will NOT work.\n\n## Converting Custom Models\n\n:::{warning}\nOnly quantized models are supported, so take care when exporting to select the option for quantization.\n:::\n\nPhotonVision now ships with a {{ '[Python Notebook](https://github.com/PhotonVision/photonvision/blob/{}/scripts/rubik_conversion.ipynb)'.format(git_tag_ref) }} that you can use in [Google Colab](https://colab.research.google.com), [Kaggle](https://kaggle.com/code), or in a local environment. In Google Colab, you can simply paste the PhotonVision GitHub URL into the \"GitHub\" tab and select the `rubik_conversion.ipynb` notebook without needing to manually download anything.\n\nPlease ensure that the model you are attempting to convert is among the {ref}`supported models ` and using the PyTorch format.\n\n## Benchmarking\n\nBefore you can perform benchmarking, it's necessary to install `tensorflow-lite-qcom-apps` with apt.\n\nBy SSHing into your Rubik Pi and running this command, replacing `PATH/TO/MODEL` with the path to your model, `benchmark_model --graph=src/test/resources/yolov8nCoco.tflite --external_delegate_path=/usr/lib/libQnnTFLiteDelegate.so --external_delegate_options=backend_type:htp --external_delegate_options=htp_use_conv_hmx:1 --external_delegate_options=htp_performance_mode:2` you can determine how long it takes for inference to be performed with your model.\n",
- "content_preview": "# Rubik Pi 3 Object Detection\n\n## How it works\n\nPhotonVision runs object detection on the Rubik Pi 3 by use of [TensorflowLite](https://github.com/tensorflow/tensorflow), and [this JNI code](https://github.com/PhotonVision/rubik_jni).\n\n## Supported models\n\nPhotonVision currently ONLY supports..."
+ "content": "Code Examples Aiming at a Target Combining Aiming and Getting in Range Using WPILib Pose Estimation, Simulation, and PhotonVision Together",
+ "content_preview": "Code Examples Aiming at a Target Combining Aiming and Getting in Range Using WPILib Pose Estimation, Simulation, and PhotonVision Together"
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/objectDetection/opi.html",
- "title": "Orange Pi 5 (and variants) Object Detection",
- "section": "Object Detection",
+ "url": "https://docs.photonvision.org/en/latest/docs/apriltag-pipelines/coordinate-systems.html",
+ "title": "Coordinate Systems",
+ "section": "AprilTag Detection",
"language": "All",
- "content": "# Orange Pi 5 (and variants) Object Detection\n\n## How it works\n\nPhotonVision runs object detection on the Orange Pi 5 by use of the RKNN model architecture, and [this JNI code](https://github.com/PhotonVision/rknn_jni).\n\n## Supported models\n\nPhotonVision currently ONLY supports 640x640 Ultralytics YOLOv5, YOLOv8, and YOLOv11 models trained and converted to `.rknn` format for RK3588 SOCs! Other models require different post-processing code and will NOT work.\n\n## Converting Custom Models\n\n:::{warning}\nOnly quantized models are supported, so take care when exporting to select the option for quantization.\n:::\n\nPhotonVision now ships with a {{ '[Python Notebook](https://github.com/PhotonVision/photonvision/blob/{}/scripts/rknn_conversion.ipynb)'.format(git_tag_ref) }} that you can use in [Google Colab](https://colab.research.google.com) or in a local **Linux** environment (since `rknn-toolkit2` only supports Linux). In Google Colab, you can simply paste the PhotonVision GitHub URL into the \"GitHub\" tab and select the `rknn_conversion.ipynb` notebook without needing to manually download anything.\n\nPlease ensure that the model you are attempting to convert is among the {ref}`supported models ` and using the PyTorch format.\n",
- "content_preview": "# Orange Pi 5 (and variants) Object Detection\n\n## How it works\n\nPhotonVision runs object detection on the Orange Pi 5 by use of the RKNN model architecture, and [this JNI code](https://github.com/PhotonVision/rknn_jni).\n\n## Supported models\n\nPhotonVision currently ONLY supports 640x640 Ultralytics..."
+ "content": "# Coordinate Systems\n\n## Field and Robot Coordinate Frame\n\nPhotonVision follows the WPILib conventions for the robot and field coordinate systems, as defined [here](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/geometry/coordinate-systems.html).\n\nYou define the camera to robot transform in the robot coordinate frame.\n\n## Camera Coordinate Frame\n\nOpenCV by default uses x-left/y-down/z-out for camera transforms. PhotonVision applies a base rotation to this transformation to make robot to tag transforms more in line with the WPILib coordinate system. The x, y, and z axes are also shown in red, green, and blue in the 3D mini-map and targeting overlay in the UI.\n\n- The origin is the focal point of the camera lens\n- The x-axis points out of the camera\n- The y-axis points to the left\n- The z-axis points upwards\n\n```{image} images/camera-coord.png\n:align: center\n:scale: 45 %\n```\n\n```{image} images/multiple-tags.png\n:align: center\n:scale: 45 %\n```\n\n## AprilTag Coordinate Frame\n\nThe AprilTag coordinate system is defined as follows, relative to the center of the AprilTag itself, and when viewing the tag as a robot would. Again, PhotonVision changes this coordinate system to be more in line with WPILib. This means that a robot facing a tag head-on would see a robot-to-tag transform with a translation only in x, and a rotation of 180 degrees about z. The tag coordinate system is also shown with x/y/z in red/green/blue in the UI target overlay and mini-map.\n\n- The origin is the center of the tag\n- The x-axis is normal to the plane the tag is printed on, pointing outward from the visible side of the tag.\n- The y-axis points to the right\n- The z-axis points upwards\n\n```{image} images/apriltag-coords.png\n:align: center\n:scale: 45 %\n```\n",
+ "content_preview": "# Coordinate Systems\n\n## Field and Robot Coordinate Frame\n\nPhotonVision follows the WPILib conventions for the robot and field coordinate systems, as defined [here](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/geometry/coordinate-systems.html).\n\nYou define the camera to robot..."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/programming/photonlib/adding-vendordep.html",
- "title": "Installing PhotonLib",
- "section": "PhotonLib",
+ "url": "https://docs.photonvision.org/en/latest/docs/quick-start/camera-matching.html",
+ "title": "Camera Matching",
+ "section": "Getting Started",
"language": "All",
- "content": "# Installing PhotonLib\n\n## What is PhotonLib?\n\nPhotonLib is the C++ and Java vendor dependency that accompanies PhotonVision. We created this vendor dependency to make it easier for teams to retrieve vision data from their integrated vision system.\n\nPhotonLibPy is a minimal, pure-python implementation of PhotonLib.\n\n## Online Install - Java/C++\n\nClick on the WPILib logo in the activity bar to access the Vendor Dependencies interface.\n\n```{image} images/wpilib-vendor-dependencies.png\n:scale: 50%\n:align: center\n:alt: WPILib Vendor Dependencies\n```\n\nSelect the install button for the \"PhotonLib\" dependency.\n\n```{image} images/photonlib-install.png\n:scale: 50%\n:align: center\n:alt: PhotonLib Install Button\n```\n\n:::{note}\nThe Dependency Manager will automatically build your program when it loses focus. This allows you to use the changed dependencies.\n:::\n\nWhen an update is available for PhotonLib, a \"To Latest\" button will become available. This will update the vendordep to the latest version of PhotonLib.\n\n```{image} images/photonlib-to-latest.png\n:align: center\n:alt: PhotonLib Update Button\n```\n\nRefer to [The WPILib docs](https://docs.wpilib.org/en/stable/docs/software/vscode-overview/3rd-party-libraries.html#installing-libraries) for more details on installing vendor libraries.\n\n## Offline Install - Java/C++\n\nDownload the latest PhotonLib release from our [GitHub releases page](https://github.com/PhotonVision/photonvision/releases) (named in the format `photonlib-VERSION.zip`), and extract the contents to `~/wpilib/YYYY/vendordeps` (where YYYY is the year and ~ is `C:\\Users\\Public` on Windows). This adds PhotonLib maven artifacts to your local maven repository. PhotonLib will now also appear available in the \"install vendor libraries (offline)\" menu in WPILib VSCode. Refer to [the WPILib docs](https://docs.wpilib.org/en/stable/docs/software/vscode-overview/3rd-party-libraries.html#how-does-it-work) for more details on installing vendor libraries offline.\n\n## Install - Python\n\nAdd photonlibpy to `pyproject.toml`.\n\n```toml\n# Other pip packages to install\nrequires = [\n \"photonlibpy\",\n]\n```\n\nSee [The WPILib/RobotPy docs](https://docs.wpilib.org/en/stable/docs/software/python/pyproject_toml.html) for more information on using `pyproject.toml.`\n\n## Install Specific Version - Java/C++\n\nIn cases where you want to test a specific version of PhotonLib, make sure you have finished the steps in Online Install - Java/C++ and then manually change the version string in the PhotonLib vendordep json file(at ``/path/to/your/project/vendordep/photonlib.json``) to your desired version.\n\n```{image} images/photonlib-vendordep-json.jpg\n```\n",
- "content_preview": "# Installing PhotonLib\n\n## What is PhotonLib?\n\nPhotonLib is the C++ and Java vendor dependency that accompanies PhotonVision. We created this vendor dependency to make it easier for teams to retrieve vision data from their integrated vision system.\n\nPhotonLibPy is a minimal, pure-python..."
+ "content": "# Camera Matching\n\n## Activating and Deactivating Cameras\n\nWhen you first plug in a camera, it will be detected and added to the list of cameras with the \"Unassigned\" status, as shown below. You can press the \"Activate\" button to enable PhotonVision to use the camera.\n\n```{image} images/camera-matching/unassigned-camera.png\n:scale: 50%\n```\n\nIf a camera has been activated in the past, it will be listed as \"Deactivated\" in the camera list. You can press the \"Activate\" button to enable PhotonVision to use the camera.\n\n```{image} images/camera-matching/deactivated-camera.png\n:scale: 50%\n```\n\nOnce a camera is activated, it will be listed as \"Active\" in the camera list. You can press the \"Deactivate\" button to stop PhotonVision from using the camera.\n\n```{image} images/camera-matching/activated-camera.png\n:scale: 50%\n```\n\n## Deleting Cameras\n\nIf you want to remove a camera from the list, you can press the delete button. This will clear all settings for that particular camera, including the calibration data and any other settings you have configured. It is recommended to make a backup of the camera's settings before deleting it, as this action cannot be undone.\n\n## Matching Cameras\n\nWhen you plug in a camera, PhotonVision will attempt to match it to a previously configured camera based on the physical USB port it is connected to. If you plug another camera into that port, the cameras will have a \"Camera Mismatch\" status, indicating that the camera is not recognized as the one that was previously configured.\n\nAdditionally, pressing on the Details button will show you the details of the camera mismatch, allowing you to compare the current camera with the previously configured camera.\n\n```{image} images/camera-matching/camera-mismatch-details.png\n:scale: 50%\n```\n\n```{note}\nCamera matching is based on the USB ports on the device. If you unplug a camera and plug it into a different port, PhotonVision will attempt to use settings from the camera that was previously configured in that port, causing unexpected behavior.\n```\n\nTo resolve the camera mismatch, you should ensure each camera is plugged into the same port that you configured it in.\n",
+ "content_preview": "# Camera Matching\n\n## Activating and Deactivating Cameras\n\nWhen you first plug in a camera, it will be detected and added to the list of cameras with the \"Unassigned\" status, as shown below."
},
{
"url": "https://docs.photonvision.org/en/latest/docs/contributing/index.html",
@@ -500,76 +212,52 @@
"content_preview": "# Contributing to PhotonVision Projects\n\n```{toctree}\nguidelines\nbuilding-photon\nbuilding-docs\nlinting\ndeveloper-docs/index\ndesign-descriptions/index\n```\n"
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/contributing/design-descriptions/camera-matching.html",
- "title": "Camera Matching",
- "section": "Contributing",
- "language": "All",
- "content": "# Camera Matching\n\nDiagrams generated by the [PlantUML UML editor](https://www.plantuml.com/plantuml/). Copy the image URLs below and decode in the editor to make changes.\n\n## Initial Setup\n\nWhen PhotonVision first starts, settings are loaded from disk and [VisionSources](https://javadocs.photonvision.org/release/org/photonvision/vision/processes/VisionSource.html) are created for every serialized & active [Camera Configuration](https://javadocs.photonvision.org/release/org/photonvision/common/configuration/CameraConfiguration.html)\n\n\n\n## UI Workflow\n\nA [background thread](https://javadocs.photonvision.org/org/photonvision/common/util/TimedTaskManager.html) will periodically query CSCore and Libcamera for what cameras we currently see connected. This list is provided to the web UI for display.\n\n\n\n\n\nThis UI allows users to \"Activate\" a camera that's never been seen before, or activate a CameraConfiguration we've seen before but was disabled. Allowing camera configurations to be saved but not loaded by default lets us support temporarily disabling/unplugging a camera without flooding log files.\n\nSince our backend logic intentionally does not protect users from plugging camera B into the port that camera A was active on, the UI shall show a warning but vision processing will (attempt to) continue like normal.\n\n### Activate New Camera\n\nWhen a new camera (ie, one we can't match by-path to a deserialized CameraConfiguration) is activated, we'll create a spin up a new Vision Module for it\n\n\n\n### Deactivate Camera\n\nDeactivating a camera will release the native resources it owns, and return the CameraConfiguration to the pool of currently disabled cameras we can re-enable later.\n\n\n\n### Reactivate a CameraConfig\n\nWhen a new camera (ie, one we can't match by-path to a deserialized CameraConfiguration) is activated, we'll create and spin up a new Vision Module for it.\n\n\n\n# Camera Matching Requirements\n\n## Definitions\n- VALID USB PATH: a path in the form `/dev/v4l/by-path/[UUID]`\n- VIDEO DEVICE PATH: a CSCore-provided identifier derived from the V4L path `/dev/video[N]` on Linux, or an opaque string on Windows\n- UNIQUE NAME: an identifier that is unique within the set of all deserialized CameraConfigurations and unmatched USB cameras\n - I don't love this, it means that a USB camera matched to a VisionModule will share a UNIQUE NAME, right?\n- DESERIALIZED CAMERA CONFIGURATIONS: The set of camera configurations loaded from disk and provided to the VisionSourceManager. This configuration data structure includes the UNIQUE NAME\n- CURRENTLY ACTIVE CAMERAS: The set of VisionModules currently active and processing vision data, and associated metadata\n\n## Startup:\n\n- GIVEN An empty set of deserialized Camera Configurations\n WHEN PhotonVision starts\n THEN no VisionModules will be started\n\n- GIVEN A valid set of deserialized Camera Configurations\n WHEN PhotonVision starts\n THEN VisionModules will be started FOR EACH un-DISABLED config\n\n- GIVEN A valid set of deserialized Camera Configurations\n WHEN PhotonVision starts\n THEN VisionModules will NOT be started FOR EACH DISABLED config\n\n- GIVEN A CameraConfiguration with a VALID USB PATH\n WHEN a VisionModule is created\n THEN The VisionModule shall open the camera using the USB path\n\n- GIVEN A CameraConfiguration without a valid USB path\n WHEN a VisionModule is created\n THEN The VisionModule shall open the camera using the VIDEO DEVICE PATH\n\n## Camera (re)enumeration:\n\n- GIVEN a NEW USB CAMERA is available for enumeration\n WHEN a USB camera is discovered by VisionSourceManager\n AND the USB camera's VIDEO DEVICE PATH is not in the set of DESERIALIZED CAMERA CONFIGURATIONS\n THEN a UNIQUE NAME will be assigned to the camera info\n\n- GIVEN a NEW USB CAMERA is available for enumeration\n WHEN a USB camera is discovered by VisionSourceManager\n AND the USB camera's VIDEO DEVICE PATH is in the set of DESERIALIZED CAMERA CONFIGURATIONS\n THEN a UNIQUE NAME equal to the matching DESERIALIZED CAMERA CONFIGURATION will be assigned to the camera info\n - This is a weird case. How -should- we handle this? see above\n\n## Creating from a new camera\n\n- Given: A UNIQUE NAME from a NEW USB CAMERA\n WHEN I request a new VisionModule is created for this NEW USB CAMERA\n AND the camera has a VALID USB PATH\n AND the camera's VALID USB PATH is not in use by any CURRENTLY ACTIVE CAMERAS\n THEN a NEW VisionModule will be started for the NEW USB CAMERA using the VALID USB PATH\n\n- Given: A UNIQUE NAME from a NEW USB CAMERA\n WHEN I request a new VisionModule is created for this NEW USB CAMERA\n AND the camera does not have a VALID USB PATH\n AND the camera's VIDEO DEVICE PATH is not in use by any CURRENTLY ACTIVE CAMERAS\n THEN a NEW VisionModule will be started for the NEW USB CAMERA using the VIDEO DEVICE PATH\n\n## Deactivate\n\n- Given: A UNIQUE NAME from a CURRENTLY ACTIVE CAMERA\n WHEN I request the VisionModule be DEACTIVATED\n THEN the VisionModule will be stopped for the given CURRENTLY ACTIVE CAMERA\n AND the CameraConfiguration DISABLED flag will be set to TRUE\n\n## Reactivate\n\n- Given: A UNIQUE NAME from a DESERIALIZED CAMERA CONFIGURATIONS\n WHEN I request the VisionModule be ACTIVATED\n AND the CameraConfiguration's DISABLED flag is TRUE\n THEN a VisionModule will be created and started for the camera\n",
- "content_preview": "# Camera Matching\n\nDiagrams generated by the [PlantUML UML editor](https://www.plantuml.com/plantuml/). Copy the image URLs below and decode in the editor to make changes.\n\n## Initial Setup\n\nWhen PhotonVision first starts, settings are loaded from disk and..."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/programming/photonlib/index.html",
- "title": "PhotonLib: Robot Code Interface",
- "section": "PhotonLib",
+ "url": "https://docs.photonvision.org/en/latest/docs/quick-start/wiring.html",
+ "title": "Wiring",
+ "section": "Getting Started",
"language": "All",
- "content": "# PhotonLib: Robot Code Interface\n\n```{toctree}\n:maxdepth: 1\n\nadding-vendordep\ngetting-target-data\nusing-target-data\nrobot-pose-estimator\ndriver-mode-pipeline-index\ncontrolling-led\nfps-limiter\n```\n",
- "content_preview": "# PhotonLib: Robot Code Interface\n\n```{toctree}\n:maxdepth: 1\n\nadding-vendordep\ngetting-target-data\nusing-target-data\nrobot-pose-estimator\ndriver-mode-pipeline-index\ncontrolling-led\nfps-limiter\n```\n"
+ "content": "# Wiring\n\n## Coprocessor with regulator\n\n1. **IT IS STRONGLY RECOMMENDED** to use one of the recommended power regulators to prevent vision from cutting out from voltage drops while operating the robot. We recommend wiring the regulator directly to the power header pins using either of the two methods listed below or using a locking USB C cable.\n * Method 1: Soldering to GPIO Header Pins\n * Using 20 AWG or preferably 18 AWG wires, solder two wires from the regulator to the power header pins on the coprocessor and cover with heat-shrink tubing.\n * Method 2: Using a Wire-to-Board Connector\n * Using a wire-to-board connector with 20 AWG or preferably 18 AWG wires, connect two wires from the regulator to the power header pins on the coprocessor. To prevent the connector from becoming unseated, we recommend applying hot glue to the connector.\n\n2. Run an ethernet cable from your coprocessor to your network switch / radio.\n\n## Raspberry Pi and Orange Pi\n\nThis diagram shows how to use the recommended regulator to power a Raspberry Pi or Orange Pi.\n\n::::{tab-set}\n\n:::{tab-item} Orange Pi 5 Zinc V USB C\n\n```{image} images/OrangePiZincUSBC.png\n:alt: Wiring the opi5 to the pdp using the Redux Robotics Zinc V and usb c\n```\n\n:::\n\n:::{tab-item} Orange Pi 5 Zinc V\n\n```{image} images/OrangePiZinc.png\n:alt: Wiring the opi5 to the pdp using the Redux Robotics Zinc V\n```\n\n:::\n\n:::{tab-item} Orange Pi 5 Pololu S13V30F5\n\n```{image} images/OrangePiPololu.png\n:alt: Wiring the opi5 to the pdp using the Pololu S13V30F5\n```\n\n:::\n\n:::{tab-item} Orange Pi 5 Pololu S13V30F5 Pigtail\n\n```{image} images/OrangePiPololuPigtail.png\n:alt: Wiring the opi5 to the pdp using the Pololu S13V30F5 and a usb c pigtail\n```\n\n:::\n\n:::{tab-item} Raspberry Pi 5 Zinc V USB C\n\n```{image} images/RPiZincUSBC.png\n:alt: Wiring the RPI5 to the pdp using the Redux Robotics Zinc V and usb c\n```\n\n:::\n\n:::{tab-item} Raspberry Pi 5 Zinc V\n\n```{image} images/RPiZinc.png\n:alt: Wiring the RPI5 to the pdp using the Redux Robotics Zinc V\n```\n\n:::\n\n:::{tab-item} Raspberry Pi 5 Pololu S13V30F5\n\n```{image} images/RPiPololu.png\n:alt: Wiring the RPI5 to the pdp using the Pololu S13V30F5\n```\n\n:::\n\n:::{tab-item} Raspberry Pi 5 Pololu S13V30F5 Pigtail\n\n```{image} images/RPiPololuPigtail.png\n:alt: Wiring the RPI5 to the pdp using the Pololu S13V30F5 and a usb c pigtail\n```\n\n:::\n\n::::\n\nPigtails can be purchased from many sources we recommend [(USB C)](https://ctr-electronics.com/products/usb-type-c-wire-breakout?_pos=19&_sid=bf06b6a6b&_ss=r) [(Micro USB)](https://ctr-electronics.com/products/usb-micro-power-wire-breakout?pr_prod_strat=e5_desc&pr_rec_id=10bf36ce7&pr_rec_pid=7863771070637&pr_ref_pid=7863771103405&pr_seq=uniform)\n\n## RUBIK Pi\n\nThe RUBIK Pi has very different power requirements than the Orange Pi (or standard Raspberry Pi). In particular it requires 12V inputs, and has\na higher maximum power draw than those coprocessors. [First Rubik](https://first-rubik.github.io/docs/power/) has recommendations for both\non-robot and off-robot scenarios.\n\n## Limelight\n\nFollow the wiring instructions located in the [Limelight Documentation](https://docs.limelightvision.io/) for your Limelight model.\n\n## Coprocessor with Passive POE (Pi with SnakeEyes)\n\n1. Plug the [passive POE injector](https://www.revrobotics.com/rev-11-1210/) into the coprocessor and wire it to PDP/PDH (NOT the VRM).\n2. Add a breaker to relevant slot in your PDP/PDH\n3. Run an ethernet cable from the passive POE injector to your network switch / radio.\n\n## Off-Robot Wiring\n\nPlugging your coprocessor into the wall via a power brick will suffice for off robot wiring.\n\n:::{note}\nPlease make sure your chosen power supply can provide enough power for your coprocessor. Undervolting (where enough power isn't being supplied) can cause many issues.\n:::\n",
+ "content_preview": "# Wiring\n\n## Coprocessor with regulator\n\n1. **IT IS STRONGLY RECOMMENDED** to use one of the recommended power regulators to prevent vision from cutting out from voltage drops while operating the robot."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/additional-resources/nt-api.html",
- "title": "NetworkTables API",
- "section": "Additional Resources",
+ "url": "https://docs.photonvision.org/en/latest/docs/troubleshooting/common-errors.html",
+ "title": "Common Issues / Questions",
+ "section": "Troubleshooting",
"language": "All",
- "content": "# NetworkTables API\n\n## About\n\n:::{warning}\nPhotonVision interfaces with PhotonLib, our vendor dependency, using NetworkTables. If you are running PhotonVision on a robot (ie. with a RoboRIO), you should **turn the NetworkTables server switch (in the settings tab) off** in order to get PhotonLib to work. Also ensure that you set your team number. **The NetworkTables server should only be enabled if you know what you're doing!**\n:::\n\n## API\n\n:::{warning}\nNetworkTables is not a supported setup/viable option when using PhotonVision as we only send one target at a time (this is problematic when using AprilTags, which will return data from multiple tags at once).\n\n**We strongly recommend using PhotonLib instead, as the NetworkTables API will most likely be removed in 2027.**\n:::\n\nThe tables below contain the the name of the key for each entry that PhotonVision sends over the network and a short description of the key. The entries should be extracted from a subtable with your camera's nickname (visible in the PhotonVision UI) under the main `photonvision` table.\n\n### Getting Target Information\n\n| Key | Type | Description |\n| --------------- | ---------- | ------------------------------------------------------------------------ |\n| `rawBytes` | `byte[]` | A byte-packed string that contains target info from the same timestamp. |\n| `latencyMillis` | `double` | The latency of the pipeline in milliseconds. |\n| `hasTarget` | `boolean` | Whether the pipeline is detecting targets or not. |\n| `targetPitch` | `double` | The pitch of the target in degrees (positive up). |\n| `targetYaw` | `double` | The yaw of the target in degrees (positive right). |\n| `targetArea` | `double` | The area (percent of bounding box in screen) as a percent (0-100). |\n| `targetSkew` | `double` | The skew of the target in degrees (counter-clockwise positive). |\n| `targetPose` | `double[]` | The pose of the target relative to the robot (x, y, z, qw, qx, qy, qz) |\n| `targetPixelsX` | `double` | The target crosshair location horizontally, in pixels (origin top-right) |\n| `targetPixelsY` | `double` | The target crosshair location vertically, in pixels (origin top-right) |\n\n### Changing Settings\n\n| Key | Type | Description |\n| --------------- | --------- | --------------------------- |\n| `pipelineIndex` | `int` | Changes the pipeline index. |\n| `driverMode` | `boolean` | Toggles driver mode. |\n\n### Saving Images\n\nPhotonVision can save images to file on command. The image is saved when PhotonVision detects the command went from `false` to `true`.\n\nPhotonVision will automatically set these back to `false` after 500ms.\n\nBe careful saving images rapidly - it will slow vision processing performance and take up disk space very quickly.\n\nImages are returned as part of the .zip package from the \"Export\" operation in the Settings tab.\n\n| Key | Type | Description |\n| ------------------ | --------- | ------------------------------------------------- |\n| `inputSaveImgCmd` | `boolean` | Triggers saving the current input image to file. |\n| `outputSaveImgCmd` | `boolean` | Triggers saving the current output image to file. |\n\n:::{warning}\nIf you manage to make calls to these commands faster than 500ms (between calls), additional photos will not be captured.\n:::\n\n### Global Entries\n\nThese entries are global, meaning that they should be called on the main `photonvision` table.\n\n| Key | Type | Description |\n| --------- | ----- | -------------------------------------------------------- |\n| `ledMode` | `int` | Sets the LED Mode (-1: default, 0: off, 1: on, 2: blink) |\n\n:::{warning}\nSetting the LED mode to -1 (default) when `multiple` cameras are connected may result in unexpected behavior. {ref}`This is a known limitation of PhotonVision. `\n\nSingle camera operation should work without issue.\n:::\n",
- "content_preview": "# NetworkTables API\n\n## About\n\n:::{warning}\nPhotonVision interfaces with PhotonLib, our vendor dependency, using NetworkTables. If you are running PhotonVision on a robot (ie."
+ "content": "# Common Issues / Questions\n\nThis page will grow as needed in order to cover commonly seen issues by teams. If this page doesn't help you and you need further assistance, feel free to {ref}`Contact Us`.\n\n## Known Issues\n\nAll known issues can be found on our [GitHub page](https://github.com/PhotonVision/photonvision/issues).\n\n### PS3Eye\n\nDue to an issue with Linux kernels, the drivers for the PS3Eye are no longer supported. If you would still like to use the PS3Eye, you can downgrade your kernel with the following command: `sudo CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt rpi-update 866751bfd023e72bd96a8225cf567e03c334ecc4`. Note: You must be connected to the internet to run the command.\n\n### LED Control\n\nThe logic for controlling LED mode when `multiple cameras are connected` is not fully fleshed out. In its current state, LED control is only enabled when a Pi Camera Module is not in driver mode—meaning a USB camera on its own is unable to control the LEDs.\n\nFor now, if you are using multiple cameras, it is recommended that teams set the value of the NetworkTables entry {code}`photonvision/ledMode` from the robot code to control LED state.\n\n## Commonly Seen Issues\n\n### Networking Issues\n\nPlease refer to our comprehensive {ref}`networking troubleshooting tips ` for debugging suggestions and possible causes.\n\n### Camera won't show up\n\nTry these steps to {ref}`troubleshoot your camera connection `.\n\nIf you are using a USB camera, it is possible your USB Camera isn't supported by CSCore and therefore won't work with PhotonVision.\n\n### Camera is consistently returning incorrect values when in 3D mode\n\nRead the tips on the {ref}`camera calibration page`, follow the advice there, and redo the calibration.\n\n### Not getting data from PhotonLib\n\n1. Ensure your coprocessor version and PhotonLib version match. This can be checked by the settings tab and examining the .json itself (respectively).\n2. Ensure that you have your team number set properly.\n3. Use Glass to verify that PhotonVision has connected to the NetworkTables server served by your robot. With Glass connected in client mode to your RoboRIO, we expect to see \"photonvision\" listed under the Clients tab of the NetworkTables Info pane.\n\n```{image} images/glass-connections.png\n:alt: Using Glass to check NT connections\n:width: 600\n```\n\n4. When creating a `PhotonCamera` in code, does the `cameraName` provided match the name in the upper-right card of the web interface? Glass can be used to verify the RoboRIO is receiving NetworkTables data by inspecting the `photonvision` subtable for your camera nickname.\n\n```{image} images/camera-subtable.png\n:alt: Using Glass to check camera publishing\n:width: 600\n```\n\n### Unable to download PhotonLib\n\nEnsure all of your network firewalls are disabled and you aren't on a school-network.\n\n### PhotonVision prompts for login on startup\n\nThis is normal. You don't need to connect a display to your Raspberry Pi to use PhotonVision, just navigate to the relevant webpage (ex. `photonvision.local:5800`) in order to see the dashboard.\n\n### Raspberry Pi enters into boot looping state when using PhotonVision\n\nThis is most commonly seen when your Pi doesn't have adequate power / is being undervolted. Ensure that your power supply is functioning properly.\n",
+ "content_preview": "# Common Issues / Questions\n\nThis page will grow as needed in order to cover commonly seen issues by teams. If this page doesn't help you and you need further assistance, feel free to {ref}`Contact Us`.\n\n## Known Issues\n\nAll known issues can be found on our [GitHub..."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/integration/simpleStrategies.html",
- "title": "Simple Strategies",
+ "url": "https://docs.photonvision.org/en/latest/docs/integration/background.html",
+ "title": "Vision - Robot Integration Background",
"section": "Robot Integration",
"language": "All",
- "content": "# Simple Strategies\n\nSimple strategies for using vision processor outputs involve using the target's position in the 2D image to infer *range* and *angle* to a particular AprilTag.\n\n## Knowledge and Equipment Needed\n\n- A Coprocessor running PhotonVision\n- A Drivetrain with wheels\n- An AprilTag to aim at\n\n## Angle Alignment\n\nThe simplest way to align a robot to an AprilTag is to rotate the drivetrain until the tag is centered in the camera image. To do this,\n\n1. Read the current yaw angle to the AprilTag from the vision Coprocessor.\n2. If too far off to one side, command the drivetrain to rotate in the opposite direction to compensate.\n\nSee the {ref}`Aiming at a Target ` example for more information.\n\nNOTE: This works if the camera is centered on the robot. This is easiest from a software perspective. If the camera is not centered, take a peek at the next example - it shows how to account for an offset.\n\n## Adding Range Alignment\n\nBy looking at the position of the AprilTag in the \"vertical\" direction in the image, and applying some trigonometry, the distance between the robot and the camera can be deduced.\n\n1. Read the current pitch angle to the AprilTag from the vision coprocessor.\n2. Do math to calculate the distance to the AprilTag.\n2. If too far in one direction, command the drivetrain to travel in the opposite direction to compensate.\n\nThis can be done simultaneously while aligning to the desired angle.\n\nSee the {ref}`Aim and Range ` example for more information.\n",
- "content_preview": "# Simple Strategies\n\nSimple strategies for using vision processor outputs involve using the target's position in the 2D image to infer *range* and *angle* to a particular AprilTag.\n\n## Knowledge and Equipment Needed\n\n- A Coprocessor running PhotonVision\n- A Drivetrain with wheels\n- An AprilTag to..."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/pipelines/about-pipelines.html",
- "title": "About Pipelines",
- "section": "Getting Started",
- "language": "All",
- "content": "---\norphan: true\n---\n\n# About Pipelines\n\n## What is a pipeline?\n\nA vision pipeline represents a series of steps that are used to acquire an image, process it, and analyzing it to find a target. In most FRC games, this means processing an image in order to detect a piece of retroreflective tape or an AprilTag.\n\n## Types of Pipelines\n\n### AprilTag / ArUco\n\nThis pipeline type is based on detecting AprilTag fiducial markers. More information about AprilTags can be found in the [WPILib documentation](https://docs.wpilib.org/en/stable/docs/software/vision-processing/apriltag/apriltag-intro.html). This pipeline provides easy to use 3D pose information which allows localization.\n\n:::{note}\nIn order to get 3D Pose data about AprilTags, you are required to {ref}`calibrate your camera`.\n:::\n\n### Object Detection\n\nThis pipeline type is based on detecting objects using a neural network. The object detection pipeline uses a pre-trained model to detect objects in the camera stream.\n\n:::{note}\nThis pipeline type is only supported on the Orange Pi 5/5+ coprocessors due to its Neural Processing Unit used by PhotonVision to support running ML-based object detection.\n:::\n\n### Driver Mode\n\nDriver Mode is a type of pipeline that doesn't run any vision processing, intended for human viewing. For more information about Driver Mode, see the {ref}`Driver Mode documentation`.\n\n### Colored Shape\n\nThis pipeline type is based on detecting different shapes like circles, triangles, quadrilaterals, or a polygon. An example usage would be detecting yellow PowerCells from the 2020 FRC game. You can read more about the specific settings available in the contours page.\n\n### Reflective\n\nThis pipeline type is based on detecting targets with reflective tape. In the contours tab of this pipeline type, you can filter the area, width/height ratio, fullness, degree of speckle rejection.\n\n:::{note}\nThis pipeline type is not used anymore due to FRC's removal of retro-reflective tape from the game. It is still available as a pipeline for legacy purposes.\n:::\n\n## Note About Multiple Cameras and Pipelines\n\nWhen using more than one camera, it is important to keep in mind that all cameras run one pipeline each, all publish to NT, and all send both streams. This will have a noticeable affect on performance and we recommend users limit themselves to 1-2 cameras per coprocessor.\n\n## Pipeline Configuration\n\nEach pipeline has a set of tabs that are used to configure the pipeline. All pipelines follow a similar structure with an Input and Output tab, as well as a set of tabs that are specific to the pipeline type.\n\n- Input: This tab allows the raw camera image to be modified before it gets processed. Here, you can set exposure, brightness, gain, orientation, and resolution.\n\n- Output: This allows you to manipulate the detected target via the target offset point (for calculating pitch/yaw) and robot (crosshair) offset. In addition, it allows users to send additional (up to 5) outputs through PhotonLib.\n\nPipielines also have additional tabs that are specific to the pipeline type. Listed below are the tabs for each pipeline type.\n\n### AprilTag / ArUco Pipelines\n\n- AprilTag: This tab includes AprilTag specific tuning parameters, such as decimate, blur, threads, pose iterations, and more.\n\n### Object Detection Pipelines\n\n- Object Detection: This tab allows you to filter results from the neural network, such as confidence, area, and width/height ratio. The end goal of this tab is to filter out any false positives.\n\n### Reflective and Colored Shape Pipelines\n\n- Threshold: This tab allows you to filter out specific colors/pixels in your camera stream through HSV tuning. The end goal here is having a black and white image that will only have your target lit up.\n- Contours: After thresholding, contiguous white pixels are grouped together, and described by a curve that outlines the group. This curve is called a \"contour\" which represent various targets on your screen. Regardless of type, you can filter how the targets are grouped, their intersection, and how the targets are sorted. Other available filters will change based on different pipeline types.\n",
- "content_preview": "---\norphan: true\n---\n\n# About Pipelines\n\n## What is a pipeline?\n\nA vision pipeline represents a series of steps that are used to acquire an image, process it, and analyzing it to find a target."
+ "content": "# Vision - Robot Integration Background\n\n## Vision Processing's Purpose\n\nEach year, the FRC game requires a fundamental operation: **Align the Robot to a Goal**.\n\nRegardless of whether that alignment point is for picking up gamepieces, or for scoring, fast and effective robots must be able to align to them quickly and repeatably.\n\nSoftware strategies can be used to help augment the ability of a human operator, or step in when a human operator is not allowed to control the robot.\n\n*Vision Processing* is one key *input* to these software strategies. However, the inputs your coprocessor provides must be interpreted and converted (ultimately) to motor voltage commands.\n\nThere are many valid strategies for doing this transformation. Picking a strategy is a balancing act between:\n\n> 1. Available team resources (time, programming skills, previous experience)\n> 2. Precision of alignment required\n> 3. Team willingness to take on risk\n\nSimple strategies are low-risk - they require comparatively little effort to implement and tune, but have hard limits on the complexity of motion they can control on the robot. Advanced methods allow for more complex and precise movement, but take more effort to implement and tune. For this reason, it is more risky to attempt to use them.\n",
+ "content_preview": "# Vision - Robot Integration Background\n\n## Vision Processing's Purpose\n\nEach year, the FRC game requires a fundamental operation: **Align the Robot to a Goal**.\n\nRegardless of whether that alignment point is for picking up gamepieces, or for scoring, fast and effective robots must be able to align..."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/description.html",
- "title": "About PhotonVision",
- "section": "General",
+ "url": "https://docs.photonvision.org/en/latest/docs/contributing/linting.html",
+ "title": "Linting the PhotonVision Codebase",
+ "section": "Contributing",
"language": "All",
- "content": "# About PhotonVision\n\n## Description\n\nPhotonVision is a free, fast, and easy-to-use vision processing solution for the _FIRST_ Robotics Competition. PhotonVision is designed to get vision working on your robot _quickly_, but with lower cost than other solutions.\nUsing PhotonVision, teams can go from setting up a camera and coprocessor to detecting and tracking AprilTags and other targets by simply tuning sliders. With an easy to use interface, comprehensive documentation, and a feature rich vendor dependency, no experience is necessary to use PhotonVision. No matter your resources, using PhotonVision is easy compared to its alternatives.\n\n## Advantages\n\nPhotonVision has a myriad of advantages over similar solutions, including:\n\n### Affordable\n\nPhotonVision offers a more affordable solution to vision, with costs being from your coprocessor(s) and camera(s). Teams may choose to run multiple cameras from one coprocessor. This makes it a great solution for teams with limited budgets.\n\n### Easy to Use User Interface\n\nThe PhotonVision user interface is simple and modular, making things easier for the user. With a simpler interface, you can focus on what matters most, tracking targets, rather than how to use our UI. A major unique quality is that the PhotonVision UI includes an offline copy of our documentation for your ease of access at competitions.\n\n### PhotonLib Vendor Dependency\n\nThe PhotonLib vendor dependency allows you to easily get necessary target data (without having to work directly with NetworkTables) while also providing utility methods to get distance and position on the field. A serialization strategy is used to guarantees data coherency, which is helpful for latency compensation. This helps your team focus less on getting data and more on using it to do cool things.\n\n### User Calibration\n\nUsing PhotonVision allows the user to calibrate for their specific camera, which will get you the best tracking results. This is extremely important as every camera (even if it is the same model) will have it's own quirks and user calibration allows for those to be accounted for.\n\n### Low Latency, High FPS Processing\n\nPhotonVision exposes specialized hardware on select coprocessors to maximize processing speed. This allows for lower-latency detection of targets to ensure you aren't losing out on any performance.\n\n### Fully Open Source and Active Developer Community\n\nYou can find all of our code on [GitHub](https://github.com/PhotonVision), including code for our main program, documentation, vendor dependency (PhotonLib), and more. This helps you see everything working behind the scenes and increases transparency. This also allows users to make pull requests for features that they want to add in to PhotonVision that will be reviewed by the development team. PhotonVision is licensed under the GNU General Public License (GPLv3) which you can learn more about [here](https://www.gnu.org/licenses/quick-guide-gplv3.html).\n\n### Multi-Camera Support\n\nYou can use multiple cameras within PhotonVision, allowing you to see multiple angles without the need to buy multiple coprocessors. This makes vision processing more affordable and simpler for your team.\n\n### Comprehensive Documentation\n\nUsing our comprehensive documentation, you will be able to easily start vision processing by following a series of simple steps.\n",
- "content_preview": "# About PhotonVision\n\n## Description\n\nPhotonVision is a free, fast, and easy-to-use vision processing solution for the _FIRST_ Robotics Competition. PhotonVision is designed to get vision working on your robot _quickly_, but with lower cost than other solutions.\nUsing PhotonVision, teams can go..."
+ "content": "# Linting the PhotonVision Codebase\n\n## Versions\n\n:::{note}\nIf you work on other projects that use different versions of the same linters as PhotonVision, you may find it beneficial to use a [venv](https://docs.python.org/3/library/venv.html) instead of installing the linters globally. This will allow you to have different versions of the same linter installed for different projects.\n:::\n\nThe correct versions for each linter can be found under the linting workflow located [here](https://github.com/PhotonVision/photonvision/tree/main/.github/workflows). For *doc8*, the version can be found in `docs/requirements.txt`. If you've linted, and are still unable to pass CI, please check the versions of your linters.\n\n## Frontend\n\n### Linting the frontend\n\nIn order to lint the frontend, run `pnpm -C photon-client lint && pnpm -C photon-client format`. This should be done from the base level of the repo.\n\n## Backend\n\n### wpiformat installation\n\nTo lint the backend, PhotonVision uses *wpiformat* and *spotless*. Spotless is included with gradle, which means installation is not needed. To install wpiformat, run `pipx install wpiformat`. To install a specific version, run `pipx install wpiformat==`.\n\n### Linting the backend\n\nTo lint, run `./gradlew spotlessApply` and `wpiformat`.\n\n## Documentation\n\n### doc8 installation\n\nTo install *doc8*, the python tool we use to lint our documentation, run `pipx install doc8`. To install a specific version, run `pipx install doc8==`.\n\n### Linting the documentation\n\nTo lint the documentation, run `doc8 docs` from the root level of the docs.\n\n## Alias\n\nThe following [alias](https://www.computerworld.com/article/1373210/how-to-use-aliases-in-linux-shell-commands.html) can be added to your shell config, which will allow you to lint the entirety of the PhotonVision project by running `pvLint`. The alias will work on Linux, macOS, Git Bash on Windows, and WSL.\n\n```sh\nalias pvLint='wpiformat -v && ./gradlew spotlessApply && pnpm -C photon-client lint && pnpm -C photon-client format && doc8 docs'\n```\n",
+ "content_preview": "# Linting the PhotonVision Codebase\n\n## Versions\n\n:::{note}\nIf you work on other projects that use different versions of the same linters as PhotonVision, you may find it beneficial to use a [venv](https://docs.python.org/3/library/venv.html) instead of installing the linters globally."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/driver-mode/index.html",
- "title": "Driver Mode",
+ "url": "https://docs.photonvision.org/en/latest/docs/advanced-installation/sw_install/windows-pc.html",
+ "title": "Windows PC Installation",
"section": "General",
"language": "All",
- "content": "# Driver Mode\n\nDriver Mode is a type of pipeline that doesn't run any vision processing, intended for viewing from a human.\n\n## Enabling Driver Mode\n\nTo enable Driver Mode, toggle the switch at the top of the Dashboard page for a selected camera.\n\n```{image} images/driver-mode-dashboard.png\n:align: center\n:alt: Driver Mode Toggle in the Dashboard Page\n```\n\nAlternatively, visit the camera settings page and toggle the \"Driver Mode\" switch for a selected camera.\n\n```{image} images/driver-mode-camera-settings.png\n:align: center\n:alt: Driver Mode Toggle in the Camera Settings Page\n```\n\n## Hiding the Crosshair\nWhen Driver Mode is enabled, a green crosshair will be shown at the center of the camera stream. If you do not want to show the green crosshair at the center of the camera stream, toggle the \"Crosshair\" switch under the Input tab, as shown in the image below.\n\n```{image} images/crosshair-switch.png\n:align: center\n:alt: Crosshair Switch\n```\n",
- "content_preview": "# Driver Mode\n\nDriver Mode is a type of pipeline that doesn't run any vision processing, intended for viewing from a human.\n\n## Enabling Driver Mode\n\nTo enable Driver Mode, toggle the switch at the top of the Dashboard page for a selected camera.\n\n```{image} images/driver-mode-dashboard.png\n:align:..."
+ "content": "# Windows PC Installation\n\nPhotonVision may be run on a Windows Desktop PC for basic testing and evaluation.\n\n:::{note}\nYou do not need to install PhotonVision on a Windows PC in order to access the webdashboard (assuming you are using an external coprocessor like a Raspberry Pi).\n:::\n\n## Install Bonjour\n\nBonjour provides more stable networking when using Windows PCs. Install [Bonjour here](https://support.apple.com/downloads/DL999/en_US/BonjourPSSetup.exe) before continuing to ensure a stable experience while using PhotonVision.\n\n## Installing Java\n\nPhotonVision requires a JDK installed and on the system path. **JDK 17 is needed.** You may already have it if you installed WPILib, but ensure that running `java -version` shows JDK 17. You will likely have to add WPILib's JDK to JAVA_HOME and the JDK's `bin` directory to PATH. If you do not have a JDK 17 install, [download and install it from here.](https://adoptium.net/temurin/releases?version=17)\n\n## Downloading the Latest Stable Release of PhotonVision\n\nGo to the [GitHub releases page](https://github.com/PhotonVision/photonvision/releases) and download the winx64.jar file.\n\n## Running PhotonVision\n\nTo run PhotonVision, open a terminal window of your choice and run the following command:\n\n```\n> java -jar C:\\path\\to\\photonvision\\NAME OF JAR FILE GOES HERE.jar\n```\n\nIf your computer has a compatible webcam connected, PhotonVision should startup without any error messages. If there are error messages, your webcam isn't supported or another issue has occurred. If it is the latter, please open an issue on the [PhotonVision issues page](https://github.com/PhotonVision/photonvision/issues).\n\n:::{warning}\nUsing an integrated laptop camera may cause issues when trying to run PhotonVision. If you are unable to run PhotonVision on a laptop with an integrated camera, try disabling the camera's driver in Windows Device Manager.\n:::\n\n## Accessing the PhotonVision Interface\n\nOnce the Java backend is up and running, you can access the main vision interface by navigating to `localhost:5800` inside your browser.\n",
+ "content_preview": "# Windows PC Installation\n\nPhotonVision may be run on a Windows Desktop PC for basic testing and evaluation.\n\n:::{note}\nYou do not need to install PhotonVision on a Windows PC in order to access the webdashboard (assuming you are using an external coprocessor like a Raspberry Pi).\n:::\n\n## Install..."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/programming/photonlib/fps-limiter.html",
- "title": "FPS Limiter",
+ "url": "https://docs.photonvision.org/en/latest/docs/programming/photonlib/using-target-data.html",
+ "title": "Using Target Data",
"section": "PhotonLib",
"language": "All",
- "content": "# FPS Limiter\n\n:::{warning}\nWhen using the FPS limiter, it's important to disable it before a match begins.\n:::\n\nThe FPS limiter can be used to lower the frames processed per second for a given camera. This is intended to be used for power-saving, particularly in the case of high FPS cameras with powerful coprocessors. The value passed to the function will indicate the frames per second that should be processed. A value of -1 should be passed to indicate that the FPS limiter should not restrict processing; this is the default behavior.\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n int limit = camera.getFPSLimit();\n\n camera.setFPSLimit(10);\n\n // This removes any previously set FPS limit.\n camera.setFPSLimit(-1);\n\n .. code-block:: c++\n\n int limit = camera.GetFPSLimit();\n\n camera.SetFPSLimit(10);\n\n // This removes any previously set FPS limit.\n camera.SetFPSLimit(-1);\n\n .. code-block:: python\n\n limit = camera.getFPSLimit()\n\n camera.setFPSLimit(10)\n\n # This removes any previously set FPS limit.\n camera.setFPSLimit(-1)\n```\n",
- "content_preview": "# FPS Limiter\n\n:::{warning}\nWhen using the FPS limiter, it's important to disable it before a match begins.\n:::\n\nThe FPS limiter can be used to lower the frames processed per second for a given camera."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/quick-start/quick-install.html",
- "title": "Quick Installation Guide",
- "section": "Getting Started",
- "language": "All",
- "content": "# Quick Installation Guide\n\n- For the following supported coprocessors\n - {ref}`Raspberry Pi 3,4,5 `\n - {ref}`Orange Pi 5, 5B, 5 Pro `\n - {ref}`Limelight 2, 2+, 3, 3G, 4 `\n - {ref}`Rubik Pi 3 `\n\nFor installing on non-supported devices {ref}`see here. `\n\n[Download the latest preconfigured image of photonvision for your coprocessor](https://github.com/PhotonVision/photonvision/releases/latest)\n\n| Coprocessor | Image filename | Jar |\n| -------------------- | -------------------------------------------------------- | ------------------------------------- |\n| Raspberry Pi 3, 4, 5 | photonvision-{version}-linuxarm64_RaspberryPi.img.xz | photonvision-{version}-linuxarm64.jar |\n| OrangePi 5 | photonvision-{version}-linuxarm64_orangepi5.img.xz | photonvision-{version}-linuxarm64.jar |\n| OrangePi 5B | photonvision-{version}-linuxarm64_orangepi5b.img.xz | photonvision-{version}-linuxarm64.jar |\n| OrangePi 5 Pro | photonvision-{version}-linuxarm64_orangepi5pro.img.xz | photonvision-{version}-linuxarm64.jar |\n| Limelight 2 | photonvision-{version}-linuxarm64_limelight2.img.xz | photonvision-{version}-linuxarm64.jar |\n| Limelight 3 | photonvision-{version}-linuxarm64_limelight3.img.xz | photonvision-{version}-linuxarm64.jar |\n| Limelight 3G | photonvision-{version}-linuxarm64_limelight3G.img.xz | photonvision-{version}-linuxarm64.jar |\n| Limelight 4 | photonvision-{version}-linuxarm64_limelight4.img.xz | photonvision-{version}-linuxarm64.jar |\n| Rubik Pi 3 | photonvision-{version}-linuxarm64_rubikpi3.tar.xz | photonvision-{version}-linuxarm64.jar |\n\nUnless otherwise noted in release notes or if updating from the prior years version, to update PhotonVision after the initial installation, use the offline update option in the settings page with the downloaded jar file from the latest release.\n\n## Raspberry Pi and Orange Pi Installation\n\nUse the [Raspberry Pi Imager](https://www.raspberrypi.com/software/) to flash the image onto the coprocessors microSD card. Select the downloaded `.img.xz` file, select your microSD card, and flash.\n\n:::{warning}\nAvoid using Raspberry Pi Imager version 2.0.2 or later. Those versions fail to write the image to an SD card. Versions 2.0.0 and earlier write images successfully. [GitHub issue 1489](https://github.com/raspberrypi/rpi-imager/issues/1489) was created for this problem.\n:::\n\n:::{warning}\nBalena Etcher has been recommended in the past, but should no longer be used due to instability and lack of ongoing support from developers.\n:::\n\n## Limelight Installation\n\nIn order to flash your Limelight you should follow the instructions on the Limelight documentation for the relevant version. Make sure to replace the Limelight OS image with the relevant PhotonVision image.\n\n| Limelight Version | Limelight Documentation | PhotonVision Image | |\n| ----------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --- |\n| 2 | [Updating Limelight 2 OS](https://docs.limelightvision.io/docs/docs-limelight/getting-started/limelight-2#4-updating-limelightos) | photonvision-{version}-linuxarm64_limelight2.img.xz | |\n| 3 | [Updating Limelight 3 OS](https://docs.limelightvision.io/docs/docs-limelight/getting-started/limelight-3#4-updating-limelightos) | photonvision-{version}-linuxarm64_limelight3.img.xz | |\n| 3G | [Updating Limelight 3G OS](https://docs.limelightvision.io/docs/docs-limelight/getting-started/limelight-3g#4-updating-limelightos) | photonvision-{version}-linuxarm64_limelight3g.img.xz | |\n| 4 | [Updating Limelight 4 OS](https://docs.limelightvision.io/docs/docs-limelight/getting-started/limelight-4#4-updating-limelightos) | photonvision-{version}-linuxarm64_limelight4.img.xz | |\n\n:::{note}\nLimelight models will need a [custom hardware config file](https://github.com/PhotonVision/photonvision/tree/main/docs/source/docs/advanced-installation/sw_install/files) for LEDs or other hardware features to work.\n:::\n\n## Rubik Pi 3 Installation\n\n:::{warning}\nThe Qualcomm Launcher caches files. If you flash multiple times, you may need to clear the cache by navigating to your temp directory, and deleting the `qualcomm-launcher` folder.\n:::\n\nTo flash the Rubik Pi 3 coprocessor, it's necessary to use the [Qualcomm Launcher](https://softwarecenter.qualcomm.com/catalog/item/Qualcomm_Launcher). Upload a custom image by selecting the *Custom* option in the launcher. If this is your first time flashing this board, ensure you check the USB firmware option. Choose the downloaded PhotonVision `.tar.xz` file and follow the prompts to complete the installation. It is recommended to skip the *Configure Login* process, as PhotonVision will handle the necessary settings.\n\n### Alternative Flashing Method (advanced users only)\n\nFollow the specific steps listed below from the [Rubik Pi 3 Docs](https://www.thundercomm.com/rubik-pi-3/en/docs/rubik-pi-3-user-manual/1.0.0-u/Troubleshooting/11.1.flash-over-android/).\n\n[Step 1](https://www.thundercomm.com/rubik-pi-3/en/docs/rubik-pi-3-user-manual/1.0.0-u/Troubleshooting/11.1.flash-over-android/#1%EF%B8%8F%E2%83%A3-setup-qdl-tool) should be completed once per computer. [Step 2](https://www.thundercomm.com/rubik-pi-3/en/docs/rubik-pi-3-user-manual/1.0.0-u/Troubleshooting/11.1.flash-over-android/#2%EF%B8%8F%E2%83%A3-ufs-provisioning) and [Step 3](https://www.thundercomm.com/rubik-pi-3/en/docs/rubik-pi-3-user-manual/1.0.0-u/Troubleshooting/11.1.flash-over-android/#3%EF%B8%8F%E2%83%A3-flash-renesas-firmware) should be completed once per Rubik Pi 3.\n\nAfter completing these steps, unzip your downloaded PhotonVision image to a folder. Navigate to that folder in your terminal or command prompt. After putting your Rubik Pi 3 into EDL mode, run the command below to flash PhotonVision. There is no need to complete any further steps from the Rubik Pi 3 documentation after running this command.\n\n\n::::{tab-set}\n:::{tab-item} Ubuntu host\n```shell\nqdl --storage ufs prog_firehose_ddr.elf rawprogram*.xml patch*.xml\n```\n:::\n\n:::{tab-item} Windows host\n```shell\nQDL.exe prog_firehose_ddr.elf rawprogram0.xml rawprogram1.xml rawprogram2.xml rawprogram3.xml rawprogram4.xml rawprogram5.xml rawprogram6.xml patch1.xml patch2.xml patch3.xml patch4.xml patch5.xml patch6.xml\n```\n:::\n\n:::{tab-item} macOS host\n```shell\nqdl prog_firehose_ddr.elf rawprogram*.xml patch*.xml\n```\n:::\n::::\n",
- "content_preview": "# Quick Installation Guide\n\n- For the following supported coprocessors\n - {ref}`Raspberry Pi 3,4,5 `\n - {ref}`Orange Pi 5, 5B, 5 Pro `\n -..."
+ "content": "# Using Target Data\n\nA `PhotonUtils` class with helpful common calculations is included within `PhotonLib` to aid teams in using AprilTag data in order to get positional information on the field. This class contains two methods, `calculateDistanceToTargetMeters()`/`CalculateDistanceToTarget()` and `estimateTargetTranslation2d()`/`EstimateTargetTranslation()` (Java and C++ respectively).\n\n## Estimating Field Relative Pose with AprilTags\n\n`estimateFieldToRobotAprilTag(Transform3d cameraToTarget, Pose3d fieldRelativeTagPose, Transform3d cameraToRobot)` returns your robot's `Pose3d` on the field using the pose of the AprilTag relative to the camera, pose of the AprilTag relative to the field, and the transform from the camera to the origin of the robot.\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n // Calculate robot's field relative pose\n if (aprilTagFieldLayout.getTagPose(target.getFiducialId()).isPresent()) {\n Pose3d robotPose = PhotonUtils.estimateFieldToRobotAprilTag(target.getBestCameraToTarget(), aprilTagFieldLayout.getTagPose(target.getFiducialId()).get(), cameraToRobot);\n }\n .. code-block:: c++\n\n //TODO\n\n .. code-block:: python\n\n # Coming Soon!\n```\n\n## Estimating Field Relative Pose (Traditional)\n\nYou can get your robot's `Pose2D` on the field using various camera data, target yaw, gyro angle, target pose, and camera position. This method estimates the target's relative position using `estimateCameraToTargetTranslation` (which uses pitch and yaw to estimate range and heading), and the robot's gyro to estimate the rotation of the target.\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n // Calculate robot's field relative pose\n Pose2D robotPose = PhotonUtils.estimateFieldToRobot(\n kCameraHeight, kTargetHeight, kCameraPitch, kTargetPitch, Rotation2d.fromDegrees(-target.getYaw()), gyro.getRotation2d(), targetPose, cameraToRobot);\n\n .. code-block:: c++\n\n // Calculate robot's field relative pose\n frc::Pose2D robotPose = photonlib::EstimateFieldToRobot(\n kCameraHeight, kTargetHeight, kCameraPitch, kTargetPitch, frc::Rotation2d(units::degree_t(-target.GetYaw())), frc::Rotation2d(units::degree_t(gyro.GetRotation2d)), targetPose, cameraToRobot);\n\n .. code-block:: python\n\n # Coming Soon!\n\n```\n\n## Calculating Distance to Target\n\nIf your camera is at a fixed height on your robot and the height of the target is fixed, you can calculate the distance to the target based on your camera's pitch and the pitch to the target.\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // TODO\n\n .. code-block:: c++\n\n // TODO\n\n .. code-block:: python\n\n # Coming Soon!\n\n```\n\n:::{note}\nThe C++ version of PhotonLib uses the Units library. For more information, see [here](https://docs.wpilib.org/en/stable/docs/software/basic-programming/cpp-units.html).\n:::\n\n## Calculating Distance Between Two Poses\n\n`getDistanceToPose(Pose2d robotPose, Pose2d targetPose)` allows you to calculate the distance between two poses. This is useful when using AprilTags, given that there may not be an AprilTag directly on the target.\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n double distanceToTarget = PhotonUtils.getDistanceToPose(robotPose, targetPose);\n\n .. code-block:: c++\n\n //TODO\n\n .. code-block:: python\n\n # Coming Soon!\n```\n\n## Estimating Camera Translation to Target\n\nYou can get a [translation](https://docs.wpilib.org/en/latest/docs/software/advanced-controls/geometry/pose.html#translation) to the target based on the distance to the target (calculated above) and angle to the target (yaw).\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n // Calculate a translation from the camera to the target.\n Translation2d translation = PhotonUtils.estimateCameraToTargetTranslation(\n distanceMeters, Rotation2d.fromDegrees(-target.getYaw()));\n\n .. code-block:: c++\n\n // Calculate a translation from the camera to the target.\n frc::Translation2d translation = photonlib::PhotonUtils::EstimateCameraToTargetTranslation(\n distance, frc::Rotation2d(units::degree_t(-target.GetYaw())));\n\n .. code-block:: python\n\n # Coming Soon!\n\n```\n\n:::{note}\nWe are negating the yaw from the camera from CV (computer vision) conventions to standard mathematical conventions. In standard mathematical conventions, as you turn counter-clockwise, angles become more positive.\n:::\n\n## Getting the Yaw To a Pose\n\n`getYawToPose(Pose2d robotPose, Pose2d targetPose)` returns the `Rotation2d` between your robot and a target. This is useful when turning towards an arbitrary target on the field (ex. the center of the hub in 2022).\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n Rotation2d targetYaw = PhotonUtils.getYawToPose(robotPose, targetPose);\n .. code-block:: c++\n\n //TODO\n\n .. code-block:: python\n\n # Coming Soon!\n```\n",
+ "content_preview": "# Using Target Data\n\nA `PhotonUtils` class with helpful common calculations is included within `PhotonLib` to aid teams in using AprilTag data in order to get positional information on the field."
},
{
"url": "https://docs.photonvision.org/en/latest/docs/hardware/selecting-hardware.html",
@@ -580,76 +268,52 @@
"content_preview": "# Selecting Hardware\n\n:::{note}\nSee the {ref}`quick start guide`, for latest, specific recommendations on hardware to use for PhotonVision.\n:::\n\nIn order to use PhotonVision, you need a coprocessor and a camera."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/reflectiveAndShape/contour-filtering.html",
- "title": "Contour Filtering and Grouping",
- "section": "Reflective & Shape Detection",
- "language": "All",
- "content": "# Contour Filtering and Grouping\n\nContours that make it past thresholding are filtered and grouped so that only likely targets remain.\n\n## Filtering Options\n\n### Reflective\n\nContours can be filtered by area, width/height ratio, \"fullness\", and \"speckle rejection\" percentage.\n\nArea filtering adjusts the percentage of overall image area that contours are allowed to occupy. The area of valid contours is shown in the \"target info\" card on the right.\n\nRatio adjusts the width to height ratio of allowable contours. For example, a width to height filtering range of \\[2, 3\\] would allow targets that are 250 x 100 pixels in size through.\n\nFullness is a measurement of the ratio between the contour's area and the area of its bounding rectangle. This can be used to reject contours that are for example solid blobs.\n\nFinally, speckle rejection is an algorithm that can discard contours whose area are below a certain percentage of the average area of all visible contours. This might be useful in rejecting stray lights or image noise.\n\n```{raw} html\n\n \n Your browser does not support the video tag.\n \n```\n\n### Colored Shape\n\nThe contours tab has new options for specifying the properties of your colored shape. The target shape types are:\n\n- Circle - No edges\n- Triangle - 3 edges\n- Quadrilateral - 4 edges\n- Polygon - Any number of edges\n\n```{image} images/triangle.png\n:alt: Dropdown to select the colored shape pipeline type.\n:width: 600\n```\n\nOnly the settings used for the current target shape are available.\n\n- Shape Simplification - This is the only setting available for polygon, triangle, and quadrilateral target shapes. If you are having issues with edges being \"noisy\" or \"unclean\", adjust this setting to be higher (>75). This high setting helps prevent imperfections in the edge from being counted as a separate edge.\n- Circle Match Distance - How close the centroid of a contour must be to the center of the circle in order for them to be matched. This value is usually pretty small (\\<25) as you usually only want to identify circles that are nearly centered in the contour.\n- Radius - Percentage of the frame that the radius of the circle represents.\n- Max Canny Threshold - This sets the amount of change between pixels needed to be considered an edge. The smaller it is, the more false circles may be detected. Circles with more points along their ring having high contrast values will be returned first.\n- Circle Accuracy - This determines how perfect the circle contour must be in order to be considered a circle. Low values (\\<40) are required to detect things that aren't perfect circles.\n\n```{image} images/pumpkin.png\n:alt: Dropdown to select the colored shape pipeline type.\n:width: 600\n```\n\n## Contour Grouping and Sorting\n\nThese options change how contours are grouped together and sorted. Target grouping can pair adjacent contours, such as the targets found in 2019. Target intersection defines where the targets would intersect if you extended them infinitely, for example, to only group targets tipped \"towards\" each other in 2019.\n\nFinally, target sort defines how targets are ranked, from \"best\" to \"worst.\" The available options are:\n\n- Largest\n- Smallest\n- Highest (towards the top of the image)\n- Lowest\n- Rightmost (Best target on the right, worst on left)\n- Leftmost\n- Centermost\n\n```{raw} html\n\n \n Your browser does not support the video tag.\n \n```\n",
- "content_preview": "# Contour Filtering and Grouping\n\nContours that make it past thresholding are filtered and grouped so that only likely targets remain.\n\n## Filtering Options\n\n### Reflective\n\nContours can be filtered by area, width/height ratio, \"fullness\", and \"speckle rejection\" percentage.\n\nArea filtering adjusts..."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/advanced-installation/index.html",
- "title": "Advanced Installation",
- "section": "General",
- "language": "All",
- "content": "# Advanced Installation\n\nThis page will help you install PhotonVision on non-supported coprocessor.\n\n## Step 1: Software Install\n\nThis section will walk you through how to install PhotonVision on your coprocessor. Your coprocessor is the device that has the camera and you are using to detect targets (ex. if you are using a Limelight / Raspberry Pi, that is your coprocessor and you should follow those instructions).\n\n:::{warning}\nYou only need to install PhotonVision on the coprocessor/device that is being used to detect targets, you do NOT need to install it on the device you use to view the webdashboard. All you need to view the webdashboard is for a device to be on the same network as your vision coprocessor and an internet browser.\n:::\n\n```{toctree}\n:maxdepth: 3\n\nsw_install/index\nprerelease-software\n```\n",
- "content_preview": "# Advanced Installation\n\nThis page will help you install PhotonVision on non-supported coprocessor.\n\n## Step 1: Software Install\n\nThis section will walk you through how to install PhotonVision on your coprocessor."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/programming/photonlib/driver-mode-pipeline-index.html",
- "title": "Driver Mode and Pipeline Index/Latency",
- "section": "PhotonLib",
- "language": "All",
- "content": "# Driver Mode and Pipeline Index/Latency\n\nAfter {ref}`creating a PhotonCamera `, one can toggle Driver Mode and change the Pipeline Index of the vision program from robot code.\n\n## Toggle Driver Mode\n\nYou can use the `setDriverMode()`/`SetDriverMode()` (Java and C++ respectively) to toggle driver mode from your robot program. Driver mode is an unfiltered / normal view of the camera to be used while driving the robot.\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // Set driver mode to on.\n camera.setDriverMode(true);\n\n .. code-block:: c++\n\n // Set driver mode to on.\n camera.SetDriverMode(true);\n\n .. code-block:: python\n\n # Coming Soon!\n```\n\n## Setting the Pipeline Index\n\nYou can use the `setPipelineIndex()`/`SetPipelineIndex()` (Java and C++ respectively) to dynamically change the vision pipeline from your robot program.\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // Change pipeline to 2\n camera.setPipelineIndex(2);\n\n .. code-block:: c++\n\n // Change pipeline to 2\n camera.SetPipelineIndex(2);\n\n .. code-block:: python\n\n # Coming Soon!\n```\n\n## Getting the Pipeline Latency\n\nYou can also get the pipeline latency from a pipeline result using the `getLatencyMillis()`/`GetLatency()` (Java and C++ respectively) methods on a `PhotonPipelineResult`.\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n // Get the pipeline latency.\n double latencySeconds = result.getLatencyMillis() / 1000.0;\n\n .. code-block:: c++\n\n // Get the pipeline latency.\n units::second_t latency = result.GetLatency();\n\n .. code-block:: python\n\n # Coming Soon!\n```\n\n:::{note}\nThe C++ version of PhotonLib returns the latency in a unit container. For more information on the Units library, see [here](https://docs.wpilib.org/en/stable/docs/software/basic-programming/cpp-units.html).\n:::\n",
- "content_preview": "# Driver Mode and Pipeline Index/Latency\n\nAfter {ref}`creating a PhotonCamera `, one can toggle Driver Mode and change the Pipeline Index of the vision program from robot code.\n\n## Toggle Driver Mode\n\nYou can use the..."
- },
- {
- "url": "https://docs.photonvision.org/en/latest/docs/hardware/customhardware.html",
- "title": "Deploying on Custom Hardware",
- "section": "Hardware Selection",
+ "url": "https://docs.photonvision.org/en/latest/docs/additional-resources/nt-api.html",
+ "title": "NetworkTables API",
+ "section": "Additional Resources",
"language": "All",
- "content": "# Deploying on Custom Hardware\n\n## Configuration\n\nBy default, PhotonVision attempts to make minimal assumptions of the hardware it runs on. However, it may be configured to enable custom LED control, branding, and other functionality.\n\n`hardwareConfig.json` is the location for this configuration. It is included when settings are exported, and can be uploaded as part of a .zip, or on its own.\n\n## LED Support\n\nWhen running on Linux, PhotonVision can use [diozero](https://www.diozero.com) to control IO pins. The mapping of which pins control which LED's is part of the hardware config. The illumination LED pins are active-high: set high when LED's are commanded on, and set low when commanded off.\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: json\n\n {\n \"ledPins\" : [ 13 ],\n \"ledsCanDim\" : true,\n \"ledBrightnessRange\" : [ 0, 100 ],\n \"ledPWMFrequency\" : 0,\n \"statusRGBPins\" : [ ],\n \"statusRGBActiveHigh\" : false,\n }\n```\n\n:::{note}\nNo hardware boards with status RGB LED pins or non-dimming LED's have been tested yet. Please reach out to the development team if these features are desired, they can assist with configuration and testing.\n:::\n\n### GPIO Pinout\n\n::::{tab-set}\n\n:::{tab-item} Raspberry Pi\n\nThe following diagram shows the GPIO pin numbering of the 40-pin header on Raspberry Pi hardware, courtesy of [pinout.xyz](https://pinout.xyz). Compute modules use the pin numbering from their respective datasheet.\n\n```{image} https://raw.githubusercontent.com/pinout-xyz/Pinout.xyz/master/resources/raspberry-pi-pinout.png\n:alt: Raspberry Pi GPIO Pinout\n```\n\n:::\n::::\n\n### Custom GPIO\n\nIf your hardware does not support diozero's default provider, custom commands can be provided to interact with the GPIO lines. The examples below show what parameters are provided to each command, which can be used in any order or multiple times as needed.\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: json\n\n {\n \"getGPIOCommand\" : \"getGPIO {p}\",\n \"setGPIOCommand\" : \"setGPIO {p} {s}\",\n \"setPWMCommand\" : \"setPWM {p} {v}\",\n \"setPWMFrequencyCommand\" : \"setPWMFrequency {p} {f}\",\n \"releaseGPIOCommand\" : \"releseGPIO {p}\",\n }\n```\n\nThe following template strings are used to input parameters to the commands:\n\n| Template | Parameter | Values |\n| -------- | ---------- | ---------- |\n| `{p}` | pin number | integers |\n| `{s}` | state | true/false |\n| `{v}` | value | 0.0-1.0 |\n| `{f}` | frequency | integers |\n\nIf you were using custom LED commands from 2025 or earlier and still need custom GPIO commands, they can likely be copied over. `ledSetCommand` can be reused as `setGPIOCommand`. `ledDimCommand` can be reused with edits as `setPWMCommand`, replacing any occurrences of `{v}` with `$(awk 'BEGIN{ print int({v}*100) }')` if your command requires integer percentages.\n\n## Hardware Interaction Commands\n\nFor non-Linux hardware, users must provide the hardware-specific command for executing system restarts.\n\nLeaving this command blank will disable the restart functionality.\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: json\n\n {\n \"restartHardwareCommand\" : \"\",\n }\n```\n\n:::{note}\nThis setting has no effect if PhotonVision detects it is running on Linux. On Linux, the restart is accomplished by executing `reboot now` in a shell.\n:::\n\n## Known Camera FOV\n\nIf your hardware contains a camera with a known field of vision, it can be entered into the hardware configuration. This will prevent users from editing it in the GUI.\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: json\n\n {\n \"vendorFOV\" : 98.9\n }\n```\n\n## Device Name Branding\n\nTo help differentiate your hardware from other solutions, a device name may be set.\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: json\n\n {\n \"deviceName\" : \"Super Cool Custom Hardware\",\n }\n```\n\n:::{note}\nNot all configuration is currently presented in the User Interface. Additional file uploads may be needed to support custom images.\n:::\n\n## Example\n\nHere is a complete example `hardwareConfig.json`:\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: json\n\n {\n \"deviceName\" : \"Blinky McBlinkface\",\n \"ledPins\" : [2, 13],\n \"ledsCanDim\" : true,\n \"ledBrightnessRange\" : [ 0, 100 ],\n \"ledPWMFrequency\" : 0,\n \"statusRGBPins\" : [ ],\n \"statusRGBActiveHigh\" : false,\n \"getGPIOCommand\" : \"getGPIO {p}\",\n \"setGPIOCommand\" : \"setGPIO {p} {s}\",\n \"setPWMCommand\" : \"setPWM {p} {v}\",\n \"setPWMFrequencyCommand\" : \"setPWMFrequency {p} {f}\",\n \"releaseGPIOCommand\" : \"releaseGPIO {p}\",\n \"restartHardwareCommand\" : \"\",\n \"vendorFOV\" : 72.5\n }\n```\n",
- "content_preview": "# Deploying on Custom Hardware\n\n## Configuration\n\nBy default, PhotonVision attempts to make minimal assumptions of the hardware it runs on. However, it may be configured to enable custom LED control, branding, and other functionality.\n\n`hardwareConfig.json` is the location for this configuration."
+ "content": "# NetworkTables API\n\n## About\n\n:::{warning}\nPhotonVision interfaces with PhotonLib, our vendor dependency, using NetworkTables. If you are running PhotonVision on a robot (ie. with a RoboRIO), you should **turn the NetworkTables server switch (in the settings tab) off** in order to get PhotonLib to work. Also ensure that you set your team number. **The NetworkTables server should only be enabled if you know what you're doing!**\n:::\n\n## API\n\n:::{warning}\nNetworkTables is not a supported setup/viable option when using PhotonVision as we only send one target at a time (this is problematic when using AprilTags, which will return data from multiple tags at once).\n\n**We strongly recommend using PhotonLib instead, as the NetworkTables API will most likely be removed in 2027.**\n:::\n\nThe tables below contain the the name of the key for each entry that PhotonVision sends over the network and a short description of the key. The entries should be extracted from a subtable with your camera's nickname (visible in the PhotonVision UI) under the main `photonvision` table.\n\n### Getting Target Information\n\n| Key | Type | Description |\n| --------------- | ---------- | ------------------------------------------------------------------------ |\n| `rawBytes` | `byte[]` | A byte-packed string that contains target info from the same timestamp. |\n| `latencyMillis` | `double` | The latency of the pipeline in milliseconds. |\n| `hasTarget` | `boolean` | Whether the pipeline is detecting targets or not. |\n| `targetPitch` | `double` | The pitch of the target in degrees (positive up). |\n| `targetYaw` | `double` | The yaw of the target in degrees (positive right). |\n| `targetArea` | `double` | The area (percent of bounding box in screen) as a percent (0-100). |\n| `targetSkew` | `double` | The skew of the target in degrees (counter-clockwise positive). |\n| `targetPose` | `double[]` | The pose of the target relative to the robot (x, y, z, qw, qx, qy, qz) |\n| `targetPixelsX` | `double` | The target crosshair location horizontally, in pixels (origin top-right) |\n| `targetPixelsY` | `double` | The target crosshair location vertically, in pixels (origin top-right) |\n\n### Changing Settings\n\n| Key | Type | Description |\n| --------------- | --------- | --------------------------- |\n| `pipelineIndex` | `int` | Changes the pipeline index. |\n| `driverMode` | `boolean` | Toggles driver mode. |\n\n### Saving Images\n\nPhotonVision can save images to file on command. The image is saved when PhotonVision detects the command went from `false` to `true`.\n\nPhotonVision will automatically set these back to `false` after 500ms.\n\nBe careful saving images rapidly - it will slow vision processing performance and take up disk space very quickly.\n\nImages are returned as part of the .zip package from the \"Export\" operation in the Settings tab.\n\n| Key | Type | Description |\n| ------------------ | --------- | ------------------------------------------------- |\n| `inputSaveImgCmd` | `boolean` | Triggers saving the current input image to file. |\n| `outputSaveImgCmd` | `boolean` | Triggers saving the current output image to file. |\n\n:::{warning}\nIf you manage to make calls to these commands faster than 500ms (between calls), additional photos will not be captured.\n:::\n\n### Global Entries\n\nThese entries are global, meaning that they should be called on the main `photonvision` table.\n\n| Key | Type | Description |\n| --------- | ----- | -------------------------------------------------------- |\n| `ledMode` | `int` | Sets the LED Mode (-1: default, 0: off, 1: on, 2: blink) |\n\n:::{warning}\nSetting the LED mode to -1 (default) when `multiple` cameras are connected may result in unexpected behavior. {ref}`This is a known limitation of PhotonVision. `\n\nSingle camera operation should work without issue.\n:::\n",
+ "content_preview": "# NetworkTables API\n\n## About\n\n:::{warning}\nPhotonVision interfaces with PhotonLib, our vendor dependency, using NetworkTables. If you are running PhotonVision on a robot (ie."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/apriltag-pipelines/multitag.html",
- "title": "MultiTag Localization",
- "section": "AprilTag Detection",
+ "url": "https://docs.photonvision.org/en/latest/docs/contributing/design-descriptions/index.html",
+ "title": "Software Architecture Design Descriptions",
+ "section": "Contributing",
"language": "All",
- "content": "# MultiTag Localization\n\nPhotonVision can combine AprilTag detections from multiple simultaneously observed AprilTags from a particular camera with information about where tags are expected to be located on the field to produce a better estimate of where the camera (and therefore robot) is located on the field. PhotonVision can calculate this multi-target result on your coprocessor, reducing CPU usage on your RoboRio. This result is sent over NetworkTables along with other detected targets as part of the `PhotonPipelineResult` provided by PhotonLib.\n\n:::{warning}\nMultiTag requires an accurate field layout JSON to be uploaded! Differences between this layout and the tags' physical location will drive error in the estimated pose output.\n:::\n\n:::{warning}\nFor the 2026 Rebuilt Season, there are two different field layouts. The first is the [welded field layout](https://github.com/wpilibsuite/allwpilib/blob/main/apriltag/src/main/native/resources/edu/wpi/first/apriltag/2026-rebuilt-welded.json), which photonvision ships with. The second is the [Andymark field layout](https://github.com/wpilibsuite/allwpilib/blob/main/apriltag/src/main/native/resources/edu/wpi/first/apriltag/2026-rebuilt-andymark.json). It is very important to ensure that you use the correct field layout, both in the [PhotonPoseEstimator](https://docs.photonvision.org/en/latest/docs/programming/photonlib/robot-pose-estimator.html#apriltags-and-photonposeestimator) and on the [coprocessor](https://docs.photonvision.org/en/latest/docs/apriltag-pipelines/multitag.html#updating-the-field-layout).\n:::\n\n## Enabling MultiTag\n\nEnsure that your camera is calibrated and 3D mode is enabled. Navigate to the Output tab and enable \"Do Multi-Target Estimation\". This enables MultiTag to use the uploaded field layout JSON to calculate your camera's pose in the field. This 3D transform will be shown as an additional table in the \"targets\" tab, along with the IDs of AprilTags used to compute this transform.\n\n```{image} images/multitag-ui.png\n:alt: Multitarget enabled and running in the PhotonVision UI\n:width: 600\n```\n\n:::{note}\nBy default, enabling multi-target will disable calculating camera-to-target transforms for each observed AprilTag target to increase performance; the X/Y/angle numbers shown in the target table of the UI are instead calculated using the tag's expected location (per the field layout JSON) and the field-to-camera transform calculated using MultiTag. If you additionally want the individual camera-to-target transform calculated using SolvePNP for each target, enable \"Always Do Single-Target Estimation\".\n:::\n\nThis multi-target pose estimate can be accessed using PhotonLib. We suggest using {ref}`the PhotonPoseEstimator class ` with the `MULTI_TAG_PNP_ON_COPROCESSOR` strategy to simplify code, but the transform can be directly accessed using `getMultiTagResult`/`MultiTagResult()`/`multitagResult` (Java/C++/Python).\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n var results = camera.getAllUnreadResults();\n for (var result : results) {\n var multiTagResult = result.getMultiTagResult();\n if (multiTagResult.isPresent()) {\n var fieldToCamera = multiTagResult.get().estimatedPose.best;\n }\n }\n\n\n .. code-block:: c++\n\n auto results = camera.GetAllUnreadResults();\n for (auto &result : results)\n {\n auto multiTagResult = result.MultiTagResult();\n if (multiTagResult.has_value()) {\n frc::Transform3d fieldToCamera = multiTagResult->estimatedPose.best;\n }\n }\n\n\n .. code-block:: python\n\n results = camera.getAllUnreadResults()\n for result in results:\n multitagResult = result.multitagResult\n if multitagResult is not None:\n fieldToCamera = multitagResult.estimatedPose.best\n```\n\n:::{note}\nThe returned field to camera transform is a transform from the fixed field origin to the camera's coordinate system. This does not change based on alliance color, and by convention is on the BLUE ALLIANCE wall.\n:::\n\n## Updating the Field Layout\n\nPhotonVision ships by default with the [2026 welded field layout JSON](https://github.com/wpilibsuite/allwpilib/blob/main/apriltag/src/main/native/resources/edu/wpi/first/apriltag/2026-rebuilt-welded.json). The layout can be inspected by navigating to the settings tab and scrolling down to the \"AprilTag Field Layout\" card, as shown below.\n\n```{image} images/field-layout.png\n:alt: The currently saved field layout in the Photon UI\n:width: 600\n```\n\nAn updated field layout can be uploaded by navigating to the \"Device Control\" card of the Settings tab and clicking \"Import Settings\". In the pop-up dialog, select the \"AprilTag Layout\" type and choose an updated layout JSON (in the same format as the WPILib field layout JSON linked above) using the paperclip icon, and select \"Import Settings\". The AprilTag layout in the \"AprilTag Field Layout\" card below should be updated to reflect the new layout.\n\n:::{note}\nCurrently, there is no way to update this layout using PhotonLib, although this feature is under consideration.\n:::\n",
- "content_preview": "# MultiTag Localization\n\nPhotonVision can combine AprilTag detections from multiple simultaneously observed AprilTags from a particular camera with information about where tags are expected to be located on the field to produce a better estimate of where the camera (and therefore robot) is located..."
+ "content": "# Software Architecture Design Descriptions\n\n```{toctree}\n:maxdepth: 1\nimage-rotation\ntime-sync\ncamera-matching\ne2e-latency\n```\n",
+ "content_preview": "# Software Architecture Design Descriptions\n\n```{toctree}\n:maxdepth: 1\nimage-rotation\ntime-sync\ncamera-matching\ne2e-latency\n```\n"
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/advanced-installation/prerelease-software.html",
- "title": "Installing Pre-Release Versions",
- "section": "General",
+ "url": "https://docs.photonvision.org/en/latest/docs/simulation/simulation-java.html",
+ "title": "Simulation Support in PhotonLib in Java",
+ "section": "Simulation",
"language": "All",
- "content": "# Installing Pre-Release Versions\n\nPre-release/development version of PhotonVision can be tested by installing/downloading artifacts from Github Actions (see below), which are built automatically on commits to open pull requests and to PhotonVision's `main` branch, or by {ref}`compiling PhotonVision locally `.\n\n:::{warning}\nIf testing a pre-release version of PhotonVision with a robot, PhotonLib must be updated to match the version downloaded! If not, packet schema definitions may not match and unexpected things will occur. To update PhotonLib, refer to {ref}`installing specific version of PhotonLib`.\n:::\n\nGitHub Actions builds pre-release version of PhotonVision automatically on PRs and on each commit merged to main. To test a particular commit to main, navigate to the [PhotonVision commit list](https://github.com/PhotonVision/photonvision/commits/main/) and click on the check mark (below). Scroll to \"Build / Build fat JAR - PLATFORM\", click details, and then summary. From here, JAR and image files can be downloaded to be flashed or uploaded using \"Offline Update\".\n\n```{image} images/gh_actions_1.png\n:alt: Github Actions Badge\n```\n\n```{image} images/gh_actions_2.png\n:alt: Github Actions artifact list\n```\n\nBuilt JAR files (but not image files) can also be downloaded from PRs before they are merged. Navigate to the PR in GitHub, and select Checks at the top. Click on \"Build\" to display the same artifact list as above.\n\n```{image} images/gh_actions_3.png\n:alt: Github Actions artifacts from PR\n```\n",
- "content_preview": "# Installing Pre-Release Versions\n\nPre-release/development version of PhotonVision can be tested by installing/downloading artifacts from Github Actions (see below), which are built automatically on commits to open pull requests and to PhotonVision's `main` branch, or by {ref}`compiling..."
+ "content": "# Simulation Support in PhotonLib in Java\n\n## What Is Simulated?\n\nSimulation is a powerful tool for validating robot code without access to a physical robot. Read more about [simulation in WPILib](https://docs.wpilib.org/en/stable/docs/software/wpilib-tools/robot-simulation/introduction.html).\n\nIn Java, PhotonLib can simulate cameras on the field and generate target data approximating what would be seen in reality. This simulation attempts to include the following:\n\n- Camera Properties\n - Field of Vision\n - Lens distortion\n - Image noise\n - Framerate\n - Latency\n- Target Data\n - Detected / minimum-area-rectangle corners\n - Center yaw/pitch\n - Contour image area percentage\n - Fiducial ID\n - Fiducial ambiguity\n - Fiducial solvePNP transform estimation\n- Camera Raw/Processed Streams (grayscale)\n\n:::{note}\nSimulation does NOT include the following:\n\n- Full physical camera/world simulation (targets are automatically thresholded)\n- Image Thresholding Process (camera gain, brightness, etc)\n- Pipeline switching\n- Snapshots\n :::\n\nThis scope was chosen to balance fidelity of the simulation with the ease of setup, in a way that would best benefit most teams.\n\n```{image} diagrams/SimArchitecture.drawio.svg\n:alt: A diagram comparing the architecture of a real PhotonVision process to a simulated\n: one.\n```\n\n## Drivetrain Simulation Prerequisite\n\nA prerequisite for simulating vision frames is knowing where the camera is on the field-- to utilize PhotonVision simulation, you'll need to supply the simulated robot pose periodically. This requires drivetrain simulation for your robot project if you want to generate camera frames as your robot moves around the field.\n\nReferences for using PhotonVision simulation with drivetrain simulation can be found in the [PhotonLib Java Examples](https://github.com/PhotonVision/photonvision/blob/2a6fa1b6ac81f239c59d724da5339f608897c510/photonlib-java-examples/README.md) for both a differential drivetrain and a swerve drive.\n\n:::{important}\nThe simulated drivetrain pose must be separate from the drivetrain estimated pose if a pose estimator is utilized.\n:::\n\n## Vision System Simulation\n\nA `VisionSystemSim` represents the simulated world for one or more cameras, and contains the vision targets they can see. It is constructed with a unique label:\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // A vision system sim labelled as \"main\" in NetworkTables\n VisionSystemSim visionSim = new VisionSystemSim(\"main\");\n```\n\nPhotonLib will use this label to put a `Field2d` widget on NetworkTables at `/VisionSystemSim-[label]/Sim Field`. This label does not need to match any camera name or pipeline name in PhotonVision.\n\nVision targets require a `TargetModel`, which describes the shape of the target. For AprilTags, PhotonLib provides `TargetModel.kAprilTag16h5` for the tags used in 2023, and `TargetModel.kAprilTag36h11` for the tags used starting in 2024. For other target shapes, convenience constructors exist for spheres, cuboids, and planar rectangles. For example, a planar rectangle can be created with:\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // A 0.5 x 0.25 meter rectangular target\n TargetModel targetModel = new TargetModel(0.5, 0.25);\n```\n\nThese `TargetModel` are paired with a target pose to create a `VisionTargetSim`. A `VisionTargetSim` is added to the `VisionSystemSim` to become visible to all of its cameras.\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // The pose of where the target is on the field.\n // Its rotation determines where \"forward\" or the target x-axis points.\n // Let's say this target is flat against the far wall center, facing the blue driver stations.\n Pose3d targetPose = new Pose3d(16, 4, 2, new Rotation3d(0, 0, Math.PI));\n // The given target model at the given pose\n VisionTargetSim visionTarget = new VisionTargetSim(targetPose, targetModel);\n\n // Add this vision target to the vision system simulation to make it visible\n visionSim.addVisionTargets(visionTarget);\n```\n\n:::{note}\nThe pose of a `VisionTargetSim` object can be updated to simulate moving targets. Note, however, that this will break latency simulation for that target.\n:::\n\nTo use simulated object detection, you must provide an objDetClassId (zero-indexed class ID) and confidence value. When you set objDetConf to -1, the simulation computes confidence based on the area of the target in the camera's field of view. To simulate a object detection model with one class (fuel, index 0) and specify confidence, you'd write:\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // arbitrary position on field\n final var targetPose = new Pose3d(new Translation3d(2, 0, 0), new Rotation3d());\n // Class id, zero-indexed\n final int classId = 0;\n // Confidence, between 0 and 1.\n final float conf = 0.67f;\n // 6 inch diameter ball\n final TargetModel ballModel = new TargetModel(Units.inchesToMeters(6));\n final var ballTargetSim = new VisionTargetSim(targetPose, ballModel, classId, conf);\n\n // Add this vision target to the vision system simulation to make it visible\n visionSim.addVisionTargets(visionTarget);\n```\n\nFor convenience, an `AprilTagFieldLayout` can also be added to automatically create a target for each of its AprilTags.\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // The layout of AprilTags which we want to add to the vision system\n AprilTagFieldLayout tagLayout = AprilTagFieldLayout.loadFromResource(AprilTagFields.kDefaultField.m_resourceFile);\n\n visionSim.addAprilTags(tagLayout);\n```\n\n:::{note}\nThe poses of the AprilTags from this layout depend on its current alliance origin (e.g. blue or red). If this origin is changed later, the targets will have to be cleared from the `VisionSystemSim` and re-added.\n:::\n\n## Camera Simulation\n\nNow that we have a simulation world with vision targets, we can add simulated cameras to view it.\n\nBefore adding a simulated camera, we need to define its properties. This is done with the `SimCameraProperties` class:\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // The simulated camera properties\n SimCameraProperties cameraProp = new SimCameraProperties();\n```\n\nBy default, this will create a 960 x 720 resolution camera with a 90 degree diagonal FOV(field-of-view) and no noise, distortion, or latency. If we want to change these properties, we can do so:\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // A 640 x 480 camera with a 100 degree diagonal FOV.\n cameraProp.setCalibration(640, 480, Rotation2d.fromDegrees(100));\n // Approximate detection noise with average and standard deviation error in pixels.\n cameraProp.setCalibError(0.25, 0.08);\n // Set the camera image capture framerate (Note: this is limited by robot loop rate).\n cameraProp.setFPS(20);\n // The average and standard deviation in milliseconds of image data latency.\n cameraProp.setAvgLatencyMs(35);\n cameraProp.setLatencyStdDevMs(5);\n```\n\nThese properties are used in a `PhotonCameraSim`, which handles generating captured frames of the field from the simulated camera's perspective, and calculating the target data which is sent to the `PhotonCamera` being simulated.\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // The PhotonCamera used in the real robot code.\n PhotonCamera camera = new PhotonCamera(\"cameraName\");\n\n // The simulation of this camera. Its values used in real robot code will be updated.\n PhotonCameraSim cameraSim = new PhotonCameraSim(camera, cameraProp);\n```\n\nThe `PhotonCameraSim` can now be added to the `VisionSystemSim`. We have to define a robot-to-camera transform, which describes where the camera is relative to the robot pose (this can be measured in CAD or by hand).\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // Our camera is mounted 0.1 meters forward and 0.5 meters up from the robot pose,\n // (Robot pose is considered the center of rotation at the floor level, or Z = 0)\n Translation3d robotToCameraTrl = new Translation3d(0.1, 0, 0.5);\n // and pitched 15 degrees up.\n Rotation3d robotToCameraRot = new Rotation3d(0, Math.toRadians(-15), 0);\n Transform3d robotToCamera = new Transform3d(robotToCameraTrl, robotToCameraRot);\n\n // Add this camera to the vision system simulation with the given robot-to-camera transform.\n visionSim.addCamera(cameraSim, robotToCamera);\n```\n\n:::{important}\nYou may add multiple cameras to one `VisionSystemSim`, but not one camera to multiple `VisionSystemSim`. All targets in the `VisionSystemSim` will be visible to all its cameras.\n:::\n\nIf the camera is mounted on a mobile mechanism (like a turret) this transform can be updated in a periodic loop.\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // The turret the camera is mounted on is rotated 5 degrees\n Rotation3d turretRotation = new Rotation3d(0, 0, Math.toRadians(5));\n robotToCamera = new Transform3d(\n robotToCameraTrl.rotateBy(turretRotation),\n robotToCameraRot.rotateBy(turretRotation));\n visionSim.adjustCamera(cameraSim, robotToCamera);\n```\n\n## Low-Resource Vision Simulation with Photonvision\n\nBy default, PhotonCameraSim renders two simulated camera streams using OpenCV:\n\n- Raw stream - The unprocessed camera view\n- Processed stream - The camera view with vision processing overlays\n\nThese streams are nice if you want to actually view the simulated images, but they can be computationally expensive. This may cause lag and reduced simulation performance on lower-powered computers.\nLightweight Configuration\n\nThe following configuration disables both streams while still allowing tag detection and pose simulation to work. It's not perfect, but it's much better performance-wise than the default configuration.\n\n.. code-block:: java\n\n // lightweight config version\n // var cameraProperties = new SimCameraProperties();\n // cameraSim = new PhotonCameraSim(camera, cameraProperties, aprilTagLayout);\n // cameraSim.enableRawStream(false); // disables raw image stream\n // cameraSim.enableProcessedStream(false); // disables processed image stream\n\n**Use Case**\n\nThis configuration is ideal for Chromebooks or low-spec machines where rendering the simulated camera images causes lag, but vision data is still desired for testing.\n\n**What Still Works**\n\n- AprilTag detection\n- Pose estimation\n- NetworkTables data publishing\n- Robot positioning and targeting\n\n**What's Disabled**\n\n- Visual camera stream rendering\n- Real-time visual debugging of camera output\n\n## Updating The Simulation World\n\nTo update the `VisionSystemSim`, we simply have to pass in the simulated robot pose periodically (in `simulationPeriodic()`).\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // Update with the simulated drivetrain pose. This should be called every loop in simulation.\n visionSim.update(robotPoseMeters);\n```\n\nTargets and cameras can be added and removed, and camera properties can be changed at any time.\n\n## Visualizing Results\n\nEach `VisionSystemSim` has its own built-in `Field2d` for displaying object poses in the simulation world such as the robot, simulated cameras, and actual/measured target poses.\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // Get the built-in Field2d used by this VisionSystemSim\n visionSim.getDebugField();\n```\n\n:::{figure} images/SimExampleField.png\n_A_ `VisionSystemSim`_'s internal_ `Field2d` _customized with target images and colors_\n:::\n\nA `PhotonCameraSim` can also draw and publish generated camera frames to a MJPEG stream similar to an actual PhotonVision process.\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // Enable the raw and processed streams. These are enabled by default.\n cameraSim.enableRawStream(true);\n cameraSim.enableProcessedStream(true);\n\n // Enable drawing a wireframe visualization of the field to the camera streams.\n // This is extremely resource-intensive and is disabled by default.\n cameraSim.enableDrawWireframe(true);\n```\n\nThese streams follow the port order mentioned in {ref}`docs/quick-start/networking:Camera Stream Ports`. For example, a single simulated camera will have its raw stream at `localhost:1181` and processed stream at `localhost:1182`, which can also be found in the CameraServer tab of Shuffleboard like a normal camera stream.\n\n:::{figure} images/SimExampleFrame.png\n_A frame from the processed stream of a simulated camera viewing some 2023 AprilTags with the field wireframe enabled_\n:::\n",
+ "content_preview": "# Simulation Support in PhotonLib in Java\n\n## What Is Simulated?\n\nSimulation is a powerful tool for validating robot code without access to a physical robot."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/apriltag-pipelines/coordinate-systems.html",
- "title": "Coordinate Systems",
- "section": "AprilTag Detection",
+ "url": "https://docs.photonvision.org/en/latest/docs/programming/photonlib/getting-target-data.html",
+ "title": "Getting Target Data",
+ "section": "PhotonLib",
"language": "All",
- "content": "# Coordinate Systems\n\n## Field and Robot Coordinate Frame\n\nPhotonVision follows the WPILib conventions for the robot and field coordinate systems, as defined [here](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/geometry/coordinate-systems.html).\n\nYou define the camera to robot transform in the robot coordinate frame.\n\n## Camera Coordinate Frame\n\nOpenCV by default uses x-left/y-down/z-out for camera transforms. PhotonVision applies a base rotation to this transformation to make robot to tag transforms more in line with the WPILib coordinate system. The x, y, and z axes are also shown in red, green, and blue in the 3D mini-map and targeting overlay in the UI.\n\n- The origin is the focal point of the camera lens\n- The x-axis points out of the camera\n- The y-axis points to the left\n- The z-axis points upwards\n\n```{image} images/camera-coord.png\n:align: center\n:scale: 45 %\n```\n\n```{image} images/multiple-tags.png\n:align: center\n:scale: 45 %\n```\n\n## AprilTag Coordinate Frame\n\nThe AprilTag coordinate system is defined as follows, relative to the center of the AprilTag itself, and when viewing the tag as a robot would. Again, PhotonVision changes this coordinate system to be more in line with WPILib. This means that a robot facing a tag head-on would see a robot-to-tag transform with a translation only in x, and a rotation of 180 degrees about z. The tag coordinate system is also shown with x/y/z in red/green/blue in the UI target overlay and mini-map.\n\n- The origin is the center of the tag\n- The x-axis is normal to the plane the tag is printed on, pointing outward from the visible side of the tag.\n- The y-axis points to the right\n- The z-axis points upwards\n\n```{image} images/apriltag-coords.png\n:align: center\n:scale: 45 %\n```\n",
- "content_preview": "# Coordinate Systems\n\n## Field and Robot Coordinate Frame\n\nPhotonVision follows the WPILib conventions for the robot and field coordinate systems, as defined [here](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/geometry/coordinate-systems.html).\n\nYou define the camera to robot..."
+ "content": "# Getting Target Data\n\n## Constructing a PhotonCamera\n\n### What is a PhotonCamera?\n\n`PhotonCamera` is a class in PhotonLib that allows a user to interact with one camera that is connected to hardware that is running PhotonVision. Through this class, users can retrieve yaw, pitch, roll, robot-relative pose, latency, and a wealth of other information.\n\nThe `PhotonCamera` class has two constructors: one that takes a `NetworkTable` and another that takes in the name of the network table that PhotonVision is broadcasting information over. For ease of use, it is recommended to use the latter. The name of the NetworkTable (for the string constructor) should be the same as the camera's nickname (from the PhotonVision UI).\n\n```{eval-rst}\n.. tab-set-code::\n\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/a3bcd3ac4f88acd4665371abc3073bdbe5effea8/photonlib-java-examples/src/main/java/org/photonlib/examples/aimattarget/Robot.java\n :language: java\n :lines: 51-52\n\n .. rli:: https://github.com/PhotonVision/photonvision/raw/a3bcd3ac4f88acd4665371abc3073bdbe5effea8/photonlib-cpp-examples/src/main/cpp/examples/aimattarget/include/Robot.h\n :language: c++\n :lines: 42-43\n\n .. code-block:: python\n\n # Change this to match the name of your camera as shown in the web ui\n self.camera = PhotonCamera(\"your_camera_name_here\")\n\n```\n\n:::{warning}\nTeams must have unique names for all of their cameras regardless of which coprocessor they are attached to.\n:::\n\n## Getting the Pipeline Result\n\n### What is a Photon Pipeline Result?\n\nA `PhotonPipelineResult` is a container that contains all information about currently detected targets from a `PhotonCamera`. You can retrieve the latest pipeline result using the PhotonCamera instance.\n\nUse the `getLatestResult()`/`GetLatestResult()` (Java and C++ respectively) to obtain the latest pipeline result. An advantage of using this method is that it returns a container with information that is guaranteed to be from the same timestamp. This is important if you are using this data for latency compensation or in an estimator.\n\n```{eval-rst}\n.. tab-set-code::\n\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/a3bcd3ac4f88acd4665371abc3073bdbe5effea8/photonlib-java-examples/src/main/java/org/photonlib/examples/aimattarget/Robot.java\n :language: java\n :lines: 79-80\n\n .. rli:: https://github.com/PhotonVision/photonvision/raw/a3bcd3ac4f88acd4665371abc3073bdbe5effea8/photonlib-cpp-examples/src/main/cpp/examples/aimattarget/cpp/Robot.cpp\n :language: c++\n :lines: 35-36\n\n .. code-block:: python\n\n # Query the latest result from PhotonVision\n result = self.camera.getLatestResult()\n\n\n```\n\n:::{note}\nUnlike other vision software solutions, using the latest result guarantees that all information is from the same timestamp. This is achievable because the PhotonVision backend sends a byte-packed string of data which is then deserialized by PhotonLib to get target data. For more information, check out the [PhotonLib source code](https://github.com/PhotonVision/photonvision/tree/main/photon-lib).\n:::\n\n## Checking for Existence of Targets\n\nEach pipeline result has a `hasTargets()`/`HasTargets()` (Java and C++ respectively) method to inform the user as to whether the result contains any targets.\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n // Check if the latest result has any targets.\n boolean hasTargets = result.hasTargets();\n\n .. code-block:: c++\n\n // Check if the latest result has any targets.\n bool hasTargets = result.HasTargets();\n\n .. code-block:: python\n\n # Check if the latest result has any targets.\n hasTargets = result.hasTargets()\n```\n\n:::{warning}\nIn Java/C++, You must _always_ check if the result has a target via `hasTargets()`/`HasTargets()` before getting targets or else you may get a null pointer exception. Further, you must use the same result in every subsequent call in that loop.\n:::\n\n## Getting a List of Targets\n\n### What is a Photon Tracked Target?\n\nA tracked target contains information about each target from a pipeline result. This information includes yaw, pitch, area, and robot relative pose.\n\nYou can get a list of tracked targets using the `getTargets()`/`GetTargets()` (Java and C++ respectively) method from a pipeline result.\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n // Get a list of currently tracked targets.\n List targets = result.getTargets();\n\n .. code-block:: c++\n\n // Get a list of currently tracked targets.\n wpi::ArrayRef targets = result.GetTargets();\n\n .. code-block:: python\n\n # Get a list of currently tracked targets.\n targets = result.getTargets()\n```\n\n## Getting the Best Target\n\nYou can get the {ref}`best target ` using `getBestTarget()`/`GetBestTarget()` (Java and C++ respectively) method from the pipeline result.\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n // Get the current best target.\n PhotonTrackedTarget target = result.getBestTarget();\n\n .. code-block:: c++\n\n // Get the current best target.\n photonlib::PhotonTrackedTarget target = result.GetBestTarget();\n\n\n .. code-block:: python\n\n # Coming Soon!\n\n```\n\n## Getting Data From A Target\n\n- double `getYaw()`/`GetYaw()`: The yaw of the target in degrees (positive left).\n- double `getPitch()`/`GetPitch()`: The pitch of the target in degrees (positive up).\n- double `getArea()`/`GetArea()`: The area (how much of the camera feed the bounding box takes up) as a percent (0-100).\n- double `getSkew()`/`GetSkew()`: The skew of the target in degrees (counter-clockwise positive).\n- double\\[\\] `getCorners()`/`GetCorners()`: The 4 corners of the minimum bounding box rectangle.\n- Transform2d `getCameraToTarget()`/`GetCameraToTarget()`: The camera to target transform. See [2d transform documentation here](https://docs.wpilib.org/en/latest/docs/software/advanced-controls/geometry/transformations.html#transform2d-and-twist2d).\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n // Get information from target.\n double yaw = target.getYaw();\n double pitch = target.getPitch();\n double area = target.getArea();\n double skew = target.getSkew();\n Transform2d pose = target.getCameraToTarget();\n List corners = target.getCorners();\n\n .. code-block:: c++\n\n // Get information from target.\n double yaw = target.GetYaw();\n double pitch = target.GetPitch();\n double area = target.GetArea();\n double skew = target.GetSkew();\n frc::Transform2d pose = target.GetCameraToTarget();\n wpi::SmallVector, 4> corners = target.GetCorners();\n\n .. code-block:: python\n\n # Get information from target.\n yaw = target.getYaw()\n pitch = target.getPitch()\n area = target.getArea()\n skew = target.getSkew()\n pose = target.getCameraToTarget()\n corners = target.getDetectedCorners()\n```\n\n## Getting AprilTag Data From A Target\n\n:::{note}\nAll of the data above (**except skew**) is available when using AprilTags.\n:::\n\n- int `getFiducialId()`/`GetFiducialId()`: The ID of the detected fiducial marker.\n- double `getPoseAmbiguity()`/`GetPoseAmbiguity()`: How ambiguous the pose of the target is (see below).\n- Transform3d `getBestCameraToTarget()`/`GetBestCameraToTarget()`: Get the transform that maps camera space (X = forward, Y = left, Z = up) to object/fiducial tag space (X forward, Y left, Z up) with the lowest reprojection error.\n- Transform3d `getAlternateCameraToTarget()`/`GetAlternateCameraToTarget()`: Get the transform that maps camera space (X = forward, Y = left, Z = up) to object/fiducial tag space (X forward, Y left, Z up) with the highest reprojection error.\n\n```{eval-rst}\n.. tab-set-code::\n .. code-block:: java\n\n // Get information from target.\n int targetID = target.getFiducialId();\n double poseAmbiguity = target.getPoseAmbiguity();\n Transform3d bestCameraToTarget = target.getBestCameraToTarget();\n Transform3d alternateCameraToTarget = target.getAlternateCameraToTarget();\n\n .. code-block:: c++\n\n // Get information from target.\n int targetID = target.GetFiducialId();\n double poseAmbiguity = target.GetPoseAmbiguity();\n frc::Transform3d bestCameraToTarget = target.getBestCameraToTarget();\n frc::Transform3d alternateCameraToTarget = target.getAlternateCameraToTarget();\n\n .. code-block:: python\n\n # Get information from target.\n targetID = target.getFiducialId()\n poseAmbiguity = target.getPoseAmbiguity()\n bestCameraToTarget = target.getBestCameraToTarget()\n alternateCameraToTarget = target.getAlternateCameraToTarget()\n```\n\n## Saving Pictures to File\n\nA `PhotonCamera` can save still images from the input or output video streams to file. This is useful for debugging what a camera is seeing while on the field and confirming targets are being identified properly.\n\nImages are stored within the PhotonVision configuration directory. Running the \"Export\" operation in the settings tab will download a .zip file which contains the image captures.\n\n```{eval-rst}\n.. tab-set-code::\n\n .. code-block:: java\n\n // Capture pre-process camera stream image\n camera.takeInputSnapshot();\n\n // Capture post-process camera stream image\n camera.takeOutputSnapshot();\n\n .. code-block:: c++\n\n // Capture pre-process camera stream image\n camera.TakeInputSnapshot();\n\n // Capture post-process camera stream image\n camera.TakeOutputSnapshot();\n\n .. code-block:: python\n\n # Capture pre-process camera stream image\n camera.takeInputSnapshot()\n\n # Capture post-process camera stream image\n camera.takeOutputSnapshot()\n```\n\n:::{note}\nSaving images to file takes a bit of time and uses up disk space, so doing it frequently is not recommended. In general, the camera will save an image every 500ms. Calling these methods faster will not result in additional images. Consider tying image captures to a button press on the driver controller, or an appropriate point in an autonomous routine.\n:::\n",
+ "content_preview": "# Getting Target Data\n\n## Constructing a PhotonCamera\n\n### What is a PhotonCamera?\n\n`PhotonCamera` is a class in PhotonLib that allows a user to interact with one camera that is connected to hardware that is running PhotonVision."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/contributing/design-descriptions/time-sync.html",
- "title": "Time Synchronization Protocol Specification, Version 1.0",
- "section": "Contributing",
+ "url": "https://docs.photonvision.org/en/latest/docs/pipelines/index.html",
+ "title": "Pipelines - PhotonVision Docs",
+ "section": "Pipelines",
"language": "All",
- "content": "# Time Synchronization Protocol Specification, Version 1.0\n\nProtocol Revision 1.0, 08/25/2024\n\n## Background\n\nIn a distributed compute environment like robots, time synchronization between computers is increasingly important. Currently, [NetworkTables Version 4.1](https://github.com/wpilibsuite/allwpilib/blob/main/ntcore/doc/networktables4.adoc) provides support for time synchronization of clients with the NetworkTables server using binary PING/PONG messages sent over WebSockets. This approach, while fundamentally the same as is described in this memo, has demonstrated some opportunities for improvement:\n\n- PING/PONG messages are processed in the same queue as other NetworkTables messages. Depending on the underlying implementation and processor speed, this can incur message processing delays and increase client-calculated Round-Trip Time (RTT), and cause messages to arrive at the server timestamped in the future.\n- Messages use WebSockets over TCP for their transport layer. We don't need the robustness guarantees of TCP as our connection is stateless.\n\nFor these reasons, a time synchronization solution separate from NetworkTables communication was desired. Architecture decisions made to address these issues are:\n\n- Use the User Datagram Protocol (UDP) transport layer, as we don't need the robustness guarantees afforded by TCP. As a Client, if a PING isn't replied to, we'll just try again at the start of the next PING window. As a bonus, we are free to use UDP port 5810 as NetworkTables only uses TCP Port 5810/5811 as of Version 4.1.\n- Use a separate thread from the current NetworkTables libUV runner.\n\n\n## Prior Art\n\nThe [NetworkTables 4.1 timestamp synchronization](https://github.com/wpilibsuite/allwpilib/blob/main/ntcore/doc/networktables4.adoc#timestamps) approach, an implementation of [Cristian's Algorithm](https://en.wikipedia.org/wiki/Cristian%27s_algorithm). We also implement Cristian’s Algorithm.\n\nThe [Precision Time Protocol](https://en.wikipedia.org/wiki/Precision_Time_Protocol#Synchronization) at it's core does something similar with Sync/Delay_Req/Delay_Resp. We do not have (guaranteed) access to hardware timestamping, but we utilize this PING/PONG pattern to estimate total round-trip time.\n\n\n## Roles\n\n```{graphviz}\ndigraph CristianAlgorithm {\n ratio=0.5;\n bgcolor=\"transparent\";\n\n node [\n fontcolor = \"#e6e6e6\",\n style = filled,\n color = \"#e6e6e6\",\n fillcolor = \"#333333\"\n fontsize=10;\n ]\n\n edge [\n color = \"#e6e6e6\",\n fontcolor = \"#e6e6e6\"\n fontsize=10;\n ]\n\n rankdir=LR;\n node [shape=box, style=filled, color=lightblue];\n\n user_send [label=\"User Sends T1\"];\n server_receive [label=\"Server Receives T1\"];\n server_send [label=\"Server Sends T2\"];\n user_receive [label=\"User Receives T2\"];\n user_compute [label=\"User Computes Time\"];\n\n user_send -> server_receive [label=\"T1 (Request)\"];\n server_receive -> server_send [label=\"T1 received by server\"];\n server_send -> user_receive [label=\"T2 sent by server\"];\n user_receive -> user_compute [label=\"T2 received by user\"];\n user_compute -> user_send [label=\"Computed Time: T3 = T2 + (deltaT2 - deltaT1)/2\"];\n}\n```\n\nTime Synchronization Protocol (TSP) participants can assume either a server role or a client role. The server role is responsible for listening for incoming time synchronization requests from clients and replying appropriately. The client role is responsible for sending \"Ping\" messages to the server and listening for \"Pong\" replies to estimate the offset between the server and client time bases.\n\nAll time values shall use units of microseconds. The epoch of the time base this is measured against is unspecified.\n\nClients shall periodically (e.g. every few seconds) send, in a manner that minimizes transmission delays, a **TSP Ping Message** that contains the client's current local time.\n\nWhen the server receives a **TSP Ping Message** from any client, it shall respond to the client, in a manner that minimizes transmission delays, with a **TSP Pong message** encoding a timestamp of its (the server's) current local time (in microseconds), and the client-provided data value.\n\nWhen the client receives a **TSP Pong Message** from the server, it shall verify that the `Client Local Time` corresponds to the currently in-flight TSP Ping message; if not, it shall drop this packet. The round trip time (RTT) shall be computed from the delta between the message's data value and the current local time. If the RTT is less than that from previous measurements, the client shall use the timestamp in the message plus ½ the RTT as the server time equivalent to the current local time, and use this equivalence to compute server time base timestamps from local time for future messages.\n\n## Transport\n\nCommunication between server and clients shall occur over the User Datagram Protocol (UDP) Port 5810.\n\n## Message Format\n\nThe message format forgoes CRCs (as these are provided by the Ethernet physical layer) or packet delineation (as our packets are assumed be under the network MTU). **TSP Ping** and **TSP Pong** messages shall be encoded in a manor compatible with a WPILib packed struct with respect to byte alignment and endianness.\n\n### TSP Ping\n\n| Offset | Format | Data | Notes |\n| ------ | ------ | ---- | ----- |\n| 0 | uint8 | Protocol version | This field shall always set to 1 (0b1) for TSP Version 1. |\n| 1 | uint8 | Message ID | This field shall always be set to 1 (0b1). |\n| 2 | uint64 | Client Local Time | The client's local time value, at the time this Ping message was sent. |\n\n### TSP Pong\n\n| Offset | Format | Data | Notes |\n| ------ | ------ | ---- | ----- |\n| 0 | uint8 | Protocol version | This field shall always set to 1 (0b1) for TSP Version 1.\n| 1 | uint8 | Message ID | This field shall always be set to 2 (0b2).\n| 2 | uint64 | Client Local Time | The client's local time value from the Ping message that this Pong is generated in response to.\n| 10 | uint64 | Server Local Time | The current time at the server, at the time this Pong message was sent.\n\n\n## Optional Protocol Extensions\n\nClients may publish statistics to NetworkTables. If they do, they shall publish to a key that is globally unique per participant in the Time Synchronization network. If a client implements this, it shall provide the following publishers:\n\n| Key | Type | Notes |\n| ------ | ------ | ---- |\n| offset_us | Integer | The time offset that, when added to the client's local clock, provides server time |\n| ping_tx_count | Integer | The total number of TSP Ping packets transmitted |\n| ping_rx_count | Integer | The total number of TSP Ping packets received |\n| pong_rx_time_us | Integer | The time, in client local time, that the last pong was received |\n| rtt2_us | Integer | The time in us from last complete (ping transmission to pong reception) |\n\nPhotonVision has chosen to publish to the sub-table `/photonvision/.timesync/{DEVICE_HOSTNAME}`. Future implementations of this protocol may decide to implement this as a structured data type.\n\n## Wireshark Dissector\n\n\n\nA [WireShark dissector](https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/devTools/photon.lua) created for Wireshark ~=4.6 can be used to inspect Time Syncronization messages. Copy the dissector to your Wireshark plugin directory (for me, that's `C:\\Users\\Me\\AppData\\Roaming\\Wireshark\\plugins`), and open the capture. Because TSP uses UDP Unicast, data must be collected on the coprocessor or robot processor using a command similar to:\n\n```\nsudo tcpdump -i any port 5810 -w tsp_capture.pcap\n```\n",
- "content_preview": "# Time Synchronization Protocol Specification, Version 1.0\n\nProtocol Revision 1.0, 08/25/2024\n\n## Background\n\nIn a distributed compute environment like robots, time synchronization between computers is increasingly important."
+ "content": "Pipelines About Pipelines What is a pipeline? Types of Pipelines AprilTag / ArUco Object Detection Driver Mode Colored Shape Reflective Note About Multiple Cameras and Pipelines Pipeline Configuration AprilTag / ArUco Pipelines Object Detection Pipelines Reflective and Colored Shape Pipelines Camera Tuning / Input Resolution Exposure and brightness AprilTags and Motion Blur Orientation Stream Resolution Output Target Manipulation Robot Offset",
+ "content_preview": "Pipelines About Pipelines What is a pipeline? Types of Pipelines AprilTag / ArUco Object Detection Driver Mode Colored Shape Reflective Note About Multiple Cameras and Pipelines Pipeline Configuration AprilTag / ArUco Pipelines Object Detection Pipelines Reflective and Colored Shape Pipelines..."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/camera-specific-configuration/picamconfig.html",
- "title": "Pi Camera Configuration",
- "section": "Camera Configuration",
+ "url": "https://docs.photonvision.org/en/latest/docs/integration/simpleStrategies.html",
+ "title": "Simple Strategies",
+ "section": "Robot Integration",
"language": "All",
- "content": "# Pi Camera Configuration\n\nThis page covers specifics about the _Raspberry Pi_ CSI camera configuration.\n\n## Background\n\nThe Raspberry Pi CSI Camera port is routed through and processed by the GPU. Since the GPU boots before the CPU, it must be configured properly for the attached camera. Additionally, this configuration cannot be changed without rebooting.\n\nThe GPU is not always capable of detecting other cameras automatically. The file `/boot/config.txt` is parsed by the GPU at boot time to determine what camera, if any, is expected to be attached. This file must be updated for some cameras.\n\n:::{warning}\nIncorrect camera configuration will cause the camera to not be detected. It looks exactly the same as if the camera was unplugged.\n:::\n\n## Updating `config.txt`\n\nAfter flashing the pi image onto an SD card, open the `boot` segment in a file browser.\n\n:::{note}\nWindows may report \"There is a problem with this drive\". This should be ignored.\n:::\n\nLocate `config.txt` in the folder, and open it with your favorite text editor.\n\n```{image} images/bootConfigTxt.png\n\n```\n\nWithin the file, find this block of text:\n\n```\n##############################################################\n### PHOTONVISION CAM CONFIG\n### Comment/Uncomment to change which camera is supported\n### Picam V1, V2 or HQ: uncomment (remove leading # ) from camera_auto_detect=1,\n### and comment out all following lines\n### IMX290/327/OV9281/Any other cameras that require additional overlays:\n### Comment out (add a # ) to camera_auto_detect=1, and uncomment the line for\n### the sensor you're trying to user\n\ncameraAutoDetect=1\n\n# dtoverlay=imx290,clock-frequency=74250000\n# dtoverlay=imx290,clock-frequency=37125000\n# dtoverlay=imx378\n# dtoverlay=ov9281\n\n##############################################################\n```\n\nRemove the leading `#` character to uncomment the line associated with your camera. Add a `#` in front of other cameras.\n\n:::{warning}\nLeave lines outside the PhotonVision Camera Config block untouched. They are necessary for proper raspberry pi functionality.\n:::\n\nSave the file, close the editor, and eject the drive. The boot configuration should now be ready for your selected camera.\n\n## Additional Information\n\nSee [the libcamera documentation](https://github.com/raspberrypi/documentation/blob/679fab721855a3e8f17aa51819e5c2a7c447e98d/documentation/asciidoc/computers/camera/rpicam_configuration.adoc) for more details on configuring cameras.\n",
- "content_preview": "# Pi Camera Configuration\n\nThis page covers specifics about the _Raspberry Pi_ CSI camera configuration.\n\n## Background\n\nThe Raspberry Pi CSI Camera port is routed through and processed by the GPU. Since the GPU boots before the CPU, it must be configured properly for the attached camera."
+ "content": "# Simple Strategies\n\nSimple strategies for using vision processor outputs involve using the target's position in the 2D image to infer *range* and *angle* to a particular AprilTag.\n\n## Knowledge and Equipment Needed\n\n- A Coprocessor running PhotonVision\n- A Drivetrain with wheels\n- An AprilTag to aim at\n\n## Angle Alignment\n\nThe simplest way to align a robot to an AprilTag is to rotate the drivetrain until the tag is centered in the camera image. To do this,\n\n1. Read the current yaw angle to the AprilTag from the vision Coprocessor.\n2. If too far off to one side, command the drivetrain to rotate in the opposite direction to compensate.\n\nSee the {ref}`Aiming at a Target ` example for more information.\n\nNOTE: This works if the camera is centered on the robot. This is easiest from a software perspective. If the camera is not centered, take a peek at the next example - it shows how to account for an offset.\n\n## Adding Range Alignment\n\nBy looking at the position of the AprilTag in the \"vertical\" direction in the image, and applying some trigonometry, the distance between the robot and the camera can be deduced.\n\n1. Read the current pitch angle to the AprilTag from the vision coprocessor.\n2. Do math to calculate the distance to the AprilTag.\n2. If too far in one direction, command the drivetrain to travel in the opposite direction to compensate.\n\nThis can be done simultaneously while aligning to the desired angle.\n\nSee the {ref}`Aim and Range ` example for more information.\n",
+ "content_preview": "# Simple Strategies\n\nSimple strategies for using vision processor outputs involve using the target's position in the 2D image to infer *range* and *angle* to a particular AprilTag.\n\n## Knowledge and Equipment Needed\n\n- A Coprocessor running PhotonVision\n- A Drivetrain with wheels\n- An AprilTag to..."
},
{
"url": "https://docs.photonvision.org/en/latest/docs/quick-start/networking.html",
@@ -660,36 +324,44 @@
"content_preview": "# Networking\n\n## Physical Networking\n\n:::{warning}\nWhen using PhotonVision off robot, you _MUST_ plug the coprocessor into a physical router/radio. You can then connect your laptop/device used to view the webdashboard to the same network."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/examples/index.html",
- "title": "Code Examples - PhotonVision Docs",
- "section": "Code Examples",
+ "url": "https://docs.photonvision.org/en/latest/docs/apriltag-pipelines/2D-tracking-tuning.html",
+ "title": "2D AprilTag Tuning / Tracking",
+ "section": "AprilTag Detection",
"language": "All",
- "content": "Code Examples Aiming at a Target Combining Aiming and Getting in Range Using WPILib Pose Estimation, Simulation, and PhotonVision Together",
- "content_preview": "Code Examples Aiming at a Target Combining Aiming and Getting in Range Using WPILib Pose Estimation, Simulation, and PhotonVision Together"
+ "content": "# 2D AprilTag Tuning / Tracking\n\n## Tracking AprilTags\n\nBefore you get started tracking AprilTags, ensure that you have followed the previous sections on installation, wiring and networking. Next, open the Web UI, go to the top right card, and switch to the \"AprilTag\" or \"ArUco\" type. You should see a screen similar to the one below.\n\n```{image} images/apriltag.png\n:align: center\n```\n\nYou are now able to detect and track AprilTags in 2D (yaw, pitch, roll, etc.). In order to get 3D data from your AprilTags, please see {ref}`here. `\n\n## Tuning AprilTags\n\nAprilTag pipelines come with reasonable defaults to get you up and running with tracking. However, in order to optimize your performance and accuracy, you must tune your AprilTag pipeline using the settings below. Note that the settings below are different between the AprilTag and ArUco detectors but the concepts are the same.\n\n```{image} images/apriltag-tune.png\n:align: center\n:scale: 45 %\n```\n\n### Target Family\n\nTarget families are defined by two numbers (before and after the h). The first number is the number of bits the tag is able to encode (which means more tags are available in the respective family) and the second is the hamming distance. Hamming distance describes the ability for error correction while identifying tag ids. A high hamming distance generally means that it will be easier for a tag to be identified even if there are errors. However, as hamming distance increases, the number of available tags decreases.\n\nThe 2026 FRC game will be using 36h11 tags, which can be found [here](https://github.com/AprilRobotics/apriltag-imgs/tree/2bc821edb4eb7b408d13c6a590d326d8a9ec98f3/tag36h11).\n\n### Decimate\n\nDecimation (also known as down-sampling) is the process of reducing the sampling frequency of a signal (in our case, the image). Increasing decimate will lead to an increased detection rate while decreasing detection distance. We recommend keeping this at the default value.\n\n### Blur\n\nThis controls the sigma of Gaussian blur for tag detection. In clearer terms, increasing blur will make the image blurrier, decreasing it will make it closer to the original image. We strongly recommend that you keep blur to a minimum (0) due to it's high performance intensity unless you have an extremely noisy image.\n\n### Threads\n\nThreads refers to the threads within your coprocessor's CPU. The theoretical maximum is device dependent, but we recommend that users to stick to one less than the amount of CPU threads that your coprocessor has. Increasing threads will increase performance at the cost of increased CPU load, temperature increase, etc. It may take some experimentation to find the most optimal value for your system.\n\n### Refine Edges\n\nThe edges of the each polygon are adjusted to \"snap to\" high color differences surrounding it. It is recommended to use this in tandem with decimate as it can increase the quality of the initial estimate.\n\n### Pose Iterations\n\nPose iterations represents the amount of iterations done in order for the AprilTag algorithm to converge on its pose solution(s). A smaller number between 0-100 is recommended. A smaller amount of iterations cause a more noisy set of poses when looking at the tag straight on, while higher values much more consistently stick to a (potentially wrong) pair of poses. WPILib contains many useful filter classes in order to account for a noisy tag reading.\n\n### Max Error Bits\n\nMax error bits, also known as hamming distance, is the number of positions at which corresponding pieces of data / tag are different. Put more generally, this is the number of bits (think of these as squares in the tag) that need to be changed / corrected in the tag to correctly detect it. A higher value means that more tags will be detected while a lower value cuts out tags that could be \"questionable\" in terms of detection.\n\nWe recommend a value of 0 for the 16h5 and at most 3 for the 36h11 family.\n\n### Decision Margin Cutoff\n\nThe decision margin cutoff is how much “margin” the detector has left before it rejects a tag; increasing this rejects poorer tags. We recommend you keep this value around a 30.\n",
+ "content_preview": "# 2D AprilTag Tuning / Tracking\n\n## Tracking AprilTags\n\nBefore you get started tracking AprilTags, ensure that you have followed the previous sections on installation, wiring and networking. Next, open the Web UI, go to the top right card, and switch to the \"AprilTag\" or \"ArUco\" type."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/objectDetection/about-object-detection.html",
- "title": "About Object Detection",
- "section": "Getting Started",
+ "url": "https://docs.photonvision.org/en/latest/docs/programming/photonlib/adding-vendordep.html",
+ "title": "Installing PhotonLib",
+ "section": "PhotonLib",
"language": "All",
- "content": "# About Object Detection\n\n## How does it work?\n\nPhotonVision supports object detection using neural network accelerator hardware, commonly known as an NPU. The two coprocessors currently supported are the {ref}`Orange Pi 5 ` and the {ref}`Rubik Pi 3 `.\n\nPhotonVision currently ships with a model trained on the [COCO dataset](https://cocodataset.org/) by [Ultralytics](https://github.com/ultralytics/ultralytics) (this model is licensed under [AGPLv3](https://www.gnu.org/licenses/agpl-3.0.en.html)). This model is meant to be used for testing and other miscellaneous purposes. It is not meant to be used in competition. For the 2026 season, PhotonVision ships with a model to detect FUEL, this is also licensed under AGPL.\n\n## Tracking Objects\n\nBefore you get started with object detection, ensure that you have followed the previous sections on installation, wiring, and networking. Next, open the Web UI, go to the top right card, and switch to the “Object Detection” type. You should see a screen similar to the image above.\n\nModels are trained to detect one or more object \"classes\" (such as cars, stoplights) in an input image. For each detected object, the model outputs a bounding box around where in the image the object is located, what class the object belongs to, and a unitless confidence between 0 and 1.\n\n:::{note}\nThis model output means that while its fairly easy to say that \"this rectangle probably contains an object\", we don't have any information about the object's orientation or location. Further math in user code would be required to make estimates about where an object is physically located relative to the camera.\n:::\n\n## Tuning and Filtering\n\nCompared to other pipelines, object detection exposes very few tuning handles. The Confidence slider changes the minimum confidence that the model needs to have in a given detection to consider it valid, as a number between 0 and 1 (with 0 meaning completely uncertain and 1 meaning maximally certain). The Non-Maximum Suppresion (NMS) Threshold slider is used to filter out overlapping detections. Higher values mean more detections are allowed through, but may result in false positives. It's generally recommended that teams leave this set at the default, unless they find they're unable to get usable results with solely the Confidence slider.\n\n```{raw} html\n\n \n Your browser does not support the video tag.\n \n```\n\nThe same area, aspect ratio, and target orientation/sort parameters from {ref}`reflective pipelines ` are also exposed in the object detection card.\n\n## Letterboxing\n\nPhotonvision will letterbox your camera frame to 640x640. This means that if you select a resolution that is larger than 640 it will be scaled down to fit inside a 640x640 frame with black bars if needed. Smaller frames will be scaled up with black bars if needed.\n\nIt is recommended that you select a resolution that results in the smaller dimension being just greater than, or equal to, 640. Anything above this will not see any increased performance.\n\n## Custom Models\n\nFor information regarding converting custom models and supported models for each platform, refer to the page detailing information about your specific coprocessor.\n\n- {ref}`Orange Pi 5 `\n- {ref}`Rubik Pi 3 `\n\n### Training Custom Models\n\nPhotonVision does not offer any support for training custom models, only conversion. For information on which models are supported for a given coprocessor, use the links above.\n\n### Managing Custom Models\n\nCustom models can now be managed from the Object Detection tab in settings. You can upload a custom model by clicking the \"Upload Model\" button, selecting your model file, and filling out the property fields. Models can also be exported, both individually and in bulk. Models exported in bulk can be imported using the `import bulk` button. Models exported individually must be re-imported as an individual model, and all the relevant metadata is stored in the filename of the model.\n",
- "content_preview": "# About Object Detection\n\n## How does it work?\n\nPhotonVision supports object detection using neural network accelerator hardware, commonly known as an NPU."
+ "content": "# Installing PhotonLib\n\n## What is PhotonLib?\n\nPhotonLib is the C++ and Java vendor dependency that accompanies PhotonVision. We created this vendor dependency to make it easier for teams to retrieve vision data from their integrated vision system.\n\nPhotonLibPy is a minimal, pure-python implementation of PhotonLib.\n\n## Online Install - Java/C++\n\nClick on the WPILib logo in the activity bar to access the Vendor Dependencies interface.\n\n```{image} images/wpilib-vendor-dependencies.png\n:scale: 50%\n:align: center\n:alt: WPILib Vendor Dependencies\n```\n\nSelect the install button for the \"PhotonLib\" dependency.\n\n```{image} images/photonlib-install.png\n:scale: 50%\n:align: center\n:alt: PhotonLib Install Button\n```\n\n:::{note}\nThe Dependency Manager will automatically build your program when it loses focus. This allows you to use the changed dependencies.\n:::\n\nWhen an update is available for PhotonLib, a \"To Latest\" button will become available. This will update the vendordep to the latest version of PhotonLib.\n\n```{image} images/photonlib-to-latest.png\n:align: center\n:alt: PhotonLib Update Button\n```\n\nRefer to [The WPILib docs](https://docs.wpilib.org/en/stable/docs/software/vscode-overview/3rd-party-libraries.html#installing-libraries) for more details on installing vendor libraries.\n\n## Offline Install - Java/C++\n\nDownload the latest PhotonLib release from our [GitHub releases page](https://github.com/PhotonVision/photonvision/releases) (named in the format `photonlib-VERSION.zip`), and extract the contents to `~/wpilib/YYYY/vendordeps` (where YYYY is the year and ~ is `C:\\Users\\Public` on Windows). This adds PhotonLib maven artifacts to your local maven repository. PhotonLib will now also appear available in the \"install vendor libraries (offline)\" menu in WPILib VSCode. Refer to [the WPILib docs](https://docs.wpilib.org/en/stable/docs/software/vscode-overview/3rd-party-libraries.html#how-does-it-work) for more details on installing vendor libraries offline.\n\n## Install - Python\n\nAdd photonlibpy to `pyproject.toml`.\n\n```toml\n# Other pip packages to install\nrequires = [\n \"photonlibpy\",\n]\n```\n\nSee [The WPILib/RobotPy docs](https://docs.wpilib.org/en/stable/docs/software/python/pyproject_toml.html) for more information on using `pyproject.toml.`\n\n## Install Specific Version - Java/C++\n\nIn cases where you want to test a specific version of PhotonLib, make sure you have finished the steps in Online Install - Java/C++ and then manually change the version string in the PhotonLib vendordep json file(at ``/path/to/your/project/vendordep/photonlib.json``) to your desired version.\n\n```{image} images/photonlib-vendordep-json.jpg\n```\n",
+ "content_preview": "# Installing PhotonLib\n\n## What is PhotonLib?\n\nPhotonLib is the C++ and Java vendor dependency that accompanies PhotonVision. We created this vendor dependency to make it easier for teams to retrieve vision data from their integrated vision system.\n\nPhotonLibPy is a minimal, pure-python..."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/quick-start/common-setups.html",
- "title": "Common Hardware Setups",
- "section": "Getting Started",
+ "url": "https://docs.photonvision.org/en/latest/index.html",
+ "title": "Content",
+ "section": "General",
"language": "All",
- "content": "# Common Hardware Setups\n\nPhotonVision requires dedicated hardware, above and beyond a roboRIO. This page lists hardware that is frequently used with PhotonVision.\n\n## Coprocessors\n\n- Orange Pi 5 4GB\n - Supports up to 2 object detection streams, along with 2 AprilTag streams at 1280x800 (30fps).\n- Raspberry Pi 5 2GB\n - Supports up to 2 AprilTag streams at 1280x800 (30fps).\n\n:::{note}\nThe Orange Pi 5 is the only currently supported device for object detection.\n:::\n\n## SD Cards\n\n- 8GB or larger micro SD card\n\n:::{important}\nIndustrial grade SD cards from major manufacturers are recommended for robotics applications. For example: Sandisk SDSDQAF3-016G-I .\n:::\n\n## Cameras\n\nInnomaker and Arducam are common manufacturers of hardware designed specifically for vision processing.\n\n- AprilTag Detection\n - OV9281\n\n- Object Detection\n - OV9782\n\n- Driver Camera\n - OV9281\n - OV9782\n - Pi Camera Module V1 {ref}`(More setup info)`\n\nFeel free to get started with any color webcam you have sitting around.\n\n## Power\n\n- Pololu S13V30F5 Regulator\n- Redux Robotics Zinc-V Regulator\n\nSee {ref}`(Selecting Hardware)` for info on why these are recommended.\n",
- "content_preview": "# Common Hardware Setups\n\nPhotonVision requires dedicated hardware, above and beyond a roboRIO. This page lists hardware that is frequently used with PhotonVision.\n\n## Coprocessors\n\n- Orange Pi 5 4GB\n - Supports up to 2 object detection streams, along with 2 AprilTag streams at 1280x800 (30fps).\n-..."
+ "content": "```{image} assets/PhotonVision-Header-onWhite.png\n:alt: PhotonVision\n```\n\nWelcome to the official documentation of PhotonVision! PhotonVision is the free, fast, and easy-to-use vision processing solution for the _FIRST_ Robotics Competition. PhotonVision is designed to get vision working on your robot _quickly_, without the significant cost of other similar solutions. PhotonVision supports a variety of COTS hardware, including the Raspberry Pi 3, 4, and 5, the [SnakeEyes Pi hat](https://www.playingwithfusion.com/productview.php?pdid=133), and the Orange Pi 5.\n\n# Content\n\n```{eval-rst}\n.. grid:: 2\n\n .. grid-item-card:: Quick Start\n :link: docs/quick-start/index\n :link-type: doc\n\n Quick start to using Photonvision.\n\n .. grid-item-card:: Advanced Installation\n :link: docs/advanced-installation/index\n :link-type: doc\n\n Get started with installing PhotonVision on non-supported hardware.\n\n```\n\n```{eval-rst}\n.. grid:: 2\n\n .. grid-item-card:: Programming Reference and PhotonLib\n :link: docs/programming/index\n :link-type: doc\n\n Learn more about PhotonLib, our vendor dependency which makes it easier for teams to retrieve vision data, make various calculations, and more.\n\n .. grid-item-card:: Integration\n :link: docs/integration/index\n :link-type: doc\n\n Pick how to use vision processing results to control a physical robot.\n\n```\n\n```{eval-rst}\n.. grid:: 2\n\n .. grid-item-card:: Code Examples\n :link: docs/examples/index\n :link-type: doc\n\n View various step by step guides on how to use data from PhotonVision in your code, along with game-specific examples.\n\n .. grid-item-card:: Hardware\n :link: docs/hardware/index\n :link-type: doc\n\n Select appropriate hardware for high-quality and easy vision target detection.\n```\n\n```{eval-rst}\n.. grid:: 2\n\n .. grid-item-card:: Contributing\n :link: docs/contributing/index\n :link-type: doc\n\n Interested in helping with PhotonVision? Learn more about how to contribute to our main code base, documentation, and more.\n```\n\n# Source Code\n\nThe source code for all PhotonVision projects is available through our [GitHub organization](https://github.com/PhotonVision).\n\n- [PhotonVision](https://github.com/PhotonVision/photonvision)\n\n# Contact Us\n\nTo report a bug or submit a feature request in PhotonVision, please [submit an issue on the PhotonVision GitHub](https://github.com/PhotonVision/photonvision) or [contact the developers on Discord](https://discord.com/invite/KS76FrX).\n\nIf you find a problem in this documentation, please submit an issue on the [PhotonVision Documentation GitHub](https://github.com/PhotonVision/photonvision/tree/main/docs).\n\n# License\n\nPhotonVision is licensed under the [GNU GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html).\n\n```{toctree}\n:caption: Getting Started\n:hidden: true\n:maxdepth: 0\n\ndocs/description\ndocs/quick-start/index\ndocs/hardware/index\ndocs/advanced-installation/index\ndocs/camera-specific-configuration/index\n```\n\n```{toctree}\n:caption: Pipeline Tuning and Calibration\n:hidden: true\n:maxdepth: 0\n\ndocs/pipelines/index\ndocs/apriltag-pipelines/index\ndocs/reflectiveAndShape/index\ndocs/objectDetection/index\ndocs/driver-mode/index\ndocs/calibration/calibration\n```\n\n```{toctree}\n:caption: Programming Reference\n:hidden: true\n:maxdepth: 1\n\ndocs/programming/photonlib/index\ndocs/simulation/index\ndocs/integration/index\ndocs/examples/index\n```\n\n```{toctree}\n:caption: Additional Resources\n:hidden: true\n:maxdepth: 1\n\ndocs/troubleshooting/index\ndocs/additional-resources/best-practices\ndocs/additional-resources/config\ndocs/additional-resources/nt-api\ndocs/benchmarks/index\ndocs/contributing/index\n```\n\n```{toctree}\n:caption: API Documentation\n:hidden: true\n:maxdepth: 1\n\n Java \n\n C++ \n```\n",
+ "content_preview": "```{image} assets/PhotonVision-Header-onWhite.png\n:alt: PhotonVision\n```\n\nWelcome to the official documentation of PhotonVision! PhotonVision is the free, fast, and easy-to-use vision processing solution for the _FIRST_ Robotics Competition."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/examples/aimandrange.html",
- "title": "Combining Aiming and Getting in Range",
- "section": "Code Examples",
+ "url": "https://docs.photonvision.org/en/latest/docs/advanced-installation/sw_install/other-coprocessors.html",
+ "title": "Other Debian-Based Co-Processor Installation",
+ "section": "General",
"language": "All",
- "content": "# Combining Aiming and Getting in Range\n\nThe following example is from the PhotonLib example repository ([Java](https://github.com/PhotonVision/photonvision/tree/main/photonlib-java-examples/aimandrange)/[C++](https://github.com/PhotonVision/photonvision/tree/main/photonlib-cpp-examples/aimandrange)/[Python](https://github.com/PhotonVision/photonvision/tree/main/photonlib-python-examples/aimandrange))\n\n## Knowledge and Equipment Needed\n\n- Everything required in {ref}`Aiming at a Target `.\n\n## Code\n\nNow that you know how to aim toward the AprilTag, let's also drive the correct distance from the AprilTag.\n\nTo do this, we'll use the _pitch_ of the target in the camera image and trigonometry to figure out how far away the robot is from the AprilTag. Then, like before, we'll use the P term of a PID controller to drive the robot to the correct distance.\n\n```{eval-rst}\n.. tab-set::\n :sync-group: code\n\n .. tab-item:: Java\n :sync: java\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/aimandrange/src/main/java/frc/robot/Robot.java\n :language: java\n :lines: 84-131\n :linenos:\n :lineno-start: 84\n\n .. tab-item:: C++ (Header)\n :sync: c++\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/aimandrange/src/main/include/Robot.h\n :language: c++\n :lines: 25-63\n :linenos:\n :lineno-start: 25\n\n .. tab-item:: C++ (Source)\n :sync: c++\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/aimandrange/src/main/cpp/Robot.cpp\n :language: c++\n :lines: 58-107\n :linenos:\n :lineno-start: 58\n\n .. tab-item:: Python\n :sync: python\n\n .. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-python-examples/aimandrange/robot.py\n :language: python\n :lines: 52-91\n :linenos:\n :lineno-start: 52\n\n```\n",
- "content_preview": "# Combining Aiming and Getting in Range\n\nThe following example is from the PhotonLib example repository ([Java](https://github.com/PhotonVision/photonvision/tree/main/photonlib-java-examples/aimandrange)/[C++](https://github.com/PhotonVision/photonvision/tree/main/photonlib-cpp-examples/aimandrange)..."
+ "content": "# Other Debian-Based Co-Processor Installation\n\n:::{warning}\nWorking with unsupported coprocessors requires some level of \"know how\" of your system. The install script has only been tested on Debian/Raspberry Pi OS Buster and Ubuntu Bionic. If any issues arise with your specific OS, please open an issue on our [issues page](https://github.com/PhotonVision/photonvision/issues).\n:::\n\n:::{note}\nWe'd love to have your input! If you get PhotonVision working on another coprocessor, consider documenting your steps and submitting a [docs issue](https://github.com/PhotonVision/photonvision-docs/issues)., [pull request](https://github.com/PhotonVision/photonvision-docs/pulls) , or [ping us on Discord](https://discord.com/invite/wYxTwym). For example, Limelight and Romi install instructions came about because someone spent the time to figure it out, and did a writeup.\n:::\n\n## Installing PhotonVision\n\nWe provide an [install script](https://git.io/JJrEP) for other Debian-based systems (with `apt`) that will automatically install PhotonVision and make sure that it runs on startup.\n\n```bash\n$ wget https://git.io/JJrEP -O install.sh\n$ sudo chmod +x install.sh\n$ sudo ./install.sh\n$ sudo reboot now\n```\n\n:::{note}\nYour co-processor will require an Internet connection for this process to work correctly.\n:::\n\nFor installation on any other co-processors, we recommend reading the {ref}`advanced command line documentation `.\n\n## Updating PhotonVision\n\nPhotonVision can be updated by downloading the latest jar file, copying it onto the processor, and restarting the service.\n\nFor example, from another computer, run the following commands. Substitute the correct username for \"\\[user\\]\" ( Provided images use username \"pi\")\n\n```bash\n$ scp [jar name].jar [user]@photonvision.local:~/\n$ ssh [user]@photonvision.local\n$ sudo mv [jar name].jar /opt/photonvision/photonvision.jar\n$ sudo systemctl restart photonvision.service\n```\n",
+ "content_preview": "# Other Debian-Based Co-Processor Installation\n\n:::{warning}\nWorking with unsupported coprocessors requires some level of \"know how\" of your system. The install script has only been tested on Debian/Raspberry Pi OS Buster and Ubuntu Bionic."
+ },
+ {
+ "url": "https://docs.photonvision.org/en/latest/docs/reflectiveAndShape/thresholding.html",
+ "title": "Thresholding",
+ "section": "Reflective & Shape Detection",
+ "language": "All",
+ "content": "# Thresholding\n\nFor colored shape detection, we want to tune our HSV thresholds such that only the goal color remains after the thresholding. The [HSV color representation](https://en.wikipedia.org/wiki/HSL_and_HSV) is similar to RGB in that it represents colors. However, HSV represents colors with hue, saturation and value components. Hue refers to the color, while saturation and value describe its richness and brightness.\n\nIn PhotonVision, HSV thresholds is available in the \"Threshold\" tab.\n\n```{raw} html\n\n \n Your browser does not support the video tag.\n \n```\n\n## Color Picker\n\nThe color picker can be used to quickly adjust HSV values. \"Set to average\" will set the HSV range to the color of the pixel selected, while \"shrink range\" and \"expand range\" will change the HSV threshold to include or exclude the selected pixel, respectively.\n\n```{raw} html\n\n \n Your browser does not support the video tag.\n \n```\n\n## Tuning Steps\n\nThe following steps were derived from FRC 254's 2016 Championship presentation on computer vision and allows you to accurately tune PhotonVision to track your target.\n\nIn order to properly capture the colors that you want, first turn your exposure low until you have a mostly dark image with the target still showing. A darker image ensures that you don't see things that aren't your target (ex. overhead lights). Be careful not to overexpose your image (you will be able to tell this if a target looks more cyan/white or equivalent instead of green when looking at it through the video feed) since that can give you poor results.\n\nFor HSV tuning, start with Hue, as it is the most important/differentiating factor when it comes to detecting color. You want to make the range for Hue as small as possible in order to get accurate tracking. Feel free to reference the chart below to help. After you have properly tuned Hue, tune for high saturation/color intensity (S), and then brightness (V). Using this method will decrease the likelihood that you need to calibrate on the field. Saturation and Value's upper bounds will often end up needing to be the maximum (255).\n\n```{image} images/hsl_top.png\n:alt: HSV chart\n:width: 600\n```\n",
+ "content_preview": "# Thresholding\n\nFor colored shape detection, we want to tune our HSV thresholds such that only the goal color remains after the thresholding. The [HSV color representation](https://en.wikipedia.org/wiki/HSL_and_HSV) is similar to RGB in that it represents colors."
},
{
"url": "https://docs.photonvision.org/en/latest/docs/benchmarks/rknn-model-benchmarks.html",
@@ -700,12 +372,12 @@
"content_preview": "# RKNN Benchmarks\n\n## Description\nThis benchmark compares the performance of four object detection models: YOLOv5, YOLOv5u, YOLOv8, and YOLOv11 on the [COCO 2017 Validation Set](http://images.cocodataset.org/zips/val2017.zip)."
},
{
- "url": "https://docs.photonvision.org/en/latest/docs/advanced-installation/sw_install/index.html",
- "title": "Software Installation",
- "section": "General",
+ "url": "https://docs.photonvision.org/en/latest/docs/troubleshooting/networking-troubleshooting.html",
+ "title": "Networking Troubleshooting",
+ "section": "Troubleshooting",
"language": "All",
- "content": "# Software Installation\n\n## Desktop Environments\n\n```{toctree}\n:maxdepth: 1\n\nwindows-pc\nlinux-pc\nmac-os\n```\n\n## Other\n\n```{toctree}\n:maxdepth: 1\n\nother-coprocessors\nadvanced-cmd\nromi\n```\n",
- "content_preview": "# Software Installation\n\n## Desktop Environments\n\n```{toctree}\n:maxdepth: 1\n\nwindows-pc\nlinux-pc\nmac-os\n```\n\n## Other\n\n```{toctree}\n:maxdepth: 1\n\nother-coprocessors\nadvanced-cmd\nromi\n```\n"
+ "content": "# Networking Troubleshooting\n\nBefore reading further, ensure that you follow all the recommendations {ref}`in our networking section `. You should follow these guidelines in order for PhotonVision to work properly; other networking setups are not officially supported.\n\n## Checklist\n\nA few issues make up the majority of support requests. Run through this checklist quickly to catch some common mistakes.\n\n- Is your camera connected to the robot's radio through a {ref}`network switch `?\n - Ethernet straight from a laptop to a coprocessor will not work (most likely), due to the unreliability of link-local connections.\n - Even if there's a switch between your laptop and coprocessor, you'll still want a radio or router in the loop somehow.\n - The FRC radio is the _only_ router we will officially support due to the innumerable variations between routers.\n- (Raspberry Pi, Orange Pi & Limelight only) have you flashed the correct image, and is it [up to date](https://github.com/PhotonVision/photonvision/releases/latest)?\n- Is your robot code using a **2026** version of WPILib, and is your coprocessor using the most up to date **2026** release?\n - 2022, 2023, 2024, 2025, and 2026 versions of either cannot be mix-and-matched!\n - Your PhotonVision version can be checked on the settings tab.\n- Is your team number correctly set on the settings tab?\n\n### photonvision.local Not Found\n\nUse [Angry IP Scanner](https://angryip.org/) and look for an IP that has port 5800 open. Then go to your web browser and do \\:5800.\n\nAlternatively, you can plug your coprocessor into a display, plug in a keyboard, and run `hostname -I` in the terminal. This should give you the IP Address of your coprocessor, then go to your web browser and do \\:5800.\n\nIf nothing shows up, ensure your coprocessor has power, and you are following all of our networking recommendations, feel free to {ref}`contact us ` and we will help you.\n\n### Can't Connect To Robot\n\nPlease check that:\n1\\. You don't have the NetworkTables Server on (toggleable in the settings tab). Turn this off when doing work on a robot.\n2\\. You have your team number set properly in the settings tab.\n3\\. Your camera name in the `PhotonCamera` constructor matches the name in the UI.\n4\\. You are using the 2026 version of WPILib and RoboRIO image.\n5\\. Your robot is on.\n\nIf all of the above are met and you still have issues, feel free to {ref}`contact us ` and provide the following information:\n\n- The WPILib version used by your robot code\n- PhotonLib vendor dependency version\n- PhotonVision version (from the UI)\n- Your settings exported from your coprocessor (if you're able to access it)\n- How your RoboRIO/coprocessor are networked together\n",
+ "content_preview": "# Networking Troubleshooting\n\nBefore reading further, ensure that you follow all the recommendations {ref}`in our networking section `."
}
]
}
\ No newline at end of file
diff --git a/src/wpilib_mcp/plugins/redux/data/index.json b/src/wpilib_mcp/plugins/redux/data/index.json
index e59f3e7..0cd5990 100644
--- a/src/wpilib_mcp/plugins/redux/data/index.json
+++ b/src/wpilib_mcp/plugins/redux/data/index.json
@@ -1,7 +1,7 @@
{
"vendor": "redux",
"version": "latest",
- "built_at": "2026-03-29T04:14:22.303466",
+ "built_at": "2026-04-22T17:55:11.348861",
"pages": [
{
"url": "https://docs.reduxrobotics.com/alchemist",
diff --git a/src/wpilib_mcp/plugins/rev/data/index.json b/src/wpilib_mcp/plugins/rev/data/index.json
index b4f0e09..799fc3d 100644
--- a/src/wpilib_mcp/plugins/rev/data/index.json
+++ b/src/wpilib_mcp/plugins/rev/data/index.json
@@ -1,695 +1,6 @@
{
"vendor": "rev",
"version": "latest",
- "built_at": "2026-03-29T03:15:26.402076",
- "pages": [
- {
- "url": "https://docs.revrobotics.com/brushless/home/brushless",
- "title": "REV ION Brushless Overview",
- "section": "General",
- "language": "All",
- "content": "# REV ION Brushless Overview\n\n## The Brushless Revolution\n\nIn the fall of 2018, the NEO Brushless Motor and SPARK MAX Motor Controller became the first brushless motor and compatible ESC (Electronic Speed Controller) designed to meet the unique demands of the FRC community. Since then, REV Robotics has been working to continue the Brushless Revolution by releasing products and software features based on our customer's most popular feedback. \n\n## The Next Generation of Motors and Controllers\n\n \n\n### NEO Vortex Brushless Motor\n\nThe NEO Vortex Brushless Motor is a high-power, high-performance, and high-resolution sensored brushless motor from REV Robotics. It features a dockable controller interface that can be mounted directly to a SPARK Flex Motor Controller or a NEO Vortex Solo Adapter allowing control from any brushless motor controller, like the SPARK MAX. Its through-bore rotor is the heart of its unique interchangeable shaft system, facilitating easy integration with various robot mechanisms.\n\n### SPARK Flex Motor Controller\n\nThe SPARK Flex Motor Controller is a new smart motor controller from REV Robotics. Its dockable form factor allows direct mounting onto a NEO Vortex to simplify wiring and maintain flexibility. Improving upon the foundation of the SPARK MAX, new features of the SPARK Flex Motor Controller include 3-phase current sensing, reverse polarity protection, and an expanded data port with additional interfaces. When docked to an adapter, the SPARK Flex can control any existing NEO or compatible brushless/brushed DC motor.\n\n## Incredible Power Density and Integrated USB Control\n\n \n\n### SPARK MAX Motor Controller\n\nThe SPARK MAX Motor Controller is your first step for getting advanced brushed and brushless DC motor control in a small, easy-to-use package. SPARK MAX is a true all-in-one controller that will push the envelope for FRC teams. Test prototypes and tune parameters without needing the full control system, only using a computer running the REV Hardware Client and a USB C Cable!\n\n### NEO V1.1 Brushless Motor\n\nThe NEO V1.1 Brushless Motor offers an incredible power density due to its compact size and reduced weight. As it is designed to have similar performance characteristics and matching mounting features, NEO V1.1 can be a drop-in replacement for CIM-style motors. This motor is perfect for your FRC Robot, Industrial platform or Warehouse robot, Electric skateboards, and more! The NEO V1.1 has been optimized to work with the SPARK MAX Motor Controller to deliver best-in-class performance and feedback.\n\n### NEO 550 Brushless Motor\n\nThe NEO 550 Brushless Motor is the smallest member of the NEO family of brushless motors. Its output power and small size are designed to make NEO 550 the perfect motor for intakes and other non-drivetrain robot mechanisms. Mounting holes and pilot match a standard 550 series motor, making it natively compatible with many existing off-the-shelf gearboxes.\n\n{% hint style=\"info\" %}\nIf there is a question that is not answered by this space, send our support team an email; ****. We are always happy to help point you in the right direction!\n{% endhint %}\n",
- "content_preview": "# REV ION Brushless Overview\n\n## The Brushless Revolution\n\nIn the fall of 2018, the NEO Brushless Motor and SPARK MAX Motor Controller became the first brushless motor and compatible ESC (Electronic Speed Controller) designed to meet the unique demands of the FRC community."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/home/links",
- "title": "Quick Links",
- "section": "General",
- "language": "All",
- "content": "# Quick Links\n\n## SPARK Motor Controllers\n\n\n\nSPARK Flex Links \n\n## General Resources\n\n* [Getting Started with the SPARK Flex](https://docs.revrobotics.com/brushless/spark-flex/gs)\n* [Troubleshooting](https://docs.revrobotics.com/brushless/spark-flex/troubleshooting)\n * [Status LED Patterns](https://docs.revrobotics.com/brushless/spark-flex/status-led)\n* [SPARK Flex Specifications](https://docs.revrobotics.com/brushless/spark-flex/specs)\n * [SPARK Flex Data Port Pinout](https://docs.revrobotics.com/brushless/spark-flex/specs#data-port-specfifications)\n\n## Software Resources\n\n* [Getting Started with the REV Hardware Client](https://docs.revrobotics.com/rev-hardware-client/)\n* [REVLib API and Installation](https://app.gitbook.com/s/0OKYENVWAIgVP2TmkWl3/install)\n* [Swerve Calibration](https://app.gitbook.com/s/8Fx2woPbmwXcR2T2XfbY/guides/swerve-calibration)\n\n \n\n\n\nSPARK MAX Links \n\n## General Resources\n\n* [Getting Started with the SPARK MAX](https://docs.revrobotics.com/brushless/spark-max/gs)\n* [Troubleshooting](https://docs.revrobotics.com/brushless/spark-max/troubleshooting)\n * [Status LED Patterns](https://docs.revrobotics.com/brushless/spark-max/status-led)\n* [SPARK MAX Specifications](https://docs.revrobotics.com/brushless/spark-max/specs)\n * [SPARK MAX Data Port Pinout](https://docs.revrobotics.com/brushless/spark-max/specs/data-port)\n* [Using Encoders with the SPARK MAX](https://docs.revrobotics.com/brushless/spark-max/encoders)\n\n## Software Resources\n\n* [Getting Started with the REV Hardware Client](https://app.gitbook.com/o/-M3qDjqMSqBbHMXNmC_O/s/-MGEfA6CxjaSQiH5kHxn/)\n* [REVLib API and Installation](https://app.gitbook.com/s/0OKYENVWAIgVP2TmkWl3/install)\n* [Swerve Calibration](https://app.gitbook.com/s/8Fx2woPbmwXcR2T2XfbY/guides/swerve-calibration)\n* [SPARK MAX Code Examples](https://github.com/REVrobotics/REVLib-Examples)\n\n \n\n## NEO Brushless Motors\n\n\n\nMotor Comparison and Testing \n\n* [Motor Comparison](https://docs.revrobotics.com/brushless/neo/compare)\n * [NEO Vortex Comparisons](https://docs.revrobotics.com/brushless/neo/compare#neo-vortex-quick-comparison)\n * [NEO V1.1 Comparisons](https://docs.revrobotics.com/brushless/neo/compare#neo-v1.1-quick-comparison)\n* [Dynamometer Testing](https://docs.revrobotics.com/brushless/home/broken-reference)\n\n \n\n\n\nNEO Vortex Links \n\n* [NEO Vortex Overview](https://docs.revrobotics.com/brushless/neo/vortex)\n* [NEO Vortex Specifications](https://docs.revrobotics.com/brushless/neo/vortex#specifications)\n * [Motor Curves - NEO Vortex](https://docs.revrobotics.com/brushless/neo/vortex#neo-vortex-motor-curve)\n* [Docking a SPARK Flex](https://docs.revrobotics.com/brushless/neo/vortex/docking-flex)\n* [Installing a shaft](https://docs.revrobotics.com/brushless/neo/vortex/shaft-installation)\n\n \n\n\n\nNEO V1.1 Links \n\n## NEO V1.1\n\n* [NEO V1.1 Overview](https://docs.revrobotics.com/brushless/neo/v1.1)\n* [NEO V1.1 Specifications](https://docs.revrobotics.com/brushless/neo/v1.1#specifications)\n * [Motor Curves - NEO V1.1](https://docs.revrobotics.com/brushless/neo/v1.1#neo-v1.1-motor-curve)\n* [Pinion Pressing Guide](https://docs.revrobotics.com/brushless/neo/v1.1/pinion-pressing)\n\nNEO V1\n\n* [NEO V1 Overview](https://docs.revrobotics.com/brushless/neo/v1.1/neo-v1)\n* [NEO V1 Specifications](https://docs.revrobotics.com/brushless/neo/v1.1/neo-v1#specifications)\n * [Motor Curves - NEO V1](https://docs.revrobotics.com/brushless/neo/v1.1/neo-v1#neo-v1-motor-curve)\n* [Pinion Pressing Guide](https://docs.revrobotics.com/brushless/neo/v1.1/pinion-pressing#neo-v1.0-pinion-pressing-guide)\n\n \n\n\n\nNEO 550 Links \n\n* [NEO 550 Overview ](https://docs.revrobotics.com/brushless/neo/550)\n* [NEO 550 Specifications](https://docs.revrobotics.com/brushless/neo/550#specifications) \n * [Motor Curves - NEO 550](https://docs.revrobotics.com/brushless/neo/550#neo-550-motor-curve)\n* [Pinion Pressing Guide ](https://docs.revrobotics.com/brushless/neo/550/pinion-pressing)\n\n \n",
- "content_preview": "# Quick Links\n\n## SPARK Motor Controllers\n\n\n\nSPARK Flex Links \n\n## General Resources\n\n* [Getting Started with the SPARK Flex](https://docs.revrobotics.com/brushless/spark-flex/gs)\n* [Troubleshooting](https://docs.revrobotics.com/brushless/spark-flex/troubleshooting)\n *..."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/home/faq",
- "title": "Frequently Asked Questions",
- "section": "General",
- "language": "All",
- "content": "# Frequently Asked Questions\n\n## NEO Vortex\n\n#### What are some best practices for using the NEO Vortex in a build?\n\n\n\nNEO Vortex Tips and Tricks \n\n1. When seating your Vortex with a SPARK Flex or Solo Adapter make sure your have aligned the bullet connectors and data connectors. They should seat near completely with hand pressure then secured with screws. Do not over tighten.\n2. Do not over tighten the the shaft screw when using a Vortex Shaft. \n3. The Vortex is an outrunner, so make sure there are not wires or components that can touch the spinning rotor. \n4. If the Vortex is used in an application that you are not using a shaft screw, make sure cover the shaft opening on the top of the Vortex to prevent dust and debris from falling into the Vortex spindle. \n\n \n\n#### What Smart Current Limit should I set for my NEO Vortex?\n\n\n\nThe default Smart Current Limit is 80A, which is acceptable for most applications when driven by a SPARK Flex however... \n\nWe recommend utilizing our [motor curves](https://docs.revrobotics.com/brushless/neo/vortex#neo-vortex-motor-curve) and [specifications](https://docs.revrobotics.com/brushless/neo/vortex#motor-specifications) to help calculate what the best current to torque ratio for your application will be. \n\n \n\n## SPARK Flex\n\n#### Are there any quick tips for getting the most from a SPARK Flex?\n\n\n\nSPARK Flex Tips and Tricks \n\n1. Be sure to clean the SPARK Flex's surface with compressed air and remove any dirt or metal debris from the surrounding wire connections. If particulates find their way into the wire connectors you may experience intermittent connection issues.\n2. Maintain sufficient wire management to avoid critical wires from being strained and ripped out. Before your team puts the robot on the field, give all wires one last smart tug to ensure everything is secure for the match.\n3. Remember to update your SPARK Flex to the latest firmware when connecting your robot through the REV Hardware Client.\n\n \n\n## SPARK MAX \n\n#### What is your best advice for using a SPARK MAX?\n\n\n\nSPARK MAX Best Practices \n\n1. Ensure the firmware installed on your SPARK MAXs is the latest version! You can do this using the REV Hardware Client.\n2. Ensure you've set a proper [Smart Current Limit ](https://www.revrobotics.com/neo-brushless-motor-locked-rotor-testing/)for your motor and what it will be driving. \n3. Keep your wire management neat. Wires need to be protected and organized so they are can not be clipped by mechanisms or become an entanglement risk. \n4. Protect the SPARK MAX from debris and impacts. Use the included port covers or tape over the data port when not in use. \n5. Regularly inspect your SPARK MAX and its wires for a secure connection and damage. \n\n \n\n#### How do I run an encoder with the SPARK MAX?\n\n\n\nWe have a documentation page that can help! \n\nCheck out [Using Encoders with the SPARK MAX](https://docs.revrobotics.com/brushless/spark-max/encoders)!\n\n \n\n#### My SPARK MAX isn't working, where do I start?\n\n\n\nDon't panic! Take a step back and assess the current situation. \n\nA good place to start isolating the root issue would be to go through our[ Troubleshooting - SPARK MAX](https://docs.revrobotics.com/sparkmax/troubleshooting) documentation.\n\n \n\n#### What does the LED on my SPARK MAX indicate?\n\n\n\nThe Status LED indicates the operation mode or fault status of your SPARK MAX \n\nSee our reference table on the[ SPARK MAX Status LED Patterns](https://docs.revrobotics.com/brushless/spark-max/status-led) to find the LED behavior that matches your SPARK MAX. Both color and speed of the blinking pattern are important to note! \n\n \n\n#### Should I change the factory default setting for Smart Current Limit?\n\n\n\nYour Smart Current Limit will depend on the motor being controlled and what it is driving. \n\nA new SPARK MAX's Smart Current Limit default setting is 80A, but may need to be less. We recommend utilizing our [locked-rotor testing data](https://docs.revrobotics.com/brushless/neo/locked-rotor-testing) or the table below to decide what to set your Smart Current Limit to for your robot.\n\nGenerally, the following ranges work well:\n\n* NEO V1.1: 40 A - 60 A\n* NEO 550: 20 A - 40 A\n\n \n\n#### Can I run a NEO Vortex with a SPARK MAX?\n\n\n\nYou will need to purchase one of our NEO Vortex Solo Adapters (REV-11_2828) \n\nCheck out our documentation on the [NEO Vortex Solo Adapter](https://docs.revrobotics.com/brushless/neo/vortex/solo-adapter) and the [SPARK MAX ](https://docs.revrobotics.com/brushless/spark-max/gs)for more information. \n\n \n\n#### Why don't the settings save on my SPARK MAX? \n\n\n\nBe sure to always click Persist Perimeters (formerly: Burn Flash) to save the settings through the REV Hardware Client! \n\nA SPARK MAX's parameters can also be overwritten within your code, so ensure you also burn flash after any changes in your program too. Any settings that had been modified and not persisted will reset when the SPARK MAX is power cycled. \n\n \n\n## NEO V1.1\n\n#### What are general best practices for the NEO V1.1?\n\n\n\nREV's NEO V1.1 Tips and Tricks \n\n1. Use care when removing the sensor wire from a SPARK MAX. Firmly gripping all 6 of the wires as close to the JST connector as possible and pulling evenly generally gives best results. If needed you can also use a small tool to pry or grab the plastic notches of the JST Connector.\n2. Periodically check the NEO's phase and data wires for noticeable damage to the wire insulation or connectors. Exposed wire on your robot runs the risk of a possible short to your system.\n3. Keep the NEO clear from any debris, especially those that could get caught in the mechanism it is driving or affect your wiring. \n4. Route all wires with proper strain relief to avoid damage or unintentional disconnects from tension.\n5. MAXPlanetary Gearbox Cartridges are pre-lubricated and sealed. If during maintenance you find that a cartridge needs more grease, we recommend using a Molybdenum Grease to apply more lubrication such as[ Synthetic NLGI #2 Molybdenum Grease](https://www.amazon.com/Schaeffer-Manufacturing-02742-029S-Synthetic-Grease/dp/B00JF2LBQ6?th=1) or[ MOLYKOTE® G-2008 Synthetic Tool Gear Grease](https://www.mmtoolparts.com/dewalt-grease-n017298).\n\n \n\n#### What Smart Current Limit should I set for my NEO V1.1?\n\n\n\nGenerally we recommend 40A - 60A \n\nBut this setting will be dependent on the application you've assigned to the NEO v1.1. Is the motor acting as the main drive motor on a MAXSwerve Module? Or is it actuating the elbow on your arm mechanism? Please briefly reference our [NEO Motor Locked Rotor Testing.](https://docs.revrobotics.com/brushless/neo/locked-rotor-testing)\n\nSay for example you have four NEO v1.1s assigned as the main drive motors on MAXSwerve Modules for your robot, and the Smart Current Limit is set to 60A. If your robot is pushing against an object causing the NEO v1.1s to experience stalling, potential motor failure would occur at approximately 105 seconds due to the build up of thermal energy. Realistically, setting the Smart Current Limit to 20A and gradually increasing the amperage enough to break traction with the ground is adequate. \n\nAnother example would be if you have one NEO v1.1, and your team has decided to set the Smart Current Limit past the recommended 60A for an arm mechanism. While the the motor is capable of preforming past 60A, if your arm mechanism is stuck on some game structure the window of motor failure is shorter than 105 seconds.\n\n \n\n#### How do I make a NEO V1.1 compatible with the new SPARK FLEX Motor Controllers?\n\n\n\nYou can use a Flex Dock! \n\nThe [Flex Dock (REV-11-2858)](https://www.revrobotics.com/rev-11-2858/) allows a SPARK Flex to control any existing NEO or compatible brushless/brushed DC motor by converting it to a standalone motor controller!\n\n \n\n#### Can I run a NEO V1.1 on 24V?\n\n\n\nThis is possible but we strongly recommend... \n\nBefore running a NEO V1.1 at a 24V, we recommend first confirming that the motor controller that will drive it can also work at 24V as well. \n\nWhen a NEO is run at higher voltages, its specifications will also scale. All of the data listed on the product page and motor testing was run at 12V, so, when running a NEO at 24V you would need to double the values. \n\nFor example:\n\n* Free Speed @ 12V - 5676 RPM\n* Free Speed @ 24V - 11352 RPM\n\n \n\n## NEO 550 FAQ\n\n#### What are the general best practices for the NEO 550?\n\n\n\nREV's NEO 550 Tips and Tricks \n\n1. Use care when removing the sensor wire from a SPARK MAX. Firmly gripping all 6 of the wires as close to the JST connector as possible and pulling evenly generally gives best results. If needed you can also use a small tool to pry or grab the plastic notches of the JST Connector.\n2. Periodically check the NEO 550's phase and data wires for noticeable damage to the wire insulation or connectors. Exposed wire on your robot runs the risk of a possible short to your system.\n3. Keep the NEO 550 clear from any debris, especially those that could get caught in the mechanism it is driving or affect your wiring.\n4. Route all wires with proper strain relief to avoid damage or unintentional disconnects from tension.\n5. UltraPlanetary Gearbox Cartridges are pre-lubricated and sealed. If during maintenance you find that a cartridge needs more grease, we recommend using a Molybdenum Grease to apply more lubrication such as [Synthetic NLGI #2 Molybdenum Grease](https://www.amazon.com/Schaeffer-Manufacturing-02742-029S-Synthetic-Grease/dp/B00JF2LBQ6?th=1) or [MOLYKOTE® G-2008 Synthetic Tool Gear Grease](https://www.mmtoolparts.com/dewalt-grease-n017298).\n6. When pressing a pinion on your NEO 550 shaft, taken care to not over press the pinion or dislodge the motor's shaft. Take a look at our [Pinion Pressing guide](https://docs.revrobotics.com/brushless/neo/550/pinion-pressing#neo-550-pinion-pressing-guide) for assistance. \n\n \n\n#### What Smart Current Limit should I set for my NEO 550?\n\n\n\nGenerally we recommend 20A - 40A \n\nBut this setting will be dependent on the application you've assigned to the NEO 550. Is the motor being used as a steering motor on a swerve drive? Or is the motor being utilized to articulate a part of your intake? Please briefly reference our [NEO 550 Motor Locked Rotor Testing](https://www.revrobotics.com/neo-550-brushless-motor-locked-rotor-testing/).\n\nSay for example you have four NEO 550s assigned as the steering motors on MAXSwerve Modules for your robot, and the Smart Current Limit is set to 40A. If field debris impedes your robot is from completing its turn causing the NEO 550 to experience stalling, potential motor failure would occur at approximately 27 seconds due to the build up of thermal energy. However, setting the Smart Current Limit to 20A increases this window to 220 seconds, which should withstand most stall encounters.\n\n \n\n \n",
- "content_preview": "# Frequently Asked Questions\n\n## NEO Vortex\n\n#### What are some best practices for using the NEO Vortex in a build?\n\n\n\nNEO Vortex Tips and Tricks \n\n1."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/neo/brushless-dc-motor-basics",
- "title": "Brushless DC Motor Basics",
- "section": "General",
- "language": "All",
- "content": "# Brushless DC Motor Basics\n\nDC Motors consist of two major parts, the part that rotates, or the “rotor”, and the part that is stationary, or the “stator”. A DC motor uses these parts to convert electrical energy into rotational mechanical energy using electricity and permanent magnets. Two types of DC motors are used in FIRST Robotics Competition: Brushed DC Motors and Brushless DC motors. Both types are useful in various robot applications, and both have their trade-offs.\n\n## Brushless vs. Brushed Motor Basics\n\nOperating a brushed DC motor is simple; provide DC electrical power and the motor spins. In a brushed motor, the rotor consists of electrical winding wires and the stator consists of permanent magnets. Since the electrical part is spinning, there needs to be a way to connect the external power wires to the spinning rotor. This is accomplished through conductive “brushes” that make contact with the stator, automatically sequencing the power to make the rotor spin. Brushes make it easy on us, but they produce extra friction which reduces the efficiency of the motor.\n\nBrushless DC motors don’t have brushes. They still have both electrical winding wires and permanent magnets, but the locations are flipped. The stator now consists of the electrical parts, and the spinning rotor consists of the magnets. This means there is no more brush friction within the motor, making a brushless motor more power-efficient. However, you can’t just give it DC power and expect it to spin. Without the brushes doing the sequencing for us, you must use a specialized motor controller that is designed for brushless motors to properly sequence the power and get the rotor spinning.\n\nThe REV NEO Brushless Motor runs an 8mm keyed output shaft which allows for an easy transition from CIM-style brushed motors into brushless.\n\nSwap a set of NEO Brushless Motors into your drivetrain or use one in an elevator to save weight and maintain peak performance. When paired with the SPARK MAX, you can use the integrated hall-effect sensors to calculate incremental position or speed from the NEO.\n\n### Key Terms\n\n{% tabs %}\n{% tab title=\"Stall Torque\" %}\n**Stall Torque** is measured when the motors RPM is zero and the motor is drawing its full **Stall Current**. This value is the maximum torque the motor is ever capable of outputting. Keep in mind the motor is not capable of outputting this torque for an indefinite period of time. Waste energy will be released into the motor as heat. When the motor is producing more waste heat than the motor body is capable of dissipating the motor will eventually overheat and fail.\n{% endtab %}\n\n{% tab title=\"Stall Current\" %}\n**Stall Current** is the maximum amount of current the motor will draw. The stall current is measured at the point when the motor has torque that the RPM goes down to zero. This is also the point at which the most waste heat will be dissipated into the motor body.\n{% endtab %}\n\n{% tab title=\"Free Speed\" %}\n**Free Speed** is the **angular velocity** that a motor will spin at when powered at the **Operating Voltage** with zero load on the motor’s output shaft. This RPM is the fastest **angular velocity** the motor will ever spin at. Once the motor is under load its **angular velocity** will decrease.\n{% endtab %}\n\n{% tab title=\"Operating Voltage\" %}\n**Operating Voltage** is the expected voltage that the motor will experience during operation. If a robot is built using a 12 volt battery the **Operating Voltage** of the motor will be 12 volts. When controlling the RPM of the motor the DC speed controller will modulate the effective voltage seen by the motor. The lower the voltage seen by the motor the slower it will spin. DC motors have a maximum rated voltage if this voltage is exceeded the motor will fail prematurely.\n{% endtab %}\n{% endtabs %}\n\n{% hint style=\"info\" %}\nThe key metrics defined above are interrelated. Take some time to familiarize yourself with the definitions and how they connect together.\n{% endhint %}\n\n## General Application Information\n\nIn order to ensure that an electric motor lasts as long as possible a few rules of thumb should be kept in mind:\n\n1. **Smooth loading** - large torque spikes or sudden changes in direction can cause excess wear and premature failure of gearbox components. This is only an issue when the torque spike exceeds the rated stall torque of the motor. When shock loading is necessary, it is best to utilize mechanical braking or a hard stop that absorbs the impact instead of the motor.\n2. **Overheating** - when a motor is loaded at near its maximum operating torque it will produce more waste heat than when operating at a lower operating torque. If this heat this allowed to build up the motor can wear out prematurely or fail spontaneously.\n3. **Poorly supported output shaft**, most motor output shafts are not designed to take large thrust forces or forces normal to the shaft. Bearings need to be used to support the axle when loads in these directions are expected.\n",
- "content_preview": "# Brushless DC Motor Basics\n\nDC Motors consist of two major parts, the part that rotates, or the “rotor”, and the part that is stationary, or the “stator”. A DC motor uses these parts to convert electrical energy into rotational mechanical energy using electricity and permanent magnets."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/neo/vortex",
- "title": "NEO Vortex",
- "section": "General",
- "language": "All",
- "content": "# NEO Vortex\n\n## NEO Vortex Overview\n\nThe [NEO Vortex Brushless Motor (REV-21-1652)](https://www.revrobotics.com/rev-21-1652/) is a high-power, high-performance, and high-resolution sensored brushless motor from REV Robotics. It features a dockable controller interface that can be mounted directly to the [SPARK Flex Motor Controller (REV-11-2159)](https://www.revrobotics.com/rev-11-2159/) or a [NEO Vortex Solo Adapter (REV-11-2828)](https://www.revrobotics.com/rev-11-2828/) allowing control from any brushless motor controller, like the SPARK MAX. Its through-bore rotor is the heart of its unique interchangeable shaft system, facilitating easy integration with various robot mechanisms.\n\n \n\n### Features\n\n* High-resolution encoder\n* Integrated motor parameter and calibration memory\n* Through-hex bore with taper for numerous quick-change shafts\n* No motor wires - reliable and robust docking connections for motor phases and sensor\n* Dual sensor, direct contact winding temperature sensing\n* 560KV (RPM per volt)\n* 640 Watts (375 @ 40A)\n* \\#10-32 threaded holes on a 2in bolt circle\n* The motor and motor controller's silhouette fits behind a standard 2in rectangular tube\n* 1/2in hex through-bore rotor compatible with any length hex shaft or application-specific Vortex Shafts:\n * 8mm keyed\n * Falcon compatible spline\n * MAXSwerve with integrated key\n * 7-tooth 20DP gear\n * MAXPlanetary input\n * Others to be announced\n\n## NEO Vortex Anatomy\n\nParts of a NEO Vortex Brushless Motor
\n\n## Motor Specifications \n\nParameter Value and Units Nominal Operating Voltage 12 V Motor Kv 565 Kv Free Speed 6784 RPM Free Running Current 3.6 A Stall Current 211 A Stall Torque 3.6 Nm Peak Output Power 640 W Typical Output Power at 40 A 375 W Pole Pairs 7 Encoder Resolution with SPARK MAX 42 Counts per rev. Encoder Resolution with SPARK Flex † 7168 Counts per rev.
\n\n| † | A firmware update will be required to access higher resolution encoder data. |\n| - | ---------------------------------------------------------------------------- |\n\n### Mechanical Specifications\n\nParameter Value and Units Docked Body Length † 79.7 mm Docked Mounting Footprint - Narrow Side Width 2 in Docked Mounting Footprint - Rounded Side Diameter 60 mm Docked Spindle Offset Depth 19.7 mm Docking Hardware ‡ M3 SHCS x 25 mm Rotor Diameter 50 mm Spindle Bore 1/2 in hex with 7.5° half-angle taper Shaft Retention Counter Bore Diameter 17.75 mm Shaft Retention Counter Bore Depth 4 mm Weight 447 g (0.99 lbs)
\n\n| † | When docked with SPARK Flex Motor Controller or NEO Vortex Solo Adapter. |\n| - | ------------------------------------------------------------------------ |\n| ‡ | Docking hardware included with SPARK Flex or Vortex Solo Adapter. |\n\n## NEO Vortex Motor Curve\n\nNEO Vortex Motor Curve
\n",
- "content_preview": "# NEO Vortex\n\n## NEO Vortex Overview\n\nThe [NEO Vortex Brushless Motor (REV-21-1652)](https://www.revrobotics.com/rev-21-1652/) is a high-power, high-performance, and high-resolution sensored brushless motor from REV Robotics."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/neo/vortex/docking-flex",
- "title": "Docking a SPARK Flex",
- "section": "General",
- "language": "All",
- "content": "# Docking a SPARK Flex\n\nWhen docked with a SPARK Flex the NEO Vortex's phase and sensor connections are kept securely together. This eliminates intermediate wiring that can fail if not secured properly.\n\n## Docking Materials\n\nDocking the NEO Vortex with the SPARK Flex is simple and only requires the following materials and tools:\n\n* NEO Vortex Brushless Motor\n* SPARK Flex Motor Controller\n* Docking Hardware\n * 4 - M3 x 25 mm Socket Head Screws (included with SPARK Flex)\n* 2.5 mm Hex Key\n\n## Docking Procedure\n\nFollow these steps to ensure a secure and proper docking:\n\n1. Ensure that power is disconnected from the SPARK Flex.\n2. Align the motor phase bullets between the NEO Vortex and SPARK Flex.\n3. Allowing the bullets to guide the two together, press the NEO Vortex and SPARK Flex together until their bodies meet. There may be a small gap between the motor and the controller opposite the bullets. This is normal.\n4. Insert the included docking screws into the counterbored Docking Screw Clearance Holes on the SPARK Flex.\n5. Using the 2.5 mm Hex Key, tighten the four screws evenly in a crisscross pattern until the screws are tight and secure. The screw heads should be sub-flush from the mounting face of she SPARK Flex. If you have a torque wrench, the ideal torque is 11.5 ±0.9 in-lb (1.3 ±0.1 Nm).\n\n{% hint style=\"danger\" %} DO NOT run the motor without the docking screws installed. These screws ensure a robust and secure electrical connection, and doing so may cause damage to the system.\n{% endhint %}\n\n \n\n## Undocking Procedure\n\nFollow these steps to undock the NEO Vortex and SPARK Flex:\n\n1. Ensure that power is disconnected from the SPARK Flex.\n2. Completely remove the four docking screws from the assembly.\n3. Gently pull the NEO Vortex and SPARK Flex apart until the bullets release. Try to maintain their relative orientation to each other while pulling them apart.\n",
- "content_preview": "# Docking a SPARK Flex\n\nWhen docked with a SPARK Flex the NEO Vortex's phase and sensor connections are kept securely together. This eliminates intermediate wiring that can fail if not secured properly.\n\n## Docking Materials\n\nDocking the NEO Vortex with the SPARK Flex is simple and only requires..."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/neo/vortex/vortex-shafts",
- "title": "Vortex Shafts",
- "section": "General",
- "language": "All",
- "content": "# Vortex Shafts\n\nNEO Vortex Shaft Features \n\nVortex shafts feature a 1/2 in hex section for transferring torque, and a locating taper section for effortless self-centering. When the shaft is secured with a #10-32 Shaft End Screw, the taper keeps your shaft perfectly centered as it's drawn in and locked into place within the NEO Vortex Spindle.\n\n### NEO Vortex Shaft Anatomy\n\nParts of a NEO Vortex Shaft
\n\n## NEO Vortex Shaft Options \n\n| Product Photo | Name, SKU, and Function |\n| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| | 1/2 in Hex Shaft - Any Length (Multiple SKU )
The NEO Vortex's 1/2in hex through-bore motor spindle is compatible with any length hex shaft.
|\n|  | Vortex Shaft - 15T Spline (REV-21-2849 ) The Vortex Shaft - 15T Spline, compatible with the REV ION System , gives users the ability to drive inputs that have a 8mm diameter involute 15T spline. Compatible with SplineXS shaft components.
|\n| | Vortex Shaft - 8 mm (REV-21-2807 ) The 8mm Vortex Shaft, compatible with the REV ION System , empowers you to utilize the NEO Vortex Brushless Motor with any 8mm keyed shaft component. This interchangeability also means that the NEO Vortex matches the output of a NEO Brushless Motor V1.1 , making it a seamless drop-in replacement for your designs.
|\n| | Vortex Shaft - MAXSwerve Integrated Key ( REV-21-2848 ) The Vortex Shaft - MAXSwerve Integrated Key gives users the ability to drive a MAXSwerve Module without the need for the MAXSwerve Key. This eliminates a potential point of failure in the MAXSwerve since the key is machined directly into the 8mm shaft.
|\n| | Vortex Shaft - 20 DP Gear - 7T (REV-21-6800 ) The Vortex Shaft - 20 DP Gear - 7T offers users the unique ability to achieve substantial gear reductions directly from the motor. This gear features a 7T 20 DP design that is typically too small for an 8mm keyed input. However, it becomes possible when integrated into the motor shaft.
The Vortex Shaft - 20 DP Gear - 7T is addendum shifted to an 8 T pitch diameter. This allows you to calculate center-to-center distances as if it were an 8T gear rather than a 7T.
|\n|  | The Vortex Shaft - 20DP Gear - 8T offers users the unique ability to achieve substantial gear reductions directly from the motor. This gear features a 8T 20DP design that is typically too small for an 8mm keyed input. However, it becomes possible when integrated into the motor shaft. |\n|  | Vortex Shaft - MAXPlanetary Input Kit
(REV-21-2130 ) Working seamlessly with the MAXPlanetary Vortex Input Stage, the Vortex Shaft - MAXPlanetary Input Coupler reduces gearbox length for more efficient designs. Note that the MAXPlanetary Vortex Input Stage is required for this setup.
|\n| | Vortex Shaft - Falcon Compatible Spline (REV-21-2827 ) The Vortex Shaft - Falcon Compatible Spline gives users the ability to drive components that have a falcon spline bore.
|\n",
- "content_preview": "# Vortex Shafts\n\nNEO Vortex Shaft Features \n\nVortex shafts feature a 1/2 in hex section for transferring torque, and a locating taper section for effortless self-centering."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/neo/vortex/shaft-installation",
- "title": "Installing a Shaft",
- "section": "General",
- "language": "All",
- "content": "# Installing a Shaft\n\n## Shaft Installation Materials\n\nInstalling a Vortex Shaft is simple and requires the following materials and tools:\n\n* NEO Vortex (docked or undocked)\n* Desired Vortex Shaft\n* \\#10-32 Shaft End Screw\n* 5/32 in Hex Key\n\n## Shaft Installation Procedure\n\nFollow these steps to ensure a secure shaft installation:\n\n1. Insert the shank of the desired shaft into the Vortex Spindle from the front mounting face of the motor.\n2. While inserting, you may need to rotate the shaft or rotor slightly to align the hexagonal portion of the shank with the hexagonal bore of the spindle.\n3. Once the shaft is fully inserted, install the Shaft End Screw through the back side of the spindle and thread into the shank.\n4. Tighten the Shaft End Screw with the 5/32 in hex key to draw the taper into a locked and centered position. If you have a torque wrench, the ideal torque is 25 ±5 in-lb (2.8 ±0.6 Nm).\n\n{% hint style=\"info\" %}\nVisually check for concentricity by rotating the motor rotor. If the shaft is not concentric, remove it, rotate its orientation, and reseat in the spindle.\n{% endhint %}\n\n## Shaft Removal Procedure\n\n1. Remove the Shaft End Screw with the 5/32 in hex key.\n2. Remove the Vortex Shaft from the motor.\n\n{% hint style=\"info\" %}\nWhile the Vortex Shaft taper is designed to self release, it may still need a gentle tap to help it along. Be sure to tap the shaft itself and not the rotor. You can partially back out the Shaft End Screw and tap it to push the shaft out.\n{% endhint %}\n",
- "content_preview": "# Installing a Shaft\n\n## Shaft Installation Materials\n\nInstalling a Vortex Shaft is simple and requires the following materials and tools:\n\n* NEO Vortex (docked or undocked)\n* Desired Vortex Shaft\n* \\#10-32 Shaft End Screw\n* 5/32 in Hex Key\n\n## Shaft Installation Procedure\n\nFollow these steps to..."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/neo/vortex/solo-adapter",
- "title": "NEO Vortex Solo Adapter",
- "section": "General",
- "language": "All",
- "content": "# NEO Vortex Solo Adapter\n\nThe NEO Vortex Solo Adapter (REV-11-2828), allows teams to seamlessly integrate the NEO Vortex Brushless Motor with a SPARK MAX. This adapter breaks out the motor sensor connector and phase wire connections for simple backward compatibility.\n\n{% hint style=\"info\" %}\nWhile the NEO Vortex Solo Adapter allows you to use the motor independently, it may not fully support certain advanced functionalities of the NEO Vortex Brushless Motor.\n{% endhint %}\n\n \n\n## Specifications\n\n| Parameter | Value and Units |\n| ---------------------------------------- | -------------------------- |\n| Phase Wire Gauge | 12 AWG |\n| Encoder Port Connector | JST PH 6-pin |\n| Through Bore Diameter | 16.5 mm (0.649 in) |\n| Mounting Footprint Narrow Side Width | 2 in |\n| Mounting Footprint Rounded Side Diameter | 60 mm |\n| Mounting Holes | #10-32 on 2 in bolt circle |\n| Mounting Hole Maximum Depth | 0.25 in |\n| Body Length (Not Docked) | 28.2 mm |\n| Docking Hardware | M3 SHCS x 25 mm |\n\n## Kit Contents\n\nThe following items are included with each **NEO Vortex Solo Adapter**\n\nSKU Product Name QTY REV-21-2828 NEO Vortex Solo Adapter 1 REV-21-3204-PK4 M3 x 25mm Socket Head Screw - 4 Pack 1 Pack, 4 Screws 6-Pin JST Cable 1
\n\n## Docking a NEO Vortex Solo Adapter\n\nWhen docked with a NEO Vortex Solo Adapter a NEO Vortex can be controlled with a SPARK MAX. \n\n### Docking Materials \n\nDocking the NEO Vortex with the SPARK Flex is simple and only requires the following materials and tools:\n\n* NEO Vortex Brushless Motor\n* NEO Vortex Solo Adapter\n* Docking Hardware\n * 4 - M3 x 25 mm Socket Head Screws (included with SPARK Flex)\n* 2.5 mm Hex Key\n\n### Docking Procedure \n\nFollow these steps to ensure a secure and proper docking:\n\n1. Ensure that power is disconnected from the NEO Vortex Solo Adapter.\n2. Align the motor phase bullets between the NEO Vortex and NEO Vortex Solo Adapter.\n3. Allowing the bullets to guide the two together, press the NEO Vortex and NEO Vortex Solo Adapter together until their bodies meet. There may be a small gap between the motor and the controller opposite the bullets. This is normal.\n4. Insert the included docking screws into the counterbored Docking Screw Clearance Holes on the NEO Vortex Solo Adapter.\n5. Using the 2.5 mm Hex Key, tighten the four screws evenly in a crisscross pattern until the screws are tight and secure. The screw heads should be sub-flush from the mounting face of she NEO Vortex Solo Adapter. If you have a torque wrench, the ideal torque is 11.5 ±0.9 in-lb (1.3 ±0.1 Nm).\n\n{% hint style=\"danger\" %}\nDO NOT run the motor without the docking screws installed. These screws ensure a robust and secure electrical connection, and doing so may cause damage to the system.\n{% endhint %}\n\nDocking a NEO Vortex to a SPARK Flex/NEO VortexSolo Adapter
\n\n### Undocking Procedure \n\nFollow these steps to undock the NEO Vortex and NEO Vortex Solo Adapter:\n\n1. Ensure that power is disconnected from the NEO Vortex Solo Adapter.\n2. Completely remove the four docking screws from the assembly.\n3. Gently pull the NEO Vortex and NEO Vortex Solo Adapter apart until the bullets release. Try to maintain their relative orientation to each other while pulling them apart.\n",
- "content_preview": "# NEO Vortex Solo Adapter\n\nThe NEO Vortex Solo Adapter (REV-11-2828), allows teams to seamlessly integrate the NEO Vortex Brushless Motor with a SPARK MAX."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/neo/2.0",
- "title": "NEO 2.0",
- "section": "General",
- "language": "All",
- "content": "# NEO 2.0\n\n## NEO 2.0 Overview\n\n \n\n### Features\n\n* High-performance brushless DC motor designed for competitive robotics\n* Compatible with all SPARK brushless motor controllers.\n* Fully integrated hall-effect encoder for closed-loop control\n* Adopts the SPARK FLEX mounting pattern for direct compatibility with MAXPlanetary and the broader REV ecosystem, aligning flats for a low-profile fit\n* Front and rear ball bearings\n* High temperature neodymium magnets\n* Motor temperature sensor\n* Connectorized motor sensor output for use with the [NEO 2.0 Sensor Cable](#neo-2.0-sensor-cable)\n* Diametrically magnetized magnet on the rotor-end of the shaft to support magnetic encoder.\n\n#### New to NEO 2.0\n\n* 15T spline shaft for secure power transfer without the need for keys or keyways\n* Detachable encoder cable for easy replacement and serviceability.\n* Slimmed 2-inch width profile with exposed outrunner design, allowing mounting within the silhouette of common 2in structural tubing like MAXTube. \n\nRepositioned sensor board for better thermal reliability.\n\n## Wiring Connections\n\nFollow the guide at[ Wiring the Spark Max](https://docs.revrobotics.com/brushless/spark-max/gs/wiring), and don't forget to connect your sensor wire; the motor will not spin without it!\n\n{% hint style=\"danger\" %}\nCAUTION: Improperly wiring the connectors can cause severe motor damage and is not covered by the warranty. DO NOT connect the motor directly to the battery. \n{% endhint %}\n\n### NEO 2.0 Sensor Port Pinout\n\n \n\n## Motor Specifications\n\n| Parameter | Value and Units |\n| ------------------------- | --------------- |\n| Nominal Operating Voltage | 12 V |\n| Motor Kv | 473 Kv |\n| Free Speed | 5676 RPM |\n| Free Running Current | 1.8 A |\n| Stall Current | 150 A |\n| Stall Torque | 3.75 Nm |\n| Peak Output Power | 540 W |\n\n### Mechanical Specifications\n\nParameter Value and Units Output Shaft Diameter 15T Spline Output Shaft Length 31.5mm (1.24in) Output Pilot 19.05mm (0.75in) Body Length 48mm (1.89in) Body Diameter - Maximum 60mm (2.36in) Body Diameter - Minimum 50.8mm (2in) Mounting Face minimum width 50.8mm (2in) Mounting Holes #10-32 tapped Mounting Hole Depth 9.5mm (0.375in) Shield Mounting Screws M3 Shield Mounting Screw max depth 2mm Weight 364g (0.8lb) Phase Wire Length 150mm (5.91in) Phase Wire Gauge 12AWG
\n\n## NEO 2.0 Sensor Cable\n\n \n\nThe NEO 2.0 Sensor Cable connects the NEO 2.0 Brushless Motor’s sensor port to a standard JST PH 6-pin interface used on REV motor controllers. The NEO 2.0 Sensor Cable provides a secure, latching connection at the motor, while the JST PH 6-pin end maintains compatibility with existing controller inputs.\n\n### Specifications\n\nParameter Value and Units Wire Length 400mm (15.75in) Wire Gauge 22AWG Weight 11g (0.024lb)
\n",
- "content_preview": "# NEO 2.0\n\n## NEO 2.0 Overview\n\n \n\n### Features\n\n* Drop-in replacement for CIM-style motors\n* Shielded out-runner construction\n* Front and rear ball bearings\n* High-temperature neodymium magnets\n* High-flex silicone motor wires\n* Integrated motor sensor\n * 3-phase hall sensors\n * Motor temperature sensor\n\n#### New to NEO V1.1\n\n* A tapped #10-32 hole on the end of the shaft, allowing teams to retain pinions on the shaft without using external retaining rings \n* A tapped #10-32 hole on the back housing of the motor, making it no longer necessary to remove the motor housing to press pinions \n* Additional holes on the front face of the motor for added mounting flexibility\n\n## Wiring Connections\n\nConnecting the NEO V1.1 Brushless motor is fairly straightforward. Follow the guide at[ Wiring the Spark Max](https://docs.revrobotics.com/brushless/spark-max/gs/wiring), and don't forget to connect your sensor wire; the motor will not spin without it!\n\n{% hint style=\"danger\" %}\nCAUTION: Improperly wiring the connectors can cause severe motor damage and is not covered by the warranty. DO NOT connect the motor directly to the battery. \n{% endhint %}\n\n## Motor Specifications\n\n| Parameter | Value and Units |\n| ------------------------------ | ------------------ |\n| Nominal Operating Voltage | 12 V |\n| Motor Kv | 473 Kv |\n| Free Speed\\` | 5676 RPM |\n| Free Running Current | 1.8 A |\n| Stall Current | 105 A |\n| Stall Torque | 2.6 Nm |\n| Peak Output Power | 406 W |\n| Typical Output Power at 40 A | 380 W |\n| Hall-Sensor Encoder Resolution | 42 counts per rev. |\n\n### Mechanical Specifications\n\nParameter Value and Units Output Shaft Diameter 8mm (keyed) Output Shaft Length 35mm (1.38in) Output Pilot 19.05mm (0.75in) Body Length 58.25mm (2.3in) Body Diameter 60mm (2.36in) Weight 0.938 lbs (0.425 kg) Phase Wire Length 5.91in (150mm) Phase Wire Gauge 12AWG Sensor Cable Length 11.81in (300mm) Sensor Cable Gauge 24AWG
\n\n## NEO V1.1 Motor Curve\n\nNEO v1.1 Motor Curve
\n\n{% hint style=\"info\" %}\nPlease read our [Locked Rotor Testing Documentation](https://docs.revrobotics.com/brushless/neo/locked-rotor-testing) and ensure you understand how to set an appropriate [Smart Current Limit](https://docs.revrobotics.com/brushless/spark-max/gs/make-it-spin#limiting-current) before using your NEO Brushless Motor.\n{% endhint %}\n",
- "content_preview": "# NEO V1.1\n\n## NEO V1.1 Overview\n\nThe REV [NEO Brushless Motor V1.1 (REV-21-1650)](https://www.revrobotics.com/rev-21-1650/) is the initial update on the first brushless motor designed to meet the unique demands of the *FIRST* Robotics Competition community."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/neo/v1.1/neo-v1",
- "title": "NEO V1",
- "section": "General",
- "language": "All",
- "content": "# NEO V1\n\n## NEO V1 Overview\n\nThe REV NEO Brushless Motor (REV-21-1650) is the first brushless motor designed to meet the unique demands of the FRC community. Offering an incredible power to weight ratio along with it's compact size it's designed to be a drop-in replacement for CIM-style motors as well as an easy install with mounting options.\n\n \n\n### Features\n\n* Drop-in replacement for CIM-style motors\n* Shielded out-runner construction\n* Front and rear ball bearings\n* High-temperature neodymium magnets\n* High-flex silicone motor wires\n* Integrated motor sensor\n * 3-phase hall sensors\n * Motor temperature sensor\n\n## Wiring Connections\n\nConnecting the NEO V1.1 Brushless motor is fairly straightforward. Follow the guide at[ Wiring the Spark Max](https://docs.revrobotics.com/brushless/spark-max/gs/wiring), and don't forget to connect your sensor wire; the motor will not spin without it!\n\n{% hint style=\"danger\" %}\nCAUTION: Improperly wiring the connectors can cause severe motor damage and is not covered by the warranty. DO NOT connect the motor directly to the battery. \n{% endhint %}\n\n## Motor Specifications\n\n| Parameter | Value and Units |\n| ------------------------------ | ------------------ |\n| Nominal Operating Voltage | 12 V |\n| Motor Kv | 473 Kv |\n| Free Speed\\` | 5676 RPM |\n| Free Running Current | 1.8 A |\n| Stall Current | 105 A |\n| Stall Torque | 2.6 Nm |\n| Peak Output Power | 406 W |\n| Typical Output Power at 40 A | 380 W |\n| Hall-Sensor Encoder Resolution | 42 counts per rev. |\n\n### Mechanical Specifications\n\nParameter Value and Units Output Shaft Diameter 8mm (keyed) Output Shaft Length 35mm (1.38in) Output Pilot 19.05mm (0.75in) Body Length 58.25mm (2.3in) Body Diameter 60mm (2.36in) Weight 0.938 lbs (0.425 kg)
\n\n## NEO V1 Motor Curve\n\n{% hint style=\"info\" %}\nThe only difference between the NEO V1 and NEO V1.1 are external changes to the motor's housing. \n{% endhint %}\n\n \n\n{% hint style=\"info\" %}\nPlease read our [Locked Rotor Testing Documentation](https://docs.revrobotics.com/brushless/neo/locked-rotor-testing) and ensure you understand how to set an appropriate [Smart Current Limit](https://docs.revrobotics.com/brushless/spark-max/gs/make-it-spin#limiting-current) before using your NEO Brushless Motor.\n{% endhint %}\n",
- "content_preview": "# NEO V1\n\n## NEO V1 Overview\n\nThe REV NEO Brushless Motor (REV-21-1650) is the first brushless motor designed to meet the unique demands of the FRC community."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/neo/v1.1/pinion-pressing",
- "title": "Pinion Pressing Guides",
- "section": "General",
- "language": "All",
- "content": "# Pinion Pressing Guides\n\n## NEO V1.1 Pinion Pressing Guide\n\n### Needed Materials\n\n* NEO V1.1 (REV-21-1650)\n* 1 - 10-32 x 3/8in long Socket Head Screw\n* Press Fit Pinion\n* Arbor Press\n\n \n\n### Steps\n\n| 1) Take a 10-32 x 3/8in long socket head screw and screw it into the back of the motor finger tight.
DO NOT USE AN ALLEN WRENCH OR POWER TOOL The screw is intended to support the end of the NEO's shaft while pressing on the pinion. Tightening the support screw with an Allen wrench or power tool may damage the motor and/or shaft.
|  |\n| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 2) Using a flat arbor press plate, balance the motor with that screw down on the arbor press |  |\n| 3) Proceed with pressing the pinion as usual. When complete, ensure that you remove the 10-32 socket head screw from the back of the NEO. |  |\n\n{% hint style=\"warning\" %}\nDo not attempt to run the NEO while a screw is still attached to the back of the motor. Not removing the screw will damage the motor and/or shaft.\n{% endhint %}\n\n## NEO V1.0 Pinion Pressing Guide\n\n### Needed Materials\n\n* NEO V1.0 (REV-21-1650)\n* A high-quality 1.5mm Allen Key (i.e. WERA Tools, Bondhus)\n* Loctite 242\n* Arbor Press\n\n \n\n### Steps\n\n| 1) Locate the first of three screws holding the back can to the front plate of the motor. |  |\n| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 2) Using a high-quality 1.5mm Allen Key, remove the bolt and set aside. Repeat this for the other two bolts around the back can. **Make sure the Allen Key is fully seated in the bolt head during removal.** |  |\n| 3) Remove the back can. Set it and the three bolts aside for reassembly after pressing on the pinion. |  |\n| 4) Place the NEO upright in the arbor press. Make sure to hold the bottom of the motor flat against the press plate, supporting the bottom of the shaft. |  |\n| 5) Press on pinion. After pinion is pressed on reattach the back can. We recommend using Loctite 242 to complete the reassembly. |  |\n",
- "content_preview": "# Pinion Pressing Guides\n\n## NEO V1.1 Pinion Pressing Guide\n\n### Needed Materials\n\n* NEO V1.1 (REV-21-1650)\n* 1 - 10-32 x 3/8in long Socket Head Screw\n* Press Fit Pinion\n* Arbor Press\n\n \n\nThe REV NEO 550 Brushless Motor runs a 0.12in output shaft which, when combined with its 550-style mounting features, allows for easy installation in many off-the-shelf gearboxes. \n\nIts small size and weight make it easy to put power where you need it, whether that is on intakes, end-effectors, or other weight-sensitive mechanisms. However, keep in mind that this motor has a lower thermal mass than a NEO, CIM, or Mini CIM, and thus it may not be ideal for some drivetrain applications.\n\n## Wiring Connections\n\nConnecting the NEO 550 Brushless motors is fairly straightforward. Follow the guide at[ Wiring the Spark Max](https://docs.revrobotics.com/brushless/spark-max/gs/wiring), and don't forget to connect your sensor wire; the motor will not spin without it!\n\n{% hint style=\"danger\" %}\nCAUTION: Improperly wiring the connectors can cause severe motor damage and is not covered by the warranty. DO NOT connect the motor directly to the battery. \n{% endhint %}\n\n## Features\n\n* Mounting features match other 550 series DC motors\n* Out-runner construction\n* Front and rear ball bearings\n* High-temperature neodymium magnets\n* High-flex silicone motor wires\n* Integrated motor sensor (3-phase hall sensors)\n* Motor temperature sensor\n\n## Motor Specifications\n\n| Parameter | Value and Units |\n| ------------------------------ | ------------------ |\n| Nominal Operating Voltage | 12 V |\n| Motor Kv | 917 Kv |\n| Free Speed\\` | 11000 RPM |\n| Free Running Current | 1.4 A |\n| Stall Current | 100 A |\n| Stall Torque | 0.97 Nm |\n| Peak Output Power | 279 W |\n| Hall-Sensor Encoder Resolution | 42 counts per rev. |\n\n### Mechanical Specifications\n\n| Parameter | Value and Units |\n| --------------------- | --------------------- |\n| Output Shaft Diameter | 0.125in (3.175mm) |\n| Output Shaft Length | 0.267in (7mm) |\n| Output Pilot | 0.512in (13mm) |\n| Body Length | 1.752in (44.5mm) |\n| Body Diameter | 1.378in (35mm) |\n| Weight | 0.142 kgs (0.313 lbs) |\n| Phase Wire Length | 5.91in (150mm) |\n| Phase Wire Gauge | 14AWG |\n| Sensor Cable Length | 11.81in (300mm) |\n| Sensor Cable Gauge | 24AWG |\n\n{% hint style=\"info\" %}\nPlease read our [Locked Rotor Testing Documentation](https://docs.revrobotics.com/brushless/neo/locked-rotor-testing) and ensure you understand how to set an appropriate [Smart Current Limit](https://docs.revrobotics.com/brushless/spark-max/gs/make-it-spin#limiting-current) before using your NEO 550 Brushless Motor.\n{% endhint %}\n",
- "content_preview": "# NEO 550\n\n## NEO 550 Overview\n\nThe REV [NEO 550 Brushless Motor (REV-21-1651)](https://www.revrobotics.com/rev-21-1651/) is the newest member of the NEO family of brushless motors."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/neo/550/pinion-pressing",
- "title": "Pinion Pressing Guide",
- "section": "General",
- "language": "All",
- "content": "# Pinion Pressing Guide\n\n## NEO 550 Pinion Pressing Guide\n\n### Needed Materials:\n\n* [NEO 550 (REV-21-1651)](https://www.revrobotics.com/rev-21-1651/)\n* Press Fit Pinion\n* Arbor Press\n\n \n\n### Steps\n\n| 1) Place the NEO 550 upright in the arbor press. Make sure to hold the bottom of the motor flat against the press plate, supporting the bottom of the shaft. |  |\n| ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 2) Place the pinion on the shaft and press. Take care to not over-press on the NEO 550 shaft! |  |\n",
- "content_preview": "# Pinion Pressing Guide\n\n## NEO 550 Pinion Pressing Guide\n\n### Needed Materials:\n\n* [NEO 550 (REV-21-1651)](https://www.revrobotics.com/rev-21-1651/)\n* Press Fit Pinion\n* Arbor Press\n\nNEO Vortex Motor Curve
Kraken X60 Motor Curve
\n\n### NEO Vortex & Kraken X60 Data\n\nParameter NEO Vortex Kraken X60 Units KV 565 523 rpm/V Free Load Speed 6784 6271 rev/min Free Load Current 3.62 2.32 A Stall Current 211 233 A Stall Torque 3.6 4.21 Nm Nominal Voltage 12 12 V Peak Efficiency 77 82 % Peak Power 640 691 W Power @ 40 Amps 361† 378 W
\n{% endtab %}\n\n{% tab title=\"NEO Vortex & Falcon 500 V2\" %}\n\n### NEO Vortex & Falcon 500 V2 Motor Curves\n\nNEO Vortex Motor Curve
Falcon 500 V2 Motor Curve
\n\n### NEO Vortex & Falcon 500 V2 Data\n\nParameter NEO Vortex Falcon 500 V2 Units KV 565 541 rpm/V Free Load Speed 6784 6489 rev/min Free Load Current 3.62 2.39 A Stall Current 211 191 A Stall Torque 3.6 3.46 Nm Nominal Voltage 12 12 V Peak Efficiency 77 83.06 % Peak Power 640 588 W Power @ 40 Amps 361† 376 W
\n{% endtab %}\n\n{% tab title=\"NEO Vortex & NEO V1.1\" %}\n\n### NEO Vortex & NEO V1.1 Motor Curves\n\nNEO Vortex Motor Curve
NEO V1.1 Motor Curve
\n\n{% hint style=\"success\" %}\nThe data shown for NEO V1.1 is also valid for the NEO V1\n{% endhint %}\n\n### NEO Vortex & NEO V1.1 Data\n\nParameter NEO Vortex NEO V 1.1 Units KV 565 485 rpm/V Free Load Speed 6784 5820 rev/min Free Load Current 3.62 2.065 A Stall Current 211 160 A Stall Torque 3.6 3 Nm Nominal Voltage 12 12 V Peak Efficiency 77 76.54 % Peak Power 640 456 W Power @ 40 Amps 361† 333 W
\n{% endtab %}\n{% endtabs %}\n\n† This value is from the graph above reflecting the one test depicted.
\n\n## NEO V1.1 - Quick Comparison\n\n{% hint style=\"success\" %}\nThe data shown for NEO V1.1 is also valid for the NEO V1\n{% endhint %}\n\n{% tabs %}\n{% tab title=\"NEO V1.1 & Kraken X60\" %}\n\n### NEO V1.1 & Kraken X60 Motor Curves\n\nNEO V1.1 Motor Curve
Kraken X60 Motor Curve
\n\n### NEO V1.1 & Kraken X60\n\nParameter NEO V1.1 Kraken X60 Units KV 485 523 rpm/V Free Load Speed 5820 6271 rev/min Free Load Current 2.065 2.32 A Stall Current 160 233 A Stall Torque 3 4.21 Nm Nominal Voltage 12 12 V Peak Efficiency 76.54 82 % Peak Power 456 691 W Power @ 40 Amps 333 387 W
\n{% endtab %}\n\n{% tab title=\"NEO V1.1 & Falcon 500 V2\" %}\n\n### NEO V1.1 & Falcon 500 V2 Motor Curves\n\nNEO V1.1 Motor Curve
Falcon 500 V2 Motor Curve
\n\n### NEO V1.1 & Falcon 500 V2 Data\n\nParameter NEO V1.1 Falcon 500 V2 Units KV 485 541 rpm/V Free Load Speed 5820 6489 rev/min Free Load Current 2.065 2.39 A Stall Current 160 191 A Stall Torque 3 3.46 Nm Nominal Voltage 12 12 V Peak Efficiency 76.54 83.06 % Peak Power 456 588 W Power @ 40 Amps 333 376 W
\n{% endtab %}\n\n{% tab title=\"NEO V1.1 & NEO Vortex\" %}\n\n### NEO Vortex & NEO V1.1 Motor Curves\n\nNEO V1.1 Motor Curve
NEO Vortex Motor Curve
\n\n### NEO Vortex & NEO V1.1 Data\n\nParameter NEO V1.1 NEO Vortex Units KV 485 565 rpm/V Free Load Speed 5820 6784 rev/min Free Load Current 2.065 3.62 A Stall Current 160 211 A Stall Torque 3 3.6 Nm Nominal Voltage 12 12 V Peak Efficiency 76.54 77 % Peak Power 456 640 W Power @ 40 Amps 333 361 W
\n{% endtab %}\n{% endtabs %}\n\n## Interpreting this Data\n\nValues of motor data may vary from manufacturer to manufacturer because of variances in the dynamometer used to test each motor and how that data is analyzed. With each motor shown on this page, REV Robotics performed the same dynamometer testing and analysis of the data collected. \n\n### Absolute vs. Relative Data\n\n* **Absolute Values** for a specification are determined independently of each similar motor made by other manufacturers. Each of the specifications are determined by a manufacturer's individual testing procedures and methods of analyzing test data. \n* **Relative Values** for a specification are found by running the same test and analysis on a motor. While the values provided may not match a motor's listed Absolute Values for a given specification, this kind of data helps us compare the motors as fairly as possible. \n\n## Motor Curves\n\n### NEO Vortex - REV Robotics \n\nNEO Vortex Motor Curve
\n\n### NEO V1.1 - REV Robotics \n\nNEO V1.1 Motor Curve
\n\n### Kraken X60 - WestCoast Products\n\nKraken X60 Motor Curve
\n\n### Falcon 500 V2 - VEX Robotics\n\nFalcon 500 V2 Motor Curve
\n",
- "content_preview": "# Motor Comparison\n\n{% hint style=\"info\" %}\nThis page is intended to highlight the ***relative*** comparison between REV ION motors and similar motors from other vendors. \n\nFor more information please see the section below - [Interpreting this Data](#interpreting-this-data)\n{% endhint %}\n\n##..."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/neo/locked-rotor-testing",
- "title": "Locked Rotor Testing",
- "section": "General",
- "language": "All",
- "content": "# Locked Rotor Testing\n\n## What is Locked Rotor Testing?\n\nWhen stalled, both brushed and brushless motors draw a lot of current and generate a large amount of heat. This heat can permanently damage a motor in a fraction of a second if not managed correctly. Locked-rotor stall data can be a useful tool when designing robot mechanisms that need to hold a mechanical load for a particular amount of time. It is useful to know the approximate time to failure depending on the applied load so that mechanisms can be successfully designed around these limitations.\n\nBrushless motors, like the NEO family of Brushless Motors, offer higher efficiency and higher power density than brushed motors. However, they require a more complex control scheme to operate due to the fundamentally different motor technology. Sensors are built into each version of NEO Brushless Motors to enable proper operation with a SPARK MAX or SPARK Flex Motor Controller, and are required for proper operation.\n\n### Constant Current vs. Constant Voltage\n\nMost Brushed DC motors used in FRC have locked-rotor stall data available showing Torque vs. Time at a particular constant applied voltage. This data shows how long a particular voltage can be applied before the motor fails. Because of differences in motor construction, especially winding resistance and torque constants, voltage data cannot be used alone to compare the survivability of different motors.\n\nWhile applied voltage is straightforward and sometimes the only variable that you can control (e.g., with the original SPARK or Victor SPX), it isn’t as useful when you need to maintain a constant torque to hold a constant load. Over time, when a constant voltage is applied to a brushless motor, the motor's current and torque output will change as the motor's windings heat up. \n\nHolding a constant torque requires the current applied to the motor windings (or motor phases) to remain constant, despite these changes due to heat. Therefore, it is useful to know the time to failure of a motor at different currents rather than different voltages.\n\n### Locked-rotor Testing with SPARK MAX Smart Current Limit\n\nThe SPARK MAX Motor Controller includes a Smart Current Limit feature that can adjust the applied output to the motor to maintain a constant phase current. Below you will find data at various current levels being maintained in the NEO 550 Brushless Motor.\n\nPlease take the following into consideration when interpreting the data below: \n\n* Average motor phase current (or winding current) is different than the average input current to the motor controller.\n* Average Input Current = Average Phase Current x Duty Cycle\n* Motor torque is proportional to phase current, not the input current. Therefore, it is important to control the phase current and not the input current.\n* The torque values in the graphs are approximate and are measured using a digital torque wrench which includes an error up to \\~5%. **The intent is to show the point of failure and not the torque/current relationship.** Please see each motor's documentation page for its torque and current specifications.\n* **At higher current limits, the time-to-failure depends on many different factors. It is best practice to design mechanisms with a considerable safety margin.**\n* Locked-rotor test setup:\n * The motor is mounted to an aluminum motor bracket at the face plate with the output shaft locked in place through a digital torque wrench.\n * SPARK MAX is controlling the motor and its Smart Current Limit is configured to the desired limit for the test. It is then commanded to go full-power while letting the Smart Current Limit adjust the applied output duty cycle as necessary to keep the phase current at the limit.\n * Temperature measurements are read from the motor's internal temperature sensor. Temperature measurements lag behind the actual motor coil temperature due to the physical location on the motor. This data can also be used to approximate where limiting can be useful in user code based on the temperature measured.\n * Power is provided by the following:\n * NEO V1 & NEO V1.1 Locked Rotor Testing - 12V nominal, 18Ah, lead-acid battery through an FRC Power Distribution Panel with a 40A breaker. The breaker did not trip during any of the tests. Bus voltage is graphed to show the drop in battery voltage throughout the tests.\n * NEO 550 Locked Rotor Testing - 200A 12V DC power supply.\n\n## Locked Rotor Testing Data\n\n{% tabs %}\n{% tab title=\"NEO V1 & NEO V1.1\" %}\n\n \n\n{% hint style=\"danger\" %}\nThe intent of this graph and the graphs below is to show an approximate time-to-failure at various current limits. Various factors can affect these times and, as always, mechanisms should be designed with a considerable margin. \n\nThese stall times are not guaranteed.\n{% endhint %}\n\n#### Download the raw data in CSV format: [NEO Locked-rotor Testing Raw Data](https://www.revrobotics.com/content/docs/NEOLocked-rotorTestingRawData.zip)\n\n \n\n \n\n \n\n \n{% endtab %}\n\n{% tab title=\"NEO 550\" %}\n\n \n\n{% hint style=\"danger\" %}\nThe intent of this graph and the graphs below is to show an approximate time-to-failure at various current limits. Various factors can affect these times and, as always, mechanisms should be designed with a considerable margin. \n\nThese stall times are not guaranteed.\n{% endhint %}\n\n#### Time to Failure Summary\n\n* 20A Limit - Motor survived full 220s test.\n* 40A Limit - Motor failure at approximately 27s.\n* 60A Limit - Motor failure at approximately 5.5s\n* 80A Limit\\* - Motor failure at approximately 2.0s\n\n\\*80A is the default Smart Current Limit in the SPARK MAX. It is highly recommended to adjust the Smart Current Limit when driving the NEO 550.\n\n \n\n \n\n \n\n \n{% endtab %}\n{% endtabs %}\n",
- "content_preview": "# Locked Rotor Testing\n\n## What is Locked Rotor Testing?\n\nWhen stalled, both brushed and brushless motors draw a lot of current and generate a large amount of heat. This heat can permanently damage a motor in a fraction of a second if not managed correctly."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-flex/overview",
- "title": "SPARK Flex Overview",
- "section": "SPARK Flex",
- "language": "All",
- "content": "# SPARK Flex Overview\n\nThe [SPARK Flex (REV-11-2159)](https://www.revrobotics.com/rev-11-2159/) is a new smart motor controller from REV Robotics. Its dockable form factor allows for direct mounting onto a [NEO Vortex (REV-21-1652)](https://www.revrobotics.com/rev-11-2828/), simplifying wiring while maintaining flexibility. Improving upon the foundation of the SPARK MAX, new features include 3-phase current sensing, reverse polarity protection, and an expanded Data Port with additional interfaces. When docked to an adapter, the SPARK Flex can control any existing NEO or compatible brushless/brushed DC motor.\n\nSPARK Flex Motor Controller
\n\n## Feature Highlights:\n\n* Docking interface for motor phases and sensors \n* USB type C configuration and control\n* PWM and CAN communication\n* Fully integrated power and control wires\n* Enhanced data port with more power, latching connector, and additional serial interfaces\n* Advanced motor control modes include:\n * Velocity\n * Position\n * Current\n * New modes with future firmware updates\n* \\#10-32 threaded holes on a 2in bolt circle\n* Motor and motor controller's silhouette fits behind a standard 2in rectangular tube\n\n## SPARK Flex Resources\n\n### General Resources\n\n* [Getting Started with the SPARK Flex](https://docs.revrobotics.com/brushless/spark-flex/gs)\n* [Troubleshooting](https://docs.revrobotics.com/brushless/spark-flex/troubleshooting)\n * [Status LED Patterns](https://docs.revrobotics.com/brushless/spark-flex/status-led)\n* [SPARK Flex Specifications](https://docs.revrobotics.com/brushless/spark-flex/specs)\n * [SPARK Flex Data Port Pinout](https://docs.revrobotics.com/brushless/spark-flex/spark-flex-feature-description/data-port)\n\n### Software Resources\n\n* [Getting Started with the REV Hardware Client](https://app.gitbook.com/s/-MGEfA6CxjaSQiH5kHxn/home/rev-hardware-client-overview)\n* [REVLib API and Installation](https://app.gitbook.com/s/0OKYENVWAIgVP2TmkWl3/install)\n* [SPARK Flex Code Examples](https://github.com/REVrobotics/REVLib-Examples)\n",
- "content_preview": "# SPARK Flex Overview\n\nThe [SPARK Flex (REV-11-2159)](https://www.revrobotics.com/rev-11-2159/) is a new smart motor controller from REV Robotics. Its dockable form factor allows for direct mounting onto a [NEO Vortex (REV-21-1652)](https://www.revrobotics.com/rev-11-2828/), simplifying wiring..."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-flex/overview/dock",
- "title": "Flex Dock",
- "section": "SPARK Flex",
- "language": "All",
- "content": "# Flex Dock\n\n## Flex Dock Overview\n\nThe Flex Dock transforms the SPARK Flex ([REV-11-2159](https://www.revrobotics.com/rev-11-2159/)) into a standalone motor controller, supporting REV brushless motors and virtually any 12V brushed DC motor by providing standard phase wire outputs. The 6-pin JST PH encoder port is compatible with NEO/NEO550 hall sensors in brushless mode and standard quadrature encoders in brushed mode.\n\nFlex Dock
\n\nThe dock securely mounts to a SPARK Flex using the same docking hardware as if you were mounting to a NEO Vortex. It features five #10-32 threaded mounting holes on a 0.5-inch grid, enabling versatile mounting on either face of the combined stack.\n\nAdditionally, the Flex Dock offers added protection against common faults caused by damaged motors or wiring. Compact and lightweight, it empowers you to adapt a SPARK Flex to meet your specific motor control requirements.\n\n## Flex Dock Features\n\n* Docks to Spark Flex with standard M3 docking hardware (not included)\n* Five #10-32 threaded holes on 0.5in grid with max depth of 0.25in for mounting\n\n \n\n## Flex Dock Specifications\n\nBody:\n\n* Material: Aluminum\n* Finish: Black Anodized\n* Length (Docked with SPARK Flex): 35mm (1.378in) \n\nMotor Phase Wires:\n\n* Length: 150mm (5.91in)\n* 12AWG\n\nEncoder Port:\n\n* 6-pin JST PH\n* Primary encoder input for: \n * When in Brushless Mode: NEO or NEO550's built in hall sensor encoder.\n * When in Brushed Mode: Standard quadrature encoder with index (ABI).\n * Note: Does not support absolute (duty cycle) encoders. Use the SPARK Flex Data Port for absolute encoders.\n* Electrical Fault Protection\n * Protects the SPARK Flex from typical faults caused by damaged motors or motor wires.\n* Weight 64g (0.141lb)\n\n \n",
- "content_preview": "# Flex Dock\n\n## Flex Dock Overview\n\nThe Flex Dock transforms the SPARK Flex ([REV-11-2159](https://www.revrobotics.com/rev-11-2159/)) into a standalone motor controller, supporting REV brushless motors and virtually any 12V brushed DC motor by providing standard phase wire outputs."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-flex/specs",
- "title": "SPARK Flex Specifications",
- "section": "SPARK Flex",
- "language": "All",
- "content": "# SPARK Flex Specifications\n\nThe following tables provide the operating and mechanical specifications for the SPARK Flex Motor Controller. \n\n{% hint style=\"danger\" %}\nDO NOT exceed the maximum electrical specifications. Doing so will cause permanent damage to the SPARK Flex and will void the warranty.\n{% endhint %}\n\n## Main Electrical Specifications\n\nParameter Min Typ Max Units Input Voltage (Nominal) - 12 - V Operating Voltage Range † 6 - 24 V Absolute Maximum Supply Voltage - - 30 V Continuous Output Current †† - - 60 A Peak Current (2 second surge) - - 100A A
\n\n† 6 V minimum for 5 V Data Port output. 4.5 V minimum before full brownout. †† Continuous current duration tested at 3 minutes.
\n\n## PWM Input Specifications\n\nParameter Min Typ Max Units Full-reverse Input Pulse - 1000 - μs Neutral Input Pulse † - 1500 - μs Full-forward Input Pulse - 2000 - μs Valid Input Pulse Range 500 - 2500 μs Input Frequency 50 - 200 Hz Input Timeout †† - 50 - ms Default Input Deadband ††† - 5 - Hz Input High Level 0.5 0.7 0.9 V
\n\n†
Neutral corresponds to zero output voltage (0 V) and is either braking or coasting depending on the current idle behavior mode.
†† If a valid pulse isn't received within the timeout period, the SPARK Flex will disable its output. ††† Input deadband is added to each side of the neutral pulse width. Within the deadband, output state is neutral. The deadband value is configurable using the REV Hardware Client or through the CAN interface.
\n\n## Data Port Specifications\n\nParameter Min Typ Max Units 5V Supply Output Voltage (Vout) V 5V Supply Output Current † - - 500 mA Digital Input Voltage Range 0 - 5 V Digital Input High Voltage 1.85 - - V Digital Input Low Voltage - - 1.36 V Analog Input Voltage Range 0 - Vout V
\n\n†
Available output current may be reduced when the SPARK Flex is powered only by USB and not main power.
\n\n## Mechanical Specifications\n\nParameter Value and Units Power Wire Gauge 12 AWG Power Wire Length 450 mm (17.72in) Control Wire Gauge 26 AWG Control Wire Length 450 mm (17.72in) Through Bore Diameter 16.5 mm (0.649 in) Mounting Footprint Narrow Side Width 2 in Mounting Footprint Rounded Side Diameter 60 mm Mounting Holes #10-32 on 2 in bolt circle Mounting Hole Maximum Depth 0.25 in Body Length (Not Docked) 28.2mm Docking Hardware M3 SHCS x 25mm Weight (with Wires & Docking Screws) 130g (0.29lb)
\n",
- "content_preview": "# SPARK Flex Specifications\n\nThe following tables provide the operating and mechanical specifications for the SPARK Flex Motor Controller. \n\n{% hint style=\"danger\" %}\nDO NOT exceed the maximum electrical specifications."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-flex/spark-flex-feature-description",
- "title": "SPARK Flex Feature Description",
- "section": "SPARK Flex",
- "language": "All",
- "content": "# SPARK Flex Feature Description\n\nThe SPARK Flex Motor Controller is a fully featured smart motor controller designed to be robust and easy to use yet fully capable of advanced motion control. The following sections describe each feature in detail.\n\n### SPARK Flex Connections\n\n#### [USB-C Port ](https://docs.revrobotics.com/brushless/spark-flex/control-connections#usb-c-port)\n\nAllows for seamless firmware updates and code uploads, facilitating quick and efficient software management through the REV Hardware Client\n\n#### [Locking Data Port ](https://docs.revrobotics.com/brushless/spark-flex/spark-flex-feature-description/data-port)\n\nAllows for integration of additional sensors such as the Through Bore Encoder, analog sensors, absolute encoders, and limit switches. \n\n#### Ultra-flexible, and Silicone-coated Wires \n\nSupply [Input Power](https://docs.revrobotics.com/brushless/spark-flex/spark-flex-feature-description/power-and-motor-connections) and [Control Signals](https://docs.revrobotics.com/brushless/spark-flex/spark-flex-feature-description/control-connections) through 45cm long high-quality wires. Twisted Control wires also feature [two standardized connectors](https://docs.revrobotics.com/brushless/spark-flex/control-connections#can-pwm-connections) to make wiring the SPARK Flex easy for PWM or CAN control. \n\nSPARK Flex Main Power and Control Connections
\n\n### Status LED Indicator\n\nDisplays the [operational status and error codes](https://docs.revrobotics.com/brushless/spark-flex/status-led), ensuring easy troubleshooting and real-time monitoring\n\n### [SPARK Flex Docking Interface](https://docs.revrobotics.com/brushless/spark-flex/power-and-motor-connections#docking-interface)\n\n* Precisely engineered clearance holes for docking screws provide stable and secure attachment of the SPARK Flex and NEO Vortex to your Mechanism\n* High-current Bullet Connectors facilitate quick and secure connections to the NEO Vortex's phases, ideal for high-performance and high-power applications\n* Robust Motor Interface Connector mates to the NEO Vortex's control systems to ensure efficient, secure, and reliable electrical connections and communication\n\nSPARK Flex Docking Interface
\n\n### [SPARK Flex Mounting Holes](https://docs.revrobotics.com/brushless/spark-flex/spark-flex-feature-description/mounting-holes)\n\n* The SPARK Flex features six #10-32 threaded mounting holes on a 2 in bolt circle\n\nSPARK Flex Mounting Hole Pattern
\n",
- "content_preview": "# SPARK Flex Feature Description\n\nThe SPARK Flex Motor Controller is a fully featured smart motor controller designed to be robust and easy to use yet fully capable of advanced motion control."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-flex/spark-flex-feature-description/power-and-motor-connections",
- "title": "Power and Motor Connections",
- "section": "SPARK Flex",
- "language": "All",
- "content": "# Power and Motor Connections\n\nSPARK Flex is designed to drive 12V brushed and brushless DC motors at current up to 60A continuously. It features a unique Docking Interface that ensures secure and reliable motor phase and sensor connections, reducing the chance of intermittent or poor connections affecting the commutation of the attached motor. \n\n## Power Input\n\nPower input wires are labeled as + and - with red and black wires, respectively, and consist of two 12 AWG ultra-flexible silicone-coated wires extending 45 cm from the motor controller case.\n\nSPARK Flex Power Wires
\n\nSPARK Flex is intended to operate in a 12 V DC robot system, however it is compatible with any DC power source between 4.5 V and 24 V. \n\n{% hint style=\"info\" %}\nPlease note that the 5 V power output of the Data Port requires a 6 V minimum input voltage.\n{% endhint %}\n\n{% hint style=\"danger\" %} DO NOT exceed the maximum supply voltage of 30 V. Doing so will cause permanent damage to the SPARK Flex and will void the warranty.\n{% endhint %}\n\nWhen used in high power applications, it is recommended to use a power source that is capable of handling large surge currents, e.g. a 12V lead-acid battery. If the supply voltage drops below 4.5 V the SPARK Flex will brown out, which can result in unexpected behavior. It is also highly recommended to add a fuse or circuit breaker in between your SPARK Flex and its power source to prevent exceeding the maximum current rating.\n\n{% hint style=\"danger\" %}\nDO NOT exceed the maximum current ratings:\n\n* 60A for 3 minutes\n* 100A for 2 seconds\n\nDoing so will cause permanent damage to the SPARK Flex and will void the warranty.\n{% endhint %}\n\n## Docking Interface\n\nSPARK Flex is specifically designed to dock with the NEO Vortex Brushless Motor. Docking eliminates the extra connections between the motor and motor controller that are prone to fail due to assembly issues and rough environments.\n\nWhen docked into a SPARK Flex Dock (coming soon), the motor controller can also drive virtually any 12 V brushed DC motor and the existing NEO & NEO 550 brushless motors.\n\nSPARK Flex Docking Interface
\n\nInstruction on how to dock a SPARK Flex can be found within the documentation of the applicable device:\n\n* [NEO Vortex Docking Instructions](https://docs.revrobotics.com/brushless/neo/vortex/docking-flex)\n* SPARK Flex Dock Instructions - Coming Soon\n\n{% hint style=\"warning\" %}\nBe sure to fully install the Docking Screws when docking the SPARK Flex. These screws ensure a robust and secure electrical connection. Operating the SPARK Flex and its attached motor or dock without these screws can cause unintended behavior and damage to the system.\n{% endhint %}\n\n{% hint style=\"danger\" %}\nAlways dock and undock the SPARK Flex with both main power and USB power disconnected.\n{% endhint %}\n",
- "content_preview": "# Power and Motor Connections\n\nSPARK Flex is designed to drive 12V brushed and brushless DC motors at current up to 60A continuously. It features a unique Docking Interface that ensures secure and reliable motor phase and sensor connections, reducing the chance of intermittent or poor connections..."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-flex/spark-flex-feature-description/control-connections",
- "title": "Control Connections",
- "section": "SPARK Flex",
- "language": "All",
- "content": "# Control Connections\n\nThe SPARK Flex can be controlled by three different interfaces: servo-style PWM, Controller Area Network (CAN), and USB. The following sections describe the physical connections to these interfaces. For details on the operation and protocols of the PWM, CAN, or USB interfaces, please see [Control Interfaces](https://docs.revrobotics.com/brushless/spark-flex/spark-flex-feature-description/control-interfaces).\n\nSPARK Flex Control Connections
\n\n## CAN/PWM Connections\n\nCAN and PWM control connections share a set of four integrated 26 AWG twisted wires extending 45 cm from the case of the motor controller. Each wire is color coded according to its function:\n\n| Wire Color | CAN Function | PWM Function |\n| ---------- | --------------- | ------------ |\n| Yellow | CAN High (CANH) | Signal |\n| Green | CAN Low (CANL) | Ground |\n\nThe wires are terminated with two 1 x 3, 0.1 in pitch, rectangular connectors, both excluding the center pin. One connector is pinned and the other socketed to facilitate daisy-chaining between multiple CAN devices on the bus when using the CAN interface.\n\n{% hint style=\"info\" %}\nEach matching wire pair is physically connected to its functional counterpart within the device. Even if the SPARK Flex loses power, the CAN bus remains unbroken, leaving downstream devices unaffected.\n{% endhint %}\n\n{% hint style=\"warning\" %}\nPay close attention when daisy-chaining devices, and make sure that the colors match from connector-to-connecter along the entire CAN bus. Mismatched connections can cause difficult-to-diagnose communications issues along the entire bus.\n{% endhint %}\n\nWhen using the PWM interface, only one of the two connectors should be used. In most systems this will be the socketed connector. Therefore, it is best practice to secure the unused wires and protect the exposed pins by covering them with electrical tape.\n\nWhen daisy-chaining or extending the connections, use the included [PWM Cable Clips (REV-11-1229)](https://www.revrobotics.com/rev-11-1229/) to secure the two mating connectors together to prevent unintended disconnections.\n\n## USB-C Port\n\nThe USB-C Port is located above the CAN/PWM wires of the SPARK Flex. It supports USB 2.0 and can provide 5 V power for the SPARK Flex's internal microcontroller. \n\n{% hint style=\"info\" %}\nWhile you can configure the SPARK Flex under USB-only power, you will not be able to spin a motor unless main power is also connected.\n{% endhint %}\n\nMore information about what can be configured and operated through the USB port can be found in the USB Interface section.\n",
- "content_preview": "# Control Connections\n\nThe SPARK Flex can be controlled by three different interfaces: servo-style PWM, Controller Area Network (CAN), and USB. The following sections describe the physical connections to these interfaces."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-flex/spark-flex-feature-description/data-port",
- "title": "Data Port",
- "section": "SPARK Flex",
- "language": "All",
- "content": "# Data Port\n\nLocated next to the SPARK Flex's power and control input wires, the Data Port allows for extra sensor input and future feature expansion. Connector details can be found below. \n\n## Data Port Pinout\n\nSPARK Flex Data Port Pinout Diagram
\n\nConnector Pin Pin Type Pin Function 1 Digital Reserved 2 Power +5V 3 Analog Analog Input 4 Digital Forward Limit Switch Input 5 Digital External Encoder - B Input 6 Digital Absolute Encoder - Duty Cycle Input 7 Digital External Encoder - A Input 8 Digital Reverse Limit Switch Input 9 Digital External Encoder - Index Input 10 Power Ground
\n\n### SPARK Flex Data Port Break Out Cable\n\nThe [SPARK Flex Data Port Breakout Cable (REV-11-2853)](https://www.revrobotics.com/rev-11-2853/) breaks out the default pin functions of the SPARK Flex Data Port into commonly used connectors for external sensors.\n\nThe keyed and locking Data Port connector ensures a secure and aligned fit when plugged into the SPARK Flex Motor Controller. Its JST-PH 6-pin connector is designed to plug directly into the REV Through Bore Encoder, connecting both the quadrature and absolute encoder outputs to the appropriate SPARK Flex Data Port pins. Additional inputs, like Limit Switch and Analog inputs, are broken out to shrouded, 1 x 3 pinned, 0.1in pitch, PWM-style connectors that provide both power and ground in addition to the input signal.\n\n \n\n### SPARK Flex Data Port Pigtail Cable\n\nThe [SPARK Flex Data Port Pigtail Cable (REV-11-2852)](https://www.revrobotics.com/rev-11-2852-pk4/) breaks out the SPARK Flex Data Port to individual unterminated wires and is useful for connecting custom circuits or sensors without a standard connector. It features the keyed and locking Data Port connector to ensure a secure and aligned fit when plugged into the SPARK Flex Motor Controller. Each wire is uniquely color-coded for easy identification.\n\n \n\n### Data Port Connector Information\n\nThe SPARK Flex Data Port is a 0.05 in pitch, 2 x 5 pin, keyed and locking connector. Custom cables can be made with the following parts:\n\n| Connector Part | Manufacturer | Part Number |\n| --------------------- | ------------ | ------------------------------------------ |\n| Latching Housing | Samtec | ISDF-05-D-M |\n| Contact (28 - 30 AWG) | Samtec | CC03R-2830-01-GF CC03R-2830-01-G
|\n\n### Data Port Pigtail - Electrical Specifications\n\nParameter Value and Units Length 30cm Wire Gauge 28AWG
\n\n \n\n### Data Port Breakout Cable - Electrical Specifications\n\nParameter Value and Units Total Length 30cm Wire Gauge 28AWG
\n\n* 1 x JST PH, 6-pin connector\n* 4 x Servo Connector 0.1\" Pitch, 3-pin Male Shrouded\n\n \n",
- "content_preview": "# Data Port\n\nLocated next to the SPARK Flex's power and control input wires, the Data Port allows for extra sensor input and future feature expansion. Connector details can be found below. \n\n## Data Port Pinout\n\nSPARK Flex Mounting Hole Pattern
\n\n{% hint style=\"danger\" %} DO NOT exceed the maximum mounting screw depth of 0.25 in when mounting the SPARK Flex. Doing so will result in permanent damage to the SPARK Flex and will void the warranty.\n{% endhint %}\n\nDepth gauges are laser-etched into each side of the SPARK Flex body to make it easy to check that the chosen screw length will not violate the maximum depth.\n\nSPARK Flex Mounting Screw Depth Gauge
\n\nSimply check the screw length against the intended stack-up of structure and motor controller, and verify that it does not violate the maximum depth.\n\nChecking Screw Length with SPARK Flex Mounting Screw Depth Gauge
\n",
- "content_preview": "# Mounting Holes\n\nThe mounting face of the SPARK Flex features six #10-32 threaded mounting holes on a 2 in bolt circle. Each hole has an absolute **maximum** depth of 0.25 in."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-flex/spark-flex-feature-description/control-interfaces",
- "title": "Control Interfaces",
- "section": "SPARK Flex",
- "language": "All",
- "content": "# Control Interfaces\n\nThe SPARK Flex can be controlled by three different interfaces: servo-style PWM, Controller Area Network (CAN), and USB. The following sections describe the operation and protocols of these interfaces. For more details on the physical connections, see [Control Connections](https://docs.revrobotics.com/brushless/spark-flex/spark-flex-feature-description/control-connections).\n\n## PWM Interface\n\nThe SPARK Flex can accept a standard servo-style PWM signal as a control for the output duty cycle. Even though the PWM port is shared with the CAN port, SPARK Flex will automatically detect the incoming signal type and respond accordingly. For details on how to connect a PWM cable to the SPARK Flex, see [CAN/PWM](https://docs.revrobotics.com/brushless/spark-flex/control-connections#can-pwm-connections)[ Connections](https://docs.revrobotics.com/brushless/spark-flex/spark-flex-feature-description/control-connections).\n\nThe SPARK Flex responds to a factory default pulse range of 1000 µs to 2000 µs. These pulses correspond to full-reverse and full-forward rotation, respectively, with 1500 µs (±5% default input deadband) as the neutral position, i.e. no rotation. The input deadband is configurable with the REV Hardware Client or the CAN interface. The table below describes how the default pulse range maps to the output behavior.\n\n.svg)\n\n{% hint style=\"info\" %}\nIf a valid signal isn't received within a 60 ms window, the SPARK Flex will disable the motor output and either brake or coast the motor depending on the configured Idle Mode. For details on the Idle Mode, see Idle Mode - Brake/Coast Mode.\n{% endhint %}\n\n## CAN Interface\n\nThe SPARK Flex can be connected to a robot CAN network. CAN is a bi-directional communications bus that enables advanced features within the SPARK Flex.\n\n{% hint style=\"warning\" %}\nSPARK Flex must be connected to a CAN network that has the appropriate termination resistors at both endpoints. Please see the FIRST Robotics Competition Robot Rules for the CAN bus wiring requirements. \n{% endhint %}\n\nEven though the CAN port is shared with the PWM port, SPARK Flex will automatically detect the incoming signal type and respond accordingly.\n\nEach device on the CAN bus must be assigned a unique CAN ID number. Out of the box, SPARK Flex is assigned a device ID of 0. This ID is considered \"unconfigured\" and must be assigned to a unique number from 1 to 62. CAN IDs can be changed by connecting the SPARK Flex to a Windows computer and using the REV Hardware Client.\n\nAdditional information about the CAN accessible features and how to access them can be found in the SPARK Flex API Information section.\n\n## USB Interface\n\nThe SPARK Flex can be configured and controlled through a USB connection to a computer running the REV Hardware Client. \n\n{% hint style=\"info\" %}\nMore information coming soon!\n{% endhint %}\n",
- "content_preview": "# Control Interfaces\n\nThe SPARK Flex can be controlled by three different interfaces: servo-style PWM, Controller Area Network (CAN), and USB. The following sections describe the operation and protocols of these interfaces."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-flex/spark-flex-feature-description/mode-button",
- "title": "Mode Button",
- "section": "SPARK Flex",
- "language": "All",
- "content": "# Mode Button\n\nThe mode button can be used to activate basic operating modes within the SPARK Flex. For information on those modes, please see the [Operating Modes](https://docs.revrobotics.com/brushless/spark-flex/spark-flex-feature-description/operating-modes) section.\n\n## Pressing the SPARK Flex Mode Button\n\nThe Mode Button is specifically designed to be difficult to press inadvertently. Therefore, please follow the steps below to press the mode button successfully.\n\nUsing a **small and blunt tool**, like a straightened paper clip, gently press the Mode Button. You should feel and hear a soft click. If you are in a noisy environment, you may only be able to feel the click through the tool.\n\n{% hint style=\"danger\" %} **DO NOT** use a sharp tool to press the Mode Button. \n\nSafety pins, thumbtacks, pinbacks buttons, and other sharp tools will cause damage to the Mode Button's material.\n{% endhint %}\n\nIf you do not feel the click, ensure the tool is aligned with the button.\n\n{% hint style=\"danger\" %} **DO NOT** press with excessive force. \n\nYou should feel the click of the button with relatively gentle pressure. Pressing with excessive force can permanently damage the button.\n{% endhint %}\n\nSome early batches of SPARK Flex Motor Controllers have variances in the alignment of the Mode Button and the case hole. Misaligned buttons can still be pressed and the alignment does not affect the functionality of the SPARK Flex Motor Controller as a whole. \n\nIf your button is misaligned, please pay close attention and avoid the gap between the button and the printed circuit board (PCB):\n\nTips for Pressing a Misaligned Mode Button
\n\n{% hint style=\"danger\" %}\nAgain, DO NOT use a sharp tool to press a misaligned Mode Button. It can easily be inserted into the indicated gap above, and permanently damage the button.\\\n\\\nGenerally, a sharp tool should never be used to press the Mode Button.\n{% endhint %}\n\n{% hint style=\"info\" %}\nPlease reach out to us at if you are still having difficulty pressing your Mode Button after following this guide.\n{% endhint %}\n",
- "content_preview": "# Mode Button\n\nThe mode button can be used to activate basic operating modes within the SPARK Flex. For information on those modes, please see the [Operating Modes](https://docs.revrobotics.com/brushless/spark-flex/spark-flex-feature-description/operating-modes) section.\n\n## Pressing the SPARK Flex..."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-flex/spark-flex-feature-description/operating-modes",
- "title": "Operating Modes",
- "section": "SPARK Flex",
- "language": "All",
- "content": "# Operating Modes\n\n## Brushed/Brushless Mode - Motor Type\n\nThis mode is only compatible with the SPARK Flex Dock (coming soon). More information about this mode will be available once the dock is available.\n\n## Brake/Coast Mode - Idle Behavior\n\nWhen the SPARK Flex is receiving a neutral command the idle behavior of the motor can be handled in two different ways: **Braking** or **Coasting**. \n\nWhen in **Brake Mode**, the SPARK Flex will effectively short all motor wires together. This quickly dissipates any electrical energy within the motor and brings it to a quick stop.\n\nWhen in **Coast Mode**, the SPARK Flex will effectively disconnect all motor wires. This allows the motor to spin down at its own rate.\n\nThe Idle Mode can be configured using the Mode Button, CAN, and USB interfaces.\n\n### Mode Button Configuration \n\nFollow the steps below to switch the Idle Mode between Brake and Coast with the Mode Button.\n\n{% hint style=\"info\" %}\nPlease follow the [Pressing the SPARK Flex Mode Button guide](https://docs.revrobotics.com/brushless/spark-flex/mode-button#pressing-the-spark-flex-mode-button) before continuing. \n\nUse a small **straightened paper clip** or other **small blunt tool** to press the button. Never use a sharp tool or any type of pencil, as the pencil lead can break off inside the SPARK Flex.\n{% endhint %}\n\n1. Connect the SPARK Flex to main power, not just USB Power.\n2. The Status LED will indicate which Idle Mode is currently configured by blinking blue or cyan for Brake and yellow or magenta for Coast depending on the motor type.\n3. Press and release the Mode Button\n4. You should see the Status LED change to indicate the selected Idle Mode.\n\n{% hint style=\"info\" %}\nPlease see the [Status LED Patterns guide](https://docs.revrobotics.com/brushless/spark-flex/status-led) for information on how to identify the Idle Behavior configuration by the color of the Status LED!\n{% endhint %}\n\n### USB Configuration\n\nFollow the steps below to switch the Idle Mode between Brake and Coast with the USB and the REV Hardware Client application. Be sure to download and install the REV Hardware Client application before continuing.\n\n1. Connect the SPARK Flex to your computer using a USB-C cable.\n2. Open the REV Hardware Client application and verify that the application is connected to your SPARK Flex.\n3. On the **Basic** tab, select the desired mode with the **Idle Mode** switch.\n4. Click **Update Configuration** and confirm the change.\n\n### CAN Configuration\n\nPlease see the API Information for information on how to configure the SPARK Flex using the CAN interface.\n\n## Recovery Mode\n\nIn the rare case where a firmware update has been interrupted or corrupted, it may be necessary to boot the SPARK Flex into Recovery Mode. This can be done by following the steps below. Have a computer running the REV Hardware Client and a USB cable ready in order to reload the device firmware once the device is in Recovery Mode.\n\n{% hint style=\"info\" %}\nPlease follow the [Pressing the SPARK Flex Mode Button guide](https://docs.revrobotics.com/brushless/spark-flex/mode-button#pressing-the-spark-flex-mode-button) before continuing. \n\nUse a small **straightened paper clip** or other **small blunt tool** to press the button. Never use a sharp tool or any type of pencil, as the pencil lead can break off inside the SPARK Flex.\n{% endhint %}\n\n1. Start with all power disconnected from the SPARK Flex.\n2. Press and hold the Mode Button.\n3. While still holding the Mode Button, connect power by either turning on main power or connecting the USB cable between the SPARK Flex and the computer.\n4. Once powered, you may release the Mode Button. The LED should stay dark. The SPARK Flex is now in Recovery Mode.\n\nIf the firmware is not booting properly, it may not be easy to know if the device has entered Recovery Mode since the LED will be dark in both cases. To confirm that the SPARK Flex is in Recovery Mode:\n\n1. Connect the USB cable between the SPARK Flex and computer if not already connected.\n2. In the REV Hardware Client, the SPARK Flex should show up as a Recovery Mode Device when scanning for devices.\n\nIf the device doesn't show up, please repeat the process, ensuring that the power and button holding sequence is followed exactly. If the device still doesn't show up, please contact REV Support.\n",
- "content_preview": "# Operating Modes\n\n## Brushed/Brushless Mode - Motor Type\n\nThis mode is only compatible with the SPARK Flex Dock (coming soon). More information about this mode will be available once the dock is available.\n\n## Brake/Coast Mode - Idle Behavior\n\nWhen the SPARK Flex is receiving a neutral command the..."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-flex/gs",
- "title": "SPARK Flex Getting Started ",
- "section": "SPARK Flex",
- "language": "All",
- "content": "# SPARK Flex Getting Started \n\n## SPARK Flex Anatomy\n\n### Side View\n\n \n\n### Outside \n\n \n\n### Inside\n\n \n\nThe SPARK Flex is a motor controller with many features allowing it to control a host of brushless and brushed motor controllers. Out of the box, the Flex can be docked directly with a NEO Vortex Brushless Motor and drive it with the Flex's PWM interface. This Getting Started guide will assume that you are driving NEO Vortex and includes steps to get the motor spinning using the REV Hardware Client as well as information on how to configure your SPARK Flex.\\\n\\\nDriving other motors requires the SPARK Flex Dock (coming soon) and will be covered in its own separate guides.\n\n## Before You Start\n\nBefore following this guide, the REV Hardware Client should be installed before continuing. It is the best way to verify that the device up to date and configured correctly. Configuration through the Hardware Client is ***required*** before using the CAN interface.\n",
- "content_preview": "# SPARK Flex Getting Started \n\n## SPARK Flex Anatomy\n\n### Side View\n\n DO NOT exceed the 0.25 in mounting screw depth of the SPARK Flex. Doing so can permanently damage the SPARK Flex and will void the warranty.\\\n\\\nSee the [Mounting Holes](https://docs.revrobotics.com/brushless/spark-flex/spark-flex-feature-description/mounting-holes) section for more details **before** mounting your SPARK Flex for the first time.\n{% endhint %}\n\n### Power Connections\n\nThe power wires are permanently connected to the SPARK Flex and are not replaceable. Take care not to cut these wires too short. It is highly recommended to install connectors on these wires to allow for reconfiguration as you experiment and design your robot. WAGO 221 Inline Splicing Connectors (REV-19-2491-PK50) and Anderson Power Pole connectors are commonly used for this purpose.\n\n{% hint style=\"warning\" %}\nMake sure the power is disconnected or turned off before making any electrical connections on your test bed or robot.\n{% endhint %}\n\nConnect the integrated SPARK Flex power leads labeled + (red) and - (black) to an available channel on the Power Distribution Hub. If you need to extend the length of the integrated wires, it is recommended to use 12 AWG wire or larger (a lower gauge number).\n",
- "content_preview": "# Wiring the SPARK Flex\n\n## Required Materials\n\n* 12 V battery\n* 120 A circuit breaker\n* Power Distribution Hub\n* SPARK Flex\n* NEO Vortex\n* Associated wiring and a \"test bed\" described below\n* USB type-C cable\n* A Computer Running the REV Hardware Client\n\n## Prepare the Components\n\n### Test..."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-flex/gs/make-it-spin",
- "title": "Make it Spin!",
- "section": "SPARK Flex",
- "language": "All",
- "content": "# Make it Spin!\n\n{% hint style=\"success\" %}\n[Make it Spin!](https://docs.revrobotics.com/rev-hardware-client-2/home/run-motor) For REV Hardware Client 2 is available.\n{% endhint %}\n\n## Power On\n\nNow that the device is wired, and the connections carefully checked, power on the robot. You should see the SPARK Flex slowly blinking its for a new device the color will be Magenta. If the LED is dark, or you see a different blink pattern, refer to the [Status LED ](https://docs.revrobotics.com/brushless/spark-flex/status-led)guide for troubleshooting. \n\n{% hint style=\"info\" %}\nIf you are using a brushed motor, you may see a sensor error. This is expected until you configure the device to accept a brushed motor in the following steps.\n{% endhint %}\n\n## Connect to the SPARK Flex\n\nPlug in the USB cable and start the REV Hardware Client. Select the SPARK Flex from the Connected Hardware\n\n \n\n{% hint style=\"info\" %}\nIf you cannot see the SPARK Flex, make sure that the SPARK Flex is not being used by another application\r. Then unplug the SPARK Flex from the computer and plug it back in.\n{% endhint %}\n\n## Basic Setup and Configuration\n\nBefore any parameters can be changed, you **must** first assign a unique CAN ID to the device. This can be any number between 1 and 63. After setting a unique CAN ID, the user interface will refresh and allow you to change other parameters.\n\n \n\n{% hint style=\"info\" %}\nEventually you may set up a CAN network on your test bench or robot. Be sure each device on the network has a unique CAN ID. It is helpful to label each device with its ID number to aid in troubleshooting.\n{% endhint %}\n\n### Set the Motor Type\n\nIf you are using a NEO Vortex, NEO, or NEO 550, verify that the motor type is set to **REV NEO Brushless**, Sensor Type is **Hall Effect**, and the LED is blinking Magenta or Cyan.\n\n \n\n{% hint style=\"info\" %}\nIf you see a *Sensor Fault* blink code, make sure the Motor Interface Connector is properly seated.\n{% endhint %}\n\n{% hint style=\"warning\" %}\nThe ability to run a Brushed motor using a SPARK Flex will not be available until the release of the SPARK Flex Dock.\n{% endhint %}\n\n### Suggested Current Limits\n\nYour ideal current limit may vary based on your specific application, but these values can be used as a starting point to reduce the chance of an overload on your motor as you begin tuning your specific mechanism's Smart Current Limit.\n\n| Motor Type | Current Limit Range |\n| ----------------------------------------------------------------- | ------------------- |\n| NEO Vortex | 80A |\n| NEO ([REV-21-1650](https://www.revrobotics.com/rev-21-1650/)) | 40A - 60A |\n| NEO 550 ([REV-21-1651](https://www.revrobotics.com/rev-21-1651/)) | 20A - 40A |\n\n{% hint style=\"warning\" %}\nWarning: Setting current limits outside of the suggested ranges listed above may cause unintended overload and severe damage to components that are not covered by warranty.\n{% endhint %}\n\n \n\n## Save the Settings\n\nThe settings must be saved for the SPARK Flex to remember its new configuration through a power cycle. To do this, press the *Burn Flash* button at the bottom of the page. It will take a few seconds to save, indicated by the loading symbol on the button.\n\n{% hint style=\"warning\" %}\nAs of REV Hardware Client version 1.7.0, \"Burn Flash\" has been renamed to \"Persist Perimeters\"!\n{% endhint %}\n\n \n\nAny settings saved this way will be remembered when the device is powered back on. You can always restore the factory defaults if you need to reset the device.\n\n## Spin the Motor\n\n{% hint style=\"danger\" %}\nBefore running any motor, make sure all components are in a safe state, that the motor is secured, and that anyone nearby is aware. FRC motors are very powerful and can quickly cause damage to people and property. \n{% endhint %}\n\n{% hint style=\"info\" %}\nKeep the CAN cable disconnected throughout the test. For safety reasons, the REV Hardware Client will not run the motor if the roboRIO is connected. If the roboRIO was connected, power cycle the SPARK Flex.\n{% endhint %}\n\nTo spin the motor, go to the Run tab, keep all of the default settings and press *Run* *Motor.* The *setpoint* is 0 by default, meaning that the motor is being commanded to **idle** (0% power). When you press *Run* you should see the LED go from slow blinking to solid, indicating that the motor is idling.\n\n \n\n**Slowly** ramp the setpoint slider up. The motor should start to spin and you should see a green blink pattern proportional to the speed you have set to the motor. Slowly ramp the slider down. The motor should spin in reverse, and you should see a red blink pattern proportional to the speed you have set to the motor.\n\nIf you are unable to spin the motor, visit our[ troubleshooting guide](https://docs.revrobotics.com/brushless/spark-flex/troubleshooting).\n",
- "content_preview": "# Make it Spin!\n\n{% hint style=\"success\" %}\n[Make it Spin!](https://docs.revrobotics.com/rev-hardware-client-2/home/run-motor) For REV Hardware Client 2 is available.\n{% endhint %}\n\n## Power On\n\nNow that the device is wired, and the connections carefully checked, power on the robot."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-flex/gs/basic-config",
- "title": "Basic Configurations",
- "section": "SPARK Flex",
- "language": "All",
- "content": "# Basic Configurations\n\nSPARK Flex has many operating modes that can be configured through its CAN and USB interfaces.\n\n{% hint style=\"success\" %}\nComing soon!\n{% endhint %}\n",
- "content_preview": "# Basic Configurations\n\nSPARK Flex has many operating modes that can be configured through its CAN and USB interfaces.\n\n{% hint style=\"success\" %}\nComing soon!\n{% endhint %}\n"
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-flex/status-led",
- "title": "SPARK Flex Status LED Patterns",
- "section": "SPARK Flex",
- "language": "All",
- "content": "# SPARK Flex Status LED Patterns\n\nSPARK Flex will indicate important status information on its multi-colored Status LED visible through the frosted plastic near the USB port. The following tables shows each state and the corresponding LED color pattern. \n\n### Standard Operation\n\nOperating Mode Idle Mode State Color/Pattern Graphic Brushed Brake No Signal Blue Blink Valid Signal Blue Solid Coast No Signal Yellow Blink Valid Signal Yellow Solid Brushless Brake No Signal Cyan Blink Valid Signal Cyan Solid Coast No Signal Magenta Blink Valid Signal Magenta Solid Partial Forward Green Blink Full Forward Green Solid Partial Reverse Red Blink Full Reverse Red Solid Forward Limit Green/White Blink Reverse Limit Red/White Blink
\n\n### **Identification, Updating, and Recovery**\n\n| Mode | Color/Pattern | Graphic |\n| ------------------------------------------ | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |\n| Device Identify | White/Magenta Fast Blink |  |\n| CAN Bootloader Firmware Updating
| White/Yellow Blink |  |\n| CAN Bootloader Firmware Retry
| White/Blue Blink |  |\n| USB DFU (Device Firmware Update)
| Dark (LED off) |  |\n| Recovery Mode | Dark (LED off) |  |\n\n### Fault Conditions\n\nFault Condition Color/Pattern Graphic 12V Missing The motor will not drive if powered only by USB. This blink code warns the user of this condition. Orange/Blue Slow Blink Sensor Fault This can occur if the sensor type is misconfigured, the sensor cable is not plugged in or damaged, or if a sensor other than the motor sensor is plugged in. Orange/Magenta Slow Blink Gate Driver Fault A fault reported by the core internal electronic circuitry. If this code persists after power cycling the controller, contact REV. Orange/Cyan Slow Blink CAN Fault The CAN fault will be shown after the first time the device is plugged into the CAN port and a fault later occurs. Check your CAN wiring if you see this fault. Orange/Yellow Slow Blink Temperature Cutoff Fault The motor or motor controller has gotten too hot to continue running. This fault will clear automatically after the system cools down enough to run again. Orange/Green Slow Blink Corrupt Firmware (recover using Recovery Mode) Firmware failed to load. Dark (LED off)
\n",
- "content_preview": "# SPARK Flex Status LED Patterns\n\nSPARK Flex will indicate important status information on its multi-colored Status LED visible through the frosted plastic near the USB port. The following tables shows each state and the corresponding LED color pattern."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-flex/troubleshooting",
- "title": "SPARK Flex Troubleshooting",
- "section": "SPARK Flex",
- "language": "All",
- "content": "# SPARK Flex Troubleshooting\n\nMany issues can be solved by systematic troubleshooting without needing to contact REV Support. Take a look at the troubleshooting tips below for help in determining the cause of the issue you are seeing. Should you need to contact us, describing the steps you've taken in detail will help us get you up and running quickly.\n\n## General Troubleshooting Tips\n\nThe key to effective troubleshooting is isolating the issue. Many issues can show the same symptom, so eliminating failure points one at a time is critical to finding the root cause.\n\n### Rule Out Issues By Isolation\n\nIf possible, try to eliminate a section of the system when troubleshooting. For example:\n\n* Rule out a code or control wiring issue:\n * Use the REV Hardware Client to run the SPARK Flex over USB.\n * **Please be aware of the CAN lockout feature of the SPARK Flex**. If it has been connected to the roboRIO's CAN bus, a safety feature within the SPARK Flex will lock out USB communication. Disconnecting from the CAN bus and power cycling the MAX will release the lock.\n * If this is your first time running the REV Hardware client, see the [Getting Started with the REV Hardware Client](https://app.gitbook.com/s/-MGEfA6CxjaSQiH5kHxn/ion/spark-flex) for a tour of the software and its features.\n* Rule out a code issue:\n * Create a simple test program using our SPARK Flex Example Code.\n* Rule out a mechanical issue:\n * Remove the motor from the mechanism or use a different, free-spinning motor.\n\n### Use the Driver Station\n\nAn extremely useful set of tools can be found on the Driver Station:\n\n* Use the [Driver Station Log File Viewer](https://docs.wpilib.org/en/latest/docs/software/driverstation/driver-station-log-viewer.html)\n * Look at the PDP channel current draw:\n * Higher than expected current on a channel can indicate both mechanical and electrical issues.\n * Look at the battery voltage:\n * Large dips in the battery voltage around the time of an issue can indicate battery health issues that cause brownouts.\n* Use the [CAN/Power Tab](https://docs.wpilib.org/en/latest/docs/software/driverstation/driver-station.html#can-power-tab)\n * Look at the CAN Bus Utilization.\n * Look at CAN Faults.\n * Look at Comms Faults:\n * Comms faults can affect the SPARK Flex. If it loses communication with the roboRIO, it will go to its safe disabled state. This can look like a momentary glitch in a motor spinning if the comms faults are infrequent and irregular.\n\n### Use the APIs\n\nIt is also very useful to log or plot operating values internal to the SPARK Flex. These values can be accessed using the SPARK Flex APIs. Useful values to log:\n\n* *getAppliedOutput()*\n * This value will show what the SPARK Flex is actually applying to the motor output. This can illuminate issues with closed-loop control tuning.\n* *getOutputCurrent()*\n * This value will show the output current going to the phases of the motor. Output current won't always be the same as the Input current measured by the PDP. Knowing the output current is useful to diagnose current-limit issues if motors are overheating.\n* *getBusVoltage()*\n * A way to measure the input voltage right at the controller.\n* *getStickyFaults()*\n * A sticky fault indicates if a fault has occurred since the last time the faults were reset. Checking these can provide a lot of insight into what the controller is experiencing.\n\n## CAN Troubleshooting\n\n \n\n## Recovery Mode\n\nSometimes, when updating the firmware on a SPARK Flex, it is possible for the process to be interrupted or for the firmware to be corrupted by a bad download or other type of interruption in data transfer. In this state, the Status LED will be dark or dim and the device will fail to operate. There is a built-in recovery mode that can force your device to accept new firmware even if the controller seems to be bricked and the procedure is outlined below:\n\nPlease note, that performing this procedure will erase all data and settings on the device. To perform the procedure a small tool, like a straightened paper clip, is necessary to press the Mode Button (located to the right of the Status LED), the orange USB-C cable that came with the unit (or a DATA capable USB-C cable), and a native Windows based computer with the REV Hardware Client installed:\n\n1. With the SPARK Flex disconnected from power, press and hold the Mode Button.\n2. While still holding the Mode Button, connect the Device to the computer using the USB-C cable - the Status LED will not illuminate - this is expected.\n3. With the REV Hardware Client running on the computer, wait a few seconds for the audible tone or icon for the device to be recognized in recovery mode then release the Mode Button - no lights will be present on the SPARK Flex during this stage of the process, this is expected.\n4. Select the SPARK Flex in Recovery Mode from the REV Hardware Client window.\n5. From the \"Choose a Device\" type dropdown, choose - SPARK Flex.\n6. Choose the latest version of the firmware from the dropdown and then click update.\n7. Wait for the software update to complete.\n8. Power cycle unit (unplug and plug in USB-C) click on the SPARK Flex icon, and clear any sticky faults - the recovery should be complete!\n",
- "content_preview": "# SPARK Flex Troubleshooting\n\nMany issues can be solved by systematic troubleshooting without needing to contact REV Support. Take a look at the troubleshooting tips below for help in determining the cause of the issue you are seeing."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-flex/spark-flex-operating-modes",
- "title": "SPARK Flex Operating Modes",
- "section": "SPARK Flex",
- "language": "All",
- "content": "# SPARK Flex Operating Modes\n\n## Brake/Coast Mode - Idle Behavior\n\nWhen the SPARK Flex is receiving a neutral command the idle behavior of the motor can be handled in two different ways: **Braking** or **Coasting**.\n\nWhen in **Brake Mode**, the SPARK Flex will effectively short all motor wires together. This quickly dissipates any electrical energy within the motor and brings it to a quick stop.\n\nWhen in **Coast Mode**, the SPARK Flex will effectively disconnect all motor wires. This allows the motor to spin down at its own rate.\n\nThe Idle Mode can be configured using the Mode Button, CAN, and USB interfaces.\n\n### Mode Button Configuration\n\nFollow the steps below to switch the Idle Mode between Brake and Coast with the Mode Button.\n\n{% hint style=\"info\" %}\nUse a small screwdriver, straightened paper clip, pen, or other small implement to press the button. Do not use any type of pencil as the pencil lead can break off inside the SPARK Flex.\n{% endhint %}\n\n1. Connect the SPARK Flex to main power, not just USB Power.\n2. The Status LED will indicate which Idle Mode is currently configured by blinking blue or cyan for Brake and yellow or magenta for Coast depending on the motor type.\n3. Press and release the Mode Button.\n4. You should see the Status LED change to indicate the selected Idle Mode.\n\n{% hint style=\"info\" %}\nPlease see the [Status LED Patterns](https://docs.revrobotics.com/brushless/spark-flex/status-led) guide for information on how to identify the Idle Behavior configuration by the color of the Status LED!\n{% endhint %}\n\n### USB Configuration\n\nFollow the steps below to switch the Idle Mode between Brake and Coast with the USB and the REV Hardware Client application. Be sure to [download and install the REV Hardware Client](https://app.gitbook.com/s/-MGEfA6CxjaSQiH5kHxn/gs/install) application before continuing.\n\n1. Connect the SPARK Flex to your computer using a USB-C cable.\n2. Open the REV Hardware Client application and verify that the application is connected to your SPARK MAX.\n3. On the **Basic** tab, select the desired mode with the **Idle Mode** switch.\n4. Click **Persist Parameters** and confirm the change.\n\n### CAN Configuration\n\nPlease see the [API Information](https://app.gitbook.com/s/0OKYENVWAIgVP2TmkWl3/install) for information on how to configure the SPARK Flex using the CAN interface.\n",
- "content_preview": "# SPARK Flex Operating Modes\n\n## Brake/Coast Mode - Idle Behavior\n\nWhen the SPARK Flex is receiving a neutral command the idle behavior of the motor can be handled in two different ways: **Braking** or **Coasting**.\n\nWhen in **Brake Mode**, the SPARK Flex will effectively short all motor wires..."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-max/overview",
- "title": "SPARK MAX Overview",
- "section": "SPARK MAX",
- "language": "All",
- "content": "# SPARK MAX Overview\n\n\n\nSPARK MAX Resources \n\n## General Resources\n\n* [Getting Started with the SPARK MAX](https://docs.revrobotics.com/brushless/spark-max/gs)\n* [Troubleshooting](https://docs.revrobotics.com/brushless/spark-max/troubleshooting)\n * [Status LED Patterns](https://docs.revrobotics.com/brushless/spark-max/status-led)\n* [SPARK MAX Specifications](https://docs.revrobotics.com/brushless/spark-max/specs)\n * [SPARK MAX Data Port Pinout](https://docs.revrobotics.com/brushless/spark-max/specs/data-port)\n* [Using Encoders with the SPARK MAX](https://docs.revrobotics.com/brushless/spark-max/encoders)\n\n## Software Resources\n\n* [Getting Started with the REV Hardware Client](https://docs.revrobotics.com/rev-hardware-client/)\n* [REVLib API and Installation](https://app.gitbook.com/s/0OKYENVWAIgVP2TmkWl3/install)\n* [MAXSwerve Calibration](https://docs.revrobotics.com/rev-hardware-client/brushless/maxswerve)\n* [SPARK MAX Code Examples](https://github.com/REVrobotics/REVLib-Examples)\n\n \n\nThe REV Robotics [SPARK MAX Motor Controller (REV-11-2158)](https://www.revrobotics.com/rev-11-2158/) is an all-in-one USB, CAN, and PWM enabled motor controller that can drive both 12 V brushed and 12 V brushless DC motors. SPARK MAX is designed for use in the *FIRST* Robotics Competition (FRC), incorporating advanced motor control in a small, easy-to-use, and affordable package. Configure and run the SPARK MAX through its built-in USB interface without needing a full control system. \n\n\n\n## Feature Summary\n\n* Brushed and sensored-brushless motor control\n* PWM, CAN, and USB control interfaces\n * PWM/CAN - Locking and keyed 4-pin JST-PH\n * USB - USB type C\n* USB configuration and control\n * Rapid configuration with a PC\n* Smart control modes\n * Closed-loop velocity control\n * Closed-loop position control\n * Follower mode\n* Encoder port\n * Locking and keyed 6-pin JST-PH\n * 3-phase hall-sensor encoder input\n * Motor temperature sensor input\n* Data port\n * Limit switch input\n * Quadrature encoder input with index\n * Multi-function pin\n* Mode button\n * On-board motor type and idle behavior configuration\n* RGB status LED\n * Detailed mode and operation feedback\n* Integrated power and motor wires\n * 12 AWG ultra-flexible silicone wire\n* Passive cooling\n\n## Kit Contents\n\nThe following items are included with each SPARK MAX Motor Controller\n\n* 1 - SPARK MAX Motor Controller\n* 1 - USB-A male to USB-C cable\n* 1 - 4-pin JST-PH to CAN cable\n* 1 - 4-pin JST-PH to single PWM cable\n* 1 - PWM/CAN cable retention clip\n* 1 - Data port protection cap\n\n#### Special Thanks\n\nWe appreciate the assistance from the community for feedback, contributions, and testing the SPARK MAX, especially [Team 195 The CyberKnights](https://team195.com/).\n",
- "content_preview": "# SPARK MAX Overview\n\n\n\nSPARK MAX Resources \n\n## General Resources\n\n* [Getting Started with the SPARK MAX](https://docs.revrobotics.com/brushless/spark-max/gs)\n* [Troubleshooting](https://docs.revrobotics.com/brushless/spark-max/troubleshooting)\n * [Status LED..."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-max/specs",
- "title": "SPARK MAX Specifications",
- "section": "SPARK MAX",
- "language": "All",
- "content": "# SPARK MAX Specifications\n\nThe following tables provide the operating and mechanical specifications for the SPARK MAX Motor Controller. \n\n{% hint style=\"danger\" %}\nDO NOT exceed the maximum electrical specifications. Doing so will cause permanent damage to the SPARK MAX and will void the warranty.\n{% endhint %}\n\n### Main Electrical Specifications\n\nParameter Min Typ Max Units Operating Voltage Range 5.5 12 24 V Absolute Maximum Supply Voltage - - 30 V Continuous Output Current - - 60* A Maximum Output Current (2 second surge) - - 100 A Output Frequency - 20 - kHz
\n\n{% hint style=\"warning\" %}\n\\*Continuous operation at 60A may produce high temperatures on the heat sink. Caution should be taken when handling the SPARK MAX if it has been running at higher current level for an extended period of time.\n{% endhint %}\n\n{% hint style=\"warning\" %}\nIf using a battery to power SPARK MAX, make sure the fully charged voltage is below 24V allowing for sustained operation. Some battery chemistries and configurations, including 6S LiPo packs, have a charge voltage above the maximum operating voltage for SPARK MAX.\n{% endhint %}\n\n### PWM Input Specifications\n\n| Parameter | Min | Typ | Max | Units |\n| ---------------------------- | --- | ---- | ---- | ----- |\n| Full-reverse Input Pulse † | - | 1000 | - | μs |\n| Neutral Input Pulse †† | - | 1500 | - | μs |\n| Full-forward Input Pulse ††† | - | 2000 | - | μs |\n| Valid Input Pulse Range | 500 | - | 2500 | μs |\n| Input Frequency | 50 | - | 200 | Hz |\n| Input Timeout ‡ | - | 50 | - | ms |\n| Default Input Deadband ‡‡ | - | 5 | - | % |\n| Input High Level | 0.5 | 0.7 | 0.9 | V |\n| Input Voltage Max | 12 | - | - | V |\n\n† Brushed: between A and B outputs at 100% duty. Brushless: A->B->C direction at 100% duty. †† Neutral corresponds to zero output voltage (0 V) and is either braking or coasting depending on the current idle behavior mode. ††† Brushed: between A and B outputs at 100% duty.
Brushless: C->B->A direction at 100% duty.
‡ If a valid pulse isn't received within the timeout period, the SPARK MAX will disable its output. ‡‡ Input deadband is added to each side of the neutral pulse width. Within the deadband, output state is neutral. The deadband value is configurable using the REV Hardware Client or through the CAN interface.
\n\n### Data Port Specifications\n\nParameter Min Typ Max Units Parameter Min Typ Max Units Digital input voltage range † 0 - 5 V Digital input-high voltage † 1.85 - - V Digital input-low voltage † - - 1.36 V Analog input voltage range †† 0 - 3.3 V Analog input (12bit) - 81 - μV 5V supply current (I5V) ‡ - - 100 mA 3.3V supply current (I3.3V) - - 30 mA Total supply current (I5V + I3.3V) - - 100 mA
\n\n| † | See the [Data Port](https://docs.revrobotics.com/brushless/spark-max/specs/data-port) documentation for more details on the digital pins on the Data Port. |\n| -- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| †† | See the [Analog Input](https://docs.revrobotics.com/brushless/spark-max/data-port#analog-input) documentation for more details on the Data Port's analog pin. |\n| ‡ | The 5V supply is shared between the Data Port and Encoder Port. |\n\n### Encoder Port Specifications\n\n| **Parameter** | **Min** | **Typ** | **Max** | **Units** |\n| ---------------------------------- | ------- | ------- | ------- | --------- |\n| Digital input voltage range † | 0 | - | 5 | V |\n| Digital input-high voltage † | 1.85 | - | - | V |\n| Digital input-low voltage † | - | - | 1.36 | V |\n| Analog input voltage range †† | 0 | - | 3.3 | V |\n| 5V supply current (I5V) ‡ | - | - | 100 | mA |\n| 3.3V supply current (I3.3V) | - | - | 30 | mA |\n| Total supply current (I5V + I3.3V) | - | - | 100 | mA |\n\n| † | See the [Data Port](https://docs.revrobotics.com/brushless/spark-max/specs/data-port) documentation for more details on the digital pins on the Data Port. |\n| -- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| †† | See the [Analog Input](https://docs.revrobotics.com/brushless/spark-max/data-port#analog-input) documentation for more details on the Data Port's analog pin. |\n| ‡ | The 5V supply is shared between the Data Port and Encoder Port. |\n\n### Mechanical Specifications\n\n| **Parameter** | **Min** | **Typ** | **Max** | **Units** |\n| --------------------------- | ------- | ------- | ------- | --------- |\n| Body length | - | 70 | - | mm |\n| Body width | - | 35 | - | mm |\n| Body height | - | 25.5 | - | mm |\n| Weight | - | 113.3 | - | g |\n| Power and motor wire gauge | - | 12 | - | AWG |\n| Power and motor wire length | - | 15 | - | cm |\n",
- "content_preview": "# SPARK MAX Specifications\n\nThe following tables provide the operating and mechanical specifications for the SPARK MAX Motor Controller. \n\n{% hint style=\"danger\" %}\nDO NOT exceed the maximum electrical specifications."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-max/specs/power-and-motor-connections",
- "title": "Power and Motor Connections",
- "section": "SPARK MAX",
- "language": "All",
- "content": "# Power and Motor Connections\n\nSPARK MAX is designed to drive 12V brushed and brushless DC motors at currents up to 60A continuously. Power and motor connections are made through the two sets of wires built into the SPARK MAX. The wires are 12AWG ultra-flexible silicone-coated wire. Each wire runs approximately 15cm from the end faces of the controller. Be sure to take care when cutting and stripping the wires as not to cut them too short. The figure below shows these connections in detail. \n\n\n\n{% hint style=\"warning\" %}\nAs with any electrical component, make all connections with the power turned off. Connecting the SPARK MAX to a powered system may result in unexpected behavior an may pose a safety risk.\n{% endhint %}\n\n## Motor Output\n\nMotor output wires are labeled as A, B, and C with red, black, and white wires. Brushed motors must be connected to the A and B wires, while brushless motors must be connected to all three. **It is critical that the order of the brushless motor wires match the SPARK MAX or the motor will not spin and could be damaged.** Additional details are below.\n\n#### Motor Connections\n\n\n\nSPARK MAX cannot detect which motor type it is connected to. Be sure to configure the SPARK MAX to run the type of motor you have connected. See the [Motor Type - Brushed/Brushless Mode](https://docs.revrobotics.com/brushless/operating-modes#brushed-brushless-mode-motor-type) section for more details on configuring the appropriate motor type.\n\n## Power Input\n\nPower input wires are labeled as V+ and V- with red and black wires. The SPARK MAX is intended to operate in a 12 V DC robot system, however, it is compatible with any DC power source between 5.5 V and 24 V.\n\n{% hint style=\"danger\" %}\nDO NOT reverse V+ and V- or swap motor and power connections. Doing so will cause permanent damage to the SPARK MAX and will void the warranty.\n{% endhint %}\n\n{% hint style=\"danger\" %}\nDO NOT exceed the maximum supply voltage of 30V. Doing so will cause permanent damage to the SPARK MAX and will void the warranty.\n{% endhint %}\n\nWhen using high-current motors, it is recommended to use a power source that is capable of handling large surge currents, e.g. a 12V lead-acid battery. If the supply voltage drops below 5.5V the SPARK MAX will brown out, resulting in unexpected behavior. It is also highly recommended to incorporate a fuse or circuit breaker in series with the SPARK MAX between it and the power source to prevent exceeding the maximum current rating.\n\n{% hint style=\"danger\" %} DO NOT exceed the maximum current ratings:\n\n* 60A for 3 minutes\n* 100A for 2 seconds\n\nDoing so will cause permanent damage to the SPARK Flex and will void the warranty.\n{% endhint %}\n",
- "content_preview": "# Power and Motor Connections\n\nSPARK MAX is designed to drive 12V brushed and brushless DC motors at currents up to 60A continuously. Power and motor connections are made through the two sets of wires built into the SPARK MAX. The wires are 12AWG ultra-flexible silicone-coated wire."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-max/specs/control-connections",
- "title": "Control Connections",
- "section": "SPARK MAX",
- "language": "All",
- "content": "# Control Connections\n\nThe SPARK MAX can be controlled by three different interfaces, servo-style PWM, controller area network (CAN), and USB. The following sections describe the physical connections to these interfaces in detail. For details on the operation and protocols of the PWM, CAN, and USB interfaces, please see the [section on Control Interfaces](https://docs.revrobotics.com/brushless/operating-modes#usb-configuration). \n\n## CAN/PWM Port\n\nThe CAN/PWM Port is located on the power input side of the SPARK MAX. This port can be connected to either a servo-style PWM signal or a CAN bus with other devices. Connector details can be found below.\n\n\n\n### CAN/PWM Port Connector Information\n\n| Connector Pin | CAN Function | PWM Function |\n| ------------- | ------------ | ------------ |\n| 1 | CAN High | Signal |\n| 2 | CAN Low | Ground |\n| 3 | CAN High | Signal |\n| 4 | CAN Low | Ground |\n\n### Mating Connector Information\n\n| **Description** | **Manufacturer** | **Part Number** | **Vendor** | **Vendor P/N** |\n| ------------------------- | ---------------- | --------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| JST-PH 4-pin Housing | JST | PHR-4 | DigiKey | [455-1164-ND](https://www.digikey.com/products/en?keywords=455-1164-ND) |\n| JST-PH Contact | JST | SPH-002T-P0.5L | DigiKey | [455-2148-1-ND](https://www.digikey.com/products/en?keywords=455-2148-1-ND) |\n| Recommended Crimping Tool | IWISS | SN-2549 | Amazon | [SN-2549](https://www.amazon.com/IWISS-Crimping-AWG28-18-Ratcheting-Connector/dp/B01N4L8QMW/ref=sr_1_2?ie=UTF8\\&qid=1546882885\\&sr=8-2\\&keywords=sn-2549) |\n\nIdentical-function pins are electrically connected inside the SPARK MAX, therefore the CAN daisy-chain is completed internally and any two signal and ground pairs can be used for PWM.\n\n## USB-C Port\n\nThe USB-C Port is located on the power input side of the SPARK MAX. It supports USB 2.0 and 5V power for the SPARK MAX's internal microcontroller. While you can configure the SPARK MAX without main power, you will not be able to spin a motor.\n",
- "content_preview": "# Control Connections\n\nThe SPARK MAX can be controlled by three different interfaces, servo-style PWM, controller area network (CAN), and USB. The following sections describe the physical connections to these interfaces in detail."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-max/specs/encoder-port",
- "title": "Encoder Port",
- "section": "SPARK MAX",
- "language": "All",
- "content": "# Encoder Port\n\nLocated on the motor output side of the SPARK MAX is a 6-pin Encoder Port. This port is designed to accept the built-in hall-encoder from the [NEO Brushless Motor](http://www.revrobotics.com/rev-21-1650/), but it can also connect to other external encoders when running in Brushed Mode. The connector details can be found below. \n\n{% hint style=\"info\" %}\nThe SPARK MAX can be configured to run in [Alternate Encoder Mode](https://docs.revrobotics.com/brushless/spark-max/encoders/alternate-encoder), which reconfigures the Data Port on the top of the controller to accept an alternative quadrature encoder in addition to the Encoder Port.\n{% endhint %}\n\n\n\n#### Encoder Port Connector Information\n\n| **Connector Pin** | **Pin Type** | **Pin Function** |\n| :---------------: | :----------: | :---------------: |\n| 1 | Power | Ground |\n| 2 | Digital | Encoder C / Index |\n| 3 | Digital | Encoder B |\n| 4 | Digital | Encoder A |\n| 5 | Analog | Motor Temperature |\n| 6 | Power | +5V |\n\n \n\n#### Mating Connector Information\n\n| Description | Manufacturer | Part Number | Vendor | Vendor P/N |\n| ------------------------- | ------------ | -------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| JST-PH 6-pin Housing | JST | PHR-6 | DigiKey | [455-1162-ND](https://www.digikey.com/products/en?keywords=455-1162-ND) |\n| JST-PH Contact | JST | SPH-002T-P0.5L | DigiKey | [455-2148-1-ND](https://www.digikey.com/products/en?keywords=455-2148-1-ND) |\n| Recommended Crimping Tool | IWISS | SN-2549 | Amazon | [SN-2549](https://www.amazon.com/IWISS-Crimping-AWG28-18-Ratcheting-Connector/dp/B01N4L8QMW/ref=sr_1_2?ie=UTF8\\&qid=1546882885\\&sr=8-2\\&keywords=sn-2549) |\n",
- "content_preview": "# Encoder Port\n\nLocated on the motor output side of the SPARK MAX is a 6-pin Encoder Port. This port is designed to accept the built-in hall-encoder from the [NEO Brushless Motor](http://www.revrobotics.com/rev-21-1650/), but it can also connect to other external encoders when running in Brushed..."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-max/specs/data-port",
- "title": "Data Port",
- "section": "SPARK MAX",
- "language": "All",
- "content": "# Data Port\n\n## SPARK MAX Data Port Pinout\n\nLocated on the top of the SPARK MAX, the Data Port allows for extra sensor input and future feature development. The connector details can be found below. \n\n\n\nConnector Pin Pin Type Pin Function Connector Pin Pin Type Pin Function 1 Power +3.3V 2 Power +5V 3 Analog Analog Input 4 Digital Forward Limit Switch Input 5 Digital Encoder B 6 Digital Multi-function Pin 7 Digital Encoder A 8 Digital Reverse Limit Switch Input 9 Digital Encoder C / Index 10 Ground Ground
\n\nUsing the [SPARK MAX Data Port Breakout Board](#spark-max-data-breakout-board-features) ([REV-11-1278](https://www.revrobotics.com/rev-11-1278/)) makes interfacing with the SPARK MAX Data Port easier. \n\n## SPARK MAX Data Port Features\n\n### Limit Switch Inputs\n\nSPARK MAX has two limit switch inputs that, when triggered, can independently prevent motion in both the forward and reverse directions. By default, when the pin for the corresponding direction is grounded, SPARK MAX will override any input commands for that direction and force the output into the neutral state. Input commands for the opposite direction will still be processed unless the corresponding limit signal is also triggered.\n\nThe default polarity is compatible with Normally Open (NO) style limit switches, whose contacts are shorted together when the switch is pressed. The Limit Switch Inputs can be configured for the opposite polarity using the USB or CAN interfaces. When configured for the opposite polarity, Normally Closed (NC), the limit will be triggered when the pin is left disconnected from ground. In other words, connecting the pin to ground will release the limit. The following table shows these configurations in detail:\n\n#### Limit Switch Operation\n\n\n\n### Quadrature Encoder Input\n\nThe Quadrature Encoder Input on the Data Port is compatible with standard quadrature encoder signals, usually labeled as channel A, channel B, and Index. SPARK MAX shares these signals with the Encoder Port on the output side of the controller, therefore the Index signal is shared with the third brushless encoder signal C. When in Brushless Mode, these Data Port pins cannot be used with an external encoder. See [Alternate Encoder Mode](https://docs.revrobotics.com/brushless/spark-max/encoders/alternate-encoder) for information on how to configure the SPARK MAX to accept an alternative encoder source when running in Brushless Mode.\n\nWhen in Brushed Mode, an external encoder can be connected through either the Data Port or the Encoder Port.\n\nThe SPARK MAX encoder signals are not pulled high internally. This is to ensure the maximum compatibility with different types of encoders.\n\n### Analog Input\n\nThe Analog Port on the SPARK MAX can measure voltages up to 3.3V with 12-bit resolution. The SPARK MAX Data Port Breakout includes a 5V to 3.3V amplifier circuit so that 5V signals can be sensed with the Analog Input pin.\n\nAnalog input is supported on firmware versions 1.4.0 and newer.\n\n### Multi-function Pin\n\nThis pin is reconfigured when the SPARK MAX is configured in Alternate Encoder Mode.\n\n### Power Rails\n\nThe SPARK MAX Data Port can provide both 3.3V and 5V power to connected devices. Please check [Data Port Specifications](https://docs.revrobotics.com/brushless/overview#data-port-specifications) for details on the supply current capabilities of both rails.\n\n## SPARK MAX Data Port Accessories\n\n### Alternate Encoder Adapter\n\n#### Features\n\nThe SPARK MAX Alternate Encoder Adapter ([REV-11-1881-PK2](https://www.revrobotics.com/rev-11-1881/)) enables the use of an alternative encoder source different from the default. This is especially useful when running one of the NEO Brushless Motors, as the default encoder port is occupied by the built-in NEO hall sensors. Please see the Alternate Encoder Mode section in the [SPARK MAX User's Manual](https://docs.revrobotics.com/brushless/spark-max/overview) for more information.\n\n* JST PH 6-pin connector\n* Pinout compatible with REV Through Bore Encoder\n* Index Signal/Absolute PWM Pulse selection switch\n* Selects which signal is connected to pin 4 of the Data Port\n* Solder pads\n* Analog Input\n* 3.3V and 5.0V Power\n* Ground\n\n#### Specifications\n\n* 1 x JST PH, 6-pin connector\n* 1 x 14 Position 2 Row Receptacle Connector 0.050\"\n\n### Absolute Encoder Adapter\n\n#### Features\n\nThe SPARK MAX Absolute Encoder Adapter ([REV-11-3326](https://www.revrobotics.com/rev-11-3326/)) connects the Absolute Duty Cycle output of the Through Bore Encoder to the correct SPARK MAX Data Port pins, leaving the incremental quadrature pins disconnected.\n\n* JST PH 6-pin connector\n* Pinout Compatible with REV Through Bore Encoder\n* Solder pads\n* Limit Switches\n* Ground\n\n#### Specifications\n\n* 1 x JST PH, 6-pin connector\n* 1 x 14 Position 2 Row Receptacle Connector 0.050\"\n\n### Data Port Breakout Board\n\n#### Features\n\nThe SPARK MAX Data Port Breakout Board ([REV-11-1278](https://www.revrobotics.com/rev-11-1278/)) makes it easy to connect external sensors to the SPARK MAX Data Port.\n\n* Solder pads for every Data Port pin\n* Analog input 5V to 3.3V converter\n * Built-in amplifier maps 0V - 5V analog signals to the native 0V - 3.3V range of the SPARK MAX Analog Input\n * Configurable resistors can bypass the amplifier (move R3 to R4 position)\n* Pass-through Data Port connector\n * Connect other sensors with data port-compatible cables while using this breakout\n* Mounts directly to SPARK MAX\n * No need for a data port cable\n * Securely mounts to the SPARK MAX zip-tie notches\n",
- "content_preview": "# Data Port\n\n## SPARK MAX Data Port Pinout\n\nLocated on the top of the SPARK MAX, the Data Port allows for extra sensor input and future feature development."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-max/gs",
- "title": "SPARK MAX Getting Started",
- "section": "SPARK MAX",
- "language": "All",
- "content": "# SPARK MAX Getting Started\n\nThe SPARK MAX is a motor controller that can control both Brushed DC and Brushless DC motors. Out of the box, the MAX defaults to its Brushless Mode and is ready to drive a NEO Brushless Motor with its PWM interface. Included in this section are the basic steps to get a motor spinning using the REV Hardware Client as well as information on how to configure your SPARK MAX. \n\n{% hint style=\"info\" %}\nWe recommend following this guide in its entirety at least once to understand the key features of the SPARK MAX. This guide can also serve as a fallback in case of any issues faced.\n{% endhint %}\n\n## Before You Start\n\nBefore following this guide, the[ REV Hardware Client should be installed](https://app.gitbook.com/s/-MGEfA6CxjaSQiH5kHxn/gs/install) before continuing. The client is the best way to verify that the device is configured correctly, and is ***required*** before using the CAN interface.\n",
- "content_preview": "# SPARK MAX Getting Started\n\nThe SPARK MAX is a motor controller that can control both Brushed DC and Brushless DC motors. Out of the box, the MAX defaults to its Brushless Mode and is ready to drive a NEO Brushless Motor with its PWM interface."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-max/gs/wiring",
- "title": "Wiring the SPARK MAX",
- "section": "SPARK MAX",
- "language": "All",
- "content": "# Wiring the SPARK MAX\n\n## Required Materials\n\n* 12V Battery\n* 120A Circuit Breaker\n* Power Distribution Panel\n* SPARK MAX\n* Brushed or Brushless Motor\n* USB Type-C Cable\n\n## Prepare the Components\n\n### Test Bed\n\nUsing a test bed is an easy way to get started with using the SPARK MAX and verify connections and code. For the initial bring up of the SPARK MAX a test bed with a single SPARK MAX, a brushless or brushed motor, and a [properly wired Power Distribution Panel with breaker](https://docs.wpilib.org/en/stable/docs/getting-started/getting-started-frc-control-system/how-to-wire-a-robot.html#attach-battery-connector-to-pdp) is recommended. \n\n### Electrical Connections\n\nThe power and motor wires are permanently connected to the SPARK MAX and are not replaceable. So take care not to cut these wires too short. It is highly recommended to install connectors on these wires to simplify both the power and motor connections. A common connector used for this purpose is the Anderson Power Pole connector. Follow our [Anderson Power Pole](https://docs.revrobotics.com/brushless/tips/anderson-connectors) guide for tips on how to properly crimp these connectors.\n\n{% hint style=\"warning\" %}\nMake sure the power is disconnected or turned off before making any electrical connections on your test bed or robot.\n{% endhint %}\n\nConnect the integrated SPARK MAX power leads labeled V+ (red) and V- (black) to an available channel on the Power Distribution Panel. If you need to extend the length of the integrated wires, it is recommended to use 12AWG wire or larger (lower gauge number).\n\n## Motor Connections\n\nThe first step is determining the type of motor you wish to connect. The SPARK MAX supports two types of motors: brushed DC and brushless DC. An easy way to determine the motor type is to look at the number of primary (larger) motor wires. Brushed motors only have 2 primary motor wires, while brushless motors have 3 primary wires and additional smaller sensor wires.\n\n\n\n### NEO Brushless Motor Connections\n\nConnect the three motor wires; red, black, and white, to the matching SPARK MAX output wires labeled A (red), B (black), and C (white).\n\n\n\nNext connect the NEO or NEO 550's encoder cable to the port labeled ENCODER just above the output wires.\n\n\n\n{% hint style=\"warning\" %}\nThe encoder sensor cable is ***required*** for the operation of brushless motors with SPARK MAX. The motor will not spin without it.\n{% endhint %}\n\n### Brushed DC Motor Connections\n\nConnect the two motor wires, M+ (red) and M- (black), to the SPARK MAX output wires labeled A (red) and B (black).\n\nThe third output wire, labeled C (white), is not used when driving a brushed motor and should be secured and insulated. We recommend tying it back with a zip-tie and covering the end with a piece of electrical tape. Do not cut this wire in case you wish to use a brushless motor in the future. In the example below the extra unused motor wire is insulated by the white connector and secured in the block.\n\n\n\n## Verify Connection\n\nCarefully check all connections before continuing and verify that all colors match. The SPARK MAX can be permanently damaged if the power connection is reversed.\n\n{% hint style=\"info\" %}\nLeave the CAN cable disconnected for now, we will wiring this up later.\n{% endhint %}\n",
- "content_preview": "# Wiring the SPARK MAX\n\n## Required Materials\n\n* 12V Battery\n* 120A Circuit Breaker\n* Power Distribution Panel\n* SPARK MAX\n* Brushed or Brushless Motor\n* USB Type-C Cable\n\n## Prepare the Components\n\n### Test Bed\n\nUsing a test bed is an easy way to get started with using the SPARK MAX and verify..."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-max/gs/make-it-spin",
- "title": "Make it Spin!",
- "section": "SPARK MAX",
- "language": "All",
- "content": "# Make it Spin!\n\n{% hint style=\"success\" %}\n[Make it Spin!](https://docs.revrobotics.com/rev-hardware-client-2/home/run-motor) For REV Hardware Client 2 is available.\n{% endhint %}\n\n## Power On\n\nNow that the device is wired, and the connections carefully checked, power on the robot. You should see the SPARK MAX slowly blinking its for a new device the color will be Magenta. If the LED is dark, or you see a different blink pattern, refer to the [Status LED](https://docs.revrobotics.com/brushless/spark-max/status-led) guide for troubleshooting. \n\n{% hint style=\"info\" %}\nIf you are using a brushed motor, you may see a sensor error. This is expected until you configure the device to accept a brushed motor in the following steps.\n{% endhint %}\n\n## Connect to the SPARK MAX\n\nPlug in the USB cable and start the REV Hardware Client. Select the SPARK MAX from the Connected Hardware.\n\n\n\n{% hint style=\"info\" %}\nIf you can not see the SPARK MAX, make sure that the SPARK MAX is not being used by another application\r. Then unplug the SPARK MAX from the computer and plug it back in.\n{% endhint %}\n\n## Basic Setup and Configuration\n\nBefore any parameters can be changed, you **must** first assign a unique CAN ID to the device. This can be any number between 1 and 63. After setting a unique CAN ID, the user interface will refresh and allow you to change other parameters.\n\n\n\n{% hint style=\"info\" %}\nEventually you may set up a CAN network on your test bench or robot. Be sure each device on the network has a unique CAN ID. It is helpful to label each device with its ID number to aid in troubleshooting.\n{% endhint %}\n\n### Set the Motor Type\n\nIf you are using a NEO or NEO 550, verify that the motor type is set to **REV NEO Brushless**, Sensor Type is **Hall Effect**, and the LED is blinking Magenta or Cyan.\n\n\n\n{% hint style=\"info\" %}\nIf you see a *Sensor Fault* blink code, make sure the encoder cable is plugged in completely.\n{% endhint %}\n\nIf you are running brushed motor, set the motor type to **Brushed** and the sensor type will change to **Quadrature**, and verify that the LED is blinking Yellow or Blue.\n\n\n\n### Limiting Current\n\nThere are two ways to protect your robot’s motors from electrical damage in high-current situations: Circuit Breakers and the SPARK MAX’s Smart Current Limit Setting. To protect your motors from currents that are too high, it is a best practice to limit your current both with the SPARK MAX’s Smart Current Limit **and** an appropriately rated circuit breaker.\n\nCircuit breakers, while an extremely important part of a robot's wiring and safety, are only designed to trip at a specific temperature, after a set amount of time, to protect the electrical system from fire or other electrical hazards. Due to this, we recommend setting a Smart Current Limit to protect your motors from damage due to high currents.\n\nThe SPARK MAX Motor Controller includes a Smart Current Limit feature that can adjust the applied output to the motor to maintain a constant phase current. \n\nOut of the box, the SPARK MAX's Smart Current Limit default setting is 80A for any motor that you use. We recommend utilizing our locked-rotor testing data or the table below to decide what to set your Smart Current Limit to for your robot: Locked-Rotor Testing for the [NEO (REV-21-1650) ](https://www.revrobotics.com/neo-brushless-motor-locked-rotor-testing/)and [NEO 550 (REV-21-1651)](https://www.revrobotics.com/neo-550-brushless-motor-locked-rotor-testing/).\n\n{% hint style=\"danger\" %}\nRemember that some settings, like Smart Current Limit, must be burned to flash via code or the Hardware Client in order to be retained through a power cycle of the SPARK MAX.\n{% endhint %}\n\n#### Suggested Current Limits\n\nYour ideal current limit may vary based on your specific application, but these values can be used as a starting point to reduce the chance of an overload on your motor as you begin tuning your specific mechanism's Smart Current Limit.\n\n| Motor Type | Current Limit Range |\n| ----------------------------------------------------------------- | ------------------- |\n| NEO ([REV-21-1650](https://www.revrobotics.com/rev-21-1650/)) | 40A - 60A |\n| NEO 550 ([REV-21-1651](https://www.revrobotics.com/rev-21-1651/)) | 20A - 40A |\n\n{% hint style=\"warning\" %}\nWarning: Setting current limits outside of the suggested ranges listed above may cause unintended overload and severe damage to components that are not covered by warranty.\n{% endhint %}\n\n\n\n## Save the Settings\n\nThe settings must be saved for the SPARK MAX to remember its new configuration through a power cycle. To do this, press the *Burn Flash* button at the bottom of the page. It will take a few seconds to save, indicated by the loading symbol on the button.\n\n{% hint style=\"warning\" %}\nAs of REV Hardware Client version 1.7.0, \"Burn Flash\" has been renamed to \"Persist Perimeters\"!\n{% endhint %}\n\n\n\nAny settings saved this way will be remembered when the device is powered back on. You can always restore the factory defaults if you need to reset the device.\n\n## Spin the Motor\n\n{% hint style=\"danger\" %}\nBefore running any motor, make sure all components are in a safe state, that the motor is secured, and that anyone nearby is aware. FRC motors are very powerful and can quickly cause damage to people and property. \n{% endhint %}\n\n{% hint style=\"info\" %}\nKeep the CAN cable disconnected throughout the test. For safety reasons, the REV Hardware Client will not run the motor if the roboRIO is connected. If the roboRIO was connected, power cycle the SPARK MAX.\n{% endhint %}\n\nTo spin the motor, go to the Run tab, keep all of the default settings and press *Run* *Motor.* The *setpoint* is 0 by default, meaning that the motor is being commanded to **idle** (0% power). When you press *Run* you should see the LED go from slow blinking to solid, indicating that the motor is idling.\n\n\n\n**Slowly** ramp the setpoint slider up. The motor should start to spin and you should see a green blink pattern proportional to the speed you have set to the motor. Slowly ramp the slider down. The motor should spin in reverse, and you should see a red blink pattern proportional to the speed you have set to the motor.\n\nIf you are unable to spin the motor, visit our [troubleshooting guide](https://docs.revrobotics.com/brushless/spark-max/troubleshooting).\n",
- "content_preview": "# Make it Spin!\n\n{% hint style=\"success\" %}\n[Make it Spin!](https://docs.revrobotics.com/rev-hardware-client-2/home/run-motor) For REV Hardware Client 2 is available.\n{% endhint %}\n\n## Power On\n\nNow that the device is wired, and the connections carefully checked, power on the robot."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-max/gs/basic-config",
- "title": "Basic Configurations",
- "section": "SPARK MAX",
- "language": "All",
- "content": "# Basic Configurations\n\nSPARK MAX has many operating modes that can be configured through its CAN and USB interfaces. Additionally, the following basic operating modes can be configured with the MODE button located on the top of the SPARK MAX: \n\n* [Idle Behavior](https://docs.revrobotics.com/brushless/operating-modes#brake-coast-mode-idle-behavior): Brake/Coast\n* [Motor Type](https://docs.revrobotics.com/brushless/operating-modes#brushed-brushless-mode-motor-type): Brushed/Brushless\n\n**Mode configuration must be done with power applied to the SPARK MAX.**\n\n{% hint style=\"info\" %}\nConfiguring the idle behavior and motor type using the mode button is a quick way to set up a SPARK MAX without using a computer. This is most useful when you are controlling the SPARK MAX through its PWM interface or when testing the affect of Braking or Coasting on a mechanism.\n{% endhint %}\n\n### Idle Behavior\n\nWhenever the SPARK MAX receives a neutral signal (no motor movement) or no signal at all (robot disabled), it can either brake the motor or let it coast. When in Brake Mode, MAX will short the motor wires to each other, electrically braking the motor. This slows the motor down very quickly if it was spinning and makes it harder, but not impossible to back-drive the motor when it is stopped.\n\n* With power turned on, press and release the MODE button to switch between Brake and Coast Mode.\n* The Status LED will indicate which idle behavior mode it is in. See the [Status LED Colors and Patterns section](https://docs.revrobotics.com/brushless/status-led#standard-operation) for more information.\n\n### Motor Type\n\nIt is very important to have the SPARK MAX configured for the appropriate motor type. \n\n{% hint style=\"danger\" %}\nOperating in Brushed Mode with a brushless motor connected will permanently damage the motor!\n{% endhint %}\n\nWith power turned on, press and hold the MODE button for approximately 3 - 4 seconds.\n\n* The Status LED will change and indicate which motor type is selected. See the [Status LED Colors and Patterns section](https://docs.revrobotics.com/brushless/status-led#standard-operation) for more information.\n* Release the MODE button.\n",
- "content_preview": "# Basic Configurations\n\nSPARK MAX has many operating modes that can be configured through its CAN and USB interfaces. Additionally, the following basic operating modes can be configured with the MODE button located on the top of the SPARK MAX: \n\n* [Idle..."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-max/status-led",
- "title": "SPARK MAX Status LED Patterns",
- "section": "SPARK MAX",
- "language": "All",
- "content": "# SPARK MAX Status LED Patterns\n\nSPARK MAX will indicate important status information on its multi-colored STATUS LED located on the top of its case. The following tables shows each state and the corresponding LED color pattern. \n\n### Standard Operation\n\nOperating Mode Idle Mode State Color/Pattern Graphic Brushed Brake No Signal Blue Blink Valid Signal Blue Solid Coast No Signal Yellow Blink Valid Signal Yellow Solid Brushless Brake No Signal Cyan Blink Valid Signal Cyan Solid Coast No Signal Magenta Blink Valid Signal Magenta Solid Partial Forward Green Blink Full Forward Green Solid Partial Reverse Red Blink Full Reverse Red Solid Forward Limit Green/White Blink Reverse Limit Red/White Blink
\n\n### **Identification, Updating, and Recovery**\n\n| Mode | Color/Pattern | Graphic |\n| ------------------------------------------ | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |\n| Device Identify | White/Magenta Fast Blink |  |\n| CAN Bootloader Firmware Updating
| White/Yellow Blink (v1.5.0) Green/Magenta Blink (v1.4.0)
|  |\n| CAN Bootloader Firmware Retry
| White/Blue Blink |  |\n| USB DFU (Device Firmware Update)
| Dark (LED off) |  |\n| Recovery Mode | Dark (LED off) |  |\n\n### Fault Conditions\n\n| Fault | Condition | Color/Pattern | Graphic |\n| -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------- |\n| 12V Missing | The motor will not drive if powered only by USB. This blink code warns the user of this condition. | Orange/Blue Slow Blink |  |\n| Sensor Fault | This can occur if the sensor type is misconfigured, the sensor cable is not plugged in or damaged, or if a sensor other than the motor sensor is plugged in. | Orange/Magenta Slow Blink |  |\n| Gate Driver Fault | A fault reported by the core internal electronic circuitry. If this code persists after power cycling the controller, contact REV. | Orange/Cyan Slow Blink |  |\n| CAN Fault | The CAN fault will be shown after the first time the device is plugged into the CAN port and a fault later occurs. Check your CAN wiring if you see this fault. | Orange/Yellow Slow Blink |  |\n| Corrupt Firmware (recover using Recovery Mode)
| Firmware failed to load. | Dark (LED off) |  |\n",
- "content_preview": "# SPARK MAX Status LED Patterns\n\nSPARK MAX will indicate important status information on its multi-colored STATUS LED located on the top of its case. The following tables shows each state and the corresponding LED color pattern."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-max/troubleshooting",
- "title": "SPARK MAX Troubleshooting",
- "section": "SPARK MAX",
- "language": "All",
- "content": "# SPARK MAX Troubleshooting\n\nMany issues can be solved by systematic troubleshooting without needing to contact REV Support. Take a look at the troubleshooting tips below for help in determining the cause of the issue you are seeing. Should you need to contact us, describing the steps you've taken in detail will help us get you up and running quickly. \n\n## General Troubleshooting Tips\n\nThe key to effective troubleshooting is isolating the issue. Many issues can show the same symptom, so eliminating failure points one at a time is critical to finding the root cause.\n\n### Rule Out Issues By Isolation\n\nIf possible, try to eliminate a section of the system when troubleshooting. For example:\n\n* Rule out a code or control wiring issue:\n * Use the REV Hardware Client to run the SPARK MAX over USB.\n * **Please be aware of the CAN lockout feature of the SPARK MAX.** If it has been connected to the roboRIO's CAN bus, a safety feature within the SPARK MAX will lock out USB communication. Disconnecting from the CAN bus and power-cycling the MAX will release the lock.\n * If this is your first time running the REV Hardware client, see the [Getting Started with the REV Hardware Client](https://app.gitbook.com/s/-MGEfA6CxjaSQiH5kHxn/gs/install) for a tour of the software and its features.\n* Rule out a code issue:\n * Create a simple test program using our SPARK MAX Example Code.\n* Rule out a mechanical issue:\n * Remove the motor from the mechanism or use a different, free spinning motor.\n\n### Use the Driver Station\n\nAn extremely useful set of tools can be found on the Driver Station:\n\n* Use the [Driver Station Log File Viewer](https://docs.wpilib.org/en/latest/docs/software/driverstation/driver-station-log-viewer.html)\n * Look at the PDP channel current draw:\n * * Higher than expected current on a channel can indicate both mechanical and electrical issues.\n * Look at the battery voltage:\n * Large dips in the battery voltage around the time of an issue can indicate battery health issues that cause brownouts.\n* Use the [CAN/Power Tab](https://docs.wpilib.org/en/latest/docs/software/driverstation/driver-station.html#can-power-tab)\n * Look at the CAN Bus Utilization.\n * Look at CAN Faults.\n * Look at Comms Faults:\n * Comms faults can affect the SPARK MAX. If it loses communication with the roboRIO, it will go to its safe disabled state. This can look like a momentary glitch in a motor spinning if the comms faults are infrequent and irregular.\n\n### Use the APIs\n\nIt is also very useful to log or plot operating values internal to the SPARK MAX. These values can be accessed using the [SPARK MAX APIs](https://app.gitbook.com/s/0OKYENVWAIgVP2TmkWl3/install). Useful values to log:\n\n* *getAppliedOutput()*\n * This value will show what the SPARK MAX is actually applying to the motor output. This can illuminate issues with closed loop control tuning.\n* *getOutputCurrent()*\n * This value will show the output current going to the phases of the motor. [Output current won't always be the same as the Input current](https://www.chiefdelphi.com/t/neo-motors-pulling-only-5-amps-when-stalled/350542/25) measured by the PDP. Knowing the output current is useful to diagnose current-limit issues if motors are overheating.\n* *getBusVoltage()*\n * A way to measure the input voltage right at the controller.\n* *getStickyFaults()*\n * A sticky fault indicates if a fault has occurred since the last time the faults were reset. Checking these can provide a lot of insight into what the controller is experiencing.\n\n## Common Faults and Issues\n\nBelow you will find some troubleshooting steps for some common faults and issues related to operating the SPARK MAX.\n\n### Motor Not Spinning\n\n\n\n### Gate Driver Fault\n\n\n\n### Sensor Fault\n\n\n\n### Powers via USB but not 12V\n\n \n\n#### Continuity Check Instructions\n\n| Measure the continuity between each of the motor phase wires and the sensor wire, as pictured here. Also, check the resistance between wires of the sensor cable and motor wires. Reach out to with results for each phase wire.
|  |\n| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- |\n\n## Recovery Mode\n\nSometimes, when updating the firmware on a SPARK MAX, it is possible for the process to be interrupted or for the firmware to be corrupted by a bad download or other type of interruption in data transfer. In this state, the Status LED will be dark or dim and the device will fail to operate. There is a built-in recovery mode that can force your device to accept new firmware even if the controller seems to be bricked and the procedure is outlined below:\n\nPlease note, performing this procedure will erase all data and settings on the device. To perform the procedure a small tool, like a straightened paper clip, is necessary to press the Mode Button (located to the right of the Status LED), the orange USB-C cable that came with the unit (or a DATA capable USB-C cable), and a native Windows based computer with the [REV Hardware Client](https://app.gitbook.com/o/-M3qDjqMSqBbHMXNmC_O/s/-MGEfA6CxjaSQiH5kHxn/) installed:\n\n1. With the SPARK MAX disconnected from power, press and hold the Mode Button\n2. While still holding the Mode Button, connect the Device to the computer using the USB-C cable - the Status LED will not illuminate - this is expected.\n3. With the REV Hardware Client running on the computer, wait a few seconds for the audible tone or icon for the device to be recognized in recovery mode then release the Mode Button - no lights will be present on the SPARK MAX during this stage of the process, this is expected\n4. Select the SPARK MAX in Recovery Mode from the REV Hardware Client window\n5. From the \"Choose a Device\" type dropdown, choose - SPARK MAX\n6. Choose the latest version of the firmware from the dropdown and then click update\n7. Wait for the software update to complete\n8. Power cycle unit (unplug and plug in USB-C) click on SPARK MAX icon, clear any sticky faults - the recovery should be complete!\n",
- "content_preview": "# SPARK MAX Troubleshooting\n\nMany issues can be solved by systematic troubleshooting without needing to contact REV Support. Take a look at the troubleshooting tips below for help in determining the cause of the issue you are seeing."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-max/operating-modes",
- "title": "SPARK MAX Operating Modes",
- "section": "SPARK MAX",
- "language": "All",
- "content": "# SPARK MAX Operating Modes\n\n## Brushed/Brushless Mode - Motor Type\n\nBrushed and brushless DC motors require different motor control schemes based on the differences in their technology. It is possible to damage the SPARK MAX, the motor, or both if the appropriate motor type isn't configured properly. \n\nBrushed or brushless motor types can be configured using the Mode Button, CAN, and USB interfaces.\n\n### Mode Button Configuration\n\nFollow the steps below to switch motor types with the Mode Button. It is recommended that the motor be left disconnected until the correct mode is selected.\n\n{% hint style=\"info\" %}\nUse a small screwdriver, straightened paper clip, pen, or other small implement to press the button. Do not use any type of pencil as the pencil lead can break off inside the SPARK MAX.\n{% endhint %}\n\n1. Connect the SPARK MAX to the main power, not just USB Power.\n2. The Status LED will indicate which motor type is configured by blinking yellow or blue for Brushed Mode or blinking magenta or cyan for Brushless Mode.\n3. Press and hold the Mode Button for approximately 3 seconds.\n4. After the button has been held for enough time, the Status LED will change and indicate the different motor configuration.\n5. Release the mode button.\n\n{% hint style=\"info\" %}\nPlease see the [Status LED Patterns](https://docs.revrobotics.com/brushless/spark-max/status-led) guide for information on how to identify the Motor Type configuration by the color of the Status LED!\n{% endhint %}\n\n### USB Configuration\n\nFollow the steps below to switch motor types with the USB and the REV Hardware Client application. Be sure to [download and install the REV Hardware Client](https://app.gitbook.com/s/-MGEfA6CxjaSQiH5kHxn/gs/install) application before continuing.\n\n1. Connect the SPARK MAX to your computer using a USB-C cable.\n2. Open the REV Hardware Client and verify that the application is connected to your SPARK MAX.\n3. On the **Basic** tab, select the appropriate motor type under the **Select Motor Type** menu.\n4. Click **Burn Flash** and confirm the change.\n\n### CAN Configuration\n\nPlease see the [API Information](https://app.gitbook.com/s/0OKYENVWAIgVP2TmkWl3/install) for information on how to configure the SPARK MAX using the CAN interface. \n\n## Brake/Coast Mode - Idle Behavior\n\nWhen the SPARK MAX is receiving a neutral command the idle behavior of the motor can be handled in two different ways: **Braking** or **Coasting**. \n\nWhen in **Brake Mode**, the SPARK MAX will effectively short all motor wires together. This quickly dissipates any electrical energy within the motor and brings it to a quick stop.\n\nWhen in **Coast Mode**, the SPARK MAX will effectively disconnect all motor wires. This allows the motor to spin down at its own rate.\n\nThe Idle Mode can be configured using the Mode Button, CAN, and USB interfaces.\n\n### Mode Button Configuration \n\nFollow the steps below to switch the Idle Mode between Brake and Coast with the Mode Button.\n\n{% hint style=\"info\" %}\nUse a small screwdriver, straightened paper clip, pen, or other small implement to press the button. Do not use any type of pencil as the pencil lead can break off inside the SPARK MAX.\n{% endhint %}\n\n1. Connect the SPARK MAX to main power, not just USB Power.\n2. The Status LED will indicate which Idle Mode is currently configured by blinking blue or cyan for Brake and yellow or magenta for Coast depending on the motor type.\n3. Press and release the Mode Button\n4. You should see the Status LED change to indicate the selected Idle Mode.\n\n{% hint style=\"info\" %}\nPlease see the [Status LED Patterns](https://docs.revrobotics.com/brushless/spark-max/status-led) guide for information on how to identify the Idle Behavior configuration by the color of the Status LED!\n{% endhint %}\n\n### USB Configuration\n\nFollow the steps below to switch the Idle Mode between Brake and Coast with the USB and the REV Hardware Client application. Be sure to [download and install the REV Hardware Client](https://app.gitbook.com/s/-MGEfA6CxjaSQiH5kHxn/home/rev-hardware-client-overview) application before continuing.\n\n1. Connect the SPARK MAX to your computer using a USB-C cable.\n2. Open the REV Hardware Client application and verify that the application is connected to your SPARK MAX.\n3. On the **Basic** tab, select the desired mode with the **Idle Mode** switch.\n4. Click **Burn Flash** and confirm the change.\n\n### CAN Configuration\n\nPlease see the [API Information](https://app.gitbook.com/s/0OKYENVWAIgVP2TmkWl3/install) for information on how to configure the SPARK MAX using the CAN interface. \n",
- "content_preview": "# SPARK MAX Operating Modes\n\n## Brushed/Brushless Mode - Motor Type\n\nBrushed and brushless DC motors require different motor control schemes based on the differences in their technology."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-max/control-interfaces",
- "title": "SPARK MAX Control Interfaces",
- "section": "SPARK MAX",
- "language": "All",
- "content": "# SPARK MAX Control Interfaces\n\nThe SPARK MAX can be controlled by three different interfaces, servo-style PWM, controller area network (CAN), and USB. The following sections describe the operation and protocols of these interfaces. For more details on the physical connections, see [Control Connections](https://docs.revrobotics.com/brushless/spark-max/specs/control-connections).\n\n## PWM Interface\n\nThe SPARK MAX can accept a standard servo-style PWM signal as a control for the output duty cycle. Even though the PWM port is shared with the CAN port, SPARK MAX will automatically detect the incoming signal type and respond accordingly. For details on how to connect a PWM cable to the SPARK MAX, see [CAN/PWM Port](https://docs.revrobotics.com/brushless/specs/control-connections#can-pwm-port).\n\nThe SPARK MAX responds to a factory default pulse range of 1000µs to 2000µs. These pulses correspond to full-reverse and full-forward rotation, respectively, with 1500µs (±5% default input deadband) as the neutral position, i.e. no rotation. The input deadband is configurable with the [REV Hardware Client](https://app.gitbook.com/o/-M3qDjqMSqBbHMXNmC_O/s/-MGEfA6CxjaSQiH5kHxn/) or the CAN interface. The table below describes how the default pulse range maps to the output behavior.\n\n#### PWM Pulse Mapping\n\n.svg)\n\n{% hint style=\"warning\" %}\nIf a valid signal isn't received within a 60ms window, the SPARK MAX will disable the motor output and either brake or coast the motor depending on the configured Idle Mode. For details on the Idle Mode, see [Idle Mode - Brake/Coast Mode](https://docs.revrobotics.com/brushless/spark-max/operating-modes).\n{% endhint %}\n\n## CAN Interface\n\nThe SPARK MAX can be connected to a robot CAN network. CAN is a bi-directional communications bus that enables advanced features within the SPARK MAX. SPARK MAX must be connected to a CAN network that has the appropriate termination resistors at both endpoints. Please see the FIRST Robotics Competition Robot Rules for the CAN bus wiring requirements. Even though the CAN port is shared with the PWM port, SPARK MAX will automatically detect the incoming signal type and respond accordingly. SPARK MAX uses standard CAN frames with an extended ID (29 bits), and utilizes the FRC CAN protocol for defining the bits of the extended ID:\n\n#### CAN Packet Structure\n\n| **ExtID \\[28:24]** | **ExtID \\[23:16]** | **ExtID \\[15:10]** | **ExtID \\[9:6]** | **ExtID \\[5:0]** |\n| ------------------ | ------------------ | ------------------ | ---------------- | ---------------- |\n| Device Type | Manufacturer | API Class | API Index | Device ID |\n\nEach device on the CAN bus must be assigned a unique CAN ID number. Out of the box, SPARK MAX is assigned a device ID of 0. It is highly recommended to change all SPARK MAX CAN IDs from 0 to any unused ID from 1 to 62. CAN IDs can be changed by connecting the SPARK MAX to a Windows computer and using the [REV Hardware Client](https://app.gitbook.com/s/-MGEfA6CxjaSQiH5kHxn/home/rev-hardware-client-overview). For details on other SPARK MAX configuration parameters, see [Configuration Parameters](https://docs.revrobotics.com/brushless/spark-max/parameters).[ ](https://github.com/REVrobotics/SPARK-MAX-Documentation/blob/master/operating-modes/broken-reference/README.md)\n\nAdditional information about the CAN accessible features and how to access them can be found in the [SPARK MAX API Information](https://app.gitbook.com/s/0OKYENVWAIgVP2TmkWl3/install) section.\n\n### **Periodic Status Frames**\n\nThe SPARK MAX sends data periodically back to the roboRIO. Frequently accessed data, like motor position and temperature, can be accessed using several APIs. Data is broken up into several CAN \"frames\" which are sent at a periodic rate. This rate can be changed manually in code, but unlike other parameters, this setting **does not persist** through a power cycle. The rate can be set anywhere from a minimum 1ms to a maximum 32767ms period. The table below describes each status frame and its available data.\n\n#### Periodic Status 0 - Default Rate: 10ms\n\n| **Available Data** | **Description** |\n| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Applied \\*\\*\\*\\* Output | The actual value sent to the motors from the motor controller. The frame stores this value as a 16-bit signed integer, and is converted to a floating point value between -1 and 1 by the roboRIO SDK. This value is also used by any follower controllers to set their output. |\n| Faults | Each bit represents a different fault on the controller. These fault bits clear automatically when the fault goes away. |\n| Sticky Faults | The same as the Faults field, however the bits do not reset until a power cycle or a 'Clear Faults' command is sent. |\n| Is Follower | A single bit that is true if the controller is configured to follow another controller. |\n\n#### Periodic Status 1 - Default Rate: 20ms\n\n| **Available Data** | **Description** |\n| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Motor Velocity | 32-bit IEEE floating-point representation of the motor velocity in RPM using the selected sensor. |\n| Motor Temperature | 8-bit unsigned value representing:
Firmware version 1.0.381 - Voltage of the temperature sensor with 0 = 0V and 255 = 3.3V. Current firmware versions - Motor temperature in °C for the NEO Brushless Motor.
|\n| Motor Voltage | 12-bit fixed-point value that is converted to a floating point voltage value (in Volts) by the roboRIO SDK. This is the input voltage to the controller. |\n| Motor Current | 12-bit fixed-point value that is converted to a floating point current value (in Amps) by the roboRIO SDK. This is the raw phase current of the motor. |\n\n#### Periodic Status 2 - Default Rate: 20ms\n\n| **Available Data** | **Description** |\n| ------------------ | ----------------------------------------------------------------------------- |\n| Motor Position | 32-bit IEEE floating-point representation of the motor position in rotations. |\n\n#### Periodic Status 3 - Default Rate: 50ms\n\n| **Available Data** | **Description** |\n| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| Analog Sensor Voltage | 10-bit fixed-point value that is converted to a floating point voltage value (in Volts) by the roboRIO SDK. This is the voltage being output by the analog sensor. |\n| Analog Sensor Velocity | 22-bit fixed-point value that is converted to a floating point voltage value (in RPM) by the roboRIO SDK. This is the velocity reported by the analog sensor. |\n| Analog Sensor Position | 32-bit IEEE floating-point representation of the velocity in RPM reported by the analog sensor. |\n\n#### Periodic Status 4 - Default Rate: 20ms\n\n| **Available Data** | **Description** |\n| -------------------------- | ------------------------------------------------------------------------------------------------ |\n| Alternate Encoder Velocity | 32-bit IEEE floating-point representation of the velocity in RPM of the alternate encoder. |\n| Alternate Encoder Position | 32-bit IEEE floating-point representation of the position in rotations of the alternate encoder. |\n\n#### Periodic Status 5 - Default Rate: 200ms\n\n| **Available Data** | **Description** |\n| ------------------------------------------ | --------------------------------------------------------------------------------------------- |\n| Duty Cycle Absolute Encoder Position | 32-bit IEEE floating-point representation of the position of the duty cycle absolute encoder. |\n| Duty Cycle Absolute Encoder Absolute Angle | 16-bit integer representation of the absolute angle of the duty cycle absolute encoder. |\n\n#### Periodic Status 6 - Default Rate: 200ms\n\n| **Available Data** | **Description** |\n| ------------------------------------- | ----------------------------------------------------------------------------------------------------- |\n| Duty Cycle Absolute Encoder Velocity | 32-bit IEEE floating-point representation of the velocity in RPM of the duty cycle absolute encoder. |\n| Duty Cycle Absolute Encoder Frequency | 16-bit unsigned integer representation of the frequency at which the duty cycle signal is being sent. |\n\n### **Use-case Examples**\n\n#### **Position Control on the roboRIO**\n\nA user wants to implement their own PID loop on the roboRIO to hold a position. They want to run this loop at 100Hz (every 10ms), but the motor position data in Periodic Status 2 is sent at 20Hz (every 50ms).\n\nThe user can change this rate to 10ms by calling:\n\n| *Pseudocode* |\n| --------------------------------------------------- |\n| `setPeriodicFrameRate(PeriodicFrame.kStatus2, 10);` |\n\n#### **High CAN Utilization**\n\nA user has many connected CAN devices and wishes to minimize the CAN bus utilization. They do not need any telemetry feedback, and have several follower devices that are only checked for faults.\n\nThe user can set the telemetry frame rates low, and set the Periodic Status 0 frame rate low on the follower devices:\n\n| *Pseudocode* |\n| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `leader.setPeriodicFrameRate(PeriodicFrame.kStatus1, 500); leader.setPeriodicFrameRate(PeriodicFrame.kStatus2, 500); follower.setPeriodicFrameRate(PeriodicFrame.kStatus0, 100); follower.setPeriodicFrameRate(PeriodicFrame.kStatus1, 500); follower.setPeriodicFrameRate(PeriodicFrame.kStatus2, 500);` |\n\n#### **Faster Follower Bandwidth**\n\nThe user wants the follower devices to update at a faster rate: 200Hz (every 5ms).\n\nThe Periodic Status 0 frame can be increased to achieve this.\n\n| *Pseudocode* |\n| --------------------------------------------------------- |\n| `leader.setPeriodicFrameRate(PeriodicFrame.kStatus0, 5);` |\n\n## USB Interface\n\nThe SPARK MAX can be configured and controlled through a USB connection to a computer running the [REV Hardware Client](https://app.gitbook.com/s/-MGEfA6CxjaSQiH5kHxn/home/rev-hardware-client-overview). The USB interface utilizes a standard CDC (USB to Serial) driver. The command interface is similar to CAN, using the same ID and data structure, but always sends and receives a full 12-byte packet. The CAN ID is omitted (DNC) when talking directly to the device. However, the three MSB of the ID allow selection of alternate commands:\n\n* 0b000 - Standard command - CAN ID omitted (DNC)\n* 0b001 - Extended command - USB specific\n\nAll commands sent over USB receive a response. In the case that the corresponding CAN command does not receive a response, the USB interface receives an Ack command.\n\n#### USB Packet Structure\n\n| **ExtID \\[31:29]** | **ExtID \\[28:24]** | **ExtID \\[23:16]** | **ExtID \\[15:10]** | **ExtID \\[9:6]** | **ExtID \\[5:0]** |\n| ------------------ | ------------------ | ------------------- | ------------------ | ---------------- | ---------------- |\n| USB Command Type | Device Type (2) | Manufacturer (0x15) | API Class | API Index | Device ID |\n\n#### USB Non-Standard Commands\n\n| **Command** | **API Class** | **API Index** |\n| ------------------------------------------------------------------- | ------------- | ------------- |\n| Enter DFU Bootloader (will also disconnect USB interface)
| 0 | 1 |\n",
- "content_preview": "# SPARK MAX Control Interfaces\n\nThe SPARK MAX can be controlled by three different interfaces, servo-style PWM, controller area network (CAN), and USB. The following sections describe the operation and protocols of these interfaces."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-max/parameters",
- "title": "SPARK MAX Configuration Parameters",
- "section": "SPARK MAX",
- "language": "All",
- "content": "# SPARK MAX Configuration Parameters\n\nBelow is a list of all the configurable parameters within the SPARK MAX. Parameters can be set through the CAN or USB interfaces. The parameters are saved in a different region of memory from the device firmware and persist through a firmware update. \n\nName ID Type Default Description Name ID Type Default Description kCanID 0 uint 0 CAN ID This parameter persists through a normal firmware update. kInputMode 1 Input Mode 0 Input mode, this parameter is read only and the input mode is detected by the firmware automatically. 0 - PWM 1 - CAN 2 - USB kMotorType 2 Motor Type BRUSHLESS Motor type: 0 - Brushed 1 - Brushless This parameter persists through a normal firmware update. Reserved 3 - Reserved kSensorType 4 Sensor Type HALL_EFFECT Sensor type: 0 - No Sensor 1 - Hall Sensor 2 - Encoder This parameter persists through a normal firmware update. kCtrlType 5 Ctrl Type CTRL_DUTY_CYCLE Control Type, this is a read only parameter of the currently active control type. The control type is changed by calling the correct API. 0 - Duty Cycle 1 - Velocity 2 - Voltage 3 - Position kIdleMode 6 Idle Mode IDLE_COAST State of the half bridge when the motor controller commands zero output or is disabled. 0 - Coast 1 - Brake This parameter persists through a normal firmware update. kInputDeadband 7 float32 %0.05 Percent of the input which results in zero output for PWM mode. This parameter persists through a normal firmware update. Reserved 8 - - Reserved Reserved 9 - - Reserved kPolePairs 10 uint 7 Number of pole pairs for the brushless motor. This is the number of poles/2 and can be determined by either counting the number of magnets or counting the number of windings and dividing by 3. This is an important term for speed regulation to properly calculate the speed. kCurrentChop 11 float32 115/Amps If the half bridge detects this current limit, it will disable the motor driver for a fixed amount of time set by kCurrentChopCycles. This is a low sophistication 'current control'. Set to 0 to disable. The max value is 125. kCurrentChopCycles 12 uint 0 Number of PWM Cycles for the h-bridge to be off in the case that the current limit is set. Min = 1, multiples of PWM period (50μs). During this time the current will be recirculating through the low side MOSFETs, so instead of 'freewheeling' the diodes, the bridge will be in brake mode during this time. kP_0 13 float32 0 Proportional gain constant for gain slot 0. kI_0 14 float32 0 Integral gain constant for gain slot 0. kD_0 15 float32 0 Derivative gain constant for gain slot 0. kF_0 16 float32 0 Feed Forward gain constant for gain slot 0. kIZone_0 17 float32 0 Integrator zone constant for gain slot 0. The PIDF loop integrator will only accumulate while the setpoint is within IZone of the target. kDFilter_0 18 float32 0 PIDF derivative filter constant for gain slot 0. kOutputMin_0 19 float32 -1 Max output constant for gain slot 0. This is the max output of the controller. kOutputMax_0 20 float32 1 Min output constant for gain slot 0. This is the min output of the controller. kP_1 21 float32 0 Proportional gain constant for gain slot 1. kI_1 22 float32 0 Integral gain constant for gain slot 1. kD_1 23 float32 0 Derivative gain constant for gain slot 1. kF_1 24 float32 0 Feed Forward gain constant for gain slot 1. kIZone_1 25 float32 0 Integrator zone constant for gain slot 1. The PIDF loop integrator will only accumulate while the setpoint is within IZone of the target. kDFilter_1 26 float32 0 PIDF derivative filter constant for gain slot 1. kOutputMin_1 27 float32 -1 Max output constant for gain slot 1. This is the max output of the controller. kOutputMax_1 28 float32 1 Min output constant for gain slot 1. This is the min output of the controller. kP_2 29 float32 0 Proportional gain constant for gain slot 2. kI_2 30 float32 0 Integral gain constant for gain slot 2. kD_2 31 float32 0 Derivative gain constant for gain slot 2. kF_2 32 float32 0 Feed Forward gain constant for gain slot 2. kIZone_2 33 float32 0 Integrator zone constant for gain slot 2. The PIDF loop integrator will only accumulate while the setpoint is within IZone of the target. kDFilter_2 34 float32 0 PIDF derivative filter constant for gain slot 2. kOutputMin_2 35 float32 -1 Max output constant for gain slot 2. This is the max output of the controller. kOutputMax_2 36 float32 1 Min output constant for gain slot 2. This is the min output of the controller. kP_3 37 float32 0 Proportional gain constant for gain slot 3. kI_3 38 float32 0 Integral gain constant for gain slot 3. kD_3 39 float32 0 Derivative gain constant for gain slot 3. kF_3 40 float32 0 Feed Forward gain constant for gain slot 3. kIZone_3 41 float32 0 Integrator zone constant for gain slot 3. The PIDF loop integrator will only accumulate while the setpoint is within IZone of the target. kDFilter_3 42 float32 0 PIDF derivative filter constant for gain slot 3. kOutputMin_3 43 float32 -1 Max output constant for gain slot 3. This is the max output of the controller. kOutputMax_3 44 float32 1 Min output constant for gain slot 3. This is the min output of the controller. Reserved 45 - - Reserved Reserved 46 - - Reserved Reserved 47 - - Reserved Reserved 48 - - Reserved Reserved 49 - - Reserved kLimitSwitchFwdPolarity 50 bool 0 Forward Limit Switch polarity. 0 - Normally Open 1 - Normally Closed kLimitSwitchRevPolarity 51 bool 0 Reverse Limit Switch polarity. 0 - Normally Open 1 - Normally Closed kHardLimitFwdEn 52 bool 1 Limit switch enable, enabled by default kHardLimitRevEn 53 bool 1 Limit switch enable, enabled by default Reserved 54 - - Reserved Reserved 55 - - Reserved kRampRate 56 float32 V/s 0 Voltage ramp rate active for all control modes in % output per second, a value of 0 disables this feature. All APIs take the reciprocal to make the unit 'time from 0 to full'. kFollowerID 57 uint 0 CAN EXTID of the message with data to follow kFollowerConfig 58 uint 0 Special configuration register for setting up to follow on a repeating message (follower mode). CFG[0] to CFG[3] where CFG[0] is the motor output start bit (LSB), CFG[1] is the motor output stop bit (MSB). CFG[0] - CFG[1] determines endianness. CFG[2] bits determine sign mode and inverted, CFG[3] sets a preconfigured controller (0x1A = REV, 0x1B = Talon/Victor style as of 2018 season) kSmartCurrentStallLimit 59 uint 80A Smart Current Limit at stall, or any RPM less than kSmartCurrentConfig RPM. kSmartCurrentFreeLimit 60 uint 20A Smart current limit at free speed kSmartCurrentConfig 61 uint 10000 Smart current limit RPM value to start linear reduction of current limit. Set this > free speed to disable. Reserved 62 - - Reserved Reserved 63 - - Reserved Reserved 64 - - Reserved Reserved 65 - - Reserved Reserved 66 - - Reserved Reserved 67 - - Reserved Reserved 68 - - Reserved kEncoderCountsPerRev 69 uint 4096 Number of encoder counts in a single revolution, counting every edge on the A and B lines of a quadrature encoder. (Note: This is different than the CPR spec of the encoder which is 'Cycles per revolution'. This value is 4 * CPR. kEncoderAverageDepth 70 uint 64 Number of samples to average for velocity data based on quadrature encoder input. This value can be between 1 and 64. kEncoderSampleDelta 71 uint 200 per 500us Delta time value for encoder velocity measurement in 500μs increments. The velocity calculation will take delta the current sample, and the sample x * 500μs behind, and divide by this the sample delta time. Can be any number between 1 and 255 Reserved 72 - - Reserved Reserved 73 - - Reserved Reserved 74 - - Reserved kCompensatedNominalVoltage 75 float32 0 V In voltage compensation mode mode, this is the max scaled voltage. kSmartMotionMaxVelocity_0 76 float32 0 kSmartMotionMaxAccel_0 77 float32 0 kSmartMotionMinVelOutput_0 78 float32 0 kSmartMotionAllowedClosedLoopError_0 79 float32 0 kSmartMotionAccelStrategy_0 80 float32 0 kSmartMotionMaxVelocity_1 81 float32 0 kSmartMotionMaxAccel_1 82 float32 0 kSmartMotionMinVelOutput_1 83 float32 0 kSmartMotionAllowedClosedLoopError_1 84 float32 0 kSmartMotionAccelStrategy_1 85 float32 0 kSmartMotionMaxVelocity_2 86 float32 0 kSmartMotionMaxAccel_2 87 float32 0 kSmartMotionMinVelOutput_2 88 float32 0 kSmartMotionAllowedClosedLoopError_2 89 float32 0 kSmartMotionAccelStrategy_2 90 float32 0 kSmartMotionMaxVelocity_3 91 float32 0 kSmartMotionMaxAccel_3 92 float32 0 kSmartMotionMinVelOutput_3 93 float32 0 kSmartMotionAllowedClosedLoopError_3 94 float32 0 kSmartMotionAccelStrategy_3 95 float32 0 kIMaxAccum_0 96 float32 0 kSlot3Placeholder1_0 97 float32 0 kSlot3Placeholder2_0 98 float32 0 kSlot3Placeholder3_0 99 float32 0 kIMaxAccum_1 100 float32 0 kSlot3Placeholder1_1 101 float32 0 kSlot3Placeholder2_1 102 float32 0 kSlot3Placeholder3_1 103 float32 0 kIMaxAccum_2 104 float32 0 kSlot3Placeholder1_2 105 float32 0 kSlot3Placeholder2_2 106 float32 0 kSlot3Placeholder3_2 107 float32 0 kIMaxAccum_3 108 float32 0 kSlot3Placeholder1_3 109 float32 0 kSlot3Placeholder2_3 110 float32 0 kSlot3Placeholder3_3 111 float32 0 kPositionConversionFactor 112 float32 1 kVelocityConversionFactor 113 float32 1 kClosedLoopRampRate 114 float32 0 DC/sec kSoftLimitFwd 115 float32 0 Soft limit forward value kSoftLimitRev 116 float32 0 Soft limit reverse value Reserved 117 - - Reserved Reserved 118 - - Reserved kAnalogPositionConversion 119 float32 1 rev/volt Conversion factor for position from analog sensor. This value is multiplied by the voltage to give an output value. kAnalogVelocityConversion 120 float32 1 vel/v/s Conversion factor for velocity from analog sensor. This value is multiplied by the voltage to give an output value. kAnalogAverageDepth 121 uint 0 Number of samples in moving average of velocity. kAnalogSensorMode 122 uint 0 0 Absolute: In this mode the sensor position is always read as voltage * conversion factor and reads the absolute position of the sensor. In this mode setPosition() does not have an effect. 1 Relative: In this mode the voltage difference is summed to calculate a relative position. kAnalogInverted 123 bool 0 When inverted, the voltage is calculated as (ADC Full Scale - ADC Reading). This means that for absolute mode, the sensor value is 3.3V - voltage. In relative mode the direction is reversed. kAnalogSampleDelta 124 uint 0 Delta time between samples for velocity measurement Reserved 125 - - Reserved Reserved 126 - - Reserved kDataPortConfig 127 uint 0 0: Default configuration using limit switches 1: Alternate Encoder Mode - limit switches are disabled and alternate encoder is enabled. This parameter persists through a normal firmware update. kAltEncoderCountsPerRev 128 uint 4096 Number of encoder counts in a single revolution, counting every edge on the A and B lines of a quadrature encoder. (Note: This is different than the CPR spec of the encoder which is 'Cycles per revolution'. This value is 4 * CPR. kAltEncoderAverageDepth 129 uint 64 Number of samples to average for velocity data based on quadrature encoder input. This value can be between 1 and 64. kAltEncoderSampleDelta 130 uint 200 Delta time value for encoder velocity measurement in 500μs increments. The velocity calculation will take delta the current sample, and the sample x * 500μs behind, and divide by this the sample delta time. Can be any number between 1 and 255. kAltEncoderInverted 131 bool 0 Invert the phase of the encoder sensor. This is useful when the motor direction is opposite of the motor direction. kAltEncoderPositionFactor 132 float32 1 Value multiplied by the native units (rotations) of the encoder for position. kAltEncoderVelocityFactor 133 float32 1 Value multiplied by the native units (rotations) of the encoder for velocity.
\n",
- "content_preview": "# SPARK MAX Configuration Parameters\n\nBelow is a list of all the configurable parameters within the SPARK MAX. Parameters can be set through the CAN or USB interfaces."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-max/encoders",
- "title": "Using Encoders with the SPARK MAX",
- "section": "SPARK MAX",
- "language": "All",
- "content": "# Using Encoders with the SPARK MAX\n\nThe SPARK MAX can accept data from encoders through both the Encoder Port and the Data Port on the top of the motor controller. Encoders have a different method of connecting to the SPARK MAX that depends on what motor you are using and what type of encoder it is. When preparing your encoders, be sure to set up your SPARK MAX correctly for the type of devices you are using. \n\n## Incremental vs Absolute Encoders\n\nIncremental encoders measure a change in position as a mechanism rotates while absolute encoders will report an exact position at any time, including at startup. A common analogy to help with knowing the difference is that incremental encoders are like a stopwatch that measures a change in time and absolute encoders are like a clock where you can know exactly what time it is. \n\nWith SPARK MAX Firmware Versions 1.6.0 and newer, absolute encoders are compatible with the SPARK MAX Data Port.\n\n## How to Connect an Encoder\n\nTo connect an encoder to your SPARK MAX, start by identifying what type of encoder you are using. Below is a flowchart to help generally identify the method you should use.\n\n \n\n{% hint style=\"warning\" %}\nRunning a brushless motor, like the NEO and NEO 550, without the integrated encoder plugged into the SPARK MAX's Encoder Port can damage your motor. \n{% endhint %}\n\n### Absolute Encoders\n\nAbsolute encoders will work with the default pinout of the SPARK MAX Data Port if the latest firmware has been installed. When using an absolute or duty cycle encoder it is recommended to use one of the following methods to connect your encoder to the SPARK MAX.\n\n{% hint style=\"info\" %}\n*Check out our documentation of the* [*Through Bore Encoder.*](https://app.gitbook.com/s/-ME3KPEhFI6-MDoP9nZD/sensors/tbe/v1)\n{% endhint %}\n\n* [Through Bore Encoder (REV-11-1271)](https://www.revrobotics.com/rev-11-1271/): Connect with [Absolute Encoder Adapter (REV-11-3326)](https://www.revrobotics.com/rev-11-3326/)\n* Other Absolute Encoder: Connect with [SPARK MAX Data Port Breakout Board (REV-11-1278)](https://www.revrobotics.com/rev-11-1278/)\n * Use the Data Port Pinout to match the signals from your encoder\n\n{% hint style=\"info\" %}\nAbsolute Encoders are only supported through the SPARK MAX Data Port at this time\n{% endhint %}\n\n#### Absolute Encoder Wiring Examples\n\n{% tabs %}\n{% tab title=\"Absolute Encoder Adapter\" %}\nEasily connect a Through Bore Encoder to your SPARK MAX with an Absolute Encoder Adapter and a 6-Pin JST PH Cable.\n\n \n{% endtab %}\n\n{% tab title=\"Data Port Breakout Board\" %}\nFor encoders that need a custom wiring harness, you can use the following solder pads on a SPARK MAX Data Port Breakout Board with any generic absolute encoder. \n\n \n{% endtab %}\n{% endtabs %}\n\n## Incremental Encoders\n\nIf you are using an Incremental Encoder with a **brushed motor** you can plug your encoder into the SPARK MAX's front Encoder Port directly. If you are driving a NEO or NEO 550 **brushless motor** with your SPARK MAX, you will need to configure Alternate Encoder Mode to accommodate the additional encoder input from the Data Port. When using an incremental or quadrature encoder it is recommended to use one of the following methods to connect your encoder to the SPARK MAX.\n\n{% hint style=\"info\" %}\n*Check out our documentation of the* [*Through Bore Encoder.*](https://app.gitbook.com/s/-ME3KPEhFI6-MDoP9nZD/sensors/tbe/v1)\n{% endhint %}\n\n* [NEO](https://www.revrobotics.com/rev-21-1650/) or [NEO 550](https://www.revrobotics.com/rev-21-1651/) Internal Encoder: **MUST** be connected to the SPARK MAX Encoder Port\n* [Through Bore Encoder (REV-11-1271)](https://www.revrobotics.com/rev-11-1271/) \n * With brushless motor: Connect with [Alternate Encoder Adapter (REV-11-1881)](https://www.revrobotics.com/rev-11-1881/). **You will need to configure Alternate Encoder Mode.**\n * With brushed motor: Connect to the Encoder Port\n* Other Incremental Encoder \n * With a brushless motor: Connect the encoder with [SPARK MAX Data Port Breakout Board (REV-11-1278)](https://www.revrobotics.com/rev-11-1278/). Use the Alternate Encoder Mode Data Port Pinout to match the signals from your encoder. **You will need to configure Alternate Encoder Mode.**\n * With brushed motor: Connect to the Encoder Port with a [6-Pin JST Breakout Board (REV-11-1276)](https://www.revrobotics.com/rev-11-1276/) using the Encoder Port pinout\n * With a brushed motor: Connect the encoder with [SPARK MAX Data Port Breakout Board (REV-11-1278)](https://www.revrobotics.com/rev-11-1278/) or a similar custom cable. Use the Data Port Pinout to match the signals from your encoder. \n\n#### Incremental Encoder Wiring Examples\n\n{% tabs %}\n{% tab title=\"Alternate Encoder Adapter\" %}\nEasily connect a Through Bore Encoder to your SPARK MAX with an Alternate Encoder Adapter and a 6-Pin JST PH Cable. When using the Alternate Encoder Adapter make sure the switch is set to \"index\".\n\n \n{% endtab %}\n\n{% tab title=\"Brushed Motor\" %}\nA Through Bore Encoder, or any incremental encoder with the same pinout, can be plugged in directly to the SPARK MAX's Encoder Port. \n\n \n{% endtab %}\n\n{% tab title=\"Data Port Breakout Board\" %}\nFor encoders that need a custom wiring harness, you can use the following solder pads on a SPARK MAX Data Port Breakout Board with any generic incremental encoder. \n\n \n{% endtab %}\n{% endtabs %}\n\n## Receiving both Incremental and Absolute Encoder Feedback\n\nReceiving both Incremental and Absolute encoder feedback from a single encoder through the SPARK MAX directly is not currently supported. To do this you will need to wire the encoder directly to your roboRIO or robot controller. \n\n## Using Limit Switches with an Encoder\n\nCurrently, Limit Switch inputs are only supported when using an absolute encoder or an incremental encoder run through the SPARK MAX's Encoder port. **Please note, the limit switch inputs cannot be used at the same time as an Alternate Encoder Mode.** The limit switch pins are repurposed for the alternate encoder and are thus disabled. \n",
- "content_preview": "# Using Encoders with the SPARK MAX\n\nThe SPARK MAX can accept data from encoders through both the Encoder Port and the Data Port on the top of the motor controller."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-max/encoders/absolute",
- "title": "Absolute Encoders",
- "section": "SPARK MAX",
- "language": "All",
- "content": "# Absolute Encoders\n\nThe SPARK MAX does not need to be configured to a specific mode to accept input from an absolute encoder as long as the encoder is connected to the SPARK MAX Data Port. \n\n#### Absolute Encoder Specifications\n\n| Parameter | Specification |\n| ---------------------------- | ----------------- |\n| Encoder Output Voltage Level | 5.0V |\n| Encoder Type Supported | Duty Cycle or PWM |\n\n{% hint style=\"info\" %}\nAbsolute encoder input is supported by SPARK MAX Firmware Version 1.6.0 and newer, \n{% endhint %}\n\n## Connecting an Absolute Encoder\n\nConnecting an absolute encoder that is not a Through Bore Encoder will likely require a custom wiring harness or [SPARK MAX Data Port Breakout Board](https://www.revrobotics.com/rev-11-1278/) to connect the necessary encoder power, ground, and signals to the SPARK MAX Data Port. When using an Absolute Encoder use the following pinout information for the Data Port:\n\n\n\n#### Data Port Connector Information\n\n| **Connector Pin** | **Pin Type** | **Pin Function** |\n| ----------------- | ------------ | -------------------------- |\n| 1 | Power | +3.3V |\n| 2 | Power | +5V |\n| 3 | Analog | Analog Input |\n| 4 | Digital | Forward Limit Switch Input |\n| 5 | Digital | Encoder B |\n| 6 | Digital | Absolute/PWM Input |\n| 7 | Digital | Encoder A |\n| 8 | Digital | Reverse Limit Switch Input |\n| 9 | Digital | Encoder C / Index |\n| 10 | Ground | Ground |\n",
- "content_preview": "# Absolute Encoders\n\nThe SPARK MAX does not need to be configured to a specific mode to accept input from an absolute encoder as long as the encoder is connected to the SPARK MAX Data Port. \n\n#### Absolute Encoder Specifications\n\n| Parameter | Specification |\n|..."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-max/encoders/alternate-encoder",
- "title": "Alternate Encoder Mode",
- "section": "SPARK MAX",
- "language": "Java",
- "content": "# Alternate Encoder Mode\n\nThe SPARK MAX can be configured to run in Alternate Encoder Mode, which reconfigures the Data Port on the top of the controller to accept an alternative quadrature encoder, separate from the default encoder inputs shared between the front Encoder Port and the default quadrature encoder Data Port pins. Analog input is not affected by Alternate Encoder Mode. \n\n{% hint style=\"info\" %}\n**This feature is designed for use in low-RPM mechanisms such as drivetrains, arms, and other manipulators.** For high RPM applications, it is recommended to use the built-in motor sensor for brushless motors or the default encoder inputs for brushed motors. \n{% endhint %}\n\n#### Alternate Encoder Specifications\n\n| Parameter | Specification |\n| ---------------------------- | ------------- |\n| Encoder Output Voltage Level | 3.3V or 5.0V |\n| Encoder Type Supported | Quadrature† |\n| Maximum Counts per Second | 165000 |\n\n| † | Index pulses are not currently supported |\n| - | ---------------------------------------- |\n\n{% hint style=\"danger\" %}\n **Before connecting a sensor with 5V output**, the SPARK MAX **must first be updated** to firmware version 1.5.0 or later, or **damage may occur**. This can be done through the REV Hardware Client.\n{% endhint %}\n\n#### Maximum RPM with Common Quadrature Encoders\n\n| **Encoder** | **Counts per Revolution** | **Max RPM** |\n| ------------------------------------------------------------------- | ------------------------- | ----------- |\n| [REV Through Bore Encoder](http://www.revrobotics.com/rev-11-1271/) | 8192 | 1200 |\n| CTRE SRX Mag Encoder | 4096 | 2400 |\n| Greyhill 63R256 | 1024 | 9600 |\n\n\\\nWhen configured for Alternate Encoder Mode, a quadrature encoder connected to the reconfigured Data Port pins can be used as a feedback device by the SPARK MAX. **Please note, the limit switch inputs cannot be used at the same time as an alternate encoder.** The limit switch pins are repurposed for the alternate encoder and are thus disabled. Please see [Connecting an Alternate Encoder](#connecting-an-alternate-encoder) for for more information.\n\n## Connecting an Alternate Encoder\n\nConnecting an alternate encoder will likely require a custom wiring harness to connect the necessary encoder power, ground, and signals to the reconfigured Data Port. When configured in Alternate Encoder Mode, the Data Port has the following pinout:\n\n\n\n#### Data Port Pinout in Alternate Encoder Mode\n\n| **Connector Pin** | **Pin Type** | **Pin Function** |\n| ----------------- | ------------ | ---------------------------- |\n| 1 | Power | +3.3V |\n| 2 | Power | +5V |\n| 3 | Analog | Analog Input |\n| 4 | Digital | **Alternate Encoder Index**† |\n| 5 | Digital | Encoder B |\n| 6 | Digital | **Alternate Encoder A** |\n| 7 | Digital | Encoder A |\n| 8 | Digital | **Alternate Encoder B** |\n| 9 | Digital | Encoder C / Index |\n| 10 | Ground | Ground |\n\n| | |\n| - | ----------------------------------------------------------------------- |\n| † | The Alternate Encoder Index pin is reserved but not currently supported |\n\nUse an Alternate Encoder Adapter ([REV-11-1881](https://www.revrobotics.com/rev-11-1881/)) to connect a [REV Through Bore Encoder](https://www.revrobotics.com/rev-11-1271/) directly to the SPARK MAX Data Port. This adapter has a JST PH 6-pin connector that is compatible with the Through Bore Encoder's pinout and a selection switch to change the signal that is connect to pin 4 of the data port.\n\n{% hint style=\"info\" %}\n*Check out our documentation of the* [*Through Bore Encoder.*](https://docs.revrobotics.com/rev-crossover-products/sensors/tbe)\n{% endhint %}\n\nAnother option is the [SPARK MAX Data Port Breakout Board](http://www.revrobotics.com/rev-11-1278/). This board can be used to wire an alternate encoder to the Data Port. The following table describes which pads on the breakout should be used for which signals coming from the alternate encoder. \n\n#### Alternate Encoder Pin-mapping for SPARK MAX Data Port Breakout Board\n\n| **Breakout Board Pad Label** | **Alternate Encoder Function** |\n| ---------------------------- | ------------------------------ |\n| Limit - F | Index† |\n| P6 (P5 in older batches) | A |\n| Limit - R | B |\n| 3.3V or 5.0V | Encoder Power |\n| GND | Encoder Ground |\n\n| | |\n| - | ----------------------------------------------------------------------- |\n| † | The Alternate Encoder Index pin is reserved but not currently supported |\n\n## Configuring and Using the Alternate Encoder Mode\n\nBelow you will find the steps required to set up and use the Alternate Encoder Mode on the SPARK MAX, starting with configuration through either the REV Hardware Client or the SPARK MAX APIs.\n\n### **Configuration Using the REV Hardware Client**\n\nUsing the REV Hardware Client, select your SPARK MAX, then navigate to the Advanced Tab and scroll to the Alternate Encoder parameter section. Enable the alternate encoder by setting the *kDataPortConfig* parameter to 'Alternate Encoder' via the drop down menu. You can also set the other Alternate Encoder parameters at this time.\n\n\n\n### **Configuring Using the SPARK MAX APIs**\n\nIf using the SPARK MAX APIs, the Alternate Encoder is automatically configured when the Alternate Encoder object is instantiated. An Alternate Encoder is created the same as a *CANEncoder*, either by directly using the constructor or calling *GetAlternateEncoder()* on a previously constructed *CANSparkMax*.\n\n```java\nstatic constexpr int kCanId = 1;\nstatic constexpr auto kMotorType = rev::CANSparkMax::MotorType::kBrushless;\nstatic constexpr auto kAltEncType = rev::CANEncoder::AlternateEncoderType::kQuadrature;\nstatic constexpr int kCPR = 8192;\n\n// initialize SPARK MAX with CAN ID\nrev::CANSparkMax m_motor{kCanID, kMotorType};\n\n/**\n* An alternate encoder object is constructed using the GetAlternateEncoder()\n* method on an existing CANSparkMax object. If using a REV Through Bore\n* Encoder, the type should be set to quadrature and the counts per\n* revolution set to 8192\n*/\nrev::CANEncoder m_alternateEncoder = m_motor.GetAlternateEncoder(kAltEncType, kCPR);\n```\n\nCurrently, quadrature is the only available type of configuration for an alternate encoder. This is differentiated from the other types of encoder configurations available for an encoder connected through the front facing Encoder Port on the SPARK MAX.\n\n### **Configuration Conflicts**\n\nSince the alternate encoder inputs and the default digital inputs are shared on the Data Port, the user cannot use both the alternate encoder and a digital inputs in code. Therefore, a **std::invalid\\_argument** (C++), **IllegalArgumentException** (Java), or an **Error on the Error Out terminal** (LabVIEW) will be thrown if a user tries to construct both types objects in code simultaneously.\n\n### **Closed-Loop Control**\n\nThe alternate encoder can be used with the different closed-loop control modes available on the SPARK MAX. The feedback device used by a *CANPIDController* must be set to use the alternate encoder through *SetFeedbackDevice().*\n\n```java\n/**\n* By default, the PID controller will use the Hall sensor from a NEO or NEO 550 for\n* its feedback device. Instead, we can set the feedback device to the alternate\n* encoder object\n*/\nm_pidController.SetFeedbackDevice(m_alternateEncoder);\n```\n\n### **Initial Bring-up**\n\nUnlike the built-in sensor on the NEO Brushless motors, the 'phase' of the alternate encoder is unknown to the SPARK MAX. Before enabling any closed-loop control, it is critical that the phase is configured correctly. To verify:\n\n1. Configure and connect the sensor as a quadrature alternate encoder, but **do not** run a closed-loop mode.\n2. Plot the output signal of the motor using *GetAppliedOutput()* and the output of the encoder using *altEncoder.GetVelocity()*. Confirm that the sensor is behaving as expected. This can be done on the SmartDashboard:\\\n `frc::SmartDashboard::PutNumber(\"Alt Encoder Velocity\", m_alternateEncoder.GetVelocity());`\\\n `frc::SmartDashboard::PutNumber(\"Applied Output\", m_motor.GetAppliedOutput());`\n3. Verify that the sign of the sensor is correct relative to the motor direction when driving it forward and backward. If it is not, the sensor must be inverted by calling *altEncoder.SetInverted(true).*\n",
- "content_preview": "# Alternate Encoder Mode\n\nThe SPARK MAX can be configured to run in Alternate Encoder Mode, which reconfigures the Data Port on the top of the controller to accept an alternative quadrature encoder, separate from the default encoder inputs shared between the front Encoder Port and the default..."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/spark-max/encoders/securing-adapters",
- "title": "Securing the Encoder Adapters",
- "section": "SPARK MAX",
- "language": "All",
- "content": "# Securing the Encoder Adapters\n\nThe Encoder Adapters and SPARK MAX Data Port Breakout Boards can be secured to a SPARK MAX in two ways.\n\n## SPARK MAX Mounting Bracket ([REV-45-2468](https://www.revrobotics.com/rev-45-2468-pk2/))\n\n| Description | Image |\n| ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |\n| 1) To use a SPARK MAX Mounting Bracket to secure your Encoder Adapter or Breakout Board you will need to remove the middle tab of plastic |  |\n| 2) Cut this piece of plastic with a pair of snips, or remove it by twisting the plastic until it breaks |  |\n| 3) Once the middle piece of plastic has been removed, remove any sharp edges with a file or some sandpaper |  |\n| 4) The SPARK MAX Mounting Bracket will fit over the board as shown in this image. Attach the mounting bracket to your surface as you normally would after this step |  |\n\n## Zip-tie Notches\n\nA zip-tie can be secured around the SPARK MAX's zip-tie notches and over the board to securely attach it to the motor controller as well. \n\n \n",
- "content_preview": "# Securing the Encoder Adapters\n\nThe Encoder Adapters and SPARK MAX Data Port Breakout Boards can be secured to a SPARK MAX in two ways.\n\n## SPARK MAX Mounting Bracket ([REV-45-2468](https://www.revrobotics.com/rev-45-2468-pk2/))\n\n| Description ..."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/tips/anderson-connectors",
- "title": "Anderson Powerpole Connectors",
- "section": "General",
- "language": "All",
- "content": "# Anderson Powerpole Connectors\n\nAnderson Powerpole connectors are a popular choice in the FIRST community for electrical connections. Ensuring that these connectors are crimped properly and the contact is fully inserted into the housing is key to having a good electrical connection. \n\nAnderson Powerpole Connectors consist of two major parts: the **housing** and **contact.** There are a number of different housings and contacts depending on the power requirements of the system. The most common housing and contact used with the SPARK MAX is the 1327 series housing paired with the 45 amp contacts.\n\n### Anderson Connector Housing\n\n\n\nThe housings for the connectors are genderless allowing all powerpole connectors to mate with themselves. The 1327 series housing can utilize contacts rated for 15-45 amps. Housings come in a variety of colors allowing for easy pairings with the wire color. \n\n\n\nDovetails on each housing allow them to slide together. Do not attempt to snap the housings together as they can break. After the housings are mated together adding a roll pin prevents the housings from become detached during operation. Each housing is fitted with a spring to retain the contacts after they are inserted\n\n### Anderson Contacts\n\n1327 series housings can use contacts rated for between 15 and 45 amps. The 45 amp contacts are used with the SPARK MAX. When striping wire for the contact, make sure the stripped wire is the length of the large flap. No wire should extend past the large flap into the gap between the large and small flaps on the contact. Utilize a proper crimping tool when crimping on the connector.\n\n\n\nHaving too much wire exposed can cause issues with proper placement of the contact in the housing. This can lead to bad connections. If the contacts are not fully inserted into the housing the contact connection issues can arise. The images above are examples of good crimps with the proper amount of wire inserted into the contact.\n\n\n\nHaving improper placement of the contact in the housing can lead to intermittent brownouts of the SPARK MAX, the contact dislodging from the housing, or have cause problems with one or more of the phases of a brushless motor.\n\nFor more information on powerpole assembly see the [Powerwerx ](https://powerwerx.com/help/powerpole-assembly-instructions)instructions.\n",
- "content_preview": "# Anderson Powerpole Connectors\n\nAnderson Powerpole connectors are a popular choice in the FIRST community for electrical connections. Ensuring that these connectors are crimped properly and the contact is fully inserted into the housing is key to having a good electrical..."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/legacy/og-spark",
- "title": "SPARK Motor Controller",
- "section": "General",
- "language": "All",
- "content": "# SPARK Motor Controller\n\n{% hint style=\"warning\" %}\nThe Original SPARK Motor Controller has been discontinued and will not be re-stocked.\n{% endhint %}\n\n## SPARK Overview\n\nThe SPARK Motor Controller (REV-11-1200) was a 12V 60A PWM-controlled brushed DC motor controller designed for *FIRST* Robotics Competition robots. It featured 60A continuous current with passive cooling, bi-directional limit switch inputs for smart mechanism control, an RGB LED status indicator, and a button-activated brake/coast mode.\n\n \n\n## Features\n\n* Passive cooling \n * No fans required \n* Synchronous rectification \n * Reduces heat generation \n* Limit switch inputs \n * Stops forward and/or reverse motion automatically \n * No programming required \n * Compatible with normally open (NO) limit switches \n* Calibration \n * Factory calibrated to 1ms – 2ms input signal\n * User calibratable \n* Integrated cable retention for PWM port \n* Clamping screw terminals \n * Better contact area and retention \n* RGB status LED \n * Detailed mode and operation feedback\n\n## Specifications\n\n| Parameter | Value & Units |\n| --------------------------------- | --------------------------- |\n| Input Voltage (Nominal) | 12 V |\n| Continuous Current | 60 A |\n| Peak Current (2 second surge) | 100 A |\n| Input Pulse Width Range (Nominal) | 1ms-2ms |\n| Input Resolution | 1μs |\n| Input Deadband | 40μs |\n| Output Frequency | 15.625 kHz |\n| Output Voltage Range | 0 V - ±Vin |\n| Maximum Output Voltage Resolution | 0.001 x Vin |\n| Dimensions | 2.860in x 1.875in x 0.868in |\n| Weight | 74g or 2.61oz |\n\n## Additional Documentation\n\n### [SPARK Motor Controller Quick Start Guide](https://www.revrobotics.com/content/docs/REV-11-1200-QS.pdf)\n\n### [SPARK Motor Controller User's Manual](https://www.revrobotics.com/content/docs/REV-11-1200-UM.pdf)\n",
- "content_preview": "# SPARK Motor Controller\n\n{% hint style=\"warning\" %}\nThe Original SPARK Motor Controller has been discontinued and will not be re-stocked.\n{% endhint %}\n\n## SPARK Overview\n\nThe SPARK Motor Controller (REV-11-1200) was a 12V 60A PWM-controlled brushed DC motor controller designed for *FIRST*..."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/legacy/spark-max-client",
- "title": "SPARK MAX Client",
- "section": "General",
- "language": "All",
- "content": "# SPARK MAX Client\n\n{% hint style=\"warning\" %}\nThis is **legacy documentation** for our discontinued SPARK MAX Client Software. If you are interested in running a SPARK MAX via a computer, please see our newer documentation: [Getting Started with the REV Hardware Client.](https://docs.revrobotics.com/rev-hardware-client)\n{% endhint %}\n\nUpdate, configure, and test your SPARK MAX Motor Controller with the SPARK MAX Client application. \n\n| Latest SPARK MAX Client - Version 2.1.1 |\n| :------------------------------------------------------------------------------------------------------------------------------------------: |\n| For instructions on how to access this legacy software, please email |\n\nThe SPARK MAX Client **will not work with SPARK MAX beta units** distributed by REV to the SPARK MAX beta testers. It is only compatible with units received after 12/21/2018.\n\n### System Requirements\n\n* Windows 10 64-bit\n * Windows 7 64-bit might work but it is [not supported](https://support.microsoft.com/en-us/help/4057281/windows-7-support-will-end-on-january-14-2020).\n* Internet connection for automatic updates\n\n### Installation Instructions\n\n1. Download the SPARK MAX Client installer above.\n2. Run the installer. Windows may require approval to install the application.\n3. During the installation process, separate driver installation windows may appear. Some driver installations may fail if you already have the driver installed from a previously installed Client, this is expected.\n4. Once installed, run the application. If prompted, be sure to grant network access. Without network access, the client software won't be able to download the latest SPARK MAX firmware and client updates.\n\n\n\n####\n",
- "content_preview": "# SPARK MAX Client\n\n{% hint style=\"warning\" %}\nThis is **legacy documentation** for our discontinued SPARK MAX Client Software. If you are interested in running a SPARK MAX via a computer, please see our newer documentation: [Getting Started with the REV Hardware..."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/legacy/spark-max-client/navigation",
- "title": "Navigating the SPARK MAX Client",
- "section": "General",
- "language": "All",
- "content": "# Navigating the SPARK MAX Client\n\n{% hint style=\"info\" %}\nThis is **legacy documentation** for our discontinued SPARK MAX Client Software. If you are interested in running a SPARK MAX via a computer, please see our newer documentation: [Getting Started with the REV Hardware Client.](https://app.gitbook.com/o/-M3qDjqMSqBbHMXNmC_O/s/-MGEfA6CxjaSQiH5kHxn/)\n{% endhint %}\n\n## Navigation Bar\n\nThe Navigation bar is visible on all tabs of the SPARK MAX Client and allows you to select with SPARK MAX the Client is connected to. \n\n\n\n1. **Identify Device:** The status LED of a selected device will blink. This is helpful when troubleshooting or configuring multiple devices.\n2. **Device Selection:** See each SPARK MAX connected to the SPARK MAX Client. This includes other devices connected via CAN if running firmware 1.4.0 or later.\n3. **Rescan:** Looks for additional SPARK MAX devices connected to the SPARK MAX Client. This includes other devices connect via CAN if running firmware 1.4.0 or later.\n4. **Connect/Disconnect:** After selecting a device connecting to the device pulls all the configuration parameters set on the device.\n5. **Tabs**: Select one of the five tabs to gain access to configure, update, and run SPARK MAX.\n\n## Basic Tab\n\nThe Basic Tab is used to set the most common parameters for the SPARK MAX\n\n\n\n1. **Configurations:** This drop down allows you to select pre-existing configurations store on the Windows machine running the SPARK MAX Client or to pull the existing parameters stored on in RAM on the SPARK MAX. This is helpful when configuring multiple motor controllers to the same settings.\n2. **CAN ID:** This assigns a SPARK MAX a CAN ID for identification over the CAN BUS. Any configured SPARK MAX **must have** a CAN ID.\n3. **Configured Parameters:** Change the motor type, sensor type, idle mode behavior, and more.\n\n## Advanced Tab\n\nThe Advanced Tab allows for changing all configurable parameters of the SPARK MAX without needing to set them in code.\n\n\n\n1. **Search Parameters:** Allows for easy look up of a specific parameter for editing.\n2. **Parameter Table:** Select the arrow to show all configurable parameters within a specific group. For more information on each parameter type see [Configuration Parameters](https://docs.revrobotics.com/brushless/spark-max/parameters).\n\n## Run Tab\n\nThe Run Tab allows for the SPARK MAX to operate over USB or a USB to CAN Bridge without the need for a full control system. This is helpful for testing mechanisms and tuning their control loops.\n\n\n\n1. **Bar Select:** Select from either run, parameters, or signals to provide information and feedback when operating SPARK MAX.\n2. **Signal Chart:** Shows any added signals in graph form when running a SPARK MAX. This is helpful when tuning control loops.\n3. **PIDF:** Update PIDF parameters on the fly to tune control loops on the SPARK MAX.\n4. **Run:** Choose setpoints to run a motor connected to a SPARK MAX using various modes, including position, velocity, and duty cycle.\n\n\n\n{% hint style=\"info\" %}\nThe three icons for bar select change the bottom third of the Run Tab for configuration. Once signals and other parameters are configured selecting the run bar icon will allow for running of a motor with the SPARK MAX Client.\n{% endhint %}\n\n## Network Tab\n\nThe Network Tab shows all connected devices via USB and the USB to CAN interface. From the Network Tab each device can be identified and firmware updated.\n\n\n\n1. **Device Select:** Select a device to update firmware.\n2. **Load Firmware:** Select what firmware to update onto selected devices.\n\n{% hint style=\"info\" %}\nFor more information on the firmware updating process see [Updating Device Firmware](https://docs.revrobotics.com/brushless/legacy/spark-max-client/update) for both [single device](https://docs.revrobotics.com/brushless/legacy/update#updating-a-single-device) and [multiple device](https://docs.revrobotics.com/brushless/legacy/update#updating-multiple-devices-with-the-usb-to-can-bridge) updates.\n{% endhint %}\n",
- "content_preview": "# Navigating the SPARK MAX Client\n\n{% hint style=\"info\" %}\nThis is **legacy documentation** for our discontinued SPARK MAX Client Software. If you are interested in running a SPARK MAX via a computer, please see our newer documentation: [Getting Started with the REV Hardware..."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/legacy/spark-max-client/update",
- "title": "Updating Device Firmware",
- "section": "General",
- "language": "All",
- "content": "# Updating Device Firmware\n\n{% hint style=\"info\" %}\nThis is **legacy documentation** for our discontinued SPARK MAX Client Software. If you are interested in running a SPARK MAX via a computer, please see our newer documentation: [Getting Started with the REV Hardware Client.](https://app.gitbook.com/o/-M3qDjqMSqBbHMXNmC_O/s/-MGEfA6CxjaSQiH5kHxn/)\n{% endhint %}\n\n### Updating a Single Device\n\nFollow the steps below to update the firmware on your SPARK MAX:\n\n* Connect your SPARK MAX Motor Controller to your computer with a USB-C cable.\n* Open the REV SPARK MAX Client application. \n* The Client should automatically scan and connect to your SPARK MAX. If your SPARK MAX is running outdated firmware, you will be notified with a pop-up window like the one pictured below:\n\n\n\n{% hint style=\"info\" %}\nIf your SPARK MAX is running firmware older than 1.4.0, you may not see a pop-up and will need to proceed directly to the **Network** tab and click **Scan Bus** manually. \n{% endhint %}\n\n* Click **Open Network Tab & Scan Bus** and proceed to the next step.\n* Your SPARK MAX should now be listed in the device list. Click the checkbox next to the SPARK MAX you wish to update, and click **Load Firmware**.\n\n\n\n{% hint style=\"info\" %}\nIf your SPARK MAX is listed and you are unable to click the checkbox next to it, you must put your SPARK MAX into [Recovery Mode](https://docs.revrobotics.com/brushless/spark-max/troubleshooting#recovery-mode). \n{% endhint %}\n\n* Select the latest firmware file in the firmware directory that the client created on startup. If there isn't a firmware directory, you can also navigate to a file that was downloaded manually. Click **Open** once the appropriate firmware file is selected:\n\n\n\n* Click **Yes** to confirm the update.\n\n\n\n* Once complete, the Client will rescan the bus and display the updated controllers.\n\n\n\n### Updating Multiple Devices with the USB-to-CAN Bridge\n\nSPARK MAX Firmware Version 1.5.0 includes a USB-to-CAN Bridge feature that allows a single USB-connected SPARK MAX to act as a bridge to the entire CAN bus it is connected to. This allows for configuration and simultaneous updating of multiple SPARK MAX controllers without having to connect to each one individually. Using this feature requires the following:\n\n* A USB-connected SPARK MAX that is updated to firmware version 1.5.0 or newer to act as the Bridge.\n* Other SPARK MAXs connected on the CAN bus must be individually updated to firmware version 1.4.0 before they are able to receive mass-updates from the Bridging SPARK MAX.\n\nOnce these requirements are satisfied, navigate to the **Network** tab, select the controllers you wish to update, and follow the same firmware update procedure described above starting at Step 4.\n\n\n\nWhen complete, the Client will display the number of successfully updated controllers.\n\n\n\nIf a controller fails to update it is usually due to the process being interrupted by a bad power or CAN connection. Severe interruptions can cause the firmware update to be corrupted. A corrupted controller can no longer be updated over the USB-to-CAN Bridge, however, it can be recovered by connecting to the controller directly over USB and putting it in [Recovery Mode](https://docs.revrobotics.com/brushless/spark-max/troubleshooting#recovery-mode).\n",
- "content_preview": "# Updating Device Firmware\n\n{% hint style=\"info\" %}\nThis is **legacy documentation** for our discontinued SPARK MAX Client Software. If you are interested in running a SPARK MAX via a computer, please see our newer documentation: [Getting Started with the REV Hardware..."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/legacy/spark-max-client/recovery",
- "title": "Recovery Mode with the SPARK MAX Client",
- "section": "General",
- "language": "All",
- "content": "# Recovery Mode with the SPARK MAX Client\n\n{% hint style=\"info\" %}\nThis is **legacy documentation** for our discontinued SPARK MAX Client Software. If you are interested in running a SPARK MAX via a computer, please see our newer documentation: [Getting Started with the REV Hardware Client.](https://app.gitbook.com/o/-M3qDjqMSqBbHMXNmC_O/s/-MGEfA6CxjaSQiH5kHxn/)\n{% endhint %}\n\nWhen updating the firmware on the SPARK MAX, it is possible for the process to be interrupted or for the firmware to be corrupted by a bad download. In this state, the Status LED will be dark and the SPARK MAX will fail to operate. SPARK MAX has a built-in recovery mode that can force it to accept new firmware even if the controller seems to be bricked. The following procedure requires a small tool, like a straightened paper clip, to press the Mode Button, a USB C cable, and a computer with the [SPARK MAX Client Application](https://docs.revrobotics.com/brushless/legacy/spark-max-client) installed: \n\n1. With the SPARK MAX powered off completely, press and hold the Mode Button.\n2. While still holding the Mode Button, connect the SPARK MAX to the computer using the USB cable. The Status LED **will not** illuminate, this is expected.\n3. Wait a few seconds for the computer to recognize the connected device, then release the Mode Button.\n4. Open the SPARK MAX Client Application. The SPARK MAX will remain dark and it **will not** connect to the Client, this is expected.\n5. Navigate to the **Network** tab and click the **Rescan** arrows at the top of the window.\n6. The SPARK MAX will be listed under *Devices in Recovery Mode.* Click the checkbox next to the device.\n7. Click the **Load Firmware** button.\n8. Select the latest firmware file and click **Open**.\n9. The firmware should load successfully and the SPARK MAX will now connect to the Client.\n",
- "content_preview": "# Recovery Mode with the SPARK MAX Client\n\n{% hint style=\"info\" %}\nThis is **legacy documentation** for our discontinued SPARK MAX Client Software. If you are interested in running a SPARK MAX via a computer, please see our newer documentation: [Getting Started with the REV Hardware..."
- },
- {
- "url": "https://docs.revrobotics.com/brushless/legacy/spark-max-client/troubleshooting",
- "title": "SPARK MAX Client Troubleshooting",
- "section": "General",
- "language": "All",
- "content": "# SPARK MAX Client Troubleshooting\n\n{% hint style=\"info\" %}\nThis is **legacy documentation** for our discontinued SPARK MAX Client Software. If you are interested in running a SPARK MAX via a computer, please see our newer documentation: [Getting Started with the REV Hardware Client.](https://app.gitbook.com/o/-M3qDjqMSqBbHMXNmC_O/s/-MGEfA6CxjaSQiH5kHxn/)\n{% endhint %}\n\n### Error During First-time Firmware Update\n\nIf this is the first time installing the SPARK MAX Client or connecting a SPARK MAX in Recovery Mode, you may see an error the first time you try to update firmware on your computer. The DFU driver is one of two drivers installed by the Client and is used for updating firmware. It may not install completely until a SPARK MAX in DFU Mode (Recovery Mode) is plugged into the computer. \n\nIf you see an error during your first firmware update, please do the following:\n\n1. Close the Client application.\n2. Unplug the SPARK MAX from the computer.\n3. Plug the SPARK MAX back into the computer.\n4. Open the Client application.\n\nAlternatively, you can preemptively finalize the DFU driver installation by following the [Recovery Mode](https://docs.revrobotics.com/brushless/legacy/spark-max-client/recovery) steps before using the Client for the first time.\n\nWe are aware of this issue and will release a fix in a future update of the SPARK MAX Client.\n\n### Troubleshooting\n\nAs we get feedback from users and identify exact causes for issues, please look back here for troubleshooting help. If you are running into issues running the SPARK MAX Client try the following **BEFORE** contacting :\n\n* Try running the SPARK MAX Client as an Administrator\n* Make sure that Windows is fully up-to-date. Some computers have Windows Update disabled and need to be updated manually.\n* Check the Device Manager and verify that the SPARK MAX shows up as one of the following two devices with no caution symbols:\n * Normal operating mode: Device Manager -> Ports (COM & LPT) -> USB Serial Device (COMx)\n * Recovery mode: Device Manager -> Universal Serial Bus Controllers -> STM Device in DFU Mode\n * If the device shows up with errors or as STM32 BOOTLOADER, try installing the [DFU drivers](https://www.revrobotics.com/content/sw/max/STMDFUDriver.zip) separately.\n",
- "content_preview": "# SPARK MAX Client Troubleshooting\n\n{% hint style=\"info\" %}\nThis is **legacy documentation** for our discontinued SPARK MAX Client Software. If you are interested in running a SPARK MAX via a computer, please see our newer documentation: [Getting Started with the REV Hardware..."
- },
- {
- "url": "https://docs.revrobotics.com/rev-hardware-client/home/rev-hardware-client-overview",
- "title": "REV Hardware Client Overview",
- "section": "REV Hardware Client",
- "language": "All",
- "content": "# REV Hardware Client Overview\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2](https://docs.revrobotics.com/rev-hardware-client-2).\n{% endhint %}\n\nThe REV Hardware Client (RHC) is software designed to make managing REV devices easier for the user. This Client automatically detects connected device(s), downloads the latest software for those device(s), and allows for seamless updating of the device(s) \n\nBe sure to check the [changelog](https://docs.revrobotics.com/rev-hardware-client/home/changelog#version-1.7.0) for the latest version of the RHC.\n\n| [REV Hardware Client - Version 1.7.6](https://github.com/REVrobotics/REV-Software-Binaries/releases/download/rhc-1.7.6/REV-Hardware-Client-Setup-1.7.6.exe) |\n| :---------------------------------------------------------------------------------------------------------------------------------------------------------: |\n\n{% hint style=\"info\" %}\nYou can also download the REV Hardware Client using an offline installer, bundled with software from **March 18, 2025**:\\\n-[ Offline Installer bundled with FRC software](https://github.com/REVrobotics/REV-Software-Binaries/releases/download/rhc-1.7.5/REV-Hardware-Client-Setup-1.7.5-offline-FRC-2025-03-18.exe)\\\n\\- [Offline Installer bundled with FTC software](https://github.com/REVrobotics/REV-Software-Binaries/releases/download/rhc-1.7.5/REV-Hardware-Client-Setup-1.7.5-offline-FTC-2025-03-18.exe)\\\n\\- [Offline Installer bundled with all available software](https://github.com/REVrobotics/REV-Software-Binaries/releases/download/rhc-1.7.5/REV-Hardware-Client-Setup-1.7.5-offline-allSoftware-2025-03-18.exe)\n{% endhint %}\n\n{% hint style=\"warning\" %}\nAs of April 12, 2024 Windows 10 or later is required for the latest version of the REV Hardware Client. [Please use 1.6.4 if you are on an older version of Windows.](https://github.com/REVrobotics/REV-Software-Binaries/releases/download/rhc-1.6.4/REV-Hardware-Client-Setup-1.6.4.exe)\n{% endhint %}\n\n## Feature Summary\n\n* Automatically detect supported devices when connected via USB\n* Connect a REV Control Hub via Wi-Fi\n* One Click update of all software on connected devices\n* Pre-download software updates without a connected device\n* Back up and restore user data from supported devices \n * REV DUO: Control Hub \n * REV ION: SPARK Flex and SPARK MAX \n* Install and switch between DS and RC applications on Android Devices\n* Access the Robot Control Console on the Control Hub\n* Auto-update to latest version of the REV Hardware Client\n* Display devices connected via RS485\n\n## Supported Devices\n\n* REV Control Hub (REV-31-1595)\n* REV Expansion Hub (REV-31-1153)\n* REV Driver Hub (REV-31-1596)\n* REV Servo Hub (REV-11-1855)\n* Android Device via ADB\n* REV SPARK Flex (REV-11-2159)\n* REV SPARK MAX (REV-11-2158)\n* REV Power Distribution Hub (REV-11-1850)\n* REV Pneumatic Hub (REV-11-1852)\n* Generic CAN Devices\n",
- "content_preview": "# REV Hardware Client Overview\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2](https://docs.revrobotics.com/rev-hardware-client-2).\n{% endhint %}\n\nThe REV Hardware Client (RHC) is software designed to make managing REV devices easier..."
- },
- {
- "url": "https://docs.revrobotics.com/rev-hardware-client/gs/install",
- "title": "Installation",
- "section": "REV Hardware Client",
- "language": "All",
- "content": "# Installation\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2](https://docs.revrobotics.com/rev-hardware-client-2).\n{% endhint %}\n\nBefore starting download the latest version of the REV Hardware Client.\n\n| [REV Hardware Client - Version 1.7.5](https://github.com/REVrobotics/REV-Software-Binaries/releases/download/rhc-1.7.5/REV-Hardware-Client-Setup-1.7.5.exe) |\n| :---------------------------------------------------------------------------------------------------------------------------------------------------------: |\n\n### System Requirements\n\n* Operating System: Windows 10 (64-bit) or newer\n* Processor: 64-bit\n\n{% hint style=\"warning\" %}\nAs of April 12, 2024 Windows 10 or later is required for the latest version of the REV Hardware Client. [Please use 1.6.4 if you are on an older version of Windows.](https://github.com/REVrobotics/REV-Software-Binaries/releases/download/rhc-1.6.4/REV-Hardware-Client-Setup-1.6.4.exe)\n{% endhint %}\n\n### Installation Instructions\n\n* Download the REV Hardware Client Installer \n* Run the Installer\n* Run the REV Hardware Client from the Windows Start Menu or a desktop shortcut\n",
- "content_preview": "# Installation\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2](https://docs.revrobotics.com/rev-hardware-client-2).\n{% endhint %}\n\nBefore starting download the latest version of the REV Hardware Client.\n\n| [REV Hardware Client -..."
- },
- {
- "url": "https://docs.revrobotics.com/rev-hardware-client/gs/navigation",
- "title": "Navigating the Client",
- "section": "REV Hardware Client",
- "language": "All",
- "content": "# Navigating the Client\n\nThe REV Hardware Client has three tabs to manage different features of the Client. The Hardware Tab is where supported hardware devices are managed. The Downloads Tab allows for the downloading of supported device software for updating when offline. The About Tab has information on what devices are supported, updating the REV Hardware Client, and having issue reporting.\n\n## Hardware Tab\n\nThe Hardware Tab is where supported hardware devices are managed in the REV Hardware Client. When opening the REV Hardware Client the Hardware Tab is displayed.\n\n### No Hardware Detected\n\n\n\n1. **Scan for Devices** - When supported REV hardware is connected to a Windows PC the Client will auto scan for new devices. You can Scan for Devices if the device did not automatically populate.\n2. **Check for Updates** - This checks for any updates available for the REV Hardware Client\n3. **Navigation** - Three tabs are at the top of REV Hardware Client allowing \n\n### Hardware Detected\n\nConnecting supported hardware to a Windows PC with the REV Hardware Client running will automatically scan and add the devices to the Hardware Tab.\n\n\n\n1. **Supported Hardware** - Each type of supported hardware will appear. Clicking on the Hardware will bring up each units Device Menu\n\n### Device Menu\n\nSelecting a Device will bring up that device's menu. Below is a screenshot of the Control Hub's Device Menu.\n\n\n\n1. **Device Menu Tabs** - Different devices will have various supported tabs. All devices have an Updates Tab handling software updates for for device. The Control Hub has a Program and Manage Tab giving access to the Robot Control Console. The Control Hub also has a [Backup and Restore Tab](https://docs.revrobotics.com/rev-hardware-client/duo/control-hub/restore) allowing for the back up and restoration of configuration files as well as blocks and java code.\n2. **Out of Date Warning** - There are two indicators that a part of the software on the device is out of date.\n3. **Download and Install** - Under each update type are buttons to download and install that update.\n4. **Update All** - This button will update all software items for all connected devices. This type of update can take a while depending on the number of devices connected and the type of update.\n\n## Downloads Tab\n\nThe Downloads Tab is where download software files are managed. The latest software updates are able to be downloaded without a hardware device connected to the REV Hardware Client.\n\n\n\n1. **Downloaded Update Files -** This section allows for each version of software already downloaded to be viewed, released notes checked, and deleting the files from the local machine.\n2. **Available Update Files -** This section displays the latest version of software to download, release notes for that software, and a button to initiate the download.\n\n## About Tab\n\nThe About Tab is where the REV Hardware Client is managed. Here updates for the REV Hardware Client are checked, downloaded, and installed.\n\n\n\n1. **Check for Updates -** This section displays the current version of the REV Hardware Client and allows for checking for software updates to the REV Hardware Client.\n2. **Supported Devices -** This section lists all of the currently supported devices for the version of the REV Hardware Client installed on the user's device.\n",
- "content_preview": "# Navigating the Client\n\nThe REV Hardware Client has three tabs to manage different features of the Client. The Hardware Tab is where supported hardware devices are managed. The Downloads Tab allows for the downloading of supported device software for updating when offline."
- },
- {
- "url": "https://docs.revrobotics.com/rev-hardware-client/gs/troubleshooting",
- "title": "Troubleshooting",
- "section": "REV Hardware Client",
- "language": "All",
- "content": "# Troubleshooting\n\n## Device is not visible\n\n If you don't see all of the devices that you expect to see, follow these steps:\n\n* Make sure that you are connected to the Internet, so that Windows can download the necessary drivers\n* Disconnect the device from the computer and then re-connect it\n* Click the \"Scan for Devices\" link in the bottom-right corner of the Hardware tab\n\nIf that doesn't work, follow the steps for the type of device that is missing.\n\n### Control Hubs connected via USB\n\n* Make sure that the Control Hub is plugged in via USB-C, not Mini USB\n* Make sure the Control Hub has had a chance to finish starting up, and that its light is green\n* Unplug the Control Hub from the computer and plug it back in\n\n### Control Hubs connected via WiFi\n\n* Make sure that the Control Hub is running version 5.5 or later of the Robot Controller app\n* Make sure the Control Hub has had a chance to finish starting up, and that its light is green\n* Make sure that you are currently connected to the Control Hub's WiFi network\n* Try rebooting the Control Hub. Re-connect to its WiFi network after its light turns green\n* Plug the Control Hub in via USB instead\n\n### Expansion Hubs connected to a Control Hub\n\n* Make sure that the Expansion Hub is in the active configuration file\n* Make sure that the Control Hub is running version 5.5 or later of the Robot Controller app\n\n### Android phones\n\n* Make sure that USB debugging is enabled in the Developer Options\n * If you can't find Developer Options anywhere in the Settings app (it may be listed on a System screen or similar), make sure it is enabled by tapping on the Build number 7 times on the About screen of the Settings app.\n* Unplug the phone from the computer and plug it back in. Look for a prompt to allow USB debugging, and click OK when it comes up.\n* Make sure that the ADB driver for your Android phone is installed. For Motorola phones, do this by installing Motorola Device Manager.\n\n### SPARK MAX Motor Controller\n\n* Make sure that the SPARK MAX is not being used by another application, such as the REV SPARK MAX Client\n* Unplug the SPARK MAX from the computer and plug it back in\n",
- "content_preview": "# Troubleshooting\n\n## Device is not visible\n\n If you don't see all of the devices that you expect to see, follow these steps:\n\n* Make sure that you are connected to the Internet, so that Windows can download the necessary drivers\n* Disconnect the device from the computer and then re-connect..."
- },
- {
- "url": "https://docs.revrobotics.com/rev-hardware-client/duo/control-hub",
- "title": "Control Hub",
- "section": "REV Hardware Client",
- "language": "All",
- "content": "# Control Hub\n\n## Connecting Via USB\n\n| Steps | |\n| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Power on the Control Hub, by plugging the 12V Slim Battery ([REV-31-1302](https://www.revrobotics.com/rev-31-1302/)) into the XT30 connector labeled “BATTERY” on the Control Hub. | |\n| The Control Hub is ready to connect with a PC when the LED turns green. Note: the light blinks blue every \\~5 seconds to indicate that the Control Hub is healthy. |  |\n| Plug the Control Hub into the PC using a USB-A to USB-C Cable ([REV-11-1232](https://www.revrobotics.com/rev-11-1232/)) | |\n\nStartup the REV Hardware Client. Once the Hub is fully connected it will show up on the front page of the UI under the Hardware Tab. Select the Control Hub. \n\n\n",
- "content_preview": "# Control Hub\n\n## Connecting Via USB\n\n| Steps | ..."
- },
- {
- "url": "https://docs.revrobotics.com/rev-hardware-client/duo/control-hub/update",
- "title": "Updating a Control Hub",
- "section": "REV Hardware Client",
- "language": "All",
- "content": "# Updating a Control Hub\n\n## Update All\n\nOnce one or more supported REV Hardware devices are connected that require updates, the **Update All** button will appear.\n\n\n\nOnce Update All is selected the REV Hardware Client will confirm the updates for all connected devices. Select Update to download and update all devices.\n\n\n\n## Individual Updates\n\n### Operating System\n\nAfter selecting the Connected Hardware the Update tab will pop up. Under **Control Hub Operating System** select Download.\n\n\n\nOnce the OS has downloaded, select Update. \n\n\n\nKeep the Control Hub powered while the upload finishes.\n\n\n\nA successful upload will be denoted by the \"Update Verification Succeeded\" message highlighted in the image below. Once the upload is successful the install will begin. \n\nKeep the Control Hub powered while the update is installed. The Control Hub will reboot to complete the update.\n\n\n\nWhen the OS update has completed a status message \"Operating System update complete.\" The status for the Control Hub Operation System will also change to \"Up-to-Date.\"\n\n\n\n### Firmware\n\nThere are two boards within the Control Hub: an Expansion Hub and an Android controller. The Expansion Hub board built into the Control Hub, facilitates a line of communication between the built in Robot Controller and the motors, servos, and sensors. In order to improve the quality of the Hubs, REV Robotics will release firmware updates for the Expansion Hub. When a firmware release occurs, both Control Hub and Expansion Hub users will need to update their Expansion Hub firmware to the newest version. \n\n{% hint style=\"warning\" %}\nIn order to use the REV Hardware Client for firmware updates, the Robot Controller Application must first be updated to version 5.5. After updating the application you may need to close out of the REV Hardware Client in order for the firmware update to be available. \n{% endhint %}\n\nAfter selecting the Connected Hardware the Update tab will pop up. Under **Hub Firmware** select Download.\n\n\n\nOnce the firmware has downloaded, select Update. \n\n\n\nWhen the firmware update has completed a status message \"Firmware successfully updated\" The status for the Hub Firmware will also change to \"Up-to-Date.\"\n\n\n\n### Robot Controller Application\n\nAfter selecting the Connected Hardware the Update tab will pop up. Under **Robot Controller App** select Download.\n\nOnce the app has downloaded, select Update. \n\n\n\nWhen the Robot Controller Application update has completed a status message \"Robot Controller app update complete.\" The status of the **Robot Controller App** will also change to \"Up-to-Date.\"\n\n\n\n##\n",
- "content_preview": "# Updating a Control Hub\n\n## Update All\n\nOnce one or more supported REV Hardware devices are connected that require updates, the **Update All** button will appear.\n\n\n\nOnce selected a prompt will display confirming Configuration Files and Robot Code are backed up. Also, the zip file name is visible.\n\n\n\n## Restoring Files\n\nTo Restore Files, select the Restore Files button.\n\n\n\nOnce selected, a window opens prompting you to select the zip file to restore. Select the zip file and press Restore Files.\n\n\n\nOnce selected the Client will prompt with the Configuration Files and Robot Code are restored.\n\n\n",
- "content_preview": "# Restoring Control Hub Data\n\n## Backup Files\n\nOnce on the Backup and Restore Tab, select the Backup Files button.\n\n) | |\n| Startup the REV Hardware Client. Once the Driver Hub is on and fully connected it will show up on the front page of the UI under the Hardware Tab. | |\n\n{% hint style=\"info\" %}\nIf your Driver Hub is showing a battery charging symbol when plugged in via USB it may not be able to connect to the REV Hardware Client. Please check our Driver Hub Troubleshooting documentation for help connecting.\n{% endhint %}\n\n\n\n##\n",
- "content_preview": "# Driver Hub\n\n## Connect Via USB-C\n\n| Steps | ..."
- },
- {
- "url": "https://docs.revrobotics.com/rev-hardware-client/duo/driver-hub/update",
- "title": "Updating a Driver Hub",
- "section": "REV Hardware Client",
- "language": "All",
- "content": "# Updating a Driver Hub\n\n## Update All\n\nWhen the Driver Hub and any other supported REV Hardware devices that require updates are connected the Update All button will appear. \n\n\n\nOnce Update All is is selected the REV Hardware Client will confirm the updates for all connected devices. Select Update to download and update all devices. \n\n\n\n## Individual Updates\n\nTo install individual updates to you Driver Hub select the Driver Hub from the list of devices on the Hardware Tab. This will bring up the Update Tab.\n\n\n\n### Driver Hub Operating System\n\nAfter the Update Tab opens, select Download under Driver Hub Operating System (OS) to begin downloading the update.\n\n\n\nOnce the OS update has downloaded select Update.\n\n\n\nKeep the Driver Hub powered on and connected to the PC while the update finishes.\n\n\n\nWhen the Driver Station Operating System update has completed a status message \"Operating System update complete.\" The status of the Driver Station OS will also change to \"Up-to-Date.\"\n\n\n\n### Driver Station Application \n\nAfter selecting the Connected Hardware the Update tab will pop up. Under Driver Station App select Download.\n\nOnce the app has downloaded, select Update. \n\n\n\nWhen the Driver Station Application update has completed a status message \"Driver Station App update complete.\" The status of the Driver Station App will also change to \"Up-to-Date.\"\n\n\n",
- "content_preview": "# Updating a Driver Hub\n\n## Update All\n\nWhen the Driver Hub and any other supported REV Hardware devices that require updates are connected the Update All button will appear. \n\n\n\nAfter selecting the Connected Hardware the Update tab will pop up. Under **Hub Firmware** select Download.\n\n\n\nOnce the firmware has downloaded, select Update. \n\n\n\nWhen the firmware update has completed a status message \"Firmware successfully updated\" The status for the Hub Firmware will also change to \"Up-to-Date.\"\n\n\n",
- "content_preview": "# Expansion Hub\n\n## Updating an Expansion Hub\n\nThe Expansion Hub facilitates a line of communication between a connected Robot Controller and the motors, servos, and sensors. In order to improve the quality of the Hub, REV Robotics will release firmware updates for the Expansion Hub."
- },
- {
- "url": "https://docs.revrobotics.com/rev-hardware-client/duo/android-devices",
- "title": "Android Devices",
- "section": "REV Hardware Client",
- "language": "All",
- "content": "# Android Devices\n\n### **Android Developer Options**\n\nIn order to use the REV Hardware Client the phone's developer settings and USB debugging options need to be turned on. \n\nThe developer options on Android Devices are hidden within the phone as a default. Different phone manufactures have different ways of accessing the developer options. However, once the developer options are available in the phone's settings, the steps for activating USB debugging and development settings are similar. \n\n{% hint style=\"danger\" %}\nBefore moving forward it is advised to look up where the developer options on your Android Device are located. For Motorola users, the [Motorola Support Page](https://en-us.support.motorola.com/app/home) has information on how to unlock the developer options.\n{% endhint %}\n\n| | |\n| -------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Open the Android Devices settings | |\n| Scroll to the bottom of the settings, where the unlocked developer options are available. Open the developer options | |\n| At the top of the developer options page is an on/off switch. Turn the developer options on. | |\n| The device will open a confirmation message. Select 'OK.' | |\n| Scroll through the developer options until you find the Debugging section. Turn USB Debugging on. | |\n| Another confirmation message will appear, click 'OK.' | |\n\nUSB debugging is now on! You can move on to the steps for installing the application.\n\nDepending on the device you may need to change the USB Settings from \"Charging only\" to \"File Transfer\".\n\nPlug the Android Device with USB Debugging into the Windows PC running the REV Hardware Client.\n\n\n",
- "content_preview": "# Android Devices\n\n### **Android Developer Options**\n\nIn order to use the REV Hardware Client the phone's developer settings and USB debugging options need to be turned on. \n\nThe developer options on Android Devices are hidden within the phone as a default."
- },
- {
- "url": "https://docs.revrobotics.com/rev-hardware-client/duo/android-devices/install-apps",
- "title": "Installing RC/DS Applications",
- "section": "REV Hardware Client",
- "language": "All",
- "content": "# Installing RC/DS Applications\n\n{% hint style=\"info\" %}\nThe steps below show installing the Driver Station Application. Follow the same steps, except for the Robot Controller \n{% endhint %}\n\nConnect the Android Device to a PC with the [REV Hardware Client](https://www.revrobotics.com/software/#REVHardwareClient) installed.\n\nStartup the REV Hardware Client. Once the Android Device is fully connected it will show up on the front page of the UI under the **Hardware Tab**. Select the Android Device. \n\n\n\nAfter selecting the Connected Hardware the Update tab will pop up. Under **Driver Station App** select Download.\n\n\n\nOnce the Driver Station App has downloaded, select Install. \n\n\n\nWhen the application installation has completed the status for the Driver Station App will change to \"Up-to-Date.\"\n\n\n\n##\n",
- "content_preview": "# Installing RC/DS Applications\n\n{% hint style=\"info\" %}\nThe steps below show installing the Driver Station Application. Follow the same steps, except for the Robot Controller \n{% endhint %}\n\nConnect the Android Device to a PC with the [REV Hardware..."
- },
- {
- "url": "https://docs.revrobotics.com/rev-hardware-client/duo/log-viewer",
- "title": "Using the Log Viewer",
- "section": "REV Hardware Client",
- "language": "All",
- "content": "# Using the Log Viewer\n\nWhen troubleshooting problems with the REV Control System log files provide indicators of what the status of the Control Hub or Expansion Hub were during an event. A look at the Robot Controller, WiFi log, or Updater log may help you better understand the root cause of the issue. \n\nHowever the logs document all activities that the Control System performs, not just issues, but normal startup procedures or op mode runs. The means that logs often contain more information than can be reasonably sifted through. To make the content in the logs more palatable to sort through, the logs need to be parsed. \n\nThe REV Hardware Client has a Log Viewer that makes it easier to parse overall log files. Through a series of filters, tags, and a search function makes it easy to see what is happening on the Control Hub or Driver Hub during any opmode run.\n\n## Accessing the Log Viewer\n\nTo access the Log Viewer, head to the Utilities Tab.\n\n\n\nFrom there you can select and open log files for connected devices or for ones downloaded onto the computer. \n\n\n\n## Parsing the Logs\n\nOne of the most common issues that arises when trying to interpret logs is the wrong time and date on the Driver Hub or Driver Station phones. Ensuring that your Driver Station is set to the right time before observing or sending a log can help make data between the Robot Controller Log and Driver Station more interpretable. It also helps when observing a unique issue to pay attention to the time of the incident. If a problem or an indicator of a problem starts occurring at 4:38:57 PM then that time can be tracked in the logs to help determine what happened that initiated the issues.\n\nOnce you have confirmed the correct date and time you can use the Log Viewer to try to track the problem within the logs. \n\nIf you select a connected device, such as a Control Hub, the Log viewer will give you an option to select standard logs or matches. Matches are segments of the robot controller log where a particular op mode is running. This is helpful to parse the data further by limiting the content to particular op mode runs that you know a system failure occurred during. \n\n\n\n### Interpreting Filters\n\n\n\nAside from helping parse data down to specific time intervals the Log Viewer offers filters to select narrow down data in the logs to a particular type of data. The Log Viewer splits the data from the logs into six types: error, warning, info, fatal, debug, and verbose. \n\n#### Errors \n\n\n\nErrors occur when system actions do not execute properly. These log lines are typically indicative of a user created issues, such as errors in code, configuration, or wiring. For instance, in the image above the error is stating that it failed to calibrate the camera, which could be a sign that the USB Camera was detached from the Control Hub. \n\nAnother common error you might see are compilation errors. These particular errors are the same error messages you receive in OnBot Java when you attempt to Build code and it fails. \n\n\n\n#### Warnings\n\n\n\nWhen something none fatal occurs in the system, the system sends warning messages. Instances that warrant a warning message, do not cause the Control System to fail, but may cause unexpected behavior. This could be a warning about mismatched versions between the Robot Controller and Driver Station applications, or a warning that your Control System is not receiving enough power to function. \n\n#### Info\n\n\n\nInfo messages communication information that my be worthwhile to know for troubleshooting but not necessarily indicative of an issue. This is information like, when a program is initialized, started, or stopped. \n\n#### Fatal\n\n\n\nLike Errors, Fatal actions occur when something within the system does not execute properly. However, fatal actions are indicative that something more severe is happening in a system. If you are having an issues and notice a lot of instances of the fatal data type, please send your [diagnostic data to REV,](https://docs.revrobotics.com/rev-control-system/managing-the-control-system/downloading-log-file#rev-hardware-client) with information on the issue you are having and the status LED behavior.\n\n#### Debug\n\n\n\nThe debug filter, showcases instances within the logs where debugging functions built into the SDK are performing their jobs. The Log Viewer defaults to filtering out debug information, as the information is typically not needed for troubleshooting. \n\n#### Verbose\n\n\n\nMany of the log types we have discussed thus far provide the insight needed to troubleshoot an issue. However, the logs track much more information than what falls in the other categories. The verbose log type covers the rest of the information included in the logs. This information is typically recording normal system behaviors that do not provide much insight to a problem. \n",
- "content_preview": "# Using the Log Viewer\n\nWhen troubleshooting problems with the REV Control System log files provide indicators of what the status of the Control Hub or Expansion Hub were during an event."
- },
- {
- "url": "https://docs.revrobotics.com/rev-hardware-client/ion/spark-flex",
- "title": "SPARK Flex",
- "section": "REV Hardware Client",
- "language": "All",
- "content": "# SPARK Flex\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2.](https://docs.revrobotics.com/rev-hardware-client-2)\n{% endhint %}\n\n## Connecting a SPARK Flex via USB\n\n* Connect your SPARK Flex Motor Controller to your computer with a USB C cable.\n* Open the REV Hardware Client application.\n* The Client should automatically scan and connect to your SPARK Flex\n\nSPARK Flex in the 1.6.1v of the REV Hardware Client
\n\n{% hint style=\"info\" %}\nAdditional SPARK Flex devices connected via CAN to the USB Host SPARK Flex are visible when using the latest firmware. For more information see the [SPARK Flex User's Manual.](https://docs.revrobotics.com/brushless/spark-flex/overview)\n{% endhint %}\n\n## Navigating the REV Hardware Client\n\nThe REV Hardware Client has four tabs to manage different features of the Client. The \"Hardware Tab\" is where supported hardware devices are managed. The \"Utilities Tab\" allows for viewing log files collected by the Control Hub or Driver Hub. The \"Downloads Tab\" allows for the downloading of supported device software for updating when offline. The \"About Tab\" has information on what devices are supported, updating the REV Hardware Client, and troubleshooting.\n\nIndividual devices, like the SPARK Flex, have additional tabs available when the device is selected. For a full overview of the default navigation features of the REV Hardware Client see the [User's Manual.](https://docs.revrobotics.com/rev-hardware-client/home/rev-hardware-client-overview) Below is more information on using the specific features for the SPARK Flex.\n\n{% hint style=\"warning\" %}\nAs of REV Hardware Client version 1.7.0, \"Burn Flash\" has been renamed to \"Persist Perimeters\"!\n{% endhint %}\n\n### Hardware Tab\n\nThe Hardware Tab is used to select devices connected via USB or the USB to CAN bridge for configuration, updates, and more.\n\nA SPARK Flex, PDH, and SPARK MAX showing in the 1.6.1 RHC
\n\nOnce a SPARK Flex is selected from the Hardware tab a number of device specific tabs will show.\n\n### Basic Tab\n\nThe Basic Tab is used to set the most common parameters for the SPARK Flex.\n\n \n\n1. **Device Identify:** Blink the selected SPARK Flex's LED for identification.\n2. **CAN ID:** This assigns a SPARK Flex a CAN ID for identification over the CAN BUS. Any configured SPARK Flex **must have** a CAN ID.\n3. **Configurations:** This drop down allows you to select pre-existing configurations store on the Windows machine running the SPARK Flex Client or to pull the existing parameters stored on in RAM on the SPARK Flex. This is helpful when configuring multiple motor controllers to the same settings.\n4. **Configured Parameters:** Change the motor type, sensor type, idle mode behavior, and more.\n\n{% hint style=\"warning\" %}\nThe ability to switch \"Motor Type\" to brushed on the SPARK Flex will be available with the SPARK Flex Dock (Coming Soon!)\n{% endhint %}\n\n### Advanced Tab\n\nThe Advanced Tab allows for changing all configurable parameters of the SPARK Flex without needing to set them in code.\n\nSPARK Flex \"Advanced\" Menu
\n\n1. **Search Parameters:** Allows for easy look up of a specific parameter for editing.\n2. **Parameter Table:** Select the arrow to show all configurable parameters within a specific group. \n\n{% hint style=\"info\" %}\nRemember to persist the parameters to memory before disconnecting the SPARK Flex\n{% endhint %}\n\n### Run Tab \n\nThe Run Tab allows for the SPARK Flex to operate over USB or a USB to CAN Bridge without the need for a full control system. This is helpful for testing mechanisms and tuning their control loops.\n\nSPARK Flex \"Run\" Menu
\n\n1. **Run:** Choose setpoints to run a motor connected to a SPARK Flex using various modes, including position, velocity, and duty cycle.\n2. **PIDF:** Update PIDF parameters on the fly to tune control loops on the SPARK Flex.\n3. **View Graph:** Moves the Client over to the Telemetry Tab to show any added signals in graph form when running a SPARK Flex. This is helpful when tuning control loops.\n\n### Update Tab \n\nThe Update tab shows what version of firmware is on the selected device, if that device is up to date, and update the firmware of the selected device.\n\nSPARK Flex \"Update\" Tab
\n\n1. **Download Latest Firmware:** Downloads latest firmware onto the local machine running the Client.\n2. **Update Firmware:** Updates the selected device with the latest firmware.\n3. **Out-of-date Firmware Warning:** Warning to alert the user there is new firmware available for any connected device.\n",
- "content_preview": "# SPARK Flex\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2.](https://docs.revrobotics.com/rev-hardware-client-2)\n{% endhint %}\n\n## Connecting a SPARK Flex via USB\n\n* Connect your SPARK Flex Motor Controller to your computer with a..."
- },
- {
- "url": "https://docs.revrobotics.com/rev-hardware-client/ion/spark-flex/updating-a-spark-flex",
- "title": "Updating a SPARK Flex",
- "section": "SPARK Flex",
- "language": "All",
- "content": "# Updating a SPARK Flex\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2.](https://docs.revrobotics.com/rev-hardware-client-2)\n{% endhint %}\n\n## Updating a Single SPARK Flex\n\n* Connect your SPARK Flex Motor Controller to your computer with a USB-C cable.\n* Open the REV SPARK Flex Client application.\n* The Client should automatically scan and connect to your SPARK Flex. \n\nOnce the SPARK Flex is connected via USB-C select it within the **Connected Hardware.** \n\nSPARK Flex needing updates in the RHC
\n\nThe Hardware Client will open up on the **Basic** tab. To update firmware select the **Update** tab after selecting the SPARK Flex from the Connected Hardware list. \n\nSPARK Flex \"Basic\" Tab with the \"Update\" Tab highlighted
\n\nUnder **SPARK Flex Firmware**, select download to download the latest version of the firmware. \n\nSPARK Flex Download Button
\n\nOnce the firmware has downloaded select update.\n\nSPARK Flex Update Button
\n\nThe update process will flash the firmware image onto the SPARK Flex. The status bar will show the progress of the process. \n\nSPARK Flex currently updating
\n\nOnce the firmware update is done your SPARK Flex will show a new status of **Up-to-Date.**\n\nA SPARK Flex shown as Up-to-Date
\n",
- "content_preview": "# Updating a SPARK Flex\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2.](https://docs.revrobotics.com/rev-hardware-client-2)\n{% endhint %}\n\n## Updating a Single SPARK Flex\n\n* Connect your SPARK Flex Motor Controller to your computer..."
- },
- {
- "url": "https://docs.revrobotics.com/rev-hardware-client/ion/spark-max",
- "title": "SPARK MAX",
- "section": "REV Hardware Client",
- "language": "All",
- "content": "# SPARK MAX\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2](https://docs.revrobotics.com/rev-hardware-client-2).\n{% endhint %}\n\n## Connecting a SPARK MAX via USB\n\n* Connect your SPARK MAX Motor Controller to your computer with a USB C cable.\n* Open the REV Hardware Client application.\n* The Client should automatically scan and connect to your SPARK MAX\n\n{% hint style=\"info\" %}\nAll SPARK MAX Motor Controllers comes with a USB C to USB A cable. \n{% endhint %}\n\n \n\n{% hint style=\"info\" %}\nAdditional SPARK MAX devices connected via CAN to the USB Host SPARK MAX are visible when using the latest firmware. For more information see the [SPARK MAX User's Manual.](https://docs.revrobotics.com/brushless/spark-max/overview)\n{% endhint %}\n\n## Navigating the REV Hardware Client\n\nThe REV Hardware Client has four tabs to manage different features of the Client. The \"Hardware Tab\" is where supported hardware devices are managed. The \"Utilities Tab\" allows for viewing log files collected by the Control Hub or Driver Hub. The \"Downloads Tab\" allows for the downloading of supported device software for updating when offline. The \"About Tab\" has information on what devices are supported, updating the REV Hardware Client, and troubleshooting.\n\nIndividual devices, like the SPARK MAX, have additional tabs available when the device is selected. For a full overview of the default navigation features of the REV Hardware Client see the [User's Manual.](https://docs.revrobotics.com/rev-hardware-client/home/rev-hardware-client-overview) Below is more information on using the specific features for the SPARK MAX.\n\n{% hint style=\"warning\" %}\nAs of REV Hardware Client version 1.7.0, \"Burn Flash\" has been renamed to \"Persist Perimeters\"!\n{% endhint %}\n\n### Hardware Tab\n\nThe Hardware Tab is used to select devices connected via USB or the USB to CAN bridge for configuration, updates, and more.\n\n \n\nOnce a SPARK MAX is selected from the Hardware tab a number of device specific tabs will show.\n\n### Basic Tab\n\nThe Basic Tab is used to set the most common parameters for the SPARK MAX.\n\n \n\n1. **Device Identify:** Blink the selected SPARK MAX's LED for identification.\n2. **CAN ID:** This assigns a SPARK MAX a CAN ID for identification over the CAN BUS. Any configured SPARK MAX **must have** a CAN ID.\n3. **Configurations:** This drop down allows you to select pre-existing configurations store on the Windows machine running the SPARK MAX Client or to pull the existing parameters stored on in RAM on the SPARK MAX. This is helpful when configuring multiple motor controllers to the same settings.\n4. **Configured Parameters:** Change the motor type, sensor type, idle mode behavior, and more.\n\n### Advanced Tab\n\nThe Advanced Tab allows for changing all configurable parameters of the SPARK MAX without needing to set them in code.\n\n \n\n1. **Search Parameters:** Allows for easy look up of a specific parameter for editing.\n2. **Parameter Table:** Select the arrow to show all configurable parameters within a specific group. For more information on each parameter type see [Configuration Parameters](https://docs.revrobotics.com/brushless/spark-max/revlib/parameters).\n\n{% hint style=\"info\" %}\nRemember to burn the persist parameters to memory before disconnecting the SPARK MAX\n{% endhint %}\n\n### Run Tab \n\nThe Run Tab allows for the SPARK MAX to operate over USB or a USB to CAN Bridge without the need for a full control system. This is helpful for testing mechanisms and tuning their control loops.\n\n \n\n1. **Run:** Choose setpoints to run a motor connected to a SPARK MAX using various modes, including position, velocity, and duty cycle.\n2. **PIDF:** Update PIDF parameters on the fly to tune control loops on the SPARK MAX.\n3. **View Graph:** Moves the Client over to the Telemetry Tab to show any added signals in graph form when running a SPARK MAX. This is helpful when tuning control loops.\n\n### Update Tab \n\nThe Update tab shows what version of firmware is on the selected device, if that device is up to date, and update the firmware of the selected device.\n\n \n\n1. **Download Latest Firmware:** Downloads latest firmware onto the local machine running the Client.\n2. **Update Firmware:** Updates the selected device with the latest firmware.\n3. **Out-of-date Firmware Warning:** Warning to alert the user there is new firmware available for any connected device.\n\nFor more information on the firmware updating process see Updating Device Firmware\n",
- "content_preview": "# SPARK MAX\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2](https://docs.revrobotics.com/rev-hardware-client-2).\n{% endhint %}\n\n## Connecting a SPARK MAX via USB\n\n* Connect your SPARK MAX Motor Controller to your computer with a USB C..."
- },
- {
- "url": "https://docs.revrobotics.com/rev-hardware-client/ion/spark-max/update",
- "title": "Updating a SPARK MAX",
- "section": "SPARK MAX",
- "language": "All",
- "content": "# Updating a SPARK MAX\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2](https://docs.revrobotics.com/rev-hardware-client-2).\n{% endhint %}\n\n## Updating a Single SPARK MAX\n\n* Connect your SPARK MAX Motor Controller to your computer with a USB-C cable.\n* Open the REV SPARK MAX Client application.\n* The Client should automatically scan and connect to your SPARK MAX. \n\nOnce the SPARK MAX is connected via USB-C select it within the **Connected Hardware.** \n\n \n\n{% hint style=\"info\" %}\nIf the SPARK MAX connected via USB-C is running firmware version 1.5.0 or later allows the SPARK MAX to work as a USB to CAN Bridge. Other CAN connected SPARK MAXs running version 1.4.0 can be selected for firmware updates over CAN.\n{% endhint %}\n\nWithin the Hardware Client, for the SPARK MAX, there are 5 tabs. The Hardware Client will open up on the **Basic** tab. To update firmware select the **Update** tab. \n\n \n\nUnder **SPARK MAX Firmware**, select download to download the latest version of the firmware. \n\n \n\nOnce the firmware has downloaded select update.\n\n \n\nThe update process will flash the firmware image onto the SPARK MAX. The status bar will show the progress of the process. \n\n \n\nOnce the firmware update is done your SPARK MAX will show a new status of **Up-to-Date.**\n\n \n\n{% hint style=\"info\" %}\nIf your SPARK MAX is running firmware older than 1.4.0, you may need to unplug and replug the USB-C cable into the SPARK MAX for it to reconnect to the Client. \n{% endhint %}\n\n## Updating Multiple Devices with the USB-to-CAN Bridge\n\nSPARK MAX Firmware Version 1.5.0 includes a USB-to-CAN Bridge feature that allows a single USB-connected SPARK MAX to act as a bridge to the entire CAN bus it is connected to. This allows for configuration and simultaneous updating of multiple SPARK MAX controllers without having to connect to each one individually. Using this feature requires the following:\n\n* A USB-connected SPARK MAX that is updated to firmware version 1.5.0 or newer to act as the Bridge.\n* Other SPARK MAXs connected on the CAN bus must be individually updated to firmware version 1.4.0 before they are able to receive mass-updates from the Bridging SPARK MAX.\n\nOnce these requirements are satisfied, navigate to the **Hardware** tab, select the **Update All** button.\n\n \n\nEach device with the Out-of-Date warning will update with the latest version of the firmware.\n",
- "content_preview": "# Updating a SPARK MAX\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2](https://docs.revrobotics.com/rev-hardware-client-2).\n{% endhint %}\n\n## Updating a Single SPARK MAX\n\n* Connect your SPARK MAX Motor Controller to your computer with..."
- },
- {
- "url": "https://docs.revrobotics.com/rev-hardware-client/ion/power-distribution-hub",
- "title": "Power Distribution Hub",
- "section": "REV Hardware Client",
- "language": "All",
- "content": "# Power Distribution Hub\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2](https://docs.revrobotics.com/rev-hardware-client-2).\n{% endhint %}\n\nThe REV Hardware Client has three tabs to manage different features of the Client. The Hardware Tab is where supported hardware devices are managed. The Downloads Tab allows for the downloading of supported device software for updating when offline. The About Tab has information on what devices are supported, updating the REV Hardware Client, and having issue reporting.\n\n## Basic Tab\n\nThe Basic Tab is used to set the most common parameters for the Power Distribution Hub. You can also view and clear sticky faults here.\n\n \n\n{% hint style=\"info\" %}\nSticky Fault - an indicator there was a fault that will stay until the fault has been cleared manually. \n{% endhint %}\n\n \n",
- "content_preview": "# Power Distribution Hub\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2](https://docs.revrobotics.com/rev-hardware-client-2).\n{% endhint %}\n\nThe REV Hardware Client has three tabs to manage different features of the Client."
- },
- {
- "url": "https://docs.revrobotics.com/rev-hardware-client/ion/power-distribution-hub/updating-a-power-distribution-hub",
- "title": "Updating a Power Distribution Hub",
- "section": "REV Hardware Client",
- "language": "All",
- "content": "# Updating a Power Distribution Hub\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2](https://docs.revrobotics.com/rev-hardware-client-2).\n{% endhint %}\n\nStartup the REV Hardware Client. Once the Power Distribution Hub is fully connected it will show up on the front page of the UI under the **Hardware Tab**. Select the Power Distribution Hub. \n\n \n\nAfter selecting the Connected Hardware, the Update tab will pop up. Under **Power Distribution Hub Firmware** select Download.\n\n \n\nOnce the firmware has downloaded, select Update. \n\n \n\nWhen the firmware update has completed a status message \"Update Completed\" will appear. The status for the Power Distribution Hub Firmware will also change to \"Up-to-Date.\"\n\n \n",
- "content_preview": "# Updating a Power Distribution Hub\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2](https://docs.revrobotics.com/rev-hardware-client-2).\n{% endhint %}\n\nStartup the REV Hardware Client."
- },
- {
- "url": "https://docs.revrobotics.com/rev-hardware-client/ion/pneumatic-hub",
- "title": "Pneumatic Hub",
- "section": "REV Hardware Client",
- "language": "All",
- "content": "# Pneumatic Hub\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2](https://docs.revrobotics.com/rev-hardware-client-2).\n{% endhint %}\n\nThe REV Hardware Client has three tabs to manage different features of the Client. The Hardware Tab is where supported hardware devices are managed. The Downloads Tab allows for the downloading of supported device software for updating when offline. The About Tab has information on what devices are supported, updating the REV Hardware Client, and having issue reporting. \n\n## Basic Tab\n\nThe Basic Tab is used to set the most common parameters for the Pneumatic Hub. You can also view and clear sticky faults here.\n\n \n\n{% hint style=\"info\" %}\nSticky Fault - an indicator there was a fault that will stay until the fault has been cleared manually. \n{% endhint %}\n\n \n\n## Operating your pneumatics system without a roboRIO \n\nUsing the REV Hardware Client, you can completely operate and test your pneumatic system without needing a roboRIO or software. Here are some useful applications that can help your team: \n\n* Testing your pneumatic system in a bench top setting \n* Testing the pneumatic system on your robot without the need for software support (e.g. allowing your build team to test the pneumatics while your programmers are busy). \n* Using pneumatics in your quick and early prototypes during the beginning of the build season \n* Using the telemetry tab on the Hardware Client, you can see if you have leaks in your systems or check how much pressure you lose during normal operation without needing to write extra software. (An analog pressure sensor is needed).\n",
- "content_preview": "# Pneumatic Hub\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2](https://docs.revrobotics.com/rev-hardware-client-2).\n{% endhint %}\n\nThe REV Hardware Client has three tabs to manage different features of the Client."
- },
- {
- "url": "https://docs.revrobotics.com/rev-hardware-client/ion/pneumatic-hub/updating-a-pneumatic-hub",
- "title": "Updating a Pneumatic Hub",
- "section": "REV Hardware Client",
- "language": "All",
- "content": "# Updating a Pneumatic Hub\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2](https://docs.revrobotics.com/rev-hardware-client-2).\n{% endhint %}\n\nStartup the REV Hardware Client. Once the Pneumatic Hub is fully connected it will show up on the front page of the UI under the **Hardware Tab**. Select the Pneumatic Hub. \n\n \n\nAfter selecting the Connected Hardware, the Update tab will pop up. Under **Pneumatic Hub Firmware** select Download.\n\n \n\nOnce the firmware has downloaded, select Update. (Your Hardware Client will say \"Update\" instead of \"Re-install\")\n\n \n\nWhen the firmware update has completed a status message \"Update Completed\" will appear. The status for the Pneumatic Hub Firmware will also change to \"Up-to-Date.\"\n\n \n",
- "content_preview": "# Updating a Pneumatic Hub\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2](https://docs.revrobotics.com/rev-hardware-client-2).\n{% endhint %}\n\nStartup the REV Hardware Client."
- },
- {
- "url": "https://docs.revrobotics.com/rev-hardware-client/ion/telemetry",
- "title": "Telemetry Tab",
- "section": "REV Hardware Client",
- "language": "All",
- "content": "# Telemetry Tab\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2](https://docs.revrobotics.com/rev-hardware-client-2).\n{% endhint %}\n\n### Connected Devices\n\nDevices available in the REV Hardware Client are shown on the left side of the window. The device that the USB C Cable is connected to will be listed first, followed by any devices connected over CAN.\n\n\n\n### Available Devices\n\nThe below devices are able to provide telemetry and allow the Telemetry Tab to be used. \n\n* SPARK MAX\n* SPARK Flex\n* Power Distribution Hub \n* Pneumatic Hub\n* Devices Connected to the SPARK MAX or SPARK Flex Motor Controllers:\n * NEO Vortex\n * NEO Brushless Motor V1.1\n * NEO 550\n * Brushed DC Motor\n * Through Bore Encoder \n * Other inputs connected to the Data Port\n\n## Telemetry Settings\n\n### Signals and Graph\n\n \n\n1. **Run Motor:** Choose setpoints to run a motor connected to a SPARK MAX using various modes, including position, velocity, and duty cycle.\n2. **Signals:** Select the different signals from the SPARK MAX you want to monitor here\n3. **Start & Restart Graph:** Start initiates recording of telemetry. Restart will erase the data and start again\n4. **Time Span:** Change the time span shown on the x-axis of the graph\n5. **Scales:** Different Signals will have different scales for the y-axis. You can change which are shown by clicking the arrows here\n6. **Signal Key and Scale Adjustment:** Signals you choose to monitor will be shown here. Click **X** to delete a signal from the graph and **>** to adjust the scale of the signal's graph y-axis\n7. **Save Data:** Save your data as a .CSV or image using this menu\n\n### Tuning\n\n \n\nUpdate PIDF parameters on the fly to tune control loops on the SPARK MAX.\n\n### Parameters\n\n \n\nSelect the arrow to show all configurable parameters within a specific group. For more information on each parameter type see [Configuration Parameters](https://docs.revrobotics.com/brushless/spark-max/parameters).\n\n## Editing the Y-Axis Scale\n\n \n\n1. **Y-Axis Labels:** Select the label you want to view by clicking the arrow at the bottom of the label. In this image the Power Distribution Hub Channel Currents are selected.\n2. **Y-Axis Scale:** Use the drop down arrow next to the parameter you would like to change the scale for. Be sure to un-check the \"use defaults\" box to apply your changes. \n\n## Example\n\n \n\nIn this example the SPARK MAX and NEO Motor were run at 30% power, switching between forwards and backwards several times. The first switch in direction occurs near t=5s where you can see the Applied Output, Position, and Velocity change. \n\n## Exporting Data\n\n### Exporting as a Image\n\nThis will export a .png image of the of the graph. The whole Time Span x-axis will be exported regardless of the time information was collected. The image below exported a 30 second graph while only 8 seconds of data was recorded. \n\n \n\n### Exporting as a .CSV\n\nThis will export a .csv file of the of the values and timestamps.\n\n \n\n1. **Timestamp:** This is the timestamp that the data was record for each signal in Unix time. Note that different signals may have their data recorded at different times than other signals. \n2. **Signal Name:** The Label of signal selected when creating your Telemetry graph.\n3. **Device Name:** The name of the device and the randomly generated ID assigned to each device when connected to the REV Hardware Client. This is randomly generated each time the device is connected to the REV Hardware Client.\n4. **Signal Value:** The value recorded for each Signal Name.\n\n### Record to .CSV\n\nThis allows you to select an existing .csv file prior to starting your graph and record the data straight to the .csv file. This allows you export additional data to a previously exported telemetry file without headers to seamlessly add to your existing columns. \n\n## Troubleshooting\n\n### roboRio Lockout\n\n**Please be aware of the CAN lockout feature of the ION Control System.** If it has been connected to the roboRIO's CAN bus, a safety feature within all ION Control System Devices and will lock out USB communication. You may be able to change some parameters on select devices but in order to run motors through the telemetry tab disconnecting from the CAN bus and power-cycling the device will release the lock.\n\n## PID Set Up Guide\n\n{% hint style=\"warning\" %}\nAs of REV Hardware Client version 1.7.0, \"Burn Flash\" has been renamed to \"Persist Perimeters\"!\n{% endhint %}\n\n1. Under the device list, select your SPARK motor controller. \n\n \n2. Click the \"Advanced\" tab. \n\n \n3. For a SPARK MAX, under the \"Alternate Encoder\" section, make sure \"kDataPortConfig\" is set to \"Default\". \n\n \n4. Under the \"Closed Loop\" section, set \"kCtrlType\" to \"Position\". \n\n \n5. Under the same section, set \"kFeedbackSensorPID0\" to \"Duty Cycle\". \n\n \n6. Click \"Burn Flash\" at the bottom of the page. (Burn Flash has been renamed to Persist Perimeters in v 1.7.0) \n\n \n7. To tune your PID gains, click the \"Telemetry\" tab. \n\n \n8. Set the \"Mode\" to \"Position\". \n\n \n9. Under the \"Signals\" tab, select \"Run Setpoint\" and \"Duty Cycle Position\". \n\n \n10. Click the \"Tuning\" tab. \n\n \n11. Begin tuning your PID gains! Note that the setpoint in Hardware Client only allows whole numbers, so it would be helpful to set the Duty Cycle Position Factor parameter to something like 360 for degrees.\n\n## Frequently Asked Questions\n\n\n\nDo I need to use a special type of USB cable? \n\nYou need to use a USB-A to USB-C cable data capable cable. The orange USB cable that comes with most ION devices fits this description. \n\n \n\n\n\nDoes the SPARK Flex offer more options than the SPARK MAX? \n\nAs of the 2024 FRC season the SPARK Flex and SPARK MAX offer the same options. New features will become available with future updates to the SPARK Flex that will be free to SPARK Flex owners forever.\n\n \n\n\n\nCan the units of the telemetry channels be changed? \n\nNo, not at this time. We recommend exporting the data as a .csv file and converting to fit your specific needs.\n\n \n",
- "content_preview": "# Telemetry Tab\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2](https://docs.revrobotics.com/rev-hardware-client-2).\n{% endhint %}\n\n### Connected Devices\n\nDevices available in the REV Hardware Client are shown on the left side of the..."
- },
- {
- "url": "https://docs.revrobotics.com/rev-hardware-client/ion/telemetry/running-multiple-spark-motor-controllers",
- "title": "Running Multiple SPARK Motor Controllers",
- "section": "REV Hardware Client",
- "language": "All",
- "content": "# Running Multiple SPARK Motor Controllers\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2](https://docs.revrobotics.com/rev-hardware-client-2).\n{% endhint %}\n\nThis guide will help illustrate how to use the REV Hardware Client to run multiple ION motor controllers and motor pairs over CAN using various modes, including position, velocity, and duty cycle.\n\n### Running One SPARK Motor Controller\n\n1. Connect your SPARK to the REV Hardware Client via a USB-C cable plugged into the SPARK itself or another device on the CAN network. If available, other CAN Devices will appear on the left under \"Connected Hardware\". In our example, we have plugged into the Power Distribution Hub. \\\n \\\n Then, click the \"Telemetry\" tab to continue.\\\n \\\n \\NAN;*Note: For best results, ensure all devices have the latest firmware installed.* \n\n{% hint style=\"danger\" %}\nAs a safety precaution, USB control is disabled on all REV Devices if a roboRIO has been detected at any time while the control system is on. To resolve the lockout, power cycle your control system after disconnecting the roboRIO.\n{% endhint %}\n\n \n\n2. Here, all available devices should be visible on the left. Click the first device you wish to configure.\n\n \n\n3. While the motor is running, you can select different signals to display on the graph, providing valuable telemetry data for prototyping or troubleshooting. To track key metrics, select Voltage, Primary Encoder Position, and Primary Encoder Velocity to visualize these signals on the graph during operation.\n\n \n\n4. After selecting your signals, click the \"Start Graph\" button to begin data collection before clicking the \"Run Motor\" button.\n\n{% hint style=\"warning\" %}\nBelow \"Run Motor,\" you'll find the Mode and Setpoint settings, which determine the motor's behavior while running. For this guide, leave the Mode at Percent and the Setpoint at 0.05. Ensure the motor is securely fastened before clicking \"Run Motor.\"\n{% endhint %}\n\n5. Congratulations, you've successfully run your SPARK MAX through the REV Hardware Client!\n\n \n\n{% hint style=\"info\" %}\nIf you are only using multiple SPARK MAXs and a power source, you can terminate both ends of your CAN Bus with 120Ω resistors!\n{% endhint %}\n\n### Running Multiple SPARKs\n\n1. With the Power Distribution Hub and all SPARK devices wired, updated, and connected via CAN, plug the provided orange USB-C cable into the Power Distribution Hub. You should then see all the respective hardware appear on the left. Click the \"Telemetry\" tab to continue.\n\n{% hint style=\"danger\" %}\nA roboRIO lockout can occur when it is active on the CAN bus while the REV Hardware Client is connected. To resolve this issue, power cycle all SPARK motor controllers after disconnecting the roboRIO.\n{% endhint %}\n\n \n\n2. Here, all available devices should be visible on the left. Click the first device you wish to configure.\n\n \n\n3. After selecting your desired signals, click on the \"Run Multiple\" tab. You'll notice that our SPARK Flex is checked, meaning that it's ready to run. We need to check off the SPARK MAX so that both run at the same time. When you're done, click on the back button highlighted in orange to return to the Telemetry Devices tray. \n\n \n\n4. In the previous step, we selected the signals for our SPARK Flex. The same needs to be done for our SPARK MAX, click on it to continue.\n\n \n\n5. After selecting your signals for the second motor controller, click on the *Run Multiple* tab.\n\n \n\n6. After selecting your signals, click the \"Start Graph\" button to begin data collection before clicking the \"Run Motor\" button.\n\n{% hint style=\"danger\" %}\nBelow \"Run Motor,\" you'll find the Mode and Setpoint settings, which determine the motor's behavior while running. For this guide, leave the Mode at Percent and the Setpoint at 0.03. Ensure the motor is securely fastened before clicking \"Run Motor.\"\n{% endhint %}\n\n7. Congratulations, you've successfully run multiple SPARK Motor Controllers through the REV Hardware Client!\n\n \n\n{% hint style=\"info\" %}\nIf you are only using multiple SPARK MAXs and a power source, you can terminate both ends of your CAN Bus with 120Ω resistors!\n{% endhint %}\n",
- "content_preview": "# Running Multiple SPARK Motor Controllers\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2](https://docs.revrobotics.com/rev-hardware-client-2).\n{% endhint %}\n\nThis guide will help illustrate how to use the REV Hardware Client to run..."
- },
- {
- "url": "https://docs.revrobotics.com/rev-hardware-client/ion/recovery-mode",
- "title": "Recovery Mode",
- "section": "REV Hardware Client",
- "language": "All",
- "content": "# Recovery Mode\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2](https://docs.revrobotics.com/rev-hardware-client-2).\n{% endhint %}\n\nSometimes, when updating the firmware on an ION Control System Device, it is possible for the process to be interrupted or for the firmware to be corrupted by a bad download or other type of interruption in data transfer. In this state, the Status LED will be dark or dim and the device will fail to operate. There is a built-in recovery mode that can force your device to accept new firmware even if the controller seems to be bricked and the procedure is outlined below:\n\n{% hint style=\"warning\" %}\nPerforming this procedure will erase all data and settings on the device. Be sure to burn your desired settings to flash after recovering the device\n{% endhint %}\n\n### Materials Needed\n\n* A small tool, like a straightened paper clip or a SIM card removal tool, to press the Mode Button \n* Data-capable USB-C to USB-A cable\n* A Windows computer with the [REV Hardware Client](https://docs.revrobotics.com/rev-hardware-client/gs/install)[ Installed](https://docs.revrobotics.com/rev-hardware-client/gs/install) and updated to the latest version\n\n### Recovery Mode Steps\n\n1. With the Device powered off, press and hold the Mode Button\n2. While still holding the Mode Button, connect the Device to the computer using the USB-C cable - the Status LED will not illuminate - this is expected.\n3. With the REV Hardware Client running on the computer, wait a few seconds for the audible tone or icon for the device to be recognized in recovery mode then release the Mode Button - no lights will be present on the device during this stage of the process, this is expected\n4. Select the Device in Recovery Mode from the REV Hardware Client window \n\n Example of a Device in Recovery Mode
\n5. From the Choose a Device type dropdown, choose - the firmware that matches the device you are attempting to recover. *It is possible to install incorrect firmware on your device.* \n\n \n6. Choose the latest version of Firmware from the dropdown and then click update \n\n Selecting the firmware on a SPARK MAX in recovery
\n7. Wait for the software update to complete \n\n \n8. Power cycle your device (unplug and plug in USB-C) click on the device's icon, and then clear any sticky faults\n\n{% hint style=\"success\" %}\nFirmware Recovery Complete! \n{% endhint %}\n",
- "content_preview": "# Recovery Mode\n\n{% hint style=\"danger\" %}\nThose using REV ION products on REVLib 2026 or newer must use [REV Hardware Client 2](https://docs.revrobotics.com/rev-hardware-client-2).\n{% endhint %}\n\nSometimes, when updating the firmware on an ION Control System Device, it is possible for the process..."
- },
- {
- "url": "https://docs.revrobotics.com/rev-hardware-client/crossover/servo-hub",
- "title": "Servo Hub",
- "section": "REV Hardware Client",
- "language": "All",
- "content": "# Servo Hub\n\n \n\n## Connecting a Servo Hub via USB\n\n* Connect your Servo Hub to your computer with a USB C cable.\n* Open the REV Hardware Client application.\n* The Client should automatically scan and connect to your Servo Hub\n\n## Navigating the REV Hardware Client\n\nThe REV Hardware Client has four tabs to manage different features of the Client. The \"Hardware Tab\" is where supported hardware devices are managed. The \"Utilities Tab\" allows for viewing log files collected by the Control Hub or Driver Hub. The \"Downloads Tab\" allows for the downloading of supported device software for updating when offline. The \"About Tab\" has information on what devices are supported, updating the REV Hardware Client, and troubleshooting.\n\nIndividual devices, like the Servo Hub, have additional tabs available when the device is selected. For a full overview of the default navigation features of the REV Hardware Client see the [User's Manual.](https://docs.revrobotics.com/rev-hardware-client/home/rev-hardware-client-overview) Below is more information on using the specific features for the Servo Hub.\n\n### Basic Tab\n\n \n\n1. **CAN ID:** This assigns the Servo Hub a CAN ID for identification over the CAN BUS. Any configured Servo Hub **must have** a CAN ID. This is also the **Hub Address** used with the Control Hub and Expansion Hub. \n2. **Servo Limits:** Here you can set the angular limits for each servo port. Any changes to these parameters are automatically saved to the Servo Hub. \n3. **Disabled Behavior:** This drop down allows you to select the behavior of the Servo Hub when it is disabled through code. \n\n### Servo Tab\n\n \n\n1. **Servo Controls:** These controls allow you to run and test servos connected to the Servo Hub through the REV Hardware Client. This is useful for testing your servo's application. \n\n### Update Tab \n\nThe Update tab shows what version of firmware is on the selected device, if that device is up to date, and update the firmware of the selected device.\n\n \n\n1. **Download Latest Firmware:** Downloads latest firmware onto the local machine running the Client.\n2. **Update Firmware:** Updates the selected device with the latest firmware.\n3. **Out-of-date Firmware Warning:** Warning to alert the user there is new firmware available for any connected device.\n",
- "content_preview": "# Servo Hub\n\n This runs after the mode specific periodic functions, but before LiveWindow and 36 * SmartDashboard integrated updating. 37 */ 38 @Override 39 public void robotPeriodic () { 40 // Runs the Scheduler. This is responsible for polling buttons, adding newly-scheduled 41 // commands, running already-scheduled commands, removing finished or interrupted commands, 42 // and running subsystem periodic() methods. This must be called from the robot's periodic 43 // block in order for anything in the Command-based framework to work. 44 CommandScheduler . getInstance (). run (); 45 } C++ (Source) 11 /** 12 * This function is called every 20 ms, no matter the mode. Use 13 * this for items like diagnostics that you want to run during disabled, 14 * autonomous, teleoperated and test. 15 * 16 * This runs after the mode specific periodic functions, but before 17 * LiveWindow and SmartDashboard integrated updating. 18 */ 19 void Robot::RobotPeriodic () { 20 frc2 :: CommandScheduler :: GetInstance (). Run (); 21 } The inclusion of the CommandScheduler.getInstance().run() call in the robotPeriodic() method is essential; without this call, the scheduler will not execute any scheduled commands. Since TimedRobot runs with a default main loop frequency of 50Hz, this is the frequency with which periodic command and subsystem methods will be called. It is not recommended for new users to call this method from anywhere else in their code. Java 54 /** This autonomous runs the autonomous command selected by your {@link RobotContainer} class. */ 55 @Override 56 public void autonomousInit () { 57 m_autonomousCommand = m_robotContainer . getAutonomousCommand (); 58 59 // schedule the autonomous command (example) 60 if ( m_autonomousCommand != null ) { 61 CommandScheduler . getInstance (). schedule ( m_autonomousCommand ); 62 } 63 } C++ (Source) 32 /** 33 * This autonomous runs the autonomous command selected by your {@link 34 * RobotContainer} class. 35 */ 36 void Robot::AutonomousInit () { 37 m_autonomousCommand = m_container . GetAutonomousCommand (); 38 39 if ( m_autonomousCommand ) { 40 frc2 :: CommandScheduler :: GetInstance (). Schedule ( m_autonomousCommand . value ()); 41 } 42 } The autonomousInit() method schedules an autonomous command returned by the RobotContainer instance. The logic for selecting which autonomous command to run can be handled inside of RobotContainer . Java 69 @Override 70 public void teleopInit () { 71 // This makes sure that the autonomous stops running when 72 // teleop starts running. If you want the autonomous to 73 // continue until interrupted by another command, remove 74 // this line or comment it out. 75 if ( m_autonomousCommand != null ) { 76 m_autonomousCommand . cancel (); 77 } 78 } C++ (Source) 46 void Robot::TeleopInit () { 47 // This makes sure that the autonomous stops running when 48 // teleop starts running. If you want the autonomous to 49 // continue until interrupted by another command, remove 50 // this line or comment it out. 51 if ( m_autonomousCommand ) { 52 m_autonomousCommand -> Cancel (); 53 } 54 } The teleopInit() method cancels any still-running autonomous commands. This is generally good practice. Advanced users are free to add additional code to the various init and periodic methods as they see fit; however, it should be noted that including large amounts of imperative robot code in Robot.java is contrary to the declarative design philosophy of the command-based paradigm, and can result in confusingly-structured/disorganized code. RobotContainer This class ( Java , C++ (Header) , C++ (Source) ) is where most of the setup for your command-based robot will take place. In this class, you will define your robot’s subsystems and commands, bind those commands to triggering events (such as buttons), and specify which command you will run in your autonomous routine. There are a few aspects of this class new users may want explanations for: Java 23 private final ExampleSubsystem m_exampleSubsystem = new ExampleSubsystem (); C++ (Header) 32 ExampleSubsystem m_subsystem ; Notice that subsystems are declared as private fields in RobotContainer . This is in stark contrast to the previous incarnation of the command-based framework, but is much more-aligned with agreed-upon object-oriented best-practices. If subsystems are declared as global variables, it allows the user to access them from anywhere in the code. While this can make certain things easier (for example, there would be no need to pass subsystems to commands in order for those commands to access them), it makes the control flow of the program much harder to keep track of as it is not immediately obvious which parts of the code can change or be changed by which other parts of the code. This also circumvents the ability of the resource-management system to do its job, as ease-of-access makes it easy for users to accidentally make conflicting calls to subsystem methods outside of the resource-managed commands. Java 61 return Autos . exampleAuto ( m_exampleSubsystem ); C++ (Source) 34 return autos :: ExampleAuto ( & m_subsystem ); Since subsystems are declared as private members, they must be explicitly passed to commands (a pattern called “dependency injection”) in order for those commands to call methods on them. This is done here with ExampleCommand , which is passed a pointer to an ExampleSubsystem . Java 35 /** 36 * Use this method to define your trigger->command mappings. Triggers can be created via the 37 * {@link Trigger#Trigger(java.util.function.BooleanSupplier)} constructor with an arbitrary 38 * predicate, or via the named factories in {@link 39 * edu.wpi.first.wpilibj2.command.button.CommandGenericHID}'s subclasses for {@link 40 * CommandXboxController Xbox}/{@link edu.wpi.first.wpilibj2.command.button.CommandPS4Controller 41 * PS4} controllers or {@link edu.wpi.first.wpilibj2.command.button.CommandJoystick Flight 42 * joysticks}. 43 */ 44 private void configureBindings () { 45 // Schedule `ExampleCommand` when `exampleCondition` changes to `true` 46 new Trigger ( m_exampleSubsystem :: exampleCondition ) 47 . onTrue ( new ExampleCommand ( m_exampleSubsystem )); 48 49 // Schedule `exampleMethodCommand` when the Xbox controller's B button is pressed, 50 // cancelling on release. 51 m_driverController . b (). whileTrue ( m_exampleSubsystem . exampleMethodCommand ()); 52 } C++ (Source) 19 void RobotContainer::ConfigureBindings () { 20 // Configure your trigger bindings here 21 22 // Schedule `ExampleCommand` when `exampleCondition` changes to `true` 23 frc2 :: Trigger ([ this ] { 24 return m_subsystem . ExampleCondition (); 25 }). OnTrue ( ExampleCommand ( & m_subsystem ). ToPtr ()); 26 27 // Schedule `ExampleMethodCommand` when the Xbox controller's B button is 28 // pressed, cancelling on release. 29 m_driverController . B (). WhileTrue ( m_subsystem . ExampleMethodCommand ()); 30 } As mentioned before, the RobotContainer() constructor is where most of the declarative setup for the robot should take place, including button bindings, configuring autonomous selectors, etc. If the constructor gets too “busy,” users are encouraged to migrate code into separate subroutines (such as the configureBindings() method included by default) which are called from the constructor. Java 54 /** 55 * Use this to pass the autonomous command to the main {@link Robot} class. 56 * 57 * @return the command to run in autonomous 58 */ 59 public Command getAutonomousCommand () { 60 // An example command will be run in autonomous 61 return Autos . exampleAuto ( m_exampleSubsystem ); 62 } 63 } C++ (Source) 32 frc2 :: CommandPtr RobotContainer::GetAutonomousCommand () { 33 // An example command will be run in autonomous 34 return autos :: ExampleAuto ( & m_subsystem ); 35 } Finally, the getAutonomousCommand() method provides a convenient way for users to send their selected autonomous command to the main Robot class (which needs access to it to schedule it when autonomous starts). Constants The Constants class ( Java , C++ (Header) ) (in C++ this is not a class, but simply a header file in which several namespaces are defined) is where globally-accessible robot constants (such as speeds, unit conversion factors, PID gains, and sensor/motor ports) can be stored. It is recommended that users separate these constants into individual inner classes corresponding to subsystems or robot modes, to keep variable names shorter. In Java, all constants should be declared public static final so that they are globally accessible and cannot be changed. In C++, all constants should be constexpr . For more illustrative examples of what a constants class should look like in practice, see those of the various command-based example projects: Hatchbot ( Java , C++ ) RapidReactCommandBot ( Java , C++ ) In Java, it is recommended that the constants be used from other classes by statically importing the necessary inner class. An import static statement imports the static namespace of a class into the class in which you are working, so that any static constants can be referenced directly as if they had been defined in that class. In C++, the same effect can be attained with using namespace : JAVA import static edu.wpi.first.wpilibj.templates.commandbased.Constants.OIConstants.* ; C++ using namespace OIConstants ; Subsystems User-defined subsystems should go in this package/directory. Commands User-defined commands should go in this package/directory.",
- "content_preview": "Structuring a Command-Based Robot Project While users are free to use the command-based libraries however they like (and advanced users are encouraged to do so), new users may want some guidance on how to structure a basic command-based robot project."
- },
- {
- "url": "https://docs.wpilib.org/en/stable/docs/software/commandbased/structuring-command-based-project.html?present",
- "title": "Structuring a Command",
+ "url": "https://docs.wpilib.org/en/stable/docs/software/commandbased/what-is-command-based.html",
+ "title": "What Is “Command",
"section": "Command-Based Programming",
"language": "All",
- "content": "Structuring a Command-Based Robot Project While users are free to use the command-based libraries however they like (and advanced users are encouraged to do so), new users may want some guidance on how to structure a basic command-based robot project. A standard template for a command-based robot project is included in the WPILib examples repository ( Java , C++ ). This section will walk users through the structure of this template. The root package/directory generally will contain four classes: Main , which is the main robot application (Java only). New users should not touch this class. Robot , which is responsible for the main control flow of the robot code. RobotContainer , which holds robot subsystems and commands, and is where most of the declarative robot setup (e.g. button bindings) is performed. Constants , which holds globally-accessible constants to be used throughout the robot. The root directory will also contain two sub-packages/sub-directories: Subsystems contains all user-defined subsystem classes. Commands contains all user-defined command classes. Robot As Robot ( Java , C++ (Header) , C++ (Source) ) is responsible for the program’s control flow, and command-based is an declarative paradigm designed to minimize the amount of attention the user has to pay to explicit program control flow, the Robot class of a command-based project should be mostly empty. However, there are a few important things that must be included Java 21 /** 22 * This function is run when the robot is first started up and should be used for any 23 * initialization code. 24 */ 25 public Robot () { 26 // Instantiate our RobotContainer. This will perform all our button bindings, and put our 27 // autonomous chooser on the dashboard. 28 m_robotContainer = new RobotContainer (); 29 } In Java, an instance of RobotContainer should be constructed during the Robot constructor - this is important, as most of the declarative robot setup will be called from the RobotContainer constructor. In C++, this is not needed as RobotContainer is a value member and will be constructed during the construction of Robot . Java 31 /** 32 * This function is called every 20 ms, no matter the mode. Use this for items like diagnostics 33 * that you want ran during disabled, autonomous, teleoperated and test. 34 * 35 *
This runs after the mode specific periodic functions, but before LiveWindow and 36 * SmartDashboard integrated updating. 37 */ 38 @Override 39 public void robotPeriodic () { 40 // Runs the Scheduler. This is responsible for polling buttons, adding newly-scheduled 41 // commands, running already-scheduled commands, removing finished or interrupted commands, 42 // and running subsystem periodic() methods. This must be called from the robot's periodic 43 // block in order for anything in the Command-based framework to work. 44 CommandScheduler . getInstance (). run (); 45 } C++ (Source) 11 /** 12 * This function is called every 20 ms, no matter the mode. Use 13 * this for items like diagnostics that you want to run during disabled, 14 * autonomous, teleoperated and test. 15 * 16 *
This runs after the mode specific periodic functions, but before 17 * LiveWindow and SmartDashboard integrated updating. 18 */ 19 void Robot::RobotPeriodic () { 20 frc2 :: CommandScheduler :: GetInstance (). Run (); 21 } The inclusion of the CommandScheduler.getInstance().run() call in the robotPeriodic() method is essential; without this call, the scheduler will not execute any scheduled commands. Since TimedRobot runs with a default main loop frequency of 50Hz, this is the frequency with which periodic command and subsystem methods will be called. It is not recommended for new users to call this method from anywhere else in their code. Java 54 /** This autonomous runs the autonomous command selected by your {@link RobotContainer} class. */ 55 @Override 56 public void autonomousInit () { 57 m_autonomousCommand = m_robotContainer . getAutonomousCommand (); 58 59 // schedule the autonomous command (example) 60 if ( m_autonomousCommand != null ) { 61 CommandScheduler . getInstance (). schedule ( m_autonomousCommand ); 62 } 63 } C++ (Source) 32 /** 33 * This autonomous runs the autonomous command selected by your {@link 34 * RobotContainer} class. 35 */ 36 void Robot::AutonomousInit () { 37 m_autonomousCommand = m_container . GetAutonomousCommand (); 38 39 if ( m_autonomousCommand ) { 40 frc2 :: CommandScheduler :: GetInstance (). Schedule ( m_autonomousCommand . value ()); 41 } 42 } The autonomousInit() method schedules an autonomous command returned by the RobotContainer instance. The logic for selecting which autonomous command to run can be handled inside of RobotContainer . Java 69 @Override 70 public void teleopInit () { 71 // This makes sure that the autonomous stops running when 72 // teleop starts running. If you want the autonomous to 73 // continue until interrupted by another command, remove 74 // this line or comment it out. 75 if ( m_autonomousCommand != null ) { 76 m_autonomousCommand . cancel (); 77 } 78 } C++ (Source) 46 void Robot::TeleopInit () { 47 // This makes sure that the autonomous stops running when 48 // teleop starts running. If you want the autonomous to 49 // continue until interrupted by another command, remove 50 // this line or comment it out. 51 if ( m_autonomousCommand ) { 52 m_autonomousCommand -> Cancel (); 53 } 54 } The teleopInit() method cancels any still-running autonomous commands. This is generally good practice. Advanced users are free to add additional code to the various init and periodic methods as they see fit; however, it should be noted that including large amounts of imperative robot code in Robot.java is contrary to the declarative design philosophy of the command-based paradigm, and can result in confusingly-structured/disorganized code. RobotContainer This class ( Java , C++ (Header) , C++ (Source) ) is where most of the setup for your command-based robot will take place. In this class, you will define your robot’s subsystems and commands, bind those commands to triggering events (such as buttons), and specify which command you will run in your autonomous routine. There are a few aspects of this class new users may want explanations for: Java 23 private final ExampleSubsystem m_exampleSubsystem = new ExampleSubsystem (); C++ (Header) 32 ExampleSubsystem m_subsystem ; Notice that subsystems are declared as private fields in RobotContainer . This is in stark contrast to the previous incarnation of the command-based framework, but is much more-aligned with agreed-upon object-oriented best-practices. If subsystems are declared as global variables, it allows the user to access them from anywhere in the code. While this can make certain things easier (for example, there would be no need to pass subsystems to commands in order for those commands to access them), it makes the control flow of the program much harder to keep track of as it is not immediately obvious which parts of the code can change or be changed by which other parts of the code. This also circumvents the ability of the resource-management system to do its job, as ease-of-access makes it easy for users to accidentally make conflicting calls to subsystem methods outside of the resource-managed commands. Java 61 return Autos . exampleAuto ( m_exampleSubsystem ); C++ (Source) 34 return autos :: ExampleAuto ( & m_subsystem ); Since subsystems are declared as private members, they must be explicitly passed to commands (a pattern called “dependency injection”) in order for those commands to call methods on them. This is done here with ExampleCommand , which is passed a pointer to an ExampleSubsystem . Java 35 /** 36 * Use this method to define your trigger->command mappings. Triggers can be created via the 37 * {@link Trigger#Trigger(java.util.function.BooleanSupplier)} constructor with an arbitrary 38 * predicate, or via the named factories in {@link 39 * edu.wpi.first.wpilibj2.command.button.CommandGenericHID}'s subclasses for {@link 40 * CommandXboxController Xbox}/{@link edu.wpi.first.wpilibj2.command.button.CommandPS4Controller 41 * PS4} controllers or {@link edu.wpi.first.wpilibj2.command.button.CommandJoystick Flight 42 * joysticks}. 43 */ 44 private void configureBindings () { 45 // Schedule `ExampleCommand` when `exampleCondition` changes to `true` 46 new Trigger ( m_exampleSubsystem :: exampleCondition ) 47 . onTrue ( new ExampleCommand ( m_exampleSubsystem )); 48 49 // Schedule `exampleMethodCommand` when the Xbox controller's B button is pressed, 50 // cancelling on release. 51 m_driverController . b (). whileTrue ( m_exampleSubsystem . exampleMethodCommand ()); 52 } C++ (Source) 19 void RobotContainer::ConfigureBindings () { 20 // Configure your trigger bindings here 21 22 // Schedule `ExampleCommand` when `exampleCondition` changes to `true` 23 frc2 :: Trigger ([ this ] { 24 return m_subsystem . ExampleCondition (); 25 }). OnTrue ( ExampleCommand ( & m_subsystem ). ToPtr ()); 26 27 // Schedule `ExampleMethodCommand` when the Xbox controller's B button is 28 // pressed, cancelling on release. 29 m_driverController . B (). WhileTrue ( m_subsystem . ExampleMethodCommand ()); 30 } As mentioned before, the RobotContainer() constructor is where most of the declarative setup for the robot should take place, including button bindings, configuring autonomous selectors, etc. If the constructor gets too “busy,” users are encouraged to migrate code into separate subroutines (such as the configureBindings() method included by default) which are called from the constructor. Java 54 /** 55 * Use this to pass the autonomous command to the main {@link Robot} class. 56 * 57 * @return the command to run in autonomous 58 */ 59 public Command getAutonomousCommand () { 60 // An example command will be run in autonomous 61 return Autos . exampleAuto ( m_exampleSubsystem ); 62 } 63 } C++ (Source) 32 frc2 :: CommandPtr RobotContainer::GetAutonomousCommand () { 33 // An example command will be run in autonomous 34 return autos :: ExampleAuto ( & m_subsystem ); 35 } Finally, the getAutonomousCommand() method provides a convenient way for users to send their selected autonomous command to the main Robot class (which needs access to it to schedule it when autonomous starts). Constants The Constants class ( Java , C++ (Header) ) (in C++ this is not a class, but simply a header file in which several namespaces are defined) is where globally-accessible robot constants (such as speeds, unit conversion factors, PID gains, and sensor/motor ports) can be stored. It is recommended that users separate these constants into individual inner classes corresponding to subsystems or robot modes, to keep variable names shorter. In Java, all constants should be declared public static final so that they are globally accessible and cannot be changed. In C++, all constants should be constexpr . For more illustrative examples of what a constants class should look like in practice, see those of the various command-based example projects: Hatchbot ( Java , C++ ) RapidReactCommandBot ( Java , C++ ) In Java, it is recommended that the constants be used from other classes by statically importing the necessary inner class. An import static statement imports the static namespace of a class into the class in which you are working, so that any static constants can be referenced directly as if they had been defined in that class. In C++, the same effect can be attained with using namespace : JAVA import static edu.wpi.first.wpilibj.templates.commandbased.Constants.OIConstants.* ; C++ using namespace OIConstants ; Subsystems User-defined subsystems should go in this package/directory. Commands User-defined commands should go in this package/directory.",
- "content_preview": "Structuring a Command-Based Robot Project While users are free to use the command-based libraries however they like (and advanced users are encouraged to do so), new users may want some guidance on how to structure a basic command-based robot project."
+ "content": "What Is “Command-Based” Programming? WPILib supports a robot programming methodology called “command-based” programming. In general, “command-based” can refer both the general programming paradigm, and to the set of WPILib library resources included to facilitate it. “Command-based” programming is one possible design pattern for robot software. It is not the only way to write a robot program, but it is a very effective one. Command-based robot code tends to be clean, extensible, and (with some tricks) easy to reuse from year to year. The command-based paradigm is also an example of declarative programming . The command-based library allow users to define desired robot behaviors while minimizing the amount of iteration-by-iteration robot logic that they must write. For example, in the command-based program, a user can specify that “the robot should perform an action when a condition is true” (note the use of a lambda ): JAVA new Trigger ( condition :: get ). onTrue ( Commands . runOnce (() -> piston . set ( DoubleSolenoid . Value . kForward ))); C++ Trigger ([ & condition ] { return condition . Get (); }). OnTrue ( frc2 :: cmd :: RunOnce ([ & piston ] { piston . Set ( frc :: DoubleSolenoid :: kForward ); })); PYTHON Trigger ( condition . get ) . onTrue ( Commands . runOnce ( lambda : piston . set ( DoubleSolenoid . Value . kForward ))) In contrast, without using command-based, the user would need to check the button state every iteration, and perform the appropriate action based on the state of the button. JAVA if ( condition . get ()) { if ( ! pressed ) { piston . set ( DoubleSolenoid . Value . kForward ); pressed = true ; } } else { pressed = false ; } C++ if ( condition . Get ()) { if ( ! pressed ) { piston . Set ( frc :: DoubleSolenoid :: kForward ); pressed = true ; } } else { pressed = false ; } PYTHON if condition . get (): if not pressed : piston . set ( DoubleSolenoid . Value . kForward ) pressed = True else : pressed = False Subsystems and Commands The command-based pattern is based around two core abstractions: commands , and subsystems. Commands represent actions the robot can take. Commands run when scheduled, until they are interrupted or their end condition is met. Commands are very recursively composable: commands can be composed to accomplish more-complicated tasks. See Commands for more info. Subsystems represent independently-controlled collections of robot hardware (such as motor controllers, sensors, pneumatic actuators, etc.) that operate together. Subsystems back the resource-management system of command-based: only one command can use a given subsystem at the same time. Subsystems allow users to “hide” the internal complexity of their actual hardware from the rest of their code - this both simplifies the rest of the robot code, and allows changes to the internal details of a subsystem’s hardware without also changing the rest of the robot code. How Commands Are Run Note For a more detailed explanation, see The Command Scheduler . Commands are run by the CommandScheduler ( Java , C++ , Python ) singleton, which polls triggers (such as buttons) for commands to schedule, preventing resource conflicts, and executing scheduled commands. The scheduler’s run() method must be called; it is generally recommended to call it from the robotPeriodic() method of the Robot class, which is run at a default frequency of 50Hz (once every 20ms). Multiple commands can run concurrently, as long as they do not require the same resources on the robot. Resource management is handled on a per-subsystem basis: commands specify which subsystems they interact with, and the scheduler will ensure that no more more than one command requiring a given subsystem is scheduled at a time. This ensures that, for example, users will not end up with two different pieces of code attempting to set the same motor controller to different output values. Command Compositions It is often desirable to build complex commands from simple pieces. This is achievable by creating a composition of commands. The command-based library provides several types of command compositions for teams to use, and users may write their own. As command compositions are commands themselves, they may be used in a recursive composition . That is to say - one can create a command compositions from multiple command compositions. This provides an extremely powerful way of building complex robot actions from simple components.",
+ "content_preview": "What Is “Command-Based” Programming? WPILib supports a robot programming methodology called “command-based” programming. In general, “command-based” can refer both the general programming paradigm, and to the set of WPILib library resources included to facilitate it."
},
{
- "url": "https://docs.wpilib.org/en/stable/docs/software/commandbased/command-compositions.html",
- "title": "Command Compositions",
+ "url": "https://docs.wpilib.org/en/stable/docs/software/commandbased/commands.html",
+ "title": "Commands",
"section": "Command-Based Programming",
"language": "All",
- "content": "Command Compositions Individual commands are capable of accomplishing a large variety of robot tasks, but the simple three-state format can quickly become cumbersome when more advanced functionality requiring extended sequences of robot tasks or coordination of multiple robot subsystems is required. In order to accomplish this, users are encouraged to use the powerful command composition functionality included in the command-based library. As the name suggests, a command composition is a composition of one or more commands. This allows code to be kept much cleaner and simpler, as the individual component commands may be written independently of the code that combines them, greatly reducing the amount of complexity at any given step of the process. Most importantly, however, command compositions are themselves commands - they extend the Command class. This allows command compositions to be further composed as a recursive composition - that is, a command composition may contain other command compositions as components. This allows very powerful and concise inline expressions: JAVA // Will run fooCommand, and then a race between barCommand and bazCommand button . onTrue ( fooCommand . andThen ( barCommand . raceWith ( bazCommand ))); C++ // Will run fooCommand, and then a race between barCommand and bazCommand button . OnTrue ( std :: move ( fooCommand ). AndThen ( std :: move ( barCommand ). RaceWith ( std :: move ( bazCommand )))); PYTHON # Will run fooCommand, and then a race between barCommand and bazCommand button . onTrue ( fooCommand . andThen ( barCommand . raceWith ( bazCommand ))) As a rule, command compositions require all subsystems their components require, may run when disabled if all their component set runsWhenDisabled as true , and are kCancelIncoming if all their components are kCancelIncoming as well. Command instances that have been passed to a command composition cannot be independently scheduled or passed to a second command composition. Attempting to do so will throw an exception and crash the user program. This is because composition members are run through their encapsulating command composition, and errors could occur if those same command instances were independently scheduled at the same time as the composition - the command would be being run from multiple places at once, and thus could end up with inconsistent internal state, causing unexpected and hard-to-diagnose behavior. The C++ command-based library uses CommandPtr , a class with move-only semantics, so this type of mistake is easier to avoid. Composition Types The command-based library includes various composition types. All of them can be constructed using factories that accept the member commands, and some can also be constructed using decorators: methods that can be called on a command object, which is transformed into a new object that is returned. Important After calling a decorator or being passed to a composition, the command object cannot be reused! Use only the command object returned from the decorator. Repeating The repeatedly() decorator ( Java , C++ , Python ), backed by the RepeatCommand class ( Java , C++ , Python ) restarts the command each time it ends, so that it runs until interrupted. JAVA // Will run forever unless externally interrupted, restarting every time command.isFinished() returns true Command repeats = command . repeatedly (); C++ // Will run forever unless externally interrupted, restarting every time command.IsFinished() returns true frc2 :: CommandPtr repeats = std :: move ( command ). Repeatedly (); PYTHON # Will run forever unless externally interrupted, restarting every time command.IsFinished() returns true repeats = command . repeatedly () Sequence The Sequence factory ( Java , C++ , Python ), backed by the SequentialCommandGroup class ( Java , C++ , Python ), runs a list of commands in sequence: the first command will be executed, then the second, then the third, and so on until the list finishes. The sequential group finishes after the last command in the sequence finishes. It is therefore usually important to ensure that each command in the sequence does actually finish (if a given command does not finish, the next command will never start!). The andThen() ( Java , C++ , Python ) and beforeStarting() ( Java , C++ , Python ) decorators can be used to construct a sequence composition with infix syntax. JAVA fooCommand . andThen ( barCommand ) C++ std :: move ( fooCommand ). AndThen ( std :: move ( barCommand )) PYTHON fooCommand . andThen ( barCommand ) Repeating Sequence As it’s a fairly common combination, the RepeatingSequence factory ( Java , C++ , Python ) creates a Repeating Sequence that runs until interrupted, restarting from the first command each time the last command finishes. Parallel There are three types of parallel compositions, differing based on when the composition finishes: The Parallel factory ( Java , C++ , Python ), backed by the ParallelCommandGroup class ( Java , C++ , Python ), constructs a parallel composition that finishes when all members finish. The alongWith decorator ( Java , C++ , Python ) does the same in infix notation. The Race factory ( Java , C++ , Python ), backed by the ParallelRaceGroup class ( Java , C++ , Python ), constructs a parallel composition that finishes as soon as any member finishes; all other members are interrupted at that point. The raceWith decorator ( Java , C++ , Python ) does the same in infix notation. The Deadline factory ( Java , C++ , Python ), ParallelDeadlineGroup ( Java , C++ , Python ) finishes when a specific command (the “deadline”) ends; all other members still running at that point are interrupted. The deadlineWith decorator ( Java , C++ , Python ) does the same in infix notation; the command the decorator was called on is the deadline. JAVA // Will be a parallel command composition that ends after three seconds with all three commands running their full duration. button . onTrue ( Commands . parallel ( twoSecCommand , oneSecCommand , threeSecCommand )); // Will be a parallel race composition that ends after one second with the two and three second commands getting interrupted. button . onTrue ( Commands . race ( twoSecCommand , oneSecCommand , threeSecCommand )); // Will be a parallel deadline composition that ends after two seconds (the deadline) with the three second command getting interrupted (one second command already finished). button . onTrue ( Commands . deadline ( twoSecCommand , oneSecCommand , threeSecCommand )); C++ // Will be a parallel command composition that ends after three seconds with all three commands running their full duration. button . OnTrue ( frc2 :: cmd :: Parallel ( std :: move ( twoSecCommand ), std :: move ( oneSecCommand ), std :: move ( threeSecCommand ))); // Will be a parallel race composition that ends after one second with the two and three second commands getting interrupted. button . OnTrue ( frc2 :: cmd :: Race ( std :: move ( twoSecCommand ), std :: move ( oneSecCommand ), std :: move ( threeSecCommand ))); // Will be a parallel deadline composition that ends after two seconds (the deadline) with the three second command getting interrupted (one second command already finished). button . OnTrue ( frc2 :: cmd :: Deadline ( std :: move ( twoSecCommand ), std :: move ( oneSecCommand ), std :: move ( threeSecCommand ))); PYTHON # Will be a parallel command composition that ends after three seconds with all three commands running their full duration. button . onTrue ( commands2 . cmd . parallel ( twoSecCommand , oneSecCommand , threeSecCommand )) # Will be a parallel race composition that ends after one second with the two and three second commands getting interrupted. button . onTrue ( commands2 . cmd . race ( twoSecCommand , oneSecCommand , threeSecCommand )) # Will be a parallel deadline composition that ends after two seconds (the deadline) with the three second command getting interrupted (one second command already finished). button . onTrue ( commands2 . cmd . deadline ( twoSecCommand , oneSecCommand , threeSecCommand )) Adding Command End Conditions The until() ( Java , C++ , Python ) decorator composes the command with an additional end condition. Note that the command the decorator was called on will see this end condition as an interruption. JAVA // Will be interrupted if m_limitSwitch.get() returns true button . onTrue ( command . until ( m_limitSwitch :: get )); C++ // Will be interrupted if m_limitSwitch.get() returns true button . OnTrue ( command . Until ([ & m_limitSwitch ] { return m_limitSwitch . Get (); })); PYTHON # Will be interrupted if limitSwitch.get() returns true button . onTrue ( commands2 . cmd . until ( limitSwitch . get )) The withTimeout() decorator ( Java , C++ , Python ) is a specialization of until that uses a timeout as the additional end condition. JAVA // Will time out 5 seconds after being scheduled, and be interrupted button . onTrue ( command . withTimeout ( 5 )); C++ // Will time out 5 seconds after being scheduled, and be interrupted button . OnTrue ( command . WithTimeout ( 5.0 _s )); PYTHON # Will time out 5 seconds after being scheduled, and be interrupted button . onTrue ( commands2 . cmd . withTimeout ( 5.0 )) Adding End Behavior The finallyDo() ( Java , C++ , Python ) decorator composes the command with an a lambda that will be called after the command’s end() method, with the same boolean parameter indicating whether the command finished or was interrupted. The handleInterrupt() ( Java , C++ , Python ) decorator composes the command with an a lambda that will be called only when the command is interrupted. Selecting Compositions Sometimes it’s desired to run a command out of a few options based on sensor feedback or other data known only at runtime. This can be useful for determining an auto routine, or running a different command based on whether a game piece is present or not, and so on. The Select factory ( Java , C++ , Python ), backed by the SelectCommand class ( Java , C++ , Python ), executes one command from a map, based on a selector function called when scheduled. Java 20 public class RobotContainer { 21 // The enum used as keys for selecting the command to run. 22 private enum CommandSelector { 23 ONE , 24 TWO , 25 THREE 26 } 27 28 // An example selector method for the selectcommand. Returns the selector that will select 29 // which command to run. Can base this choice on logical conditions evaluated at runtime. 30 private CommandSelector select () { 31 return CommandSelector . ONE ; 32 } 33 34 // An example selectcommand. Will select from the three commands based on the value returned 35 // by the selector method at runtime. Note that selectcommand works on Object(), so the 36 // selector does not have to be an enum; it could be any desired type (string, integer, 37 // boolean, double...) 38 private final Command m_exampleSelectCommand = 39 new SelectCommand <> ( 40 // Maps selector values to commands 41 Map . ofEntries ( 42 Map . entry ( CommandSelector . ONE , new PrintCommand ( \"Command one was selected!\" )), 43 Map . entry ( CommandSelector . TWO , new PrintCommand ( \"Command two was selected!\" )), 44 Map . entry ( CommandSelector . THREE , new PrintCommand ( \"Command three was selected!\" ))), 45 this :: select ); C++ (Header) 26 // The enum used as keys for selecting the command to run. 27 enum CommandSelector { ONE , TWO , THREE }; 28 29 // An example of how command selector may be used with SendableChooser 30 frc :: SendableChooser < CommandSelector > m_chooser ; 31 32 // The robot's subsystems and commands are defined here... 33 34 // An example selectcommand. Will select from the three commands based on the 35 // value returned by the selector method at runtime. Note that selectcommand 36 // takes a generic type, so the selector does not have to be an enum; it could 37 // be any desired type (string, integer, boolean, double...) 38 frc2 :: CommandPtr m_exampleSelectCommand = frc2 :: cmd :: Select < CommandSelector > ( 39 [ this ] { return m_chooser . GetSelected (); }, 40 // Maps selector values to commands 41 std :: pair { ONE , frc2 :: cmd :: Print ( \"Command one was selected!\" )}, 42 std :: pair { TWO , frc2 :: cmd :: Print ( \"Command two was selected!\" )}, 43 std :: pair { THREE , frc2 :: cmd :: Print ( \"Command three was selected!\" )}); The Either factory ( Java , C++ , Python ), backed by the ConditionalCommand class ( Java , C++ , Python ), is a specialization accepting two commands and a boolean selector function. JAVA // Runs either commandOnTrue or commandOnFalse depending on the value of m_limitSwitch.get() new ConditionalCommand ( commandOnTrue , commandOnFalse , m_limitSwitch :: get ) C++ // Runs either commandOnTrue or commandOnFalse depending on the value of m_limitSwitch.get() frc2 :: ConditionalCommand ( commandOnTrue , commandOnFalse , [ & m_limitSwitch ] { return m_limitSwitch . Get (); }) PYTHON # Runs either commandOnTrue or commandOnFalse depending on the value of limitSwitch.get() ConditionalCommand ( commandOnTrue , commandOnFalse , limitSwitch . get ) The unless() decorator ( Java , C++ , Python ) composes a command with a condition that will prevent it from running. JAVA // Command will only run if the intake is deployed. If the intake gets deployed while the command is running, the command will not stop running button . onTrue ( command . unless (() -> ! intake . isDeployed ())); C++ // Command will only run if the intake is deployed. If the intake gets deployed while the command is running, the command will not stop running button . OnTrue ( command . Unless ([ & intake ] { return ! intake . IsDeployed (); })); PYTHON # Command will only run if the intake is deployed. If the intake gets deployed while the command is running, the command will not stop running button . onTrue ( command . unless ( lambda : not intake . isDeployed ())) ProxyCommand described below also has a constructor overload ( Java , C++ , Python ) that calls a command-returning lambda at schedule-time and runs the returned command by proxy. Scheduling Other Commands By default, composition members are run through the command composition, and are never themselves seen by the scheduler. Accordingly, their requirements are added to the composition’s requirements. While this is usually fine, sometimes it is undesirable for the entire command composition to gain the requirements of a single command. A good solution is to “fork off” from the command composition and schedule that command separately. However, this requires synchronization between the composition and the individually-scheduled command. ProxyCommand ( Java , C++ , Python ), also creatable using the .asProxy() decorator ( Java , C++ , Python ), schedules a command “by proxy”: the command is scheduled when the proxy is scheduled, and the proxy finishes when the command finishes. In the case of “forking off” from a command composition, this allows the composition to track the command’s progress without it being in the composition. Command compositions inherit the union of their compoments’ requirements and requirements are immutable. Therefore, a SequentialCommandGroup ( Java , C++ , Python ) that intakes a game piece, indexes it, aims a shooter, and shoots it would reserve all three subsystems (the intake, indexer, and shooter), precluding any of those subsystems from performing other operations in their “downtime”. If this is not desired, the subsystems that should only be reserved for the composition while they are actively being used by it should have their commands proxied. Warning Do not use ProxyCommand unless you are sure of what you are doing and there is no other way to accomplish your need! Proxying is only intended for use as an escape hatch from command composition requirement unions. Note Because proxied commands still require their subsystem, despite not leaking that requirement to the composition, all of the commands that require a given subsystem must be proxied if one of them is. Otherwise, when the proxied command is scheduled its requirement will conflict with that of the composition, canceling the composition. JAVA // composition requirements are indexer and shooter, intake still reserved during its command but not afterwards Commands . sequence ( intake . intakeGamePiece (). asProxy (), // we want to let the intake intake another game piece while we are processing this one indexer . processGamePiece (), shooter . aimAndShoot () ); C++ // composition requirements are indexer and shooter, intake still reserved during its command but not afterwards frc2 :: cmd :: Sequence ( intake . IntakeGamePiece (). AsProxy (), // we want to let the intake intake another game piece while we are processing this one indexer . ProcessGamePiece (), shooter . AimAndShoot () ); PYTHON # composition requirements are indexer and shooter, intake still reserved during its command but not afterwards commands2 . cmd . sequence ( intake . intakeGamePiece () . asProxy (), # we want to let the intake intake another game piece while we are processing this one indexer . processGamePiece (), shooter . aimAndShoot () ) For cases that don’t need to track the proxied command, ScheduleCommand ( Java , C++ , Python ) schedules a specified command and ends instantly. JAVA // ScheduleCommand ends immediately, so the sequence continues new ScheduleCommand ( Commands . waitSeconds ( 5.0 )) . andThen ( Commands . print ( \"This will be printed immediately!\" )) C++ // ScheduleCommand ends immediately, so the sequence continues frc2 :: ScheduleCommand ( frc2 :: cmd :: Wait ( 5.0 _s )) . AndThen ( frc2 :: cmd :: Print ( \"This will be printed immediately!\" )) PYTHON # ScheduleCommand ends immediately, so the sequence continues ScheduleCommand ( commands2 . cmd . waitSeconds ( 5.0 )) . andThen ( commands2 . cmd . print ( \"This will be printed immediately!\" )) Subclassing Compositions Command compositions can also be written as a constructor-only subclass of the most exterior composition type, passing the composition members to the superclass constructor. Consider the following from the Hatch Bot example project ( Java , C++ ): Java 5 package edu.wpi.first.wpilibj.examples.hatchbottraditional.commands ; 6 7 import edu.wpi.first.wpilibj.examples.hatchbottraditional.Constants.AutoConstants ; 8 import edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems.DriveSubsystem ; 9 import edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems.HatchSubsystem ; 10 import edu.wpi.first.wpilibj2.command.SequentialCommandGroup ; 11 12 /** A complex auto command that drives forward, releases a hatch, and then drives backward. */ 13 public class ComplexAuto extends SequentialCommandGroup { 14 /** 15 * Creates a new ComplexAuto. 16 * 17 * @param drive The drive subsystem this command will run on 18 * @param hatch The hatch subsystem this command will run on 19 */ 20 public ComplexAuto ( DriveSubsystem drive , HatchSubsystem hatch ) { 21 addCommands ( 22 // Drive forward the specified distance 23 new DriveDistance ( 24 AutoConstants . kAutoDriveDistanceInches , AutoConstants . kAutoDriveSpeed , drive ), 25 26 // Release the hatch 27 new ReleaseHatch ( hatch ), 28 29 // Drive backward the specified distance 30 new DriveDistance ( 31 AutoConstants . kAutoBackupDistanceInches , - AutoConstants . kAutoDriveSpeed , drive )); 32 } 33 } C++ (Header) 5 #pragma once 6 7 #include 8 #include 9 10 #include \"Constants.h\" 11 #include \"commands/DriveDistance.h\" 12 #include \"commands/ReleaseHatch.h\" 13 14 /** 15 * A complex auto command that drives forward, releases a hatch, and then drives 16 * backward. 17 */ 18 class ComplexAuto 19 : public frc2 :: CommandHelper < frc2 :: SequentialCommandGroup , ComplexAuto > { 20 public : 21 /** 22 * Creates a new ComplexAuto. 23 * 24 * @param drive The drive subsystem this command will run on 25 * @param hatch The hatch subsystem this command will run on 26 */ 27 ComplexAuto ( DriveSubsystem * drive , HatchSubsystem * hatch ); 28 }; C++ (Source) 5 #include \"commands/ComplexAuto.h\" 6 7 using namespace AutoConstants ; 8 9 ComplexAuto :: ComplexAuto ( DriveSubsystem * drive , HatchSubsystem * hatch ) { 10 AddCommands ( 11 // Drive forward the specified distance 12 DriveDistance ( kAutoDriveDistanceInches , kAutoDriveSpeed , drive ), 13 // Release the hatch 14 ReleaseHatch ( hatch ), 15 // Drive backward the specified distance 16 DriveDistance ( kAutoBackupDistanceInches , - kAutoDriveSpeed , drive )); 17 } Python 7 import commands2 8 9 import constants 10 11 from .drivedistance import DriveDistance 12 from .releasehatch import ReleaseHatch 13 14 from subsystems.drivesubsystem import DriveSubsystem 15 from subsystems.hatchsubsystem import HatchSubsystem 16 17 18 class ComplexAuto ( commands2 . SequentialCommandGroup ): 19 \"\"\" 20 A complex auto command that drives forward, releases a hatch, and then drives backward. 21 \"\"\" 22 23 def __init__ ( self , drive : DriveSubsystem , hatch : HatchSubsystem ): 24 super () . __init__ ( 25 # Drive forward the specified distance 26 DriveDistance ( 27 constants . kAutoDriveDistanceInches , constants . kAutoDriveSpeed , drive 28 ), 29 # Release the hatch 30 ReleaseHatch ( hatch ), 31 # Drive backward the specified distance 32 DriveDistance ( 33 constants . kAutoBackupDistanceInches , - constants . kAutoDriveSpeed , drive 34 ), 35 ) The advantages and disadvantages of this subclassing approach in comparison to others are discussed in Subclassing Command Groups .",
- "content_preview": "Command Compositions Individual commands are capable of accomplishing a large variety of robot tasks, but the simple three-state format can quickly become cumbersome when more advanced functionality requiring extended sequences of robot tasks or coordination of multiple robot subsystems is..."
+ "content": "Commands Commands represent actions the robot can take. Commands run when scheduled, until they are interrupted or their end condition is met. Commands are represented in the command-based library by the Command class ( Java , C++ ) or the Command class in commands2 library ( Python ). The Structure of a Command Commands specify what the command will do in each of its possible states. This is done by overriding the initialize() , execute() , and end() methods. Additionally, a command must be able to tell the scheduler when (if ever) it has finished execution - this is done by overriding the isFinished() method. All of these methods are defaulted to reduce clutter in user code: initialize() , execute() , and end() are defaulted to simply do nothing, while isFinished() is defaulted to return false (resulting in a command that never finishes naturally, and will run until interrupted). Initialization The initialize() method ( Java , C++ , Python ) marks the command start, and is called exactly once per time a command is scheduled. The initialize() method should be used to place the command in a known starting state for execution. Command objects may be reused and scheduled multiple times, so any state or resources needed for the command’s functionality should be initialized or opened in initialize (which will be called at the start of each use) rather than the constructor (which is invoked only once on object allocation). It is also useful for performing tasks that only need to be performed once per time scheduled, such as setting motors to run at a constant speed or setting the state of a solenoid actuator. Execution The execute() method ( Java , C++ , Python ) is called repeatedly while the command is scheduled; this is when the scheduler’s run() method is called (this is generally done in the main robot periodic method, which runs every 20ms by default). The execute block should be used for any task that needs to be done continually while the command is scheduled, such as updating motor outputs to match joystick inputs, or using the output of a control loop. Ending The end(bool interrupted) method ( Java , C++ , Python ) is called once when the command ends, whether it finishes normally (i.e. isFinished() returned true) or it was interrupted (either by another command or by being explicitly canceled). The method argument specifies the manner in which the command ended; users can use this to differentiate the behavior of their command end accordingly. The end block should be used to “wrap up” command state in a neat way, such as setting motors back to zero or reverting a solenoid actuator to a “default” state. Any state or resources initialized in initialize() should be closed in end() . Specifying end conditions The isFinished() method ( Java , C++ , Python ) is called repeatedly while the command is scheduled, whenever the scheduler’s run() method is called. As soon as it returns true, the command’s end() method is called and it ends. The isFinished() method is called after the execute() method, so the command will execute once on the same iteration that it ends. Command Properties In addition to the four lifecycle methods described above, each Command also has three properties, defined by getter methods that should always return the same value with no side affects. getRequirements Each command should declare any subsystems it controls as requirements. This backs the scheduler’s resource management mechanism, ensuring that no more than one command requires a given subsystem at the same time. This prevents situations such as two different pieces of code attempting to set the same motor controller to different output values. Declaring requirements is done by overriding the getRequirements() method in the relevant command class, by calling addRequirements() , or by using the requirements vararg (Java) / Requirements struct (C++) parameter / requirements argument (Python) at the end of the parameter list of most command constructors and factories in the library: JAVA Commands . run ( intake :: activate , intake ); C++ frc2 :: cmd :: Run ([ & intake ] { intake . Activate (); }, { & intake }); PYTHON commands2 . cmd . run ( intake . activate , intake ) As a rule, command compositions require all subsystems their components require. runsWhenDisabled The runsWhenDisabled() method ( Java , C++ , Python ) returns a boolean / bool specifying whether the command may run when the robot is disabled. With the default of returning false , the command will be canceled when the robot is disabled and attempts to schedule it will do nothing. Returning true will allow the command to run and be scheduled when the robot is disabled. Important When the robot is disabled, PWM outputs are disabled and CAN motor controllers may not apply voltage, regardless of runsWhenDisabled ! This property can be set either by overriding the runsWhenDisabled() method in the relevant command class, or by using the ignoringDisable decorator ( Java , C++ , Python ): JAVA Command mayRunDuringDisabled = Commands . run (() -> updateTelemetry ()). ignoringDisable ( true ); C++ frc2 :: CommandPtr mayRunDuringDisabled = frc2 :: cmd :: Run ([] { UpdateTelemetry (); }). IgnoringDisable ( true ); PYTHON may_run_during_disabled = commands2 . cmd . run ( lambda : update_telemetry ()) . ignoring_disable ( True ) As a rule, command compositions may run when disabled if all their component commands set runsWhenDisabled as true . getInterruptionBehavior The getInterruptionBehavior() method ( Java , C++ , Python ) defines what happens if another command sharing a requirement is scheduled while this one is running. In the default behavior, kCancelSelf , the current command will be canceled and the incoming command will be scheduled successfully. If kCancelIncoming is returned, the incoming command’s scheduling will be aborted and this command will continue running. Note that getInterruptionBehavior only affects resolution of requirement conflicts: all commands can be canceled, regardless of getInterruptionBehavior . Note This was previously controlled by the interruptible parameter passed when scheduling a command, and is now a property of the command object. This property can be set either by overriding the getInterruptionBehavior method in the relevant command class, or by using the withInterruptBehavior() decorator ( Java , C++ , Python ) JAVA Command noninteruptible = Commands . run ( intake :: activate , intake ). withInterruptBehavior ( Command . InterruptBehavior . kCancelIncoming ); C++ frc2 :: CommandPtr noninterruptible = frc2 :: cmd :: Run ([ & intake ] { intake . Activate (); }, { & intake }). WithInterruptBehavior ( Command :: InterruptBehavior :: kCancelIncoming ); PYTHON non_interruptible = commands2 . cmd . run ( intake . activate , intake ) . with_interrupt_behavior ( Command . InterruptBehavior . kCancelIncoming ) As a rule, command compositions are kCancelIncoming if all their components are kCancelIncoming as well. Included Command Types The command-based library includes many pre-written command types. Through the use of lambdas , these commands can cover almost all use cases and teams should rarely need to write custom command classes. Many of these commands are provided via static factory functions in the Commands utility class (Java), in the frc2::cmd namespace defined in the Commands.h header (C++), or in the commands2.cmd namespace (Python). In Java and C++, classes inheriting from Subsystem also have instance methods that implicitly require this . Running Actions The most basic commands are actions the robot takes: setting voltage to a motor, changing a solenoid’s direction, etc. For these commands, which typically consist of a method call or two, the command-based library offers several factories to be construct commands inline with one or more lambdas to be executed. The runOnce factory, backed by the InstantCommand ( Java , C++ , Python ) class, creates a command that calls a lambda once, and then finishes. Java 25 /** Grabs the hatch. */ 26 public Command grabHatchCommand () { 27 // implicitly require `this` 28 return this . runOnce (() -> m_hatchSolenoid . set ( kForward )); 29 } 30 31 /** Releases the hatch. */ 32 public Command releaseHatchCommand () { 33 // implicitly require `this` 34 return this . runOnce (() -> m_hatchSolenoid . set ( kReverse )); 35 } C++ (Header) 20 /** 21 * Grabs the hatch. 22 */ 23 frc2 :: CommandPtr GrabHatchCommand (); 24 25 /** 26 * Releases the hatch. 27 */ 28 frc2 :: CommandPtr ReleaseHatchCommand (); C++ (Source) 15 frc2 :: CommandPtr HatchSubsystem::GrabHatchCommand () { 16 // implicitly require `this` 17 return this -> RunOnce ( 18 [ this ] { m_hatchSolenoid . Set ( frc :: DoubleSolenoid :: kForward ); }); 19 } 20 21 frc2 :: CommandPtr HatchSubsystem::ReleaseHatchCommand () { 22 // implicitly require `this` 23 return this -> RunOnce ( 24 [ this ] { m_hatchSolenoid . Set ( frc :: DoubleSolenoid :: kReverse ); }); 25 } Python 24 def grabHatch ( self ) -> commands2 . Command : 25 \"\"\"Grabs the hatch\"\"\" 26 return commands2 . cmd . runOnce ( 27 lambda : self . hatchSolenoid . set ( wpilib . DoubleSolenoid . Value . kForward ), self 28 ) 29 30 def releaseHatch ( self ) -> commands2 . Command : 31 \"\"\"Releases the hatch\"\"\" 32 return commands2 . cmd . runOnce ( 33 lambda : self . hatchSolenoid . set ( wpilib . DoubleSolenoid . Value . kReverse ), self 34 ) The run factory, backed by the RunCommand ( Java , C++ , Python ) class, creates a command that calls a lambda repeatedly, until interrupted. JAVA // A split-stick arcade command, with forward/backward controlled by the left // hand, and turning controlled by the right. new RunCommand (() -> m_robotDrive . arcadeDrive ( - driverController . getLeftY (), driverController . getRightX ()), m_robotDrive ) C++ // A split-stick arcade command, with forward/backward controlled by the left // hand, and turning controlled by the right. frc2 :: RunCommand ( [ this ] { m_drive . ArcadeDrive ( - m_driverController . GetLeftY (), m_driverController . GetRightX ()); }, { & m_drive }) PYTHON # A split-stick arcade command, with forward/backward controlled by the left # hand, and turning controlled by the right. commands2 . cmd . run ( lambda : robot_drive . arcade_drive ( - driver_controller . get_left_y (), driver_controller . get_right_x ()), robot_drive ) The startEnd factory, backed by the StartEndCommand ( Java , C++ , Python ) class, calls one lambda when scheduled, and then a second lambda when interrupted. JAVA Commands . startEnd ( // Start a flywheel spinning at 50% power () -> m_shooter . shooterSpeed ( 0.5 ), // Stop the flywheel at the end of the command () -> m_shooter . shooterSpeed ( 0.0 ), // Requires the shooter subsystem m_shooter ) C++ frc2 :: cmd :: StartEnd ( // Start a flywheel spinning at 50% power [ this ] { m_shooter . shooterSpeed ( 0.5 ); }, // Stop the flywheel at the end of the command [ this ] { m_shooter . shooterSpeed ( 0.0 ); }, // Requires the shooter subsystem { & m_shooter } ) PYTHON commands2 . cmd . start_end ( # Start a flywheel spinning at 50% power lambda : shooter . shooter_speed ( 0.5 ), # Stop the flywheel at the end of the command lambda : shooter . shooter_speed ( 0.0 ), # Requires the shooter subsystem shooter ) FunctionalCommand ( Java , C++ , Python ) accepts four lambdas that constitute the four command lifecycle methods: a Runnable / std::function/Callable for each of initialize() and execute() , a BooleanConsumer / std::function/Callable[bool,[]] for end() , and a BooleanSupplier / std::function/Callable[[],bool] for isFinished() . JAVA new FunctionalCommand ( // Reset encoders on command start m_robotDrive :: resetEncoders , // Start driving forward at the start of the command () -> m_robotDrive . arcadeDrive ( kAutoDriveSpeed , 0 ), // Stop driving at the end of the command interrupted -> m_robotDrive . arcadeDrive ( 0 , 0 ), // End the command when the robot's driven distance exceeds the desired value () -> m_robotDrive . getAverageEncoderDistance () >= kAutoDriveDistanceInches , // Require the drive subsystem m_robotDrive ) C++ frc2 :: FunctionalCommand ( // Reset encoders on command start [ this ] { m_drive . ResetEncoders (); }, // Start driving forward at the start of the command [ this ] { m_drive . ArcadeDrive ( ac :: kAutoDriveSpeed , 0 ); }, // Stop driving at the end of the command [ this ] ( bool interrupted ) { m_drive . ArcadeDrive ( 0 , 0 ); }, // End the command when the robot's driven distance exceeds the desired value [ this ] { return m_drive . GetAverageEncoderDistance () >= kAutoDriveDistanceInches ; }, // Requires the drive subsystem { & m_drive } ) PYTHON commands2 . cmd . functional_command ( # Reset encoders on command start lambda : robot_drive . reset_encoders (), # Start driving forward at the start of the command lambda : robot_drive . arcade_drive ( ac . kAutoDriveSpeed , 0 ), # Stop driving at the end of the command lambda interrupted : robot_drive . arcade_drive ( 0 , 0 ), # End the command when the robot's driven distance exceeds the desired value lambda : robot_drive . get_average_encoder_distance () >= ac . kAutoDriveDistanceInches , # Require the drive subsystem robot_drive ) To print a string and ending immediately, the library offers the Commands.print(String) / frc2::cmd::Print(std::string_view) / commands2.cmd.print(String) factory, backed by the PrintCommand ( Java , C++ , Python ) subclass of InstantCommand . Waiting Waiting for a certain condition to happen or adding a delay can be useful to synchronize between different commands in a command composition or between other robot actions. To wait and end after a specified period of time elapses, the library offers the Commands.waitSeconds(double) / frc2::cmd::Wait(units::second_t) / commands2.cmd.wait(float) factory, backed by the WaitCommand ( Java , C++ , Python ) class. JAVA // Ends 5 seconds after being scheduled new WaitCommand ( 5.0 ) C++ // Ends 5 seconds after being scheduled frc2 :: WaitCommand ( 5.0 _s ) PYTHON # Ends 5 seconds after being scheduled commands2 . cmd . wait ( 5.0 ) To wait until a certain condition becomes true , the library offers the Commands.waitUntil(BooleanSupplier) / frc2::cmd::WaitUntil(std::function) factory, backed by the WaitUntilCommand class ( Java , C++ , Python ). JAVA // Ends after m_limitSwitch.get() returns true new WaitUntilCommand ( m_limitSwitch :: get ) C++ // Ends after m_limitSwitch.Get() returns true frc2 :: WaitUntilCommand ([ & m_limitSwitch ] { return m_limitSwitch . Get (); }) PYTHON # Ends after limit_switch.get() returns True commands2 . cmd . wait_until ( limit_switch . get ) Control Algorithm Commands There are commands for various control setups: TrapezoidProfile tracks a trapezoid motion profile. For more info, see Motion Profiling in Command-based . MecanumControllerCommand ( Java , C++ ) is useful for controlling mecanum drivetrains. See API docs and the MecanumControllerCommand ( Java , C++ ) example project for more info. SwerveControllerCommand ( Java , C++ ) is useful for controlling swerve drivetrains. See API docs and the SwerveControllerCommand ( Java , C++ ) example project for more info. RamseteCommand ( Java , C++ ) is useful for path following with differential drivetrains (“tank drive”). See API docs and the Trajectory Tutorial for more info. Custom Command Classes Users may also write custom command classes. As this is significantly more verbose, it’s recommended to use the more concise factories mentioned above. Note In the C++ API, a CRTP is used to allow certain Command methods to work with the object ownership model. Users should always extend the CommandHelper class when defining their own command classes, as is shown below. To write a custom command class, subclass the abstract Command class ( Java ) or CommandHelper ( C++ ), as seen in the command-based template ( Java , C++ ): JAVA 7 import edu.wpi.first.wpilibj.templates.commandbased.subsystems.ExampleSubsystem ; 8 import edu.wpi.first.wpilibj2.command.Command ; 9 10 /** An example command that uses an example subsystem. */ 11 public class ExampleCommand extends Command { 12 @SuppressWarnings ( \"PMD.UnusedPrivateField\" ) 13 private final ExampleSubsystem m_subsystem ; 14 15 /** 16 * Creates a new ExampleCommand. 17 * 18 * @param subsystem The subsystem used by this command. 19 */ 20 public ExampleCommand ( ExampleSubsystem subsystem ) { 21 m_subsystem = subsystem ; 22 // Use addRequirements() here to declare subsystem dependencies. 23 addRequirements ( subsystem ); 24 } C++ 5 #pragma once 6 7 #include 8 #include 9 10 #include \"subsystems/ExampleSubsystem.h\" 11 12 /** 13 * An example command that uses an example subsystem. 14 * 15 * Note that this extends CommandHelper, rather extending Command 16 * directly; this is crucially important, or else the decorator functions in 17 * Command will *not* work! 18 */ 19 class ExampleCommand 20 : public frc2 :: CommandHelper < frc2 :: Command , ExampleCommand > { 21 public : 22 /** 23 * Creates a new ExampleCommand. 24 * 25 * @param subsystem The subsystem used by this command. 26 */ 27 explicit ExampleCommand ( ExampleSubsystem * subsystem ); 28 29 private : 30 ExampleSubsystem * m_subsystem ; 31 }; Simple Command Example What might a functional command look like in practice? As before, below is a simple command from the HatchBot example project ( Java , C++ ) that uses the HatchSubsystem : Java 5 package edu.wpi.first.wpilibj.examples.hatchbottraditional.commands ; 6 7 import edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems.HatchSubsystem ; 8 import edu.wpi.first.wpilibj2.command.Command ; 9 10 /** 11 * A simple command that grabs a hatch with the {@link HatchSubsystem}. Written explicitly for 12 * pedagogical purposes. Actual code should inline a command this simple with {@link 13 * edu.wpi.first.wpilibj2.command.InstantCommand}. 14 */ 15 public class GrabHatch extends Command { 16 // The subsystem the command runs on 17 private final HatchSubsystem m_hatchSubsystem ; 18 19 public GrabHatch ( HatchSubsystem subsystem ) { 20 m_hatchSubsystem = subsystem ; 21 addRequirements ( m_hatchSubsystem ); 22 } 23 24 @Override 25 public void initialize () { 26 m_hatchSubsystem . grabHatch (); 27 } 28 29 @Override 30 public boolean isFinished () { 31 return true ; 32 } 33 } C++ (Header) 5 #pragma once 6 7 #include 8 #include 9 10 #include \"subsystems/HatchSubsystem.h\" 11 12 /** 13 * A simple command that grabs a hatch with the HatchSubsystem. Written 14 * explicitly for pedagogical purposes. Actual code should inline a command 15 * this simple with InstantCommand. 16 * 17 * @see InstantCommand 18 */ 19 class GrabHatch : public frc2 :: CommandHelper < frc2 :: Command , GrabHatch > { 20 public : 21 explicit GrabHatch ( HatchSubsystem * subsystem ); 22 23 void Initialize () override ; 24 25 bool IsFinished () override ; 26 27 private : 28 HatchSubsystem * m_hatch ; 29 }; C++ (Source) 5 #include \"commands/GrabHatch.h\" 6 7 GrabHatch :: GrabHatch ( HatchSubsystem * subsystem ) : m_hatch ( subsystem ) { 8 AddRequirements ( subsystem ); 9 } 10 11 void GrabHatch :: Initialize () { 12 m_hatch -> GrabHatch (); 13 } 14 15 bool GrabHatch :: IsFinished () { 16 return true ; 17 } Python 7 import commands2 8 from subsystems.hatchsubsystem import HatchSubsystem 9 10 11 class GrabHatch ( commands2 . Command ): 12 def __init__ ( self , hatch : HatchSubsystem ) -> None : 13 super () . __init__ () 14 self . hatch = hatch 15 self . addRequirements ( hatch ) 16 17 def initialize ( self ) -> None : 18 self . hatch . grabHatch () 19 20 def isFinished ( self ) -> bool : 21 return True Notice that the hatch subsystem used by the command is passed into the command through the command’s constructor. This is a pattern called dependency injection , and allows users to avoid declaring their subsystems as global variables. This is widely accepted as a best-practice - the reasoning behind this is discussed in a later section . Notice also that the above command calls the subsystem method once from initialize, and then immediately ends (as isFinished() simply returns true). This is typical for commands that toggle the states of subsystems, and as such it would be more succinct to write this command using the factories described above. What about a more complicated case? Below is a drive command, from the same example project: Java 5 package edu.wpi.first.wpilibj.examples.hatchbottraditional.commands ; 6 7 import edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems.DriveSubsystem ; 8 import edu.wpi.first.wpilibj2.command.Command ; 9 import java.util.function.DoubleSupplier ; 10 11 /** 12 * A command to drive the robot with joystick input (passed in as {@link DoubleSupplier}s). Written 13 * explicitly for pedagogical purposes - actual code should inline a command this simple with {@link 14 * edu.wpi.first.wpilibj2.command.RunCommand}. 15 */ 16 public class DefaultDrive extends Command { 17 private final DriveSubsystem m_drive ; 18 private final DoubleSupplier m_forward ; 19 private final DoubleSupplier m_rotation ; 20 21 /** 22 * Creates a new DefaultDrive. 23 * 24 * @param subsystem The drive subsystem this command wil run on. 25 * @param forward The control input for driving forwards/backwards 26 * @param rotation The control input for turning 27 */ 28 public DefaultDrive ( DriveSubsystem subsystem , DoubleSupplier forward , DoubleSupplier rotation ) { 29 m_drive = subsystem ; 30 m_forward = forward ; 31 m_rotation = rotation ; 32 addRequirements ( m_drive ); 33 } 34 35 @Override 36 public void execute () { 37 m_drive . arcadeDrive ( m_forward . getAsDouble (), m_rotation . getAsDouble ()); 38 } 39 } C++ (Header) 5 #pragma once 6 7 #include 8 9 #include 10 #include 11 12 #include \"subsystems/DriveSubsystem.h\" 13 14 /** 15 * A command to drive the robot with joystick input passed in through lambdas. 16 * Written explicitly for pedagogical purposes - actual code should inline a 17 * command this simple with RunCommand. 18 * 19 * @see RunCommand 20 */ 21 class DefaultDrive : public frc2 :: CommandHelper < frc2 :: Command , DefaultDrive > { 22 public : 23 /** 24 * Creates a new DefaultDrive. 25 * 26 * @param subsystem The drive subsystem this command wil run on. 27 * @param forward The control input for driving forwards/backwards 28 * @param rotation The control input for turning 29 */ 30 DefaultDrive ( DriveSubsystem * subsystem , std :: function < double () > forward , 31 std :: function < double () > rotation ); 32 33 void Execute () override ; 34 35 private : 36 DriveSubsystem * m_drive ; 37 std :: function < double () > m_forward ; 38 std :: function < double () > m_rotation ; 39 }; C++ (Source) 5 #include \"commands/DefaultDrive.h\" 6 7 #include 8 9 DefaultDrive :: DefaultDrive ( DriveSubsystem * subsystem , 10 std :: function < double () > forward , 11 std :: function < double () > rotation ) 12 : m_drive { subsystem }, 13 m_forward { std :: move ( forward )}, 14 m_rotation { std :: move ( rotation )} { 15 AddRequirements ( subsystem ); 16 } 17 18 void DefaultDrive :: Execute () { 19 m_drive -> ArcadeDrive ( m_forward (), m_rotation ()); 20 } Python 7 import typing 8 import commands2 9 from subsystems.drivesubsystem import DriveSubsystem 10 11 12 class DefaultDrive ( commands2 . Command ): 13 def __init__ ( 14 self , 15 drive : DriveSubsystem , 16 forward : typing . Callable [[], float ], 17 rotation : typing . Callable [[], float ], 18 ) -> None : 19 super () . __init__ () 20 21 self . drive = drive 22 self . forward = forward 23 self . rotation = rotation 24 25 self . addRequirements ( self . drive ) 26 27 def execute ( self ) -> None : 28 self . drive . arcadeDrive ( self . forward (), self . rotation ()) And then usage: JAVA 59 // Configure default commands 60 // Set the default drive command to split-stick arcade drive 61 m_robotDrive . setDefaultCommand ( 62 // A split-stick arcade command, with forward/backward controlled by the left 63 // hand, and turning controlled by the right. 64 new DefaultDrive ( 65 m_robotDrive , 66 () -> - m_driverController . getLeftY (), 67 () -> - m_driverController . getRightX ())); C++ 57 // Set up default drive command 58 m_drive . SetDefaultCommand ( DefaultDrive ( 59 & m_drive , [ this ] { return - m_driverController . GetLeftY (); }, 60 [ this ] { return - m_driverController . GetRightX (); })); PYTHON 65 # set up default drive command 66 self . drive . setDefaultCommand ( 67 DefaultDrive ( 68 self . drive , 69 lambda : - self . driverController . getY (), 70 lambda : self . driverController . getX (), 71 ) 72 ) Notice that this command does not override isFinished() , and thus will never end; this is the norm for commands that are intended to be used as default commands. Once more, this command is rather simple and calls the subsystem method only from one place, and as such, could be more concisely written using factories: JAVA 51 // Configure default commands 52 // Set the default drive command to split-stick arcade drive 53 m_robotDrive . setDefaultCommand ( 54 // A split-stick arcade command, with forward/backward controlled by the left 55 // hand, and turning controlled by the right. 56 Commands . run ( 57 () -> 58 m_robotDrive . arcadeDrive ( 59 - m_driverController . getLeftY (), - m_driverController . getRightX ()), 60 m_robotDrive )); C++ 52 // Set up default drive command 53 m_drive . SetDefaultCommand ( frc2 :: cmd :: Run ( 54 [ this ] { 55 m_drive . ArcadeDrive ( - m_driverController . GetLeftY (), 56 - m_driverController . GetRightX ()); 57 }, 58 { & m_drive })); PYTHON 53 # Configure default commands 54 # Set the default drive command to split-stick arcade drive 55 self . driveSubsystem . setDefaultCommand ( 56 # A split-stick arcade command, with forward/backward controlled by the left 57 # hand, and turning controlled by the right. 58 commands2 . cmd . run ( 59 lambda : self . driveSubsystem . arcadeDrive ( 60 - self . driverController . getLeftY (), 61 - self . driverController . getRightX (), 62 ), 63 self . driveSubsystem , 64 ) 65 )",
+ "content_preview": "Commands Commands represent actions the robot can take. Commands run when scheduled, until they are interrupted or their end condition is met. Commands are represented in the command-based library by the Command class ( Java , C++ ) or the Command class in commands2 library ( Python )."
},
{
- "url": "https://docs.wpilib.org/en/stable/docs/software/commandbased/organizing-command-based.html",
- "title": "Organizing Command",
+ "url": "https://docs.wpilib.org/en/stable/docs/software/commandbased/commands.html?present",
+ "title": "Commands",
"section": "Command-Based Programming",
"language": "All",
- "content": "Organizing Command-Based Robot Projects As robot code becomes more complicated, navigating, understanding, and maintaining the code takes up more and more time and energy. Making changes to the code often becomes more difficult, sometimes for reasons that have very little to do with the actual complexity of the underlying logic. For a simplified example: putting the logic for many unrelated robot functions into a single 1000-line file makes it difficult to find a specific piece of code within that file, particularly under stress at a competition. But spreading out closely related logic across dozens of tiny files is often just as difficult to navigate. This is not a problem unique to FRC, and in fact, good organization only becomes more and more critical as software projects become bigger and bigger. The “best” organization system is a perennial topic of debate, much like the “best” programming language, but in the end, the choice (in both cases) comes down to the specific task at hand and the programmer (or programmers) implementing said task. Even in the relatively small space of FRC robot programming, there is no right answer. The best choice for a given team will depend on the nature of the specific robot code, team structure, and pure personal preference. This article discusses various facets of command-based robot program design that advanced FRC programmers may want to be aware of when writing code. It is not a prescriptive tutorial, though it presents some recommended best practices. If this level of choice seems daunting, however, many teams have been highly successful while sticking closely to WPILib’s example code and guidelines. However, this discussion may be of interest to intermediate and advanced programmers who want to make their code not only effective, but flexible, easily changeable, and sometimes even beautiful. Why Care About Organization? Good code organization will rarely make or break a team’s competitive ability—but it does mean easier debugging, faster modifications, nicer-looking code, and happier programmers. While it’s impossible to define “good” organization by way of what the code looks like from the inside, it’s easier to define in terms of what the robot’s software looks like from the outside. What Good Organization Looks Like When code is well-designed and well-organized, the code’s internal structure is intuitive and easily comprehensible. Cumbersome boilerplate is minimized, meaning that new robot functionality can often be added with just a few lines of code. When a constant value (such as the speed of the robot’s intake) needs to be changed, it only needs to change in one place. If multiple programmers are working together, they can easily understand each others’ work. Bugs are rare, since it is difficult to accidentally introduce unintended behavior (such as creating a command that does not require necessary subsystems). Implementing more advanced functions like unit tests is easier, since the code is abstracted away from the physical hardware. Programmers are happy (most of the time). What Bad Organization Looks Like Poorly organized code often has internal structure that makes little to no sense, even to whoever wrote it. When functionality has to be added or changed, it often breaks unrelated parts of the robot: adding automatic shooter control might introduce a bug in the climbing sequence for unclear reasons. Alternatively, the organizational framework might be so strict that it’s impossible to implement necessary behavior, requiring nasty hacks or workarounds. Many lines of boilerplate code are needed for simple robot logic. Constants are scattered across the codebase, and changing basic behavior often requires making the same change to many different files. Collaboration among multiple programmers is difficult or impossible. Defining Commands In larger robot codebases, multiple copies of the same command need to be used in many different places. For instance, a command that runs a robot’s intake might be used in teleop, bound to a certain button; as part of a complicated command group for an autonomous routine; and as part of a self-test sequence. As an example, let’s look at some ways to define a simple command that simply runs the robot’s intake forward at full power until canceled. Inline Commands The easiest and most expressive way to do this is with a StartEndCommand : JAVA Command runIntake = Commands . startEnd (() -> intake . set ( 1.0 ), () -> intake . set ( 0.0 ), intake ); C++ frc2 :: CommandPtr runIntake = frc2 :: cmd :: StartEnd ([ & intake ] { intake . Set ( 1.0 ); }, [ & intake ] { intake . Set ( 0.0 ); }, { & intake }); This is sufficient for commands that are only used once. However, for a command like this that might get used in many different autonomous routines and button bindings, inline commands everywhere means a lot of repetitive code: JAVA // RobotContainer.java intakeButton . whileTrue ( Commands . startEnd (() -> intake . set ( 1.0 ), () -> intake . set ( 0.0 ), intake )); Command intakeAndShoot = Commands . startEnd (() -> intake . set ( 1.0 ), () -> intake . set ( 0.0 ), intake ) . alongWith ( new RunShooter ( shooter )); Command autonomousCommand = Commands . sequence ( Commands . startEnd (() -> intake . set ( 1.0 ), () -> intake . set ( 0.0 ), intake ). withTimeout ( 5.0 ), Commands . waitSeconds ( 3.0 ), Commands . startEnd (() -> intake . set ( 1.0 ), () -> intake . set ( 0.0 ), intake ). withTimeout ( 5.0 ) ); C++ intakeButton . WhileTrue ( frc2 :: cmd :: StartEnd ([ & intake ] { intake . Set ( 1.0 ); }, [ & intake ] { intake . Set ( 0.0 ); }, { & intake })); frc2 :: CommandPtr intakeAndShoot = frc2 :: cmd :: StartEnd ([ & intake ] { intake . Set ( 1.0 ); }, [ & intake ] { intake . Set ( 0.0 ); }, { & intake }) . AlongWith ( RunShooter ( & shooter ). ToPtr ()); frc2 :: CommandPtr autonomousCommand = frc2 :: cmd :: Sequence ( frc2 :: cmd :: StartEnd ([ & intake ] { intake . Set ( 1.0 ); }, [ & intake ] { intake . Set ( 0.0 ); }, { & intake }). WithTimeout ( 5.0 _s ), frc2 :: cmd :: Wait ( 3.0 _s ), frc2 :: cmd :: StartEnd ([ & intake ] { intake . Set ( 1.0 ); }, [ & intake ] { intake . Set ( 0.0 ); }, { & intake }). WithTimeout ( 5.0 _s ) ); Creating one StartEndCommand instance and putting it in a variable won’t work here, since once an instance of a command is added to a command group it is effectively “owned” by that command group and cannot be used in any other context. Instance Command Factory Methods One way to solve this quandary is using the “factory method” design pattern: a function that returns a new object every invocation, according to some specification. Using command composition , a factory method can construct a complex command object with merely a few lines of code. For example, a command like the intake-running command is conceptually related to exactly one subsystem: the Intake . As such, it makes sense to put a runIntakeCommand method as an instance method of the Intake class: Note In this document we will name factory methods as lowerCamelCaseCommand , but teams may decide on other conventions. In general, it is recommended to end the method name with Command if it might otherwise be confused with an ordinary method (e.g. intake.run might be the name of a method that simply turns on the intake). JAVA public class Intake extends SubsystemBase { // [code for motor controllers, configuration, etc.] // ... public Command runIntakeCommand () { // implicitly requires `this` return this . startEnd (() -> this . set ( 1.0 ), () -> this . set ( 0.0 )); } } C++ frc2 :: CommandPtr Intake::RunIntakeCommand () { // implicitly requires `this` return this -> StartEnd ([ this ] { this -> Set ( 1.0 ); }, [ this ] { this -> Set ( 0.0 ); }); } Notice how since we are in the Intake class, we no longer refer to intake ; instead, we use the this keyword to refer to the current instance. Since we are inside the Intake class, technically we can access private variables and methods directly from within the runIntakeCommand method, thus not needing intermediary methods. (For example, the runIntakeCommand method can directly interface with the motor controller objects instead of calling set() .) On the other hand, these intermediary methods can reduce code duplication and increase encapsulation. Like many other choices outlined in this document, this tradeoff is a matter of personal preference on a case-by-case basis. Using this new factory method in command groups and button bindings is highly expressive: JAVA intakeButton . whileTrue ( intake . runIntakeCommand ()); Command intakeAndShoot = intake . runIntakeCommand (). alongWith ( new RunShooter ( shooter )); Command autonomousCommand = Commands . sequence ( intake . runIntakeCommand (). withTimeout ( 5.0 ), Commands . waitSeconds ( 3.0 ), intake . runIntakeCommand (). withTimeout ( 5.0 ) ); C++ intakeButton . WhileTrue ( intake . RunIntakeCommand ()); frc2 :: CommandPtr intakeAndShoot = intake . RunIntakeCommand (). AlongWith ( RunShooter ( & shooter ). ToPtr ()); frc2 :: CommandPtr autonomousCommand = frc2 :: cmd :: Sequence ( intake . RunIntakeCommand (). WithTimeout ( 5.0 _s ), frc2 :: cmd :: Wait ( 3.0 _s ), intake . RunIntakeCommand (). WithTimeout ( 5.0 _s ) ); Adding a parameter to the runIntakeCommand method to provide the exact percentage to run the intake is easy and allows for even more flexibility. JAVA public Command runIntakeCommand ( double percent ) { return new StartEndCommand (() -> this . set ( percent ), () -> this . set ( 0.0 ), this ); } C++ frc2 :: CommandPtr Intake::RunIntakeCommand ( double percent ) { // implicitly requires `this` return this -> StartEnd ([ this , percent ] { this -> Set ( percent ); }, [ this ] { this -> Set ( 0.0 ); }); } For instance, this code creates a command group that runs the intake forwards for two seconds, waits for two seconds, and then runs the intake backwards for five seconds. JAVA Command intakeRunSequence = intake . runIntakeCommand ( 1.0 ). withTimeout ( 2.0 ) . andThen ( Commands . waitSeconds ( 2.0 )) . andThen ( intake . runIntakeCommand ( - 1.0 ). withTimeout ( 5.0 )); C++ frc2 :: CommandPtr intakeRunSequence = intake . RunIntakeCommand ( 1.0 ). WithTimeout ( 2.0 _s ) . AndThen ( frc2 :: cmd :: Wait ( 2.0 _s )) . AndThen ( intake . RunIntakeCommand ( -1.0 ). WithTimeout ( 5.0 _s )); This approach is recommended for commands that are conceptually related to only a single subsystem, and is very concise. However, it doesn’t fare well with commands related to more than one subsystem: passing in other subsystem objects is unintuitive and can cause race conditions and circular dependencies, and thus should be avoided. Therefore, this approach is best suited for single-subsystem commands, and should be used only for those cases. Static Command Factories Instance factory methods work great for single-subsystem commands. However, complicated robot actions (like the ones often required during the autonomous period) typically need to coordinate multiple subsystems at once. When we want to define an inline command that uses multiple subsystems, it doesn’t make sense for the command factory to live in any single one of those subsystems. Instead, it can be cleaner to define the command factory methods statically in some external class: Note The sequence and parallel static factories construct sequential and parallel command groups: this is equivalent to the andThen and alongWith decorators, but can be more readable. Their use is a matter of personal preference. JAVA public class AutoRoutines { public static Command driveAndIntake ( Drivetrain drivetrain , Intake intake ) { return Commands . sequence ( Commands . parallel ( drivetrain . driveCommand ( 0.5 , 0.5 ), intake . runIntakeCommand ( 1.0 ) ). withTimeout ( 5.0 ), Commands . parallel ( drivetrain . stopCommand (); intake . stopCommand (); ) ); } } C++ // TODO Non-Static Command Factories If we want to avoid the verbosity of adding required subsystems as parameters to our factory methods, we can instead construct an instance of our AutoRoutines class and inject our subsystems through the constructor: JAVA public class AutoRoutines { private Drivetrain drivetrain ; private Intake intake ; public AutoRoutines ( Drivetrain drivetrain , Intake intake ) { this . drivetrain = drivetrain ; this . intake = intake ; } public Command driveAndIntake () { return Commands . sequence ( Commands . parallel ( drivetrain . driveCommand ( 0.5 , 0.5 ), intake . runIntakeCommand ( 1.0 ) ). withTimeout ( 5.0 ), Commands . parallel ( drivetrain . stopCommand (); intake . stopCommand (); ) ); } public Command driveThenIntake () { return Commands . sequence ( drivetrain . driveCommand ( 0.5 , 0.5 ). withTimeout ( 5.0 ), drivetrain . stopCommand (), intake . runIntakeCommand ( 1.0 ). withTimeout ( 5.0 ), intake . stopCommand () ); } } C++ // TODO Then, elsewhere in our code, we can instantiate an single instance of this class and use it to produce several commands: JAVA AutoRoutines autoRoutines = new AutoRoutines ( this . drivetrain , this . intake ); Command driveAndIntake = autoRoutines . driveAndIntake (); Command driveThenIntake = autoRoutines . driveThenIntake (); Command drivingAndIntakingSequence = Commands . sequence ( autoRoutines . driveAndIntake (), autoRoutines . driveThenIntake () ); C++ // TODO Capturing State in Inline Commands Inline commands are extremely concise and expressive, but do not offer explicit support for commands that have their own internal state (such as a drivetrain trajectory following command, which may encapsulate an entire controller). This is often accomplished by instead writing a Command class, which will be covered later in this article. However, it is still possible to ergonomically write a stateful command composition using inline syntax, so long as we are working within a factory method. To do so, we declare the state as a method local and “capture” it in our inline definition. For example, consider the following instance command factory to turn a drivetrain to a specific angle with a PID controller: Note The Subsystem.run and Subsystem.runOnce factory methods sugar the creation of a RunCommand and an InstantCommand requiring this subsystem. JAVA public Command turnToAngle ( double targetDegrees ) { // Create a controller for the inline command to capture PIDController controller = new PIDController ( Constants . kTurnToAngleP , 0 , 0 ); // We can do whatever configuration we want on the created state before returning from the factory controller . setPositionTolerance ( Constants . kTurnToAngleTolerance ); // Try to turn at a rate proportional to the heading error until we're at the setpoint, then stop return run (() -> arcadeDrive ( 0 , - controller . calculate ( gyro . getHeading (), targetDegrees ))) . until ( controller :: atSetpoint ) . andThen ( runOnce (() -> arcadeDrive ( 0 , 0 ))); } C++ // TODO This pattern works very well in Java so long as the captured state is “effectively final” - i.e., it is never reassigned. This means that we cannot directly define and capture primitive types (e.g. int , double , boolean ) - to circumvent this, we need to wrap any state primitives in a mutable container type (the same way PIDController wraps its internal kP , kI , and kD values). Writing Command Classes Another possible way to define reusable commands is to write a class that represents the command. This is typically done by subclassing either Command or one of the CommandGroup classes. Subclassing Command Returning to our simple intake command from earlier, we could do this by creating a new subclass of Command that implements the necessary initialize and end methods. JAVA public class RunIntakeCommand extends Command { private Intake m_intake ; public RunIntakeCommand ( Intake intake ) { this . m_intake = intake ; addRequirements ( intake ); } @Override public void initialize () { m_intake . set ( 1.0 ); } @Override public void end ( boolean interrupted ) { m_intake . set ( 0.0 ); } // execute() defaults to do nothing // isFinished() defaults to return false } C++ // TODO This, however, is just as cumbersome as the original repetitive code, if not more verbose. The only two lines that really matter in this entire file are the two calls to intake.set() , yet there are over 20 lines of boilerplate code! Not to mention, doing this for a lot of robot actions quickly clutters up a robot project with dozens of small files. Nevertheless, this might feel more “natural,” particularly for programmers who prefer to stick closely to an object-oriented model. This approach should be used for commands with internal state (not subsystem state!), as the class can have fields to manage said state. It may also be more intuitive to write commands with complex logic as classes, especially for those less experienced with command composition. As the command is detached from any specific subsystem class and the required subsystem objects are injected through the constructor, this approach deals well with commands involving multiple subsystems. Subclassing Command Groups If we wish to write composite commands as their own classes, we may write a constructor-only subclass of the most exterior group type. For example, an intake-then-outtake sequence (with single-subsystem commands defined as instance factory methods) can look like this: JAVA public class IntakeThenOuttake extends SequentialCommandGroup { public IntakeThenOuttake ( Intake intake ) { super ( intake . runIntakeCommand ( 1.0 ). withTimeout ( 2.0 ), new WaitCommand ( 2.0 ), intake . runIntakeCommand ( - 1 ). withTimeout ( 5.0 ) ); } } C++ // TODO This is relatively short and minimizes boilerplate. It is also comfortable to use in a purely object-oriented paradigm and may be more acceptable to novice programmers. However, it has some downsides. For one, it is not immediately clear exactly what type of command group this is from the constructor definition: it is better to define this in a more inline and expressive way, particularly when nested command groups start showing up. Additionally, it requires a new file for every single command group, even when the groups are conceptually related. As with factory methods, state can be defined and captured within the command group subclass constructor, if necessary. Summary Approach Primary Use Case Single-subsystem Commands Multi-subsystem Commands Stateful Commands Complex Logic Commands Instance Factory Methods Single-subsystem commands Excels at them No Yes, but must obey capture rules Yes Subclassing Command Stateful commands Very verbose Relatively verbose Excels at them Yes; may be more natural than other approaches Static and Instance Command Factories Multi-subsystem commands Yes Yes Yes, but must obey capture rules Yes Subclassing Command Groups Multi-subsystem command groups Yes Yes Yes, but must obey capture rules Yes",
- "content_preview": "Organizing Command-Based Robot Projects As robot code becomes more complicated, navigating, understanding, and maintaining the code takes up more and more time and energy."
+ "content": "Commands Commands represent actions the robot can take. Commands run when scheduled, until they are interrupted or their end condition is met. Commands are represented in the command-based library by the Command class ( Java , C++ ) or the Command class in commands2 library ( Python ). The Structure of a Command Commands specify what the command will do in each of its possible states. This is done by overriding the initialize() , execute() , and end() methods. Additionally, a command must be able to tell the scheduler when (if ever) it has finished execution - this is done by overriding the isFinished() method. All of these methods are defaulted to reduce clutter in user code: initialize() , execute() , and end() are defaulted to simply do nothing, while isFinished() is defaulted to return false (resulting in a command that never finishes naturally, and will run until interrupted). Initialization The initialize() method ( Java , C++ , Python ) marks the command start, and is called exactly once per time a command is scheduled. The initialize() method should be used to place the command in a known starting state for execution. Command objects may be reused and scheduled multiple times, so any state or resources needed for the command’s functionality should be initialized or opened in initialize (which will be called at the start of each use) rather than the constructor (which is invoked only once on object allocation). It is also useful for performing tasks that only need to be performed once per time scheduled, such as setting motors to run at a constant speed or setting the state of a solenoid actuator. Execution The execute() method ( Java , C++ , Python ) is called repeatedly while the command is scheduled; this is when the scheduler’s run() method is called (this is generally done in the main robot periodic method, which runs every 20ms by default). The execute block should be used for any task that needs to be done continually while the command is scheduled, such as updating motor outputs to match joystick inputs, or using the output of a control loop. Ending The end(bool interrupted) method ( Java , C++ , Python ) is called once when the command ends, whether it finishes normally (i.e. isFinished() returned true) or it was interrupted (either by another command or by being explicitly canceled). The method argument specifies the manner in which the command ended; users can use this to differentiate the behavior of their command end accordingly. The end block should be used to “wrap up” command state in a neat way, such as setting motors back to zero or reverting a solenoid actuator to a “default” state. Any state or resources initialized in initialize() should be closed in end() . Specifying end conditions The isFinished() method ( Java , C++ , Python ) is called repeatedly while the command is scheduled, whenever the scheduler’s run() method is called. As soon as it returns true, the command’s end() method is called and it ends. The isFinished() method is called after the execute() method, so the command will execute once on the same iteration that it ends. Command Properties In addition to the four lifecycle methods described above, each Command also has three properties, defined by getter methods that should always return the same value with no side affects. getRequirements Each command should declare any subsystems it controls as requirements. This backs the scheduler’s resource management mechanism, ensuring that no more than one command requires a given subsystem at the same time. This prevents situations such as two different pieces of code attempting to set the same motor controller to different output values. Declaring requirements is done by overriding the getRequirements() method in the relevant command class, by calling addRequirements() , or by using the requirements vararg (Java) / Requirements struct (C++) parameter / requirements argument (Python) at the end of the parameter list of most command constructors and factories in the library: JAVA Commands . run ( intake :: activate , intake ); C++ frc2 :: cmd :: Run ([ & intake ] { intake . Activate (); }, { & intake }); PYTHON commands2 . cmd . run ( intake . activate , intake ) As a rule, command compositions require all subsystems their components require. runsWhenDisabled The runsWhenDisabled() method ( Java , C++ , Python ) returns a boolean / bool specifying whether the command may run when the robot is disabled. With the default of returning false , the command will be canceled when the robot is disabled and attempts to schedule it will do nothing. Returning true will allow the command to run and be scheduled when the robot is disabled. Important When the robot is disabled, PWM outputs are disabled and CAN motor controllers may not apply voltage, regardless of runsWhenDisabled ! This property can be set either by overriding the runsWhenDisabled() method in the relevant command class, or by using the ignoringDisable decorator ( Java , C++ , Python ): JAVA Command mayRunDuringDisabled = Commands . run (() -> updateTelemetry ()). ignoringDisable ( true ); C++ frc2 :: CommandPtr mayRunDuringDisabled = frc2 :: cmd :: Run ([] { UpdateTelemetry (); }). IgnoringDisable ( true ); PYTHON may_run_during_disabled = commands2 . cmd . run ( lambda : update_telemetry ()) . ignoring_disable ( True ) As a rule, command compositions may run when disabled if all their component commands set runsWhenDisabled as true . getInterruptionBehavior The getInterruptionBehavior() method ( Java , C++ , Python ) defines what happens if another command sharing a requirement is scheduled while this one is running. In the default behavior, kCancelSelf , the current command will be canceled and the incoming command will be scheduled successfully. If kCancelIncoming is returned, the incoming command’s scheduling will be aborted and this command will continue running. Note that getInterruptionBehavior only affects resolution of requirement conflicts: all commands can be canceled, regardless of getInterruptionBehavior . Note This was previously controlled by the interruptible parameter passed when scheduling a command, and is now a property of the command object. This property can be set either by overriding the getInterruptionBehavior method in the relevant command class, or by using the withInterruptBehavior() decorator ( Java , C++ , Python ) JAVA Command noninteruptible = Commands . run ( intake :: activate , intake ). withInterruptBehavior ( Command . InterruptBehavior . kCancelIncoming ); C++ frc2 :: CommandPtr noninterruptible = frc2 :: cmd :: Run ([ & intake ] { intake . Activate (); }, { & intake }). WithInterruptBehavior ( Command :: InterruptBehavior :: kCancelIncoming ); PYTHON non_interruptible = commands2 . cmd . run ( intake . activate , intake ) . with_interrupt_behavior ( Command . InterruptBehavior . kCancelIncoming ) As a rule, command compositions are kCancelIncoming if all their components are kCancelIncoming as well. Included Command Types The command-based library includes many pre-written command types. Through the use of lambdas , these commands can cover almost all use cases and teams should rarely need to write custom command classes. Many of these commands are provided via static factory functions in the Commands utility class (Java), in the frc2::cmd namespace defined in the Commands.h header (C++), or in the commands2.cmd namespace (Python). In Java and C++, classes inheriting from Subsystem also have instance methods that implicitly require this . Running Actions The most basic commands are actions the robot takes: setting voltage to a motor, changing a solenoid’s direction, etc. For these commands, which typically consist of a method call or two, the command-based library offers several factories to be construct commands inline with one or more lambdas to be executed. The runOnce factory, backed by the InstantCommand ( Java , C++ , Python ) class, creates a command that calls a lambda once, and then finishes. Java 25 /** Grabs the hatch. */ 26 public Command grabHatchCommand () { 27 // implicitly require `this` 28 return this . runOnce (() -> m_hatchSolenoid . set ( kForward )); 29 } 30 31 /** Releases the hatch. */ 32 public Command releaseHatchCommand () { 33 // implicitly require `this` 34 return this . runOnce (() -> m_hatchSolenoid . set ( kReverse )); 35 } C++ (Header) 20 /** 21 * Grabs the hatch. 22 */ 23 frc2 :: CommandPtr GrabHatchCommand (); 24 25 /** 26 * Releases the hatch. 27 */ 28 frc2 :: CommandPtr ReleaseHatchCommand (); C++ (Source) 15 frc2 :: CommandPtr HatchSubsystem::GrabHatchCommand () { 16 // implicitly require `this` 17 return this -> RunOnce ( 18 [ this ] { m_hatchSolenoid . Set ( frc :: DoubleSolenoid :: kForward ); }); 19 } 20 21 frc2 :: CommandPtr HatchSubsystem::ReleaseHatchCommand () { 22 // implicitly require `this` 23 return this -> RunOnce ( 24 [ this ] { m_hatchSolenoid . Set ( frc :: DoubleSolenoid :: kReverse ); }); 25 } Python 24 def grabHatch ( self ) -> commands2 . Command : 25 \"\"\"Grabs the hatch\"\"\" 26 return commands2 . cmd . runOnce ( 27 lambda : self . hatchSolenoid . set ( wpilib . DoubleSolenoid . Value . kForward ), self 28 ) 29 30 def releaseHatch ( self ) -> commands2 . Command : 31 \"\"\"Releases the hatch\"\"\" 32 return commands2 . cmd . runOnce ( 33 lambda : self . hatchSolenoid . set ( wpilib . DoubleSolenoid . Value . kReverse ), self 34 ) The run factory, backed by the RunCommand ( Java , C++ , Python ) class, creates a command that calls a lambda repeatedly, until interrupted. JAVA // A split-stick arcade command, with forward/backward controlled by the left // hand, and turning controlled by the right. new RunCommand (() -> m_robotDrive . arcadeDrive ( - driverController . getLeftY (), driverController . getRightX ()), m_robotDrive ) C++ // A split-stick arcade command, with forward/backward controlled by the left // hand, and turning controlled by the right. frc2 :: RunCommand ( [ this ] { m_drive . ArcadeDrive ( - m_driverController . GetLeftY (), m_driverController . GetRightX ()); }, { & m_drive }) PYTHON # A split-stick arcade command, with forward/backward controlled by the left # hand, and turning controlled by the right. commands2 . cmd . run ( lambda : robot_drive . arcade_drive ( - driver_controller . get_left_y (), driver_controller . get_right_x ()), robot_drive ) The startEnd factory, backed by the StartEndCommand ( Java , C++ , Python ) class, calls one lambda when scheduled, and then a second lambda when interrupted. JAVA Commands . startEnd ( // Start a flywheel spinning at 50% power () -> m_shooter . shooterSpeed ( 0.5 ), // Stop the flywheel at the end of the command () -> m_shooter . shooterSpeed ( 0.0 ), // Requires the shooter subsystem m_shooter ) C++ frc2 :: cmd :: StartEnd ( // Start a flywheel spinning at 50% power [ this ] { m_shooter . shooterSpeed ( 0.5 ); }, // Stop the flywheel at the end of the command [ this ] { m_shooter . shooterSpeed ( 0.0 ); }, // Requires the shooter subsystem { & m_shooter } ) PYTHON commands2 . cmd . start_end ( # Start a flywheel spinning at 50% power lambda : shooter . shooter_speed ( 0.5 ), # Stop the flywheel at the end of the command lambda : shooter . shooter_speed ( 0.0 ), # Requires the shooter subsystem shooter ) FunctionalCommand ( Java , C++ , Python ) accepts four lambdas that constitute the four command lifecycle methods: a Runnable / std::function/Callable for each of initialize() and execute() , a BooleanConsumer / std::function/Callable[bool,[]] for end() , and a BooleanSupplier / std::function/Callable[[],bool] for isFinished() . JAVA new FunctionalCommand ( // Reset encoders on command start m_robotDrive :: resetEncoders , // Start driving forward at the start of the command () -> m_robotDrive . arcadeDrive ( kAutoDriveSpeed , 0 ), // Stop driving at the end of the command interrupted -> m_robotDrive . arcadeDrive ( 0 , 0 ), // End the command when the robot's driven distance exceeds the desired value () -> m_robotDrive . getAverageEncoderDistance () >= kAutoDriveDistanceInches , // Require the drive subsystem m_robotDrive ) C++ frc2 :: FunctionalCommand ( // Reset encoders on command start [ this ] { m_drive . ResetEncoders (); }, // Start driving forward at the start of the command [ this ] { m_drive . ArcadeDrive ( ac :: kAutoDriveSpeed , 0 ); }, // Stop driving at the end of the command [ this ] ( bool interrupted ) { m_drive . ArcadeDrive ( 0 , 0 ); }, // End the command when the robot's driven distance exceeds the desired value [ this ] { return m_drive . GetAverageEncoderDistance () >= kAutoDriveDistanceInches ; }, // Requires the drive subsystem { & m_drive } ) PYTHON commands2 . cmd . functional_command ( # Reset encoders on command start lambda : robot_drive . reset_encoders (), # Start driving forward at the start of the command lambda : robot_drive . arcade_drive ( ac . kAutoDriveSpeed , 0 ), # Stop driving at the end of the command lambda interrupted : robot_drive . arcade_drive ( 0 , 0 ), # End the command when the robot's driven distance exceeds the desired value lambda : robot_drive . get_average_encoder_distance () >= ac . kAutoDriveDistanceInches , # Require the drive subsystem robot_drive ) To print a string and ending immediately, the library offers the Commands.print(String) / frc2::cmd::Print(std::string_view) / commands2.cmd.print(String) factory, backed by the PrintCommand ( Java , C++ , Python ) subclass of InstantCommand . Waiting Waiting for a certain condition to happen or adding a delay can be useful to synchronize between different commands in a command composition or between other robot actions. To wait and end after a specified period of time elapses, the library offers the Commands.waitSeconds(double) / frc2::cmd::Wait(units::second_t) / commands2.cmd.wait(float) factory, backed by the WaitCommand ( Java , C++ , Python ) class. JAVA // Ends 5 seconds after being scheduled new WaitCommand ( 5.0 ) C++ // Ends 5 seconds after being scheduled frc2 :: WaitCommand ( 5.0 _s ) PYTHON # Ends 5 seconds after being scheduled commands2 . cmd . wait ( 5.0 ) To wait until a certain condition becomes true , the library offers the Commands.waitUntil(BooleanSupplier) / frc2::cmd::WaitUntil(std::function) factory, backed by the WaitUntilCommand class ( Java , C++ , Python ). JAVA // Ends after m_limitSwitch.get() returns true new WaitUntilCommand ( m_limitSwitch :: get ) C++ // Ends after m_limitSwitch.Get() returns true frc2 :: WaitUntilCommand ([ & m_limitSwitch ] { return m_limitSwitch . Get (); }) PYTHON # Ends after limit_switch.get() returns True commands2 . cmd . wait_until ( limit_switch . get ) Control Algorithm Commands There are commands for various control setups: TrapezoidProfile tracks a trapezoid motion profile. For more info, see Motion Profiling in Command-based . MecanumControllerCommand ( Java , C++ ) is useful for controlling mecanum drivetrains. See API docs and the MecanumControllerCommand ( Java , C++ ) example project for more info. SwerveControllerCommand ( Java , C++ ) is useful for controlling swerve drivetrains. See API docs and the SwerveControllerCommand ( Java , C++ ) example project for more info. RamseteCommand ( Java , C++ ) is useful for path following with differential drivetrains (“tank drive”). See API docs and the Trajectory Tutorial for more info. Custom Command Classes Users may also write custom command classes. As this is significantly more verbose, it’s recommended to use the more concise factories mentioned above. Note In the C++ API, a CRTP is used to allow certain Command methods to work with the object ownership model. Users should always extend the CommandHelper class when defining their own command classes, as is shown below. To write a custom command class, subclass the abstract Command class ( Java ) or CommandHelper ( C++ ), as seen in the command-based template ( Java , C++ ): JAVA 7 import edu.wpi.first.wpilibj.templates.commandbased.subsystems.ExampleSubsystem ; 8 import edu.wpi.first.wpilibj2.command.Command ; 9 10 /** An example command that uses an example subsystem. */ 11 public class ExampleCommand extends Command { 12 @SuppressWarnings ( \"PMD.UnusedPrivateField\" ) 13 private final ExampleSubsystem m_subsystem ; 14 15 /** 16 * Creates a new ExampleCommand. 17 * 18 * @param subsystem The subsystem used by this command. 19 */ 20 public ExampleCommand ( ExampleSubsystem subsystem ) { 21 m_subsystem = subsystem ; 22 // Use addRequirements() here to declare subsystem dependencies. 23 addRequirements ( subsystem ); 24 } C++ 5 #pragma once 6 7 #include 8 #include 9 10 #include \"subsystems/ExampleSubsystem.h\" 11 12 /** 13 * An example command that uses an example subsystem. 14 * 15 * Note that this extends CommandHelper, rather extending Command 16 * directly; this is crucially important, or else the decorator functions in 17 * Command will *not* work! 18 */ 19 class ExampleCommand 20 : public frc2 :: CommandHelper < frc2 :: Command , ExampleCommand > { 21 public : 22 /** 23 * Creates a new ExampleCommand. 24 * 25 * @param subsystem The subsystem used by this command. 26 */ 27 explicit ExampleCommand ( ExampleSubsystem * subsystem ); 28 29 private : 30 ExampleSubsystem * m_subsystem ; 31 }; Simple Command Example What might a functional command look like in practice? As before, below is a simple command from the HatchBot example project ( Java , C++ ) that uses the HatchSubsystem : Java 5 package edu.wpi.first.wpilibj.examples.hatchbottraditional.commands ; 6 7 import edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems.HatchSubsystem ; 8 import edu.wpi.first.wpilibj2.command.Command ; 9 10 /** 11 * A simple command that grabs a hatch with the {@link HatchSubsystem}. Written explicitly for 12 * pedagogical purposes. Actual code should inline a command this simple with {@link 13 * edu.wpi.first.wpilibj2.command.InstantCommand}. 14 */ 15 public class GrabHatch extends Command { 16 // The subsystem the command runs on 17 private final HatchSubsystem m_hatchSubsystem ; 18 19 public GrabHatch ( HatchSubsystem subsystem ) { 20 m_hatchSubsystem = subsystem ; 21 addRequirements ( m_hatchSubsystem ); 22 } 23 24 @Override 25 public void initialize () { 26 m_hatchSubsystem . grabHatch (); 27 } 28 29 @Override 30 public boolean isFinished () { 31 return true ; 32 } 33 } C++ (Header) 5 #pragma once 6 7 #include 8 #include 9 10 #include \"subsystems/HatchSubsystem.h\" 11 12 /** 13 * A simple command that grabs a hatch with the HatchSubsystem. Written 14 * explicitly for pedagogical purposes. Actual code should inline a command 15 * this simple with InstantCommand. 16 * 17 * @see InstantCommand 18 */ 19 class GrabHatch : public frc2 :: CommandHelper < frc2 :: Command , GrabHatch > { 20 public : 21 explicit GrabHatch ( HatchSubsystem * subsystem ); 22 23 void Initialize () override ; 24 25 bool IsFinished () override ; 26 27 private : 28 HatchSubsystem * m_hatch ; 29 }; C++ (Source) 5 #include \"commands/GrabHatch.h\" 6 7 GrabHatch :: GrabHatch ( HatchSubsystem * subsystem ) : m_hatch ( subsystem ) { 8 AddRequirements ( subsystem ); 9 } 10 11 void GrabHatch :: Initialize () { 12 m_hatch -> GrabHatch (); 13 } 14 15 bool GrabHatch :: IsFinished () { 16 return true ; 17 } Python 7 import commands2 8 from subsystems.hatchsubsystem import HatchSubsystem 9 10 11 class GrabHatch ( commands2 . Command ): 12 def __init__ ( self , hatch : HatchSubsystem ) -> None : 13 super () . __init__ () 14 self . hatch = hatch 15 self . addRequirements ( hatch ) 16 17 def initialize ( self ) -> None : 18 self . hatch . grabHatch () 19 20 def isFinished ( self ) -> bool : 21 return True Notice that the hatch subsystem used by the command is passed into the command through the command’s constructor. This is a pattern called dependency injection , and allows users to avoid declaring their subsystems as global variables. This is widely accepted as a best-practice - the reasoning behind this is discussed in a later section . Notice also that the above command calls the subsystem method once from initialize, and then immediately ends (as isFinished() simply returns true). This is typical for commands that toggle the states of subsystems, and as such it would be more succinct to write this command using the factories described above. What about a more complicated case? Below is a drive command, from the same example project: Java 5 package edu.wpi.first.wpilibj.examples.hatchbottraditional.commands ; 6 7 import edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems.DriveSubsystem ; 8 import edu.wpi.first.wpilibj2.command.Command ; 9 import java.util.function.DoubleSupplier ; 10 11 /** 12 * A command to drive the robot with joystick input (passed in as {@link DoubleSupplier}s). Written 13 * explicitly for pedagogical purposes - actual code should inline a command this simple with {@link 14 * edu.wpi.first.wpilibj2.command.RunCommand}. 15 */ 16 public class DefaultDrive extends Command { 17 private final DriveSubsystem m_drive ; 18 private final DoubleSupplier m_forward ; 19 private final DoubleSupplier m_rotation ; 20 21 /** 22 * Creates a new DefaultDrive. 23 * 24 * @param subsystem The drive subsystem this command wil run on. 25 * @param forward The control input for driving forwards/backwards 26 * @param rotation The control input for turning 27 */ 28 public DefaultDrive ( DriveSubsystem subsystem , DoubleSupplier forward , DoubleSupplier rotation ) { 29 m_drive = subsystem ; 30 m_forward = forward ; 31 m_rotation = rotation ; 32 addRequirements ( m_drive ); 33 } 34 35 @Override 36 public void execute () { 37 m_drive . arcadeDrive ( m_forward . getAsDouble (), m_rotation . getAsDouble ()); 38 } 39 } C++ (Header) 5 #pragma once 6 7 #include 8 9 #include 10 #include 11 12 #include \"subsystems/DriveSubsystem.h\" 13 14 /** 15 * A command to drive the robot with joystick input passed in through lambdas. 16 * Written explicitly for pedagogical purposes - actual code should inline a 17 * command this simple with RunCommand. 18 * 19 * @see RunCommand 20 */ 21 class DefaultDrive : public frc2 :: CommandHelper < frc2 :: Command , DefaultDrive > { 22 public : 23 /** 24 * Creates a new DefaultDrive. 25 * 26 * @param subsystem The drive subsystem this command wil run on. 27 * @param forward The control input for driving forwards/backwards 28 * @param rotation The control input for turning 29 */ 30 DefaultDrive ( DriveSubsystem * subsystem , std :: function < double () > forward , 31 std :: function < double () > rotation ); 32 33 void Execute () override ; 34 35 private : 36 DriveSubsystem * m_drive ; 37 std :: function < double () > m_forward ; 38 std :: function < double () > m_rotation ; 39 }; C++ (Source) 5 #include \"commands/DefaultDrive.h\" 6 7 #include 8 9 DefaultDrive :: DefaultDrive ( DriveSubsystem * subsystem , 10 std :: function < double () > forward , 11 std :: function < double () > rotation ) 12 : m_drive { subsystem }, 13 m_forward { std :: move ( forward )}, 14 m_rotation { std :: move ( rotation )} { 15 AddRequirements ( subsystem ); 16 } 17 18 void DefaultDrive :: Execute () { 19 m_drive -> ArcadeDrive ( m_forward (), m_rotation ()); 20 } Python 7 import typing 8 import commands2 9 from subsystems.drivesubsystem import DriveSubsystem 10 11 12 class DefaultDrive ( commands2 . Command ): 13 def __init__ ( 14 self , 15 drive : DriveSubsystem , 16 forward : typing . Callable [[], float ], 17 rotation : typing . Callable [[], float ], 18 ) -> None : 19 super () . __init__ () 20 21 self . drive = drive 22 self . forward = forward 23 self . rotation = rotation 24 25 self . addRequirements ( self . drive ) 26 27 def execute ( self ) -> None : 28 self . drive . arcadeDrive ( self . forward (), self . rotation ()) And then usage: JAVA 59 // Configure default commands 60 // Set the default drive command to split-stick arcade drive 61 m_robotDrive . setDefaultCommand ( 62 // A split-stick arcade command, with forward/backward controlled by the left 63 // hand, and turning controlled by the right. 64 new DefaultDrive ( 65 m_robotDrive , 66 () -> - m_driverController . getLeftY (), 67 () -> - m_driverController . getRightX ())); C++ 57 // Set up default drive command 58 m_drive . SetDefaultCommand ( DefaultDrive ( 59 & m_drive , [ this ] { return - m_driverController . GetLeftY (); }, 60 [ this ] { return - m_driverController . GetRightX (); })); PYTHON 65 # set up default drive command 66 self . drive . setDefaultCommand ( 67 DefaultDrive ( 68 self . drive , 69 lambda : - self . driverController . getY (), 70 lambda : self . driverController . getX (), 71 ) 72 ) Notice that this command does not override isFinished() , and thus will never end; this is the norm for commands that are intended to be used as default commands. Once more, this command is rather simple and calls the subsystem method only from one place, and as such, could be more concisely written using factories: JAVA 51 // Configure default commands 52 // Set the default drive command to split-stick arcade drive 53 m_robotDrive . setDefaultCommand ( 54 // A split-stick arcade command, with forward/backward controlled by the left 55 // hand, and turning controlled by the right. 56 Commands . run ( 57 () -> 58 m_robotDrive . arcadeDrive ( 59 - m_driverController . getLeftY (), - m_driverController . getRightX ()), 60 m_robotDrive )); C++ 52 // Set up default drive command 53 m_drive . SetDefaultCommand ( frc2 :: cmd :: Run ( 54 [ this ] { 55 m_drive . ArcadeDrive ( - m_driverController . GetLeftY (), 56 - m_driverController . GetRightX ()); 57 }, 58 { & m_drive })); PYTHON 53 # Configure default commands 54 # Set the default drive command to split-stick arcade drive 55 self . driveSubsystem . setDefaultCommand ( 56 # A split-stick arcade command, with forward/backward controlled by the left 57 # hand, and turning controlled by the right. 58 commands2 . cmd . run ( 59 lambda : self . driveSubsystem . arcadeDrive ( 60 - self . driverController . getLeftY (), 61 - self . driverController . getRightX (), 62 ), 63 self . driveSubsystem , 64 ) 65 )",
+ "content_preview": "Commands Commands represent actions the robot can take. Commands run when scheduled, until they are interrupted or their end condition is met. Commands are represented in the command-based library by the Command class ( Java , C++ ) or the Command class in commands2 library ( Python )."
},
{
- "url": "https://docs.wpilib.org/en/stable/docs/software/commandbased/organizing-command-based.html?present",
- "title": "Organizing Command",
+ "url": "https://docs.wpilib.org/en/stable/docs/software/commandbased/structuring-command-based-project.html",
+ "title": "Structuring a Command",
"section": "Command-Based Programming",
"language": "All",
- "content": "Organizing Command-Based Robot Projects As robot code becomes more complicated, navigating, understanding, and maintaining the code takes up more and more time and energy. Making changes to the code often becomes more difficult, sometimes for reasons that have very little to do with the actual complexity of the underlying logic. For a simplified example: putting the logic for many unrelated robot functions into a single 1000-line file makes it difficult to find a specific piece of code within that file, particularly under stress at a competition. But spreading out closely related logic across dozens of tiny files is often just as difficult to navigate. This is not a problem unique to FRC, and in fact, good organization only becomes more and more critical as software projects become bigger and bigger. The “best” organization system is a perennial topic of debate, much like the “best” programming language, but in the end, the choice (in both cases) comes down to the specific task at hand and the programmer (or programmers) implementing said task. Even in the relatively small space of FRC robot programming, there is no right answer. The best choice for a given team will depend on the nature of the specific robot code, team structure, and pure personal preference. This article discusses various facets of command-based robot program design that advanced FRC programmers may want to be aware of when writing code. It is not a prescriptive tutorial, though it presents some recommended best practices. If this level of choice seems daunting, however, many teams have been highly successful while sticking closely to WPILib’s example code and guidelines. However, this discussion may be of interest to intermediate and advanced programmers who want to make their code not only effective, but flexible, easily changeable, and sometimes even beautiful. Why Care About Organization? Good code organization will rarely make or break a team’s competitive ability—but it does mean easier debugging, faster modifications, nicer-looking code, and happier programmers. While it’s impossible to define “good” organization by way of what the code looks like from the inside, it’s easier to define in terms of what the robot’s software looks like from the outside. What Good Organization Looks Like When code is well-designed and well-organized, the code’s internal structure is intuitive and easily comprehensible. Cumbersome boilerplate is minimized, meaning that new robot functionality can often be added with just a few lines of code. When a constant value (such as the speed of the robot’s intake) needs to be changed, it only needs to change in one place. If multiple programmers are working together, they can easily understand each others’ work. Bugs are rare, since it is difficult to accidentally introduce unintended behavior (such as creating a command that does not require necessary subsystems). Implementing more advanced functions like unit tests is easier, since the code is abstracted away from the physical hardware. Programmers are happy (most of the time). What Bad Organization Looks Like Poorly organized code often has internal structure that makes little to no sense, even to whoever wrote it. When functionality has to be added or changed, it often breaks unrelated parts of the robot: adding automatic shooter control might introduce a bug in the climbing sequence for unclear reasons. Alternatively, the organizational framework might be so strict that it’s impossible to implement necessary behavior, requiring nasty hacks or workarounds. Many lines of boilerplate code are needed for simple robot logic. Constants are scattered across the codebase, and changing basic behavior often requires making the same change to many different files. Collaboration among multiple programmers is difficult or impossible. Defining Commands In larger robot codebases, multiple copies of the same command need to be used in many different places. For instance, a command that runs a robot’s intake might be used in teleop, bound to a certain button; as part of a complicated command group for an autonomous routine; and as part of a self-test sequence. As an example, let’s look at some ways to define a simple command that simply runs the robot’s intake forward at full power until canceled. Inline Commands The easiest and most expressive way to do this is with a StartEndCommand : JAVA Command runIntake = Commands . startEnd (() -> intake . set ( 1.0 ), () -> intake . set ( 0.0 ), intake ); C++ frc2 :: CommandPtr runIntake = frc2 :: cmd :: StartEnd ([ & intake ] { intake . Set ( 1.0 ); }, [ & intake ] { intake . Set ( 0.0 ); }, { & intake }); This is sufficient for commands that are only used once. However, for a command like this that might get used in many different autonomous routines and button bindings, inline commands everywhere means a lot of repetitive code: JAVA // RobotContainer.java intakeButton . whileTrue ( Commands . startEnd (() -> intake . set ( 1.0 ), () -> intake . set ( 0.0 ), intake )); Command intakeAndShoot = Commands . startEnd (() -> intake . set ( 1.0 ), () -> intake . set ( 0.0 ), intake ) . alongWith ( new RunShooter ( shooter )); Command autonomousCommand = Commands . sequence ( Commands . startEnd (() -> intake . set ( 1.0 ), () -> intake . set ( 0.0 ), intake ). withTimeout ( 5.0 ), Commands . waitSeconds ( 3.0 ), Commands . startEnd (() -> intake . set ( 1.0 ), () -> intake . set ( 0.0 ), intake ). withTimeout ( 5.0 ) ); C++ intakeButton . WhileTrue ( frc2 :: cmd :: StartEnd ([ & intake ] { intake . Set ( 1.0 ); }, [ & intake ] { intake . Set ( 0.0 ); }, { & intake })); frc2 :: CommandPtr intakeAndShoot = frc2 :: cmd :: StartEnd ([ & intake ] { intake . Set ( 1.0 ); }, [ & intake ] { intake . Set ( 0.0 ); }, { & intake }) . AlongWith ( RunShooter ( & shooter ). ToPtr ()); frc2 :: CommandPtr autonomousCommand = frc2 :: cmd :: Sequence ( frc2 :: cmd :: StartEnd ([ & intake ] { intake . Set ( 1.0 ); }, [ & intake ] { intake . Set ( 0.0 ); }, { & intake }). WithTimeout ( 5.0 _s ), frc2 :: cmd :: Wait ( 3.0 _s ), frc2 :: cmd :: StartEnd ([ & intake ] { intake . Set ( 1.0 ); }, [ & intake ] { intake . Set ( 0.0 ); }, { & intake }). WithTimeout ( 5.0 _s ) ); Creating one StartEndCommand instance and putting it in a variable won’t work here, since once an instance of a command is added to a command group it is effectively “owned” by that command group and cannot be used in any other context. Instance Command Factory Methods One way to solve this quandary is using the “factory method” design pattern: a function that returns a new object every invocation, according to some specification. Using command composition , a factory method can construct a complex command object with merely a few lines of code. For example, a command like the intake-running command is conceptually related to exactly one subsystem: the Intake . As such, it makes sense to put a runIntakeCommand method as an instance method of the Intake class: Note In this document we will name factory methods as lowerCamelCaseCommand , but teams may decide on other conventions. In general, it is recommended to end the method name with Command if it might otherwise be confused with an ordinary method (e.g. intake.run might be the name of a method that simply turns on the intake). JAVA public class Intake extends SubsystemBase { // [code for motor controllers, configuration, etc.] // ... public Command runIntakeCommand () { // implicitly requires `this` return this . startEnd (() -> this . set ( 1.0 ), () -> this . set ( 0.0 )); } } C++ frc2 :: CommandPtr Intake::RunIntakeCommand () { // implicitly requires `this` return this -> StartEnd ([ this ] { this -> Set ( 1.0 ); }, [ this ] { this -> Set ( 0.0 ); }); } Notice how since we are in the Intake class, we no longer refer to intake ; instead, we use the this keyword to refer to the current instance. Since we are inside the Intake class, technically we can access private variables and methods directly from within the runIntakeCommand method, thus not needing intermediary methods. (For example, the runIntakeCommand method can directly interface with the motor controller objects instead of calling set() .) On the other hand, these intermediary methods can reduce code duplication and increase encapsulation. Like many other choices outlined in this document, this tradeoff is a matter of personal preference on a case-by-case basis. Using this new factory method in command groups and button bindings is highly expressive: JAVA intakeButton . whileTrue ( intake . runIntakeCommand ()); Command intakeAndShoot = intake . runIntakeCommand (). alongWith ( new RunShooter ( shooter )); Command autonomousCommand = Commands . sequence ( intake . runIntakeCommand (). withTimeout ( 5.0 ), Commands . waitSeconds ( 3.0 ), intake . runIntakeCommand (). withTimeout ( 5.0 ) ); C++ intakeButton . WhileTrue ( intake . RunIntakeCommand ()); frc2 :: CommandPtr intakeAndShoot = intake . RunIntakeCommand (). AlongWith ( RunShooter ( & shooter ). ToPtr ()); frc2 :: CommandPtr autonomousCommand = frc2 :: cmd :: Sequence ( intake . RunIntakeCommand (). WithTimeout ( 5.0 _s ), frc2 :: cmd :: Wait ( 3.0 _s ), intake . RunIntakeCommand (). WithTimeout ( 5.0 _s ) ); Adding a parameter to the runIntakeCommand method to provide the exact percentage to run the intake is easy and allows for even more flexibility. JAVA public Command runIntakeCommand ( double percent ) { return new StartEndCommand (() -> this . set ( percent ), () -> this . set ( 0.0 ), this ); } C++ frc2 :: CommandPtr Intake::RunIntakeCommand ( double percent ) { // implicitly requires `this` return this -> StartEnd ([ this , percent ] { this -> Set ( percent ); }, [ this ] { this -> Set ( 0.0 ); }); } For instance, this code creates a command group that runs the intake forwards for two seconds, waits for two seconds, and then runs the intake backwards for five seconds. JAVA Command intakeRunSequence = intake . runIntakeCommand ( 1.0 ). withTimeout ( 2.0 ) . andThen ( Commands . waitSeconds ( 2.0 )) . andThen ( intake . runIntakeCommand ( - 1.0 ). withTimeout ( 5.0 )); C++ frc2 :: CommandPtr intakeRunSequence = intake . RunIntakeCommand ( 1.0 ). WithTimeout ( 2.0 _s ) . AndThen ( frc2 :: cmd :: Wait ( 2.0 _s )) . AndThen ( intake . RunIntakeCommand ( -1.0 ). WithTimeout ( 5.0 _s )); This approach is recommended for commands that are conceptually related to only a single subsystem, and is very concise. However, it doesn’t fare well with commands related to more than one subsystem: passing in other subsystem objects is unintuitive and can cause race conditions and circular dependencies, and thus should be avoided. Therefore, this approach is best suited for single-subsystem commands, and should be used only for those cases. Static Command Factories Instance factory methods work great for single-subsystem commands. However, complicated robot actions (like the ones often required during the autonomous period) typically need to coordinate multiple subsystems at once. When we want to define an inline command that uses multiple subsystems, it doesn’t make sense for the command factory to live in any single one of those subsystems. Instead, it can be cleaner to define the command factory methods statically in some external class: Note The sequence and parallel static factories construct sequential and parallel command groups: this is equivalent to the andThen and alongWith decorators, but can be more readable. Their use is a matter of personal preference. JAVA public class AutoRoutines { public static Command driveAndIntake ( Drivetrain drivetrain , Intake intake ) { return Commands . sequence ( Commands . parallel ( drivetrain . driveCommand ( 0.5 , 0.5 ), intake . runIntakeCommand ( 1.0 ) ). withTimeout ( 5.0 ), Commands . parallel ( drivetrain . stopCommand (); intake . stopCommand (); ) ); } } C++ // TODO Non-Static Command Factories If we want to avoid the verbosity of adding required subsystems as parameters to our factory methods, we can instead construct an instance of our AutoRoutines class and inject our subsystems through the constructor: JAVA public class AutoRoutines { private Drivetrain drivetrain ; private Intake intake ; public AutoRoutines ( Drivetrain drivetrain , Intake intake ) { this . drivetrain = drivetrain ; this . intake = intake ; } public Command driveAndIntake () { return Commands . sequence ( Commands . parallel ( drivetrain . driveCommand ( 0.5 , 0.5 ), intake . runIntakeCommand ( 1.0 ) ). withTimeout ( 5.0 ), Commands . parallel ( drivetrain . stopCommand (); intake . stopCommand (); ) ); } public Command driveThenIntake () { return Commands . sequence ( drivetrain . driveCommand ( 0.5 , 0.5 ). withTimeout ( 5.0 ), drivetrain . stopCommand (), intake . runIntakeCommand ( 1.0 ). withTimeout ( 5.0 ), intake . stopCommand () ); } } C++ // TODO Then, elsewhere in our code, we can instantiate an single instance of this class and use it to produce several commands: JAVA AutoRoutines autoRoutines = new AutoRoutines ( this . drivetrain , this . intake ); Command driveAndIntake = autoRoutines . driveAndIntake (); Command driveThenIntake = autoRoutines . driveThenIntake (); Command drivingAndIntakingSequence = Commands . sequence ( autoRoutines . driveAndIntake (), autoRoutines . driveThenIntake () ); C++ // TODO Capturing State in Inline Commands Inline commands are extremely concise and expressive, but do not offer explicit support for commands that have their own internal state (such as a drivetrain trajectory following command, which may encapsulate an entire controller). This is often accomplished by instead writing a Command class, which will be covered later in this article. However, it is still possible to ergonomically write a stateful command composition using inline syntax, so long as we are working within a factory method. To do so, we declare the state as a method local and “capture” it in our inline definition. For example, consider the following instance command factory to turn a drivetrain to a specific angle with a PID controller: Note The Subsystem.run and Subsystem.runOnce factory methods sugar the creation of a RunCommand and an InstantCommand requiring this subsystem. JAVA public Command turnToAngle ( double targetDegrees ) { // Create a controller for the inline command to capture PIDController controller = new PIDController ( Constants . kTurnToAngleP , 0 , 0 ); // We can do whatever configuration we want on the created state before returning from the factory controller . setPositionTolerance ( Constants . kTurnToAngleTolerance ); // Try to turn at a rate proportional to the heading error until we're at the setpoint, then stop return run (() -> arcadeDrive ( 0 , - controller . calculate ( gyro . getHeading (), targetDegrees ))) . until ( controller :: atSetpoint ) . andThen ( runOnce (() -> arcadeDrive ( 0 , 0 ))); } C++ // TODO This pattern works very well in Java so long as the captured state is “effectively final” - i.e., it is never reassigned. This means that we cannot directly define and capture primitive types (e.g. int , double , boolean ) - to circumvent this, we need to wrap any state primitives in a mutable container type (the same way PIDController wraps its internal kP , kI , and kD values). Writing Command Classes Another possible way to define reusable commands is to write a class that represents the command. This is typically done by subclassing either Command or one of the CommandGroup classes. Subclassing Command Returning to our simple intake command from earlier, we could do this by creating a new subclass of Command that implements the necessary initialize and end methods. JAVA public class RunIntakeCommand extends Command { private Intake m_intake ; public RunIntakeCommand ( Intake intake ) { this . m_intake = intake ; addRequirements ( intake ); } @Override public void initialize () { m_intake . set ( 1.0 ); } @Override public void end ( boolean interrupted ) { m_intake . set ( 0.0 ); } // execute() defaults to do nothing // isFinished() defaults to return false } C++ // TODO This, however, is just as cumbersome as the original repetitive code, if not more verbose. The only two lines that really matter in this entire file are the two calls to intake.set() , yet there are over 20 lines of boilerplate code! Not to mention, doing this for a lot of robot actions quickly clutters up a robot project with dozens of small files. Nevertheless, this might feel more “natural,” particularly for programmers who prefer to stick closely to an object-oriented model. This approach should be used for commands with internal state (not subsystem state!), as the class can have fields to manage said state. It may also be more intuitive to write commands with complex logic as classes, especially for those less experienced with command composition. As the command is detached from any specific subsystem class and the required subsystem objects are injected through the constructor, this approach deals well with commands involving multiple subsystems. Subclassing Command Groups If we wish to write composite commands as their own classes, we may write a constructor-only subclass of the most exterior group type. For example, an intake-then-outtake sequence (with single-subsystem commands defined as instance factory methods) can look like this: JAVA public class IntakeThenOuttake extends SequentialCommandGroup { public IntakeThenOuttake ( Intake intake ) { super ( intake . runIntakeCommand ( 1.0 ). withTimeout ( 2.0 ), new WaitCommand ( 2.0 ), intake . runIntakeCommand ( - 1 ). withTimeout ( 5.0 ) ); } } C++ // TODO This is relatively short and minimizes boilerplate. It is also comfortable to use in a purely object-oriented paradigm and may be more acceptable to novice programmers. However, it has some downsides. For one, it is not immediately clear exactly what type of command group this is from the constructor definition: it is better to define this in a more inline and expressive way, particularly when nested command groups start showing up. Additionally, it requires a new file for every single command group, even when the groups are conceptually related. As with factory methods, state can be defined and captured within the command group subclass constructor, if necessary. Summary Approach Primary Use Case Single-subsystem Commands Multi-subsystem Commands Stateful Commands Complex Logic Commands Instance Factory Methods Single-subsystem commands Excels at them No Yes, but must obey capture rules Yes Subclassing Command Stateful commands Very verbose Relatively verbose Excels at them Yes; may be more natural than other approaches Static and Instance Command Factories Multi-subsystem commands Yes Yes Yes, but must obey capture rules Yes Subclassing Command Groups Multi-subsystem command groups Yes Yes Yes, but must obey capture rules Yes",
- "content_preview": "Organizing Command-Based Robot Projects As robot code becomes more complicated, navigating, understanding, and maintaining the code takes up more and more time and energy."
+ "content": "Structuring a Command-Based Robot Project While users are free to use the command-based libraries however they like (and advanced users are encouraged to do so), new users may want some guidance on how to structure a basic command-based robot project. A standard template for a command-based robot project is included in the WPILib examples repository ( Java , C++ ). This section will walk users through the structure of this template. The root package/directory generally will contain four classes: Main , which is the main robot application (Java only). New users should not touch this class. Robot , which is responsible for the main control flow of the robot code. RobotContainer , which holds robot subsystems and commands, and is where most of the declarative robot setup (e.g. button bindings) is performed. Constants , which holds globally-accessible constants to be used throughout the robot. The root directory will also contain two sub-packages/sub-directories: Subsystems contains all user-defined subsystem classes. Commands contains all user-defined command classes. Robot As Robot ( Java , C++ (Header) , C++ (Source) ) is responsible for the program’s control flow, and command-based is an declarative paradigm designed to minimize the amount of attention the user has to pay to explicit program control flow, the Robot class of a command-based project should be mostly empty. However, there are a few important things that must be included Java 21 /** 22 * This function is run when the robot is first started up and should be used for any 23 * initialization code. 24 */ 25 public Robot () { 26 // Instantiate our RobotContainer. This will perform all our button bindings, and put our 27 // autonomous chooser on the dashboard. 28 m_robotContainer = new RobotContainer (); 29 } In Java, an instance of RobotContainer should be constructed during the Robot constructor - this is important, as most of the declarative robot setup will be called from the RobotContainer constructor. In C++, this is not needed as RobotContainer is a value member and will be constructed during the construction of Robot . Java 31 /** 32 * This function is called every 20 ms, no matter the mode. Use this for items like diagnostics 33 * that you want ran during disabled, autonomous, teleoperated and test. 34 * 35 * This runs after the mode specific periodic functions, but before LiveWindow and 36 * SmartDashboard integrated updating. 37 */ 38 @Override 39 public void robotPeriodic () { 40 // Runs the Scheduler. This is responsible for polling buttons, adding newly-scheduled 41 // commands, running already-scheduled commands, removing finished or interrupted commands, 42 // and running subsystem periodic() methods. This must be called from the robot's periodic 43 // block in order for anything in the Command-based framework to work. 44 CommandScheduler . getInstance (). run (); 45 } C++ (Source) 11 /** 12 * This function is called every 20 ms, no matter the mode. Use 13 * this for items like diagnostics that you want to run during disabled, 14 * autonomous, teleoperated and test. 15 * 16 *
This runs after the mode specific periodic functions, but before 17 * LiveWindow and SmartDashboard integrated updating. 18 */ 19 void Robot::RobotPeriodic () { 20 frc2 :: CommandScheduler :: GetInstance (). Run (); 21 } The inclusion of the CommandScheduler.getInstance().run() call in the robotPeriodic() method is essential; without this call, the scheduler will not execute any scheduled commands. Since TimedRobot runs with a default main loop frequency of 50Hz, this is the frequency with which periodic command and subsystem methods will be called. It is not recommended for new users to call this method from anywhere else in their code. Java 54 /** This autonomous runs the autonomous command selected by your {@link RobotContainer} class. */ 55 @Override 56 public void autonomousInit () { 57 m_autonomousCommand = m_robotContainer . getAutonomousCommand (); 58 59 // schedule the autonomous command (example) 60 if ( m_autonomousCommand != null ) { 61 CommandScheduler . getInstance (). schedule ( m_autonomousCommand ); 62 } 63 } C++ (Source) 32 /** 33 * This autonomous runs the autonomous command selected by your {@link 34 * RobotContainer} class. 35 */ 36 void Robot::AutonomousInit () { 37 m_autonomousCommand = m_container . GetAutonomousCommand (); 38 39 if ( m_autonomousCommand ) { 40 frc2 :: CommandScheduler :: GetInstance (). Schedule ( m_autonomousCommand . value ()); 41 } 42 } The autonomousInit() method schedules an autonomous command returned by the RobotContainer instance. The logic for selecting which autonomous command to run can be handled inside of RobotContainer . Java 69 @Override 70 public void teleopInit () { 71 // This makes sure that the autonomous stops running when 72 // teleop starts running. If you want the autonomous to 73 // continue until interrupted by another command, remove 74 // this line or comment it out. 75 if ( m_autonomousCommand != null ) { 76 m_autonomousCommand . cancel (); 77 } 78 } C++ (Source) 46 void Robot::TeleopInit () { 47 // This makes sure that the autonomous stops running when 48 // teleop starts running. If you want the autonomous to 49 // continue until interrupted by another command, remove 50 // this line or comment it out. 51 if ( m_autonomousCommand ) { 52 m_autonomousCommand -> Cancel (); 53 } 54 } The teleopInit() method cancels any still-running autonomous commands. This is generally good practice. Advanced users are free to add additional code to the various init and periodic methods as they see fit; however, it should be noted that including large amounts of imperative robot code in Robot.java is contrary to the declarative design philosophy of the command-based paradigm, and can result in confusingly-structured/disorganized code. RobotContainer This class ( Java , C++ (Header) , C++ (Source) ) is where most of the setup for your command-based robot will take place. In this class, you will define your robot’s subsystems and commands, bind those commands to triggering events (such as buttons), and specify which command you will run in your autonomous routine. There are a few aspects of this class new users may want explanations for: Java 23 private final ExampleSubsystem m_exampleSubsystem = new ExampleSubsystem (); C++ (Header) 32 ExampleSubsystem m_subsystem ; Notice that subsystems are declared as private fields in RobotContainer . This is in stark contrast to the previous incarnation of the command-based framework, but is much more-aligned with agreed-upon object-oriented best-practices. If subsystems are declared as global variables, it allows the user to access them from anywhere in the code. While this can make certain things easier (for example, there would be no need to pass subsystems to commands in order for those commands to access them), it makes the control flow of the program much harder to keep track of as it is not immediately obvious which parts of the code can change or be changed by which other parts of the code. This also circumvents the ability of the resource-management system to do its job, as ease-of-access makes it easy for users to accidentally make conflicting calls to subsystem methods outside of the resource-managed commands. Java 61 return Autos . exampleAuto ( m_exampleSubsystem ); C++ (Source) 34 return autos :: ExampleAuto ( & m_subsystem ); Since subsystems are declared as private members, they must be explicitly passed to commands (a pattern called “dependency injection”) in order for those commands to call methods on them. This is done here with ExampleCommand , which is passed a pointer to an ExampleSubsystem . Java 35 /** 36 * Use this method to define your trigger->command mappings. Triggers can be created via the 37 * {@link Trigger#Trigger(java.util.function.BooleanSupplier)} constructor with an arbitrary 38 * predicate, or via the named factories in {@link 39 * edu.wpi.first.wpilibj2.command.button.CommandGenericHID}'s subclasses for {@link 40 * CommandXboxController Xbox}/{@link edu.wpi.first.wpilibj2.command.button.CommandPS4Controller 41 * PS4} controllers or {@link edu.wpi.first.wpilibj2.command.button.CommandJoystick Flight 42 * joysticks}. 43 */ 44 private void configureBindings () { 45 // Schedule `ExampleCommand` when `exampleCondition` changes to `true` 46 new Trigger ( m_exampleSubsystem :: exampleCondition ) 47 . onTrue ( new ExampleCommand ( m_exampleSubsystem )); 48 49 // Schedule `exampleMethodCommand` when the Xbox controller's B button is pressed, 50 // cancelling on release. 51 m_driverController . b (). whileTrue ( m_exampleSubsystem . exampleMethodCommand ()); 52 } C++ (Source) 19 void RobotContainer::ConfigureBindings () { 20 // Configure your trigger bindings here 21 22 // Schedule `ExampleCommand` when `exampleCondition` changes to `true` 23 frc2 :: Trigger ([ this ] { 24 return m_subsystem . ExampleCondition (); 25 }). OnTrue ( ExampleCommand ( & m_subsystem ). ToPtr ()); 26 27 // Schedule `ExampleMethodCommand` when the Xbox controller's B button is 28 // pressed, cancelling on release. 29 m_driverController . B (). WhileTrue ( m_subsystem . ExampleMethodCommand ()); 30 } As mentioned before, the RobotContainer() constructor is where most of the declarative setup for the robot should take place, including button bindings, configuring autonomous selectors, etc. If the constructor gets too “busy,” users are encouraged to migrate code into separate subroutines (such as the configureBindings() method included by default) which are called from the constructor. Java 54 /** 55 * Use this to pass the autonomous command to the main {@link Robot} class. 56 * 57 * @return the command to run in autonomous 58 */ 59 public Command getAutonomousCommand () { 60 // An example command will be run in autonomous 61 return Autos . exampleAuto ( m_exampleSubsystem ); 62 } 63 } C++ (Source) 32 frc2 :: CommandPtr RobotContainer::GetAutonomousCommand () { 33 // An example command will be run in autonomous 34 return autos :: ExampleAuto ( & m_subsystem ); 35 } Finally, the getAutonomousCommand() method provides a convenient way for users to send their selected autonomous command to the main Robot class (which needs access to it to schedule it when autonomous starts). Constants The Constants class ( Java , C++ (Header) ) (in C++ this is not a class, but simply a header file in which several namespaces are defined) is where globally-accessible robot constants (such as speeds, unit conversion factors, PID gains, and sensor/motor ports) can be stored. It is recommended that users separate these constants into individual inner classes corresponding to subsystems or robot modes, to keep variable names shorter. In Java, all constants should be declared public static final so that they are globally accessible and cannot be changed. In C++, all constants should be constexpr . For more illustrative examples of what a constants class should look like in practice, see those of the various command-based example projects: Hatchbot ( Java , C++ ) RapidReactCommandBot ( Java , C++ ) In Java, it is recommended that the constants be used from other classes by statically importing the necessary inner class. An import static statement imports the static namespace of a class into the class in which you are working, so that any static constants can be referenced directly as if they had been defined in that class. In C++, the same effect can be attained with using namespace : JAVA import static edu.wpi.first.wpilibj.templates.commandbased.Constants.OIConstants.* ; C++ using namespace OIConstants ; Subsystems User-defined subsystems should go in this package/directory. Commands User-defined commands should go in this package/directory.",
+ "content_preview": "Structuring a Command-Based Robot Project While users are free to use the command-based libraries however they like (and advanced users are encouraged to do so), new users may want some guidance on how to structure a basic command-based robot project."
},
{
- "url": "https://docs.wpilib.org/en/stable/docs/software/commandbased/command-compositions.html?present",
- "title": "Command Compositions",
- "section": "Command-Based Programming",
+ "url": "https://docs.wpilib.org/en/stable/docs/software/pathplanning/trajectory-tutorial/creating-following-trajectory.html",
+ "title": "Step 4: Creating and Following a Trajectory",
+ "section": "Path Planning",
"language": "All",
- "content": "Command Compositions Individual commands are capable of accomplishing a large variety of robot tasks, but the simple three-state format can quickly become cumbersome when more advanced functionality requiring extended sequences of robot tasks or coordination of multiple robot subsystems is required. In order to accomplish this, users are encouraged to use the powerful command composition functionality included in the command-based library. As the name suggests, a command composition is a composition of one or more commands. This allows code to be kept much cleaner and simpler, as the individual component commands may be written independently of the code that combines them, greatly reducing the amount of complexity at any given step of the process. Most importantly, however, command compositions are themselves commands - they extend the Command class. This allows command compositions to be further composed as a recursive composition - that is, a command composition may contain other command compositions as components. This allows very powerful and concise inline expressions: JAVA // Will run fooCommand, and then a race between barCommand and bazCommand button . onTrue ( fooCommand . andThen ( barCommand . raceWith ( bazCommand ))); C++ // Will run fooCommand, and then a race between barCommand and bazCommand button . OnTrue ( std :: move ( fooCommand ). AndThen ( std :: move ( barCommand ). RaceWith ( std :: move ( bazCommand )))); PYTHON # Will run fooCommand, and then a race between barCommand and bazCommand button . onTrue ( fooCommand . andThen ( barCommand . raceWith ( bazCommand ))) As a rule, command compositions require all subsystems their components require, may run when disabled if all their component set runsWhenDisabled as true , and are kCancelIncoming if all their components are kCancelIncoming as well. Command instances that have been passed to a command composition cannot be independently scheduled or passed to a second command composition. Attempting to do so will throw an exception and crash the user program. This is because composition members are run through their encapsulating command composition, and errors could occur if those same command instances were independently scheduled at the same time as the composition - the command would be being run from multiple places at once, and thus could end up with inconsistent internal state, causing unexpected and hard-to-diagnose behavior. The C++ command-based library uses CommandPtr , a class with move-only semantics, so this type of mistake is easier to avoid. Composition Types The command-based library includes various composition types. All of them can be constructed using factories that accept the member commands, and some can also be constructed using decorators: methods that can be called on a command object, which is transformed into a new object that is returned. Important After calling a decorator or being passed to a composition, the command object cannot be reused! Use only the command object returned from the decorator. Repeating The repeatedly() decorator ( Java , C++ , Python ), backed by the RepeatCommand class ( Java , C++ , Python ) restarts the command each time it ends, so that it runs until interrupted. JAVA // Will run forever unless externally interrupted, restarting every time command.isFinished() returns true Command repeats = command . repeatedly (); C++ // Will run forever unless externally interrupted, restarting every time command.IsFinished() returns true frc2 :: CommandPtr repeats = std :: move ( command ). Repeatedly (); PYTHON # Will run forever unless externally interrupted, restarting every time command.IsFinished() returns true repeats = command . repeatedly () Sequence The Sequence factory ( Java , C++ , Python ), backed by the SequentialCommandGroup class ( Java , C++ , Python ), runs a list of commands in sequence: the first command will be executed, then the second, then the third, and so on until the list finishes. The sequential group finishes after the last command in the sequence finishes. It is therefore usually important to ensure that each command in the sequence does actually finish (if a given command does not finish, the next command will never start!). The andThen() ( Java , C++ , Python ) and beforeStarting() ( Java , C++ , Python ) decorators can be used to construct a sequence composition with infix syntax. JAVA fooCommand . andThen ( barCommand ) C++ std :: move ( fooCommand ). AndThen ( std :: move ( barCommand )) PYTHON fooCommand . andThen ( barCommand ) Repeating Sequence As it’s a fairly common combination, the RepeatingSequence factory ( Java , C++ , Python ) creates a Repeating Sequence that runs until interrupted, restarting from the first command each time the last command finishes. Parallel There are three types of parallel compositions, differing based on when the composition finishes: The Parallel factory ( Java , C++ , Python ), backed by the ParallelCommandGroup class ( Java , C++ , Python ), constructs a parallel composition that finishes when all members finish. The alongWith decorator ( Java , C++ , Python ) does the same in infix notation. The Race factory ( Java , C++ , Python ), backed by the ParallelRaceGroup class ( Java , C++ , Python ), constructs a parallel composition that finishes as soon as any member finishes; all other members are interrupted at that point. The raceWith decorator ( Java , C++ , Python ) does the same in infix notation. The Deadline factory ( Java , C++ , Python ), ParallelDeadlineGroup ( Java , C++ , Python ) finishes when a specific command (the “deadline”) ends; all other members still running at that point are interrupted. The deadlineWith decorator ( Java , C++ , Python ) does the same in infix notation; the command the decorator was called on is the deadline. JAVA // Will be a parallel command composition that ends after three seconds with all three commands running their full duration. button . onTrue ( Commands . parallel ( twoSecCommand , oneSecCommand , threeSecCommand )); // Will be a parallel race composition that ends after one second with the two and three second commands getting interrupted. button . onTrue ( Commands . race ( twoSecCommand , oneSecCommand , threeSecCommand )); // Will be a parallel deadline composition that ends after two seconds (the deadline) with the three second command getting interrupted (one second command already finished). button . onTrue ( Commands . deadline ( twoSecCommand , oneSecCommand , threeSecCommand )); C++ // Will be a parallel command composition that ends after three seconds with all three commands running their full duration. button . OnTrue ( frc2 :: cmd :: Parallel ( std :: move ( twoSecCommand ), std :: move ( oneSecCommand ), std :: move ( threeSecCommand ))); // Will be a parallel race composition that ends after one second with the two and three second commands getting interrupted. button . OnTrue ( frc2 :: cmd :: Race ( std :: move ( twoSecCommand ), std :: move ( oneSecCommand ), std :: move ( threeSecCommand ))); // Will be a parallel deadline composition that ends after two seconds (the deadline) with the three second command getting interrupted (one second command already finished). button . OnTrue ( frc2 :: cmd :: Deadline ( std :: move ( twoSecCommand ), std :: move ( oneSecCommand ), std :: move ( threeSecCommand ))); PYTHON # Will be a parallel command composition that ends after three seconds with all three commands running their full duration. button . onTrue ( commands2 . cmd . parallel ( twoSecCommand , oneSecCommand , threeSecCommand )) # Will be a parallel race composition that ends after one second with the two and three second commands getting interrupted. button . onTrue ( commands2 . cmd . race ( twoSecCommand , oneSecCommand , threeSecCommand )) # Will be a parallel deadline composition that ends after two seconds (the deadline) with the three second command getting interrupted (one second command already finished). button . onTrue ( commands2 . cmd . deadline ( twoSecCommand , oneSecCommand , threeSecCommand )) Adding Command End Conditions The until() ( Java , C++ , Python ) decorator composes the command with an additional end condition. Note that the command the decorator was called on will see this end condition as an interruption. JAVA // Will be interrupted if m_limitSwitch.get() returns true button . onTrue ( command . until ( m_limitSwitch :: get )); C++ // Will be interrupted if m_limitSwitch.get() returns true button . OnTrue ( command . Until ([ & m_limitSwitch ] { return m_limitSwitch . Get (); })); PYTHON # Will be interrupted if limitSwitch.get() returns true button . onTrue ( commands2 . cmd . until ( limitSwitch . get )) The withTimeout() decorator ( Java , C++ , Python ) is a specialization of until that uses a timeout as the additional end condition. JAVA // Will time out 5 seconds after being scheduled, and be interrupted button . onTrue ( command . withTimeout ( 5 )); C++ // Will time out 5 seconds after being scheduled, and be interrupted button . OnTrue ( command . WithTimeout ( 5.0 _s )); PYTHON # Will time out 5 seconds after being scheduled, and be interrupted button . onTrue ( commands2 . cmd . withTimeout ( 5.0 )) Adding End Behavior The finallyDo() ( Java , C++ , Python ) decorator composes the command with an a lambda that will be called after the command’s end() method, with the same boolean parameter indicating whether the command finished or was interrupted. The handleInterrupt() ( Java , C++ , Python ) decorator composes the command with an a lambda that will be called only when the command is interrupted. Selecting Compositions Sometimes it’s desired to run a command out of a few options based on sensor feedback or other data known only at runtime. This can be useful for determining an auto routine, or running a different command based on whether a game piece is present or not, and so on. The Select factory ( Java , C++ , Python ), backed by the SelectCommand class ( Java , C++ , Python ), executes one command from a map, based on a selector function called when scheduled. Java 20 public class RobotContainer { 21 // The enum used as keys for selecting the command to run. 22 private enum CommandSelector { 23 ONE , 24 TWO , 25 THREE 26 } 27 28 // An example selector method for the selectcommand. Returns the selector that will select 29 // which command to run. Can base this choice on logical conditions evaluated at runtime. 30 private CommandSelector select () { 31 return CommandSelector . ONE ; 32 } 33 34 // An example selectcommand. Will select from the three commands based on the value returned 35 // by the selector method at runtime. Note that selectcommand works on Object(), so the 36 // selector does not have to be an enum; it could be any desired type (string, integer, 37 // boolean, double...) 38 private final Command m_exampleSelectCommand = 39 new SelectCommand <> ( 40 // Maps selector values to commands 41 Map . ofEntries ( 42 Map . entry ( CommandSelector . ONE , new PrintCommand ( \"Command one was selected!\" )), 43 Map . entry ( CommandSelector . TWO , new PrintCommand ( \"Command two was selected!\" )), 44 Map . entry ( CommandSelector . THREE , new PrintCommand ( \"Command three was selected!\" ))), 45 this :: select ); C++ (Header) 26 // The enum used as keys for selecting the command to run. 27 enum CommandSelector { ONE , TWO , THREE }; 28 29 // An example of how command selector may be used with SendableChooser 30 frc :: SendableChooser < CommandSelector > m_chooser ; 31 32 // The robot's subsystems and commands are defined here... 33 34 // An example selectcommand. Will select from the three commands based on the 35 // value returned by the selector method at runtime. Note that selectcommand 36 // takes a generic type, so the selector does not have to be an enum; it could 37 // be any desired type (string, integer, boolean, double...) 38 frc2 :: CommandPtr m_exampleSelectCommand = frc2 :: cmd :: Select < CommandSelector > ( 39 [ this ] { return m_chooser . GetSelected (); }, 40 // Maps selector values to commands 41 std :: pair { ONE , frc2 :: cmd :: Print ( \"Command one was selected!\" )}, 42 std :: pair { TWO , frc2 :: cmd :: Print ( \"Command two was selected!\" )}, 43 std :: pair { THREE , frc2 :: cmd :: Print ( \"Command three was selected!\" )}); The Either factory ( Java , C++ , Python ), backed by the ConditionalCommand class ( Java , C++ , Python ), is a specialization accepting two commands and a boolean selector function. JAVA // Runs either commandOnTrue or commandOnFalse depending on the value of m_limitSwitch.get() new ConditionalCommand ( commandOnTrue , commandOnFalse , m_limitSwitch :: get ) C++ // Runs either commandOnTrue or commandOnFalse depending on the value of m_limitSwitch.get() frc2 :: ConditionalCommand ( commandOnTrue , commandOnFalse , [ & m_limitSwitch ] { return m_limitSwitch . Get (); }) PYTHON # Runs either commandOnTrue or commandOnFalse depending on the value of limitSwitch.get() ConditionalCommand ( commandOnTrue , commandOnFalse , limitSwitch . get ) The unless() decorator ( Java , C++ , Python ) composes a command with a condition that will prevent it from running. JAVA // Command will only run if the intake is deployed. If the intake gets deployed while the command is running, the command will not stop running button . onTrue ( command . unless (() -> ! intake . isDeployed ())); C++ // Command will only run if the intake is deployed. If the intake gets deployed while the command is running, the command will not stop running button . OnTrue ( command . Unless ([ & intake ] { return ! intake . IsDeployed (); })); PYTHON # Command will only run if the intake is deployed. If the intake gets deployed while the command is running, the command will not stop running button . onTrue ( command . unless ( lambda : not intake . isDeployed ())) ProxyCommand described below also has a constructor overload ( Java , C++ , Python ) that calls a command-returning lambda at schedule-time and runs the returned command by proxy. Scheduling Other Commands By default, composition members are run through the command composition, and are never themselves seen by the scheduler. Accordingly, their requirements are added to the composition’s requirements. While this is usually fine, sometimes it is undesirable for the entire command composition to gain the requirements of a single command. A good solution is to “fork off” from the command composition and schedule that command separately. However, this requires synchronization between the composition and the individually-scheduled command. ProxyCommand ( Java , C++ , Python ), also creatable using the .asProxy() decorator ( Java , C++ , Python ), schedules a command “by proxy”: the command is scheduled when the proxy is scheduled, and the proxy finishes when the command finishes. In the case of “forking off” from a command composition, this allows the composition to track the command’s progress without it being in the composition. Command compositions inherit the union of their compoments’ requirements and requirements are immutable. Therefore, a SequentialCommandGroup ( Java , C++ , Python ) that intakes a game piece, indexes it, aims a shooter, and shoots it would reserve all three subsystems (the intake, indexer, and shooter), precluding any of those subsystems from performing other operations in their “downtime”. If this is not desired, the subsystems that should only be reserved for the composition while they are actively being used by it should have their commands proxied. Warning Do not use ProxyCommand unless you are sure of what you are doing and there is no other way to accomplish your need! Proxying is only intended for use as an escape hatch from command composition requirement unions. Note Because proxied commands still require their subsystem, despite not leaking that requirement to the composition, all of the commands that require a given subsystem must be proxied if one of them is. Otherwise, when the proxied command is scheduled its requirement will conflict with that of the composition, canceling the composition. JAVA // composition requirements are indexer and shooter, intake still reserved during its command but not afterwards Commands . sequence ( intake . intakeGamePiece (). asProxy (), // we want to let the intake intake another game piece while we are processing this one indexer . processGamePiece (), shooter . aimAndShoot () ); C++ // composition requirements are indexer and shooter, intake still reserved during its command but not afterwards frc2 :: cmd :: Sequence ( intake . IntakeGamePiece (). AsProxy (), // we want to let the intake intake another game piece while we are processing this one indexer . ProcessGamePiece (), shooter . AimAndShoot () ); PYTHON # composition requirements are indexer and shooter, intake still reserved during its command but not afterwards commands2 . cmd . sequence ( intake . intakeGamePiece () . asProxy (), # we want to let the intake intake another game piece while we are processing this one indexer . processGamePiece (), shooter . aimAndShoot () ) For cases that don’t need to track the proxied command, ScheduleCommand ( Java , C++ , Python ) schedules a specified command and ends instantly. JAVA // ScheduleCommand ends immediately, so the sequence continues new ScheduleCommand ( Commands . waitSeconds ( 5.0 )) . andThen ( Commands . print ( \"This will be printed immediately!\" )) C++ // ScheduleCommand ends immediately, so the sequence continues frc2 :: ScheduleCommand ( frc2 :: cmd :: Wait ( 5.0 _s )) . AndThen ( frc2 :: cmd :: Print ( \"This will be printed immediately!\" )) PYTHON # ScheduleCommand ends immediately, so the sequence continues ScheduleCommand ( commands2 . cmd . waitSeconds ( 5.0 )) . andThen ( commands2 . cmd . print ( \"This will be printed immediately!\" )) Subclassing Compositions Command compositions can also be written as a constructor-only subclass of the most exterior composition type, passing the composition members to the superclass constructor. Consider the following from the Hatch Bot example project ( Java , C++ ): Java 5 package edu.wpi.first.wpilibj.examples.hatchbottraditional.commands ; 6 7 import edu.wpi.first.wpilibj.examples.hatchbottraditional.Constants.AutoConstants ; 8 import edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems.DriveSubsystem ; 9 import edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems.HatchSubsystem ; 10 import edu.wpi.first.wpilibj2.command.SequentialCommandGroup ; 11 12 /** A complex auto command that drives forward, releases a hatch, and then drives backward. */ 13 public class ComplexAuto extends SequentialCommandGroup { 14 /** 15 * Creates a new ComplexAuto. 16 * 17 * @param drive The drive subsystem this command will run on 18 * @param hatch The hatch subsystem this command will run on 19 */ 20 public ComplexAuto ( DriveSubsystem drive , HatchSubsystem hatch ) { 21 addCommands ( 22 // Drive forward the specified distance 23 new DriveDistance ( 24 AutoConstants . kAutoDriveDistanceInches , AutoConstants . kAutoDriveSpeed , drive ), 25 26 // Release the hatch 27 new ReleaseHatch ( hatch ), 28 29 // Drive backward the specified distance 30 new DriveDistance ( 31 AutoConstants . kAutoBackupDistanceInches , - AutoConstants . kAutoDriveSpeed , drive )); 32 } 33 } C++ (Header) 5 #pragma once 6 7 #include 8 #include 9 10 #include \"Constants.h\" 11 #include \"commands/DriveDistance.h\" 12 #include \"commands/ReleaseHatch.h\" 13 14 /** 15 * A complex auto command that drives forward, releases a hatch, and then drives 16 * backward. 17 */ 18 class ComplexAuto 19 : public frc2 :: CommandHelper < frc2 :: SequentialCommandGroup , ComplexAuto > { 20 public : 21 /** 22 * Creates a new ComplexAuto. 23 * 24 * @param drive The drive subsystem this command will run on 25 * @param hatch The hatch subsystem this command will run on 26 */ 27 ComplexAuto ( DriveSubsystem * drive , HatchSubsystem * hatch ); 28 }; C++ (Source) 5 #include \"commands/ComplexAuto.h\" 6 7 using namespace AutoConstants ; 8 9 ComplexAuto :: ComplexAuto ( DriveSubsystem * drive , HatchSubsystem * hatch ) { 10 AddCommands ( 11 // Drive forward the specified distance 12 DriveDistance ( kAutoDriveDistanceInches , kAutoDriveSpeed , drive ), 13 // Release the hatch 14 ReleaseHatch ( hatch ), 15 // Drive backward the specified distance 16 DriveDistance ( kAutoBackupDistanceInches , - kAutoDriveSpeed , drive )); 17 } Python 7 import commands2 8 9 import constants 10 11 from .drivedistance import DriveDistance 12 from .releasehatch import ReleaseHatch 13 14 from subsystems.drivesubsystem import DriveSubsystem 15 from subsystems.hatchsubsystem import HatchSubsystem 16 17 18 class ComplexAuto ( commands2 . SequentialCommandGroup ): 19 \"\"\" 20 A complex auto command that drives forward, releases a hatch, and then drives backward. 21 \"\"\" 22 23 def __init__ ( self , drive : DriveSubsystem , hatch : HatchSubsystem ): 24 super () . __init__ ( 25 # Drive forward the specified distance 26 DriveDistance ( 27 constants . kAutoDriveDistanceInches , constants . kAutoDriveSpeed , drive 28 ), 29 # Release the hatch 30 ReleaseHatch ( hatch ), 31 # Drive backward the specified distance 32 DriveDistance ( 33 constants . kAutoBackupDistanceInches , - constants . kAutoDriveSpeed , drive 34 ), 35 ) The advantages and disadvantages of this subclassing approach in comparison to others are discussed in Subclassing Command Groups .",
- "content_preview": "Command Compositions Individual commands are capable of accomplishing a large variety of robot tasks, but the simple three-state format can quickly become cumbersome when more advanced functionality requiring extended sequences of robot tasks or coordination of multiple robot subsystems is..."
+ "content": "Step 4: Creating and Following a Trajectory With our drive subsystem written, it is now time to generate a trajectory and write an autonomous command to follow it. As per the standard command-based project structure , we will do this in the getAutonomousCommand method of the RobotContainer class. The full method from the RamseteCommand Example Project ( Java , C++ ) can be seen below. The rest of the article will break down the different parts of the method in more detail. Java 74 /** 75 * Use this to pass the autonomous command to the main {@link Robot} class. 76 * 77 * @return the command to run in autonomous 78 */ 79 public Command getAutonomousCommand () { 80 // Create a voltage constraint to ensure we don't accelerate too fast 81 var autoVoltageConstraint = 82 new DifferentialDriveVoltageConstraint ( 83 new SimpleMotorFeedforward ( 84 DriveConstants . ksVolts , 85 DriveConstants . kvVoltSecondsPerMeter , 86 DriveConstants . kaVoltSecondsSquaredPerMeter ), 87 DriveConstants . kDriveKinematics , 88 10 ); 89 90 // Create config for trajectory 91 TrajectoryConfig config = 92 new TrajectoryConfig ( 93 AutoConstants . kMaxSpeedMetersPerSecond , 94 AutoConstants . kMaxAccelerationMetersPerSecondSquared ) 95 // Add kinematics to ensure max speed is actually obeyed 96 . setKinematics ( DriveConstants . kDriveKinematics ) 97 // Apply the voltage constraint 98 . addConstraint ( autoVoltageConstraint ); 99 100 // An example trajectory to follow. All units in meters. 101 Trajectory exampleTrajectory = 102 TrajectoryGenerator . generateTrajectory ( 103 // Start at the origin facing the +X direction 104 new Pose2d ( 0 , 0 , new Rotation2d ( 0 )), 105 // Pass through these two interior waypoints, making an 's' curve path 106 List . of ( new Translation2d ( 1 , 1 ), new Translation2d ( 2 , - 1 )), 107 // End 3 meters straight ahead of where we started, facing forward 108 new Pose2d ( 3 , 0 , new Rotation2d ( 0 )), 109 // Pass config 110 config ); 111 112 RamseteCommand ramseteCommand = 113 new RamseteCommand ( 114 exampleTrajectory , 115 m_robotDrive :: getPose , 116 new RamseteController ( AutoConstants . kRamseteB , AutoConstants . kRamseteZeta ), 117 new SimpleMotorFeedforward ( 118 DriveConstants . ksVolts , 119 DriveConstants . kvVoltSecondsPerMeter , 120 DriveConstants . kaVoltSecondsSquaredPerMeter ), 121 DriveConstants . kDriveKinematics , 122 m_robotDrive :: getWheelSpeeds , 123 new PIDController ( DriveConstants . kPDriveVel , 0 , 0 ), 124 new PIDController ( DriveConstants . kPDriveVel , 0 , 0 ), 125 // RamseteCommand passes volts to the callback 126 m_robotDrive :: tankDriveVolts , 127 m_robotDrive ); 128 129 // Reset odometry to the initial pose of the trajectory, run path following 130 // command, then stop at the end. 131 return Commands . runOnce (() -> m_robotDrive . resetOdometry ( exampleTrajectory . getInitialPose ())) 132 . andThen ( ramseteCommand ) 133 . andThen ( Commands . runOnce (() -> m_robotDrive . tankDriveVolts ( 0 , 0 ))); 134 } 135 } C++ (Source) 45 frc2 :: CommandPtr RobotContainer::GetAutonomousCommand () { 46 // Create a voltage constraint to ensure we don't accelerate too fast 47 frc :: DifferentialDriveVoltageConstraint autoVoltageConstraint { 48 frc :: SimpleMotorFeedforward < units :: meters > { 49 DriveConstants :: ks , DriveConstants :: kv , DriveConstants :: ka }, 50 DriveConstants :: kDriveKinematics , 10 _V }; 51 52 // Set up config for trajectory 53 frc :: TrajectoryConfig config { AutoConstants :: kMaxSpeed , 54 AutoConstants :: kMaxAcceleration }; 55 // Add kinematics to ensure max speed is actually obeyed 56 config . SetKinematics ( DriveConstants :: kDriveKinematics ); 57 // Apply the voltage constraint 58 config . AddConstraint ( autoVoltageConstraint ); 59 60 // An example trajectory to follow. All units in meters. 61 auto exampleTrajectory = frc :: TrajectoryGenerator :: GenerateTrajectory ( 62 // Start at the origin facing the +X direction 63 frc :: Pose2d { 0 _m , 0 _m , 0 _deg }, 64 // Pass through these two interior waypoints, making an 's' curve path 65 { frc :: Translation2d { 1 _m , 1 _m }, frc :: Translation2d { 2 _m , -1 _m }}, 66 // End 3 meters straight ahead of where we started, facing forward 67 frc :: Pose2d { 3 _m , 0 _m , 0 _deg }, 68 // Pass the config 69 config ); 70 71 frc2 :: CommandPtr ramseteCommand { frc2 :: RamseteCommand ( 72 exampleTrajectory , [ this ] { return m_drive . GetPose (); }, 73 frc :: RamseteController { AutoConstants :: kRamseteB , 74 AutoConstants :: kRamseteZeta }, 75 frc :: SimpleMotorFeedforward < units :: meters > { 76 DriveConstants :: ks , DriveConstants :: kv , DriveConstants :: ka }, 77 DriveConstants :: kDriveKinematics , 78 [ this ] { return m_drive . GetWheelSpeeds (); }, 79 frc :: PIDController { DriveConstants :: kPDriveVel , 0 , 0 }, 80 frc :: PIDController { DriveConstants :: kPDriveVel , 0 , 0 }, 81 [ this ]( auto left , auto right ) { m_drive . TankDriveVolts ( left , right ); }, 82 { & m_drive })}; 83 84 // Reset odometry to the initial pose of the trajectory, run path following 85 // command, then stop at the end. 86 return frc2 :: cmd :: RunOnce ( 87 [ this , initialPose = exampleTrajectory . InitialPose ()] { 88 m_drive . ResetOdometry ( initialPose ); 89 }, 90 {}) 91 . AndThen ( std :: move ( ramseteCommand )) 92 . AndThen ( 93 frc2 :: cmd :: RunOnce ([ this ] { m_drive . TankDriveVolts ( 0 _V , 0 _V ); }, {})); 94 } Configuring the Trajectory Constraints First, we must set some configuration parameters for the trajectory which will ensure that the generated trajectory is followable. Creating a Voltage Constraint The first piece of configuration we will need is a voltage constraint. This will ensure that the generated trajectory never commands the robot to go faster than it is capable of achieving with the given voltage supply: Java 80 // Create a voltage constraint to ensure we don't accelerate too fast 81 var autoVoltageConstraint = 82 new DifferentialDriveVoltageConstraint ( 83 new SimpleMotorFeedforward ( 84 DriveConstants . ksVolts , 85 DriveConstants . kvVoltSecondsPerMeter , 86 DriveConstants . kaVoltSecondsSquaredPerMeter ), 87 DriveConstants . kDriveKinematics , 88 10 ); C++ (Source) 46 // Create a voltage constraint to ensure we don't accelerate too fast 47 frc :: DifferentialDriveVoltageConstraint autoVoltageConstraint { 48 frc :: SimpleMotorFeedforward < units :: meters > { 49 DriveConstants :: ks , DriveConstants :: kv , DriveConstants :: ka }, 50 DriveConstants :: kDriveKinematics , 10 _V }; Notice that we set the maximum voltage to 10V, rather than the nominal battery voltage of 12V. This gives us some “headroom” to deal with “voltage sag” during operation. Creating the Configuration Now that we have our voltage constraint, we can create our TrajectoryConfig instance, which wraps together all of our path constraints: Java 90 // Create config for trajectory 91 TrajectoryConfig config = 92 new TrajectoryConfig ( 93 AutoConstants . kMaxSpeedMetersPerSecond , 94 AutoConstants . kMaxAccelerationMetersPerSecondSquared ) 95 // Add kinematics to ensure max speed is actually obeyed 96 . setKinematics ( DriveConstants . kDriveKinematics ) 97 // Apply the voltage constraint 98 . addConstraint ( autoVoltageConstraint ); C++ (Source) 52 // Set up config for trajectory 53 frc :: TrajectoryConfig config { AutoConstants :: kMaxSpeed , 54 AutoConstants :: kMaxAcceleration }; 55 // Add kinematics to ensure max speed is actually obeyed 56 config . SetKinematics ( DriveConstants :: kDriveKinematics ); 57 // Apply the voltage constraint 58 config . AddConstraint ( autoVoltageConstraint ); Generating the Trajectory With our trajectory configuration in hand, we are now ready to generate our trajectory. For this example, we will be generating a “clamped cubic” trajectory - this means we will specify full robot poses at the endpoints, and positions only for interior waypoints (also known as “knot points”). As elsewhere, all distances are in meters. Java 100 // An example trajectory to follow. All units in meters. 101 Trajectory exampleTrajectory = 102 TrajectoryGenerator . generateTrajectory ( 103 // Start at the origin facing the +X direction 104 new Pose2d ( 0 , 0 , new Rotation2d ( 0 )), 105 // Pass through these two interior waypoints, making an 's' curve path 106 List . of ( new Translation2d ( 1 , 1 ), new Translation2d ( 2 , - 1 )), 107 // End 3 meters straight ahead of where we started, facing forward 108 new Pose2d ( 3 , 0 , new Rotation2d ( 0 )), 109 // Pass config 110 config ); C++ (Source) 60 // An example trajectory to follow. All units in meters. 61 auto exampleTrajectory = frc :: TrajectoryGenerator :: GenerateTrajectory ( 62 // Start at the origin facing the +X direction 63 frc :: Pose2d { 0 _m , 0 _m , 0 _deg }, 64 // Pass through these two interior waypoints, making an 's' curve path 65 { frc :: Translation2d { 1 _m , 1 _m }, frc :: Translation2d { 2 _m , -1 _m }}, 66 // End 3 meters straight ahead of where we started, facing forward 67 frc :: Pose2d { 3 _m , 0 _m , 0 _deg }, 68 // Pass the config 69 config ); Note Instead of generating the trajectory on the roboRIO as outlined above, one can also import a PathWeaver JSON . Creating the RamseteCommand We will first reset our robot’s pose to the starting pose of the trajectory. This ensures that the robot’s location on the coordinate system and the trajectory’s starting position are the same. Java 129 // Reset odometry to the initial pose of the trajectory, run path following 130 // command, then stop at the end. 131 return Commands . runOnce (() -> m_robotDrive . resetOdometry ( exampleTrajectory . getInitialPose ())) C++ (Source) 84 // Reset odometry to the initial pose of the trajectory, run path following 85 // command, then stop at the end. 86 return frc2 :: cmd :: RunOnce ( It is very important that the initial robot pose match the first pose in the trajectory. For the purposes of our example, the robot will be reliably starting at a position of (0,0) with a heading of 0 . In actual use, however, it is probably not desirable to base your coordinate system on the robot position, and so the starting position for both the robot and the trajectory should be set to some other value. If you wish to use a trajectory that has been defined in robot-centric coordinates in such a situation, you can transform it to be relative to the robot’s current pose using the transformBy method ( Java , C++ ). For more information about transforming trajectories, see Transforming Trajectories . Now that we have a trajectory, we can create a command that, when executed, will follow that trajectory. To do this, we use the RamseteCommand class ( Java , C++ ) Java 112 RamseteCommand ramseteCommand = 113 new RamseteCommand ( 114 exampleTrajectory , 115 m_robotDrive :: getPose , 116 new RamseteController ( AutoConstants . kRamseteB , AutoConstants . kRamseteZeta ), 117 new SimpleMotorFeedforward ( 118 DriveConstants . ksVolts , 119 DriveConstants . kvVoltSecondsPerMeter , 120 DriveConstants . kaVoltSecondsSquaredPerMeter ), 121 DriveConstants . kDriveKinematics , 122 m_robotDrive :: getWheelSpeeds , 123 new PIDController ( DriveConstants . kPDriveVel , 0 , 0 ), 124 new PIDController ( DriveConstants . kPDriveVel , 0 , 0 ), 125 // RamseteCommand passes volts to the callback 126 m_robotDrive :: tankDriveVolts , 127 m_robotDrive ); C++ (Source) 71 frc2 :: CommandPtr ramseteCommand { frc2 :: RamseteCommand ( 72 exampleTrajectory , [ this ] { return m_drive . GetPose (); }, 73 frc :: RamseteController { AutoConstants :: kRamseteB , 74 AutoConstants :: kRamseteZeta }, 75 frc :: SimpleMotorFeedforward < units :: meters > { 76 DriveConstants :: ks , DriveConstants :: kv , DriveConstants :: ka }, 77 DriveConstants :: kDriveKinematics , 78 [ this ] { return m_drive . GetWheelSpeeds (); }, 79 frc :: PIDController { DriveConstants :: kPDriveVel , 0 , 0 }, 80 frc :: PIDController { DriveConstants :: kPDriveVel , 0 , 0 }, 81 [ this ]( auto left , auto right ) { m_drive . TankDriveVolts ( left , right ); }, 82 { & m_drive })}; This declaration is fairly substantial, so we’ll go through it argument-by-argument: The trajectory: This is the trajectory to be followed; accordingly, we pass the command the trajectory we just constructed in our earlier steps. The pose supplier: This is a method reference (or lambda) to the drive subsystem method that returns the pose . The RAMSETE controller needs the current pose measurement to determine the required wheel outputs. The RAMSETE controller: This is the RamseteController object ( Java , C++ ) that will perform the path-following computation that translates the current measured pose and trajectory state into a chassis speed setpoint. The drive feedforward: This is a SimpleMotorFeedforward object ( Java , C++ ) that will automatically perform the correct feedforward calculation with the feedforward gains ( kS , kV , and kA ) that we obtained from the drive identification tool. The drive kinematics: This is the DifferentialDriveKinematics object ( Java , C++ ) that we constructed earlier in our constants file, and will be used to convert chassis speeds to wheel speeds. The wheel speed supplier: This is a method reference (or lambda) to the drive subsystem method that returns the wheel speeds The left-side PIDController: This is the PIDController object ( Java , C++ ) that will track the left-side wheel speed setpoint, using the P gain that we obtained from the drive identification tool. The right-side PIDController: This is the PIDController object ( Java , C++ ) that will track the right-side wheel speed setpoint, using the P gain that we obtained from the drive identification tool. The output consumer: This is a method reference (or lambda) to the drive subsystem method that passes the voltage outputs to the drive motors . The robot drive: This is the drive subsystem itself, included to ensure the command does not operate on the drive at the same time as any other command that uses the drive. Finally, note that we append a final “stop” command in sequence after the path-following command, to ensure that the robot stops moving at the end of the trajectory. Video If all has gone well, your robot’s autonomous routine should look something like this:",
+ "content_preview": "Step 4: Creating and Following a Trajectory With our drive subsystem written, it is now time to generate a trajectory and write an autonomous command to follow it. As per the standard command-based project structure , we will do this in the getAutonomousCommand method of the RobotContainer class."
},
{
"url": "https://docs.wpilib.org/en/stable/docs/software/frc-glossary.html",
@@ -68,15 +60,15 @@
"content_preview": "FRC Glossary accelerometer A common sensor used to measure acceleration in one or more axis. AM AndyMark, Inc - strives to develop innovative products and outstanding service while inspiring our customers and making a positive impact in our community."
},
{
- "url": "https://docs.wpilib.org/en/stable/docs/software/frc-glossary.html?present",
- "title": "FRC Glossary",
- "section": "General",
+ "url": "https://docs.wpilib.org/en/stable/docs/software/commandbased/profile-subsystems-commands.html",
+ "title": "Motion Profiling in Command",
+ "section": "Command-Based Programming",
"language": "All",
- "content": "FRC Glossary accelerometer A common sensor used to measure acceleration in one or more axis. AM AndyMark, Inc - strives to develop innovative products and outstanding service while inspiring our customers and making a positive impact in our community. AprilTags Visual tags that provide low overhead, high accuracy localization. AprilTags are useful for helping your robot know where it is at on the field, so it can align itself to some goal position. auto The first phase of each match is called Autonomous (auto) and consists of the robot’s running pre-programmed instructions. back-EMF In electric motors, the force generated by the interaction of spinning magnets in a coil of wire which opposes spinning motion. boolean A form of data with only two possible values (true or false), intended to represent the two truth values of logic and Boolean algebra. call stack A specially-organized region of memory which helps the program keep track of what function it is in. As each function calls another, the call point is recorded and added to the top of the structure, forming a “stack” of references. Additionally, local variables will also be stored in this stack. See call stack on Wikipedia for more info. CAD Computer-Aided Design - software used to design an accurate model of an object. For FRC this is often used to design the robot to get accurate measurements and aid construction. CAM Computer-Aided Manufacturing - the use of software to control machine tools in the manufacturing of work pieces. CAN Controller Area Network - message-based protocol designed to allow microcontrollers and devices to communicate with each other’s applications without a host computer. CD Chief Delphi - FRC team 47 inspired a popular community driven forum that today serves as an unofficial discussion hub for all things FRC. central limit theorem A core concept in probability which states that when many independent variables are added up, the result tends to look like a “normal” (or Gaussian) distribution, regardless of whether the independent variables themselves are normally distributed. See Central Limit Theorem on Wikipedia for more info. CIM CCL Industrial Motor, Limited - Chiaphua Components Limited is the company that made the commonly used, relatively powerful, brushed motor. Classical Mechanics The branch of physics which studies and describes the motion of relatively large, relatively slow objects. See Classical Mechanics on Wikipedia for more info. COTS Commercial off the shelf - a standard (i.e. not custom order) part commonly available from a vendor to all teams for purchase. composition A formal software term for building (or “composing”) software entities out of smaller component entities. See object composition on Wikipedia for more info. CRTP Continuously Recurring Template Pattern - A software idiom in which a class X` derives from a class template instantiation using X` itself as a template argument. See CRTP on Wikipedia for more info. CSA Control Systems Advisor - FRC volunteer position that assists teams with Robot Control System-related issues. CTRE Cross the Road Electronics LLC - is an engineering design, software development, and electronics manufacturer based outside of Detroit in Macomb, MI. They primarily focus on high-performing, high-quality electronics communication, motor control, and control system products for FIRST teams and the EV industry. CTR Electronics was founded in 2006 by two FRC mentors who met through their robotics team: Mike Copioli and Omar Zrien and is staffed largely by FRC alumni, and active volunteers & mentors. C++ One of the four officially supported programming languages. declarative programming A style of software which focuses on describing what a program should do, rather than how it gets done. See declarative programming on Wikipedia for more info. dependency injection A software design pattern where each class receives all objects it depends upon. Sometimes these are passed through the constructor, but not always. See dependency injection on Wikipedia for more info. deprecated Software that has been replaced and will no longer receive new features. Deprecated software will be maintained for at least 1 year, but may be removed after that. For example, if a method is deprecated prior to the 2022 season, it will be usable in the 2022 season, but may be removed prior to the 2023 season. Teams are encouraged to not use deprecated methods in new code. WPILib always deprecates features at least one year prior to removing them from the codebase. design pattern A particular, intentionally-chosen style of organizing code. A design pattern intentionally excludes using certain features of a programming language to constrain developers into solutions that are well-suited to a particular problem-space. See design pattern. on Wikipedia for more info. DHCP Dynamic Host Configuration Protocol - the protocol that allows a central device to assign unique IP addresses to all other devices. encapsulation A software design pattern which uses a class to hide the implementation details of other classes. See encapsulation on Wikipedia for more info. entry In NetworkTables , a combined publisher and subscriber . The subscriber is always active, but the publisher is not created until a publish operation is performed (e.g. a value is “set”, aka published, on the entry). This may be more convenient than maintaining a separate publisher and subscriber. enumeration A list of all elements of a set, typically used to refer to a set of pre-defined values. EPA Expected Points Added - builds upon the Elo rating system, but transforms ratings to point units and makes several modifications. event-driven programming A style of programming where certain parts of code generate “events” as a result of some input (sensors, user interaction, etc). Then, other parts of code listen for and respond to “handle” these events. See event-based on Wikipedia for more info. FIRST For Inspiration and Recognition of Science and Technology - a global nonprofit organization that prepares young people for the future through a suite of life-changing youth robotics programs that build skills, confidence, and resilience. FLL FIRST Lego League - Introduces science, technology, engineering, and math (STEM) to children ages 4-16 through fun, exciting hands-on learning. floating point A method for approximating real numbers in computer-based arithmetic, using a fixed precision integer scaled by an integer exponent. Typically computer systems support both “single” precision (32-bit storage) and “double” precision (64-bit storage) floating point values, as defined by IEEE 754. FMS Field Management System - the electronics core responsible for sensing and controlling the FIRST Robotics Competition field. FPGA Field-programmable gate array - a specialized integrated circuit consisting of many digital logic elements, which can be configured to act in different patterns. This allows its behavior to be changed after manufacturing. In the context of FRC, National Instruments provides a specific configuration for the RIO’s FPGA which allows it to process the electrical inputs and outputs at a very high rate. See FPGA on Wikipedia for more info. FRC FIRST Robotics Competition - Combining the excitement of sport with the rigors of science and technology. The ultimate Sport for the Mind inspiring High-school students. FTA FIRST Technical Advisor - FRC volunteer position that is responsible for ensuring FIRST Robotics Competition events run smoothly, safely, and in accordance with FIRST requirements, and ensuring a high-quality experience for all event participants and teams. FTC FIRST Tech Challenge - Grades 7-12 are challenged to design, build, program, and operate robots to compete in a head-to-head challenge in an alliance format. GDC Game Design Committee - designs the game for each year and develops the rules and field setup for each competition. GP Gracious Professionalism - part of the ethos of FIRST. It’s a way of doing things that encourages high-quality work, emphasizes the value of others, and respects individuals and the community. GradleRIO The mechanism that powers the deployment of robot code to the roboRIO. gyroscope A device that measures rate of rotation. It can add up the rotation measurements to determine heading of the robot. (“gyro”, for short) heading The direction the robot is pointed, usually expressed as an angle in degrees. imperative programming A style of programming that focuses on what the code should be doing, step by step, every loop. See imperative programming on Wikipedia for more info. IMU Inertial Measurement Unit - a sensor that combines both an accelerometer and a gyroscope into a single sensor. I2C Inter-Integrated Circuit - a synchronous, multi-master/multi-slave (controller/target), single-ended, serial communication bus. Java One of the four officially supported programming languages. JSON JavaScript Object Notation - A standardized way of organizing data into named values. The organized data can be easily serialized . While the original usage was in Javascript, it can be used and interested by most modern programming languages. See JSON on Wikipedia for more info. KOP Kit of Parts - the collection of items listed on the Kickoff Kit checklists, distributed to the team via FIRST Choice, or paid for completely (except shipping) with a Product Donation Voucher (PDV). KOP chassis The KOP contains a drive base (chassis) distributed to every team (that did not opt out) as part of the KOP . For the 2026 season, the KOP chassis is the AM14U6 . LabVIEW One of the four officially supported programming languages. LED Light-Emitting Diode - a semiconductor device that emits light when current flows through it. Used on multiple robot parts to convey the status of the device. mass the amount of matter in a physical object. Objects with more mass will resist changes in motion more than objects with less mass. See mass on Wikipedia for more info. moment of inertia The property of an object that describes both how much mass it has, and how that mass is distributed relative to a certain axis of rotation. Objects with higher moments of inertia resist changes in rotational motion more than objects with lower moments of inertia. Increasing the moment of inertia is accomplished by adding more mass, or moving the mass further away from the axis of rotation. See moment of inertia on Wikipedia for more info. mutable An object that can be modified after it is created. MXP myRIO Expansion Port - Port in the center of the roboRIO designed to expand the traditional IO count by offering multiple different IO types through one connector. NetworkTables A publish-subscribe messaging system to communicate data between programs. no-op No-op is a computer instruction which means no operation. When the computer processor encounters a no-op instruction, it simply moves to the next sequential instruction. Read more about no-op on Wikipedia . odometry Using sensors on the robot to create an estimate of the pose of the robot on the field. OPR Offensive Power Rating - a system to attempt to deduce the average point contribution of a team to an alliance PCM Pneumatic Control Module - provides an easy all-in-one interface for pneumatic components. PDH REV Power Distribution Hub - latest evolution in power distribution for FRC. With 20 high-current (40A max) channels, 3 low-current (15A max), and 1 switchable low-current channel, the PDH gives teams more flexibility for overall power delivery. PDP CTRE Power Distribution Panel - power distribution module with 8 high-current (40A max), 8 lower current (20A / 30A), 1 20A protected channel (for PCM and VRM ), and 1 10A protected channel (for the roboRIO). permanent-magnet DC motor The classification of all legal motors for the FIRST robotics competition. This type of motor takes direct current as input, and uses it to create a magnetic field. In turn, this magnetic field interacts with a physical magnet to create a force that turns the output shaft. Electrical (“brushless”) or mechanical (“brushed”) means are used to ensure the electrically-generated magnetic field always points in a direction that creates forces when it interacts with the physical magnet, even as the motor’s shaft rotates. See permanent-magnet motor on Wikipedia for more info. persistent In NetworkTables , a topic that is saved to a file by the server and restored at startup. PH Pneumatic Hub - is a standalone module that is capable of switching both 12V and 24V pneumatic solenoid valves. The Pneumatic Hub features 16 solenoid channels which allow for up to 16 single-acting solenoids, 8 double-acting solenoids, or a combination of the two types. PoE Power Over Ethernet - method of powering a device by an Ethernet cord that also carries power. FRC uses passive PoE usually 12-24V that is always being supplied, this can damage a device not expecting the provided voltage. The most common industry standard is active PoE which uses 48V but first verifies that the device is expecting the power. property In NetworkTables , named information (metadata) about a topic stored and updated separately from the topic’s data. A topic may have any number of properties. A property’s value can be any data type that can be represented in JSON. publisher In NetworkTables , an object that defines a topic and creates and sends timestamped data values. pose The collection of position and rotation information that describes how a rigid body is oriented in space, relative to some fixed reference point. pose estimation The process of estimating the robot’s pose, commonly with odometry and/or AprilTags . Also known as on-field localization . PWM Pulse-width modulation - a method of controlling the average power or amplitude delivered by an electrical signal. Used in FRC to control the output of motors not using the CAN bus. Python One of the four officially supported programming languages. RAII Resource Acquisition Is Initialization - a language behavior (in C++, but not in Java) where holding a resource is tied to object lifetime. retro-reflection The property of reflecting incoming light back at the same angle it came in at, rather than an incident angle (like a mirror), absorbing it, or scattering it. Most FRC vision processing targets are retro-reflective. See retroreflector on Wikipedia for more information. recursive composition A type of composition in which the composite object may contain components of the same type as itself. For example, a command group may contain one or more command groups. See recursive composition on Wikipedia for more info. See also recursive composition . retained In NetworkTables , a topic that is kept alive by the server even after all publishers stop publishing. REV REV Robotics - inspires innovation and creativity within the educational robotics community by offering comprehensive product lines, extensive educational resources, world-class customer service, and specialized sponsorship programs. With a global presence spanning over 190 countries, we empower the next generation of STEM professionals by providing cutting-edge solutions and essential tools for success. Founded in 2014 by robotics enthusiasts Greg Needel and David Yanoshak, REV Robotics is driven by the mission to inspire and support students as they explore the exciting world of robotics and unlock their full robotic design potential. A majority of our employees are FIRST Alumni who remain actively involved, serving as volunteers and mentors for the local FIRST Community. This deep engagement reflects our commitment to supporting and inspiring the next generation of STEM enthusiasts. RPM Radio Power Module - is designed to keep one of the most critical system components, the OpenMesh OM5P-AC WiFi radio, powered in the toughest moments of the competition. Revolutions Per Minute - a unit of rotational speed often used when describing motors. RSL Robot Signal Light - safety light on every FRC robot used to indicate its operational status. serialized The property of a data organization scheme that allows the description of the data to be sent in order, byte by byte, over some communication channel. Reading or writing a file on disk is done in this serial fashion (IE, the data is read or written byte by byte, not all at once). Sending data over a SPI or I2C bus is also done byte by byte, again requiring the data can be serialized. simulation A way for teams to test their code without having an actual robot available. software library A collection of code that can be imported into and used by other software. See software library on Wikipedia for more info. solenoid valve A airflow-controlling valve which is actuated by a small electromagnet. Strictly speaking, the solenoid is the coil of wire which forms the electromagnet, and the valve is the mechanism which actually redirects airflow. However, the set of solenoid and valve together is often simply called “a solenoid”. See solenoid valve . on Wikipedia for more info. SPI Serial Peripheral Interface - protocol for synchronous serial communication, used primarily in embedded systems for short-distance wired communication between integrated circuits. state machine A programming construct that divides a problem into many discrete, well-defined, mutually-exclusive “states”, then defines how the problem is solved by moving between different states. See state machine on Wikipedia for more more info. subscriber In NetworkTables , an object that receives timestamped data value updates to one or more topic s. TBA The Blue Alliance - Website for looking up FRC team statistics and event information. telemetry The process of recording and sending real-time data about the performance of your robot to a real-time readout or log file. For the linguists among us, the word’s roots are “tele” (remote) and “metry” (measurement). See telemetry on Wikipedia for more info. teleop The second phase of each match is called the Teleoperated Period (teleop) and consists of drivers controlling their robots. topic In NetworkTables , a named data channel. torque A force applied at a distance from some axis of rotation trajectory A trajectory is a smooth curve, with velocities, and accelerations at each point along the curve, connecting two endpoints on the field. transitory In NetworkTables , a topic that will disappear after the last publisher stops publishing. VRM Voltage Regulator Module - provides access to different constant voltages for custom sensors, cameras, or other unique applications. 12V DC Input Directly fed power from the Power Distribution Panel Designed to work with the roboRIO FRC control system. WCP WestCoast Products & Design LLC - was founded in Fall of 2011 by Ranjit Chahal (R.C.) and Harvey Rico. WCP aims to provide FIRST Teams, Hobbyists, and educators top notch quality products and designs for their projects. WFA Woodie Flowers Award - This award recognizes an individual who has done an outstanding job of motivation through communication while also challenging the students to be clear and succinct in their communications.",
- "content_preview": "FRC Glossary accelerometer A common sensor used to measure acceleration in one or more axis. AM AndyMark, Inc - strives to develop innovative products and outstanding service while inspiring our customers and making a positive impact in our community."
+ "content": "Motion Profiling in Command-based Note For a description of the WPILib motion profiling features used by these command-based wrappers, see Trapezoidal Motion Profiles in WPILib . Note The TrapezoidProfile class, used on its own, is most useful when composed with external controllers, such as a “smart” motor controller with a built-in PID functionality. For combining trapezoidal motion profiling with WPILib’s PIDController , see Combining Motion Profiling and PID in Command-Based . When controlling a mechanism, is often desirable to move it smoothly between two positions, rather than to abruptly change its setpoint. This is called “motion-profiling,” and is supported in WPILib through the TrapezoidProfile class ( Java , C++ ). Note In C++, the TrapezoidProfile class is templated on the unit type used for distance measurements, which may be angular or linear. The passed-in values must have units consistent with the distance units, or a compile-time error will be thrown. For more information on C++ units, see The C++ Units Library . The following examples are taken from the DriveDistanceOffboard example project ( Java , C++ ): Java 5 package edu.wpi.first.wpilibj.examples.drivedistanceoffboard.subsystems ; 6 7 import edu.wpi.first.math.controller.SimpleMotorFeedforward ; 8 import edu.wpi.first.math.trajectory.TrapezoidProfile ; 9 import edu.wpi.first.math.trajectory.TrapezoidProfile.State ; 10 import edu.wpi.first.util.sendable.SendableRegistry ; 11 import edu.wpi.first.wpilibj.RobotController ; 12 import edu.wpi.first.wpilibj.Timer ; 13 import edu.wpi.first.wpilibj.drive.DifferentialDrive ; 14 import edu.wpi.first.wpilibj.examples.drivedistanceoffboard.Constants.DriveConstants ; 15 import edu.wpi.first.wpilibj.examples.drivedistanceoffboard.ExampleSmartMotorController ; 16 import edu.wpi.first.wpilibj2.command.Command ; 17 import edu.wpi.first.wpilibj2.command.SubsystemBase ; 18 19 public class DriveSubsystem extends SubsystemBase { 20 // The motors on the left side of the drive. 21 private final ExampleSmartMotorController m_leftLeader = 22 new ExampleSmartMotorController ( DriveConstants . kLeftMotor1Port ); 23 24 private final ExampleSmartMotorController m_leftFollower = 25 new ExampleSmartMotorController ( DriveConstants . kLeftMotor2Port ); 26 27 // The motors on the right side of the drive. 28 private final ExampleSmartMotorController m_rightLeader = 29 new ExampleSmartMotorController ( DriveConstants . kRightMotor1Port ); 30 31 private final ExampleSmartMotorController m_rightFollower = 32 new ExampleSmartMotorController ( DriveConstants . kRightMotor2Port ); 33 34 // The feedforward controller. 35 private final SimpleMotorFeedforward m_feedforward = 36 new SimpleMotorFeedforward ( 37 DriveConstants . ksVolts , 38 DriveConstants . kvVoltSecondsPerMeter , 39 DriveConstants . kaVoltSecondsSquaredPerMeter ); 40 41 // The robot's drive 42 private final DifferentialDrive m_drive = 43 new DifferentialDrive ( m_leftLeader :: set , m_rightLeader :: set ); 44 45 // The trapezoid profile 46 private final TrapezoidProfile m_profile = 47 new TrapezoidProfile ( 48 new TrapezoidProfile . Constraints ( 49 DriveConstants . kMaxSpeedMetersPerSecond , 50 DriveConstants . kMaxAccelerationMetersPerSecondSquared )); 51 52 // The timer 53 private final Timer m_timer = new Timer (); 54 55 /** Creates a new DriveSubsystem. */ 56 public DriveSubsystem () { 57 SendableRegistry . addChild ( m_drive , m_leftLeader ); 58 SendableRegistry . addChild ( m_drive , m_rightLeader ); 59 60 // We need to invert one side of the drivetrain so that positive voltages 61 // result in both sides moving forward. Depending on how your robot's 62 // gearbox is constructed, you might have to invert the left side instead. 63 m_rightLeader . setInverted ( true ); 64 65 m_leftFollower . follow ( m_leftLeader ); 66 m_rightFollower . follow ( m_rightLeader ); 67 68 m_leftLeader . setPID ( DriveConstants . kp , 0 , 0 ); 69 m_rightLeader . setPID ( DriveConstants . kp , 0 , 0 ); 70 } 71 72 /** 73 * Drives the robot using arcade controls. 74 * 75 * @param fwd the commanded forward movement 76 * @param rot the commanded rotation 77 */ 78 public void arcadeDrive ( double fwd , double rot ) { 79 m_drive . arcadeDrive ( fwd , rot ); 80 } 81 82 /** 83 * Attempts to follow the given drive states using offboard PID. 84 * 85 * @param currentLeft The current left wheel state. 86 * @param currentRight The current right wheel state. 87 * @param nextLeft The next left wheel state. 88 * @param nextRight The next right wheel state. 89 */ 90 public void setDriveStates ( 91 TrapezoidProfile . State currentLeft , 92 TrapezoidProfile . State currentRight , 93 TrapezoidProfile . State nextLeft , 94 TrapezoidProfile . State nextRight ) { 95 // Feedforward is divided by battery voltage to normalize it to [-1, 1] 96 m_leftLeader . setSetpoint ( 97 ExampleSmartMotorController . PIDMode . kPosition , 98 currentLeft . position , 99 m_feedforward . calculateWithVelocities ( currentLeft . velocity , nextLeft . velocity ) 100 / RobotController . getBatteryVoltage ()); 101 m_rightLeader . setSetpoint ( 102 ExampleSmartMotorController . PIDMode . kPosition , 103 currentRight . position , 104 m_feedforward . calculateWithVelocities ( currentLeft . velocity , nextLeft . velocity ) 105 / RobotController . getBatteryVoltage ()); 106 } 107 108 /** 109 * Returns the left encoder distance. 110 * 111 * @return the left encoder distance 112 */ 113 public double getLeftEncoderDistance () { 114 return m_leftLeader . getEncoderDistance (); 115 } 116 117 /** 118 * Returns the right encoder distance. 119 * 120 * @return the right encoder distance 121 */ 122 public double getRightEncoderDistance () { 123 return m_rightLeader . getEncoderDistance (); 124 } 125 126 /** Resets the drive encoders. */ 127 public void resetEncoders () { 128 m_leftLeader . resetEncoder (); 129 m_rightLeader . resetEncoder (); 130 } 131 132 /** 133 * Sets the max output of the drive. Useful for scaling the drive to drive more slowly. 134 * 135 * @param maxOutput the maximum output to which the drive will be constrained 136 */ 137 public void setMaxOutput ( double maxOutput ) { 138 m_drive . setMaxOutput ( maxOutput ); 139 } 140 141 /** 142 * Creates a command to drive forward a specified distance using a motion profile. 143 * 144 * @param distance The distance to drive forward. 145 * @return A command. 146 */ 147 public Command profiledDriveDistance ( double distance ) { 148 return startRun ( 149 () -> { 150 // Restart timer so profile setpoints start at the beginning 151 m_timer . restart (); 152 resetEncoders (); 153 }, 154 () -> { 155 // Current state never changes, so we need to use a timer to get the setpoints we need 156 // to be at 157 var currentTime = m_timer . get (); 158 var currentSetpoint = 159 m_profile . calculate ( currentTime , new State (), new State ( distance , 0 )); 160 var nextSetpoint = 161 m_profile . calculate ( 162 currentTime + DriveConstants . kDt , new State (), new State ( distance , 0 )); 163 setDriveStates ( currentSetpoint , currentSetpoint , nextSetpoint , nextSetpoint ); 164 }) 165 . until (() -> m_profile . isFinished ( 0 )); 166 } 167 168 private double m_initialLeftDistance ; 169 private double m_initialRightDistance ; 170 171 /** 172 * Creates a command to drive forward a specified distance using a motion profile without 173 * resetting the encoders. 174 * 175 * @param distance The distance to drive forward. 176 * @return A command. 177 */ 178 public Command dynamicProfiledDriveDistance ( double distance ) { 179 return startRun ( 180 () -> { 181 // Restart timer so profile setpoints start at the beginning 182 m_timer . restart (); 183 // Store distance so we know the target distance for each encoder 184 m_initialLeftDistance = getLeftEncoderDistance (); 185 m_initialRightDistance = getRightEncoderDistance (); 186 }, 187 () -> { 188 // Current state never changes for the duration of the command, so we need to use a 189 // timer to get the setpoints we need to be at 190 var currentTime = m_timer . get (); 191 var currentLeftSetpoint = 192 m_profile . calculate ( 193 currentTime , 194 new State ( m_initialLeftDistance , 0 ), 195 new State ( m_initialLeftDistance + distance , 0 )); 196 var currentRightSetpoint = 197 m_profile . calculate ( 198 currentTime , 199 new State ( m_initialRightDistance , 0 ), 200 new State ( m_initialRightDistance + distance , 0 )); 201 var nextLeftSetpoint = 202 m_profile . calculate ( 203 currentTime + DriveConstants . kDt , 204 new State ( m_initialLeftDistance , 0 ), 205 new State ( m_initialLeftDistance + distance , 0 )); 206 var nextRightSetpoint = 207 m_profile . calculate ( 208 currentTime + DriveConstants . kDt , 209 new State ( m_initialRightDistance , 0 ), 210 new State ( m_initialRightDistance + distance , 0 )); 211 setDriveStates ( 212 currentLeftSetpoint , currentRightSetpoint , nextLeftSetpoint , nextRightSetpoint ); 213 }) 214 . until (() -> m_profile . isFinished ( 0 )); 215 } 216 } C++ (Header) 5 #pragma once 6 7 #include 8 #include 9 #include 10 #include 11 #include 12 #include 13 #include 14 #include 15 16 #include \"Constants.h\" 17 #include \"ExampleSmartMotorController.h\" 18 19 class DriveSubsystem : public frc2 :: SubsystemBase { 20 public : 21 DriveSubsystem (); 22 23 /** 24 * Will be called periodically whenever the CommandScheduler runs. 25 */ 26 void Periodic () override ; 27 28 // Subsystem methods go here. 29 30 /** 31 * Attempts to follow the given drive states using offboard PID. 32 * 33 * @param currentLeft The current left wheel state. 34 * @param currentRight The current right wheel state. 35 * @param nextLeft The next left wheel state. 36 * @param nextRight The next right wheel state. 37 */ 38 void SetDriveStates ( frc :: TrapezoidProfile < units :: meters >:: State currentLeft , 39 frc :: TrapezoidProfile < units :: meters >:: State currentRight , 40 frc :: TrapezoidProfile < units :: meters >:: State nextLeft , 41 frc :: TrapezoidProfile < units :: meters >:: State nextRight ); 42 43 /** 44 * Drives the robot using arcade controls. 45 * 46 * @param fwd the commanded forward movement 47 * @param rot the commanded rotation 48 */ 49 void ArcadeDrive ( double fwd , double rot ); 50 51 /** 52 * Resets the drive encoders to currently read a position of 0. 53 */ 54 void ResetEncoders (); 55 56 /** 57 * Gets the distance of the left encoder. 58 * 59 * @return the average of the TWO encoder readings 60 */ 61 units :: meter_t GetLeftEncoderDistance (); 62 63 /** 64 * Gets the distance of the right encoder. 65 * 66 * @return the average of the TWO encoder readings 67 */ 68 units :: meter_t GetRightEncoderDistance (); 69 70 /** 71 * Sets the max output of the drive. Useful for scaling the drive to drive 72 * more slowly. 73 * 74 * @param maxOutput the maximum output to which the drive will be constrained 75 */ 76 void SetMaxOutput ( double maxOutput ); 77 78 /** 79 * Creates a command to drive forward a specified distance using a motion 80 * profile. 81 * 82 * @param distance The distance to drive forward. 83 * @return A command. 84 */ 85 frc2 :: CommandPtr ProfiledDriveDistance ( units :: meter_t distance ); 86 87 /** 88 * Creates a command to drive forward a specified distance using a motion 89 * profile without resetting the encoders. 90 * 91 * @param distance The distance to drive forward. 92 * @return A command. 93 */ 94 frc2 :: CommandPtr DynamicProfiledDriveDistance ( units :: meter_t distance ); 95 96 private : 97 frc :: TrapezoidProfile < units :: meters > m_profile { 98 { DriveConstants :: kMaxSpeed , DriveConstants :: kMaxAcceleration }}; 99 frc :: Timer m_timer ; 100 units :: meter_t m_initialLeftDistance ; 101 units :: meter_t m_initialRightDistance ; 102 // Components (e.g. motor controllers and sensors) should generally be 103 // declared private and exposed only through public methods. 104 105 // The motor controllers 106 ExampleSmartMotorController m_leftLeader ; 107 ExampleSmartMotorController m_leftFollower ; 108 ExampleSmartMotorController m_rightLeader ; 109 ExampleSmartMotorController m_rightFollower ; 110 111 // A feedforward component for the drive 112 frc :: SimpleMotorFeedforward < units :: meters > m_feedforward ; 113 114 // The robot's drive 115 frc :: DifferentialDrive m_drive { 116 [ & ]( double output ) { m_leftLeader . Set ( output ); }, 117 [ & ]( double output ) { m_rightLeader . Set ( output ); }}; 118 }; C++ (Source) 5 #include \"subsystems/DriveSubsystem.h\" 6 7 #include 8 9 using namespace DriveConstants ; 10 11 DriveSubsystem :: DriveSubsystem () 12 : m_leftLeader { kLeftMotor1Port }, 13 m_leftFollower { kLeftMotor2Port }, 14 m_rightLeader { kRightMotor1Port }, 15 m_rightFollower { kRightMotor2Port }, 16 m_feedforward { ks , kv , ka } { 17 wpi :: SendableRegistry :: AddChild ( & m_drive , & m_leftLeader ); 18 wpi :: SendableRegistry :: AddChild ( & m_drive , & m_rightLeader ); 19 20 // We need to invert one side of the drivetrain so that positive voltages 21 // result in both sides moving forward. Depending on how your robot's 22 // gearbox is constructed, you might have to invert the left side instead. 23 m_rightLeader . SetInverted ( true ); 24 25 m_leftFollower . Follow ( m_leftLeader ); 26 m_rightFollower . Follow ( m_rightLeader ); 27 28 m_leftLeader . SetPID ( kp , 0 , 0 ); 29 m_rightLeader . SetPID ( kp , 0 , 0 ); 30 } 31 32 void DriveSubsystem :: Periodic () { 33 // Implementation of subsystem periodic method goes here. 34 } 35 36 void DriveSubsystem :: SetDriveStates ( 37 frc :: TrapezoidProfile < units :: meters >:: State currentLeft , 38 frc :: TrapezoidProfile < units :: meters >:: State currentRight , 39 frc :: TrapezoidProfile < units :: meters >:: State nextLeft , 40 frc :: TrapezoidProfile < units :: meters >:: State nextRight ) { 41 // Feedforward is divided by battery voltage to normalize it to [-1, 1] 42 m_leftLeader . SetSetpoint ( 43 ExampleSmartMotorController :: PIDMode :: kPosition , 44 currentLeft . position . value (), 45 m_feedforward . Calculate ( currentLeft . velocity , nextLeft . velocity ) / 46 frc :: RobotController :: GetBatteryVoltage ()); 47 m_rightLeader . SetSetpoint ( 48 ExampleSmartMotorController :: PIDMode :: kPosition , 49 currentRight . position . value (), 50 m_feedforward . Calculate ( currentRight . velocity , nextRight . velocity ) / 51 frc :: RobotController :: GetBatteryVoltage ()); 52 } 53 54 void DriveSubsystem :: ArcadeDrive ( double fwd , double rot ) { 55 m_drive . ArcadeDrive ( fwd , rot ); 56 } 57 58 void DriveSubsystem :: ResetEncoders () { 59 m_leftLeader . ResetEncoder (); 60 m_rightLeader . ResetEncoder (); 61 } 62 63 units :: meter_t DriveSubsystem :: GetLeftEncoderDistance () { 64 return units :: meter_t { m_leftLeader . GetEncoderDistance ()}; 65 } 66 67 units :: meter_t DriveSubsystem :: GetRightEncoderDistance () { 68 return units :: meter_t { m_rightLeader . GetEncoderDistance ()}; 69 } 70 71 void DriveSubsystem :: SetMaxOutput ( double maxOutput ) { 72 m_drive . SetMaxOutput ( maxOutput ); 73 } 74 75 frc2 :: CommandPtr DriveSubsystem :: ProfiledDriveDistance ( 76 units :: meter_t distance ) { 77 return StartRun ( 78 [ & ] { 79 // Restart timer so profile setpoints start at the beginning 80 m_timer . Restart (); 81 ResetEncoders (); 82 }, 83 [ & ] { 84 // Current state never changes, so we need to use a timer to get 85 // the setpoints we need to be at 86 auto currentTime = m_timer . Get (); 87 auto currentSetpoint = 88 m_profile . Calculate ( currentTime , {}, { distance , 0 _mps }); 89 auto nextSetpoint = m_profile . Calculate ( currentTime + kDt , {}, 90 { distance , 0 _mps }); 91 SetDriveStates ( currentSetpoint , currentSetpoint , nextSetpoint , 92 nextSetpoint ); 93 }) 94 . Until ([ & ] { return m_profile . IsFinished ( 0 _s ); }); 95 } 96 97 frc2 :: CommandPtr DriveSubsystem :: DynamicProfiledDriveDistance ( 98 units :: meter_t distance ) { 99 return StartRun ( 100 [ & ] { 101 // Restart timer so profile setpoints start at the beginning 102 m_timer . Restart (); 103 // Store distance so we know the target distance for each encoder 104 m_initialLeftDistance = GetLeftEncoderDistance (); 105 m_initialRightDistance = GetRightEncoderDistance (); 106 }, 107 [ & ] { 108 // Current state never changes for the duration of the command, 109 // so we need to use a timer to get the setpoints we need to be 110 // at 111 auto currentTime = m_timer . Get (); 112 113 auto currentLeftSetpoint = m_profile . Calculate ( 114 currentTime , { m_initialLeftDistance , 0 _mps }, 115 { m_initialLeftDistance + distance , 0 _mps }); 116 auto currentRightSetpoint = m_profile . Calculate ( 117 currentTime , { m_initialRightDistance , 0 _mps }, 118 { m_initialRightDistance + distance , 0 _mps }); 119 120 auto nextLeftSetpoint = m_profile . Calculate ( 121 currentTime + kDt , { m_initialLeftDistance , 0 _mps }, 122 { m_initialLeftDistance + distance , 0 _mps }); 123 auto nextRightSetpoint = m_profile . Calculate ( 124 currentTime + kDt , { m_initialRightDistance , 0 _mps }, 125 { m_initialRightDistance + distance , 0 _mps }); 126 SetDriveStates ( currentLeftSetpoint , currentRightSetpoint , 127 nextLeftSetpoint , nextRightSetpoint ); 128 }) 129 . Until ([ & ] { return m_profile . IsFinished ( 0 _s ); }); 130 } There are two commands in this example. They function very similarly, with the main difference being that one resets encoders, and the other doesn’t, which allows encoder data to be preserved. The subsystem contains a TrapezoidProfile with a Timer . The timer is used along with a kDt constant of 0.02 seconds to calculate the current and next states from the TrapezoidProfile . The current state is fed to the “smart” motor controller for PID control, while the current and next state are used to calculate feedforward outputs. Both commands end when isFinished(0) returns true, which means that the profile has reached the goal state.",
+ "content_preview": "Motion Profiling in Command-based Note For a description of the WPILib motion profiling features used by these command-based wrappers, see Trapezoidal Motion Profiles in WPILib ."
},
{
- "url": "https://docs.wpilib.org/en/stable/docs/software/commandbased/what-is-command-based.html",
+ "url": "https://docs.wpilib.org/en/stable/docs/software/commandbased/what-is-command-based.html?present",
"title": "What Is “Command",
"section": "Command-Based Programming",
"language": "All",
@@ -91,14 +83,6 @@
"content": "The Command Scheduler The CommandScheduler ( Java , C++ ) is the class responsible for actually running commands. Each iteration (ordinarily once per 20ms), the scheduler polls all registered buttons, schedules commands for execution accordingly, runs the command bodies of all scheduled commands, and ends those commands that have finished or are interrupted. The CommandScheduler also runs the periodic() method of each registered Subsystem . Using the Command Scheduler The CommandScheduler is a singleton , meaning that it is a globally-accessible class with only one instance. Accordingly, in order to access the scheduler, users must call the CommandScheduler.getInstance() command. For the most part, users do not have to call scheduler methods directly - almost all important scheduler methods have convenience wrappers elsewhere (e.g. in the Command and Subsystem classes). However, there is one exception: users must call CommandScheduler.getInstance().run() from the robotPeriodic() method of their Robot class. If this is not done, the scheduler will never run, and the command framework will not work. The provided command-based project template has this call already included. The schedule() Method To schedule a command, users call the schedule() method ( Java , C++ ). This method takes a command, and attempts to add it to list of currently-running commands, pending whether it is already running or whether its requirements are available. If it is added, its initialize() method is called. This method walks through the following steps: Verifies that the command isn’t in a composition. No-op if scheduler is disabled, command is already scheduled, or robot is disabled and command doesn’t runsWhenDisabled . If requirements are in use: If all conflicting commands are interruptible, cancel them. If not, don’t schedule the new command. Call initialize() . Java 202 private void schedule ( Command command ) { 203 if ( command == null ) { 204 DriverStation . reportWarning ( \"Tried to schedule a null command\" , true ); 205 return ; 206 } 207 if ( m_inRunLoop ) { 208 m_toSchedule . add ( command ); 209 return ; 210 } 211 212 requireNotComposed ( command ); 213 214 // Do nothing if the scheduler is disabled, the robot is disabled and the command doesn't 215 // run when disabled, or the command is already scheduled. 216 if ( m_disabled 217 || isScheduled ( command ) 218 || RobotState . isDisabled () && ! command . runsWhenDisabled ()) { 219 return ; 220 } 221 222 Set < Subsystem > requirements = command . getRequirements (); 223 224 // Schedule the command if the requirements are not currently in-use. 225 if ( Collections . disjoint ( m_requirements . keySet (), requirements )) { 226 initCommand ( command , requirements ); 227 } else { 228 // Else check if the requirements that are in use have all have interruptible commands, 229 // and if so, interrupt those commands and schedule the new command. 230 for ( Subsystem requirement : requirements ) { 231 Command requiring = requiring ( requirement ); 232 if ( requiring != null 233 && requiring . getInterruptionBehavior () == InterruptionBehavior . kCancelIncoming ) { 234 return ; 235 } 236 } 237 for ( Subsystem requirement : requirements ) { 238 Command requiring = requiring ( requirement ); 239 if ( requiring != null ) { 240 cancel ( requiring ); 241 } 242 } 243 initCommand ( command , requirements ); 244 } 245 } 181 private void initCommand ( Command command , Set < Subsystem > requirements ) { 182 m_scheduledCommands . add ( command ); 183 for ( Subsystem requirement : requirements ) { 184 m_requirements . put ( requirement , command ); 185 } 186 command . initialize (); 187 for ( Consumer < Command > action : m_initActions ) { 188 action . accept ( command ); 189 } 190 191 m_watchdog . addEpoch ( command . getName () + \".initialize()\" ); C++ (Source) 114 void CommandScheduler::Schedule ( Command * command ) { 115 if ( m_impl -> inRunLoop ) { 116 m_impl -> toSchedule . emplace_back ( command ); 117 return ; 118 } 119 120 RequireUngrouped ( command ); 121 122 if ( m_impl -> disabled || m_impl -> scheduledCommands . contains ( command ) || 123 ( frc :: RobotState :: IsDisabled () && ! command -> RunsWhenDisabled ())) { 124 return ; 125 } 126 127 const auto & requirements = command -> GetRequirements (); 128 129 wpi :: SmallVector < Command * , 8 > intersection ; 130 131 bool isDisjoint = true ; 132 bool allInterruptible = true ; 133 for ( auto && i1 : m_impl -> requirements ) { 134 if ( requirements . find ( i1 . first ) != requirements . end ()) { 135 isDisjoint = false ; 136 allInterruptible &= ( i1 . second -> GetInterruptionBehavior () == 137 Command :: InterruptionBehavior :: kCancelSelf ); 138 intersection . emplace_back ( i1 . second ); 139 } 140 } 141 142 if ( isDisjoint || allInterruptible ) { 143 if ( allInterruptible ) { 144 for ( auto && cmdToCancel : intersection ) { 145 Cancel ( cmdToCancel ); 146 } 147 } 148 m_impl -> scheduledCommands . insert ( command ); 149 for ( auto && requirement : requirements ) { 150 m_impl -> requirements [ requirement ] = command ; 151 } 152 command -> Initialize (); 153 for ( auto && action : m_impl -> initActions ) { 154 action ( * command ); 155 } 156 m_watchdog . AddEpoch ( command -> GetName () + \".Initialize()\" ); 157 } 158 } The Scheduler Run Sequence Note The initialize() method of each Command is called when the command is scheduled, which is not necessarily when the scheduler runs (unless that command is bound to a button). What does a single iteration of the scheduler’s run() method ( Java , C++ ) actually do? The following section walks through the logic of a scheduler iteration. For the full implementation, see the source code ( Java , C++ ). Step 1: Run Subsystem Periodic Methods First, the scheduler runs the periodic() method of each registered Subsystem . In simulation, each subsystem’s simulationPeriodic() method is called as well. Java 278 // Run the periodic method of all registered subsystems. 279 for ( Subsystem subsystem : m_subsystems . keySet ()) { 280 subsystem . periodic (); 281 if ( RobotBase . isSimulation ()) { 282 subsystem . simulationPeriodic (); 283 } 284 m_watchdog . addEpoch ( subsystem . getClass (). getSimpleName () + \".periodic()\" ); 285 } C++ (Source) 183 // Run the periodic method of all registered subsystems. 184 for ( auto && subsystem : m_impl -> subsystems ) { 185 subsystem . getFirst () -> Periodic (); 186 if constexpr ( frc :: RobotBase :: IsSimulation ()) { 187 subsystem . getFirst () -> SimulationPeriodic (); 188 } 189 m_watchdog . AddEpoch ( \"Subsystem Periodic()\" ); 190 } Step 2: Poll Command Scheduling Triggers Note For more information on how trigger bindings work, see Binding Commands to Triggers Secondly, the scheduler polls the state of all registered triggers to see if any new commands that have been bound to those triggers should be scheduled. If the conditions for scheduling a bound command are met, the command is scheduled and its initialize() method is run. Note If a newly-scheduled command has requirement conflicts with a currently-running command, the currently-running command is interrupted first. The end(true) method of the interrupted command is called before the initialize() method of the new command. Java 290 // Poll buttons for new commands to add. 291 loopCache . poll (); 292 m_watchdog . addEpoch ( \"buttons.run()\" ); C++ (Source) 195 // Poll buttons for new commands to add. 196 loopCache -> Poll (); 197 m_watchdog . AddEpoch ( \"buttons.Run()\" ); Step 3: Run/Finish Scheduled Commands Thirdly, the scheduler calls the execute() method of each currently-scheduled command, and then checks whether the command has finished by calling the isFinished() method. If the command has finished, the end() method is also called, and the command is de-scheduled and its required subsystems are freed. Note that this sequence of calls is done in order for each command - thus, one command may have its end() method called before another has its execute() method called. Commands are handled in the order they were scheduled. Java 295 // Run scheduled commands, remove finished commands. 296 for ( Iterator < Command > iterator = m_scheduledCommands . iterator (); iterator . hasNext (); ) { 297 Command command = iterator . next (); 298 299 if ( ! command . runsWhenDisabled () && RobotState . isDisabled ()) { 300 command . end ( true ); 301 for ( Consumer < Command > action : m_interruptActions ) { 302 action . accept ( command ); 303 } 304 m_requirements . keySet (). removeAll ( command . getRequirements ()); 305 iterator . remove (); 306 m_watchdog . addEpoch ( command . getName () + \".end(true)\" ); 307 continue ; 308 } 309 310 command . execute (); 311 for ( Consumer < Command > action : m_executeActions ) { 312 action . accept ( command ); 313 } 314 m_watchdog . addEpoch ( command . getName () + \".execute()\" ); 315 if ( command . isFinished ()) { 316 command . end ( false ); 317 for ( Consumer < Command > action : m_finishActions ) { 318 action . accept ( command ); 319 } 320 iterator . remove (); 321 322 m_requirements . keySet (). removeAll ( command . getRequirements ()); 323 m_watchdog . addEpoch ( command . getName () + \".end(false)\" ); 324 } 325 } C++ (Source) 201 for ( Command * command : m_impl -> scheduledCommands ) { 202 if ( ! command -> RunsWhenDisabled () && frc :: RobotState :: IsDisabled ()) { 203 Cancel ( command ); 204 continue ; 205 } 206 207 command -> Execute (); 208 for ( auto && action : m_impl -> executeActions ) { 209 action ( * command ); 210 } 211 m_watchdog . AddEpoch ( command -> GetName () + \".Execute()\" ); 212 213 if ( command -> IsFinished ()) { 214 command -> End ( false ); 215 for ( auto && action : m_impl -> finishActions ) { 216 action ( * command ); 217 } 218 219 for ( auto && requirement : command -> GetRequirements ()) { 220 m_impl -> requirements . erase ( requirement ); 221 } 222 223 m_impl -> scheduledCommands . erase ( command ); 224 m_watchdog . AddEpoch ( command -> GetName () + \".End(false)\" ); 225 } 226 } Step 4: Schedule Default Commands Finally, any registered Subsystem has its default command scheduled (if it has one). Note that the initialize() method of the default command will be called at this time. Java 340 // Add default commands for un-required registered subsystems. 341 for ( Map . Entry < Subsystem , Command > subsystemCommand : m_subsystems . entrySet ()) { 342 if ( ! m_requirements . containsKey ( subsystemCommand . getKey ()) 343 && subsystemCommand . getValue () != null ) { 344 schedule ( subsystemCommand . getValue ()); 345 } 346 } C++ (Source) 240 // Add default commands for un-required registered subsystems. 241 for ( auto && subsystem : m_impl -> subsystems ) { 242 auto s = m_impl -> requirements . find ( subsystem . getFirst ()); 243 if ( s == m_impl -> requirements . end () && subsystem . getSecond ()) { 244 Schedule ({ subsystem . getSecond (). get ()}); 245 } 246 } Disabling the Scheduler The scheduler can be disabled by calling CommandScheduler.getInstance().disable() . When disabled, the scheduler’s schedule() and run() commands will not do anything. The scheduler may be re-enabled by calling CommandScheduler.getInstance().enable() . Command Event Methods Occasionally, it is desirable to have the scheduler execute a custom action whenever a certain command event (initialization, execution, or ending) occurs. This can be done with the following methods: onCommandInitialize ( Java , C++ ) runs a specified action whenever a command is initialized. onCommandExecute ( Java , C++ ) runs a specified action whenever a command is executed. onCommandFinish ( Java , C++ ) runs a specified action whenever a command finishes normally (i.e. the isFinished() method returned true). onCommandInterrupt ( Java , C++ ) runs a specified action whenever a command is interrupted (i.e. by being explicitly canceled or by another command that shares one of its requirements). A typical use-case for these methods is adding markers in an event log whenever a command scheduling event takes place, as demonstrated in the following code from the HatchbotInlined example project ( Java , C++ ): Java 73 // Set the scheduler to log Shuffleboard events for command initialize, interrupt, finish 74 CommandScheduler . getInstance () 75 . onCommandInitialize ( 76 command -> 77 Shuffleboard . addEventMarker ( 78 \"Command initialized\" , command . getName (), EventImportance . kNormal )); 79 CommandScheduler . getInstance () 80 . onCommandInterrupt ( 81 command -> 82 Shuffleboard . addEventMarker ( 83 \"Command interrupted\" , command . getName (), EventImportance . kNormal )); 84 CommandScheduler . getInstance () 85 . onCommandFinish ( 86 command -> 87 Shuffleboard . addEventMarker ( 88 \"Command finished\" , command . getName (), EventImportance . kNormal )); C++ (Source) 23 // Log Shuffleboard events for command initialize, execute, finish, interrupt 24 frc2 :: CommandScheduler :: GetInstance (). OnCommandInitialize ( 25 []( const frc2 :: Command & command ) { 26 frc :: Shuffleboard :: AddEventMarker ( 27 \"Command initialized\" , command . GetName (), 28 frc :: ShuffleboardEventImportance :: kNormal ); 29 }); 30 frc2 :: CommandScheduler :: GetInstance (). OnCommandExecute ( 31 []( const frc2 :: Command & command ) { 32 frc :: Shuffleboard :: AddEventMarker ( 33 \"Command executed\" , command . GetName (), 34 frc :: ShuffleboardEventImportance :: kNormal ); 35 }); 36 frc2 :: CommandScheduler :: GetInstance (). OnCommandFinish ( 37 []( const frc2 :: Command & command ) { 38 frc :: Shuffleboard :: AddEventMarker ( 39 \"Command finished\" , command . GetName (), 40 frc :: ShuffleboardEventImportance :: kNormal ); 41 }); 42 frc2 :: CommandScheduler :: GetInstance (). OnCommandInterrupt ( 43 []( const frc2 :: Command & command ) { 44 frc :: Shuffleboard :: AddEventMarker ( 45 \"Command interrupted\" , command . GetName (), 46 frc :: ShuffleboardEventImportance :: kNormal ); 47 });",
"content_preview": "The Command Scheduler The CommandScheduler ( Java , C++ ) is the class responsible for actually running commands. Each iteration (ordinarily once per 20ms), the scheduler polls all registered buttons, schedules commands for execution accordingly, runs the command bodies of all scheduled commands,..."
},
- {
- "url": "https://docs.wpilib.org/en/stable/docs/software/commandbased/command-scheduler.html?present",
- "title": "The Command Scheduler",
- "section": "Command-Based Programming",
- "language": "All",
- "content": "The Command Scheduler The CommandScheduler ( Java , C++ ) is the class responsible for actually running commands. Each iteration (ordinarily once per 20ms), the scheduler polls all registered buttons, schedules commands for execution accordingly, runs the command bodies of all scheduled commands, and ends those commands that have finished or are interrupted. The CommandScheduler also runs the periodic() method of each registered Subsystem . Using the Command Scheduler The CommandScheduler is a singleton , meaning that it is a globally-accessible class with only one instance. Accordingly, in order to access the scheduler, users must call the CommandScheduler.getInstance() command. For the most part, users do not have to call scheduler methods directly - almost all important scheduler methods have convenience wrappers elsewhere (e.g. in the Command and Subsystem classes). However, there is one exception: users must call CommandScheduler.getInstance().run() from the robotPeriodic() method of their Robot class. If this is not done, the scheduler will never run, and the command framework will not work. The provided command-based project template has this call already included. The schedule() Method To schedule a command, users call the schedule() method ( Java , C++ ). This method takes a command, and attempts to add it to list of currently-running commands, pending whether it is already running or whether its requirements are available. If it is added, its initialize() method is called. This method walks through the following steps: Verifies that the command isn’t in a composition. No-op if scheduler is disabled, command is already scheduled, or robot is disabled and command doesn’t runsWhenDisabled . If requirements are in use: If all conflicting commands are interruptible, cancel them. If not, don’t schedule the new command. Call initialize() . Java 202 private void schedule ( Command command ) { 203 if ( command == null ) { 204 DriverStation . reportWarning ( \"Tried to schedule a null command\" , true ); 205 return ; 206 } 207 if ( m_inRunLoop ) { 208 m_toSchedule . add ( command ); 209 return ; 210 } 211 212 requireNotComposed ( command ); 213 214 // Do nothing if the scheduler is disabled, the robot is disabled and the command doesn't 215 // run when disabled, or the command is already scheduled. 216 if ( m_disabled 217 || isScheduled ( command ) 218 || RobotState . isDisabled () && ! command . runsWhenDisabled ()) { 219 return ; 220 } 221 222 Set < Subsystem > requirements = command . getRequirements (); 223 224 // Schedule the command if the requirements are not currently in-use. 225 if ( Collections . disjoint ( m_requirements . keySet (), requirements )) { 226 initCommand ( command , requirements ); 227 } else { 228 // Else check if the requirements that are in use have all have interruptible commands, 229 // and if so, interrupt those commands and schedule the new command. 230 for ( Subsystem requirement : requirements ) { 231 Command requiring = requiring ( requirement ); 232 if ( requiring != null 233 && requiring . getInterruptionBehavior () == InterruptionBehavior . kCancelIncoming ) { 234 return ; 235 } 236 } 237 for ( Subsystem requirement : requirements ) { 238 Command requiring = requiring ( requirement ); 239 if ( requiring != null ) { 240 cancel ( requiring ); 241 } 242 } 243 initCommand ( command , requirements ); 244 } 245 } 181 private void initCommand ( Command command , Set < Subsystem > requirements ) { 182 m_scheduledCommands . add ( command ); 183 for ( Subsystem requirement : requirements ) { 184 m_requirements . put ( requirement , command ); 185 } 186 command . initialize (); 187 for ( Consumer < Command > action : m_initActions ) { 188 action . accept ( command ); 189 } 190 191 m_watchdog . addEpoch ( command . getName () + \".initialize()\" ); C++ (Source) 114 void CommandScheduler::Schedule ( Command * command ) { 115 if ( m_impl -> inRunLoop ) { 116 m_impl -> toSchedule . emplace_back ( command ); 117 return ; 118 } 119 120 RequireUngrouped ( command ); 121 122 if ( m_impl -> disabled || m_impl -> scheduledCommands . contains ( command ) || 123 ( frc :: RobotState :: IsDisabled () && ! command -> RunsWhenDisabled ())) { 124 return ; 125 } 126 127 const auto & requirements = command -> GetRequirements (); 128 129 wpi :: SmallVector < Command * , 8 > intersection ; 130 131 bool isDisjoint = true ; 132 bool allInterruptible = true ; 133 for ( auto && i1 : m_impl -> requirements ) { 134 if ( requirements . find ( i1 . first ) != requirements . end ()) { 135 isDisjoint = false ; 136 allInterruptible &= ( i1 . second -> GetInterruptionBehavior () == 137 Command :: InterruptionBehavior :: kCancelSelf ); 138 intersection . emplace_back ( i1 . second ); 139 } 140 } 141 142 if ( isDisjoint || allInterruptible ) { 143 if ( allInterruptible ) { 144 for ( auto && cmdToCancel : intersection ) { 145 Cancel ( cmdToCancel ); 146 } 147 } 148 m_impl -> scheduledCommands . insert ( command ); 149 for ( auto && requirement : requirements ) { 150 m_impl -> requirements [ requirement ] = command ; 151 } 152 command -> Initialize (); 153 for ( auto && action : m_impl -> initActions ) { 154 action ( * command ); 155 } 156 m_watchdog . AddEpoch ( command -> GetName () + \".Initialize()\" ); 157 } 158 } The Scheduler Run Sequence Note The initialize() method of each Command is called when the command is scheduled, which is not necessarily when the scheduler runs (unless that command is bound to a button). What does a single iteration of the scheduler’s run() method ( Java , C++ ) actually do? The following section walks through the logic of a scheduler iteration. For the full implementation, see the source code ( Java , C++ ). Step 1: Run Subsystem Periodic Methods First, the scheduler runs the periodic() method of each registered Subsystem . In simulation, each subsystem’s simulationPeriodic() method is called as well. Java 278 // Run the periodic method of all registered subsystems. 279 for ( Subsystem subsystem : m_subsystems . keySet ()) { 280 subsystem . periodic (); 281 if ( RobotBase . isSimulation ()) { 282 subsystem . simulationPeriodic (); 283 } 284 m_watchdog . addEpoch ( subsystem . getClass (). getSimpleName () + \".periodic()\" ); 285 } C++ (Source) 183 // Run the periodic method of all registered subsystems. 184 for ( auto && subsystem : m_impl -> subsystems ) { 185 subsystem . getFirst () -> Periodic (); 186 if constexpr ( frc :: RobotBase :: IsSimulation ()) { 187 subsystem . getFirst () -> SimulationPeriodic (); 188 } 189 m_watchdog . AddEpoch ( \"Subsystem Periodic()\" ); 190 } Step 2: Poll Command Scheduling Triggers Note For more information on how trigger bindings work, see Binding Commands to Triggers Secondly, the scheduler polls the state of all registered triggers to see if any new commands that have been bound to those triggers should be scheduled. If the conditions for scheduling a bound command are met, the command is scheduled and its initialize() method is run. Note If a newly-scheduled command has requirement conflicts with a currently-running command, the currently-running command is interrupted first. The end(true) method of the interrupted command is called before the initialize() method of the new command. Java 290 // Poll buttons for new commands to add. 291 loopCache . poll (); 292 m_watchdog . addEpoch ( \"buttons.run()\" ); C++ (Source) 195 // Poll buttons for new commands to add. 196 loopCache -> Poll (); 197 m_watchdog . AddEpoch ( \"buttons.Run()\" ); Step 3: Run/Finish Scheduled Commands Thirdly, the scheduler calls the execute() method of each currently-scheduled command, and then checks whether the command has finished by calling the isFinished() method. If the command has finished, the end() method is also called, and the command is de-scheduled and its required subsystems are freed. Note that this sequence of calls is done in order for each command - thus, one command may have its end() method called before another has its execute() method called. Commands are handled in the order they were scheduled. Java 295 // Run scheduled commands, remove finished commands. 296 for ( Iterator < Command > iterator = m_scheduledCommands . iterator (); iterator . hasNext (); ) { 297 Command command = iterator . next (); 298 299 if ( ! command . runsWhenDisabled () && RobotState . isDisabled ()) { 300 command . end ( true ); 301 for ( Consumer < Command > action : m_interruptActions ) { 302 action . accept ( command ); 303 } 304 m_requirements . keySet (). removeAll ( command . getRequirements ()); 305 iterator . remove (); 306 m_watchdog . addEpoch ( command . getName () + \".end(true)\" ); 307 continue ; 308 } 309 310 command . execute (); 311 for ( Consumer < Command > action : m_executeActions ) { 312 action . accept ( command ); 313 } 314 m_watchdog . addEpoch ( command . getName () + \".execute()\" ); 315 if ( command . isFinished ()) { 316 command . end ( false ); 317 for ( Consumer < Command > action : m_finishActions ) { 318 action . accept ( command ); 319 } 320 iterator . remove (); 321 322 m_requirements . keySet (). removeAll ( command . getRequirements ()); 323 m_watchdog . addEpoch ( command . getName () + \".end(false)\" ); 324 } 325 } C++ (Source) 201 for ( Command * command : m_impl -> scheduledCommands ) { 202 if ( ! command -> RunsWhenDisabled () && frc :: RobotState :: IsDisabled ()) { 203 Cancel ( command ); 204 continue ; 205 } 206 207 command -> Execute (); 208 for ( auto && action : m_impl -> executeActions ) { 209 action ( * command ); 210 } 211 m_watchdog . AddEpoch ( command -> GetName () + \".Execute()\" ); 212 213 if ( command -> IsFinished ()) { 214 command -> End ( false ); 215 for ( auto && action : m_impl -> finishActions ) { 216 action ( * command ); 217 } 218 219 for ( auto && requirement : command -> GetRequirements ()) { 220 m_impl -> requirements . erase ( requirement ); 221 } 222 223 m_impl -> scheduledCommands . erase ( command ); 224 m_watchdog . AddEpoch ( command -> GetName () + \".End(false)\" ); 225 } 226 } Step 4: Schedule Default Commands Finally, any registered Subsystem has its default command scheduled (if it has one). Note that the initialize() method of the default command will be called at this time. Java 340 // Add default commands for un-required registered subsystems. 341 for ( Map . Entry < Subsystem , Command > subsystemCommand : m_subsystems . entrySet ()) { 342 if ( ! m_requirements . containsKey ( subsystemCommand . getKey ()) 343 && subsystemCommand . getValue () != null ) { 344 schedule ( subsystemCommand . getValue ()); 345 } 346 } C++ (Source) 240 // Add default commands for un-required registered subsystems. 241 for ( auto && subsystem : m_impl -> subsystems ) { 242 auto s = m_impl -> requirements . find ( subsystem . getFirst ()); 243 if ( s == m_impl -> requirements . end () && subsystem . getSecond ()) { 244 Schedule ({ subsystem . getSecond (). get ()}); 245 } 246 } Disabling the Scheduler The scheduler can be disabled by calling CommandScheduler.getInstance().disable() . When disabled, the scheduler’s schedule() and run() commands will not do anything. The scheduler may be re-enabled by calling CommandScheduler.getInstance().enable() . Command Event Methods Occasionally, it is desirable to have the scheduler execute a custom action whenever a certain command event (initialization, execution, or ending) occurs. This can be done with the following methods: onCommandInitialize ( Java , C++ ) runs a specified action whenever a command is initialized. onCommandExecute ( Java , C++ ) runs a specified action whenever a command is executed. onCommandFinish ( Java , C++ ) runs a specified action whenever a command finishes normally (i.e. the isFinished() method returned true). onCommandInterrupt ( Java , C++ ) runs a specified action whenever a command is interrupted (i.e. by being explicitly canceled or by another command that shares one of its requirements). A typical use-case for these methods is adding markers in an event log whenever a command scheduling event takes place, as demonstrated in the following code from the HatchbotInlined example project ( Java , C++ ): Java 73 // Set the scheduler to log Shuffleboard events for command initialize, interrupt, finish 74 CommandScheduler . getInstance () 75 . onCommandInitialize ( 76 command -> 77 Shuffleboard . addEventMarker ( 78 \"Command initialized\" , command . getName (), EventImportance . kNormal )); 79 CommandScheduler . getInstance () 80 . onCommandInterrupt ( 81 command -> 82 Shuffleboard . addEventMarker ( 83 \"Command interrupted\" , command . getName (), EventImportance . kNormal )); 84 CommandScheduler . getInstance () 85 . onCommandFinish ( 86 command -> 87 Shuffleboard . addEventMarker ( 88 \"Command finished\" , command . getName (), EventImportance . kNormal )); C++ (Source) 23 // Log Shuffleboard events for command initialize, execute, finish, interrupt 24 frc2 :: CommandScheduler :: GetInstance (). OnCommandInitialize ( 25 []( const frc2 :: Command & command ) { 26 frc :: Shuffleboard :: AddEventMarker ( 27 \"Command initialized\" , command . GetName (), 28 frc :: ShuffleboardEventImportance :: kNormal ); 29 }); 30 frc2 :: CommandScheduler :: GetInstance (). OnCommandExecute ( 31 []( const frc2 :: Command & command ) { 32 frc :: Shuffleboard :: AddEventMarker ( 33 \"Command executed\" , command . GetName (), 34 frc :: ShuffleboardEventImportance :: kNormal ); 35 }); 36 frc2 :: CommandScheduler :: GetInstance (). OnCommandFinish ( 37 []( const frc2 :: Command & command ) { 38 frc :: Shuffleboard :: AddEventMarker ( 39 \"Command finished\" , command . GetName (), 40 frc :: ShuffleboardEventImportance :: kNormal ); 41 }); 42 frc2 :: CommandScheduler :: GetInstance (). OnCommandInterrupt ( 43 []( const frc2 :: Command & command ) { 44 frc :: Shuffleboard :: AddEventMarker ( 45 \"Command interrupted\" , command . GetName (), 46 frc :: ShuffleboardEventImportance :: kNormal ); 47 });",
- "content_preview": "The Command Scheduler The CommandScheduler ( Java , C++ ) is the class responsible for actually running commands. Each iteration (ordinarily once per 20ms), the scheduler polls all registered buttons, schedules commands for execution accordingly, runs the command bodies of all scheduled commands,..."
- },
{
"url": "https://docs.wpilib.org/en/stable/docs/software/commandbased/binding-commands-to-triggers.html",
"title": "Binding Commands to Triggers",
@@ -108,100 +92,92 @@
"content_preview": "Binding Commands to Triggers Apart from autonomous commands, which are scheduled at the start of the autonomous period, and default commands, which are automatically scheduled whenever their subsystem is not currently in-use, the most common way to run a command is by binding it to a triggering..."
},
{
- "url": "https://docs.wpilib.org/en/stable/docs/software/commandbased/commands.html",
- "title": "Commands",
+ "url": "https://docs.wpilib.org/en/stable/docs/software/commandbased/command-scheduler.html?present",
+ "title": "The Command Scheduler",
"section": "Command-Based Programming",
"language": "All",
- "content": "Commands Commands represent actions the robot can take. Commands run when scheduled, until they are interrupted or their end condition is met. Commands are represented in the command-based library by the Command class ( Java , C++ ) or the Command class in commands2 library ( Python ). The Structure of a Command Commands specify what the command will do in each of its possible states. This is done by overriding the initialize() , execute() , and end() methods. Additionally, a command must be able to tell the scheduler when (if ever) it has finished execution - this is done by overriding the isFinished() method. All of these methods are defaulted to reduce clutter in user code: initialize() , execute() , and end() are defaulted to simply do nothing, while isFinished() is defaulted to return false (resulting in a command that never finishes naturally, and will run until interrupted). Initialization The initialize() method ( Java , C++ , Python ) marks the command start, and is called exactly once per time a command is scheduled. The initialize() method should be used to place the command in a known starting state for execution. Command objects may be reused and scheduled multiple times, so any state or resources needed for the command’s functionality should be initialized or opened in initialize (which will be called at the start of each use) rather than the constructor (which is invoked only once on object allocation). It is also useful for performing tasks that only need to be performed once per time scheduled, such as setting motors to run at a constant speed or setting the state of a solenoid actuator. Execution The execute() method ( Java , C++ , Python ) is called repeatedly while the command is scheduled; this is when the scheduler’s run() method is called (this is generally done in the main robot periodic method, which runs every 20ms by default). The execute block should be used for any task that needs to be done continually while the command is scheduled, such as updating motor outputs to match joystick inputs, or using the output of a control loop. Ending The end(bool interrupted) method ( Java , C++ , Python ) is called once when the command ends, whether it finishes normally (i.e. isFinished() returned true) or it was interrupted (either by another command or by being explicitly canceled). The method argument specifies the manner in which the command ended; users can use this to differentiate the behavior of their command end accordingly. The end block should be used to “wrap up” command state in a neat way, such as setting motors back to zero or reverting a solenoid actuator to a “default” state. Any state or resources initialized in initialize() should be closed in end() . Specifying end conditions The isFinished() method ( Java , C++ , Python ) is called repeatedly while the command is scheduled, whenever the scheduler’s run() method is called. As soon as it returns true, the command’s end() method is called and it ends. The isFinished() method is called after the execute() method, so the command will execute once on the same iteration that it ends. Command Properties In addition to the four lifecycle methods described above, each Command also has three properties, defined by getter methods that should always return the same value with no side affects. getRequirements Each command should declare any subsystems it controls as requirements. This backs the scheduler’s resource management mechanism, ensuring that no more than one command requires a given subsystem at the same time. This prevents situations such as two different pieces of code attempting to set the same motor controller to different output values. Declaring requirements is done by overriding the getRequirements() method in the relevant command class, by calling addRequirements() , or by using the requirements vararg (Java) / Requirements struct (C++) parameter / requirements argument (Python) at the end of the parameter list of most command constructors and factories in the library: JAVA Commands . run ( intake :: activate , intake ); C++ frc2 :: cmd :: Run ([ & intake ] { intake . Activate (); }, { & intake }); PYTHON commands2 . cmd . run ( intake . activate , intake ) As a rule, command compositions require all subsystems their components require. runsWhenDisabled The runsWhenDisabled() method ( Java , C++ , Python ) returns a boolean / bool specifying whether the command may run when the robot is disabled. With the default of returning false , the command will be canceled when the robot is disabled and attempts to schedule it will do nothing. Returning true will allow the command to run and be scheduled when the robot is disabled. Important When the robot is disabled, PWM outputs are disabled and CAN motor controllers may not apply voltage, regardless of runsWhenDisabled ! This property can be set either by overriding the runsWhenDisabled() method in the relevant command class, or by using the ignoringDisable decorator ( Java , C++ , Python ): JAVA Command mayRunDuringDisabled = Commands . run (() -> updateTelemetry ()). ignoringDisable ( true ); C++ frc2 :: CommandPtr mayRunDuringDisabled = frc2 :: cmd :: Run ([] { UpdateTelemetry (); }). IgnoringDisable ( true ); PYTHON may_run_during_disabled = commands2 . cmd . run ( lambda : update_telemetry ()) . ignoring_disable ( True ) As a rule, command compositions may run when disabled if all their component commands set runsWhenDisabled as true . getInterruptionBehavior The getInterruptionBehavior() method ( Java , C++ , Python ) defines what happens if another command sharing a requirement is scheduled while this one is running. In the default behavior, kCancelSelf , the current command will be canceled and the incoming command will be scheduled successfully. If kCancelIncoming is returned, the incoming command’s scheduling will be aborted and this command will continue running. Note that getInterruptionBehavior only affects resolution of requirement conflicts: all commands can be canceled, regardless of getInterruptionBehavior . Note This was previously controlled by the interruptible parameter passed when scheduling a command, and is now a property of the command object. This property can be set either by overriding the getInterruptionBehavior method in the relevant command class, or by using the withInterruptBehavior() decorator ( Java , C++ , Python ) JAVA Command noninteruptible = Commands . run ( intake :: activate , intake ). withInterruptBehavior ( Command . InterruptBehavior . kCancelIncoming ); C++ frc2 :: CommandPtr noninterruptible = frc2 :: cmd :: Run ([ & intake ] { intake . Activate (); }, { & intake }). WithInterruptBehavior ( Command :: InterruptBehavior :: kCancelIncoming ); PYTHON non_interruptible = commands2 . cmd . run ( intake . activate , intake ) . with_interrupt_behavior ( Command . InterruptBehavior . kCancelIncoming ) As a rule, command compositions are kCancelIncoming if all their components are kCancelIncoming as well. Included Command Types The command-based library includes many pre-written command types. Through the use of lambdas , these commands can cover almost all use cases and teams should rarely need to write custom command classes. Many of these commands are provided via static factory functions in the Commands utility class (Java), in the frc2::cmd namespace defined in the Commands.h header (C++), or in the commands2.cmd namespace (Python). In Java and C++, classes inheriting from Subsystem also have instance methods that implicitly require this . Running Actions The most basic commands are actions the robot takes: setting voltage to a motor, changing a solenoid’s direction, etc. For these commands, which typically consist of a method call or two, the command-based library offers several factories to be construct commands inline with one or more lambdas to be executed. The runOnce factory, backed by the InstantCommand ( Java , C++ , Python ) class, creates a command that calls a lambda once, and then finishes. Java 25 /** Grabs the hatch. */ 26 public Command grabHatchCommand () { 27 // implicitly require `this` 28 return this . runOnce (() -> m_hatchSolenoid . set ( kForward )); 29 } 30 31 /** Releases the hatch. */ 32 public Command releaseHatchCommand () { 33 // implicitly require `this` 34 return this . runOnce (() -> m_hatchSolenoid . set ( kReverse )); 35 } C++ (Header) 20 /** 21 * Grabs the hatch. 22 */ 23 frc2 :: CommandPtr GrabHatchCommand (); 24 25 /** 26 * Releases the hatch. 27 */ 28 frc2 :: CommandPtr ReleaseHatchCommand (); C++ (Source) 15 frc2 :: CommandPtr HatchSubsystem::GrabHatchCommand () { 16 // implicitly require `this` 17 return this -> RunOnce ( 18 [ this ] { m_hatchSolenoid . Set ( frc :: DoubleSolenoid :: kForward ); }); 19 } 20 21 frc2 :: CommandPtr HatchSubsystem::ReleaseHatchCommand () { 22 // implicitly require `this` 23 return this -> RunOnce ( 24 [ this ] { m_hatchSolenoid . Set ( frc :: DoubleSolenoid :: kReverse ); }); 25 } Python 24 def grabHatch ( self ) -> commands2 . Command : 25 \"\"\"Grabs the hatch\"\"\" 26 return commands2 . cmd . runOnce ( 27 lambda : self . hatchSolenoid . set ( wpilib . DoubleSolenoid . Value . kForward ), self 28 ) 29 30 def releaseHatch ( self ) -> commands2 . Command : 31 \"\"\"Releases the hatch\"\"\" 32 return commands2 . cmd . runOnce ( 33 lambda : self . hatchSolenoid . set ( wpilib . DoubleSolenoid . Value . kReverse ), self 34 ) The run factory, backed by the RunCommand ( Java , C++ , Python ) class, creates a command that calls a lambda repeatedly, until interrupted. JAVA // A split-stick arcade command, with forward/backward controlled by the left // hand, and turning controlled by the right. new RunCommand (() -> m_robotDrive . arcadeDrive ( - driverController . getLeftY (), driverController . getRightX ()), m_robotDrive ) C++ // A split-stick arcade command, with forward/backward controlled by the left // hand, and turning controlled by the right. frc2 :: RunCommand ( [ this ] { m_drive . ArcadeDrive ( - m_driverController . GetLeftY (), m_driverController . GetRightX ()); }, { & m_drive }) PYTHON # A split-stick arcade command, with forward/backward controlled by the left # hand, and turning controlled by the right. commands2 . cmd . run ( lambda : robot_drive . arcade_drive ( - driver_controller . get_left_y (), driver_controller . get_right_x ()), robot_drive ) The startEnd factory, backed by the StartEndCommand ( Java , C++ , Python ) class, calls one lambda when scheduled, and then a second lambda when interrupted. JAVA Commands . startEnd ( // Start a flywheel spinning at 50% power () -> m_shooter . shooterSpeed ( 0.5 ), // Stop the flywheel at the end of the command () -> m_shooter . shooterSpeed ( 0.0 ), // Requires the shooter subsystem m_shooter ) C++ frc2 :: cmd :: StartEnd ( // Start a flywheel spinning at 50% power [ this ] { m_shooter . shooterSpeed ( 0.5 ); }, // Stop the flywheel at the end of the command [ this ] { m_shooter . shooterSpeed ( 0.0 ); }, // Requires the shooter subsystem { & m_shooter } ) PYTHON commands2 . cmd . start_end ( # Start a flywheel spinning at 50% power lambda : shooter . shooter_speed ( 0.5 ), # Stop the flywheel at the end of the command lambda : shooter . shooter_speed ( 0.0 ), # Requires the shooter subsystem shooter ) FunctionalCommand ( Java , C++ , Python ) accepts four lambdas that constitute the four command lifecycle methods: a Runnable / std::function/Callable for each of initialize() and execute() , a BooleanConsumer / std::function/Callable[bool,[]] for end() , and a BooleanSupplier / std::function/Callable[[],bool] for isFinished() . JAVA new FunctionalCommand ( // Reset encoders on command start m_robotDrive :: resetEncoders , // Start driving forward at the start of the command () -> m_robotDrive . arcadeDrive ( kAutoDriveSpeed , 0 ), // Stop driving at the end of the command interrupted -> m_robotDrive . arcadeDrive ( 0 , 0 ), // End the command when the robot's driven distance exceeds the desired value () -> m_robotDrive . getAverageEncoderDistance () >= kAutoDriveDistanceInches , // Require the drive subsystem m_robotDrive ) C++ frc2 :: FunctionalCommand ( // Reset encoders on command start [ this ] { m_drive . ResetEncoders (); }, // Start driving forward at the start of the command [ this ] { m_drive . ArcadeDrive ( ac :: kAutoDriveSpeed , 0 ); }, // Stop driving at the end of the command [ this ] ( bool interrupted ) { m_drive . ArcadeDrive ( 0 , 0 ); }, // End the command when the robot's driven distance exceeds the desired value [ this ] { return m_drive . GetAverageEncoderDistance () >= kAutoDriveDistanceInches ; }, // Requires the drive subsystem { & m_drive } ) PYTHON commands2 . cmd . functional_command ( # Reset encoders on command start lambda : robot_drive . reset_encoders (), # Start driving forward at the start of the command lambda : robot_drive . arcade_drive ( ac . kAutoDriveSpeed , 0 ), # Stop driving at the end of the command lambda interrupted : robot_drive . arcade_drive ( 0 , 0 ), # End the command when the robot's driven distance exceeds the desired value lambda : robot_drive . get_average_encoder_distance () >= ac . kAutoDriveDistanceInches , # Require the drive subsystem robot_drive ) To print a string and ending immediately, the library offers the Commands.print(String) / frc2::cmd::Print(std::string_view) / commands2.cmd.print(String) factory, backed by the PrintCommand ( Java , C++ , Python ) subclass of InstantCommand . Waiting Waiting for a certain condition to happen or adding a delay can be useful to synchronize between different commands in a command composition or between other robot actions. To wait and end after a specified period of time elapses, the library offers the Commands.waitSeconds(double) / frc2::cmd::Wait(units::second_t) / commands2.cmd.wait(float) factory, backed by the WaitCommand ( Java , C++ , Python ) class. JAVA // Ends 5 seconds after being scheduled new WaitCommand ( 5.0 ) C++ // Ends 5 seconds after being scheduled frc2 :: WaitCommand ( 5.0 _s ) PYTHON # Ends 5 seconds after being scheduled commands2 . cmd . wait ( 5.0 ) To wait until a certain condition becomes true , the library offers the Commands.waitUntil(BooleanSupplier) / frc2::cmd::WaitUntil(std::function) factory, backed by the WaitUntilCommand class ( Java , C++ , Python ). JAVA // Ends after m_limitSwitch.get() returns true new WaitUntilCommand ( m_limitSwitch :: get ) C++ // Ends after m_limitSwitch.Get() returns true frc2 :: WaitUntilCommand ([ & m_limitSwitch ] { return m_limitSwitch . Get (); }) PYTHON # Ends after limit_switch.get() returns True commands2 . cmd . wait_until ( limit_switch . get ) Control Algorithm Commands There are commands for various control setups: TrapezoidProfile tracks a trapezoid motion profile. For more info, see Motion Profiling in Command-based . MecanumControllerCommand ( Java , C++ ) is useful for controlling mecanum drivetrains. See API docs and the MecanumControllerCommand ( Java , C++ ) example project for more info. SwerveControllerCommand ( Java , C++ ) is useful for controlling swerve drivetrains. See API docs and the SwerveControllerCommand ( Java , C++ ) example project for more info. RamseteCommand ( Java , C++ ) is useful for path following with differential drivetrains (“tank drive”). See API docs and the Trajectory Tutorial for more info. Custom Command Classes Users may also write custom command classes. As this is significantly more verbose, it’s recommended to use the more concise factories mentioned above. Note In the C++ API, a CRTP is used to allow certain Command methods to work with the object ownership model. Users should always extend the CommandHelper class when defining their own command classes, as is shown below. To write a custom command class, subclass the abstract Command class ( Java ) or CommandHelper ( C++ ), as seen in the command-based template ( Java , C++ ): JAVA 7 import edu.wpi.first.wpilibj.templates.commandbased.subsystems.ExampleSubsystem ; 8 import edu.wpi.first.wpilibj2.command.Command ; 9 10 /** An example command that uses an example subsystem. */ 11 public class ExampleCommand extends Command { 12 @SuppressWarnings ( \"PMD.UnusedPrivateField\" ) 13 private final ExampleSubsystem m_subsystem ; 14 15 /** 16 * Creates a new ExampleCommand. 17 * 18 * @param subsystem The subsystem used by this command. 19 */ 20 public ExampleCommand ( ExampleSubsystem subsystem ) { 21 m_subsystem = subsystem ; 22 // Use addRequirements() here to declare subsystem dependencies. 23 addRequirements ( subsystem ); 24 } C++ 5 #pragma once 6 7 #include 8 #include 9 10 #include \"subsystems/ExampleSubsystem.h\" 11 12 /** 13 * An example command that uses an example subsystem. 14 * 15 * Note that this extends CommandHelper, rather extending Command 16 * directly; this is crucially important, or else the decorator functions in 17 * Command will *not* work! 18 */ 19 class ExampleCommand 20 : public frc2 :: CommandHelper < frc2 :: Command , ExampleCommand > { 21 public : 22 /** 23 * Creates a new ExampleCommand. 24 * 25 * @param subsystem The subsystem used by this command. 26 */ 27 explicit ExampleCommand ( ExampleSubsystem * subsystem ); 28 29 private : 30 ExampleSubsystem * m_subsystem ; 31 }; Simple Command Example What might a functional command look like in practice? As before, below is a simple command from the HatchBot example project ( Java , C++ ) that uses the HatchSubsystem : Java 5 package edu.wpi.first.wpilibj.examples.hatchbottraditional.commands ; 6 7 import edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems.HatchSubsystem ; 8 import edu.wpi.first.wpilibj2.command.Command ; 9 10 /** 11 * A simple command that grabs a hatch with the {@link HatchSubsystem}. Written explicitly for 12 * pedagogical purposes. Actual code should inline a command this simple with {@link 13 * edu.wpi.first.wpilibj2.command.InstantCommand}. 14 */ 15 public class GrabHatch extends Command { 16 // The subsystem the command runs on 17 private final HatchSubsystem m_hatchSubsystem ; 18 19 public GrabHatch ( HatchSubsystem subsystem ) { 20 m_hatchSubsystem = subsystem ; 21 addRequirements ( m_hatchSubsystem ); 22 } 23 24 @Override 25 public void initialize () { 26 m_hatchSubsystem . grabHatch (); 27 } 28 29 @Override 30 public boolean isFinished () { 31 return true ; 32 } 33 } C++ (Header) 5 #pragma once 6 7 #include 8 #include 9 10 #include \"subsystems/HatchSubsystem.h\" 11 12 /** 13 * A simple command that grabs a hatch with the HatchSubsystem. Written 14 * explicitly for pedagogical purposes. Actual code should inline a command 15 * this simple with InstantCommand. 16 * 17 * @see InstantCommand 18 */ 19 class GrabHatch : public frc2 :: CommandHelper < frc2 :: Command , GrabHatch > { 20 public : 21 explicit GrabHatch ( HatchSubsystem * subsystem ); 22 23 void Initialize () override ; 24 25 bool IsFinished () override ; 26 27 private : 28 HatchSubsystem * m_hatch ; 29 }; C++ (Source) 5 #include \"commands/GrabHatch.h\" 6 7 GrabHatch :: GrabHatch ( HatchSubsystem * subsystem ) : m_hatch ( subsystem ) { 8 AddRequirements ( subsystem ); 9 } 10 11 void GrabHatch :: Initialize () { 12 m_hatch -> GrabHatch (); 13 } 14 15 bool GrabHatch :: IsFinished () { 16 return true ; 17 } Python 7 import commands2 8 from subsystems.hatchsubsystem import HatchSubsystem 9 10 11 class GrabHatch ( commands2 . Command ): 12 def __init__ ( self , hatch : HatchSubsystem ) -> None : 13 super () . __init__ () 14 self . hatch = hatch 15 self . addRequirements ( hatch ) 16 17 def initialize ( self ) -> None : 18 self . hatch . grabHatch () 19 20 def isFinished ( self ) -> bool : 21 return True Notice that the hatch subsystem used by the command is passed into the command through the command’s constructor. This is a pattern called dependency injection , and allows users to avoid declaring their subsystems as global variables. This is widely accepted as a best-practice - the reasoning behind this is discussed in a later section . Notice also that the above command calls the subsystem method once from initialize, and then immediately ends (as isFinished() simply returns true). This is typical for commands that toggle the states of subsystems, and as such it would be more succinct to write this command using the factories described above. What about a more complicated case? Below is a drive command, from the same example project: Java 5 package edu.wpi.first.wpilibj.examples.hatchbottraditional.commands ; 6 7 import edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems.DriveSubsystem ; 8 import edu.wpi.first.wpilibj2.command.Command ; 9 import java.util.function.DoubleSupplier ; 10 11 /** 12 * A command to drive the robot with joystick input (passed in as {@link DoubleSupplier}s). Written 13 * explicitly for pedagogical purposes - actual code should inline a command this simple with {@link 14 * edu.wpi.first.wpilibj2.command.RunCommand}. 15 */ 16 public class DefaultDrive extends Command { 17 private final DriveSubsystem m_drive ; 18 private final DoubleSupplier m_forward ; 19 private final DoubleSupplier m_rotation ; 20 21 /** 22 * Creates a new DefaultDrive. 23 * 24 * @param subsystem The drive subsystem this command wil run on. 25 * @param forward The control input for driving forwards/backwards 26 * @param rotation The control input for turning 27 */ 28 public DefaultDrive ( DriveSubsystem subsystem , DoubleSupplier forward , DoubleSupplier rotation ) { 29 m_drive = subsystem ; 30 m_forward = forward ; 31 m_rotation = rotation ; 32 addRequirements ( m_drive ); 33 } 34 35 @Override 36 public void execute () { 37 m_drive . arcadeDrive ( m_forward . getAsDouble (), m_rotation . getAsDouble ()); 38 } 39 } C++ (Header) 5 #pragma once 6 7 #include 8 9 #include 10 #include 11 12 #include \"subsystems/DriveSubsystem.h\" 13 14 /** 15 * A command to drive the robot with joystick input passed in through lambdas. 16 * Written explicitly for pedagogical purposes - actual code should inline a 17 * command this simple with RunCommand. 18 * 19 * @see RunCommand 20 */ 21 class DefaultDrive : public frc2 :: CommandHelper < frc2 :: Command , DefaultDrive > { 22 public : 23 /** 24 * Creates a new DefaultDrive. 25 * 26 * @param subsystem The drive subsystem this command wil run on. 27 * @param forward The control input for driving forwards/backwards 28 * @param rotation The control input for turning 29 */ 30 DefaultDrive ( DriveSubsystem * subsystem , std :: function < double () > forward , 31 std :: function < double () > rotation ); 32 33 void Execute () override ; 34 35 private : 36 DriveSubsystem * m_drive ; 37 std :: function < double () > m_forward ; 38 std :: function < double () > m_rotation ; 39 }; C++ (Source) 5 #include \"commands/DefaultDrive.h\" 6 7 #include 8 9 DefaultDrive :: DefaultDrive ( DriveSubsystem * subsystem , 10 std :: function < double () > forward , 11 std :: function < double () > rotation ) 12 : m_drive { subsystem }, 13 m_forward { std :: move ( forward )}, 14 m_rotation { std :: move ( rotation )} { 15 AddRequirements ( subsystem ); 16 } 17 18 void DefaultDrive :: Execute () { 19 m_drive -> ArcadeDrive ( m_forward (), m_rotation ()); 20 } Python 7 import typing 8 import commands2 9 from subsystems.drivesubsystem import DriveSubsystem 10 11 12 class DefaultDrive ( commands2 . Command ): 13 def __init__ ( 14 self , 15 drive : DriveSubsystem , 16 forward : typing . Callable [[], float ], 17 rotation : typing . Callable [[], float ], 18 ) -> None : 19 super () . __init__ () 20 21 self . drive = drive 22 self . forward = forward 23 self . rotation = rotation 24 25 self . addRequirements ( self . drive ) 26 27 def execute ( self ) -> None : 28 self . drive . arcadeDrive ( self . forward (), self . rotation ()) And then usage: JAVA 59 // Configure default commands 60 // Set the default drive command to split-stick arcade drive 61 m_robotDrive . setDefaultCommand ( 62 // A split-stick arcade command, with forward/backward controlled by the left 63 // hand, and turning controlled by the right. 64 new DefaultDrive ( 65 m_robotDrive , 66 () -> - m_driverController . getLeftY (), 67 () -> - m_driverController . getRightX ())); C++ 57 // Set up default drive command 58 m_drive . SetDefaultCommand ( DefaultDrive ( 59 & m_drive , [ this ] { return - m_driverController . GetLeftY (); }, 60 [ this ] { return - m_driverController . GetRightX (); })); PYTHON 65 # set up default drive command 66 self . drive . setDefaultCommand ( 67 DefaultDrive ( 68 self . drive , 69 lambda : - self . driverController . getY (), 70 lambda : self . driverController . getX (), 71 ) 72 ) Notice that this command does not override isFinished() , and thus will never end; this is the norm for commands that are intended to be used as default commands. Once more, this command is rather simple and calls the subsystem method only from one place, and as such, could be more concisely written using factories: JAVA 51 // Configure default commands 52 // Set the default drive command to split-stick arcade drive 53 m_robotDrive . setDefaultCommand ( 54 // A split-stick arcade command, with forward/backward controlled by the left 55 // hand, and turning controlled by the right. 56 Commands . run ( 57 () -> 58 m_robotDrive . arcadeDrive ( 59 - m_driverController . getLeftY (), - m_driverController . getRightX ()), 60 m_robotDrive )); C++ 52 // Set up default drive command 53 m_drive . SetDefaultCommand ( frc2 :: cmd :: Run ( 54 [ this ] { 55 m_drive . ArcadeDrive ( - m_driverController . GetLeftY (), 56 - m_driverController . GetRightX ()); 57 }, 58 { & m_drive })); PYTHON 53 # Configure default commands 54 # Set the default drive command to split-stick arcade drive 55 self . driveSubsystem . setDefaultCommand ( 56 # A split-stick arcade command, with forward/backward controlled by the left 57 # hand, and turning controlled by the right. 58 commands2 . cmd . run ( 59 lambda : self . driveSubsystem . arcadeDrive ( 60 - self . driverController . getLeftY (), 61 - self . driverController . getRightX (), 62 ), 63 self . driveSubsystem , 64 ) 65 )",
- "content_preview": "Commands Commands represent actions the robot can take. Commands run when scheduled, until they are interrupted or their end condition is met. Commands are represented in the command-based library by the Command class ( Java , C++ ) or the Command class in commands2 library ( Python )."
+ "content": "The Command Scheduler The CommandScheduler ( Java , C++ ) is the class responsible for actually running commands. Each iteration (ordinarily once per 20ms), the scheduler polls all registered buttons, schedules commands for execution accordingly, runs the command bodies of all scheduled commands, and ends those commands that have finished or are interrupted. The CommandScheduler also runs the periodic() method of each registered Subsystem . Using the Command Scheduler The CommandScheduler is a singleton , meaning that it is a globally-accessible class with only one instance. Accordingly, in order to access the scheduler, users must call the CommandScheduler.getInstance() command. For the most part, users do not have to call scheduler methods directly - almost all important scheduler methods have convenience wrappers elsewhere (e.g. in the Command and Subsystem classes). However, there is one exception: users must call CommandScheduler.getInstance().run() from the robotPeriodic() method of their Robot class. If this is not done, the scheduler will never run, and the command framework will not work. The provided command-based project template has this call already included. The schedule() Method To schedule a command, users call the schedule() method ( Java , C++ ). This method takes a command, and attempts to add it to list of currently-running commands, pending whether it is already running or whether its requirements are available. If it is added, its initialize() method is called. This method walks through the following steps: Verifies that the command isn’t in a composition. No-op if scheduler is disabled, command is already scheduled, or robot is disabled and command doesn’t runsWhenDisabled . If requirements are in use: If all conflicting commands are interruptible, cancel them. If not, don’t schedule the new command. Call initialize() . Java 202 private void schedule ( Command command ) { 203 if ( command == null ) { 204 DriverStation . reportWarning ( \"Tried to schedule a null command\" , true ); 205 return ; 206 } 207 if ( m_inRunLoop ) { 208 m_toSchedule . add ( command ); 209 return ; 210 } 211 212 requireNotComposed ( command ); 213 214 // Do nothing if the scheduler is disabled, the robot is disabled and the command doesn't 215 // run when disabled, or the command is already scheduled. 216 if ( m_disabled 217 || isScheduled ( command ) 218 || RobotState . isDisabled () && ! command . runsWhenDisabled ()) { 219 return ; 220 } 221 222 Set < Subsystem > requirements = command . getRequirements (); 223 224 // Schedule the command if the requirements are not currently in-use. 225 if ( Collections . disjoint ( m_requirements . keySet (), requirements )) { 226 initCommand ( command , requirements ); 227 } else { 228 // Else check if the requirements that are in use have all have interruptible commands, 229 // and if so, interrupt those commands and schedule the new command. 230 for ( Subsystem requirement : requirements ) { 231 Command requiring = requiring ( requirement ); 232 if ( requiring != null 233 && requiring . getInterruptionBehavior () == InterruptionBehavior . kCancelIncoming ) { 234 return ; 235 } 236 } 237 for ( Subsystem requirement : requirements ) { 238 Command requiring = requiring ( requirement ); 239 if ( requiring != null ) { 240 cancel ( requiring ); 241 } 242 } 243 initCommand ( command , requirements ); 244 } 245 } 181 private void initCommand ( Command command , Set < Subsystem > requirements ) { 182 m_scheduledCommands . add ( command ); 183 for ( Subsystem requirement : requirements ) { 184 m_requirements . put ( requirement , command ); 185 } 186 command . initialize (); 187 for ( Consumer < Command > action : m_initActions ) { 188 action . accept ( command ); 189 } 190 191 m_watchdog . addEpoch ( command . getName () + \".initialize()\" ); C++ (Source) 114 void CommandScheduler::Schedule ( Command * command ) { 115 if ( m_impl -> inRunLoop ) { 116 m_impl -> toSchedule . emplace_back ( command ); 117 return ; 118 } 119 120 RequireUngrouped ( command ); 121 122 if ( m_impl -> disabled || m_impl -> scheduledCommands . contains ( command ) || 123 ( frc :: RobotState :: IsDisabled () && ! command -> RunsWhenDisabled ())) { 124 return ; 125 } 126 127 const auto & requirements = command -> GetRequirements (); 128 129 wpi :: SmallVector < Command * , 8 > intersection ; 130 131 bool isDisjoint = true ; 132 bool allInterruptible = true ; 133 for ( auto && i1 : m_impl -> requirements ) { 134 if ( requirements . find ( i1 . first ) != requirements . end ()) { 135 isDisjoint = false ; 136 allInterruptible &= ( i1 . second -> GetInterruptionBehavior () == 137 Command :: InterruptionBehavior :: kCancelSelf ); 138 intersection . emplace_back ( i1 . second ); 139 } 140 } 141 142 if ( isDisjoint || allInterruptible ) { 143 if ( allInterruptible ) { 144 for ( auto && cmdToCancel : intersection ) { 145 Cancel ( cmdToCancel ); 146 } 147 } 148 m_impl -> scheduledCommands . insert ( command ); 149 for ( auto && requirement : requirements ) { 150 m_impl -> requirements [ requirement ] = command ; 151 } 152 command -> Initialize (); 153 for ( auto && action : m_impl -> initActions ) { 154 action ( * command ); 155 } 156 m_watchdog . AddEpoch ( command -> GetName () + \".Initialize()\" ); 157 } 158 } The Scheduler Run Sequence Note The initialize() method of each Command is called when the command is scheduled, which is not necessarily when the scheduler runs (unless that command is bound to a button). What does a single iteration of the scheduler’s run() method ( Java , C++ ) actually do? The following section walks through the logic of a scheduler iteration. For the full implementation, see the source code ( Java , C++ ). Step 1: Run Subsystem Periodic Methods First, the scheduler runs the periodic() method of each registered Subsystem . In simulation, each subsystem’s simulationPeriodic() method is called as well. Java 278 // Run the periodic method of all registered subsystems. 279 for ( Subsystem subsystem : m_subsystems . keySet ()) { 280 subsystem . periodic (); 281 if ( RobotBase . isSimulation ()) { 282 subsystem . simulationPeriodic (); 283 } 284 m_watchdog . addEpoch ( subsystem . getClass (). getSimpleName () + \".periodic()\" ); 285 } C++ (Source) 183 // Run the periodic method of all registered subsystems. 184 for ( auto && subsystem : m_impl -> subsystems ) { 185 subsystem . getFirst () -> Periodic (); 186 if constexpr ( frc :: RobotBase :: IsSimulation ()) { 187 subsystem . getFirst () -> SimulationPeriodic (); 188 } 189 m_watchdog . AddEpoch ( \"Subsystem Periodic()\" ); 190 } Step 2: Poll Command Scheduling Triggers Note For more information on how trigger bindings work, see Binding Commands to Triggers Secondly, the scheduler polls the state of all registered triggers to see if any new commands that have been bound to those triggers should be scheduled. If the conditions for scheduling a bound command are met, the command is scheduled and its initialize() method is run. Note If a newly-scheduled command has requirement conflicts with a currently-running command, the currently-running command is interrupted first. The end(true) method of the interrupted command is called before the initialize() method of the new command. Java 290 // Poll buttons for new commands to add. 291 loopCache . poll (); 292 m_watchdog . addEpoch ( \"buttons.run()\" ); C++ (Source) 195 // Poll buttons for new commands to add. 196 loopCache -> Poll (); 197 m_watchdog . AddEpoch ( \"buttons.Run()\" ); Step 3: Run/Finish Scheduled Commands Thirdly, the scheduler calls the execute() method of each currently-scheduled command, and then checks whether the command has finished by calling the isFinished() method. If the command has finished, the end() method is also called, and the command is de-scheduled and its required subsystems are freed. Note that this sequence of calls is done in order for each command - thus, one command may have its end() method called before another has its execute() method called. Commands are handled in the order they were scheduled. Java 295 // Run scheduled commands, remove finished commands. 296 for ( Iterator < Command > iterator = m_scheduledCommands . iterator (); iterator . hasNext (); ) { 297 Command command = iterator . next (); 298 299 if ( ! command . runsWhenDisabled () && RobotState . isDisabled ()) { 300 command . end ( true ); 301 for ( Consumer < Command > action : m_interruptActions ) { 302 action . accept ( command ); 303 } 304 m_requirements . keySet (). removeAll ( command . getRequirements ()); 305 iterator . remove (); 306 m_watchdog . addEpoch ( command . getName () + \".end(true)\" ); 307 continue ; 308 } 309 310 command . execute (); 311 for ( Consumer < Command > action : m_executeActions ) { 312 action . accept ( command ); 313 } 314 m_watchdog . addEpoch ( command . getName () + \".execute()\" ); 315 if ( command . isFinished ()) { 316 command . end ( false ); 317 for ( Consumer < Command > action : m_finishActions ) { 318 action . accept ( command ); 319 } 320 iterator . remove (); 321 322 m_requirements . keySet (). removeAll ( command . getRequirements ()); 323 m_watchdog . addEpoch ( command . getName () + \".end(false)\" ); 324 } 325 } C++ (Source) 201 for ( Command * command : m_impl -> scheduledCommands ) { 202 if ( ! command -> RunsWhenDisabled () && frc :: RobotState :: IsDisabled ()) { 203 Cancel ( command ); 204 continue ; 205 } 206 207 command -> Execute (); 208 for ( auto && action : m_impl -> executeActions ) { 209 action ( * command ); 210 } 211 m_watchdog . AddEpoch ( command -> GetName () + \".Execute()\" ); 212 213 if ( command -> IsFinished ()) { 214 command -> End ( false ); 215 for ( auto && action : m_impl -> finishActions ) { 216 action ( * command ); 217 } 218 219 for ( auto && requirement : command -> GetRequirements ()) { 220 m_impl -> requirements . erase ( requirement ); 221 } 222 223 m_impl -> scheduledCommands . erase ( command ); 224 m_watchdog . AddEpoch ( command -> GetName () + \".End(false)\" ); 225 } 226 } Step 4: Schedule Default Commands Finally, any registered Subsystem has its default command scheduled (if it has one). Note that the initialize() method of the default command will be called at this time. Java 340 // Add default commands for un-required registered subsystems. 341 for ( Map . Entry < Subsystem , Command > subsystemCommand : m_subsystems . entrySet ()) { 342 if ( ! m_requirements . containsKey ( subsystemCommand . getKey ()) 343 && subsystemCommand . getValue () != null ) { 344 schedule ( subsystemCommand . getValue ()); 345 } 346 } C++ (Source) 240 // Add default commands for un-required registered subsystems. 241 for ( auto && subsystem : m_impl -> subsystems ) { 242 auto s = m_impl -> requirements . find ( subsystem . getFirst ()); 243 if ( s == m_impl -> requirements . end () && subsystem . getSecond ()) { 244 Schedule ({ subsystem . getSecond (). get ()}); 245 } 246 } Disabling the Scheduler The scheduler can be disabled by calling CommandScheduler.getInstance().disable() . When disabled, the scheduler’s schedule() and run() commands will not do anything. The scheduler may be re-enabled by calling CommandScheduler.getInstance().enable() . Command Event Methods Occasionally, it is desirable to have the scheduler execute a custom action whenever a certain command event (initialization, execution, or ending) occurs. This can be done with the following methods: onCommandInitialize ( Java , C++ ) runs a specified action whenever a command is initialized. onCommandExecute ( Java , C++ ) runs a specified action whenever a command is executed. onCommandFinish ( Java , C++ ) runs a specified action whenever a command finishes normally (i.e. the isFinished() method returned true). onCommandInterrupt ( Java , C++ ) runs a specified action whenever a command is interrupted (i.e. by being explicitly canceled or by another command that shares one of its requirements). A typical use-case for these methods is adding markers in an event log whenever a command scheduling event takes place, as demonstrated in the following code from the HatchbotInlined example project ( Java , C++ ): Java 73 // Set the scheduler to log Shuffleboard events for command initialize, interrupt, finish 74 CommandScheduler . getInstance () 75 . onCommandInitialize ( 76 command -> 77 Shuffleboard . addEventMarker ( 78 \"Command initialized\" , command . getName (), EventImportance . kNormal )); 79 CommandScheduler . getInstance () 80 . onCommandInterrupt ( 81 command -> 82 Shuffleboard . addEventMarker ( 83 \"Command interrupted\" , command . getName (), EventImportance . kNormal )); 84 CommandScheduler . getInstance () 85 . onCommandFinish ( 86 command -> 87 Shuffleboard . addEventMarker ( 88 \"Command finished\" , command . getName (), EventImportance . kNormal )); C++ (Source) 23 // Log Shuffleboard events for command initialize, execute, finish, interrupt 24 frc2 :: CommandScheduler :: GetInstance (). OnCommandInitialize ( 25 []( const frc2 :: Command & command ) { 26 frc :: Shuffleboard :: AddEventMarker ( 27 \"Command initialized\" , command . GetName (), 28 frc :: ShuffleboardEventImportance :: kNormal ); 29 }); 30 frc2 :: CommandScheduler :: GetInstance (). OnCommandExecute ( 31 []( const frc2 :: Command & command ) { 32 frc :: Shuffleboard :: AddEventMarker ( 33 \"Command executed\" , command . GetName (), 34 frc :: ShuffleboardEventImportance :: kNormal ); 35 }); 36 frc2 :: CommandScheduler :: GetInstance (). OnCommandFinish ( 37 []( const frc2 :: Command & command ) { 38 frc :: Shuffleboard :: AddEventMarker ( 39 \"Command finished\" , command . GetName (), 40 frc :: ShuffleboardEventImportance :: kNormal ); 41 }); 42 frc2 :: CommandScheduler :: GetInstance (). OnCommandInterrupt ( 43 []( const frc2 :: Command & command ) { 44 frc :: Shuffleboard :: AddEventMarker ( 45 \"Command interrupted\" , command . GetName (), 46 frc :: ShuffleboardEventImportance :: kNormal ); 47 });",
+ "content_preview": "The Command Scheduler The CommandScheduler ( Java , C++ ) is the class responsible for actually running commands. Each iteration (ordinarily once per 20ms), the scheduler polls all registered buttons, schedules commands for execution accordingly, runs the command bodies of all scheduled commands,..."
},
{
- "url": "https://docs.wpilib.org/en/stable/docs/software/commandbased/what-is-command-based.html?present",
- "title": "What Is “Command",
+ "url": "https://docs.wpilib.org/en/stable/docs/software/commandbased/command-compositions.html",
+ "title": "Command Compositions",
"section": "Command-Based Programming",
"language": "All",
- "content": "What Is “Command-Based” Programming? WPILib supports a robot programming methodology called “command-based” programming. In general, “command-based” can refer both the general programming paradigm, and to the set of WPILib library resources included to facilitate it. “Command-based” programming is one possible design pattern for robot software. It is not the only way to write a robot program, but it is a very effective one. Command-based robot code tends to be clean, extensible, and (with some tricks) easy to reuse from year to year. The command-based paradigm is also an example of declarative programming . The command-based library allow users to define desired robot behaviors while minimizing the amount of iteration-by-iteration robot logic that they must write. For example, in the command-based program, a user can specify that “the robot should perform an action when a condition is true” (note the use of a lambda ): JAVA new Trigger ( condition :: get ). onTrue ( Commands . runOnce (() -> piston . set ( DoubleSolenoid . Value . kForward ))); C++ Trigger ([ & condition ] { return condition . Get (); }). OnTrue ( frc2 :: cmd :: RunOnce ([ & piston ] { piston . Set ( frc :: DoubleSolenoid :: kForward ); })); PYTHON Trigger ( condition . get ) . onTrue ( Commands . runOnce ( lambda : piston . set ( DoubleSolenoid . Value . kForward ))) In contrast, without using command-based, the user would need to check the button state every iteration, and perform the appropriate action based on the state of the button. JAVA if ( condition . get ()) { if ( ! pressed ) { piston . set ( DoubleSolenoid . Value . kForward ); pressed = true ; } } else { pressed = false ; } C++ if ( condition . Get ()) { if ( ! pressed ) { piston . Set ( frc :: DoubleSolenoid :: kForward ); pressed = true ; } } else { pressed = false ; } PYTHON if condition . get (): if not pressed : piston . set ( DoubleSolenoid . Value . kForward ) pressed = True else : pressed = False Subsystems and Commands The command-based pattern is based around two core abstractions: commands , and subsystems. Commands represent actions the robot can take. Commands run when scheduled, until they are interrupted or their end condition is met. Commands are very recursively composable: commands can be composed to accomplish more-complicated tasks. See Commands for more info. Subsystems represent independently-controlled collections of robot hardware (such as motor controllers, sensors, pneumatic actuators, etc.) that operate together. Subsystems back the resource-management system of command-based: only one command can use a given subsystem at the same time. Subsystems allow users to “hide” the internal complexity of their actual hardware from the rest of their code - this both simplifies the rest of the robot code, and allows changes to the internal details of a subsystem’s hardware without also changing the rest of the robot code. How Commands Are Run Note For a more detailed explanation, see The Command Scheduler . Commands are run by the CommandScheduler ( Java , C++ , Python ) singleton, which polls triggers (such as buttons) for commands to schedule, preventing resource conflicts, and executing scheduled commands. The scheduler’s run() method must be called; it is generally recommended to call it from the robotPeriodic() method of the Robot class, which is run at a default frequency of 50Hz (once every 20ms). Multiple commands can run concurrently, as long as they do not require the same resources on the robot. Resource management is handled on a per-subsystem basis: commands specify which subsystems they interact with, and the scheduler will ensure that no more more than one command requiring a given subsystem is scheduled at a time. This ensures that, for example, users will not end up with two different pieces of code attempting to set the same motor controller to different output values. Command Compositions It is often desirable to build complex commands from simple pieces. This is achievable by creating a composition of commands. The command-based library provides several types of command compositions for teams to use, and users may write their own. As command compositions are commands themselves, they may be used in a recursive composition . That is to say - one can create a command compositions from multiple command compositions. This provides an extremely powerful way of building complex robot actions from simple components.",
- "content_preview": "What Is “Command-Based” Programming? WPILib supports a robot programming methodology called “command-based” programming. In general, “command-based” can refer both the general programming paradigm, and to the set of WPILib library resources included to facilitate it."
+ "content": "Command Compositions Individual commands are capable of accomplishing a large variety of robot tasks, but the simple three-state format can quickly become cumbersome when more advanced functionality requiring extended sequences of robot tasks or coordination of multiple robot subsystems is required. In order to accomplish this, users are encouraged to use the powerful command composition functionality included in the command-based library. As the name suggests, a command composition is a composition of one or more commands. This allows code to be kept much cleaner and simpler, as the individual component commands may be written independently of the code that combines them, greatly reducing the amount of complexity at any given step of the process. Most importantly, however, command compositions are themselves commands - they extend the Command class. This allows command compositions to be further composed as a recursive composition - that is, a command composition may contain other command compositions as components. This allows very powerful and concise inline expressions: JAVA // Will run fooCommand, and then a race between barCommand and bazCommand button . onTrue ( fooCommand . andThen ( barCommand . raceWith ( bazCommand ))); C++ // Will run fooCommand, and then a race between barCommand and bazCommand button . OnTrue ( std :: move ( fooCommand ). AndThen ( std :: move ( barCommand ). RaceWith ( std :: move ( bazCommand )))); PYTHON # Will run fooCommand, and then a race between barCommand and bazCommand button . onTrue ( fooCommand . andThen ( barCommand . raceWith ( bazCommand ))) As a rule, command compositions require all subsystems their components require, may run when disabled if all their component set runsWhenDisabled as true , and are kCancelIncoming if all their components are kCancelIncoming as well. Command instances that have been passed to a command composition cannot be independently scheduled or passed to a second command composition. Attempting to do so will throw an exception and crash the user program. This is because composition members are run through their encapsulating command composition, and errors could occur if those same command instances were independently scheduled at the same time as the composition - the command would be being run from multiple places at once, and thus could end up with inconsistent internal state, causing unexpected and hard-to-diagnose behavior. The C++ command-based library uses CommandPtr , a class with move-only semantics, so this type of mistake is easier to avoid. Composition Types The command-based library includes various composition types. All of them can be constructed using factories that accept the member commands, and some can also be constructed using decorators: methods that can be called on a command object, which is transformed into a new object that is returned. Important After calling a decorator or being passed to a composition, the command object cannot be reused! Use only the command object returned from the decorator. Repeating The repeatedly() decorator ( Java , C++ , Python ), backed by the RepeatCommand class ( Java , C++ , Python ) restarts the command each time it ends, so that it runs until interrupted. JAVA // Will run forever unless externally interrupted, restarting every time command.isFinished() returns true Command repeats = command . repeatedly (); C++ // Will run forever unless externally interrupted, restarting every time command.IsFinished() returns true frc2 :: CommandPtr repeats = std :: move ( command ). Repeatedly (); PYTHON # Will run forever unless externally interrupted, restarting every time command.IsFinished() returns true repeats = command . repeatedly () Sequence The Sequence factory ( Java , C++ , Python ), backed by the SequentialCommandGroup class ( Java , C++ , Python ), runs a list of commands in sequence: the first command will be executed, then the second, then the third, and so on until the list finishes. The sequential group finishes after the last command in the sequence finishes. It is therefore usually important to ensure that each command in the sequence does actually finish (if a given command does not finish, the next command will never start!). The andThen() ( Java , C++ , Python ) and beforeStarting() ( Java , C++ , Python ) decorators can be used to construct a sequence composition with infix syntax. JAVA fooCommand . andThen ( barCommand ) C++ std :: move ( fooCommand ). AndThen ( std :: move ( barCommand )) PYTHON fooCommand . andThen ( barCommand ) Repeating Sequence As it’s a fairly common combination, the RepeatingSequence factory ( Java , C++ , Python ) creates a Repeating Sequence that runs until interrupted, restarting from the first command each time the last command finishes. Parallel There are three types of parallel compositions, differing based on when the composition finishes: The Parallel factory ( Java , C++ , Python ), backed by the ParallelCommandGroup class ( Java , C++ , Python ), constructs a parallel composition that finishes when all members finish. The alongWith decorator ( Java , C++ , Python ) does the same in infix notation. The Race factory ( Java , C++ , Python ), backed by the ParallelRaceGroup class ( Java , C++ , Python ), constructs a parallel composition that finishes as soon as any member finishes; all other members are interrupted at that point. The raceWith decorator ( Java , C++ , Python ) does the same in infix notation. The Deadline factory ( Java , C++ , Python ), ParallelDeadlineGroup ( Java , C++ , Python ) finishes when a specific command (the “deadline”) ends; all other members still running at that point are interrupted. The deadlineWith decorator ( Java , C++ , Python ) does the same in infix notation; the command the decorator was called on is the deadline. JAVA // Will be a parallel command composition that ends after three seconds with all three commands running their full duration. button . onTrue ( Commands . parallel ( twoSecCommand , oneSecCommand , threeSecCommand )); // Will be a parallel race composition that ends after one second with the two and three second commands getting interrupted. button . onTrue ( Commands . race ( twoSecCommand , oneSecCommand , threeSecCommand )); // Will be a parallel deadline composition that ends after two seconds (the deadline) with the three second command getting interrupted (one second command already finished). button . onTrue ( Commands . deadline ( twoSecCommand , oneSecCommand , threeSecCommand )); C++ // Will be a parallel command composition that ends after three seconds with all three commands running their full duration. button . OnTrue ( frc2 :: cmd :: Parallel ( std :: move ( twoSecCommand ), std :: move ( oneSecCommand ), std :: move ( threeSecCommand ))); // Will be a parallel race composition that ends after one second with the two and three second commands getting interrupted. button . OnTrue ( frc2 :: cmd :: Race ( std :: move ( twoSecCommand ), std :: move ( oneSecCommand ), std :: move ( threeSecCommand ))); // Will be a parallel deadline composition that ends after two seconds (the deadline) with the three second command getting interrupted (one second command already finished). button . OnTrue ( frc2 :: cmd :: Deadline ( std :: move ( twoSecCommand ), std :: move ( oneSecCommand ), std :: move ( threeSecCommand ))); PYTHON # Will be a parallel command composition that ends after three seconds with all three commands running their full duration. button . onTrue ( commands2 . cmd . parallel ( twoSecCommand , oneSecCommand , threeSecCommand )) # Will be a parallel race composition that ends after one second with the two and three second commands getting interrupted. button . onTrue ( commands2 . cmd . race ( twoSecCommand , oneSecCommand , threeSecCommand )) # Will be a parallel deadline composition that ends after two seconds (the deadline) with the three second command getting interrupted (one second command already finished). button . onTrue ( commands2 . cmd . deadline ( twoSecCommand , oneSecCommand , threeSecCommand )) Adding Command End Conditions The until() ( Java , C++ , Python ) decorator composes the command with an additional end condition. Note that the command the decorator was called on will see this end condition as an interruption. JAVA // Will be interrupted if m_limitSwitch.get() returns true button . onTrue ( command . until ( m_limitSwitch :: get )); C++ // Will be interrupted if m_limitSwitch.get() returns true button . OnTrue ( command . Until ([ & m_limitSwitch ] { return m_limitSwitch . Get (); })); PYTHON # Will be interrupted if limitSwitch.get() returns true button . onTrue ( commands2 . cmd . until ( limitSwitch . get )) The withTimeout() decorator ( Java , C++ , Python ) is a specialization of until that uses a timeout as the additional end condition. JAVA // Will time out 5 seconds after being scheduled, and be interrupted button . onTrue ( command . withTimeout ( 5 )); C++ // Will time out 5 seconds after being scheduled, and be interrupted button . OnTrue ( command . WithTimeout ( 5.0 _s )); PYTHON # Will time out 5 seconds after being scheduled, and be interrupted button . onTrue ( commands2 . cmd . withTimeout ( 5.0 )) Adding End Behavior The finallyDo() ( Java , C++ , Python ) decorator composes the command with an a lambda that will be called after the command’s end() method, with the same boolean parameter indicating whether the command finished or was interrupted. The handleInterrupt() ( Java , C++ , Python ) decorator composes the command with an a lambda that will be called only when the command is interrupted. Selecting Compositions Sometimes it’s desired to run a command out of a few options based on sensor feedback or other data known only at runtime. This can be useful for determining an auto routine, or running a different command based on whether a game piece is present or not, and so on. The Select factory ( Java , C++ , Python ), backed by the SelectCommand class ( Java , C++ , Python ), executes one command from a map, based on a selector function called when scheduled. Java 20 public class RobotContainer { 21 // The enum used as keys for selecting the command to run. 22 private enum CommandSelector { 23 ONE , 24 TWO , 25 THREE 26 } 27 28 // An example selector method for the selectcommand. Returns the selector that will select 29 // which command to run. Can base this choice on logical conditions evaluated at runtime. 30 private CommandSelector select () { 31 return CommandSelector . ONE ; 32 } 33 34 // An example selectcommand. Will select from the three commands based on the value returned 35 // by the selector method at runtime. Note that selectcommand works on Object(), so the 36 // selector does not have to be an enum; it could be any desired type (string, integer, 37 // boolean, double...) 38 private final Command m_exampleSelectCommand = 39 new SelectCommand <> ( 40 // Maps selector values to commands 41 Map . ofEntries ( 42 Map . entry ( CommandSelector . ONE , new PrintCommand ( \"Command one was selected!\" )), 43 Map . entry ( CommandSelector . TWO , new PrintCommand ( \"Command two was selected!\" )), 44 Map . entry ( CommandSelector . THREE , new PrintCommand ( \"Command three was selected!\" ))), 45 this :: select ); C++ (Header) 26 // The enum used as keys for selecting the command to run. 27 enum CommandSelector { ONE , TWO , THREE }; 28 29 // An example of how command selector may be used with SendableChooser 30 frc :: SendableChooser < CommandSelector > m_chooser ; 31 32 // The robot's subsystems and commands are defined here... 33 34 // An example selectcommand. Will select from the three commands based on the 35 // value returned by the selector method at runtime. Note that selectcommand 36 // takes a generic type, so the selector does not have to be an enum; it could 37 // be any desired type (string, integer, boolean, double...) 38 frc2 :: CommandPtr m_exampleSelectCommand = frc2 :: cmd :: Select < CommandSelector > ( 39 [ this ] { return m_chooser . GetSelected (); }, 40 // Maps selector values to commands 41 std :: pair { ONE , frc2 :: cmd :: Print ( \"Command one was selected!\" )}, 42 std :: pair { TWO , frc2 :: cmd :: Print ( \"Command two was selected!\" )}, 43 std :: pair { THREE , frc2 :: cmd :: Print ( \"Command three was selected!\" )}); The Either factory ( Java , C++ , Python ), backed by the ConditionalCommand class ( Java , C++ , Python ), is a specialization accepting two commands and a boolean selector function. JAVA // Runs either commandOnTrue or commandOnFalse depending on the value of m_limitSwitch.get() new ConditionalCommand ( commandOnTrue , commandOnFalse , m_limitSwitch :: get ) C++ // Runs either commandOnTrue or commandOnFalse depending on the value of m_limitSwitch.get() frc2 :: ConditionalCommand ( commandOnTrue , commandOnFalse , [ & m_limitSwitch ] { return m_limitSwitch . Get (); }) PYTHON # Runs either commandOnTrue or commandOnFalse depending on the value of limitSwitch.get() ConditionalCommand ( commandOnTrue , commandOnFalse , limitSwitch . get ) The unless() decorator ( Java , C++ , Python ) composes a command with a condition that will prevent it from running. JAVA // Command will only run if the intake is deployed. If the intake gets deployed while the command is running, the command will not stop running button . onTrue ( command . unless (() -> ! intake . isDeployed ())); C++ // Command will only run if the intake is deployed. If the intake gets deployed while the command is running, the command will not stop running button . OnTrue ( command . Unless ([ & intake ] { return ! intake . IsDeployed (); })); PYTHON # Command will only run if the intake is deployed. If the intake gets deployed while the command is running, the command will not stop running button . onTrue ( command . unless ( lambda : not intake . isDeployed ())) ProxyCommand described below also has a constructor overload ( Java , C++ , Python ) that calls a command-returning lambda at schedule-time and runs the returned command by proxy. Scheduling Other Commands By default, composition members are run through the command composition, and are never themselves seen by the scheduler. Accordingly, their requirements are added to the composition’s requirements. While this is usually fine, sometimes it is undesirable for the entire command composition to gain the requirements of a single command. A good solution is to “fork off” from the command composition and schedule that command separately. However, this requires synchronization between the composition and the individually-scheduled command. ProxyCommand ( Java , C++ , Python ), also creatable using the .asProxy() decorator ( Java , C++ , Python ), schedules a command “by proxy”: the command is scheduled when the proxy is scheduled, and the proxy finishes when the command finishes. In the case of “forking off” from a command composition, this allows the composition to track the command’s progress without it being in the composition. Command compositions inherit the union of their compoments’ requirements and requirements are immutable. Therefore, a SequentialCommandGroup ( Java , C++ , Python ) that intakes a game piece, indexes it, aims a shooter, and shoots it would reserve all three subsystems (the intake, indexer, and shooter), precluding any of those subsystems from performing other operations in their “downtime”. If this is not desired, the subsystems that should only be reserved for the composition while they are actively being used by it should have their commands proxied. Warning Do not use ProxyCommand unless you are sure of what you are doing and there is no other way to accomplish your need! Proxying is only intended for use as an escape hatch from command composition requirement unions. Note Because proxied commands still require their subsystem, despite not leaking that requirement to the composition, all of the commands that require a given subsystem must be proxied if one of them is. Otherwise, when the proxied command is scheduled its requirement will conflict with that of the composition, canceling the composition. JAVA // composition requirements are indexer and shooter, intake still reserved during its command but not afterwards Commands . sequence ( intake . intakeGamePiece (). asProxy (), // we want to let the intake intake another game piece while we are processing this one indexer . processGamePiece (), shooter . aimAndShoot () ); C++ // composition requirements are indexer and shooter, intake still reserved during its command but not afterwards frc2 :: cmd :: Sequence ( intake . IntakeGamePiece (). AsProxy (), // we want to let the intake intake another game piece while we are processing this one indexer . ProcessGamePiece (), shooter . AimAndShoot () ); PYTHON # composition requirements are indexer and shooter, intake still reserved during its command but not afterwards commands2 . cmd . sequence ( intake . intakeGamePiece () . asProxy (), # we want to let the intake intake another game piece while we are processing this one indexer . processGamePiece (), shooter . aimAndShoot () ) For cases that don’t need to track the proxied command, ScheduleCommand ( Java , C++ , Python ) schedules a specified command and ends instantly. JAVA // ScheduleCommand ends immediately, so the sequence continues new ScheduleCommand ( Commands . waitSeconds ( 5.0 )) . andThen ( Commands . print ( \"This will be printed immediately!\" )) C++ // ScheduleCommand ends immediately, so the sequence continues frc2 :: ScheduleCommand ( frc2 :: cmd :: Wait ( 5.0 _s )) . AndThen ( frc2 :: cmd :: Print ( \"This will be printed immediately!\" )) PYTHON # ScheduleCommand ends immediately, so the sequence continues ScheduleCommand ( commands2 . cmd . waitSeconds ( 5.0 )) . andThen ( commands2 . cmd . print ( \"This will be printed immediately!\" )) Subclassing Compositions Command compositions can also be written as a constructor-only subclass of the most exterior composition type, passing the composition members to the superclass constructor. Consider the following from the Hatch Bot example project ( Java , C++ ): Java 5 package edu.wpi.first.wpilibj.examples.hatchbottraditional.commands ; 6 7 import edu.wpi.first.wpilibj.examples.hatchbottraditional.Constants.AutoConstants ; 8 import edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems.DriveSubsystem ; 9 import edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems.HatchSubsystem ; 10 import edu.wpi.first.wpilibj2.command.SequentialCommandGroup ; 11 12 /** A complex auto command that drives forward, releases a hatch, and then drives backward. */ 13 public class ComplexAuto extends SequentialCommandGroup { 14 /** 15 * Creates a new ComplexAuto. 16 * 17 * @param drive The drive subsystem this command will run on 18 * @param hatch The hatch subsystem this command will run on 19 */ 20 public ComplexAuto ( DriveSubsystem drive , HatchSubsystem hatch ) { 21 addCommands ( 22 // Drive forward the specified distance 23 new DriveDistance ( 24 AutoConstants . kAutoDriveDistanceInches , AutoConstants . kAutoDriveSpeed , drive ), 25 26 // Release the hatch 27 new ReleaseHatch ( hatch ), 28 29 // Drive backward the specified distance 30 new DriveDistance ( 31 AutoConstants . kAutoBackupDistanceInches , - AutoConstants . kAutoDriveSpeed , drive )); 32 } 33 } C++ (Header) 5 #pragma once 6 7 #include 8 #include 9 10 #include \"Constants.h\" 11 #include \"commands/DriveDistance.h\" 12 #include \"commands/ReleaseHatch.h\" 13 14 /** 15 * A complex auto command that drives forward, releases a hatch, and then drives 16 * backward. 17 */ 18 class ComplexAuto 19 : public frc2 :: CommandHelper < frc2 :: SequentialCommandGroup , ComplexAuto > { 20 public : 21 /** 22 * Creates a new ComplexAuto. 23 * 24 * @param drive The drive subsystem this command will run on 25 * @param hatch The hatch subsystem this command will run on 26 */ 27 ComplexAuto ( DriveSubsystem * drive , HatchSubsystem * hatch ); 28 }; C++ (Source) 5 #include \"commands/ComplexAuto.h\" 6 7 using namespace AutoConstants ; 8 9 ComplexAuto :: ComplexAuto ( DriveSubsystem * drive , HatchSubsystem * hatch ) { 10 AddCommands ( 11 // Drive forward the specified distance 12 DriveDistance ( kAutoDriveDistanceInches , kAutoDriveSpeed , drive ), 13 // Release the hatch 14 ReleaseHatch ( hatch ), 15 // Drive backward the specified distance 16 DriveDistance ( kAutoBackupDistanceInches , - kAutoDriveSpeed , drive )); 17 } Python 7 import commands2 8 9 import constants 10 11 from .drivedistance import DriveDistance 12 from .releasehatch import ReleaseHatch 13 14 from subsystems.drivesubsystem import DriveSubsystem 15 from subsystems.hatchsubsystem import HatchSubsystem 16 17 18 class ComplexAuto ( commands2 . SequentialCommandGroup ): 19 \"\"\" 20 A complex auto command that drives forward, releases a hatch, and then drives backward. 21 \"\"\" 22 23 def __init__ ( self , drive : DriveSubsystem , hatch : HatchSubsystem ): 24 super () . __init__ ( 25 # Drive forward the specified distance 26 DriveDistance ( 27 constants . kAutoDriveDistanceInches , constants . kAutoDriveSpeed , drive 28 ), 29 # Release the hatch 30 ReleaseHatch ( hatch ), 31 # Drive backward the specified distance 32 DriveDistance ( 33 constants . kAutoBackupDistanceInches , - constants . kAutoDriveSpeed , drive 34 ), 35 ) The advantages and disadvantages of this subclassing approach in comparison to others are discussed in Subclassing Command Groups .",
+ "content_preview": "Command Compositions Individual commands are capable of accomplishing a large variety of robot tasks, but the simple three-state format can quickly become cumbersome when more advanced functionality requiring extended sequences of robot tasks or coordination of multiple robot subsystems is..."
},
{
- "url": "https://docs.wpilib.org/en/stable/docs/software/commandbased/subsystems.html",
- "title": "Subsystems",
+ "url": "https://docs.wpilib.org/en/stable/docs/software/commandbased/command-compositions.html?present",
+ "title": "Command Compositions",
"section": "Command-Based Programming",
"language": "All",
- "content": "Subsystems Subsystems are the basic unit of robot organization in the command-based paradigm. A subsystem is an abstraction for a collection of robot hardware that operates together as a unit . Subsystems form an encapsulation for this hardware, “hiding” it from the rest of the robot code and restricting access to it except through the subsystem’s public methods. Restricting the access in this way provides a single convenient place for code that might otherwise be duplicated in multiple places (such as scaling motor outputs or checking limit switches) if the subsystem internals were exposed. It also allows changes to the specific details of how the subsystem works (the “implementation”) to be isolated from the rest of robot code, making it far easier to make substantial changes if/when the design constraints change. Subsystems also serve as the backbone of the CommandScheduler ’s resource management system. Commands may declare resource requirements by specifying which subsystems they interact with; the scheduler will never concurrently schedule more than one command that requires a given subsystem. An attempt to schedule a command that requires a subsystem that is already-in-use will either interrupt the currently-running command or be ignored, based on the running command’s Interruption Behavior . Subsystems can be associated with “default commands” that will be automatically scheduled when no other command is currently using the subsystem. This is useful for “background” actions such as controlling the robot drive, keeping an arm held at a setpoint, or stopping motors when the subsystem isn’t used. Similar functionality can be achieved in the subsystem’s periodic() method, which is run once per run of the scheduler; teams should try to be consistent within their codebase about which functionality is achieved through either of these methods. Subsystems are represented in the command-based library by the Subsystem interface ( Java , C++ , Python ). Creating a Subsystem The recommended method to create a subsystem for most users is to subclass the abstract SubsystemBase class in ( Java , C++ ), as seen in the command-based template ( Java , C++ ). In Python, because Python does not have interfaces, the Subsystem class is a concrete class that can be subclassed directly ( Python ). The following example demonstrates how to create a simple subsystem in each of the supported languages: Java 7 import edu.wpi.first.wpilibj2.command.Command ; 8 import edu.wpi.first.wpilibj2.command.SubsystemBase ; 9 10 public class ExampleSubsystem extends SubsystemBase { 11 /** Creates a new ExampleSubsystem. */ 12 public ExampleSubsystem () {} 13 14 /** 15 * Example command factory method. 16 * 17 * @return a command 18 */ 19 public Command exampleMethodCommand () { 20 // Inline construction of command goes here. 21 // Subsystem::RunOnce implicitly requires `this` subsystem. 22 return runOnce ( 23 () -> { 24 /* one-time action goes here */ 25 }); 26 } 27 28 /** 29 * An example method querying a boolean state of the subsystem (for example, a digital sensor). 30 * 31 * @return value of some boolean subsystem state, such as a digital sensor. 32 */ 33 public boolean exampleCondition () { 34 // Query some boolean state, such as a digital sensor. 35 return false ; 36 } 37 38 @Override 39 public void periodic () { 40 // This method will be called once per scheduler run 41 } 42 43 @Override 44 public void simulationPeriodic () { 45 // This method will be called once per scheduler run during simulation 46 } 47 } C++ 5 #pragma once 6 7 #include 8 #include 9 10 class ExampleSubsystem : public frc2 :: SubsystemBase { 11 public : 12 ExampleSubsystem (); 13 14 /** 15 * Example command factory method. 16 */ 17 frc2 :: CommandPtr ExampleMethodCommand (); 18 19 /** 20 * An example method querying a boolean state of the subsystem (for example, a 21 * digital sensor). 22 * 23 * @return value of some boolean subsystem state, such as a digital sensor. 24 */ 25 bool ExampleCondition (); 26 27 /** 28 * Will be called periodically whenever the CommandScheduler runs. 29 */ 30 void Periodic () override ; 31 32 /** 33 * Will be called periodically whenever the CommandScheduler runs during 34 * simulation. 35 */ 36 void SimulationPeriodic () override ; 37 38 private : 39 // Components (e.g. motor controllers and sensors) should generally be 40 // declared private and exposed only through public methods. 41 }; Python from commands2 import Command from commands2 import Subsystem class ExampleSubsystem ( Subsystem ): def __init__ ( self ): \"\"\"Creates a new ExampleSubsystem.\"\"\" super () . __init__ () def exampleMethodCommand () -> Command : \"\"\" Example command factory method. :return a command \"\"\" return self . runOnce ( lambda : # one-time action goes here # ) def exampleCondition ( self ) -> bool : \"\"\" An example method querying a boolean state of the subsystem (for example, a digital sensor). :return value of some boolean subsystem state, such as a digital sensor. \"\"\" #Query some boolean state, such as a digital sensor. return False def periodic ( self ): # This method will be called once per scheduler run pass def simulationPeriodic ( self ): # This method will be called once per scheduler run during simulation pass This class contains a few convenience features on top of the basic Subsystem interface: it automatically calls the register() method in its constructor to register the subsystem with the scheduler (this is necessary for the periodic() method to be called when the scheduler runs), and also implements the Sendable interface so that it can be sent to the dashboard to display/log relevant status information. Advanced users seeking more flexibility may simply create a class that implements the Subsystem interface. Simple Subsystem Example What might a functional subsystem look like in practice? Below is a simple pneumatically-actuated hatch mechanism from the HatchBotTraditional example project ( Java , C++ , Python ): Java 5 package edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems ; 6 7 import static edu.wpi.first.wpilibj.DoubleSolenoid.Value.kForward ; 8 import static edu.wpi.first.wpilibj.DoubleSolenoid.Value.kReverse ; 9 10 import edu.wpi.first.util.sendable.SendableBuilder ; 11 import edu.wpi.first.wpilibj.DoubleSolenoid ; 12 import edu.wpi.first.wpilibj.PneumaticsModuleType ; 13 import edu.wpi.first.wpilibj.examples.hatchbottraditional.Constants.HatchConstants ; 14 import edu.wpi.first.wpilibj2.command.SubsystemBase ; 15 16 /** A hatch mechanism actuated by a single {@link DoubleSolenoid}. */ 17 public class HatchSubsystem extends SubsystemBase { 18 private final DoubleSolenoid m_hatchSolenoid = 19 new DoubleSolenoid ( 20 PneumaticsModuleType . CTREPCM , 21 HatchConstants . kHatchSolenoidPorts [ 0 ] , 22 HatchConstants . kHatchSolenoidPorts [ 1 ] ); 23 24 /** Grabs the hatch. */ 25 public void grabHatch () { 26 m_hatchSolenoid . set ( kForward ); 27 } 28 29 /** Releases the hatch. */ 30 public void releaseHatch () { 31 m_hatchSolenoid . set ( kReverse ); 32 } 33 34 @Override 35 public void initSendable ( SendableBuilder builder ) { 36 super . initSendable ( builder ); 37 // Publish the solenoid state to telemetry. 38 builder . addBooleanProperty ( \"extended\" , () -> m_hatchSolenoid . get () == kForward , null ); 39 } 40 } C++ (Header) 5 #pragma once 6 7 #include 8 #include 9 #include 10 11 #include \"Constants.h\" 12 13 class HatchSubsystem : public frc2 :: SubsystemBase { 14 public : 15 HatchSubsystem (); 16 17 // Subsystem methods go here. 18 19 /** 20 * Grabs the hatch. 21 */ 22 void GrabHatch (); 23 24 /** 25 * Releases the hatch. 26 */ 27 void ReleaseHatch (); 28 29 void InitSendable ( wpi :: SendableBuilder & builder ) override ; 30 31 private : 32 // Components (e.g. motor controllers and sensors) should generally be 33 // declared private and exposed only through public methods. 34 frc :: DoubleSolenoid m_hatchSolenoid ; 35 }; C++ (Source) 5 #include \"subsystems/HatchSubsystem.h\" 6 7 #include 8 9 using namespace HatchConstants ; 10 11 HatchSubsystem :: HatchSubsystem () 12 : m_hatchSolenoid { frc :: PneumaticsModuleType :: CTREPCM , 13 kHatchSolenoidPorts [ 0 ], kHatchSolenoidPorts [ 1 ]} {} 14 15 void HatchSubsystem :: GrabHatch () { 16 m_hatchSolenoid . Set ( frc :: DoubleSolenoid :: kForward ); 17 } 18 19 void HatchSubsystem :: ReleaseHatch () { 20 m_hatchSolenoid . Set ( frc :: DoubleSolenoid :: kReverse ); 21 } 22 23 void HatchSubsystem :: InitSendable ( wpi :: SendableBuilder & builder ) { 24 SubsystemBase :: InitSendable ( builder ); 25 26 // Publish the solenoid state to telemetry. 27 builder . AddBooleanProperty ( 28 \"extended\" , 29 [ this ] { return m_hatchSolenoid . Get () == frc :: DoubleSolenoid :: kForward ; }, 30 nullptr ); 31 } Python 7 import wpilib 8 import commands2 9 10 import constants 11 12 13 class HatchSubsystem ( commands2 . Subsystem ): 14 def __init__ ( self ) -> None : 15 super () . __init__ () 16 17 self . hatchSolenoid = wpilib . DoubleSolenoid ( 18 constants . kHatchSolenoidModule , 19 constants . kHatchSolenoidModuleType , 20 * constants . kHatchSolenoidPorts 21 ) 22 23 def grabHatch ( self ) -> None : 24 \"\"\"Grabs the hatch\"\"\" 25 self . hatchSolenoid . set ( wpilib . DoubleSolenoid . Value . kForward ) 26 27 def releaseHatch ( self ) -> None : 28 \"\"\"Releases the hatch\"\"\" 29 self . hatchSolenoid . set ( wpilib . DoubleSolenoid . Value . kReverse ) Notice that the subsystem hides the presence of the DoubleSolenoid from outside code (it is declared private ), and instead publicly exposes two higher-level, descriptive robot actions: grabHatch() and releaseHatch() . It is extremely important that “implementation details” such as the double solenoid be “hidden” in this manner; this ensures that code outside the subsystem will never cause the solenoid to be in an unexpected state. It also allows the user to change the implementation (for instance, a motor could be used instead of a pneumatic) without any of the code outside of the subsystem having to change with it. Alternatively, instead of writing void public methods that are called from commands, we can define the public methods as factories that return a command. Consider the following from the HatchBotInlined example project ( Java , C++ , Python ): Java 5 package edu.wpi.first.wpilibj.examples.hatchbotinlined.subsystems ; 6 7 import static edu.wpi.first.wpilibj.DoubleSolenoid.Value.kForward ; 8 import static edu.wpi.first.wpilibj.DoubleSolenoid.Value.kReverse ; 9 10 import edu.wpi.first.util.sendable.SendableBuilder ; 11 import edu.wpi.first.wpilibj.DoubleSolenoid ; 12 import edu.wpi.first.wpilibj.PneumaticsModuleType ; 13 import edu.wpi.first.wpilibj.examples.hatchbotinlined.Constants.HatchConstants ; 14 import edu.wpi.first.wpilibj2.command.Command ; 15 import edu.wpi.first.wpilibj2.command.SubsystemBase ; 16 17 /** A hatch mechanism actuated by a single {@link edu.wpi.first.wpilibj.DoubleSolenoid}. */ 18 public class HatchSubsystem extends SubsystemBase { 19 private final DoubleSolenoid m_hatchSolenoid = 20 new DoubleSolenoid ( 21 PneumaticsModuleType . CTREPCM , 22 HatchConstants . kHatchSolenoidPorts [ 0 ] , 23 HatchConstants . kHatchSolenoidPorts [ 1 ] ); 24 25 /** Grabs the hatch. */ 26 public Command grabHatchCommand () { 27 // implicitly require `this` 28 return this . runOnce (() -> m_hatchSolenoid . set ( kForward )); 29 } 30 31 /** Releases the hatch. */ 32 public Command releaseHatchCommand () { 33 // implicitly require `this` 34 return this . runOnce (() -> m_hatchSolenoid . set ( kReverse )); 35 } 36 37 @Override 38 public void initSendable ( SendableBuilder builder ) { 39 super . initSendable ( builder ); 40 // Publish the solenoid state to telemetry. 41 builder . addBooleanProperty ( \"extended\" , () -> m_hatchSolenoid . get () == kForward , null ); 42 } 43 } C++ (Header) 5 #pragma once 6 7 #include 8 #include 9 #include 10 #include 11 12 #include \"Constants.h\" 13 14 class HatchSubsystem : public frc2 :: SubsystemBase { 15 public : 16 HatchSubsystem (); 17 18 // Subsystem methods go here. 19 20 /** 21 * Grabs the hatch. 22 */ 23 frc2 :: CommandPtr GrabHatchCommand (); 24 25 /** 26 * Releases the hatch. 27 */ 28 frc2 :: CommandPtr ReleaseHatchCommand (); 29 30 void InitSendable ( wpi :: SendableBuilder & builder ) override ; 31 32 private : 33 // Components (e.g. motor controllers and sensors) should generally be 34 // declared private and exposed only through public methods. 35 frc :: DoubleSolenoid m_hatchSolenoid ; 36 }; C++ (Source) 5 #include \"subsystems/HatchSubsystem.h\" 6 7 #include 8 9 using namespace HatchConstants ; 10 11 HatchSubsystem :: HatchSubsystem () 12 : m_hatchSolenoid { frc :: PneumaticsModuleType :: CTREPCM , 13 kHatchSolenoidPorts [ 0 ], kHatchSolenoidPorts [ 1 ]} {} 14 15 frc2 :: CommandPtr HatchSubsystem :: GrabHatchCommand () { 16 // implicitly require `this` 17 return this -> RunOnce ( 18 [ this ] { m_hatchSolenoid . Set ( frc :: DoubleSolenoid :: kForward ); }); 19 } 20 21 frc2 :: CommandPtr HatchSubsystem :: ReleaseHatchCommand () { 22 // implicitly require `this` 23 return this -> RunOnce ( 24 [ this ] { m_hatchSolenoid . Set ( frc :: DoubleSolenoid :: kReverse ); }); 25 } 26 27 void HatchSubsystem :: InitSendable ( wpi :: SendableBuilder & builder ) { 28 SubsystemBase :: InitSendable ( builder ); 29 30 // Publish the solenoid state to telemetry. 31 builder . AddBooleanProperty ( 32 \"extended\" , 33 [ this ] { return m_hatchSolenoid . Get () == frc :: DoubleSolenoid :: kForward ; }, 34 nullptr ); 35 } Python 7 import wpilib 8 import commands2 9 import commands2.cmd 10 11 import constants 12 13 14 class HatchSubsystem ( commands2 . Subsystem ): 15 def __init__ ( self ) -> None : 16 super () . __init__ () 17 18 self . hatchSolenoid = wpilib . DoubleSolenoid ( 19 constants . kHatchSolenoidModule , 20 constants . kHatchSolenoidModuleType , 21 * constants . kHatchSolenoidPorts 22 ) 23 24 def grabHatch ( self ) -> commands2 . Command : 25 \"\"\"Grabs the hatch\"\"\" 26 return commands2 . cmd . runOnce ( 27 lambda : self . hatchSolenoid . set ( wpilib . DoubleSolenoid . Value . kForward ), self 28 ) 29 30 def releaseHatch ( self ) -> commands2 . Command : 31 \"\"\"Releases the hatch\"\"\" 32 return commands2 . cmd . runOnce ( 33 lambda : self . hatchSolenoid . set ( wpilib . DoubleSolenoid . Value . kReverse ), self 34 ) Note the qualification of the RunOnce factory used here: this isn’t the static factory in Commands ! Subsystems have similar instance factories that return commands requiring this (Java/C++) or self (Python) subsystem. Here, the Subsystem.runOnce(Runnable) factory ( Java , C++ , Python ) is used. For a comparison between these options, see Instance Command Factory Methods . Periodic Subsystems have a periodic method that is called once every scheduler iteration (usually, once every 20 ms). This method is typically used for telemetry and other periodic actions that do not interfere with whatever command is requiring the subsystem. Java 117 @Override 118 public void periodic () { 119 // Update the odometry in the periodic block 120 m_odometry . update ( 121 Rotation2d . fromDegrees ( getHeading ()), 122 m_leftEncoder . getDistance (), 123 m_rightEncoder . getDistance ()); 124 m_fieldSim . setRobotPose ( getPose ()); 125 } C++ (Header) 30 void Periodic () override ; C++ (Source) 30 void DriveSubsystem::Periodic () { 31 // Implementation of subsystem periodic method goes here. 32 m_odometry . Update ( m_gyro . GetRotation2d (), 33 units :: meter_t { m_leftEncoder . GetDistance ()}, 34 units :: meter_t { m_rightEncoder . GetDistance ()}); 35 m_fieldSim . SetRobotPose ( m_odometry . GetPose ()); 36 } Python def periodic ( self ): #Update the odometry in the periodic block self . odometry . update ( Rotation2d . fromDegrees ( getHeading ()), self . leftEncoder . getDistance (), self . rightEncoder . getDistance ()) self . fieldSim . setRobotPose ( getPose ()) There is also a simulationPeriodic() method that is similar to periodic() except that it is only run during Simulation and can be used to update the state of the robot. Default Commands Note In the C++ command-based library, the CommandScheduler owns the default command object. “Default commands” are commands that run automatically whenever a subsystem is not being used by another command. This can be useful for “background” actions such as controlling the robot drive, or keeping an arm held at a setpoint. Setting a default command for a subsystem is very easy; one simply calls CommandScheduler.getInstance().setDefaultCommand() , or, more simply, the setDefaultCommand() method of the Subsystem interface: JAVA CommandScheduler . getInstance (). setDefaultCommand ( exampleSubsystem , exampleCommand ); C++ CommandScheduler . GetInstance (). SetDefaultCommand ( exampleSubsystem , std :: move ( exampleCommand )); PYTHON CommandScheduler . getInstance () . setDefaultCommand ( exampleSubsystem , exampleCommand ) JAVA exampleSubsystem . setDefaultCommand ( exampleCommand ); C++ exampleSubsystem . SetDefaultCommand ( std :: move ( exampleCommand )); PYTHON exampleSubsystem . setDefaultCommand ( exampleCommand ) Note A command that is assigned as the default command for a subsystem must require that subsystem.",
- "content_preview": "Subsystems Subsystems are the basic unit of robot organization in the command-based paradigm. A subsystem is an abstraction for a collection of robot hardware that operates together as a unit ."
+ "content": "Command Compositions Individual commands are capable of accomplishing a large variety of robot tasks, but the simple three-state format can quickly become cumbersome when more advanced functionality requiring extended sequences of robot tasks or coordination of multiple robot subsystems is required. In order to accomplish this, users are encouraged to use the powerful command composition functionality included in the command-based library. As the name suggests, a command composition is a composition of one or more commands. This allows code to be kept much cleaner and simpler, as the individual component commands may be written independently of the code that combines them, greatly reducing the amount of complexity at any given step of the process. Most importantly, however, command compositions are themselves commands - they extend the Command class. This allows command compositions to be further composed as a recursive composition - that is, a command composition may contain other command compositions as components. This allows very powerful and concise inline expressions: JAVA // Will run fooCommand, and then a race between barCommand and bazCommand button . onTrue ( fooCommand . andThen ( barCommand . raceWith ( bazCommand ))); C++ // Will run fooCommand, and then a race between barCommand and bazCommand button . OnTrue ( std :: move ( fooCommand ). AndThen ( std :: move ( barCommand ). RaceWith ( std :: move ( bazCommand )))); PYTHON # Will run fooCommand, and then a race between barCommand and bazCommand button . onTrue ( fooCommand . andThen ( barCommand . raceWith ( bazCommand ))) As a rule, command compositions require all subsystems their components require, may run when disabled if all their component set runsWhenDisabled as true , and are kCancelIncoming if all their components are kCancelIncoming as well. Command instances that have been passed to a command composition cannot be independently scheduled or passed to a second command composition. Attempting to do so will throw an exception and crash the user program. This is because composition members are run through their encapsulating command composition, and errors could occur if those same command instances were independently scheduled at the same time as the composition - the command would be being run from multiple places at once, and thus could end up with inconsistent internal state, causing unexpected and hard-to-diagnose behavior. The C++ command-based library uses CommandPtr , a class with move-only semantics, so this type of mistake is easier to avoid. Composition Types The command-based library includes various composition types. All of them can be constructed using factories that accept the member commands, and some can also be constructed using decorators: methods that can be called on a command object, which is transformed into a new object that is returned. Important After calling a decorator or being passed to a composition, the command object cannot be reused! Use only the command object returned from the decorator. Repeating The repeatedly() decorator ( Java , C++ , Python ), backed by the RepeatCommand class ( Java , C++ , Python ) restarts the command each time it ends, so that it runs until interrupted. JAVA // Will run forever unless externally interrupted, restarting every time command.isFinished() returns true Command repeats = command . repeatedly (); C++ // Will run forever unless externally interrupted, restarting every time command.IsFinished() returns true frc2 :: CommandPtr repeats = std :: move ( command ). Repeatedly (); PYTHON # Will run forever unless externally interrupted, restarting every time command.IsFinished() returns true repeats = command . repeatedly () Sequence The Sequence factory ( Java , C++ , Python ), backed by the SequentialCommandGroup class ( Java , C++ , Python ), runs a list of commands in sequence: the first command will be executed, then the second, then the third, and so on until the list finishes. The sequential group finishes after the last command in the sequence finishes. It is therefore usually important to ensure that each command in the sequence does actually finish (if a given command does not finish, the next command will never start!). The andThen() ( Java , C++ , Python ) and beforeStarting() ( Java , C++ , Python ) decorators can be used to construct a sequence composition with infix syntax. JAVA fooCommand . andThen ( barCommand ) C++ std :: move ( fooCommand ). AndThen ( std :: move ( barCommand )) PYTHON fooCommand . andThen ( barCommand ) Repeating Sequence As it’s a fairly common combination, the RepeatingSequence factory ( Java , C++ , Python ) creates a Repeating Sequence that runs until interrupted, restarting from the first command each time the last command finishes. Parallel There are three types of parallel compositions, differing based on when the composition finishes: The Parallel factory ( Java , C++ , Python ), backed by the ParallelCommandGroup class ( Java , C++ , Python ), constructs a parallel composition that finishes when all members finish. The alongWith decorator ( Java , C++ , Python ) does the same in infix notation. The Race factory ( Java , C++ , Python ), backed by the ParallelRaceGroup class ( Java , C++ , Python ), constructs a parallel composition that finishes as soon as any member finishes; all other members are interrupted at that point. The raceWith decorator ( Java , C++ , Python ) does the same in infix notation. The Deadline factory ( Java , C++ , Python ), ParallelDeadlineGroup ( Java , C++ , Python ) finishes when a specific command (the “deadline”) ends; all other members still running at that point are interrupted. The deadlineWith decorator ( Java , C++ , Python ) does the same in infix notation; the command the decorator was called on is the deadline. JAVA // Will be a parallel command composition that ends after three seconds with all three commands running their full duration. button . onTrue ( Commands . parallel ( twoSecCommand , oneSecCommand , threeSecCommand )); // Will be a parallel race composition that ends after one second with the two and three second commands getting interrupted. button . onTrue ( Commands . race ( twoSecCommand , oneSecCommand , threeSecCommand )); // Will be a parallel deadline composition that ends after two seconds (the deadline) with the three second command getting interrupted (one second command already finished). button . onTrue ( Commands . deadline ( twoSecCommand , oneSecCommand , threeSecCommand )); C++ // Will be a parallel command composition that ends after three seconds with all three commands running their full duration. button . OnTrue ( frc2 :: cmd :: Parallel ( std :: move ( twoSecCommand ), std :: move ( oneSecCommand ), std :: move ( threeSecCommand ))); // Will be a parallel race composition that ends after one second with the two and three second commands getting interrupted. button . OnTrue ( frc2 :: cmd :: Race ( std :: move ( twoSecCommand ), std :: move ( oneSecCommand ), std :: move ( threeSecCommand ))); // Will be a parallel deadline composition that ends after two seconds (the deadline) with the three second command getting interrupted (one second command already finished). button . OnTrue ( frc2 :: cmd :: Deadline ( std :: move ( twoSecCommand ), std :: move ( oneSecCommand ), std :: move ( threeSecCommand ))); PYTHON # Will be a parallel command composition that ends after three seconds with all three commands running their full duration. button . onTrue ( commands2 . cmd . parallel ( twoSecCommand , oneSecCommand , threeSecCommand )) # Will be a parallel race composition that ends after one second with the two and three second commands getting interrupted. button . onTrue ( commands2 . cmd . race ( twoSecCommand , oneSecCommand , threeSecCommand )) # Will be a parallel deadline composition that ends after two seconds (the deadline) with the three second command getting interrupted (one second command already finished). button . onTrue ( commands2 . cmd . deadline ( twoSecCommand , oneSecCommand , threeSecCommand )) Adding Command End Conditions The until() ( Java , C++ , Python ) decorator composes the command with an additional end condition. Note that the command the decorator was called on will see this end condition as an interruption. JAVA // Will be interrupted if m_limitSwitch.get() returns true button . onTrue ( command . until ( m_limitSwitch :: get )); C++ // Will be interrupted if m_limitSwitch.get() returns true button . OnTrue ( command . Until ([ & m_limitSwitch ] { return m_limitSwitch . Get (); })); PYTHON # Will be interrupted if limitSwitch.get() returns true button . onTrue ( commands2 . cmd . until ( limitSwitch . get )) The withTimeout() decorator ( Java , C++ , Python ) is a specialization of until that uses a timeout as the additional end condition. JAVA // Will time out 5 seconds after being scheduled, and be interrupted button . onTrue ( command . withTimeout ( 5 )); C++ // Will time out 5 seconds after being scheduled, and be interrupted button . OnTrue ( command . WithTimeout ( 5.0 _s )); PYTHON # Will time out 5 seconds after being scheduled, and be interrupted button . onTrue ( commands2 . cmd . withTimeout ( 5.0 )) Adding End Behavior The finallyDo() ( Java , C++ , Python ) decorator composes the command with an a lambda that will be called after the command’s end() method, with the same boolean parameter indicating whether the command finished or was interrupted. The handleInterrupt() ( Java , C++ , Python ) decorator composes the command with an a lambda that will be called only when the command is interrupted. Selecting Compositions Sometimes it’s desired to run a command out of a few options based on sensor feedback or other data known only at runtime. This can be useful for determining an auto routine, or running a different command based on whether a game piece is present or not, and so on. The Select factory ( Java , C++ , Python ), backed by the SelectCommand class ( Java , C++ , Python ), executes one command from a map, based on a selector function called when scheduled. Java 20 public class RobotContainer { 21 // The enum used as keys for selecting the command to run. 22 private enum CommandSelector { 23 ONE , 24 TWO , 25 THREE 26 } 27 28 // An example selector method for the selectcommand. Returns the selector that will select 29 // which command to run. Can base this choice on logical conditions evaluated at runtime. 30 private CommandSelector select () { 31 return CommandSelector . ONE ; 32 } 33 34 // An example selectcommand. Will select from the three commands based on the value returned 35 // by the selector method at runtime. Note that selectcommand works on Object(), so the 36 // selector does not have to be an enum; it could be any desired type (string, integer, 37 // boolean, double...) 38 private final Command m_exampleSelectCommand = 39 new SelectCommand <> ( 40 // Maps selector values to commands 41 Map . ofEntries ( 42 Map . entry ( CommandSelector . ONE , new PrintCommand ( \"Command one was selected!\" )), 43 Map . entry ( CommandSelector . TWO , new PrintCommand ( \"Command two was selected!\" )), 44 Map . entry ( CommandSelector . THREE , new PrintCommand ( \"Command three was selected!\" ))), 45 this :: select ); C++ (Header) 26 // The enum used as keys for selecting the command to run. 27 enum CommandSelector { ONE , TWO , THREE }; 28 29 // An example of how command selector may be used with SendableChooser 30 frc :: SendableChooser < CommandSelector > m_chooser ; 31 32 // The robot's subsystems and commands are defined here... 33 34 // An example selectcommand. Will select from the three commands based on the 35 // value returned by the selector method at runtime. Note that selectcommand 36 // takes a generic type, so the selector does not have to be an enum; it could 37 // be any desired type (string, integer, boolean, double...) 38 frc2 :: CommandPtr m_exampleSelectCommand = frc2 :: cmd :: Select < CommandSelector > ( 39 [ this ] { return m_chooser . GetSelected (); }, 40 // Maps selector values to commands 41 std :: pair { ONE , frc2 :: cmd :: Print ( \"Command one was selected!\" )}, 42 std :: pair { TWO , frc2 :: cmd :: Print ( \"Command two was selected!\" )}, 43 std :: pair { THREE , frc2 :: cmd :: Print ( \"Command three was selected!\" )}); The Either factory ( Java , C++ , Python ), backed by the ConditionalCommand class ( Java , C++ , Python ), is a specialization accepting two commands and a boolean selector function. JAVA // Runs either commandOnTrue or commandOnFalse depending on the value of m_limitSwitch.get() new ConditionalCommand ( commandOnTrue , commandOnFalse , m_limitSwitch :: get ) C++ // Runs either commandOnTrue or commandOnFalse depending on the value of m_limitSwitch.get() frc2 :: ConditionalCommand ( commandOnTrue , commandOnFalse , [ & m_limitSwitch ] { return m_limitSwitch . Get (); }) PYTHON # Runs either commandOnTrue or commandOnFalse depending on the value of limitSwitch.get() ConditionalCommand ( commandOnTrue , commandOnFalse , limitSwitch . get ) The unless() decorator ( Java , C++ , Python ) composes a command with a condition that will prevent it from running. JAVA // Command will only run if the intake is deployed. If the intake gets deployed while the command is running, the command will not stop running button . onTrue ( command . unless (() -> ! intake . isDeployed ())); C++ // Command will only run if the intake is deployed. If the intake gets deployed while the command is running, the command will not stop running button . OnTrue ( command . Unless ([ & intake ] { return ! intake . IsDeployed (); })); PYTHON # Command will only run if the intake is deployed. If the intake gets deployed while the command is running, the command will not stop running button . onTrue ( command . unless ( lambda : not intake . isDeployed ())) ProxyCommand described below also has a constructor overload ( Java , C++ , Python ) that calls a command-returning lambda at schedule-time and runs the returned command by proxy. Scheduling Other Commands By default, composition members are run through the command composition, and are never themselves seen by the scheduler. Accordingly, their requirements are added to the composition’s requirements. While this is usually fine, sometimes it is undesirable for the entire command composition to gain the requirements of a single command. A good solution is to “fork off” from the command composition and schedule that command separately. However, this requires synchronization between the composition and the individually-scheduled command. ProxyCommand ( Java , C++ , Python ), also creatable using the .asProxy() decorator ( Java , C++ , Python ), schedules a command “by proxy”: the command is scheduled when the proxy is scheduled, and the proxy finishes when the command finishes. In the case of “forking off” from a command composition, this allows the composition to track the command’s progress without it being in the composition. Command compositions inherit the union of their compoments’ requirements and requirements are immutable. Therefore, a SequentialCommandGroup ( Java , C++ , Python ) that intakes a game piece, indexes it, aims a shooter, and shoots it would reserve all three subsystems (the intake, indexer, and shooter), precluding any of those subsystems from performing other operations in their “downtime”. If this is not desired, the subsystems that should only be reserved for the composition while they are actively being used by it should have their commands proxied. Warning Do not use ProxyCommand unless you are sure of what you are doing and there is no other way to accomplish your need! Proxying is only intended for use as an escape hatch from command composition requirement unions. Note Because proxied commands still require their subsystem, despite not leaking that requirement to the composition, all of the commands that require a given subsystem must be proxied if one of them is. Otherwise, when the proxied command is scheduled its requirement will conflict with that of the composition, canceling the composition. JAVA // composition requirements are indexer and shooter, intake still reserved during its command but not afterwards Commands . sequence ( intake . intakeGamePiece (). asProxy (), // we want to let the intake intake another game piece while we are processing this one indexer . processGamePiece (), shooter . aimAndShoot () ); C++ // composition requirements are indexer and shooter, intake still reserved during its command but not afterwards frc2 :: cmd :: Sequence ( intake . IntakeGamePiece (). AsProxy (), // we want to let the intake intake another game piece while we are processing this one indexer . ProcessGamePiece (), shooter . AimAndShoot () ); PYTHON # composition requirements are indexer and shooter, intake still reserved during its command but not afterwards commands2 . cmd . sequence ( intake . intakeGamePiece () . asProxy (), # we want to let the intake intake another game piece while we are processing this one indexer . processGamePiece (), shooter . aimAndShoot () ) For cases that don’t need to track the proxied command, ScheduleCommand ( Java , C++ , Python ) schedules a specified command and ends instantly. JAVA // ScheduleCommand ends immediately, so the sequence continues new ScheduleCommand ( Commands . waitSeconds ( 5.0 )) . andThen ( Commands . print ( \"This will be printed immediately!\" )) C++ // ScheduleCommand ends immediately, so the sequence continues frc2 :: ScheduleCommand ( frc2 :: cmd :: Wait ( 5.0 _s )) . AndThen ( frc2 :: cmd :: Print ( \"This will be printed immediately!\" )) PYTHON # ScheduleCommand ends immediately, so the sequence continues ScheduleCommand ( commands2 . cmd . waitSeconds ( 5.0 )) . andThen ( commands2 . cmd . print ( \"This will be printed immediately!\" )) Subclassing Compositions Command compositions can also be written as a constructor-only subclass of the most exterior composition type, passing the composition members to the superclass constructor. Consider the following from the Hatch Bot example project ( Java , C++ ): Java 5 package edu.wpi.first.wpilibj.examples.hatchbottraditional.commands ; 6 7 import edu.wpi.first.wpilibj.examples.hatchbottraditional.Constants.AutoConstants ; 8 import edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems.DriveSubsystem ; 9 import edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems.HatchSubsystem ; 10 import edu.wpi.first.wpilibj2.command.SequentialCommandGroup ; 11 12 /** A complex auto command that drives forward, releases a hatch, and then drives backward. */ 13 public class ComplexAuto extends SequentialCommandGroup { 14 /** 15 * Creates a new ComplexAuto. 16 * 17 * @param drive The drive subsystem this command will run on 18 * @param hatch The hatch subsystem this command will run on 19 */ 20 public ComplexAuto ( DriveSubsystem drive , HatchSubsystem hatch ) { 21 addCommands ( 22 // Drive forward the specified distance 23 new DriveDistance ( 24 AutoConstants . kAutoDriveDistanceInches , AutoConstants . kAutoDriveSpeed , drive ), 25 26 // Release the hatch 27 new ReleaseHatch ( hatch ), 28 29 // Drive backward the specified distance 30 new DriveDistance ( 31 AutoConstants . kAutoBackupDistanceInches , - AutoConstants . kAutoDriveSpeed , drive )); 32 } 33 } C++ (Header) 5 #pragma once 6 7 #include 8 #include 9 10 #include \"Constants.h\" 11 #include \"commands/DriveDistance.h\" 12 #include \"commands/ReleaseHatch.h\" 13 14 /** 15 * A complex auto command that drives forward, releases a hatch, and then drives 16 * backward. 17 */ 18 class ComplexAuto 19 : public frc2 :: CommandHelper < frc2 :: SequentialCommandGroup , ComplexAuto > { 20 public : 21 /** 22 * Creates a new ComplexAuto. 23 * 24 * @param drive The drive subsystem this command will run on 25 * @param hatch The hatch subsystem this command will run on 26 */ 27 ComplexAuto ( DriveSubsystem * drive , HatchSubsystem * hatch ); 28 }; C++ (Source) 5 #include \"commands/ComplexAuto.h\" 6 7 using namespace AutoConstants ; 8 9 ComplexAuto :: ComplexAuto ( DriveSubsystem * drive , HatchSubsystem * hatch ) { 10 AddCommands ( 11 // Drive forward the specified distance 12 DriveDistance ( kAutoDriveDistanceInches , kAutoDriveSpeed , drive ), 13 // Release the hatch 14 ReleaseHatch ( hatch ), 15 // Drive backward the specified distance 16 DriveDistance ( kAutoBackupDistanceInches , - kAutoDriveSpeed , drive )); 17 } Python 7 import commands2 8 9 import constants 10 11 from .drivedistance import DriveDistance 12 from .releasehatch import ReleaseHatch 13 14 from subsystems.drivesubsystem import DriveSubsystem 15 from subsystems.hatchsubsystem import HatchSubsystem 16 17 18 class ComplexAuto ( commands2 . SequentialCommandGroup ): 19 \"\"\" 20 A complex auto command that drives forward, releases a hatch, and then drives backward. 21 \"\"\" 22 23 def __init__ ( self , drive : DriveSubsystem , hatch : HatchSubsystem ): 24 super () . __init__ ( 25 # Drive forward the specified distance 26 DriveDistance ( 27 constants . kAutoDriveDistanceInches , constants . kAutoDriveSpeed , drive 28 ), 29 # Release the hatch 30 ReleaseHatch ( hatch ), 31 # Drive backward the specified distance 32 DriveDistance ( 33 constants . kAutoBackupDistanceInches , - constants . kAutoDriveSpeed , drive 34 ), 35 ) The advantages and disadvantages of this subclassing approach in comparison to others are discussed in Subclassing Command Groups .",
+ "content_preview": "Command Compositions Individual commands are capable of accomplishing a large variety of robot tasks, but the simple three-state format can quickly become cumbersome when more advanced functionality requiring extended sequences of robot tasks or coordination of multiple robot subsystems is..."
},
{
- "url": "https://docs.wpilib.org/en/stable/docs/software/commandbased/subsystems.html?present",
- "title": "Subsystems",
+ "url": "https://docs.wpilib.org/en/stable/docs/software/commandbased/organizing-command-based.html",
+ "title": "Organizing Command",
"section": "Command-Based Programming",
"language": "All",
- "content": "Subsystems Subsystems are the basic unit of robot organization in the command-based paradigm. A subsystem is an abstraction for a collection of robot hardware that operates together as a unit . Subsystems form an encapsulation for this hardware, “hiding” it from the rest of the robot code and restricting access to it except through the subsystem’s public methods. Restricting the access in this way provides a single convenient place for code that might otherwise be duplicated in multiple places (such as scaling motor outputs or checking limit switches) if the subsystem internals were exposed. It also allows changes to the specific details of how the subsystem works (the “implementation”) to be isolated from the rest of robot code, making it far easier to make substantial changes if/when the design constraints change. Subsystems also serve as the backbone of the CommandScheduler ’s resource management system. Commands may declare resource requirements by specifying which subsystems they interact with; the scheduler will never concurrently schedule more than one command that requires a given subsystem. An attempt to schedule a command that requires a subsystem that is already-in-use will either interrupt the currently-running command or be ignored, based on the running command’s Interruption Behavior . Subsystems can be associated with “default commands” that will be automatically scheduled when no other command is currently using the subsystem. This is useful for “background” actions such as controlling the robot drive, keeping an arm held at a setpoint, or stopping motors when the subsystem isn’t used. Similar functionality can be achieved in the subsystem’s periodic() method, which is run once per run of the scheduler; teams should try to be consistent within their codebase about which functionality is achieved through either of these methods. Subsystems are represented in the command-based library by the Subsystem interface ( Java , C++ , Python ). Creating a Subsystem The recommended method to create a subsystem for most users is to subclass the abstract SubsystemBase class in ( Java , C++ ), as seen in the command-based template ( Java , C++ ). In Python, because Python does not have interfaces, the Subsystem class is a concrete class that can be subclassed directly ( Python ). The following example demonstrates how to create a simple subsystem in each of the supported languages: Java 7 import edu.wpi.first.wpilibj2.command.Command ; 8 import edu.wpi.first.wpilibj2.command.SubsystemBase ; 9 10 public class ExampleSubsystem extends SubsystemBase { 11 /** Creates a new ExampleSubsystem. */ 12 public ExampleSubsystem () {} 13 14 /** 15 * Example command factory method. 16 * 17 * @return a command 18 */ 19 public Command exampleMethodCommand () { 20 // Inline construction of command goes here. 21 // Subsystem::RunOnce implicitly requires `this` subsystem. 22 return runOnce ( 23 () -> { 24 /* one-time action goes here */ 25 }); 26 } 27 28 /** 29 * An example method querying a boolean state of the subsystem (for example, a digital sensor). 30 * 31 * @return value of some boolean subsystem state, such as a digital sensor. 32 */ 33 public boolean exampleCondition () { 34 // Query some boolean state, such as a digital sensor. 35 return false ; 36 } 37 38 @Override 39 public void periodic () { 40 // This method will be called once per scheduler run 41 } 42 43 @Override 44 public void simulationPeriodic () { 45 // This method will be called once per scheduler run during simulation 46 } 47 } C++ 5 #pragma once 6 7 #include 8 #include 9 10 class ExampleSubsystem : public frc2 :: SubsystemBase { 11 public : 12 ExampleSubsystem (); 13 14 /** 15 * Example command factory method. 16 */ 17 frc2 :: CommandPtr ExampleMethodCommand (); 18 19 /** 20 * An example method querying a boolean state of the subsystem (for example, a 21 * digital sensor). 22 * 23 * @return value of some boolean subsystem state, such as a digital sensor. 24 */ 25 bool ExampleCondition (); 26 27 /** 28 * Will be called periodically whenever the CommandScheduler runs. 29 */ 30 void Periodic () override ; 31 32 /** 33 * Will be called periodically whenever the CommandScheduler runs during 34 * simulation. 35 */ 36 void SimulationPeriodic () override ; 37 38 private : 39 // Components (e.g. motor controllers and sensors) should generally be 40 // declared private and exposed only through public methods. 41 }; Python from commands2 import Command from commands2 import Subsystem class ExampleSubsystem ( Subsystem ): def __init__ ( self ): \"\"\"Creates a new ExampleSubsystem.\"\"\" super () . __init__ () def exampleMethodCommand () -> Command : \"\"\" Example command factory method. :return a command \"\"\" return self . runOnce ( lambda : # one-time action goes here # ) def exampleCondition ( self ) -> bool : \"\"\" An example method querying a boolean state of the subsystem (for example, a digital sensor). :return value of some boolean subsystem state, such as a digital sensor. \"\"\" #Query some boolean state, such as a digital sensor. return False def periodic ( self ): # This method will be called once per scheduler run pass def simulationPeriodic ( self ): # This method will be called once per scheduler run during simulation pass This class contains a few convenience features on top of the basic Subsystem interface: it automatically calls the register() method in its constructor to register the subsystem with the scheduler (this is necessary for the periodic() method to be called when the scheduler runs), and also implements the Sendable interface so that it can be sent to the dashboard to display/log relevant status information. Advanced users seeking more flexibility may simply create a class that implements the Subsystem interface. Simple Subsystem Example What might a functional subsystem look like in practice? Below is a simple pneumatically-actuated hatch mechanism from the HatchBotTraditional example project ( Java , C++ , Python ): Java 5 package edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems ; 6 7 import static edu.wpi.first.wpilibj.DoubleSolenoid.Value.kForward ; 8 import static edu.wpi.first.wpilibj.DoubleSolenoid.Value.kReverse ; 9 10 import edu.wpi.first.util.sendable.SendableBuilder ; 11 import edu.wpi.first.wpilibj.DoubleSolenoid ; 12 import edu.wpi.first.wpilibj.PneumaticsModuleType ; 13 import edu.wpi.first.wpilibj.examples.hatchbottraditional.Constants.HatchConstants ; 14 import edu.wpi.first.wpilibj2.command.SubsystemBase ; 15 16 /** A hatch mechanism actuated by a single {@link DoubleSolenoid}. */ 17 public class HatchSubsystem extends SubsystemBase { 18 private final DoubleSolenoid m_hatchSolenoid = 19 new DoubleSolenoid ( 20 PneumaticsModuleType . CTREPCM , 21 HatchConstants . kHatchSolenoidPorts [ 0 ] , 22 HatchConstants . kHatchSolenoidPorts [ 1 ] ); 23 24 /** Grabs the hatch. */ 25 public void grabHatch () { 26 m_hatchSolenoid . set ( kForward ); 27 } 28 29 /** Releases the hatch. */ 30 public void releaseHatch () { 31 m_hatchSolenoid . set ( kReverse ); 32 } 33 34 @Override 35 public void initSendable ( SendableBuilder builder ) { 36 super . initSendable ( builder ); 37 // Publish the solenoid state to telemetry. 38 builder . addBooleanProperty ( \"extended\" , () -> m_hatchSolenoid . get () == kForward , null ); 39 } 40 } C++ (Header) 5 #pragma once 6 7 #include 8 #include 9 #include 10 11 #include \"Constants.h\" 12 13 class HatchSubsystem : public frc2 :: SubsystemBase { 14 public : 15 HatchSubsystem (); 16 17 // Subsystem methods go here. 18 19 /** 20 * Grabs the hatch. 21 */ 22 void GrabHatch (); 23 24 /** 25 * Releases the hatch. 26 */ 27 void ReleaseHatch (); 28 29 void InitSendable ( wpi :: SendableBuilder & builder ) override ; 30 31 private : 32 // Components (e.g. motor controllers and sensors) should generally be 33 // declared private and exposed only through public methods. 34 frc :: DoubleSolenoid m_hatchSolenoid ; 35 }; C++ (Source) 5 #include \"subsystems/HatchSubsystem.h\" 6 7 #include 8 9 using namespace HatchConstants ; 10 11 HatchSubsystem :: HatchSubsystem () 12 : m_hatchSolenoid { frc :: PneumaticsModuleType :: CTREPCM , 13 kHatchSolenoidPorts [ 0 ], kHatchSolenoidPorts [ 1 ]} {} 14 15 void HatchSubsystem :: GrabHatch () { 16 m_hatchSolenoid . Set ( frc :: DoubleSolenoid :: kForward ); 17 } 18 19 void HatchSubsystem :: ReleaseHatch () { 20 m_hatchSolenoid . Set ( frc :: DoubleSolenoid :: kReverse ); 21 } 22 23 void HatchSubsystem :: InitSendable ( wpi :: SendableBuilder & builder ) { 24 SubsystemBase :: InitSendable ( builder ); 25 26 // Publish the solenoid state to telemetry. 27 builder . AddBooleanProperty ( 28 \"extended\" , 29 [ this ] { return m_hatchSolenoid . Get () == frc :: DoubleSolenoid :: kForward ; }, 30 nullptr ); 31 } Python 7 import wpilib 8 import commands2 9 10 import constants 11 12 13 class HatchSubsystem ( commands2 . Subsystem ): 14 def __init__ ( self ) -> None : 15 super () . __init__ () 16 17 self . hatchSolenoid = wpilib . DoubleSolenoid ( 18 constants . kHatchSolenoidModule , 19 constants . kHatchSolenoidModuleType , 20 * constants . kHatchSolenoidPorts 21 ) 22 23 def grabHatch ( self ) -> None : 24 \"\"\"Grabs the hatch\"\"\" 25 self . hatchSolenoid . set ( wpilib . DoubleSolenoid . Value . kForward ) 26 27 def releaseHatch ( self ) -> None : 28 \"\"\"Releases the hatch\"\"\" 29 self . hatchSolenoid . set ( wpilib . DoubleSolenoid . Value . kReverse ) Notice that the subsystem hides the presence of the DoubleSolenoid from outside code (it is declared private ), and instead publicly exposes two higher-level, descriptive robot actions: grabHatch() and releaseHatch() . It is extremely important that “implementation details” such as the double solenoid be “hidden” in this manner; this ensures that code outside the subsystem will never cause the solenoid to be in an unexpected state. It also allows the user to change the implementation (for instance, a motor could be used instead of a pneumatic) without any of the code outside of the subsystem having to change with it. Alternatively, instead of writing void public methods that are called from commands, we can define the public methods as factories that return a command. Consider the following from the HatchBotInlined example project ( Java , C++ , Python ): Java 5 package edu.wpi.first.wpilibj.examples.hatchbotinlined.subsystems ; 6 7 import static edu.wpi.first.wpilibj.DoubleSolenoid.Value.kForward ; 8 import static edu.wpi.first.wpilibj.DoubleSolenoid.Value.kReverse ; 9 10 import edu.wpi.first.util.sendable.SendableBuilder ; 11 import edu.wpi.first.wpilibj.DoubleSolenoid ; 12 import edu.wpi.first.wpilibj.PneumaticsModuleType ; 13 import edu.wpi.first.wpilibj.examples.hatchbotinlined.Constants.HatchConstants ; 14 import edu.wpi.first.wpilibj2.command.Command ; 15 import edu.wpi.first.wpilibj2.command.SubsystemBase ; 16 17 /** A hatch mechanism actuated by a single {@link edu.wpi.first.wpilibj.DoubleSolenoid}. */ 18 public class HatchSubsystem extends SubsystemBase { 19 private final DoubleSolenoid m_hatchSolenoid = 20 new DoubleSolenoid ( 21 PneumaticsModuleType . CTREPCM , 22 HatchConstants . kHatchSolenoidPorts [ 0 ] , 23 HatchConstants . kHatchSolenoidPorts [ 1 ] ); 24 25 /** Grabs the hatch. */ 26 public Command grabHatchCommand () { 27 // implicitly require `this` 28 return this . runOnce (() -> m_hatchSolenoid . set ( kForward )); 29 } 30 31 /** Releases the hatch. */ 32 public Command releaseHatchCommand () { 33 // implicitly require `this` 34 return this . runOnce (() -> m_hatchSolenoid . set ( kReverse )); 35 } 36 37 @Override 38 public void initSendable ( SendableBuilder builder ) { 39 super . initSendable ( builder ); 40 // Publish the solenoid state to telemetry. 41 builder . addBooleanProperty ( \"extended\" , () -> m_hatchSolenoid . get () == kForward , null ); 42 } 43 } C++ (Header) 5 #pragma once 6 7 #include 8 #include 9 #include 10 #include 11 12 #include \"Constants.h\" 13 14 class HatchSubsystem : public frc2 :: SubsystemBase { 15 public : 16 HatchSubsystem (); 17 18 // Subsystem methods go here. 19 20 /** 21 * Grabs the hatch. 22 */ 23 frc2 :: CommandPtr GrabHatchCommand (); 24 25 /** 26 * Releases the hatch. 27 */ 28 frc2 :: CommandPtr ReleaseHatchCommand (); 29 30 void InitSendable ( wpi :: SendableBuilder & builder ) override ; 31 32 private : 33 // Components (e.g. motor controllers and sensors) should generally be 34 // declared private and exposed only through public methods. 35 frc :: DoubleSolenoid m_hatchSolenoid ; 36 }; C++ (Source) 5 #include \"subsystems/HatchSubsystem.h\" 6 7 #include 8 9 using namespace HatchConstants ; 10 11 HatchSubsystem :: HatchSubsystem () 12 : m_hatchSolenoid { frc :: PneumaticsModuleType :: CTREPCM , 13 kHatchSolenoidPorts [ 0 ], kHatchSolenoidPorts [ 1 ]} {} 14 15 frc2 :: CommandPtr HatchSubsystem :: GrabHatchCommand () { 16 // implicitly require `this` 17 return this -> RunOnce ( 18 [ this ] { m_hatchSolenoid . Set ( frc :: DoubleSolenoid :: kForward ); }); 19 } 20 21 frc2 :: CommandPtr HatchSubsystem :: ReleaseHatchCommand () { 22 // implicitly require `this` 23 return this -> RunOnce ( 24 [ this ] { m_hatchSolenoid . Set ( frc :: DoubleSolenoid :: kReverse ); }); 25 } 26 27 void HatchSubsystem :: InitSendable ( wpi :: SendableBuilder & builder ) { 28 SubsystemBase :: InitSendable ( builder ); 29 30 // Publish the solenoid state to telemetry. 31 builder . AddBooleanProperty ( 32 \"extended\" , 33 [ this ] { return m_hatchSolenoid . Get () == frc :: DoubleSolenoid :: kForward ; }, 34 nullptr ); 35 } Python 7 import wpilib 8 import commands2 9 import commands2.cmd 10 11 import constants 12 13 14 class HatchSubsystem ( commands2 . Subsystem ): 15 def __init__ ( self ) -> None : 16 super () . __init__ () 17 18 self . hatchSolenoid = wpilib . DoubleSolenoid ( 19 constants . kHatchSolenoidModule , 20 constants . kHatchSolenoidModuleType , 21 * constants . kHatchSolenoidPorts 22 ) 23 24 def grabHatch ( self ) -> commands2 . Command : 25 \"\"\"Grabs the hatch\"\"\" 26 return commands2 . cmd . runOnce ( 27 lambda : self . hatchSolenoid . set ( wpilib . DoubleSolenoid . Value . kForward ), self 28 ) 29 30 def releaseHatch ( self ) -> commands2 . Command : 31 \"\"\"Releases the hatch\"\"\" 32 return commands2 . cmd . runOnce ( 33 lambda : self . hatchSolenoid . set ( wpilib . DoubleSolenoid . Value . kReverse ), self 34 ) Note the qualification of the RunOnce factory used here: this isn’t the static factory in Commands ! Subsystems have similar instance factories that return commands requiring this (Java/C++) or self (Python) subsystem. Here, the Subsystem.runOnce(Runnable) factory ( Java , C++ , Python ) is used. For a comparison between these options, see Instance Command Factory Methods . Periodic Subsystems have a periodic method that is called once every scheduler iteration (usually, once every 20 ms). This method is typically used for telemetry and other periodic actions that do not interfere with whatever command is requiring the subsystem. Java 117 @Override 118 public void periodic () { 119 // Update the odometry in the periodic block 120 m_odometry . update ( 121 Rotation2d . fromDegrees ( getHeading ()), 122 m_leftEncoder . getDistance (), 123 m_rightEncoder . getDistance ()); 124 m_fieldSim . setRobotPose ( getPose ()); 125 } C++ (Header) 30 void Periodic () override ; C++ (Source) 30 void DriveSubsystem::Periodic () { 31 // Implementation of subsystem periodic method goes here. 32 m_odometry . Update ( m_gyro . GetRotation2d (), 33 units :: meter_t { m_leftEncoder . GetDistance ()}, 34 units :: meter_t { m_rightEncoder . GetDistance ()}); 35 m_fieldSim . SetRobotPose ( m_odometry . GetPose ()); 36 } Python def periodic ( self ): #Update the odometry in the periodic block self . odometry . update ( Rotation2d . fromDegrees ( getHeading ()), self . leftEncoder . getDistance (), self . rightEncoder . getDistance ()) self . fieldSim . setRobotPose ( getPose ()) There is also a simulationPeriodic() method that is similar to periodic() except that it is only run during Simulation and can be used to update the state of the robot. Default Commands Note In the C++ command-based library, the CommandScheduler owns the default command object. “Default commands” are commands that run automatically whenever a subsystem is not being used by another command. This can be useful for “background” actions such as controlling the robot drive, or keeping an arm held at a setpoint. Setting a default command for a subsystem is very easy; one simply calls CommandScheduler.getInstance().setDefaultCommand() , or, more simply, the setDefaultCommand() method of the Subsystem interface: JAVA CommandScheduler . getInstance (). setDefaultCommand ( exampleSubsystem , exampleCommand ); C++ CommandScheduler . GetInstance (). SetDefaultCommand ( exampleSubsystem , std :: move ( exampleCommand )); PYTHON CommandScheduler . getInstance () . setDefaultCommand ( exampleSubsystem , exampleCommand ) JAVA exampleSubsystem . setDefaultCommand ( exampleCommand ); C++ exampleSubsystem . SetDefaultCommand ( std :: move ( exampleCommand )); PYTHON exampleSubsystem . setDefaultCommand ( exampleCommand ) Note A command that is assigned as the default command for a subsystem must require that subsystem.",
- "content_preview": "Subsystems Subsystems are the basic unit of robot organization in the command-based paradigm. A subsystem is an abstraction for a collection of robot hardware that operates together as a unit ."
- },
- {
- "url": "https://docs.wpilib.org/en/stable/docs/software/wpilib-tools/robot-simulation/introduction.html",
- "title": "Introduction to Robot Simulation",
- "section": "General",
- "language": "All",
- "content": "Introduction to Robot Simulation Often a team may want to test their code without having an actual robot available. WPILib provides teams with the ability to simulate various robot features using simple gradle commands. Java/C++ Use of the Desktop Simulator requires Desktop Support to be enabled. This can be done by checking the “Enable Desktop Support Checkbox” when creating your robot project or by running “WPILib: Change Desktop Support Enabled Setting” from the Visual Studio Code command palette. Desktop support can also be enabled by manually editing your build.gradle file located at the root of your robot project. Simply change includeDesktopSupport = false to includeDesktopSupport = true Important It is important to note that enabling desktop/simulation support can have unintended consequences. Not all vendors will support this option, and code that uses their libraries may even crash when attempting to run simulation! If at any point in time you want to disable Desktop Support, simply re-run the “WPILib: Change Desktop Support Enabled Setting” from the command palette or change includeDesktopSupport to false in build.gradle. Note C++ robot simulation requires that a native compiler to be installed. For Windows, this would be Visual Studio 2022 version 17.9 or later ( not VS Code), macOS requires Xcode 14 or later , and Linux (Ubuntu) requires the build-essential package. Ensure the Desktop Development with C++ option is checked in the Visual Studio installer for simulation support. Running Robot Simulation Basic robot simulation can be run using VS Code. This can be done by using VS Code’s command palette WPILib: Simulate Robot Code as shown below. The Sim GUI option will be selected by default. This will launch the Simulation GUI . You can also launch simulation without a GUI by unchecking Sim GUI in which case your console output in Visual Studio Code should look like the below. ********** Robot program starting ********** Default disabledInit() method... Override me! Default disabledPeriodic() method... Override me! Default robotPeriodic() method... Override me! If you would would like to prevent the pop up and only use build.gradle to configure your simulation settings you can add the following in your vscode settings.json file. \"wpilib.skipSelectSimulateExtension\" : true Warning You may see a run button next to the WPILib button. This button does not set up simulation appropriately and should not be used. Instead, the menu item shown above WPILib: Simulate Robot Code should be used. Note Simulation can also be run outside of VS Code using ./gradlew simulateJava for Java or ./gradlew simulateNative for C++. Note Some vendors support attaching hardware to your PC and using the hardware in desktop simulation (e.g. CANivore). See vendor documentation for more information about the command WPILib: Hardware Sim Robot Code . Python GUI simulation support is installed by default when you install RobotPy. There is a robotpy subcommand that you can execute to run your code in simulation: Windows py -3 -m robotpy sim macOS python3 -m robotpy sim Linux python3 -m robotpy sim Running Robot Dashboards Shuffleboard, SmartDashboard, Glass, and AdvantageScope can be used with WPILib simulation when they are configured to connect to the local computer (i.e. localhost ). Shuffleboard Shuffleboard is automatically configured to look for a NetworkTables instance from the robotRIO but not from other sources . To connect to a simulation, open Shuffleboard preferences from the File menu and select NetworkTables under Plugins on the left navigation bar. In the Server field, type in the IP address or hostname of the NetworkTables host. For a standard simulation configuration, use localhost . SmartDashboard SmartDashboard is automatically configured to look for a NetworkTables instance from the roboRIO, but not from other sources . To connect to a simulation, open SmartDashboard preferences under the File menu and in the Team Number field, enter the IP address or hostname of the NetworkTables host. For a standard simulation configuration, use localhost . Glass Glass is automatically configured to look for a NetworkTables instance from the roboRIO, but not from other sources . To connect to a simulation, open NetworkTables Settings under the NetworkTables menu and in the Team/IP field, enter the IP address or hostname of the NetworkTables host. For a standard simulation configuration, use localhost . AdvantageScope No configuration is required to connect to a NetworkTables instance running on the local computer. To connect to a simulation, click Connect to Simulator under the File menu or press Ctrl + Shift + K .",
- "content_preview": "Introduction to Robot Simulation Often a team may want to test their code without having an actual robot available. WPILib provides teams with the ability to simulate various robot features using simple gradle commands. Java/C++ Use of the Desktop Simulator requires Desktop Support to be enabled."
+ "content": "Organizing Command-Based Robot Projects As robot code becomes more complicated, navigating, understanding, and maintaining the code takes up more and more time and energy. Making changes to the code often becomes more difficult, sometimes for reasons that have very little to do with the actual complexity of the underlying logic. For a simplified example: putting the logic for many unrelated robot functions into a single 1000-line file makes it difficult to find a specific piece of code within that file, particularly under stress at a competition. But spreading out closely related logic across dozens of tiny files is often just as difficult to navigate. This is not a problem unique to FRC, and in fact, good organization only becomes more and more critical as software projects become bigger and bigger. The “best” organization system is a perennial topic of debate, much like the “best” programming language, but in the end, the choice (in both cases) comes down to the specific task at hand and the programmer (or programmers) implementing said task. Even in the relatively small space of FRC robot programming, there is no right answer. The best choice for a given team will depend on the nature of the specific robot code, team structure, and pure personal preference. This article discusses various facets of command-based robot program design that advanced FRC programmers may want to be aware of when writing code. It is not a prescriptive tutorial, though it presents some recommended best practices. If this level of choice seems daunting, however, many teams have been highly successful while sticking closely to WPILib’s example code and guidelines. However, this discussion may be of interest to intermediate and advanced programmers who want to make their code not only effective, but flexible, easily changeable, and sometimes even beautiful. Why Care About Organization? Good code organization will rarely make or break a team’s competitive ability—but it does mean easier debugging, faster modifications, nicer-looking code, and happier programmers. While it’s impossible to define “good” organization by way of what the code looks like from the inside, it’s easier to define in terms of what the robot’s software looks like from the outside. What Good Organization Looks Like When code is well-designed and well-organized, the code’s internal structure is intuitive and easily comprehensible. Cumbersome boilerplate is minimized, meaning that new robot functionality can often be added with just a few lines of code. When a constant value (such as the speed of the robot’s intake) needs to be changed, it only needs to change in one place. If multiple programmers are working together, they can easily understand each others’ work. Bugs are rare, since it is difficult to accidentally introduce unintended behavior (such as creating a command that does not require necessary subsystems). Implementing more advanced functions like unit tests is easier, since the code is abstracted away from the physical hardware. Programmers are happy (most of the time). What Bad Organization Looks Like Poorly organized code often has internal structure that makes little to no sense, even to whoever wrote it. When functionality has to be added or changed, it often breaks unrelated parts of the robot: adding automatic shooter control might introduce a bug in the climbing sequence for unclear reasons. Alternatively, the organizational framework might be so strict that it’s impossible to implement necessary behavior, requiring nasty hacks or workarounds. Many lines of boilerplate code are needed for simple robot logic. Constants are scattered across the codebase, and changing basic behavior often requires making the same change to many different files. Collaboration among multiple programmers is difficult or impossible. Defining Commands In larger robot codebases, multiple copies of the same command need to be used in many different places. For instance, a command that runs a robot’s intake might be used in teleop, bound to a certain button; as part of a complicated command group for an autonomous routine; and as part of a self-test sequence. As an example, let’s look at some ways to define a simple command that simply runs the robot’s intake forward at full power until canceled. Inline Commands The easiest and most expressive way to do this is with a StartEndCommand : JAVA Command runIntake = Commands . startEnd (() -> intake . set ( 1.0 ), () -> intake . set ( 0.0 ), intake ); C++ frc2 :: CommandPtr runIntake = frc2 :: cmd :: StartEnd ([ & intake ] { intake . Set ( 1.0 ); }, [ & intake ] { intake . Set ( 0.0 ); }, { & intake }); This is sufficient for commands that are only used once. However, for a command like this that might get used in many different autonomous routines and button bindings, inline commands everywhere means a lot of repetitive code: JAVA // RobotContainer.java intakeButton . whileTrue ( Commands . startEnd (() -> intake . set ( 1.0 ), () -> intake . set ( 0.0 ), intake )); Command intakeAndShoot = Commands . startEnd (() -> intake . set ( 1.0 ), () -> intake . set ( 0.0 ), intake ) . alongWith ( new RunShooter ( shooter )); Command autonomousCommand = Commands . sequence ( Commands . startEnd (() -> intake . set ( 1.0 ), () -> intake . set ( 0.0 ), intake ). withTimeout ( 5.0 ), Commands . waitSeconds ( 3.0 ), Commands . startEnd (() -> intake . set ( 1.0 ), () -> intake . set ( 0.0 ), intake ). withTimeout ( 5.0 ) ); C++ intakeButton . WhileTrue ( frc2 :: cmd :: StartEnd ([ & intake ] { intake . Set ( 1.0 ); }, [ & intake ] { intake . Set ( 0.0 ); }, { & intake })); frc2 :: CommandPtr intakeAndShoot = frc2 :: cmd :: StartEnd ([ & intake ] { intake . Set ( 1.0 ); }, [ & intake ] { intake . Set ( 0.0 ); }, { & intake }) . AlongWith ( RunShooter ( & shooter ). ToPtr ()); frc2 :: CommandPtr autonomousCommand = frc2 :: cmd :: Sequence ( frc2 :: cmd :: StartEnd ([ & intake ] { intake . Set ( 1.0 ); }, [ & intake ] { intake . Set ( 0.0 ); }, { & intake }). WithTimeout ( 5.0 _s ), frc2 :: cmd :: Wait ( 3.0 _s ), frc2 :: cmd :: StartEnd ([ & intake ] { intake . Set ( 1.0 ); }, [ & intake ] { intake . Set ( 0.0 ); }, { & intake }). WithTimeout ( 5.0 _s ) ); Creating one StartEndCommand instance and putting it in a variable won’t work here, since once an instance of a command is added to a command group it is effectively “owned” by that command group and cannot be used in any other context. Instance Command Factory Methods One way to solve this quandary is using the “factory method” design pattern: a function that returns a new object every invocation, according to some specification. Using command composition , a factory method can construct a complex command object with merely a few lines of code. For example, a command like the intake-running command is conceptually related to exactly one subsystem: the Intake . As such, it makes sense to put a runIntakeCommand method as an instance method of the Intake class: Note In this document we will name factory methods as lowerCamelCaseCommand , but teams may decide on other conventions. In general, it is recommended to end the method name with Command if it might otherwise be confused with an ordinary method (e.g. intake.run might be the name of a method that simply turns on the intake). JAVA public class Intake extends SubsystemBase { // [code for motor controllers, configuration, etc.] // ... public Command runIntakeCommand () { // implicitly requires `this` return this . startEnd (() -> this . set ( 1.0 ), () -> this . set ( 0.0 )); } } C++ frc2 :: CommandPtr Intake::RunIntakeCommand () { // implicitly requires `this` return this -> StartEnd ([ this ] { this -> Set ( 1.0 ); }, [ this ] { this -> Set ( 0.0 ); }); } Notice how since we are in the Intake class, we no longer refer to intake ; instead, we use the this keyword to refer to the current instance. Since we are inside the Intake class, technically we can access private variables and methods directly from within the runIntakeCommand method, thus not needing intermediary methods. (For example, the runIntakeCommand method can directly interface with the motor controller objects instead of calling set() .) On the other hand, these intermediary methods can reduce code duplication and increase encapsulation. Like many other choices outlined in this document, this tradeoff is a matter of personal preference on a case-by-case basis. Using this new factory method in command groups and button bindings is highly expressive: JAVA intakeButton . whileTrue ( intake . runIntakeCommand ()); Command intakeAndShoot = intake . runIntakeCommand (). alongWith ( new RunShooter ( shooter )); Command autonomousCommand = Commands . sequence ( intake . runIntakeCommand (). withTimeout ( 5.0 ), Commands . waitSeconds ( 3.0 ), intake . runIntakeCommand (). withTimeout ( 5.0 ) ); C++ intakeButton . WhileTrue ( intake . RunIntakeCommand ()); frc2 :: CommandPtr intakeAndShoot = intake . RunIntakeCommand (). AlongWith ( RunShooter ( & shooter ). ToPtr ()); frc2 :: CommandPtr autonomousCommand = frc2 :: cmd :: Sequence ( intake . RunIntakeCommand (). WithTimeout ( 5.0 _s ), frc2 :: cmd :: Wait ( 3.0 _s ), intake . RunIntakeCommand (). WithTimeout ( 5.0 _s ) ); Adding a parameter to the runIntakeCommand method to provide the exact percentage to run the intake is easy and allows for even more flexibility. JAVA public Command runIntakeCommand ( double percent ) { return new StartEndCommand (() -> this . set ( percent ), () -> this . set ( 0.0 ), this ); } C++ frc2 :: CommandPtr Intake::RunIntakeCommand ( double percent ) { // implicitly requires `this` return this -> StartEnd ([ this , percent ] { this -> Set ( percent ); }, [ this ] { this -> Set ( 0.0 ); }); } For instance, this code creates a command group that runs the intake forwards for two seconds, waits for two seconds, and then runs the intake backwards for five seconds. JAVA Command intakeRunSequence = intake . runIntakeCommand ( 1.0 ). withTimeout ( 2.0 ) . andThen ( Commands . waitSeconds ( 2.0 )) . andThen ( intake . runIntakeCommand ( - 1.0 ). withTimeout ( 5.0 )); C++ frc2 :: CommandPtr intakeRunSequence = intake . RunIntakeCommand ( 1.0 ). WithTimeout ( 2.0 _s ) . AndThen ( frc2 :: cmd :: Wait ( 2.0 _s )) . AndThen ( intake . RunIntakeCommand ( -1.0 ). WithTimeout ( 5.0 _s )); This approach is recommended for commands that are conceptually related to only a single subsystem, and is very concise. However, it doesn’t fare well with commands related to more than one subsystem: passing in other subsystem objects is unintuitive and can cause race conditions and circular dependencies, and thus should be avoided. Therefore, this approach is best suited for single-subsystem commands, and should be used only for those cases. Static Command Factories Instance factory methods work great for single-subsystem commands. However, complicated robot actions (like the ones often required during the autonomous period) typically need to coordinate multiple subsystems at once. When we want to define an inline command that uses multiple subsystems, it doesn’t make sense for the command factory to live in any single one of those subsystems. Instead, it can be cleaner to define the command factory methods statically in some external class: Note The sequence and parallel static factories construct sequential and parallel command groups: this is equivalent to the andThen and alongWith decorators, but can be more readable. Their use is a matter of personal preference. JAVA public class AutoRoutines { public static Command driveAndIntake ( Drivetrain drivetrain , Intake intake ) { return Commands . sequence ( Commands . parallel ( drivetrain . driveCommand ( 0.5 , 0.5 ), intake . runIntakeCommand ( 1.0 ) ). withTimeout ( 5.0 ), Commands . parallel ( drivetrain . stopCommand (); intake . stopCommand (); ) ); } } C++ // TODO Non-Static Command Factories If we want to avoid the verbosity of adding required subsystems as parameters to our factory methods, we can instead construct an instance of our AutoRoutines class and inject our subsystems through the constructor: JAVA public class AutoRoutines { private Drivetrain drivetrain ; private Intake intake ; public AutoRoutines ( Drivetrain drivetrain , Intake intake ) { this . drivetrain = drivetrain ; this . intake = intake ; } public Command driveAndIntake () { return Commands . sequence ( Commands . parallel ( drivetrain . driveCommand ( 0.5 , 0.5 ), intake . runIntakeCommand ( 1.0 ) ). withTimeout ( 5.0 ), Commands . parallel ( drivetrain . stopCommand (); intake . stopCommand (); ) ); } public Command driveThenIntake () { return Commands . sequence ( drivetrain . driveCommand ( 0.5 , 0.5 ). withTimeout ( 5.0 ), drivetrain . stopCommand (), intake . runIntakeCommand ( 1.0 ). withTimeout ( 5.0 ), intake . stopCommand () ); } } C++ // TODO Then, elsewhere in our code, we can instantiate an single instance of this class and use it to produce several commands: JAVA AutoRoutines autoRoutines = new AutoRoutines ( this . drivetrain , this . intake ); Command driveAndIntake = autoRoutines . driveAndIntake (); Command driveThenIntake = autoRoutines . driveThenIntake (); Command drivingAndIntakingSequence = Commands . sequence ( autoRoutines . driveAndIntake (), autoRoutines . driveThenIntake () ); C++ // TODO Capturing State in Inline Commands Inline commands are extremely concise and expressive, but do not offer explicit support for commands that have their own internal state (such as a drivetrain trajectory following command, which may encapsulate an entire controller). This is often accomplished by instead writing a Command class, which will be covered later in this article. However, it is still possible to ergonomically write a stateful command composition using inline syntax, so long as we are working within a factory method. To do so, we declare the state as a method local and “capture” it in our inline definition. For example, consider the following instance command factory to turn a drivetrain to a specific angle with a PID controller: Note The Subsystem.run and Subsystem.runOnce factory methods sugar the creation of a RunCommand and an InstantCommand requiring this subsystem. JAVA public Command turnToAngle ( double targetDegrees ) { // Create a controller for the inline command to capture PIDController controller = new PIDController ( Constants . kTurnToAngleP , 0 , 0 ); // We can do whatever configuration we want on the created state before returning from the factory controller . setPositionTolerance ( Constants . kTurnToAngleTolerance ); // Try to turn at a rate proportional to the heading error until we're at the setpoint, then stop return run (() -> arcadeDrive ( 0 , - controller . calculate ( gyro . getHeading (), targetDegrees ))) . until ( controller :: atSetpoint ) . andThen ( runOnce (() -> arcadeDrive ( 0 , 0 ))); } C++ // TODO This pattern works very well in Java so long as the captured state is “effectively final” - i.e., it is never reassigned. This means that we cannot directly define and capture primitive types (e.g. int , double , boolean ) - to circumvent this, we need to wrap any state primitives in a mutable container type (the same way PIDController wraps its internal kP , kI , and kD values). Writing Command Classes Another possible way to define reusable commands is to write a class that represents the command. This is typically done by subclassing either Command or one of the CommandGroup classes. Subclassing Command Returning to our simple intake command from earlier, we could do this by creating a new subclass of Command that implements the necessary initialize and end methods. JAVA public class RunIntakeCommand extends Command { private Intake m_intake ; public RunIntakeCommand ( Intake intake ) { this . m_intake = intake ; addRequirements ( intake ); } @Override public void initialize () { m_intake . set ( 1.0 ); } @Override public void end ( boolean interrupted ) { m_intake . set ( 0.0 ); } // execute() defaults to do nothing // isFinished() defaults to return false } C++ // TODO This, however, is just as cumbersome as the original repetitive code, if not more verbose. The only two lines that really matter in this entire file are the two calls to intake.set() , yet there are over 20 lines of boilerplate code! Not to mention, doing this for a lot of robot actions quickly clutters up a robot project with dozens of small files. Nevertheless, this might feel more “natural,” particularly for programmers who prefer to stick closely to an object-oriented model. This approach should be used for commands with internal state (not subsystem state!), as the class can have fields to manage said state. It may also be more intuitive to write commands with complex logic as classes, especially for those less experienced with command composition. As the command is detached from any specific subsystem class and the required subsystem objects are injected through the constructor, this approach deals well with commands involving multiple subsystems. Subclassing Command Groups If we wish to write composite commands as their own classes, we may write a constructor-only subclass of the most exterior group type. For example, an intake-then-outtake sequence (with single-subsystem commands defined as instance factory methods) can look like this: JAVA public class IntakeThenOuttake extends SequentialCommandGroup { public IntakeThenOuttake ( Intake intake ) { super ( intake . runIntakeCommand ( 1.0 ). withTimeout ( 2.0 ), new WaitCommand ( 2.0 ), intake . runIntakeCommand ( - 1 ). withTimeout ( 5.0 ) ); } } C++ // TODO This is relatively short and minimizes boilerplate. It is also comfortable to use in a purely object-oriented paradigm and may be more acceptable to novice programmers. However, it has some downsides. For one, it is not immediately clear exactly what type of command group this is from the constructor definition: it is better to define this in a more inline and expressive way, particularly when nested command groups start showing up. Additionally, it requires a new file for every single command group, even when the groups are conceptually related. As with factory methods, state can be defined and captured within the command group subclass constructor, if necessary. Summary Approach Primary Use Case Single-subsystem Commands Multi-subsystem Commands Stateful Commands Complex Logic Commands Instance Factory Methods Single-subsystem commands Excels at them No Yes, but must obey capture rules Yes Subclassing Command Stateful commands Very verbose Relatively verbose Excels at them Yes; may be more natural than other approaches Static and Instance Command Factories Multi-subsystem commands Yes Yes Yes, but must obey capture rules Yes Subclassing Command Groups Multi-subsystem command groups Yes Yes Yes, but must obey capture rules Yes",
+ "content_preview": "Organizing Command-Based Robot Projects As robot code becomes more complicated, navigating, understanding, and maintaining the code takes up more and more time and energy."
},
{
- "url": "https://docs.wpilib.org/en/stable/docs/software/vscode-overview/wpilib-commands-vscode.html",
- "title": "WPILib Commands in Visual Studio Code",
- "section": "General",
+ "url": "https://docs.wpilib.org/en/stable/docs/software/commandbased/index.html?present",
+ "title": "Command",
+ "section": "Command-Based Programming",
"language": "Java",
- "content": "WPILib Commands in Visual Studio Code This document contains a complete list of the commands provided by the WPILib VS Code Extension and what they do. To access these commands, press Ctrl+Shift+P to open the Command Palette, then begin typing the command name as shown here to filter the list of commands. Click on the command name to execute it. WPILib: Build Robot Code - Builds open project using GradleRIO WPILib: Create a new project - Create a new robot project WPILib C++: Refresh C++ Intellisense - Force an update to the C++ Intellisense configuration. WPILib C++: Select Current C++ Toolchain - Select the toolchain to use for Intellisense (i.e. desktop vs. roboRIO vs…). This is the same as clicking the current mode in the bottom right status bar. WPILib C++: Select Enabled C++ Intellisense Binary Types - Switch Intellisense between static, shared, and executable WPILib: Cancel currently running tasks - Cancel any tasks the WPILib extension is currently running WPILib: Change Auto Save On Deploy Setting - Change whether files are saved automatically when doing a Deploy. This defaults to Enabled. WPILib: Change Auto Start RioLog on Deploy Setting - Change whether RioLog starts automatically on deploy. This defaults to Enabled. WPILib: Change Desktop Support Enabled Setting - Change whether building robot code on Desktop is enabled. Enable this for test and simulation purposes. This defaults to Desktop Support off. WPILib: Change Language Setting - Change whether the currently open project is C++ or Java. WPILib: Change Run Commands Except Deploy/Debug in Offline Mode Setting - Change whether GradleRIO is running in Online Mode for commands other then deploy/debug (will attempt to automatically pull dependencies from online). Defaults to enabled (online mode). WPILib: Change Run Deploy/Debug Command in Offline Mode Setting - Change whether GradleRIO is running in Online Mode for deploy/debug (will attempt to automatically pull dependencies from online). Defaults to disabled (offline mode). WPILib: Change Select Default Simulate Extension Setting - Change whether simulation extensions are enabled by default (all simulation extensions defined in build.gradle will be enabled) WPILib: Change Skip Tests On Deploy Setting - Change whether to skip tests on deploy. Defaults to disabled (tests are run on deploy) WPILib: Change Stop Simulation on Entry Setting - Change whether to stop robot code on entry when running simulation. Defaults to disabled (don’t stop on entry). WPILib: Change Use WinDbg Preview (From Store) as Windows Debugger Setting - Change whether to use the VS Code debugger or WinDbg Preview (from Windows Store). WPILib: Check for WPILib Updates - Check for an update to the WPILib GradleRIO version for the project. This does not update the Visual Studio Code extension, tools, or offline dependencies. Users are strongly recommended to use the offline wpilib installer WPILib: Debug Robot Code - Build and deploy robot code to roboRIO in debug mode and start debugging WPILib: Deploy Robot Code - Build and deploy robot code to roboRIO WPILib: Hardware Sim Robot Code - This builds the current robot code project on your PC and starts it running in simulation using hardware attached to the comupter rather then pure software simulation. Requires vendor support. WPILib: Import a WPILib 2020-202r Gradle Project - Open a wizard to help you create a new project from a existing VS Code Gradle project from 2020-2022. Further documentation is at importing gradle project WPILib: Install tools from GradleRIO - Install the WPILib Java tools (e.g. SmartDashboard, Shuffleboard, etc.). Note that this is done by default by the offline installer WPILib: Manage Vendor Libraries - Install/update 3rd party libraries WPILib: Open API Documentation - Opens either the WPILib Javadocs or C++ Doxygen documentation WPILib: Open Project Information - Opens a widget with project information (Project version, extension version, etc.) WPILib: Open WPILib Command Palette - This command is used to open a WPILib Command Palette (equivalent of hitting Ctrl + Shift + P and typing WPILib ) WPILib: Open WPILib Help - This opens a simple page which links to the WPILib documentation (this site) WPILib: Reset Ask for WPILib Updates Flag - This will clear the flag on the current project, allowing you to re-prompt to update a project to the latest WPILib version if you previously chose to not update. WPILib: Run a command in Gradle - This lets you run an arbitrary command in the GradleRIO command environment WPILib: run Gradle Clean - Run Gradle Clean to delete build artifacts WPILib: Set Team Number - Used to modify the team number associated with a project. This is only needed if you need to change the team number from the one initially specified when creating the project. WPILib: Set VS Code Java Home to FRC Home - Set the VS Code Java Home variable to point to the Java Home discovered by the FRC extension. This is needed if not using the offline installer to make sure the intellisense settings are in sync with the WPILib build settings. WPILib: Show Log Folder - Shows the folder where the WPILib extension stores internal logs. This may be useful when debugging/reporting an extension issue to the WPILib developers WPILib: Simulate Robot Code - This builds the current robot code project on your PC and starts it running in simulation. This requires Desktop Support to be set to Enabled. WPILib: Start RioLog - This starts the RioLog display used to view console output from a robot program WPILib: Start Tool - This allows you to launch WPILib tools (e.g. SmartDashboard, Shuffleboard, etc.) from inside VS Code WPILib: Test Robot Code - This builds the current robot code project and runs any created tests. This requires Desktop Support to be set to Enabled.",
- "content_preview": "WPILib Commands in Visual Studio Code This document contains a complete list of the commands provided by the WPILib VS Code Extension and what they do. To access these commands, press Ctrl+Shift+P to open the Command Palette, then begin typing the command name as shown here to filter the list of..."
+ "content": "Command-Based Programming This sequence of articles serves as an introduction to and reference for the WPILib command-based framework. For a collection of example projects using the command-based framework, see Command-Based Examples . What Is “Command-Based” Programming? Commands Command Compositions Subsystems Binding Commands to Triggers Structuring a Command-Based Robot Project Organizing Command-Based Robot Projects The Command Scheduler A Technical Discussion on C++ Commands PID Control in Command-based Motion Profiling in Command-based Combining Motion Profiling and PID in Command-Based Passing Functions As Parameters In order to provide a concise inline syntax, the command-based library often accepts functions as parameters of constructors, factories, and decorators. Fortunately, both Java and C++ offer users the ability to pass functions as objects : Method References (Java) In Java, a reference to a function that can be passed as a parameter is called a method reference. The general syntax for a method reference is object::method or Class::staticMethod . Note that no method parameters are included, since the method itself is passed. The method is not being called - it is being passed to another piece of code (in this case, a command) so that that code can call it when needed. For further information on method references, see Method References . Lambda Expressions (Java) While method references work well for passing a function that has already been written, often it is inconvenient/wasteful to write a function solely for the purpose of sending as a method reference, if that function will never be used elsewhere. To avoid this, Java also supports a feature called “lambda expressions.” A lambda expression is an inline method definition - it allows a function to be defined inside of a parameter list . For specifics on how to write Java lambda expressions, see Lambda Expressions in Java . Lambda Expressions (C++) Warning Due to complications in C++ semantics, capturing this in a C++ lambda can cause a null pointer exception if done from a component command of a command composition. Whenever possible, C++ users should capture relevant command members explicitly and by value. For more details, see here . C++ lacks a close equivalent to Java method references - pointers to member functions are generally not directly usable as parameters due to the presence of the implicit this parameter. However, C++ does offer lambda expressions - in addition, the lambda expressions offered by C++ are in many ways more powerful than those in Java. For specifics on how to write C++ lambda expressions, see Lambda Expressions in C++ .",
+ "content_preview": "Command-Based Programming This sequence of articles serves as an introduction to and reference for the WPILib command-based framework. For a collection of example projects using the command-based framework, see Command-Based Examples ."
},
{
- "url": "https://docs.wpilib.org/en/stable/docs/software/wpilib-tools/robot-simulation/simulation-gui.html",
- "title": "Simulation Specific User Interface Elements",
- "section": "General",
- "language": "Java",
- "content": "Simulation Specific User Interface Elements WPILib has extended robot simulation to introduce a graphical user interface (GUI) component. This allows teams to easily visualize their robot’s inputs and outputs. Note The Simulation GUI is very similar in many ways to Glass . Some of the following pages will link to Glass sections that describe elements common to both GUIs. Running the GUI You can simply launch the GUI via the Simulate Robot Code command palette option. And the Sim GUI option should popup in a new dialog and will be selected by default. Press Ok . This will now launch the Simulation GUI. Warning You may see a run button next to the WPILib button. This button does not set up simulation appropriately and should not be used. Instead, the menu item shown above WPILib: Simulate Robot Code should be used. Using the GUI Learning the Layout The following items are shown on the simulation GUI by default: Robot State - This is the robot’s current state or “mode”. You can click on the labels to change mode as you would on the normal Driver Station. Timing - Shows the values of the Robot’s timers and allows the timing to be manipulated. System Joysticks - This is a list of joysticks connected to your system currently. FMS - This is used for simulating many of the common FMS systems. NetworkTables - This shows the data that has been published to NetworkTables. Joysticks - This is joysticks that the robot code can directly pull from. Other Devices - This includes devices that do not fall into any of the other categories, such as the ADXRS450 gyro that is included in the Kit of Parts or third party devices that support simulation. The following items can be added from the Hardware menu, but are not shown by default. Addressable LEDs - This shows LEDs controlled by the AddressableLED Class. Analog Inputs - This includes any devices that would normally use the ANALOG IN connector on the roboRIO, such as any Analog based gyros. DIO - (Digital Input Output) This includes any devices that use the DIO connector on the roboRIO. Encoders - This will show any instantiated devices that extend or use the Encoder class. PDPs - This shows the Power Distribution Panel object. PWM Outputs - This is a list of instantiated PWM devices. This will appear as many devices as you instantiate in robot code, as well as their outputs. Relays - This includes any relay devices. This includes VEX Spike relays. Solenoids - This is a list of “connected” solenoids. When you create a solenoid object and push outputs, these are shown here. Adding a System Joystick to Joysticks To add a joystick from the list of system joysticks, simply click and drag a shown joystick under the “System Joysticks” menu to the “Joysticks” menu”. Note The FRC® Driver Station does special mapping to gamepads connected and the WPILib simulator does not “map” these by default. You can turn on this behavior by pressing the “Map gamepad” toggle underneath the “Joysticks” menu. Using the Keyboard as a Joystick You add a keyboard to the list of system joysticks by clicking and dragging one of the keyboard items (e.g. Keyboard 0) just like a joystick above. To edit the settings of the keyboard go to the DS item in the menu bar then choose Keyboard 0 Settings . This allows you to control which keyboard buttons control which axis. This is a common example of how to make the keyboard similar to a split sticks arcade drive on an Xbox controller (uses axis 1 & 4): Modifying ADXRS450 Inputs Using the ADXRS450 object is a fantastic way to test gyro based outputs. This will show up in the “Other Devices” menu. A drop down menu is then exposed that shows various options such as “Connected”, “Angle”, and “Rate”. All of these values are values that you can change, and that your robot code and use on-the-fly. Determining Simulation from Robot Code In cases where vendor libraries do not compile when running the robot simulation, you can wrap their content with RobotBase.isReal() which returns a boolean . JAVA TalonSRX motorLeft ; TalonSRX motorRight ; public Robot () { if ( RobotBase . isReal ()) { motorLeft = new TalonSRX ( 0 ); motorRight = new TalonSRX ( 1 ); } } Note Reassigning value types in C++ requires move or copy assignment; vendors classes that both do not support the SIM and lack a move or copy assignment operator cannot be worked around with conditional allocation unless a pointer is used, instead of a value type. Changing View Settings The View menu item contains Zoom and Style settings that can be customized. The Zoom option dictates the size of the text in the application whereas the Style option allows you to select between the Classic , Light , and Dark modes. An example of the Dark style setting is below: Clearing Application Data Application data for the Simulation GUI, including widget sizes and positions as well as other custom information for widgets is stored in a imgui.ini file. This file is stored in the root of the project directory that the simulation is run from. The imgui.ini configuration file can simply be deleted to restore the Simulation GUI to a “clean slate”.",
- "content_preview": "Simulation Specific User Interface Elements WPILib has extended robot simulation to introduce a graphical user interface (GUI) component. This allows teams to easily visualize their robot’s inputs and outputs. Note The Simulation GUI is very similar in many ways to Glass ."
+ "url": "https://docs.wpilib.org/en/stable/docs/software/commandbased/profilepid-subsystems-commands.html",
+ "title": "Combining Motion Profiling and PID in Command",
+ "section": "Command-Based Programming",
+ "language": "All",
+ "content": "Combining Motion Profiling and PID in Command-Based Note For a description of the WPILib PID control features used by these command-based wrappers, see PID Control in WPILib . A common FRC® controls solution is to pair a trapezoidal motion profile for setpoint generation with a PID controller for setpoint tracking. To facilitate this, WPILib includes its own ProfiledPIDController class. The following example is from the RapidReactCommandBot example project ( Java , C++ ) and shows how ProfiledPIDController can be used within the command-based framework to turn a drivetrain to a specified angle: Java 5 package edu.wpi.first.wpilibj.examples.rapidreactcommandbot.subsystems ; 6 7 import edu.wpi.first.epilogue.Logged ; 8 import edu.wpi.first.epilogue.NotLogged ; 9 import edu.wpi.first.math.controller.ProfiledPIDController ; 10 import edu.wpi.first.math.controller.SimpleMotorFeedforward ; 11 import edu.wpi.first.math.trajectory.TrapezoidProfile ; 12 import edu.wpi.first.util.sendable.SendableRegistry ; 13 import edu.wpi.first.wpilibj.ADXRS450_Gyro ; 14 import edu.wpi.first.wpilibj.Encoder ; 15 import edu.wpi.first.wpilibj.RobotController ; 16 import edu.wpi.first.wpilibj.drive.DifferentialDrive ; 17 import edu.wpi.first.wpilibj.examples.rapidreactcommandbot.Constants.DriveConstants ; 18 import edu.wpi.first.wpilibj.motorcontrol.PWMSparkMax ; 19 import edu.wpi.first.wpilibj2.command.Command ; 20 import edu.wpi.first.wpilibj2.command.SubsystemBase ; 21 import java.util.function.DoubleSupplier ; 22 23 @Logged 24 public class Drive extends SubsystemBase { 25 // The motors on the left side of the drive. 26 private final PWMSparkMax m_leftLeader = new PWMSparkMax ( DriveConstants . kLeftMotor1Port ); 27 private final PWMSparkMax m_leftFollower = new PWMSparkMax ( DriveConstants . kLeftMotor2Port ); 28 29 // The motors on the right side of the drive. 30 private final PWMSparkMax m_rightLeader = new PWMSparkMax ( DriveConstants . kRightMotor1Port ); 31 private final PWMSparkMax m_rightFollower = new PWMSparkMax ( DriveConstants . kRightMotor2Port ); 32 33 // The robot's drive 34 @NotLogged // Would duplicate motor data, there's no point sending it twice 35 private final DifferentialDrive m_drive = 36 new DifferentialDrive ( m_leftLeader :: set , m_rightLeader :: set ); 37 38 // The left-side drive encoder 39 private final Encoder m_leftEncoder = 40 new Encoder ( 41 DriveConstants . kLeftEncoderPorts [ 0 ] , 42 DriveConstants . kLeftEncoderPorts [ 1 ] , 43 DriveConstants . kLeftEncoderReversed ); 44 45 // The right-side drive encoder 46 private final Encoder m_rightEncoder = 47 new Encoder ( 48 DriveConstants . kRightEncoderPorts [ 0 ] , 49 DriveConstants . kRightEncoderPorts [ 1 ] , 50 DriveConstants . kRightEncoderReversed ); 51 52 private final ADXRS450_Gyro m_gyro = new ADXRS450_Gyro (); 53 private final ProfiledPIDController m_controller = 54 new ProfiledPIDController ( 55 DriveConstants . kTurnP , 56 DriveConstants . kTurnI , 57 DriveConstants . kTurnD , 58 new TrapezoidProfile . Constraints ( 59 DriveConstants . kMaxTurnRateDegPerS , 60 DriveConstants . kMaxTurnAccelerationDegPerSSquared )); 61 private final SimpleMotorFeedforward m_feedforward = 62 new SimpleMotorFeedforward ( 63 DriveConstants . ksVolts , 64 DriveConstants . kvVoltSecondsPerDegree , 65 DriveConstants . kaVoltSecondsSquaredPerDegree ); 66 67 /** Creates a new Drive subsystem. */ 68 public Drive () { 69 SendableRegistry . addChild ( m_drive , m_leftLeader ); 70 SendableRegistry . addChild ( m_drive , m_rightLeader ); 71 72 m_leftLeader . addFollower ( m_leftFollower ); 73 m_rightLeader . addFollower ( m_rightFollower ); 74 75 // We need to invert one side of the drivetrain so that positive voltages 76 // result in both sides moving forward. Depending on how your robot's 77 // gearbox is constructed, you might have to invert the left side instead. 78 m_rightLeader . setInverted ( true ); 79 80 // Sets the distance per pulse for the encoders 81 m_leftEncoder . setDistancePerPulse ( DriveConstants . kEncoderDistancePerPulse ); 82 m_rightEncoder . setDistancePerPulse ( DriveConstants . kEncoderDistancePerPulse ); 83 84 // Set the controller to be continuous (because it is an angle controller) 85 m_controller . enableContinuousInput ( - 180 , 180 ); 86 // Set the controller tolerance - the delta tolerance ensures the robot is stationary at the 87 // setpoint before it is considered as having reached the reference 88 m_controller . setTolerance ( 89 DriveConstants . kTurnToleranceDeg , DriveConstants . kTurnRateToleranceDegPerS ); 90 } 91 92 /** 93 * Returns a command that drives the robot with arcade controls. 94 * 95 * @param fwd the commanded forward movement 96 * @param rot the commanded rotation 97 */ 98 public Command arcadeDriveCommand ( DoubleSupplier fwd , DoubleSupplier rot ) { 99 // A split-stick arcade command, with forward/backward controlled by the left 100 // hand, and turning controlled by the right. 101 return run (() -> m_drive . arcadeDrive ( fwd . getAsDouble (), rot . getAsDouble ())) 102 . withName ( \"arcadeDrive\" ); 103 } 104 105 /** 106 * Returns a command that drives the robot forward a specified distance at a specified speed. 107 * 108 * @param distanceMeters The distance to drive forward in meters 109 * @param speed The fraction of max speed at which to drive 110 */ 111 public Command driveDistanceCommand ( double distanceMeters , double speed ) { 112 return runOnce ( 113 () -> { 114 // Reset encoders at the start of the command 115 m_leftEncoder . reset (); 116 m_rightEncoder . reset (); 117 }) 118 // Drive forward at specified speed 119 . andThen ( run (() -> m_drive . arcadeDrive ( speed , 0 ))) 120 // End command when we've traveled the specified distance 121 . until ( 122 () -> 123 Math . max ( m_leftEncoder . getDistance (), m_rightEncoder . getDistance ()) 124 >= distanceMeters ) 125 // Stop the drive when the command ends 126 . finallyDo ( interrupted -> m_drive . stopMotor ()); 127 } 128 129 /** 130 * Returns a command that turns to robot to the specified angle using a motion profile and PID 131 * controller. 132 * 133 * @param angleDeg The angle to turn to 134 */ 135 public Command turnToAngleCommand ( double angleDeg ) { 136 return startRun ( 137 () -> m_controller . reset ( m_gyro . getRotation2d (). getDegrees ()), 138 () -> 139 m_drive . arcadeDrive ( 140 0 , 141 m_controller . calculate ( m_gyro . getRotation2d (). getDegrees (), angleDeg ) 142 // Divide feedforward voltage by battery voltage to normalize it to [-1, 1] 143 + m_feedforward . calculate ( m_controller . getSetpoint (). velocity ) 144 / RobotController . getBatteryVoltage ())) 145 . until ( m_controller :: atGoal ) 146 . finallyDo (() -> m_drive . arcadeDrive ( 0 , 0 )); 147 } 148 } C++ (Header) 5 #pragma once 6 7 #include 8 9 #include 10 #include 11 #include 12 #include