YAMS
Yet Another Mechanism System
Loading...
Searching...
No Matches
SwerveDrive.hpp
Go to the documentation of this file.
1// Copyright (c) 2026 Yet Another Software Suite
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
4#pragma once
5
6#include <cassert>
7#include <cstdio>
8#include <functional>
9#include <optional>
10#include <span>
11#include <stdexcept>
12#include <string>
13#include <utility>
14#include <vector>
15#include <wpi/commands2/CommandPtr.hpp>
16#include <wpi/commands2/Commands.hpp>
17#include <wpi/framework/RobotBase.hpp>
18#include <wpi/math/estimator/SwerveDrivePoseEstimator.hpp>
19#include <wpi/math/geometry/Pose2d.hpp>
20#include <wpi/math/geometry/Rotation2d.hpp>
21#include <wpi/math/geometry/Rotation3d.hpp>
22#include <wpi/math/geometry/Translation2d.hpp>
23#include <wpi/math/geometry/Twist2d.hpp>
24#include <wpi/math/kinematics/ChassisVelocities.hpp>
25#include <wpi/math/kinematics/SwerveDriveKinematics.hpp>
26#include <wpi/math/kinematics/SwerveModulePosition.hpp>
27#include <wpi/math/kinematics/SwerveModuleVelocity.hpp>
28#include <wpi/math/kinematics/struct/ChassisVelocitiesStruct.hpp>
29#include <wpi/nt/NetworkTableInstance.hpp>
30#include <wpi/smartdashboard/Field2d.hpp>
31#include <wpi/system/Timer.hpp>
32#include <wpi/tunables/Tunables.hpp>
33#include <wpi/units/angle.hpp>
34#include <wpi/units/force.hpp>
35#include <wpi/units/length.hpp>
36#include <wpi/units/math.hpp>
37#include <wpi/units/time.hpp>
38#include <wpi/units/velocity.hpp>
39#include <wpi/util/array.hpp>
40
41#include "yams/exceptions.hpp"
48
50
135template <size_t NumModules = 4>
137 public:
144 : m_config{config},
145 m_kinematics{BuildKinematics(*m_config)},
146 m_poseEstimator{m_kinematics, ComputeInitialRotation(*m_config),
147 ComputeInitialPositions(*m_config), m_config->GetInitialPose()} {
148 assert(m_config->GetModules().size() == NumModules &&
149 "Module count in SwerveDriveConfig must equal NumModules template parameter.");
150
151 if (auto optimization = m_config->GetModuleStateOptimization()) {
152 for (auto* module : m_config->GetModules()) {
153 module->GetMutableConfig().WithOptimization(*optimization);
154 }
155 }
156 m_simPose = m_config->GetInitialPose();
157 // Start with the gyro reading the starting pose's heading so field relative driving matches it.
158 ResetOdometry(m_config->GetInitialPose());
159
160 SetupTelemetry();
161
162 m_field2d.SetRobotPose(GetPose());
164 wpi::tunables::Publish("Mechanisms/" + GetName() + "/field", m_field2d);
165 }
166
167 // ---- Drive commands --------------------------------------------------------
168
175 wpi::cmd::CommandPtr Drive(std::function<wpi::math::ChassisVelocities()> robotRelativeSpeeds) {
176 return wpi::cmd::Run(
177 [this, robotRelativeSpeeds] {
178 SetRobotRelativeChassisSpeeds(robotRelativeSpeeds());
179 },
180 {m_config->GetSubsystem()})
181 .WithName("Drive");
182 }
183
193 wpi::cmd::CommandPtr DriveToPose(wpi::math::Pose2d pose) {
194 return wpi::cmd::StartRun(
195 [this] {
198 },
199 [this, pose] { SetRobotRelativeChassisSpeeds(DriveToPoseSetpoint(pose)); },
200 {m_config->GetSubsystem()})
201 .FinallyDo([this] { Stop(); })
202 .WithName("Drive to Pose");
203 }
204
217 wpi::cmd::CommandPtr DriveToPose(wpi::math::Pose2d pose,
218 wpi::units::meter_t translationTolerance,
219 wpi::units::degree_t rotationTolerance) {
220 return wpi::cmd::StartRun(
221 [this] {
224 },
225 [this, pose] { SetRobotRelativeChassisSpeeds(DriveToPoseSetpoint(pose)); },
226 {m_config->GetSubsystem()})
227 .Until([this, pose, translationTolerance, rotationTolerance] {
228 return IsNear(pose, translationTolerance, rotationTolerance);
229 })
230 .FinallyDo([this] { Stop(); })
231 .WithName("Drive to Pose");
232 }
233
242 bool IsNear(wpi::math::Pose2d pose, wpi::units::meter_t translationTolerance,
243 wpi::units::degree_t rotationTolerance) {
244 return GetDistanceFromPose(pose) <= translationTolerance &&
245 wpi::units::math::abs(GetAngleDifferenceFromPose(pose)) <= rotationTolerance;
246 }
247
249 void Stop() { SetRobotRelativeChassisSpeeds(wpi::math::ChassisVelocities{}); }
250
262 wpi::math::ChassisVelocities DriveToPoseSetpoint(wpi::math::Pose2d targetPose) {
263 return DriveToPoseSetpoint(targetPose, GetPose(),
264 wpi::math::Rotation2d{wpi::units::radian_t{GetGyroAngle()}});
265 }
266
278 wpi::math::ChassisVelocities DriveToPoseSetpoint(wpi::math::Pose2d targetPose,
279 wpi::math::Pose2d currentPose,
280 wpi::math::Rotation2d heading) {
281 auto rotationPIDRef = m_config->GetRotationPID();
282 if (!rotationPIDRef) {
284 "No rotation PID controller configured, cannot drive to pose. Use "
285 "SwerveDriveConfig::WithRotationController(PIDController) to fix this error.");
286 }
287 auto translationPIDRef = m_config->GetTranslationPID();
288 if (!translationPIDRef) {
290 "No translation PID controller configured, cannot drive to pose. Use "
291 "SwerveDriveConfig::WithTranslationController(PIDController) to fix this error.");
292 }
293 auto& rotationPID = rotationPIDRef->get();
294 auto& translationPID = translationPIDRef->get();
295 // Plain field-frame translation delta (not RelativeTo/operator-, which expresses the result
296 // in targetPose's rotated frame and would skew the commanded direction whenever targetPose's
297 // heading is non-zero).
298 auto toTarget = targetPose.Translation() - currentPose.Translation();
299 double distance = toTarget.Norm().value();
300 // The controller sees the distance as its measurement with a setpoint of 0, so its output is
301 // negative while there is distance left: the speed toward the target is the negative of it.
302 double speed = -translationPID.Calculate(distance, 0.0);
303 // Drive at that speed along the unit vector toward the target.
304 double vx = distance > 1e-9 ? toTarget.X().value() / distance * speed : 0.0;
305 double vy = distance > 1e-9 ? toTarget.Y().value() / distance * speed : 0.0;
306 return (wpi::math::ChassisVelocities{
307 wpi::units::meters_per_second_t{vx}, wpi::units::meters_per_second_t{vy},
308 wpi::units::radians_per_second_t{
309 rotationPID.Calculate(currentPose.Rotation().Radians().value(),
310 targetPose.Rotation().Radians().value())}})
311 .ToRobotRelative(heading);
312 }
313
314 // ---- Core drive methods ---------------------------------------------------
315
321 void SetSwerveModuleStates(wpi::util::array<wpi::math::SwerveModuleVelocity, NumModules> states) {
322 for (size_t i = 0; i < NumModules; ++i) {
323 m_desiredModuleStates[i] = m_config->GetModules()[i]->SetSwerveModuleState(states[i]);
324 }
325 }
326
333 void SetSwerveModuleStates(wpi::util::array<wpi::math::SwerveModuleVelocity, NumModules> states,
334 std::span<const wpi::units::newton_t> feedforwardForces) {
335 for (size_t i = 0; i < NumModules; ++i) {
336 m_desiredModuleStates[i] =
337 m_config->GetModules()[i]->SetSwerveModuleState(states[i], feedforwardForces[i]);
338 }
339 }
340
347 wpi::util::array<wpi::math::SwerveModuleVelocity, NumModules>
348 GetStateFromRobotRelativeChassisSpeeds(wpi::math::ChassisVelocities robotRelativeSpeeds) {
349 robotRelativeSpeeds = m_config->OptimizeRobotRelativeChassisSpeeds(robotRelativeSpeeds);
350 auto cor = m_config->GetCenterOfRotation();
351 auto states = cor ? m_kinematics.ToSwerveModuleVelocities(robotRelativeSpeeds, *cor)
352 : m_kinematics.ToSwerveModuleVelocities(robotRelativeSpeeds);
353 // Scale the wheels down together when one is asked to go faster than it can.
354 if (auto maxModuleSpeed = m_config->GetMaximumModuleLinearVelocity()) {
355 auto maxLinear = m_config->GetMaximumChassisLinearVelocity();
356 auto maxAngular = m_config->GetMaximumChassisAngularVelocity();
357 if (maxLinear && maxAngular) {
358 states = wpi::math::SwerveDriveKinematics<NumModules>::DesaturateWheelVelocities(
359 states, robotRelativeSpeeds, *maxModuleSpeed, *maxLinear,
360 wpi::units::radians_per_second_t{*maxAngular});
361 } else {
362 states = wpi::math::SwerveDriveKinematics<NumModules>::DesaturateWheelVelocities(
363 states, *maxModuleSpeed);
364 }
365 }
366 return states;
367 }
368
375 wpi::math::ChassisVelocities GetRobotRelativeChassisSpeedsFromState(
376 const wpi::util::array<wpi::math::SwerveModuleVelocity, NumModules>& states) {
377 return m_kinematics.ToChassisVelocities(states);
378 }
379
385 void SetRobotRelativeChassisSpeeds(wpi::math::ChassisVelocities robotRelativeSpeeds) {
386 m_desiredChassisSpeeds = robotRelativeSpeeds;
388 }
389
398 void SetRobotRelativeChassisSpeeds(wpi::math::ChassisVelocities robotRelativeSpeeds,
399 std::span<const wpi::units::newton_t> feedforwardForces) {
400 m_desiredChassisSpeeds = robotRelativeSpeeds;
401 auto states = GetStateFromRobotRelativeChassisSpeeds(robotRelativeSpeeds);
402 if (feedforwardForces.empty()) {
403 SetSwerveModuleStates(states);
404 return;
405 }
406 if (feedforwardForces.size() != NumModules) {
407 throw std::invalid_argument("feedforwardForces must be empty or have one entry per module (" +
408 std::to_string(NumModules) + "), in FL, FR, BL, BR order.");
409 }
410 SetSwerveModuleStates(states, feedforwardForces);
411 }
412
418 void SetFieldRelativeChassisSpeeds(wpi::math::ChassisVelocities fieldRelativeSpeeds) {
420 (fieldRelativeSpeeds)
421 .ToRobotRelative(wpi::math::Rotation2d{wpi::units::radian_t{GetGyroAngle()}}));
422 }
423
427 void LockPose() {
428 // Turn each wheel toward the robot center without driving it. SwerveModule::SetAzimuthAngle
429 // skips the velocity deadband, which would otherwise hold these zero speed states where the
430 // wheels are.
431 for (size_t i = 0; i < NumModules; ++i) {
432 auto* module = m_config->GetModules()[i];
433 m_desiredModuleStates[i] = module->SetAzimuthAngle(
434 module->GetConfig().GetLocation()->Angle().value_or(wpi::math::Rotation2d{}));
435 }
436 m_desiredChassisSpeeds = wpi::math::ChassisVelocities{};
437 }
438
446 void SetAzimuthAngles(wpi::math::Rotation2d angle) {
447 for (size_t i = 0; i < NumModules; ++i) {
448 m_desiredModuleStates[i] = m_config->GetModules()[i]->SetAzimuthAngle(angle);
449 }
450 m_desiredChassisSpeeds = wpi::math::ChassisVelocities{};
451 }
452
453 // ---- Odometry & pose -------------------------------------------------------
454
458 wpi::math::Pose2d GetPose() { return m_poseEstimator.GetEstimatedPosition(); }
459
467 void ResetOdometry(wpi::math::Pose2d pose) {
468 SetGyroAngle(pose.Rotation().Degrees());
469 m_poseEstimator.ResetPosition(wpi::math::Rotation2d{wpi::units::radian_t{GetGyroAngle()}},
470 GetModulePositions(), pose);
471 m_desiredChassisSpeeds = wpi::math::ChassisVelocities{};
472 m_desiredModuleStates = m_kinematics.ToSwerveModuleVelocities(wpi::math::ChassisVelocities{});
473 m_simPose = pose;
474 }
475
479 void ZeroGyro() {
480 // ResetOdometry also sets the gyro to read the new heading.
481 ResetOdometry(wpi::math::Pose2d{GetPose().Translation(), wpi::math::Rotation2d{}});
482 }
483
490 void AddVisionMeasurement(wpi::math::Pose2d robotPose, wpi::units::second_t timestamp) {
491 m_poseEstimator.AddVisionMeasurement(robotPose, timestamp);
492 }
493
501 void AddVisionMeasurement(wpi::math::Pose2d robotPose, wpi::units::second_t timestamp,
502 const wpi::util::array<double, 3>& stdDevs) {
503 m_poseEstimator.AddVisionMeasurement(robotPose, timestamp, stdDevs);
504 }
505
506 // ---- Telemetry & simulation -----------------------------------------------
507
514 UpdatePoseEstimator();
515 auto pose = GetPose();
516 auto currentStates = GetModuleStates();
517
518 m_swerveTelemetry->Publish(this);
519
520 for (auto* mod : m_config->GetModules()) {
521 mod->UpdateTelemetry();
522 }
523 m_telemetry.UpdateLoopTime();
524
525 m_field2d.SetRobotPose(pose);
526 std::vector<wpi::math::Pose2d> modulePoses;
527 modulePoses.reserve(NumModules);
528 for (size_t i = 0; i < NumModules; ++i) {
529 auto* mod = m_config->GetModules()[i];
530 auto location = *mod->GetConfig().GetLocation();
531 auto moduleTranslation = pose.Translation() + location.RotateBy(pose.Rotation());
532 auto moduleHeading = pose.Rotation() + currentStates[i].angle;
533 modulePoses.push_back(wpi::math::Pose2d{moduleTranslation, moduleHeading});
534 }
535 m_field2d.GetObject("modules")->SetPoses(modulePoses);
536 }
537
545 void SimIterate() {
546 if (!m_simTimer.IsRunning()) m_simTimer.Start();
547 for (auto* mod : m_config->GetModules()) {
548 mod->SimIterate();
549 }
550 auto desired = m_kinematics.ToChassisVelocities(m_desiredModuleStates);
551
552 auto dt = m_simTimer.Get();
553 wpi::math::Twist2d twist{desired.vx * dt, desired.vy * dt, desired.omega * dt};
554 m_simPose = m_simPose + twist.Exp();
555
556 auto speeds = m_kinematics.ToChassisVelocities(GetModuleStates());
557 m_simGyroAngle +=
558 wpi::units::degree_t{wpi::units::degrees_per_second_t{speeds.omega}.value() * dt.value()};
559 m_simTimer.Reset();
560 }
561
562 // ---- Queries ---------------------------------------------------------------
563
569 wpi::units::degree_t GetGyroAngle() {
570 if (wpi::RobotBase::IsSimulation()) return m_simGyroAngle;
571 return m_config->GetGyroAngle();
572 }
573
580 wpi::math::Rotation3d GetGyroRotation3d() {
581 if (wpi::RobotBase::IsSimulation()) {
582 return wpi::math::Rotation3d{wpi::units::radian_t{0}, wpi::units::radian_t{0},
583 wpi::units::radian_t{m_simGyroAngle}};
584 }
585 return m_config->GetGyroRotation3d();
586 }
587
593
598 if (m_swerveTelemetry) m_swerveTelemetry->ApplyTuningValues(this);
599 }
600
609 wpi::math::Pose2d GetSimPose() { return m_simPose; }
610
612 wpi::math::ChassisVelocities GetRobotRelativeSpeed() {
613 return m_kinematics.ToChassisVelocities(GetModuleStates());
614 }
615
617 wpi::math::ChassisVelocities GetFieldRelativeSpeed() {
618 return GetRobotRelativeSpeed().ToFieldRelative(
619 wpi::math::Rotation2d{wpi::units::radian_t{GetGyroAngle()}});
620 }
621
623 wpi::util::array<wpi::math::SwerveModulePosition, NumModules> GetModulePositions() {
624 wpi::util::array<wpi::math::SwerveModulePosition, NumModules> positions{wpi::util::empty_array};
625 for (size_t i = 0; i < NumModules; ++i) {
626 positions[i] = m_config->GetModules()[i]->GetPosition();
627 }
628 return positions;
629 }
630
632 wpi::util::array<wpi::math::SwerveModuleVelocity, NumModules> GetModuleStates() {
633 wpi::util::array<wpi::math::SwerveModuleVelocity, NumModules> states{wpi::util::empty_array};
634 for (size_t i = 0; i < NumModules; ++i) {
635 states[i] = m_config->GetModules()[i]->GetState();
636 }
637 return states;
638 }
639
646 wpi::units::meter_t GetDistanceFromPose(wpi::math::Pose2d pose) {
647 return wpi::units::meter_t{GetPose().Translation().Distance(pose.Translation())};
648 }
649
656 wpi::units::degree_t GetAngleDifferenceFromPose(wpi::math::Pose2d pose) {
657 return (GetPose() - pose).Rotation().Degrees();
658 }
659
666 std::optional<SwerveModule*> GetModule(const std::string& name) {
667 for (auto* mod : m_config->GetModules()) {
668 if (mod->GetName() == name) return mod;
669 }
670 return std::nullopt;
671 }
672
675 if (auto pid = m_config->GetRotationPID()) pid->get().Reset();
676 }
677
680 if (auto pid = m_config->GetTranslationPID()) pid->get().Reset();
681 }
682
688 void SetRotationPID(wpi::math::PIDController controller) {
689 auto currentRotationPID = m_config->GetRotationPID();
690 if (!currentRotationPID || currentRotationPID->get().GetP() != controller.GetP() ||
691 currentRotationPID->get().GetI() != controller.GetI() ||
692 currentRotationPID->get().GetD() != controller.GetD()) {
693 controller.Reset();
694 m_config->WithRotationController(std::move(controller));
695 }
696 }
697
703 void SetTranslationPID(wpi::math::PIDController controller) {
704 auto currentTranslationPID = m_config->GetTranslationPID();
705 if (!currentTranslationPID || currentTranslationPID->get().GetP() != controller.GetP() ||
706 currentTranslationPID->get().GetI() != controller.GetI() ||
707 currentTranslationPID->get().GetD() != controller.GetD()) {
708 controller.Reset();
709 m_config->WithTranslationController(std::move(controller));
710 }
711 }
712
718 void SetVisionMeasurementStdDevs(const wpi::util::array<double, 3>& stdDevs) {
719 m_poseEstimator.SetVisionMeasurementStdDevs(stdDevs);
720 }
721
723 std::string GetName() const { return m_config->GetTelemetryName(); }
724
726 SwerveDriveConfig& GetConfig() { return *m_config; }
727
729 wpi::math::SwerveDriveKinematics<NumModules>& GetKinematics() { return m_kinematics; }
730
735 wpi::Field2d& GetField2d() { return m_field2d; }
736
747 wpi::math::ChassisVelocities GetDesiredChassisSpeeds() { return m_desiredChassisSpeeds; }
748
755 wpi::util::array<wpi::math::SwerveModuleVelocity, NumModules> GetDesiredModuleStates() {
756 return m_desiredModuleStates;
757 }
758
759 private:
760 SwerveDriveConfig* m_config{nullptr};
761 wpi::math::SwerveDriveKinematics<NumModules> m_kinematics;
762 wpi::math::SwerveDrivePoseEstimator<NumModules> m_poseEstimator;
763
765 std::optional<telemetry::SwerveDriveTelemetry> m_swerveTelemetry;
766 std::optional<wpi::cmd::CommandPtr> m_driveToPoseTuningCommand;
767
768 wpi::Field2d m_field2d;
769 wpi::Timer m_simTimer;
770 wpi::units::degree_t m_simGyroAngle{0};
771 wpi::math::Pose2d m_simPose;
772
773 wpi::util::array<wpi::math::SwerveModuleVelocity, NumModules> m_desiredModuleStates{
774 wpi::util::empty_array};
775 wpi::math::ChassisVelocities m_desiredChassisSpeeds{};
776
782 void SetupTelemetry() {
784 telemetry::SwerveDriveTelemetryConfig telemetryCfg;
785 if (auto specified = m_config->GetSwerveDriveTelemetryConfig()) {
786 telemetryCfg = std::move(*specified);
787 } else {
788 telemetryCfg.WithTelemetryVerbosity(
789 m_config->GetTelemetryVerbosity().value_or(TelemetryVerbosity::HIGH));
790 }
791
792 if (auto dataLogName = telemetryCfg.GetDataLogName()) {
793 m_telemetry.SetupTelemetry(GetName(), *dataLogName);
794 } else {
795 m_telemetry.SetupTelemetry(GetName());
796 }
797
798 m_swerveTelemetry.emplace(std::move(telemetryCfg));
799 m_swerveTelemetry->SetupTelemetry(this);
800
801 // Drive to pose tuning needs both controllers, so only offer it when they are configured.
802 if (m_config->GetTranslationPID() && m_config->GetRotationPID()) {
803 auto tuningCommand = wpi::cmd::StartRun(
804 [this] {
805 std::puts(
806 "================= Starting SwerveDrive.driveToPoseTuning() =================\n");
809 },
810 [this] { m_swerveTelemetry->ApplyTuningValues(this); });
811 // Stop any tuning voltage when the command ends.
812 m_driveToPoseTuningCommand.emplace(
813 std::move(tuningCommand).FinallyDo([this] { m_swerveTelemetry->StopTuning(this); }));
815 wpi::tunables::Publish("Mechanisms/" + GetName() + "/tuning",
816 *m_driveToPoseTuningCommand->get());
817 }
818 }
819
820 void UpdatePoseEstimator() {
821 m_poseEstimator.Update(wpi::math::Rotation2d{wpi::units::radian_t{GetGyroAngle()}},
823 }
824
825 static wpi::math::SwerveDriveKinematics<NumModules> BuildKinematics(
826 const SwerveDriveConfig& config) {
827 assert(config.GetModules().size() == NumModules);
828 wpi::util::array<wpi::math::Translation2d, NumModules> locations{wpi::util::empty_array};
829 for (size_t i = 0; i < NumModules; ++i) {
830 locations[i] = *config.GetModules()[i]->GetConfig().GetLocation();
831 }
832 return wpi::math::SwerveDriveKinematics<NumModules>{locations};
833 }
834
839 void SetGyroAngle(wpi::units::degree_t heading) {
840 if (wpi::RobotBase::IsSimulation()) {
841 m_simGyroAngle = heading;
842 return;
843 }
844 m_config->WithGyroOffset(GetGyroAngle() + m_config->GetGyroOffset() - heading);
845 }
846
847 static wpi::math::Rotation2d ComputeInitialRotation(const SwerveDriveConfig& config) {
848 if (wpi::RobotBase::IsSimulation()) return wpi::math::Rotation2d{};
849 return wpi::math::Rotation2d{wpi::units::radian_t{config.GetGyroAngle()}};
850 }
851
852 static wpi::util::array<wpi::math::SwerveModulePosition, NumModules> ComputeInitialPositions(
853 const SwerveDriveConfig& config) {
854 wpi::util::array<wpi::math::SwerveModulePosition, NumModules> positions{wpi::util::empty_array};
855 for (size_t i = 0; i < NumModules; ++i) {
856 positions[i] = config.GetModules()[i]->GetPosition();
857 }
858 return positions;
859 }
860};
861
862} // namespace yams::mechanisms::swerve
863
865
866// SwerveDriveTelemetry::SetupTelemetry()/Publish()/ApplyTuningValues() are templated on the
867// SwerveDrive's module count so that SwerveDriveTelemetry itself need not be a template. Their
868// bodies live here after SwerveDrive<NumModules> is fully defined above because this is the
869// only place both SwerveDriveTelemetry's private members and SwerveDrive<NumModules>'s interface
870// are simultaneously visible.
871
872template <std::size_t NumModules>
874 auto driveName = drive->GetName();
875 for (auto* module : drive->GetConfig().GetModules()) {
876 module->SetupTelemetry(driveName);
877 }
878
879 auto inst = wpi::nt::NetworkTableInstance::GetDefault();
880 m_dataTable = inst.GetTable("Mechanisms")->GetSubTable(driveName);
881 m_tuningTable = inst.GetTable("Tuning")->GetSubTable(driveName);
882
883 bool nt4Enabled = m_config.GetNT4Enabled();
884 auto dataLogName = m_config.GetDataLogName();
885
886 {
887 // Only publish the drive to pose gains and auto-align fields for controllers that exist.
888 auto translationPID = drive->GetConfig().GetTranslationPID();
889 auto rotationPID = drive->GetConfig().GetRotationPID();
890 auto& doubleFields = m_config.GetDoubleFields();
891 if (translationPID) {
892 doubleFields.at(DoubleTelemetryField::TranslationP)
893 .SetDefaultValue(translationPID->get().GetP());
894 doubleFields.at(DoubleTelemetryField::TranslationI)
895 .SetDefaultValue(translationPID->get().GetI());
896 doubleFields.at(DoubleTelemetryField::TranslationD)
897 .SetDefaultValue(translationPID->get().GetD());
898 } else {
899 doubleFields.at(DoubleTelemetryField::TranslationP).Disable();
900 doubleFields.at(DoubleTelemetryField::TranslationI).Disable();
901 doubleFields.at(DoubleTelemetryField::TranslationD).Disable();
902 }
903 if (rotationPID) {
904 doubleFields.at(DoubleTelemetryField::RotationP).SetDefaultValue(rotationPID->get().GetP());
905 doubleFields.at(DoubleTelemetryField::RotationI).SetDefaultValue(rotationPID->get().GetI());
906 doubleFields.at(DoubleTelemetryField::RotationD).SetDefaultValue(rotationPID->get().GetD());
907 } else {
908 doubleFields.at(DoubleTelemetryField::RotationP).Disable();
909 doubleFields.at(DoubleTelemetryField::RotationI).Disable();
910 doubleFields.at(DoubleTelemetryField::RotationD).Disable();
911 }
912 // Auto-align drives to a pose, which needs both controllers.
913 if (!translationPID || !rotationPID) {
914 doubleFields.at(DoubleTelemetryField::AutoAlignPoseX).Disable();
915 doubleFields.at(DoubleTelemetryField::AutoAlignPoseY).Disable();
916 doubleFields.at(DoubleTelemetryField::AutoAlignPoseRotation).Disable();
917 m_config.GetBoolFields().at(BooleanTelemetryField::AutoAlignEnabled).Disable();
918 }
919
920 auto& modules = drive->GetConfig().GetModules();
921 if (!modules.empty()) {
922 auto* driveMotor = modules[0]->GetDriveMotorController();
923 auto driveGains =
924 driveMotor->GetConfig().GetSlotGains(driveMotor->GetClosedLoopControllerSlot());
925 m_config.GetDoubleFields()
926 .at(DoubleTelemetryField::ModulesDriveP)
927 .SetDefaultValue(driveGains.kP);
928 m_config.GetDoubleFields()
929 .at(DoubleTelemetryField::ModulesDriveI)
930 .SetDefaultValue(driveGains.kI);
931 m_config.GetDoubleFields()
932 .at(DoubleTelemetryField::ModulesDriveD)
933 .SetDefaultValue(driveGains.kD);
934 m_config.GetDoubleFields()
935 .at(DoubleTelemetryField::ModulesDriveKs)
936 .SetDefaultValue(driveGains.kS);
937 m_config.GetDoubleFields()
938 .at(DoubleTelemetryField::ModulesDriveKv)
939 .SetDefaultValue(driveGains.kV);
940 m_config.GetDoubleFields()
941 .at(DoubleTelemetryField::ModulesDriveKa)
942 .SetDefaultValue(driveGains.kA);
943
944 auto* azimuthMotor = modules[0]->GetAzimuthMotorController();
945 auto azimuthGains =
946 azimuthMotor->GetConfig().GetSlotGains(azimuthMotor->GetClosedLoopControllerSlot());
947 m_config.GetDoubleFields()
948 .at(DoubleTelemetryField::ModulesAzimuthP)
949 .SetDefaultValue(azimuthGains.kP);
950 m_config.GetDoubleFields()
951 .at(DoubleTelemetryField::ModulesAzimuthI)
952 .SetDefaultValue(azimuthGains.kI);
953 m_config.GetDoubleFields()
954 .at(DoubleTelemetryField::ModulesAzimuthD)
955 .SetDefaultValue(azimuthGains.kD);
956 m_config.GetDoubleFields()
957 .at(DoubleTelemetryField::ModulesAzimuthKs)
958 .SetDefaultValue(azimuthGains.kS);
959 m_config.GetDoubleFields()
960 .at(DoubleTelemetryField::ModulesAzimuthKv)
961 .SetDefaultValue(azimuthGains.kV);
962 m_config.GetDoubleFields()
963 .at(DoubleTelemetryField::ModulesAzimuthKa)
964 .SetDefaultValue(azimuthGains.kA);
965 }
966 }
967
968 for (auto& [field, dt] : m_config.GetDoubleFields()) {
969 if (!dt.IsEnabled()) continue;
970 if (nt4Enabled) dt.SetupNetworkTables(m_dataTable, m_tuningTable);
971 if (dataLogName) dt.SetupDataLog(*dataLogName);
972 }
973 for (auto& [field, bt] : m_config.GetBoolFields()) {
974 if (!bt.IsEnabled()) continue;
975 if (nt4Enabled) bt.SetupNetworkTables(m_dataTable, m_tuningTable);
976 if (dataLogName) bt.SetupDataLog(*dataLogName);
977 }
978 for (auto& [field, stt] : m_config.GetPoseFields()) {
979 if (!stt.IsEnabled()) continue;
980 if (nt4Enabled) stt.SetupNetworkTables(m_dataTable, m_tuningTable);
981 if (dataLogName) stt.SetupDataLog(*dataLogName);
982 }
983 for (auto& [field, stt] : m_config.GetChassisSpeedsFields()) {
984 if (!stt.IsEnabled()) continue;
985 if (nt4Enabled) stt.SetupNetworkTables(m_dataTable, m_tuningTable);
986 if (dataLogName) stt.SetupDataLog(*dataLogName);
987 }
988 for (auto& [field, stat] : m_config.GetModuleStatesFields()) {
989 if (!stat.IsEnabled()) continue;
990 if (nt4Enabled) stat.SetupNetworkTables(m_dataTable, m_tuningTable);
991 if (dataLogName) stat.SetupDataLog(*dataLogName);
992 }
993}
994
995template <std::size_t NumModules>
997 for (auto& [field, stt] : m_config.GetPoseFields()) {
998 if (!stt.IsEnabled()) continue;
999 switch (field) {
1000 case StructTelemetryField::Pose:
1001 stt.Set(drive->GetPose());
1002 break;
1003 default:
1004 break;
1005 }
1006 }
1007 for (auto& [field, stt] : m_config.GetChassisSpeedsFields()) {
1008 if (!stt.IsEnabled()) continue;
1009 switch (field) {
1010 case StructTelemetryField::DesiredRobotRelativeChassisSpeeds:
1011 stt.Set(drive->GetDesiredChassisSpeeds());
1012 break;
1013 case StructTelemetryField::CurrentRobotRelativeChassisSpeeds:
1014 stt.Set(drive->GetRobotRelativeSpeed());
1015 break;
1016 case StructTelemetryField::FieldRelativeChassisSpeeds:
1017 stt.Set(drive->GetFieldRelativeSpeed());
1018 break;
1019 default:
1020 break;
1021 }
1022 }
1023 for (auto& [field, dt] : m_config.GetDoubleFields()) {
1024 if (!dt.IsEnabled()) continue;
1025 switch (field) {
1026 case DoubleTelemetryField::Gyro:
1027 dt.Set(drive->GetGyroAngle().value());
1028 break;
1029 default:
1030 break;
1031 }
1032 }
1033 for (auto& [field, stat] : m_config.GetModuleStatesFields()) {
1034 if (!stat.IsEnabled()) continue;
1035 switch (field) {
1036 case StructArrayTelemetryField::DesiredModuleStates: {
1037 auto states = drive->GetDesiredModuleStates();
1038 stat.Set(std::vector<wpi::math::SwerveModuleVelocity>(states.begin(), states.end()));
1039 break;
1040 }
1041 case StructArrayTelemetryField::CurrentModuleStates: {
1042 auto states = drive->GetModuleStates();
1043 stat.Set(std::vector<wpi::math::SwerveModuleVelocity>(states.begin(), states.end()));
1044 break;
1045 }
1046 default:
1047 break;
1048 }
1049 }
1050}
1051
1052template <std::size_t NumModules>
1054 if (!m_config.GetNT4Enabled()) return;
1055 auto& modules = drive->GetConfig().GetModules();
1056 auto& boolFields = m_config.GetBoolFields();
1057 auto& doubleFields = m_config.GetDoubleFields();
1058 // Copies of the configured controllers; gain fields for missing controllers are disabled.
1059 std::optional<wpi::math::PIDController> translationPID;
1060 if (auto pid = drive->GetConfig().GetTranslationPID()) translationPID = pid->get();
1061 std::optional<wpi::math::PIDController> rotationPID;
1062 if (auto pid = drive->GetConfig().GetRotationPID()) rotationPID = pid->get();
1063
1064 // Read which tuning modes are on. Only one may drive the chassis at a time: auto-align, then
1065 // drive tuning, then azimuth tuning. A mode that is on turns the ones it outranks back off, in
1066 // NetworkTables too so the dashboard reflects what's actually happening, so the order the fields
1067 // are read in does not matter. Auto-align fields are disabled when either drive to pose
1068 // controller is missing.
1069 std::set<TuningMode> modes;
1070 for (auto& [field, bt] : boolFields) {
1071 if (!bt.IsEnabled() || !bt.Get()) continue;
1072 switch (field) {
1073 case BooleanTelemetryField::AutoAlignEnabled:
1074 modes.insert(TuningMode::AutoAlign);
1075 modes.erase(TuningMode::Drive);
1076 modes.erase(TuningMode::Azimuth);
1077 boolFields.at(BooleanTelemetryField::ModulesDriveTuningEnabled).ForceSet(false);
1078 boolFields.at(BooleanTelemetryField::ModulesAzimuthTuningEnabled).ForceSet(false);
1079 break;
1080 case BooleanTelemetryField::ModulesDriveTuningEnabled:
1081 if (!modes.contains(TuningMode::AutoAlign)) {
1082 modes.insert(TuningMode::Drive);
1083 modes.erase(TuningMode::Azimuth);
1084 boolFields.at(BooleanTelemetryField::ModulesAzimuthTuningEnabled).ForceSet(false);
1085 }
1086 break;
1087 case BooleanTelemetryField::ModulesDriveInPlace:
1088 modes.insert(TuningMode::DriveInPlace);
1089 break;
1090 case BooleanTelemetryField::ModulesDriveVoltageEnabled:
1091 modes.insert(TuningMode::DriveVoltage);
1092 break;
1093 case BooleanTelemetryField::ModulesAzimuthTuningEnabled:
1094 if (!modes.contains(TuningMode::AutoAlign) && !modes.contains(TuningMode::Drive)) {
1095 modes.insert(TuningMode::Azimuth);
1096 }
1097 break;
1098 case BooleanTelemetryField::ModulesAzimuthVoltageEnabled:
1099 modes.insert(TuningMode::AzimuthVoltage);
1100 break;
1101 }
1102 }
1103 // Spinning in place and a voltage only apply while their tuning mode is on.
1104 if (!modes.contains(TuningMode::Drive)) {
1105 modes.erase(TuningMode::DriveInPlace);
1106 modes.erase(TuningMode::DriveVoltage);
1107 }
1108 if (!modes.contains(TuningMode::Azimuth)) modes.erase(TuningMode::AzimuthVoltage);
1109 // The angle each module points while drive tuning: forward, or, to spin in place, tangent to its
1110 // position around the robot center so a positive velocity spins the robot counter-clockwise
1111 // (WPILib's positive rotation direction).
1112 wpi::util::array<wpi::math::Rotation2d, NumModules> driveAngles{wpi::util::empty_array};
1113 for (size_t i = 0; i < NumModules; ++i) {
1114 driveAngles[i] = wpi::math::Rotation2d{};
1115 if (modes.contains(TuningMode::DriveInPlace)) {
1116 auto moduleLocation = *modules[i]->GetConfig().GetLocation();
1117 driveAngles[i] = moduleLocation.Angle().value_or(wpi::math::Rotation2d{}) +
1118 wpi::math::Rotation2d{wpi::units::degree_t{90}};
1119 }
1120 }
1121
1122 // Auto-align drives to its target pose every loop.
1123 if (modes.contains(TuningMode::AutoAlign)) {
1124 wpi::math::Pose2d targetPose{
1125 wpi::units::meter_t{doubleFields.at(DoubleTelemetryField::AutoAlignPoseX).Get()},
1126 wpi::units::meter_t{doubleFields.at(DoubleTelemetryField::AutoAlignPoseY).Get()},
1127 wpi::math::Rotation2d{
1128 wpi::units::degree_t{doubleFields.at(DoubleTelemetryField::AutoAlignPoseRotation).Get()}}};
1129 drive->SetRobotRelativeChassisSpeeds(drive->DriveToPoseSetpoint(targetPose));
1130 }
1131
1132 // When the tuning modes change, switch the motors over, and send the setpoints of the modes now
1133 // on.
1134 if (modes != m_lastModes) {
1135 const std::set<TuningMode> lastModes = m_lastModes.value_or(std::set<TuningMode>{});
1136 m_lastModes = modes;
1137 // Back to the closed loops when a voltage turns off.
1138 for (auto* module : modules) {
1139 if (lastModes.contains(TuningMode::DriveVoltage) && !modes.contains(TuningMode::DriveVoltage)) {
1140 module->GetDriveMotorController()->StartClosedLoopController();
1141 }
1142 if (lastModes.contains(TuningMode::AzimuthVoltage) &&
1143 !modes.contains(TuningMode::AzimuthVoltage)) {
1144 module->GetAzimuthMotorController()->StartClosedLoopController();
1145 }
1146 }
1147 wpi::util::array<wpi::math::SwerveModuleVelocity, NumModules> states{wpi::util::empty_array};
1148 if (modes.empty()) {
1149 // No tuning mode is driving the chassis; make sure the modules don't keep spinning at
1150 // whatever velocity was last commanded while drive tuning was on.
1151 for (size_t i = 0; i < NumModules; ++i) {
1152 states[i] = wpi::math::SwerveModuleVelocity{wpi::units::meters_per_second_t{0},
1153 modules[i]->GetState().angle};
1154 }
1155 drive->SetSwerveModuleStates(states);
1156 }
1157 for (auto mode : modes) {
1158 switch (mode) {
1159 case TuningMode::AutoAlign:
1160 // Driven every loop, above.
1161 [[fallthrough]];
1162 case TuningMode::DriveInPlace:
1163 // Sent with drive tuning's setpoint.
1164 break;
1165 case TuningMode::Drive:
1166 if (!modes.contains(TuningMode::DriveVoltage)) {
1167 for (size_t i = 0; i < NumModules; ++i) {
1168 states[i] = wpi::math::SwerveModuleVelocity{
1169 wpi::units::meters_per_second_t{
1170 doubleFields.at(DoubleTelemetryField::ModulesDriveVelocity).Get()},
1171 driveAngles[i]};
1172 }
1173 drive->SetSwerveModuleStates(states);
1174 }
1175 break;
1176 case TuningMode::DriveVoltage:
1177 for (size_t i = 0; i < NumModules; ++i) {
1178 // A closed loop run on the SystemCore would replace the voltage on its next loop.
1179 if (!lastModes.contains(TuningMode::DriveVoltage)) {
1180 modules[i]->GetDriveMotorController()->StopClosedLoopController();
1181 }
1182 // The wheels are not optimized, so they always drive the same way.
1183 modules[i]->GetDriveMotorController()->SetVoltage(
1184 wpi::units::volt_t{doubleFields.at(DoubleTelemetryField::ModulesDriveVoltage).Get()});
1185 modules[i]->GetAzimuthMotorController()->SetPosition(
1186 wpi::units::degree_t{driveAngles[i].Degrees()});
1187 }
1188 break;
1189 case TuningMode::Azimuth:
1190 if (!modes.contains(TuningMode::AzimuthVoltage)) {
1191 // Zero speed states would be held where the wheels are by the velocity deadband, so turn
1192 // the wheels without driving them.
1193 drive->SetAzimuthAngles(wpi::math::Rotation2d{wpi::units::degree_t{
1194 doubleFields.at(DoubleTelemetryField::ModulesAzimuthAngle).Get()}});
1195 }
1196 break;
1197 case TuningMode::AzimuthVoltage:
1198 for (auto* module : modules) {
1199 // A closed loop run on the SystemCore would replace the voltage on its next loop.
1200 if (!lastModes.contains(TuningMode::AzimuthVoltage)) {
1201 module->GetAzimuthMotorController()->StopClosedLoopController();
1202 }
1203 // Hold the drive motors still while the azimuth motors turn.
1204 module->GetDriveMotorController()->SetVelocity(wpi::units::meters_per_second_t{0} +
1205 module->GetCouplingVelocity());
1206 module->GetAzimuthMotorController()->SetVoltage(wpi::units::volt_t{
1207 doubleFields.at(DoubleTelemetryField::ModulesAzimuthVoltage).Get()});
1208 }
1209 break;
1210 }
1211 }
1212 }
1213
1214 // Set every gain and setpoint that changed.
1215 for (auto field : {DoubleTelemetryField::TranslationP,
1216 DoubleTelemetryField::TranslationI,
1217 DoubleTelemetryField::TranslationD,
1218 DoubleTelemetryField::RotationP,
1219 DoubleTelemetryField::RotationI,
1220 DoubleTelemetryField::RotationD,
1221 DoubleTelemetryField::AutoAlignPoseX,
1222 DoubleTelemetryField::AutoAlignPoseY,
1223 DoubleTelemetryField::AutoAlignPoseRotation,
1224 DoubleTelemetryField::ModulesDriveP,
1225 DoubleTelemetryField::ModulesDriveI,
1226 DoubleTelemetryField::ModulesDriveD,
1227 DoubleTelemetryField::ModulesDriveKs,
1228 DoubleTelemetryField::ModulesDriveKv,
1229 DoubleTelemetryField::ModulesDriveKa,
1230 DoubleTelemetryField::ModulesDriveVelocity,
1231 DoubleTelemetryField::ModulesDriveVoltage,
1232 DoubleTelemetryField::ModulesAzimuthP,
1233 DoubleTelemetryField::ModulesAzimuthI,
1234 DoubleTelemetryField::ModulesAzimuthD,
1235 DoubleTelemetryField::ModulesAzimuthKs,
1236 DoubleTelemetryField::ModulesAzimuthKv,
1237 DoubleTelemetryField::ModulesAzimuthKa,
1238 DoubleTelemetryField::ModulesAzimuthAngle,
1239 DoubleTelemetryField::ModulesAzimuthVoltage}) {
1240 auto& dt = doubleFields.at(field);
1241 if (!dt.IsEnabled() || !dt.IsTunable()) continue;
1242 switch (field) {
1243 case DoubleTelemetryField::TranslationP:
1244 if (translationPID) {
1245 translationPID->SetP(dt.Get());
1246 drive->SetTranslationPID(*translationPID);
1247 }
1248 break;
1249 case DoubleTelemetryField::TranslationI:
1250 if (translationPID) {
1251 translationPID->SetI(dt.Get());
1252 drive->SetTranslationPID(*translationPID);
1253 }
1254 break;
1255 case DoubleTelemetryField::TranslationD:
1256 if (translationPID) {
1257 translationPID->SetD(dt.Get());
1258 drive->SetTranslationPID(*translationPID);
1259 }
1260 break;
1261 case DoubleTelemetryField::RotationP:
1262 if (rotationPID) {
1263 rotationPID->SetP(dt.Get());
1264 drive->SetRotationPID(*rotationPID);
1265 }
1266 break;
1267 case DoubleTelemetryField::RotationI:
1268 if (rotationPID) {
1269 rotationPID->SetI(dt.Get());
1270 drive->SetRotationPID(*rotationPID);
1271 }
1272 break;
1273 case DoubleTelemetryField::RotationD:
1274 if (rotationPID) {
1275 rotationPID->SetD(dt.Get());
1276 drive->SetRotationPID(*rotationPID);
1277 }
1278 break;
1279 case DoubleTelemetryField::AutoAlignPoseX:
1280 case DoubleTelemetryField::AutoAlignPoseY:
1281 case DoubleTelemetryField::AutoAlignPoseRotation:
1282 // Auto-align reads its target pose every loop, above.
1283 break;
1284 case DoubleTelemetryField::ModulesDriveP:
1285 for (auto* module : modules) module->GetDriveMotorController()->SetKp(dt.Get());
1286 break;
1287 case DoubleTelemetryField::ModulesDriveI:
1288 for (auto* module : modules) module->GetDriveMotorController()->SetKi(dt.Get());
1289 break;
1290 case DoubleTelemetryField::ModulesDriveD:
1291 for (auto* module : modules) module->GetDriveMotorController()->SetKd(dt.Get());
1292 break;
1293 case DoubleTelemetryField::ModulesDriveKs:
1294 for (auto* module : modules) module->GetDriveMotorController()->SetKs(dt.Get());
1295 break;
1296 case DoubleTelemetryField::ModulesDriveKv:
1297 for (auto* module : modules) module->GetDriveMotorController()->SetKv(dt.Get());
1298 break;
1299 case DoubleTelemetryField::ModulesDriveKa:
1300 for (auto* module : modules) module->GetDriveMotorController()->SetKa(dt.Get());
1301 break;
1302 case DoubleTelemetryField::ModulesAzimuthP:
1303 for (auto* module : modules) module->GetAzimuthMotorController()->SetKp(dt.Get());
1304 break;
1305 case DoubleTelemetryField::ModulesAzimuthI:
1306 for (auto* module : modules) module->GetAzimuthMotorController()->SetKi(dt.Get());
1307 break;
1308 case DoubleTelemetryField::ModulesAzimuthD:
1309 for (auto* module : modules) module->GetAzimuthMotorController()->SetKd(dt.Get());
1310 break;
1311 case DoubleTelemetryField::ModulesAzimuthKs:
1312 for (auto* module : modules) module->GetAzimuthMotorController()->SetKs(dt.Get());
1313 break;
1314 case DoubleTelemetryField::ModulesAzimuthKv:
1315 for (auto* module : modules) module->GetAzimuthMotorController()->SetKv(dt.Get());
1316 break;
1317 case DoubleTelemetryField::ModulesAzimuthKa:
1318 for (auto* module : modules) module->GetAzimuthMotorController()->SetKa(dt.Get());
1319 break;
1320 case DoubleTelemetryField::ModulesDriveVelocity:
1321 if (modes.contains(TuningMode::Drive) && !modes.contains(TuningMode::DriveVoltage)) {
1322 wpi::util::array<wpi::math::SwerveModuleVelocity, NumModules> states{
1323 wpi::util::empty_array};
1324 for (size_t i = 0; i < NumModules; ++i) {
1325 states[i] = wpi::math::SwerveModuleVelocity{wpi::units::meters_per_second_t{dt.Get()},
1326 driveAngles[i]};
1327 }
1328 drive->SetSwerveModuleStates(states);
1329 }
1330 break;
1331 case DoubleTelemetryField::ModulesDriveVoltage:
1332 if (modes.contains(TuningMode::DriveVoltage)) {
1333 for (size_t i = 0; i < NumModules; ++i) {
1334 // The wheels are not optimized, so they always drive the same way.
1335 modules[i]->GetDriveMotorController()->SetVoltage(wpi::units::volt_t{dt.Get()});
1336 modules[i]->GetAzimuthMotorController()->SetPosition(
1337 wpi::units::degree_t{driveAngles[i].Degrees()});
1338 }
1339 }
1340 break;
1341 case DoubleTelemetryField::ModulesAzimuthAngle:
1342 if (modes.contains(TuningMode::Azimuth) && !modes.contains(TuningMode::AzimuthVoltage)) {
1343 // Zero speed states would be held where the wheels are by the velocity deadband, so turn
1344 // the wheels without driving them.
1345 drive->SetAzimuthAngles(wpi::math::Rotation2d{wpi::units::degree_t{dt.Get()}});
1346 }
1347 break;
1348 case DoubleTelemetryField::ModulesAzimuthVoltage:
1349 if (modes.contains(TuningMode::AzimuthVoltage)) {
1350 for (auto* module : modules) {
1351 // Hold the drive motors still while the azimuth motors turn.
1352 module->GetDriveMotorController()->SetVelocity(wpi::units::meters_per_second_t{0} +
1353 module->GetCouplingVelocity());
1354 module->GetAzimuthMotorController()->SetVoltage(wpi::units::volt_t{dt.Get()});
1355 }
1356 }
1357 break;
1358 default:
1359 break;
1360 }
1361 }
1362}
1363
1364template <std::size_t NumModules>
1366 // Turn every tuning mode off and apply it, so a voltage stops and the wheels are held where they
1367 // are.
1368 for (auto& [field, bt] : m_config.GetBoolFields()) {
1369 if (bt.IsEnabled()) bt.ForceSet(false);
1370 }
1371 ApplyTuningValues(drive);
1372 // Send everything again when tuning starts again.
1373 m_lastModes.reset();
1374}
1375
1376} // namespace yams::telemetry
Definition SwerveDriveConfig.hpp:38
wpi::math::ChassisVelocities OptimizeRobotRelativeChassisSpeeds(wpi::math::ChassisVelocities speeds) const
wpi::math::Pose2d GetInitialPose() const
wpi::math::Rotation3d GetGyroRotation3d() const
std::optional< telemetry::SwerveDriveTelemetryConfig > GetSwerveDriveTelemetryConfig()
std::optional< wpi::units::meters_per_second_t > GetMaximumChassisLinearVelocity() const
wpi::units::degree_t GetGyroOffset() const
std::optional< wpi::units::degrees_per_second_t > GetMaximumChassisAngularVelocity() const
const std::vector< SwerveModule * > & GetModules() const
std::optional< bool > GetModuleStateOptimization() const
std::optional< std::reference_wrapper< wpi::math::PIDController > > GetRotationPID()
std::optional< wpi::units::meters_per_second_t > GetMaximumModuleLinearVelocity() const
wpi::cmd::SubsystemBase * GetSubsystem() const
SwerveDriveConfig & WithRotationController(wpi::math::PIDController controller)
wpi::units::degree_t GetGyroAngle() const
const std::string & GetTelemetryName() const
std::optional< TelemetryVerbosity > GetTelemetryVerbosity() const
SwerveDriveConfig & WithGyroOffset(wpi::units::degree_t offset)
SwerveDriveConfig & WithTranslationController(wpi::math::PIDController controller)
std::optional< std::reference_wrapper< wpi::math::PIDController > > GetTranslationPID()
std::optional< wpi::math::Translation2d > GetCenterOfRotation() const
Definition SwerveDrive.hpp:136
wpi::cmd::CommandPtr DriveToPose(wpi::math::Pose2d pose)
Definition SwerveDrive.hpp:193
void AddVisionMeasurement(wpi::math::Pose2d robotPose, wpi::units::second_t timestamp, const wpi::util::array< double, 3 > &stdDevs)
Definition SwerveDrive.hpp:501
wpi::Field2d & GetField2d()
Definition SwerveDrive.hpp:735
wpi::math::ChassisVelocities GetRobotRelativeChassisSpeedsFromState(const wpi::util::array< wpi::math::SwerveModuleVelocity, NumModules > &states)
Definition SwerveDrive.hpp:375
void ZeroGyro()
Definition SwerveDrive.hpp:479
wpi::math::ChassisVelocities GetFieldRelativeSpeed()
Definition SwerveDrive.hpp:617
void SetFieldRelativeChassisSpeeds(wpi::math::ChassisVelocities fieldRelativeSpeeds)
Definition SwerveDrive.hpp:418
wpi::math::Rotation3d GetGyroRotation3d()
Definition SwerveDrive.hpp:580
wpi::util::array< wpi::math::SwerveModuleVelocity, NumModules > GetDesiredModuleStates()
Definition SwerveDrive.hpp:755
wpi::util::array< wpi::math::SwerveModuleVelocity, NumModules > GetModuleStates()
Definition SwerveDrive.hpp:632
void AddVisionMeasurement(wpi::math::Pose2d robotPose, wpi::units::second_t timestamp)
Definition SwerveDrive.hpp:490
void SetSwerveModuleStates(wpi::util::array< wpi::math::SwerveModuleVelocity, NumModules > states, std::span< const wpi::units::newton_t > feedforwardForces)
Definition SwerveDrive.hpp:333
wpi::cmd::CommandPtr Drive(std::function< wpi::math::ChassisVelocities()> robotRelativeSpeeds)
Definition SwerveDrive.hpp:175
wpi::math::ChassisVelocities DriveToPoseSetpoint(wpi::math::Pose2d targetPose, wpi::math::Pose2d currentPose, wpi::math::Rotation2d heading)
Definition SwerveDrive.hpp:278
wpi::math::Pose2d GetSimPose()
Definition SwerveDrive.hpp:609
void ApplyTuningValues()
Definition SwerveDrive.hpp:597
void ResetOdometry(wpi::math::Pose2d pose)
Definition SwerveDrive.hpp:467
void SetAzimuthAngles(wpi::math::Rotation2d angle)
Definition SwerveDrive.hpp:446
wpi::util::array< wpi::math::SwerveModuleVelocity, NumModules > GetStateFromRobotRelativeChassisSpeeds(wpi::math::ChassisVelocities robotRelativeSpeeds)
Definition SwerveDrive.hpp:348
wpi::units::degree_t GetGyroAngle()
Definition SwerveDrive.hpp:569
void SimIterate()
Definition SwerveDrive.hpp:545
wpi::math::ChassisVelocities GetDesiredChassisSpeeds()
Definition SwerveDrive.hpp:747
void UpdateTelemetry()
Definition SwerveDrive.hpp:513
wpi::math::Pose2d GetPose()
Definition SwerveDrive.hpp:458
wpi::math::SwerveDriveKinematics< NumModules > & GetKinematics()
Definition SwerveDrive.hpp:729
wpi::units::degree_t GetAngleDifferenceFromPose(wpi::math::Pose2d pose)
Definition SwerveDrive.hpp:656
void LockPose()
Definition SwerveDrive.hpp:427
void Stop()
Definition SwerveDrive.hpp:249
void StartDriveToPoseTuning()
Definition SwerveDrive.hpp:589
void SetRotationPID(wpi::math::PIDController controller)
Definition SwerveDrive.hpp:688
void SetTranslationPID(wpi::math::PIDController controller)
Definition SwerveDrive.hpp:703
SwerveDriveConfig & GetConfig()
Definition SwerveDrive.hpp:726
std::string GetName() const
Definition SwerveDrive.hpp:723
wpi::cmd::CommandPtr DriveToPose(wpi::math::Pose2d pose, wpi::units::meter_t translationTolerance, wpi::units::degree_t rotationTolerance)
Definition SwerveDrive.hpp:217
wpi::util::array< wpi::math::SwerveModulePosition, NumModules > GetModulePositions()
Definition SwerveDrive.hpp:623
SwerveDrive(SwerveDriveConfig *config)
Definition SwerveDrive.hpp:143
wpi::math::ChassisVelocities DriveToPoseSetpoint(wpi::math::Pose2d targetPose)
Definition SwerveDrive.hpp:262
void ResetTranslationPID()
Definition SwerveDrive.hpp:679
std::optional< SwerveModule * > GetModule(const std::string &name)
Definition SwerveDrive.hpp:666
bool IsNear(wpi::math::Pose2d pose, wpi::units::meter_t translationTolerance, wpi::units::degree_t rotationTolerance)
Definition SwerveDrive.hpp:242
void SetVisionMeasurementStdDevs(const wpi::util::array< double, 3 > &stdDevs)
Definition SwerveDrive.hpp:718
wpi::math::ChassisVelocities GetRobotRelativeSpeed()
Definition SwerveDrive.hpp:612
wpi::units::meter_t GetDistanceFromPose(wpi::math::Pose2d pose)
Definition SwerveDrive.hpp:646
void SetRobotRelativeChassisSpeeds(wpi::math::ChassisVelocities robotRelativeSpeeds)
Definition SwerveDrive.hpp:385
void SetRobotRelativeChassisSpeeds(wpi::math::ChassisVelocities robotRelativeSpeeds, std::span< const wpi::units::newton_t > feedforwardForces)
Definition SwerveDrive.hpp:398
void SetSwerveModuleStates(wpi::util::array< wpi::math::SwerveModuleVelocity, NumModules > states)
Definition SwerveDrive.hpp:321
void ResetRotationPID()
Definition SwerveDrive.hpp:674
TelemetryVerbosity
Definition SmartMotorControllerConfig.hpp:72
Definition MechanismTelemetry.hpp:34
void SetupTelemetry(const std::string &mechanismName, motorcontrollers::SmartMotorController &motorController)
std::unordered_map< BooleanTelemetryField, BooleanTelemetry< BooleanTelemetryField > > & GetBoolFields()
std::optional< std::string > GetDataLogName() const
std::unordered_map< DoubleTelemetryField, DoubleTelemetry< DoubleTelemetryField > > & GetDoubleFields()
std::unordered_map< StructTelemetryField, StructTelemetry< wpi::math::Pose2d, StructTelemetryField > > & GetPoseFields()
std::unordered_map< StructArrayTelemetryField, StructArrayTelemetry< wpi::math::SwerveModuleVelocity, StructArrayTelemetryField > > & GetModuleStatesFields()
std::unordered_map< StructTelemetryField, StructTelemetry< wpi::math::ChassisVelocities, StructTelemetryField > > & GetChassisSpeedsFields()
void Publish(mechanisms::swerve::SwerveDrive< NumModules > *drive)
Definition SwerveDrive.hpp:996
void SetupTelemetry(mechanisms::swerve::SwerveDrive< NumModules > *drive)
Definition SwerveDrive.hpp:873
void StopTuning(mechanisms::swerve::SwerveDrive< NumModules > *drive)
Definition SwerveDrive.hpp:1365
void ApplyTuningValues(mechanisms::swerve::SwerveDrive< NumModules > *drive)
Definition SwerveDrive.hpp:1053
Definition SwerveDrive.hpp:49
Definition SwerveDrive.hpp:864
void EnsureMechanismsTunableBackend()
Definition NetworkTablesBackends.hpp:43