Skip to content

File CommandSwerveDrivetrain.h

File List > include > subsystems > CommandSwerveDrivetrain.h

Go to the documentation of this file

#pragma once

#include "ctre/phoenix6/SignalLogger.hpp"

#include <frc/DriverStation.h>
#include <frc/Notifier.h>
#include <frc2/command/CommandPtr.h>
#include <frc2/command/SubsystemBase.h>
#include <frc2/command/sysid/SysIdRoutine.h>

#include "generated/TunerConstants.h"

using namespace ctre::phoenix6;

namespace subsystems {

class CommandSwerveDrivetrain : public frc2::SubsystemBase, public TunerSwerveDrivetrain {
    static constexpr units::second_t kSimLoopPeriod = 5_ms;
    std::unique_ptr<frc::Notifier> m_simNotifier;
    units::second_t m_lastSimTime;

    /* 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;

    /* Swerve requests to apply during SysId characterization */
    swerve::requests::SysIdSwerveTranslation m_translationCharacterization;
    swerve::requests::SysIdSwerveSteerGains m_steerCharacterization;
    swerve::requests::SysIdSwerveRotation m_rotationCharacterization;

    /* SysId routine for characterizing translation. This is used to find PID gains for the drive motors. */
    frc2::sysid::SysIdRoutine m_sysIdRoutineTranslation{
        frc2::sysid::Config{
            std::nullopt, // Use default ramp rate (1 V/s)
            4_V,          // Reduce dynamic step voltage to 4 V to prevent brownout
            std::nullopt, // Use default timeout (10 s)
            // Log state with SignalLogger class
            [](frc::sysid::State state)
            {
                SignalLogger::WriteString("SysIdTranslation_State", frc::sysid::SysIdRoutineLog::StateEnumToString(state));
            }
        },
        frc2::sysid::Mechanism{
            [this](units::volt_t output) { SetControl(m_translationCharacterization.WithVolts(output)); },
            {},
            this
        }
    };

    /* SysId routine for characterizing steer. This is used to find PID gains for the steer motors. */
    frc2::sysid::SysIdRoutine m_sysIdRoutineSteer{
        frc2::sysid::Config{
            std::nullopt, // Use default ramp rate (1 V/s)
            7_V,          // Use dynamic voltage of 7 V
            std::nullopt, // Use default timeout (10 s)
            // Log state with SignalLogger class
            [](frc::sysid::State state)
            {
                SignalLogger::WriteString("SysIdSteer_State", frc::sysid::SysIdRoutineLog::StateEnumToString(state));
            }
        },
        frc2::sysid::Mechanism{
            [this](units::volt_t output) { SetControl(m_steerCharacterization.WithVolts(output)); },
            {},
            this
        }
    };

    /*
     * SysId routine for characterizing rotation.
     * This is used to find PID gains for the FieldCentricFacingAngle HeadingController.
     * See the documentation of swerve::requests::SysIdSwerveRotation for info on importing the log to SysId.
     */
    frc2::sysid::SysIdRoutine m_sysIdRoutineRotation{
        frc2::sysid::Config{
            /* This is in radians per second², but SysId only supports "volts per second" */
            units::constants::detail::PI_VAL / 6 * (1_V / 1_s),
            /* This is in radians per second, but SysId only supports "volts" */
            units::constants::detail::PI_VAL * 1_V,
            std::nullopt, // Use default timeout (10 s)
            // Log state with SignalLogger class
            [](frc::sysid::State state)
            {
                SignalLogger::WriteString("SysIdRotation_State", frc::sysid::SysIdRoutineLog::StateEnumToString(state));
            }
        },
        frc2::sysid::Mechanism{
            [this](units::volt_t output)
            {
                /* output is actually radians per second, but SysId only supports "volts" */
                SetControl(m_rotationCharacterization.WithRotationalRate(output * (1_rad_per_s / 1_V)));
                /* also log the requested output for SysId */
                SignalLogger::WriteValue("Rotational_Rate", output * (1_rad_per_s / 1_V));
            },
            {},
            this
        }
    };

    /* The SysId routine to test */
    frc2::sysid::SysIdRoutine *m_sysIdRoutineToApply = &m_sysIdRoutineTranslation;

public:
    template <std::same_as<SwerveModuleConstants>... ModuleConstants>
    CommandSwerveDrivetrain(swerve::SwerveDrivetrainConstants const &driveTrainConstants, ModuleConstants const &... modules) :
        TunerSwerveDrivetrain{driveTrainConstants, modules...}
    {
        if (utils::IsSimulation()) {
            StartSimThread();
        }
    }

    template <std::same_as<SwerveModuleConstants>... ModuleConstants>
    CommandSwerveDrivetrain(
        swerve::SwerveDrivetrainConstants const &driveTrainConstants,
        units::hertz_t odometryUpdateFrequency,
        ModuleConstants const &... modules
    ) :
        TunerSwerveDrivetrain{driveTrainConstants, odometryUpdateFrequency, modules...}
    {
        if (utils::IsSimulation()) {
            StartSimThread();
        }
    }

    template <std::same_as<SwerveModuleConstants>... ModuleConstants>
    CommandSwerveDrivetrain(
        swerve::SwerveDrivetrainConstants const &driveTrainConstants,
        units::hertz_t odometryUpdateFrequency,
        std::array<double, 3> const &odometryStandardDeviation,
        std::array<double, 3> const &visionStandardDeviation,
        ModuleConstants const &... modules
    ) :
        TunerSwerveDrivetrain{
            driveTrainConstants, odometryUpdateFrequency,
            odometryStandardDeviation, visionStandardDeviation, modules...
        }
    {
        if (utils::IsSimulation()) {
            StartSimThread();
        }
    }

    template <typename RequestSupplier>
        requires std::is_lvalue_reference_v<std::invoke_result_t<RequestSupplier>> &&
            requires(RequestSupplier req, TunerSwerveDrivetrain &drive) { drive.SetControl(req()); }
    frc2::CommandPtr ApplyRequest(RequestSupplier request)
    {
        return Run([this, request=std::move(request)] {
            return SetControl(request());
        });
    }

    template <typename RequestSupplier>
        requires std::negation_v<std::is_lvalue_reference<std::invoke_result_t<RequestSupplier>>> &&
            requires(RequestSupplier req, TunerSwerveDrivetrain &drive) { drive.SetControl(req()); }
    frc2::CommandPtr ApplyRequest(RequestSupplier request)
    {
        return Run([this, request=std::move(request)] {
            return SetControl(request());
        });
    }

    void Periodic() override;

    frc2::CommandPtr SysIdQuasistatic(frc2::sysid::Direction direction)
    {
        return m_sysIdRoutineToApply->Quasistatic(direction);
    }

    frc2::CommandPtr SysIdDynamic(frc2::sysid::Direction direction)
    {
        return m_sysIdRoutineToApply->Dynamic(direction);
    }

    void AddVisionMeasurement(frc::Pose2d visionRobotPose, units::second_t timestamp) override
    {
        TunerSwerveDrivetrain::AddVisionMeasurement(std::move(visionRobotPose), utils::FPGAToCurrentTime(timestamp));
    }

    void AddVisionMeasurement(
        frc::Pose2d visionRobotPose,
        units::second_t timestamp,
        std::array<double, 3> visionMeasurementStdDevs) override
    {
        TunerSwerveDrivetrain::AddVisionMeasurement(std::move(visionRobotPose), utils::FPGAToCurrentTime(timestamp), visionMeasurementStdDevs);
    }

private:
    void StartSimThread();
};

}