Skip to content

Commit

Permalink
feat(drivebase): copy phoenix tuner swerve project
Browse files Browse the repository at this point in the history
  • Loading branch information
TheGamer1002 committed Jan 13, 2025
1 parent 8cabfc6 commit 9d49044
Show file tree
Hide file tree
Showing 6 changed files with 1,226 additions and 7 deletions.
66 changes: 59 additions & 7 deletions src/main/java/frc/robot/RobotContainer.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
package frc.robot

import com.ctre.phoenix6.swerve.SwerveDrivetrain.SwerveDriveState
import com.ctre.phoenix6.swerve.SwerveModule
import com.ctre.phoenix6.swerve.SwerveRequest
import edu.wpi.first.math.geometry.Rotation2d
import edu.wpi.first.units.Units
import edu.wpi.first.wpilibj2.command.button.CommandXboxController
import edu.wpi.first.wpilibj2.command.button.Trigger
import edu.wpi.first.wpilibj2.command.sysid.SysIdRoutine
import frc.robot.Constants.OperatorConstants
import frc.robot.commands.Autos
import frc.robot.commands.ExampleCommand
import frc.robot.generated.TunerConstants
import frc.robot.subsystems.CommandSwerveDrivetrain
import frc.robot.subsystems.ExampleSubsystem

/**
Expand All @@ -20,8 +28,24 @@ import frc.robot.subsystems.ExampleSubsystem
*/
object RobotContainer
{
// Replace with CommandPS4Controller or CommandJoystick if needed
private val driverController = CommandXboxController(OperatorConstants.DRIVER_CONTROLLER_PORT)
private val MaxSpeed =
TunerConstants.kSpeedAt12Volts.`in`(Units.MetersPerSecond) // kSpeedAt12Volts desired top speed
private val MaxAngularRate = Units.RotationsPerSecond.of(0.75)
.`in`(Units.RadiansPerSecond) // 3/4 of a rotation per second max angular velocity

/* Setting up bindings for necessary control of the swerve drive platform */
private val drive: SwerveRequest.FieldCentric = SwerveRequest.FieldCentric()
.withDeadband(MaxSpeed * 0.1)
.withRotationalDeadband(MaxAngularRate * 0.1) // Add a 10% deadband
.withDriveRequestType(SwerveModule.DriveRequestType.OpenLoopVoltage) // Use open-loop control for drive motors
private val brake = SwerveRequest.SwerveDriveBrake()
private val point = SwerveRequest.PointWheelsAt()

private val logger = Telemetry(MaxSpeed)

private val joystick = CommandXboxController(OperatorConstants.DRIVER_CONTROLLER_PORT)

val drivetrain: CommandSwerveDrivetrain = TunerConstants.createDrivetrain()

init
{
Expand All @@ -39,11 +63,39 @@ object RobotContainer
*/
private fun configureBindings()
{
// Schedule `ExampleCommand` when `exampleCondition` changes to `true`
Trigger { ExampleSubsystem.exampleCondition() }.onTrue(ExampleCommand())
// Note that X is defined as forward according to WPILib convention,
// and Y is defined as to the left according to WPILib convention.
drivetrain.defaultCommand = drivetrain.applyRequest {
drive.withVelocityX(-joystick.leftY * MaxSpeed) // Drive forward with negative Y (forward)
.withVelocityY(-joystick.leftX * MaxSpeed) // Drive left with negative X (left)
.withRotationalRate(-joystick.rightX * MaxAngularRate)
} // Drive counterclockwise with negative X (left)


joystick.a().whileTrue(drivetrain.applyRequest { brake })
joystick.b().whileTrue(drivetrain.applyRequest {
point.withModuleDirection(
Rotation2d(
-joystick.leftY,
-joystick.leftX
)
)
})

// Run SysId routines when holding back/start and X/Y.
// Note that each routine should be run exactly once in a single log.
joystick.back().and(joystick.y())
.whileTrue(drivetrain.sysIdDynamic(SysIdRoutine.Direction.kForward))
joystick.back().and(joystick.x())
.whileTrue(drivetrain.sysIdDynamic(SysIdRoutine.Direction.kReverse))
joystick.start().and(joystick.y())
.whileTrue(drivetrain.sysIdQuasistatic(SysIdRoutine.Direction.kForward))
joystick.start().and(joystick.x())
.whileTrue(drivetrain.sysIdQuasistatic(SysIdRoutine.Direction.kReverse))

// reset the field-centric heading on left bumper press
joystick.leftBumper().onTrue(drivetrain.runOnce { drivetrain.seedFieldCentric() })

// Schedule `exampleMethodCommand` when the Xbox controller's B button is pressed,
// cancelling on release.
driverController.b().whileTrue(ExampleSubsystem.exampleMethodCommand())
drivetrain.registerTelemetry { state: SwerveDriveState -> logger.telemeterize(state) }
}
}
129 changes: 129 additions & 0 deletions src/main/java/frc/robot/Telemetry.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package frc.robot

import com.ctre.phoenix6.SignalLogger
import com.ctre.phoenix6.swerve.SwerveDrivetrain.SwerveDriveState
import edu.wpi.first.math.geometry.Pose2d
import edu.wpi.first.math.kinematics.ChassisSpeeds
import edu.wpi.first.math.kinematics.SwerveModulePosition
import edu.wpi.first.math.kinematics.SwerveModuleState
import edu.wpi.first.networktables.*
import edu.wpi.first.wpilibj.smartdashboard.Mechanism2d
import edu.wpi.first.wpilibj.smartdashboard.MechanismLigament2d
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard
import edu.wpi.first.wpilibj.util.Color
import edu.wpi.first.wpilibj.util.Color8Bit

class Telemetry(private val MaxSpeed: Double) {
/* What to publish over networktables for telemetry */
private val inst: NetworkTableInstance = NetworkTableInstance.getDefault()

/* Robot swerve drive state */
private val driveStateTable: NetworkTable = inst.getTable("DriveState")
private val drivePose: StructPublisher<Pose2d> =
driveStateTable.getStructTopic("Pose", Pose2d.struct).publish()
private val driveSpeeds: StructPublisher<ChassisSpeeds> =
driveStateTable.getStructTopic("Speeds", ChassisSpeeds.struct).publish()
private val driveModuleStates: StructArrayPublisher<SwerveModuleState> =
driveStateTable.getStructArrayTopic("ModuleStates", SwerveModuleState.struct).publish()
private val driveModuleTargets: StructArrayPublisher<SwerveModuleState> =
driveStateTable.getStructArrayTopic("ModuleTargets", SwerveModuleState.struct).publish()
private val driveModulePositions: StructArrayPublisher<SwerveModulePosition> =
driveStateTable.getStructArrayTopic("ModulePositions", SwerveModulePosition.struct)
.publish()
private val driveTimestamp: DoublePublisher =
driveStateTable.getDoubleTopic("Timestamp").publish()
private val driveOdometryFrequency: DoublePublisher =
driveStateTable.getDoubleTopic("OdometryFrequency").publish()

/* Robot pose for field positioning */
private val table: NetworkTable = inst.getTable("Pose")
private val fieldPub: DoubleArrayPublisher = table.getDoubleArrayTopic("robotPose").publish()
private val fieldTypePub: StringPublisher = table.getStringTopic(".type").publish()

/* Mechanisms to represent the swerve module states */
private val m_moduleMechanisms = arrayOf(
Mechanism2d(1.0, 1.0),
Mechanism2d(1.0, 1.0),
Mechanism2d(1.0, 1.0),
Mechanism2d(1.0, 1.0),
)

/* A direction and length changing ligament for speed representation */
private val m_moduleSpeeds = arrayOf(
m_moduleMechanisms[0].getRoot("RootSpeed", 0.5, 0.5)
.append(MechanismLigament2d("Speed", 0.5, 0.0)),
m_moduleMechanisms[1].getRoot("RootSpeed", 0.5, 0.5)
.append(MechanismLigament2d("Speed", 0.5, 0.0)),
m_moduleMechanisms[2].getRoot("RootSpeed", 0.5, 0.5)
.append(MechanismLigament2d("Speed", 0.5, 0.0)),
m_moduleMechanisms[3].getRoot("RootSpeed", 0.5, 0.5)
.append(MechanismLigament2d("Speed", 0.5, 0.0)),
)

/* A direction changing and length constant ligament for module direction */
private val m_moduleDirections = arrayOf(
m_moduleMechanisms[0].getRoot("RootDirection", 0.5, 0.5)
.append(MechanismLigament2d("Direction", 0.1, 0.0, 0.0, Color8Bit(Color.kWhite))),
m_moduleMechanisms[1].getRoot("RootDirection", 0.5, 0.5)
.append(MechanismLigament2d("Direction", 0.1, 0.0, 0.0, Color8Bit(Color.kWhite))),
m_moduleMechanisms[2].getRoot("RootDirection", 0.5, 0.5)
.append(MechanismLigament2d("Direction", 0.1, 0.0, 0.0, Color8Bit(Color.kWhite))),
m_moduleMechanisms[3].getRoot("RootDirection", 0.5, 0.5)
.append(MechanismLigament2d("Direction", 0.1, 0.0, 0.0, Color8Bit(Color.kWhite))),
)

private val m_poseArray = DoubleArray(3)
private val m_moduleStatesArray = DoubleArray(8)
private val m_moduleTargetsArray = DoubleArray(8)

/**
* Construct a telemetry object, with the specified max speed of the robot
*
* @param maxSpeed Maximum speed in meters per second
*/
init {
SignalLogger.start()
}

/** Accept the swerve drive state and telemeterize it to SmartDashboard and SignalLogger. */
fun telemeterize(state: SwerveDriveState) {
/* Telemeterize the swerve drive state */
drivePose.set(state.Pose)
driveSpeeds.set(state.Speeds)
driveModuleStates.set(state.ModuleStates)
driveModuleTargets.set(state.ModuleTargets)
driveModulePositions.set(state.ModulePositions)
driveTimestamp.set(state.Timestamp)
driveOdometryFrequency.set(1.0 / state.OdometryPeriod)

/* Also write to log file */
m_poseArray[0] = state.Pose.x
m_poseArray[1] = state.Pose.y
m_poseArray[2] = state.Pose.rotation.degrees
for (i in 0..3) {
m_moduleStatesArray[i * 2 + 0] = state.ModuleStates[i].angle.radians
m_moduleStatesArray[i * 2 + 1] = state.ModuleStates[i].speedMetersPerSecond
m_moduleTargetsArray[i * 2 + 0] = state.ModuleTargets[i].angle.radians
m_moduleTargetsArray[i * 2 + 1] = state.ModuleTargets[i].speedMetersPerSecond
}

SignalLogger.writeDoubleArray("DriveState/Pose", m_poseArray)
SignalLogger.writeDoubleArray("DriveState/ModuleStates", m_moduleStatesArray)
SignalLogger.writeDoubleArray("DriveState/ModuleTargets", m_moduleTargetsArray)
SignalLogger.writeDouble("DriveState/OdometryPeriod", state.OdometryPeriod, "seconds")

/* Telemeterize the pose to a Field2d */
fieldTypePub.set("Field2d")
fieldPub.set(m_poseArray)

/* Telemeterize the module states to a Mechanism2d */
for (i in 0..3) {
m_moduleSpeeds[i].setAngle(state.ModuleStates[i].angle)
m_moduleDirections[i].setAngle(state.ModuleStates[i].angle)
m_moduleSpeeds[i].length =
state.ModuleStates[i].speedMetersPerSecond / (2 * MaxSpeed)

SmartDashboard.putData("Module $i", m_moduleMechanisms[i])
}
}
}
Loading

0 comments on commit 9d49044

Please sign in to comment.