15#include <wpi/driverstation/Alliance.hpp>
16#include <wpi/driverstation/MatchState.hpp>
17#include <wpi/math/controller/SimpleMotorFeedforward.hpp>
18#include <wpi/math/geometry/Pose2d.hpp>
19#include <wpi/math/geometry/Rotation2d.hpp>
20#include <wpi/math/geometry/Translation2d.hpp>
21#include <wpi/math/kinematics/ChassisVelocities.hpp>
22#include <wpi/math/util/MathUtil.hpp>
23#include <wpi/units/angle.hpp>
24#include <wpi/units/angular_velocity.hpp>
25#include <wpi/units/length.hpp>
26#include <wpi/units/math.hpp>
27#include <wpi/units/time.hpp>
28#include <wpi/units/velocity.hpp>
36template <
size_t NumModules>
39template <
size_t NumModules = 4>
45 std::function<
double()> y) {
50 std::function<
double()> y, std::function<
double()> rot)
52 m_controllerOmega = std::move(rot);
56 std::function<
double()> y, std::function<
double()> headingX,
57 std::function<
double()> headingY)
68 clone.m_telemetry.settings.reset();
86 m_telemetry.telemetry.reset();
87 if (verbosity == TelemetryVerbosity::NONE) {
88 m_telemetry.settings.reset();
90 m_telemetry.settings.emplace(std::move(name), verbosity);
100 std::optional<std::reference_wrapper<SwerveInputStreamTelemetry<NumModules>>>
GetTelemetry() {
101 if (!m_telemetry.settings) {
104 if (!m_telemetry.telemetry) {
105 m_telemetry.telemetry = std::make_unique<SwerveInputStreamTelemetry<NumModules>>(
106 *
this, m_telemetry.settings->first, m_telemetry.settings->second);
108 return std::ref(*m_telemetry.telemetry);
112 switch (m_currentMode) {
113 case SwerveInputMode::TRANSLATION_ONLY:
114 return "TRANSLATION_ONLY";
115 case SwerveInputMode::ANGULAR_VELOCITY:
116 return "ANGULAR_VELOCITY";
117 case SwerveInputMode::HEADING:
119 case SwerveInputMode::AIM:
127 m_axisDeadband = (deadband == 0.0) ? std::nullopt : std::optional<double>{deadband};
132 m_translationAxisScale = (scale == 0.0) ? std::nullopt : std::optional<double>{scale};
137 m_omegaAxisScale = (scale == 0.0) ? std::nullopt : std::optional<double>{scale};
141 return m_maximumChassisLinearVelocity;
144 m_maximumChassisLinearVelocity = velocity;
148 return m_maximumChassisAngularVelocity;
151 m_maximumChassisAngularVelocity = velocity;
155 return m_translationCube.has_value() && m_translationCube.value()();
158 m_translationCube = enabled ? std::optional<std::function<bool()>>{[] {
return true; }} : std::nullopt;
163 m_omegaCube = enabled ? std::optional<std::function<bool()>>{[] {
return true; }} : std::nullopt;
167 return m_allianceRelative.has_value() && m_allianceRelative.value()();
170 m_allianceRelative = enabled ? std::optional<std::function<bool()>>{[] {
return true; }} : std::nullopt;
175 m_robotRelative = enabled ? std::optional<std::function<bool()>>{[] {
return true; }} : std::nullopt;
179 m_maximumChassisLinearVelocity = velocity;
184 m_maximumChassisAngularVelocity = velocity;
189 m_controllerOmega = std::move(rot);
194 std::function<
double()> headingY) {
196 return wpi::units::radian_t{std::atan2(headingX(), headingY())};
198 m_controllerHeadingX = std::move(headingX);
199 m_controllerHeadingY = std::move(headingY);
204 m_axisDeadband = (deadband == 0.0) ? std::nullopt : std::optional<double>{deadband};
212 m_translationAxisScale = (scale == 0.0) ? std::nullopt : std::optional<double>{scale};
217 m_omegaAxisScale = (scale == 0.0) ? std::nullopt : std::optional<double>{scale};
222 m_headingEnabled = std::move(trigger);
227 m_headingSupplier = std::move(heading);
228 m_controllerHeadingX.reset();
229 m_controllerHeadingY.reset();
234 std::function<
bool()> trigger) {
235 m_aimTarget = std::move(aimTarget);
236 m_aimEnabled = std::move(trigger);
253 m_aimLookahead = lookahead;
263 wpi::math::SimpleMotorFeedforward<wpi::units::radians> feedforward) {
264 m_aimFeedforward = feedforward;
273 std::function<
bool()> enabled) {
274 m_aimHeadingOffset = offset;
275 m_aimHeadingOffsetEnabled = std::move(enabled);
288 if (!m_aimEnabled.has_value() || !m_aimEnabled.value()() || !m_aimGoalHeading.has_value()) {
291 auto currentHeading =
292 wpi::math::Rotation2d{wpi::units::radian_t{m_swerveDrive->
GetGyroAngle()}};
293 return wpi::units::math::abs((m_aimGoalHeading.value() - currentHeading).Radians()) <=
302 auto robot = m_swerveDrive->
GetPose().Translation();
303 if (m_aimLookahead.has_value()) {
305 robot = robot + wpi::math::Translation2d{fieldVelocity.vx * m_aimLookahead.value(),
306 fieldVelocity.vy * m_aimLookahead.value()};
308 return target.Translation() - robot;
316 double omega = RequireRotationPID().Calculate(
317 wpi::math::Rotation2d{wpi::units::radian_t{m_swerveDrive->
GetGyroAngle()}}.Radians().value(),
319 if (m_aimFeedforward.has_value()) {
320 omega += m_aimFeedforward
322 wpi::units::radians_per_second_t{omega})
325 return wpi::units::radians_per_second_t{omega};
329 m_translationOnlyEnabled = std::move(trigger);
334 m_omegaCube = std::move(enabled);
343 m_translationCube = std::move(enabled);
352 m_allianceRelative = std::move(enabled);
361 m_robotRelative = std::move(enabled);
370 std::function<
bool()> enabled) {
371 m_translationHeadingOffset = angle;
372 m_translationHeadingOffsetEnabled = std::move(enabled);
380 wpi::math::ChassisVelocities
Get() {
383 double maxLinear = m_maximumChassisLinearVelocity.value();
384 wpi::units::radians_per_second_t maxAngular = m_maximumChassisAngularVelocity;
386 wpi::math::Translation2d scaledTranslation = ApplyTranslationScalar(
387 ApplyDeadband(m_controllerTranslationX()), ApplyDeadband(m_controllerTranslationY()));
388 scaledTranslation = ApplyTranslationCube(scaledTranslation);
389 scaledTranslation = ApplyAllianceAwareTranslation(scaledTranslation);
391 double vx = scaledTranslation.X().value() * maxLinear;
392 double vy = scaledTranslation.Y().value() * maxLinear;
394 wpi::math::ChassisVelocities speeds{};
396 SwerveInputMode newMode = FindMode();
397 if (m_currentMode != newMode) {
398 TransitionMode(newMode);
402 case SwerveInputMode::TRANSLATION_ONLY: {
403 auto& pid = RequireRotationPID();
404 omega = pid.Calculate(wpi::units::radian_t{m_swerveDrive->
GetGyroAngle()}.value(),
405 m_lockedHeading.value().Radians().value());
408 case SwerveInputMode::ANGULAR_VELOCITY: {
409 omega = ApplyOmegaCube(ApplyRotationalScalar(ApplyDeadband(m_controllerOmega.value()()))) *
413 case SwerveInputMode::HEADING: {
414 auto& pid = RequireRotationPID();
415 omega = pid.Calculate(wpi::units::radian_t{m_swerveDrive->
GetGyroAngle()}.value(),
416 m_headingSupplier.value()().value());
417 if (m_controllerHeadingX.has_value() && m_controllerHeadingY.has_value() &&
418 m_axisDeadband.has_value() &&
419 std::abs(m_controllerHeadingX.value()()) + std::abs(m_controllerHeadingY.value()()) <
420 m_axisDeadband.value()) {
425 case SwerveInputMode::AIM: {
426 auto currentHeading =
427 wpi::math::Rotation2d{wpi::units::radian_t{m_swerveDrive->
GetGyroAngle()}};
428 auto target = currentHeading;
430 if (
auto fieldAngle =
GetTargetVector(m_aimTarget.value()()).Angle()) {
431 target = fieldAngle.value() - m_swerveDrive->
GetPose().Rotation() + currentHeading;
433 if (m_aimHeadingOffset.has_value() && m_aimHeadingOffsetEnabled.has_value() &&
434 m_aimHeadingOffsetEnabled.value()()) {
435 target = target + m_aimHeadingOffset.value();
437 m_aimGoalHeading = target;
443 m_currentMode = newMode;
444 speeds = wpi::math::ChassisVelocities{wpi::units::meters_per_second_t{vx},
445 wpi::units::meters_per_second_t{vy},
446 wpi::units::radians_per_second_t{omega}};
447 auto fieldRelativeSpeeds = ApplyTranslationHeadingOffset(ApplyRobotRelativeTranslation(speeds));
449 telemetry->get().UpdateTelemetry();
451 return fieldRelativeSpeeds;
457 enum class SwerveInputMode {
465 std::function<
double()> y)
466 : m_swerveDrive{&drive},
467 m_controllerTranslationX{std::move(x)},
468 m_controllerTranslationY{std::move(y)} {
470 if (
auto maxLinear = cfg.GetMaximumChassisLinearVelocity()) {
471 m_maximumChassisLinearVelocity = *maxLinear;
473 if (
auto maxAngular = cfg.GetMaximumChassisAngularVelocity()) {
474 m_maximumChassisAngularVelocity = wpi::units::radians_per_second_t{*maxAngular};
478 SwerveDrive<NumModules>* m_swerveDrive;
479 std::function<double()> m_controllerTranslationX;
480 std::function<double()> m_controllerTranslationY;
481 std::optional<std::function<double()>> m_controllerOmega;
482 std::optional<std::function<double()>> m_controllerHeadingX;
483 std::optional<std::function<double()>> m_controllerHeadingY;
484 std::optional<std::function<wpi::units::radian_t()>> m_headingSupplier;
485 std::optional<double> m_axisDeadband;
486 std::optional<double> m_translationAxisScale;
487 std::optional<double> m_omegaAxisScale;
488 std::optional<std::function<wpi::math::Pose2d()>> m_aimTarget;
489 std::optional<std::function<bool()>> m_headingEnabled;
490 std::optional<wpi::math::Rotation2d> m_lockedHeading;
491 std::optional<std::function<bool()>> m_aimEnabled;
492 std::optional<std::function<bool()>> m_translationOnlyEnabled;
493 std::optional<std::function<bool()>> m_translationCube;
494 std::optional<std::function<bool()>> m_omegaCube;
495 std::optional<std::function<bool()>> m_robotRelative;
496 std::optional<std::function<bool()>> m_allianceRelative;
497 std::optional<std::function<bool()>> m_translationHeadingOffsetEnabled;
498 std::optional<wpi::math::Rotation2d> m_translationHeadingOffset;
499 std::optional<wpi::units::second_t> m_aimLookahead;
500 std::optional<wpi::math::SimpleMotorFeedforward<wpi::units::radians>> m_aimFeedforward;
501 std::optional<wpi::math::Rotation2d> m_aimHeadingOffset;
502 std::optional<std::function<bool()>> m_aimHeadingOffsetEnabled;
504 std::optional<wpi::math::Rotation2d> m_aimGoalHeading;
505 SwerveInputMode m_currentMode{SwerveInputMode::ANGULAR_VELOCITY};
506 wpi::units::meters_per_second_t m_maximumChassisLinearVelocity{4.0};
507 wpi::units::radians_per_second_t m_maximumChassisAngularVelocity{2.0 * std::numbers::pi};
514 struct TelemetryHolder {
515 std::optional<std::pair<std::string, TelemetryVerbosity>> settings;
516 std::unique_ptr<SwerveInputStreamTelemetry<NumModules>> telemetry;
518 TelemetryHolder() =
default;
519 TelemetryHolder(
const TelemetryHolder& other) : settings{other.settings} {}
520 TelemetryHolder(TelemetryHolder&& other) noexcept : settings{std::move(other.settings)} {
521 other.settings.reset();
522 other.telemetry.reset();
524 TelemetryHolder& operator=(
const TelemetryHolder& other) {
525 if (
this != &other) {
526 settings = other.settings;
531 TelemetryHolder& operator=(TelemetryHolder&& other)
noexcept {
532 if (
this != &other) {
533 settings = std::move(other.settings);
535 other.settings.reset();
536 other.telemetry.reset();
541 TelemetryHolder m_telemetry;
543 wpi::math::PIDController& RequireRotationPID() {
544 auto pid = m_swerveDrive->GetConfig().GetRotationPID();
546 throw exceptions::SwerveDriveConfigurationException(
547 "No rotation PID controller configured, heading, aim, and translation only control are "
548 "unavailable. Use SwerveDriveConfig::WithRotationController(PIDController) to fix this "
554 SwerveInputMode FindMode() {
555 if (m_translationOnlyEnabled.has_value() && m_translationOnlyEnabled.value()()) {
556 return SwerveInputMode::TRANSLATION_ONLY;
558 if (m_aimEnabled.has_value() && m_aimEnabled.value()()) {
559 if (m_aimTarget.has_value()) {
560 return SwerveInputMode::AIM;
562 std::cerr <<
"[YAMS SwerveInputStream] AIM mode enabled but no target set. Call WithAim() first.\n";
564 if (m_headingEnabled.has_value() && m_headingEnabled.value()()) {
565 if (m_headingSupplier.has_value()) {
566 return SwerveInputMode::HEADING;
568 std::cerr <<
"[YAMS SwerveInputStream] HEADING mode enabled but no heading set. "
569 "Call WithHeading() or WithControllerHeadingAxis() first.\n";
571 if (!m_controllerOmega.has_value()) {
572 std::cerr <<
"[YAMS SwerveInputStream] No rotation axis configured. "
573 "Call WithControllerRotationAxis() or WithControllerHeadingAxis(). "
574 "Falling back to TRANSLATION_ONLY.\n";
575 return SwerveInputMode::TRANSLATION_ONLY;
577 return SwerveInputMode::ANGULAR_VELOCITY;
580 void TransitionMode(SwerveInputMode newMode) {
581 if (m_currentMode == SwerveInputMode::AIM) {
582 m_aimGoalHeading = std::nullopt;
584 switch (m_currentMode) {
585 case SwerveInputMode::TRANSLATION_ONLY:
586 m_lockedHeading = std::nullopt;
588 case SwerveInputMode::HEADING:
589 case SwerveInputMode::AIM:
590 m_swerveDrive->ResetRotationPID();
596 case SwerveInputMode::TRANSLATION_ONLY:
598 wpi::math::Rotation2d{wpi::units::radian_t{m_swerveDrive->GetGyroAngle()}};
599 m_swerveDrive->ResetRotationPID();
601 case SwerveInputMode::HEADING:
602 case SwerveInputMode::AIM:
603 m_swerveDrive->ResetRotationPID();
610 double ApplyDeadband(
double axisValue) {
611 return m_axisDeadband.has_value() ? wpi::math::ApplyDeadband(axisValue, m_axisDeadband.value())
615 double ApplyRotationalScalar(
double axisValue) {
616 return m_omegaAxisScale.has_value() ? axisValue * m_omegaAxisScale.value() : axisValue;
619 wpi::math::Translation2d ApplyTranslationScalar(
double xAxis,
double yAxis) {
620 wpi::math::Translation2d t{wpi::units::meter_t{xAxis}, wpi::units::meter_t{yAxis}};
621 return m_translationAxisScale.has_value()
626 wpi::math::Translation2d ApplyTranslationCube(wpi::math::Translation2d translation) {
627 if (m_translationCube.has_value() && m_translationCube.value()()) {
633 double ApplyOmegaCube(
double rotationAxis) {
634 if (m_omegaCube.has_value() && m_omegaCube.value()()) {
635 return rotationAxis * rotationAxis * rotationAxis;
640 wpi::math::ChassisVelocities ApplyRobotRelativeTranslation(wpi::math::ChassisVelocities speeds) {
641 if (m_robotRelative.has_value() && m_robotRelative.value()()) {
642 return speeds.ToFieldRelative(
643 wpi::math::Rotation2d{wpi::units::radian_t{m_swerveDrive->GetGyroAngle()}});
648 wpi::math::Translation2d ApplyAllianceAwareTranslation(wpi::math::Translation2d translation) {
649 if (m_robotRelative.has_value() && m_robotRelative.value()()) {
652 if (m_allianceRelative.has_value() && m_allianceRelative.value()()) {
653 auto alliance = wpi::MatchState::GetAlliance();
654 if (alliance.has_value() && alliance.value() == wpi::Alliance::RED) {
655 return translation.RotateBy(wpi::math::Rotation2d{wpi::units::degree_t{180.0}});
661 wpi::math::ChassisVelocities ApplyTranslationHeadingOffset(wpi::math::ChassisVelocities speeds) {
662 if (m_translationHeadingOffsetEnabled.has_value() &&
663 m_translationHeadingOffsetEnabled.value()() && m_translationHeadingOffset.has_value()) {
664 wpi::math::Translation2d vec{wpi::units::meter_t{speeds.vx.value()},
665 wpi::units::meter_t{speeds.vy.value()}};
666 auto rotated = vec.RotateBy(m_translationHeadingOffset.value());
667 return wpi::math::ChassisVelocities{wpi::units::meters_per_second_t{rotated.X().value()},
668 wpi::units::meters_per_second_t{rotated.Y().value()},
static wpi::math::Translation2d CubeTranslation(wpi::math::Translation2d translation)
static wpi::math::Translation2d ScaleTranslation(wpi::math::Translation2d translation, double scalar)
motorcontrollers::SmartMotorControllerConfig::TelemetryVerbosity TelemetryVerbosity
Definition SwerveDriveConfig.hpp:40
Definition SwerveDrive.hpp:136
wpi::math::ChassisVelocities GetFieldRelativeSpeed()
Definition SwerveDrive.hpp:617
wpi::units::degree_t GetGyroAngle()
Definition SwerveDrive.hpp:569
wpi::math::Pose2d GetPose()
Definition SwerveDrive.hpp:458
SwerveDriveConfig & GetConfig()
Definition SwerveDrive.hpp:726
wpi::math::ChassisVelocities GetRobotRelativeSpeed()
Definition SwerveDrive.hpp:612
TelemetryVerbosity
Definition SmartMotorControllerConfig.hpp:72
Definition SwerveInputStream.hpp:34