Squashed 'third_party/allwpilib/' changes from 83f1860047..f1a82828fe

f1a82828fe [wpiutil] Add DataLog and DataLogManager Stop() (#5860)
2a04e12c6f [apriltag] AprilTagFieldLayout: Add accessors for origin and field dimensions (#5869)
33e0089afb Cleanup usages of std::function<void(void)> (#5864)
d06fa633d5 [build] Fix protobuf generation when building with make (#5867)
049732afb8 [cscore] Make camera connection logging clearer (#5866)
87f7c19f90 [wpimath] Make InterpolatingDoubleTreeMap constructor public (#5865)
6b53ef47cf [wpimath] Don't recreate TrapezoidProfile in ProfiledPIDController calculate() (#5863)
8a3a268ae6 [commands] Add finallyDo with zero-arg lambda (#5862)
1c35d42cd0 [wpilib] Pop diagnostic for deprecated function use (#5859)
ddc8db6c26 [wpimath] Add feedforward constant constructor to ElevatorSim (#5823)
c6aff2c431 [upstream_utils] Update to LLVM 17.0.4 (#5855)
a9c5b18a39 [build] Update OpenCV to 2024-4.8.0-2 (#5854)
9540b6922d [hal] Add CAN IDs for AndyMark and Vivid Hosting (#5852)
83a7d33c47 [glass] Improve display of protobuf/struct type strings (#5850)
a4a8ad9c75 [commands] Make Java SelectCommand generic (#5849)
9eecf2a456 [build] Add CMake option to build Java sources jars (#5768)
9536a311cb [wpilib] Add support for the PS5 DualSense controller (#5257)
8d5e6737fc [wpilibc] SolenoidSim: Add virtual destructor (#5848)
07e13d60a2 [ntcore] Fix write_impl (#5847)
1713386869 [wpiutil] ProtobufMessageDatabase: Fix out-of-order Add() rebuild (#5845)
35472f5fc9 [ntcore] Fix a use-after-free in client close (#5844)
ed168b522c [ntcore] Disable buf pool when asan is enabled (#5843)
3e7ba2cc6f [wpinet] WebSocket: Fix write behavior (#5841)
80c47da237 [sim] Disable the robot program when DS disconnects (#5818)
abe1cec90c [wpilib] Update Usage Reporting ResourceType from NI Libraries (#5842)
cdf981abba [glass] Fix position of data type in NT view (#5840)
04dcd80adb [build] Publish unit tests for examples (#5838)
49920234ac [build] Fix checkstyle rules to allow Windows paths (#5839)
366b715942 [wpilib] Fix SendableChooser test (#5835)
3ba501f947 [commands] Java: Fix CommandXboxController.leftTrigger() parameter order (#5831)
ec569a58ef [wpimath] Make KalmanTypeFilter interface public (#5830)
b91317fd36 [wpiutil] DataLog.addSchema(): Don't add into a set view (#5829)
2ab4fcbc24 [wpiutil] ProtobufMessageDatabase: Clear messages first (#5827)
98c14f1692 [wpimath] Add EKF/UKF u-y-R correct overload (#5832)
60bcdeded9 [ci] Disable java in sanitizer builds (#5833)
c87f8fd538 [commands] Add DeferredCommand (#5566)
ad80eb3a0b [ci] Update actions for comment-command (#5824)
c7d6ad5a0b [ntcore] WebSocketConnection: Use weak capture (#5822)
8a8e220792 [simgui] Add 'Invalid' option for AllianceStation (#5820)
cfc6a47f76 [sim] DS plugin: Fix off-by-one error when setting alliance station (#5819)
8efa586ace [ntcore] Don't check type string on publishing an entry (#5816)
23ea188e60 [glass] Add protobuf decode error log message (#5812)
928e87b4f4 [build] Add combined test meta-task (#5813)
63ef585d4b [wpiutil] Fix compilation of MathExtras.h on Windows with /sdl (#5809)
b03a7668f9 [build] Windows CMake/vcpkg fixes (#5807)
3f08bcde54 [hal] Fix HAL AllianceStation on rio (#5811)
196d963dc4 [ntcore] Fix off-by-one error in stream write (#5810)
f4cbcbc984 Fix typos (NFC) (#5804)
ec0f7fefb0 [myrobot] Update the myRobot JRE (#5805)
3d618bdbfd [wpiutil] Fix Java struct array unpacking (#5801)
1fa7445667 [ntcore] Check for valid client in incoming text and binary (#5799)
269b9647da [ci] Update JDK for combine step (#5794)
bee32f080e [docs] Add wpiunits to JavaDocs (#5793)
25dad5a531 [wpinet] TCPConnector_parallel: Don't use thread_local (#5791)
4a93581f1a [build] cmake: use default library type for libglassnt, libglass, wpigui, and imgui (#5797)
abb2857e03 [wpilib] Counter: Fix default distance per pulse, add distance and rate to C++ (#5796)
b14a61e1c0 [readme] Add link to QuickBuffers release page (#5795)
cf54d9ccb7 [wpiutil, ntcore] Add structured data support (#5391)
ecb7cfa9ef [wpimath] Add Exponential motion profile (#5720)
7c6fe56cf2 [ntcore] Fix crash on disconnect (#5788)
85147bf69e [wpinet] WebSocketSerializer: Fix UB (#5787)
244163acad [wpinet] uv::Stream::TryWrite(): Return 0 on EAGAIN (#5784)
820728503d [hal] Remove extra semicolon in RoboRioData (#5786)
45f307d87e [upstream_utils] Upgrade to LLVM 17.0.3 (#5785)
4ce4d63efc [wpilibj] Fix RobotBase.isSimulation() (#5783)
579007ceb3 [commands] Add requirements parameter to Commands.idle() (#5774)
3f3a169149 [wpilib] Make physics sim setState() functions public (#5779)
7501e4ac88 [wpilib] Close sim device in ADIS IMUs (#5776)
99630d2e78 [wpimath] Upgrade to EJML 0.43.1 (#5778)
02cbbc997d [wpimath] Make Vector-Vector binary operators return Vector (#5772)
ed93889e17 [examples] Fix typo in TimesliceRobot example name (#5773)
da70e4c262 [docs] Add jinja2 to CMake prerequisites (#5771)
e814595ea7 [wpimath] Add ChassisSpeeds.fromRobotRelativeSpeeds() (#5744)
f98c943445 [wpimath] LinearSystemId: Add DCMotorSystem overload (#5770)
b3eb64b0f7 [wpiutil] ct_string: Use inline namespace for literals (#5767)
7d9ba256c2 Revert "[build] Add CMake option to build Java source jars (#5756)" (#5766)
1f6492e3d8 [sysid] Update JSON library usage (#5765)
638f04f626 [wpiutil] Add protobuf to thirdparty sources (#5746)
210255bfff [wpiutil] Update json to 3.11.2 (#5680)
896772c750 [wpimath] Add DCMotor functions for Kraken X60 and Neo Vortex (#5759)
fd427f6c82 [wpimath] Fix hardcoded module count in SwerveDriveKinematics.resetHeading() (#5762)
c0b4c6cce6 [wpimath] Add overloads for Transform2d and Transform3d (#5757)
9a0aafd8ab [examples] Make swerve examples multiply desired module speeds by cosine of heading error (#5758)
1c724884ca [build] Add CMake option to build Java source jars (#5756)
5b0db6b93e [ci] Forward CI as well (#5755)
f8cbbbac12 [ci] Take 2 on passing GITHUB_REF (#5754)
b9944be09c [ci] Pass GITHUB_REF to docker container (#5753)
de5e4eda6c [build] Update apriltag, libssh, googletest for 2024 (#5752)
227e660e20 [upstream_utils] Upgrade to LLVM 17.0.2 (#5750)
36f94c9f21 [commands,romi,xrp] Add frcYear to vendordep (#5747)
741d166457 [glass] NT view: enhance array support (#5732)
1d23513945 [ntcore] Fix string array value comparison (#5745)
ff1849052e [commands] Make command scheduling order consistent (#5470)
58e8474368 [build] Disable armsimulation unit test (#5739)
fb07b0da49 [examples] Add XRP C++ Examples and Templates (#5743)
81893ad73d Run wpiformat with clang-format 17 (#5740)
faa1e665ba [wpimath] Add ElevatorFeedforward.calculate(currentV, nextV) overload (#5715)
a789632052 [build] Update to native utils 2024.3.1 (#5738)
8f60ab5182 [build] Update OpenCV to 2024-4.8.0-1 (#5737)
33243f982b [wpimath] Expand Quaternion class with additional operators (#5600)
420f2f7c80 [ntcore] Add RTT-only subprotocol (#5731)
2b63e35ded [ntcore] Fix moving outgoing queue to new period (#5735)
be939cb636 [ntcore] Fix notification of SetDefaultEntryValue (#5733)
69a54de202 [build] Update enterprise plugin (#5730)
fef03a3ff5 [commands] Clean up C++ includes after Requirements was added (#5719)
8b7c6852cf [ntcore] Networking improvements (#5659)
1d19e09ca9 [wpiutil] Set WPI_{UN}IGNORE_DEPRECATED to empty when all else fails (#5728)
58141d6eb5 [wpilib] Make BooleanEvent more consistent (#5436)
6576d9b474 [wpilib] SendableChooser: implement Sendable instead of NTSendable (#5718)
a4030c670f [build] Update to gradle 8.4, enable win arm builds (#5727)
0960f11eba [wpinet] Revert removal of uv_clock_gettime() (#5723)
cb1bd0a3be [wpiutil] Get more precise system time on Windows (#5722)
4831277ffe [wpigui] Fix loading a maximized window on second monitor (#5721)
3eb372c25a [wpiutil] SendableBuilder: Add PublishConst methods (#5158)
1fec8596a4 [ci] Fix -dirty version (#5716)
f7e47d03f3 [build] Remove unnecessary CMake config installs (#5714)
a331ed2374 [sysid] Add SysId (#5672)
8d2cbfce16 [wpiutil] DataLog: Stop logging if insufficient free space (#5699)
48facb9cef [ntcoreffi] Add DataLogManager (#5702)
aecbcb08fc [ntcore] Correctly start DataLog for existing publishers (#5703)
5e295dfbda [wpiutil] DataLog: Limit total buffer allocation (#5700)
c7c7e05d9d [ci] Unbreak combiner (#5698)
c92bad52cb [wpilib] DataLogManager: Use system time valid function (#5697)
d404af5f24 [wpilib] RobotController: Add isSystemTimeValid() (#5696)
e56f1a3632 [ci] Run combine but skip all steps (#5695)
8f5bcad244 [ci] Use sccache for cmake builds (#5692)
703dedc4a6 [ci] Upgrade get-cmake action to fix node12 deprecation warning (#5694)
c69a0d7504 [ci] Don't run example unit test that segfaults (#5693)
66358d103e Add menu items for online docs to GUI tools (#5689)
4be8384a76 [ci] Disable combine on PR builds (#5691)
90288f06a6 [ci] Fix Gradle disk space issues (#5688)
9e9583412e [wpigui] Make wpi::gui::OpenURL() fork the process first (#5687)
d4fcd80b7b [ci] Gradle: Use container only for build step (#5684)
7b70e66772 [outlineviewer] Fix thirdparty library include sorting (#5683)
5f651df5d5 [build] Clean up Gradle configs (#5685)
65b26738d5 Add CMakeSettings.json to gitignore (#5682)
d0305951ad Fix GitHub inline warnings (#5681)
e8d4a20331 [build][cmake] Fix windows tests and re-enable CI tests (#5674)
2b58bbde0b [xrp] Add Reflectance sensor and rangefinder classes (#5673)
dd5612fbee [json] Add forward definition header (#5676)
eab44534c3 [wpimath] Remove unused SmallString include (#5677)
5ab54ff760 Replace wpi::raw_istream with wpi::MemoryBuffer (#5675)
1b6ec5a95d [wpiutil] Upgrade to LLVM 17.0.1 (#5482)
07a0d22fe6 [build] Build examples in CMake CI (#5667)
97021f074a [build] Upgrade imgui and implot (#5668)
87ce1e3761 [build] Fix wpilibNewCommands CMake install (#5671)
6ef94de9b5 [wpimath] Add tests for ArmFeedforward and ElevatorFeedforward (#5663)
c395b29fb4 [wpinet] Add WebSocket::TrySendFrames() (#5607)
c4643ba047 [romi/xrp] Fix version typo in vendordep json (#5664)
51dcb8b55a [examples] Make Romi/XRP Examples use appropriate vendordeps (#5665)
daf7702007 [build] Test each example in a new environment (#5662)
e67df8c180 [wpilib] Const-qualify EncoderSim getters (#5660)
7be290147c [wpiutil] Refactor SpanMatcher and TestPrinters from ntcore (#5658)
9fe258427a [commands] Add proxy factory to Commands (#5603)
633c5a8a22 [commands] Add C++ Requirements struct (#5504)
b265a68eea [commands] Add interruptor parameter to onCommandInterrupt callbacks (#5461)
e93c233d60 [ntcore] Compute Value memory size when creating value (#5657)
5383589f99 [wpinet] uv::Request: Return shared_ptr from Release() (#5656)
40b552be4a [wpinet] uv::Stream: Return error from TryWrite() (#5655)
202a75fe08 [wpinet] RequestImpl: Avoid infinite loop in shared_from_this() (#5654)
8896515eb7 [wpinet] uv::Buffer: Add bytes() accessor (#5653)
ae59a2fba2 [wpinet] uv::Error: Change default error to 0 (#5652)
3b51ecc35b [wpiutil] SpanExtras: Add take_back and take_front (#5651)
17f1062885 Replace std::snprintf() with wpi::format_to_n_c_str() (#5645)
bb39900353 [romi/xrp] Add Romi and XRP Vendordeps (#5644)
cb99517838 [build] cmake: Use default install location on windows for dlls (#5580)
25b0622d4c [build] Add Windows CMake CI (#5516)
34e7849605 Add warning to development builds instructions (NFC) (#5646)
e9e611c9d8 [cameraserver] Remove CameraServer.SetSize() (#5650)
94f58cc536 [wpilib] Remove Compressor.Enabled() (#5649)
4da5aee88a [wpimath] Remove SlewRateLimiter 2 argument constructor (#5648)
2e3ddf5502 Update versions in development builds instructions to 2024 (#5647)
19a8850fb1 [examples] Add TimesliceRobot templates (#3683)
9047682202 [sim] Add XRP-specific plugin (#5631)
575348b81c [wpilib] Use IsSimulation() consistently (#3534)
12e2043b77 [wpilib] Clean up Notifier (#5630)
4bac4dd0f4 [wpimath] Move PIDController from frc2 to frc namespace (#5640)
494cfd78c1 [wpiutil] Fix deprecation warning in LLVM for C++23 (#5642)
43a727e868 [apriltag] Make loadAprilTagFieldLayout throw an unchecked exception instead (#5629)
ad4b017321 [ci] Use Ninja for faster builds (#5626)
4f2114d6f5 Fix warnings from GCC 13 release build (#5637)
e7e927fe26 [build] Also compress debug info for CMake RelWithDebInfo build type (#5638)
205a40c895 [build] Specify zlib for debug info compression (#5636)
707444f000 [apriltag] Suppress -Wtype-limits warning in asserts from GCC 13 (#5635)
3b79cb6ed3 [commands] Revert SubsystemBase deprecation/removal (#5634)
bc7f23a632 [build] Compress Linux debug info (#5633)
57b2d6f254 [build] Update to image 2024 v1.0 (#5625)
339ef1ea39 [wpilib] DataLogManager: Warn user if logging to RoboRIO 1 internal storage (#5617)
7a9a901a73 [build] Fix cmake config files (#5624)
298f8a6e33 [wpilib] Add Mechanism2d tests and make Java impl match C++ (#5527)
d7ef817bae [apriltag] Update apriltag library (#5619)
c3fb31fd0e [docs] Switch to Java 17 api docs (#5613)
bd64f81cf9 [build] Run Google tests in release mode in CI (#5615)
66e6bd81ea [wpimath] Cleanup wpimath/algorithms.md (NFC) (#5621)
4fa56fd884 [build] Add missing find_dependency call (#5623)
f63d958995 [build] Update to native utils 2024.2.0 (#5601)
a9ab08f48b [wpimath] Rename ChassisSpeeds.fromDiscreteSpeeds() to discretize() (#5616)
8e05983a4a [wpimath] Add math docs to plant inversion feedforward internals (NFC) (#5618)
3a33ce918b [ntcore] Add missing StringMap include (#5620)
a6157f184d [wpiutil] timestamp: Add ShutdownNowRio (#5610)
e9f612f581 [build] Guard policy setting for CMake versions below 3.24 (#5612)
1a6df6fec6 [wpimath] Fix DARE Q decomposition (#5611)
9b3f7fb548 [build] Exclude IntelliJ folders from spotless XML (#5602)
814f18c7f5 [wpimath] Fix computation of C for DARE (A, C) detectability check (#5609)
ac23f92451 [hal] Add GetTeamNumber (#5596)
a750bee54d [wpimath] Use std::norm() in IsStabilizable() (#5599)
8e2465f8a0 [wpimath] Add arithmetic functions to wheel speeds classes (#5465)
10d4f5b5df [wpimath] Clean up notation in DARE precondition docs (#5595)
b2dd59450b [hal] Fix unfinished/incorrect GetCPUTemp functions (#5598)
99f66b1e24 [wpimath] Replace frc/EigenCore.h typedefs with Eigen's where possible (#5597)
383289bc4b [build] Make custom CMake macros use lowercase (#5594)
45e7720ec1 [build] Add error message when downloading files in CMake (#5593)
4e0d785356 [wpimath] ChassisSpeeds: document that values aren't relative to the robot (NFC) (#5551)
3c04580a57 [commands] ProxyCommand: Use inner command name in unique_ptr constructor (#5570)
cf19102c4a [commands] SelectCommand: Fix leakage and multiple composition bug (#5571)
171375f440 [ntcoreffi] Link to NI libraries (#5589)
89add5d05b Disable flaky tests (#5591)
a8d4b162ab [ntcore] Remove RPC manual tests (#5590)
39a73b5b58 [commands] C++: Add CommandPtr supplier constructor to ProxyCommand (#5572)
36d514eae7 [commands] Refactor C++ ScheduleCommand to use SmallSet (#5568)
52297ffe29 [commands] Add idle command (#5555)
67043a8eeb [wpimath] Add angular jerk unit (#5582)
51b0fb1492 [wpimath] Fix incorrect header inclusion in angular_acceleration.h (#5587)
b7657a8e28 [wpimath] Split WPIMathJNI into logical chunks (#5552)
ea17f90f87 [build] Fix tool builds with multiple arm platforms installed (#5586)
f1d7b05723 [wpimath] Clean up unit formatter (#5584)
d7264ff597 Replace wpi::errs() usage with fmtlib (#5560)
ab3bf39e0e [wpiutil] Upgrade to fmt 10.1.1 (#5585)
165ebe4c79 Upgrade to fmt 10.1.0 (#5326)
8e2a7fd306 Include thirdparty libraries with angle brackets (#5578)
e322ab8e46 [wpimath] Fix docs for DARE ABQRN stabilizability check (NFC) (#5579)
360fb835f4 [upstream_utils] Handle edge case in filename matches (#5576)
9d86624c00 [build] Fix CMake configure warnings (#5577)
969979d6c7 [wpiutil] Update to foonathan memory 0.7-3 (#5573)
0d2d989e84 [wpimath] Update to gcem 1.17.0 (#5575)
cf86af7166 [wpiutil] Update to mpack 1.1.1 (#5574)
a0c029a35b [commands] Fix dangling SelectCommand documentation (NFC) (#5567)
349141b91b [upstream_utils] Document adding a patch (NFC) (#5432)
7889b35b67 [wpimath] Add RamseteController comparison to LTV controller docs (NFC) (#5559)
b3ef536677 [build] Ignore nt/sim json files in spotless (#5565)
ed895815b5 [build] Compile Java with UTF-8 encoding (#5564)
2e4ad35e36 [wpiutil] jni_util: Add JSpan and CriticalJSpan (#5554)
8f3d6a1d4b [wpimath] Remove discretizeAQTaylor() (#5562)
7c20fa1b18 [wpimath] Refactor DARE tests to reduce RAM usage at compile time (#5557)
89e738262c [ntcore] Limit buffer pool size to 64KB per connection (#5485)
96f7fa662e Upgrade Maven dependencies (#5553)
7a2d336d52 [wpinet] Leak multicast handles during windows shutdown (#5550)
f9e2757d8f [wpimath] Use JDoubleArrayRef in all JNI functions (#5546)
0cf6e37dc1 [wpimath] Make LTV controller constructors use faster DARE solver (#5543)
6953a303b3 [build] Fix the windows build with fmt (#5544)
7a37e3a496 [wpimath] Correct Rotation3d::RotateBy doc comment (NFC) (#5541)
186b409e16 [wpimath] Remove internal Eigen header include (#5539)
03764dfe93 [wpimath] Add static matrix support to DARE solver (#5536)
394cfeadbd [wpimath] Use SDA algorithm instead of SSCA for DARE solver (#5526)
a4b7fde767 [wpilib] Add mechanism specific SetState overloads to physics sims (#5534)
8121566258 [wpimath] Fix CoordinateSystem.convert() Transform3d overload (#5532)
b542e01a0b [glass] Fix array crash when clearing existing workspace (#5535)
e2e1b763b2 [wpigui] Fix PFD file dialogs not closing after window closing (#5530)
86d7bbc4e4 [examples] Add Java Examples and Templates for the XRP (#5529)
e8b5d44752 [wpimath] Make Java Quaternion use doubles instead of Vector (#5525)
38c198fa64 [myRobot] Add apriltags to myRobot build (#5528)
00450c3548 [wpimath] Upgrade to EJML 0.42 (#5531)
faf3cecd83 [wpimath] Don't copy Matrix and underlying storage in VecBuilder (#5524)
6b896a38dc [build] Don't enforce WITH_FLAT_INSTALL with MSVC (part 2) (#5517)
c01814b80e [wpiutil] Add C API for DataLog (#5509)
b5bd0771eb [wpimath] Document extrinsic vs intrinsic rotations (NFC) (#5508)
84ed8aec05 [build] Don't enforce WITH_FLAT_INSTALL with MSVC (#5515)
999f677d8c [ntcoreffi] Add WPI_Impl_SetupNowRio to exported symbols (#5510)
338f37d302 Fix header sorting of libssh (#5507)
75cbd9d6d0 [glass] Add background color selector to glass plots (#5506)
e2c190487b [examples] Add flywheel bang-bang controller example (#4071)
c52dad609e [wpinet] WebSocket: Send pong in response to ping (#5498)
e2d17a24a6 [hal] Expose power rail disable and cpu temp functionality (#5477)
3ad5d2e42d [hal,wpiutil] Use HMB for FPGA Timestamps (#5499)
b46a872494 [ntcore] Remove pImpl from implementation (#5480)
d8c59ccc71 [wpimath] Add tests for MathUtil clamp() and interpolate() (#5501)
0552c8621d [glass,ov] Improve Glass and OutlineViewer title bar message (#5502)
90e37a129f [wpiutil,wpimath] Add generic InterpolatingTreeMap (#5372)
d83a6edc20 [wpilib] Update GetMatchTime docs and units (#5232)
6db2c42966 [wpimath] Trajectory: Throw on empty lists of States (#5497)
21439b606c [wpimath] Disallow LTV controller max velocities above 15 m/s (#5495)
7496e0d208 [ntcore] Value: More efficiently store arrays (#5484)
0c93aded8a [wpimath] Change kinematics.ToTwist2d(end - start) to kinematics.ToTwist2d(start, end) (#5493)
815a8403e5 [wpimath] Give infeasible trajectory constraints a better exception message (#5492)
35a8b129d9 [wpimath] Add RotateBy() function to pose classes (#5491)
26d6e68c8f [upstream_utils] Add GCEM to CI (#5483)
6aa469ae45 [wpilib] Document how to create LinearSystem object for physics sim classes (NFC) (#5488)
a01b6467d3 [wpimath] Link to docs on LQR and KF tolerances (#5486)
d814f1d123 [wpimath] Fix copy-paste error from Pose2d docs (NFC) (#5490)
98f074b072 [wpimath] Add folder prefix to geometry includes (#5489)
e9858c10e9 [glass] Add tooltips for NT settings (#5476)
12dda24f06 [examples] Fix C robot template not correctly looping (#5474)
fc75d31755 [apriltag] Update apriltaglib (#5475)
a95994fff6 [wpiutil] timestamp: Call FPGA functions directly (#5235)
2ba8fbb6f4 [wpimath] Improve documentation for SwerveModulePosition::operator- (#5468)
b8cdf97621 [build] Prepare for Windows arm64 builds (#5390)
552f4b76b5 [wpimath] Add FOC-enabled Falcon constants to the DCMotor class (#5469)
1938251436 [examples] Add Feedforward to ElevatorProfiledPid (#5300)
873c2a6c10 [examples] Update ElevatorTrapezoidProfile example (#5466)
99b88be4f3 [wpilib] Reduce usage of NTSendable (#5434)
d125711023 [hal] Fix Java REVPH faults bitfield (take 2) (#5464)
c3fab7f1f2 [ntcore] Don't update timestamp when value is unchanged (#5356)
5ec7f18bdc [wpilib] EventLoop docs: Remove BooleanEvent references (NFC) (#5463)
c065ae1fcf [wpiunits] Add subproject for a Java typesafe unit system (#5371)
44acca7c00 [wpiutil] Add ClassPreloader (#5365)
88b11832ec [hal] Fix Java REVPH faults bitfield (#5148)
fb57d82e52 [ntcore] Enhance Java raw value support
3a6e40a44b [wpiutil] Enhance DataLog Java raw value support
8dae5af271 [wpiutil] Add compile-time string utilities (ct_string) (#5462)
fc56f8049a [wpilib] DriverStation: Change alliance station to use optional (#5229)
ef155438bd [build] Consume libuv via cmake config instead of via pkg-config (#5438)
86e91e6724 [wpimath] Refactor TrapezoidProfile API (#5457)
72a4543493 [wpilib] DutyCycleEncoderSim: Expand API (#5443)
657338715d [wpimath] Add ChassisSpeeds method to fix drifting during compound swerve drive maneuvers (#5425)
1af224c21b Add missing <functional> includes (#5459)
0b91ca6d5a [wpilib] SendableChooser: Add onChange listener (#5458)
6f7cdd460e [wpimath] Pose3d: Switch to JNI for exp and log (#5444)
c69e34c80c [wpimath] ChassisSpeeds: Add arithmetic functions (#5293)
335e7dd89d [wpilib] Simulation: Add ctor parameter to set starting state of mechanism sims (#5288)
14f30752ab [wpilib] Deprecate Accelerometer and Gyro interfaces (#5445)
70b60e3a74 [commands] Trigger: Fix method names in requireNonNullParam (#5454)
593767c8c7 [wpimath] Improve Euler angle calculations in gimbal lock (#5437)
daf022d3da [build] Make devImplementation inherit from implementation (#5450)
9b8d90b852 [examples] Convert the unitless joystick inputs to actual physical units (#5451)
1f6428ab63 [ntcore] Fix undefined comparison behavior when array is empty (#5448)
17eb9161cd Update code owners for removal of old commands (#5447)
3c4b58ae1e [wpinet] Upgrade to libuv 1.46.0 (#5446)
aaea85ff16 [commands] Merge CommandBase into Command and SubsystemBase into Subsystem (#5392)
7ac932996a [ci] Use PAT for workflow dispatch (#5442)
efe1987e8b [ci] Trigger pages repo workflow (#5441)
828bc5276f [wpiutil] Upgrade to LLVM 16.0.6 (#5435)
701df9eb87 [ci] Change documentation publish to single-commit (#5440)
e5452e3f69 [wpiutil] Add WPICleaner and an example how to use it (#4850)
7a099cb02a [commands] Remove deprecated classes and functions (#5409)
b250a03944 [wpilib] Add function to wait for DS Connection (#5230)
a6463ed761 [wpiutil] Fix unused variable warning in release build (#5430)
f031513470 [ntcore] NetworkTable::GetSubTables(): Remove duplicates (#5076)
f8e74e2f7c [hal] Unify PWM simulation Speed, Position, and Raw (#5277)
fd5699b240 Remove references to Drake (#5427)
e2d385d80a [build] cmake: Respect USE_SYSTEM_FMTLIB (#5429)
d37f990ce3 [hal] Fix HAL Relay/Main doc module (NFC) (#5422)
a7a8b874ac [docs] Expand HAL_ENUM in doxygen docs (#5421)
3a61deedde [wpimath] Rotation2d: Only use gcem::hypot when constexpr evaluated (#5419)
96145de7db [examples] Fix formatting (NFC) (#5420)
fffe6a7b9a [examples] Improve Pneumatics example coverage in Solenoid and RapidReactCmdBot examples (#4998)
6b5817836d [wpimath] Add tolerance for some tests (#5416)
3233883f3e [cscore] Fix warnings on macos arm (#5415)
c4fc21838f [commands] Add ConditionalCommand getInterruptionBehavior (#5161)
89fc51f0d4 Add tests for SendableChooser and Command Sendable functionality (#5179)
663bf25aaf [docs] Generate docs for symbols in __cplusplus (#5412)
fe32127ea8 [command] Clean up Command doc comments (NFC) (#5321)
c1a01569b4 [wpilib][hal] PWM Raw using microseconds (#5283)
1fca519fb4 [wpiutil] Remove remnants of ghc fs and tcb_span libraries (#5411)
90602cc135 [github] Update issue template to collect more project info (#5090)
34412ac57e [build] Exclude files in bin from Spotless (#5410)
61aa60f0e3 [wpilib] Add robot callback that is called when the DS is initially connected (#5231)
ebae341a91 [commands] Add test for subsystem registration and periodic (#5408)
5d3a133f9f Remove spaces in NOLINT comments (#5407)
3a0e484691 [wpimath] Fix clang-tidy warnings (#5403)
eb3810c765 [wpiutil] Fix clang-tidy warnings (#5406)
c4dc697192 [hal] WS Simulation: Add message filtering capability (#5395)
0eccc3f247 [ntcore] Fix clang-tidy warnings (#5405)
f4dda4bac0 [hal] Add javadocs for JNI (NFC) (#5298)
1c20c69793 [cscore] Fix clang-tidy warnings (#5404)
1501607e48 [commands] Fix clang-tidy warnings (#5402)
991f4b0f62 [wpimath] PIDController: Add IZone (#5315)
f5b0d1484b [wpimath] Add isNear method to MathUtil (#5353)
2ce248f66c [hal] Fix clang-tidy warnings (#5401)
5fc4aee2d2 [wpimath] SwerveDriveKinematics: Rename currentChassisSpeed to desiredChassisSpeed (#5393)
50b90ceb54 [wpimath] SwerveDriveKinematics: Add reset method (#5398)
316cd2a453 [commands] Notify DriverStationSim in CommandTestBaseWithParam (#5400)
d4ea5fa902 [cscore] VideoMode: Add equals override (Java) (#5397)
d6bd72d738 [wpimath] ProfiledPIDController: Add getConstraints (#5399)
25ad5017a9 [wpimath] Refactor kinematics, odometry, and pose estimator (#5355)
5c2addda0f [doc] Add missing pneumatics docs (NFC) (#5389)
c3e04a6ea2 Fix loading tests on macos 12 (#5388)
d5ed9fb859 [wpimath] Create separate archive with just units headers (#5383)
901ab693d4 [wpimath] Use UtilityClassTest for more utility classes (#5384)
9d53231b01 [wpilib] DataLogManager: Add warning for low storage space (#5364)
d466933963 [wpiutil] Group doxygen into MPack module (#5380)
652d1c44e3 [wpiutil] Upgrade to macOS 12 to remove concept shims (#5379)
6414be0e5d [wpimath] Group units doxygen modules (#5382)
7ab5800487 [wpiutil] Fix docs typo in SmallVector (#5381)
59905ea721 Replace WPI_DEPRECATED() macro with [[deprecated]] attribute (#5373)
753cb49a5e [ntcore] Fix doxygen module in generated C types (NFC) (#5374)
1c00a52b67 [hal] Expose CAN timestamp base clock (#5357)
91cbcea841 Replace SFINAE with concepts (#5361)
d57d1a4598 [wpimath] Remove unnecessary template argument from unit formatter (#5367)
5acc5e22aa [wpimath] Only compute eigenvalues with EigenSolvers (#5369)
d3c9316a97 extend shuffleboard test timeout (#5377)
1ea868081a [ci] Fix /format command (#5376)
5fac18ff4a Update formatting to clang-format 16 (#5370)
a94a998002 [wpimath] Generalize Eigen formatter (#5360)
125f6ea101 [wpimath] Make SwerveDriveKinematics::ToChassisSpeeds() take const-ref argument (#5363)
51066a5a8a [wpimath] Move unit formatters into units library (#5358)
282c032b60 [wpilibc] Add unit-aware Joystick.GetDirection() (#5319)
073d19cb69 [build] Fix CMake warning (#5359)
01490fc77b [wpiutil] DataLog: Add documentation for append methods (NFC) (#5348)
c9b612c986 [wpilibcExamples] Make C++ state-space elevator KF and LQR match Java (#5346)
eed1e6e3cb [wpimath] Replace DiscretizeAQTaylor() with DiscretizeAQ() (#5344)
c976f40364 [readme] Document how to run examples in simulation (#5340)
4d28bdc19e [ci] Update Github Pages deploy action parameters (#5343)
e0f851871f [ci] Fix github pages deploy version (#5342)
063c8cbedc Run wpiformat (NFC) (#5341)
96e41c0447 [ci] Update deploy and sshagent actions (#5338)
fd294bdd71 [build] Fix compilation with GCC 13 (#5322)
d223e4040b [dlt] Add delete without download functionality (#5329)
abc19bcb43 [upstream_utils] Zero out commit hashes and show 40 digits in index hashes (#5336)
e909f2e687 [build] Update gradle cache repo name (#5334)
52bd5b972d [wpimath] Rewrite DARE solver (#5328)
3876a2523a [wpimath] Remove unused MatrixImpl() function (#5330)
c82fcb1975 [wpiutil] Add reflection based cleanup helper (#4919)
15ba95df7e [wpiutil] Use std::filesystem (#4941)
77c2124fc5 [wpimath] Remove Eigen's custom STL types (#4945)
27fb47ab10 [glass] Field2D: Embed standard field images (#5159)
102e4f2566 [wpilib] Remove deprecated and broken SPI methods (#5249)
463a90f1df [wpilib, hal] Add function to read the RSL state (#5312)
7a90475eec [wpilib] Update RobotBase documentation (NFC) (#5320)
218cfea16b [wpilib] DutyCycleEncoder: Fix reset behavior (#5287)
91392823ff [build] Update to gradle 8.1 (#5303)
258b7cc48b [wpilibj] Filesystem.getDeployDirectory(): Strip JNI path from user.dir (#5317)
26cc43bee1 [wpilib] Add documentation to SPI mode enum (NFC) (#5324)
ac4da9b1cb [hal] Add HAL docs for Addressable LED (NFC) (#5304)
21d4244cf7 [wpimath] Fix DCMotor docs (NFC) (#5309)
1dff81bea7 [hal] Miscellaneous HAL doc fixes (NFC) (#5306)
7ce75574bf [wpimath] Upgrade to Drake v1.15.0 (#5310)
576bd646ae [hal] Add CANManufacturer for Redux Robotics (#5305)
ee3b4621e5 [commands] Add onlyWhile and onlyIf (#5291)
40ca094686 [commands] Fix RepeatCommand calling end() twice (#5261)
9cbeb841f5 [rtns] Match imaging tool capitalization (#5265)
a63d06ff77 [examples] Add constants to java gearsbot example (#5248)
b6c43322a3 [wpilibc] XboxController: Add return tag to docs (NFC) (#5246)
5162d0001c [hal] Fix and document addressable LED timings (#5272)
90fabe9651 [wpilibj] Use method references in drive class initSendable() (#5251)
24828afd11 [wpimath] Fix desaturateWheelSpeeds to account for negative speeds (#5269)
e099948a77 [wpimath] Clean up rank notation in docs (NFC) (#5274)
fd2d8cb9c1 [hal] Use std::log2() for base-2 logarithm (#5278)
ba8c64bcff [wpimath] Fix misspelled Javadoc parameters in pose estimators (NFC) (#5292)
f53c6813d5 [wpimath] Patch Eigen warnings (#5290)
663703d370 [gitattributes] Mark json files as lf text files (#5256)
aa34aacf6e [wpilib] Shuffleboard: Keep duplicates on SelectTab() (#5198)
63512bbbb8 [wpimath] Fix potential divide-by-zero in RKDP (#5242)
9227b2166e [wpilibj] DriverStation: Fix joystick data logs (#5240)
fbf92e9190 [wpinet] ParallelTcpConnector: don't connect to duplicate addresses (#5169)
2108a61362 [ntcore] NT4 client: close timed-out connections (#5175)
0a66479693 [ntcore] Optimize scan of outgoing messages (#5227)
b510c17ef6 [hal] Fix RobotController.getComments() mishandling quotes inside the comments string (#5197)
e7a7eb2e93 [commands] WaitCommand: Remove subclass doc note (NFC) (#5200)
a465f2d8f0 [examples] Shuffleboard: Correct parameter order (#5204)
a3364422fa LICENSE.md: Bump year to 2023 (#5195)
df3242a40a [wpimath] Fix NaN in C++ MakeCostMatrix() that takes an array (#5194)
00abb8c1e0 [commands] RamseteCommand: default-initialize m_prevSpeeds (#5188)
c886273fd7 [wpilibj] DutyCycleEncoder.setDistancePerRotation(): fix simulation (#5147)
53b5fd2ace [ntcore] Use int64 for datalog type string (#5186)
56b758320f [wpilib] DataLogManager: increase time for datetime to be valid (#5185)
08f298e4cd [wpimath] Fix Pose3d log returning Twist3d NaN for theta between 1E-8 and 1E-7 (#5168)
6d0c5b19db [commands] CommandScheduler.isComposed: Remove incorrect throws clause (NFC) (#5183)
0d22cf5ff7 [wpilib] Fix enableLiveWindowInTest crashing in disabled (#5173)
32ec5b3f75 [wpilib] Add isTestEnabled and minor docs cleanup (#5172)
e5c4c6b1a7 [wpimath] Fix invalid iterator access in TimeInterpolatableBuffer (#5138)
099d048d9e [wpimath] Fix Pose3d log returning Twist3d NaN for theta between 1E-9 and 1E-8 (#5143)
4af84a1c12 Fix Typos (NFC) (#5137)
ce3686b80d [wpimath] Check LTV controller max velocity precondition (#5142)
4b0eecaee0 [commands] Subsystem: Add default command removal method (#5064)
edf4ded412 [wpilib] PH: Revert to 5V rail being fixed 5V (#5122)
4c46b6aff9 [wpilibc] Fix DataLogManager crash on exit in sim (#5125)
490ca4a68a [wpilibc] Fix XboxController::GetBackButton doc (NFC) (#5131)
cbb5b0b802 [hal] Simulation: Fix REV PH solenoids 8+ (#5132)
bb7053d9ee [hal] Fix HAL_GetRuntimeType being slow on the roboRIO (#5130)
9efed9a533 Update .clang-format to c++20 (#5121)
dbbfe1aed2 [wpilib] Use PH voltage to calc Analog pressure switch threshold (#5115)
de65a135c3 [wpilib] DutyCycleEncoderSim: Add channel number constructor (#5118)
3e9788cdff [docs] Strip path from generated NT docs (#5119)
ecb072724d [ntcore] Client::Disconnect(): actually close connection (#5113)
0d462a4561 [glass] NT view: Change string/string array to quoted (#5111)
ba37986561 [ntcore] NetworkClient::Disconnect: Add null check (#5112)
25ab9cda92 [glass,ov] Provide menu item to create topic from root (#5110)
2f6251d4a6 [glass] Set default value when publishing new topic (#5109)
e9a7bed988 [wpimath] Add timestamp getter to MathShared (#5091)
9cc14bbb43 [ntcore] Add stress test to dev executable (#5107)
8068369542 [wpinet] uv: Stop creating handles when closing loop (#5102)
805c837a42 [ntcore] Fix use-after-free in server (#5101)
fd18577ba0 [commands] Improve documentation of addRequirements (NFC) (#5103)
74dea9f05e [wpimath] Fix exception for empty pose buffer in pose estimators (#5106)
9eef79d638 [wpilib] PneumaticHub: Document range of enableCompressorAnalog (NFC) (#5099)
843574a810 [ntcore] Use wpi::Now instead of loop time for transmit time
226ef35212 [wpinet] WebSocket: Reduce server send frame overhead
b30664d630 [ntcore] Reduce initial connection overhead
804e5ce236 [examples] MecanumDrive: Fix axis comment in C++ example (NFC) (#5096)
49af88f2bb [examples] ArmSimulation: Fix flaky test (#5093)
d56314f866 [wpiutil] Disable mock time on the Rio (#5092)
43975ac7cc [examples] ArmSimulation, ElevatorSimulation: Extract mechanism to class (#5052)
5483464158 [examples, templates] Improve descriptions (NFC) (#5051)
785e7dd85c [wpilibc] SendableChooser: static_assert copy- and default-constructibility (#5078)
e57ded8c39 [ntcore] Improve disconnect error reporting (#5085)
01f0394419 [wpinet] Revert WebSocket: When Close() is called, call closed immediately (#5084)
59be120982 [wpimath] Fix Pose3d exp()/log() and add rotation vector constructor to Rotation3d (#5072)
37f065032f [wpilib] Refactor TimedRobot tests (#5068)
22a170bee7 [wpilib] Add Notifier test (#5070)
2f310a748c [wpimath] Fix DCMotor.getSpeed() (#5061)
b43ec87f57 [wpilib] ElevatorSim: Fix WouldHitLimit methods (#5057)
19267bef0c [ntcore] Output warning on property set on unpublished topic (#5059)
84cbd48d84 [ntcore] Handle excludeSelf on SetDefault (#5058)
1f35750865 [cameraserver] Add GetInstance() to all functions (#5054)
8230fc631d [wpilib] Revert throw on nonexistent SimDevice name in SimDeviceSim (#5053)
b879a6f8c6 [wpinet] WebSocket: When Close() is called, call closed immediately (#5047)
49459d3e45 [ntcore] Change wire timeout to fixed 1 second (#5048)
4079eabe9b [wpimath] Discard stale pose estimates (#5045)
fe5d226a19 [glass] Fix option for debug-level NT logging (#5049)
b7535252c2 [ntcore] Don't leak buffers in rare WS shutdown case (#5046)
b61ac6db33 [ntcore] Add client disconnect function (#5022)
7b828ce84f [wpimath] Add nearest to Pose2d and Translation2d (#4882)
08a536291b [examples] Improvements to Elevator Simulation Example (#4937)
193a10d020 [wpigui] Limit frame rate to 120 fps by default (#5030)
7867bbde0e [wpilib] Clarify DS functions provided by FMS (NFC) (#5043)
fa7c01b598 [glass] Add option for debug-level NT logging (#5007)
2b81610248 [wpiutil] Add msgpack to datalog Python example (#5032)
a4a369b8da CONTRIBUTING.md: Add unicodeit CLI to math docs guidelines (#5031)
d991f6e435 [wpilib] Throw on nonexistent SimDevice name in SimDeviceSim constructor (#5041)
a27a047ae8 [hal] Check for null in getSimDeviceName JNI (#5038)
2f96cae31a [examples] Hatchbots: Add telemetry (#5011)
83ef8f9658 [simulation] GUI: Fix buffer overflow in joystick axes copy (#5036)
4054893669 [commands] Fix C++ Select() factory (#5024)
f75acd11ce [commands] Use Timer.restart() (#5023)
8bf67b1b33 [wpimath] PIDController::Calculate(double, double): update setpoint flag (#5021)
49bb1358d8 [wpiutil] MemoryBuffer: Fix GetMemoryBufferForStream (#5017)
9c4c07c0f9 [wpiutil] Remove NDEBUG check for debug-level logging (#5018)
1a47cc2e86 [ntcore] Use full handle when subscribing (#5013)
7cd30cffbc Ignore networktables.json (#5006)
92aecab2ef [commands] Command controllers are not subclasses (NFC) (#5000)
8785bba080 [ntcore] Special-case default timestamps (#5003)
9e5b7b8040 [ntcore] Handle topicsonly followed by value subscribe (#4991)
917906530a [wpilib] Add Timer::Restart() (#4963)
00aa66e4fd [wpimath] Remove extraneous assignments from DiscretizeAB() (#4967)
893320544a [examples] C++ RamseteCommand: Fix units (#4954)
b95d0e060d [wpilib] XboxController: Fix docs discrepancy (NFC) (#4993)
008232b43c [ntcore] Write empty persistent file if none found (#4996)
522be348f4 [examples] Rewrite tags (NFC) (#4961)
d48a83dee2 [wpimath] Update Wikipedia links for quaternion to Euler angle conversion (NFC) (#4995)
504fa22143 [wpimath] Workaround intellisense Eigen issue (#4992)
b2b25bf09f [commands] Fix docs inconsistency for toggleOnFalse(Command) (NFC) (#4978)
ce3dc4eb3b [hal] Properly use control word that is in sync with DS data (#4989)
1ea48caa7d [wpilib] Fix C++ ADXRS450 and Java SPI gyro defs (#4988)
fb101925a7 [build] Include wpimathjni in commands binaries (#4981)
657951f6dd [starter] Add a process starter for use by the installer for launching tools (#4931)
a60ca9d71c [examples] Update AprilTag field load API usage (#4975)
f8a45f1558 [wpimath] Remove print statements from tests (#4977)
ecba8b99a8 [examples] Fix swapped arguments in MecanumControllerCommand example (#4976)
e95e88fdf9 [examples] Add comment to drivedistanceoffboard example (#4877)
371d15dec3 [examples] Add Computer Vision Pose Estimation and Latency Compensation Example (#4901)
cb9b8938af [sim] Enable docking in the GUI (#4960)
3b084ecbe0 [apriltag] AprilTagFieldLayout: Improve API shape for loading builtin JSONs (#4949)
27ba096ea1 [wpilib] Fix MOI calculation error in SingleJointedArmSim (#4968)
42c997a3c4 [wpimath] Fix Pose3d exponential and clean up Pose3d logarithm (#4970)
5f1a025f27 [wpilibj] Fix typo in MecanumDrive docs (NFC) (#4969)
0ebf79b54c [wpimath] Fix typo in Pose3d::Exp() docs (NFC) (#4966)
a8c465f3fb [wpimath] HolonomicDriveController: Add getters for the controllers (#4948)
a7b1ab683d [wpilibc] Add unit test for fast deconstruction of GenericHID (#4953)
bd6479dc29 [build] Add Spotless for JSON (#4956)
5cb0340a8c [hal, wpilib] Load joystick values upon code initialization (#4950)
ab0e8c37a7 [readme] Update build requirements (NFC) (#4947)
b74ac1c645 [build] Add apriltag to C++ cmake example builds (#4944)
cf1a411acf [examples] Add example programs for AprilTags detection (#4932)
1e05b21ab5 [wpimath] Fix PID atSetpoint to not return true prematurely (#4906)
e5a6197633 [wpimath] Fix SwerveDriveKinematics not initializing a new array each time (#4942)
039edcc23f [ntcore] Queue current value on subscriber creation (#4938)
f7f19207e0 [wpimath] Allow multiple vision measurements from same timestamp (#4917)
befd12911c [commands] Delete UB-causing rvalue variants of CommandPtr methods (#4923)
34519de60a [commands] Fix spacing in command composition exception (#4924)
dc4355c031 [hal] Add handle constructor and name getters for sim devices (#4925)
53d8d33bca [hal, wpilibj] Add missing distance per pulse functions to EncoderSim (#4928)
530ae40614 [apriltag] Explain what April tag poses represent (NFC) (#4930)
79f565191e [examples] DigitalCommunication, I2CCommunication: Add tests (#4865)
2cd9be413f [wpilib, examples] Cleanup PotentiometerPID, Ultrasonic, UltrasonicPID examples (#4893)
babb0c1fcf [apriltag] Add 2023 field layout JSON (#4912)
330ba45f9c [wpimath] Fix swerve kinematics util classes equals function (#4907)
51272ef6b3 [fieldImages] Add 2023 field (#4915)
0d105ab771 [commands] Deduplicate command test utils (#4897)
cf4235ea36 [wpiutil] Guard MSVC pragma in SymbolExports.h (#4911)
2d4b7b9147 [build] Update opencv version in opencv.gradle (#4909)
aec6f3d506 [ntcore] Fix client flush behavior (#4903)
bfe346c76a [build] Fix cmake java resources (#4898)

Change-Id: Ia1dd90fe42c6cd5df281b8a5b710e136f54355f4
git-subtree-dir: third_party/allwpilib
git-subtree-split: f1a82828fed8950f9a3f1586c44327027627a0c8
Signed-off-by: James Kuszmaul <jabukuszmaul+collab@gmail.com>
diff --git a/wpilibNewCommands/.styleguide b/wpilibNewCommands/.styleguide
index 3f20ec8..7c8d7a7 100644
--- a/wpilibNewCommands/.styleguide
+++ b/wpilibNewCommands/.styleguide
@@ -17,6 +17,7 @@
   ^cscore
   ^fmt/
   ^frc/
+  ^gtest/
   ^hal/
   ^imgui
   ^mockdata/
diff --git a/wpilibNewCommands/CMakeLists.txt b/wpilibNewCommands/CMakeLists.txt
index 716c508..34a6bb1 100644
--- a/wpilibNewCommands/CMakeLists.txt
+++ b/wpilibNewCommands/CMakeLists.txt
@@ -22,8 +22,22 @@
   else()
       set (wpilibNewCommands_config_dir share/wpilibNewCommands)
   endif()
+endif()
 
-  install(FILES wpilibNewCommands-config.cmake DESTINATION ${wpilibNewCommands_config_dir})
+if (WITH_JAVA_SOURCE)
+  find_package(Java REQUIRED)
+  include(UseJava)
+  file(GLOB WPILIBNEWCOMMANDS_SOURCES src/main/java/edu/wpi/first/wpilibj2/command/*.java)
+  file(GLOB WPILIBNEWCOMMANDS_BUTTON_SOURCES src/main/java/edu/wpi/first/wpilibj2/command/button*.java)
+  add_jar(wpilibNewCommands_src_jar
+  RESOURCES NAMESPACE "edu/wpi/first/wpilibj2/command" ${WPILIBNEWCOMMANDS_SOURCES}
+  NAMESPACE "edu/wpi/first/wpilibj2/command/button" ${WPILIBNEWCOMMANDS_BUTTON_SOURCES}
+  OUTPUT_NAME wpilibNewCommands-sources)
+
+  get_property(WPILIBNEWCOMMANDS_SRC_JAR_FILE TARGET wpilibNewCommands_src_jar PROPERTY JAR_FILE)
+  install(FILES ${WPILIBNEWCOMMANDS_SRC_JAR_FILE} DESTINATION "${java_lib_dest}")
+
+  set_property(TARGET wpilibNewCommands_src_jar PROPERTY FOLDER "java")
 endif()
 
 file(GLOB_RECURSE wpilibNewCommands_native_src src/main/native/cpp/*.cpp)
@@ -39,10 +53,10 @@
                             $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
                             $<INSTALL_INTERFACE:${include_dest}/wpilibNewCommands>)
 
-install(TARGETS wpilibNewCommands EXPORT wpilibNewCommands DESTINATION "${main_lib_dest}")
+install(TARGETS wpilibNewCommands EXPORT wpilibNewCommands)
 install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/wpilibNewCommands")
 
-if (MSVC OR FLAT_INSTALL_WPILIB)
+if (FLAT_INSTALL_WPILIB)
      set(wpilibNewCommands_config_dir ${wpilib_dest})
  else()
      set(wpilibNewCommands_config_dir share/wpilibNewCommands)
diff --git a/wpilibNewCommands/WPILibNewCommands.json b/wpilibNewCommands/WPILibNewCommands.json
index da4bc52..67bf389 100644
--- a/wpilibNewCommands/WPILibNewCommands.json
+++ b/wpilibNewCommands/WPILibNewCommands.json
@@ -1,37 +1,38 @@
-{

-  "fileName": "WPILibNewCommands.json",

-  "name": "WPILib-New-Commands",

-  "version": "1.0.0",

-  "uuid": "111e20f7-815e-48f8-9dd6-e675ce75b266",

-  "mavenUrls": [],

-  "jsonUrl": "",

-  "javaDependencies": [

-      {

-          "groupId": "edu.wpi.first.wpilibNewCommands",

-          "artifactId": "wpilibNewCommands-java",

-          "version": "wpilib"

-      }

-  ],

-  "jniDependencies": [],

-  "cppDependencies": [

-      {

-          "groupId": "edu.wpi.first.wpilibNewCommands",

-          "artifactId": "wpilibNewCommands-cpp",

-          "version": "wpilib",

-          "libName": "wpilibNewCommands",

-          "headerClassifier": "headers",

-          "sourcesClassifier": "sources",

-          "sharedLibrary": true,

-          "skipInvalidPlatforms": true,

-          "binaryPlatforms": [

-              "linuxathena",

-              "linuxarm32",

-              "linuxarm64",

-              "windowsx86-64",

-              "windowsx86",

-              "linuxx86-64",

-              "osxuniversal"

-          ]

-      }

-  ]

-}

+{
+  "fileName": "WPILibNewCommands.json",
+  "name": "WPILib-New-Commands",
+  "version": "1.0.0",
+  "uuid": "111e20f7-815e-48f8-9dd6-e675ce75b266",
+  "frcYear": "2024",
+  "mavenUrls": [],
+  "jsonUrl": "",
+  "javaDependencies": [
+    {
+      "groupId": "edu.wpi.first.wpilibNewCommands",
+      "artifactId": "wpilibNewCommands-java",
+      "version": "wpilib"
+    }
+  ],
+  "jniDependencies": [],
+  "cppDependencies": [
+    {
+      "groupId": "edu.wpi.first.wpilibNewCommands",
+      "artifactId": "wpilibNewCommands-cpp",
+      "version": "wpilib",
+      "libName": "wpilibNewCommands",
+      "headerClassifier": "headers",
+      "sourcesClassifier": "sources",
+      "sharedLibrary": true,
+      "skipInvalidPlatforms": true,
+      "binaryPlatforms": [
+        "linuxathena",
+        "linuxarm32",
+        "linuxarm64",
+        "windowsx86-64",
+        "windowsx86",
+        "linuxx86-64",
+        "osxuniversal"
+      ]
+    }
+  ]
+}
diff --git a/wpilibNewCommands/build.gradle b/wpilibNewCommands/build.gradle
index 82fc470..d60fcde 100644
--- a/wpilibNewCommands/build.gradle
+++ b/wpilibNewCommands/build.gradle
@@ -1,6 +1,6 @@
 ext {
     nativeName = 'wpilibNewCommands'
-    devMain = 'edu.wpi.first.wpilibj.commands.DevMain'
+    devMain = 'edu.wpi.first.wpilibj2.commands.DevMain'
 }
 
 evaluationDependsOn(':ntcore')
@@ -21,13 +21,6 @@
     implementation project(':hal')
     implementation project(':wpimath')
     implementation project(':wpilibj')
-    devImplementation project(':wpiutil')
-    devImplementation project(':wpinet')
-    devImplementation project(':ntcore')
-    devImplementation project(':cscore')
-    devImplementation project(':hal')
-    devImplementation project(':wpimath')
-    devImplementation project(':wpilibj')
     testImplementation 'org.mockito:mockito-core:4.1.0'
 }
 
@@ -67,6 +60,7 @@
                 project(':ntcore').addNtcoreJniDependency(it)
                 lib project: ':wpinet', library: 'wpinetJNIShared', linkage: 'shared'
                 lib project: ':wpiutil', library: 'wpiutilJNIShared', linkage: 'shared'
+                lib project: ':wpimath', library: 'wpimathJNIShared', linkage: 'shared'
                 project(':hal').addHalJniDependency(it)
             }
 
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/Command.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/Command.java
index 0383099..a913b55 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/Command.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/Command.java
@@ -7,6 +7,10 @@
 import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
 
 import edu.wpi.first.util.function.BooleanConsumer;
+import edu.wpi.first.util.sendable.Sendable;
+import edu.wpi.first.util.sendable.SendableBuilder;
+import edu.wpi.first.util.sendable.SendableRegistry;
+import java.util.HashSet;
 import java.util.Set;
 import java.util.function.BooleanSupplier;
 
@@ -20,12 +24,19 @@
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-public interface Command {
+public abstract class Command implements Sendable {
+  protected Set<Subsystem> m_requirements = new HashSet<>();
+
+  protected Command() {
+    String name = getClass().getName();
+    SendableRegistry.add(this, name.substring(name.lastIndexOf('.') + 1));
+  }
+
   /** The initial subroutine of a command. Called once when the command is initially scheduled. */
-  default void initialize() {}
+  public void initialize() {}
 
   /** The main body of a command. Called repeatedly while the command is scheduled. */
-  default void execute() {}
+  public void execute() {}
 
   /**
    * The action to take when the command ends. Called when either the command finishes normally, or
@@ -36,7 +47,7 @@
    *
    * @param interrupted whether the command was interrupted/canceled
    */
-  default void end(boolean interrupted) {}
+  public void end(boolean interrupted) {}
 
   /**
    * Whether the command has finished. Once a command finishes, the scheduler will call its end()
@@ -44,7 +55,7 @@
    *
    * @return whether the command has finished.
    */
-  default boolean isFinished() {
+  public boolean isFinished() {
     return false;
   }
 
@@ -60,12 +71,67 @@
    * @return the set of subsystems that are required
    * @see InterruptionBehavior
    */
-  Set<Subsystem> getRequirements();
+  public Set<Subsystem> getRequirements() {
+    return m_requirements;
+  }
+
+  /**
+   * Adds the specified subsystems to the requirements of the command. The scheduler will prevent
+   * two commands that require the same subsystem from being scheduled simultaneously.
+   *
+   * <p>Note that the scheduler determines the requirements of a command when it is scheduled, so
+   * this method should normally be called from the command's constructor.
+   *
+   * @param requirements the requirements to add
+   */
+  public final void addRequirements(Subsystem... requirements) {
+    for (Subsystem requirement : requirements) {
+      m_requirements.add(requireNonNullParam(requirement, "requirement", "addRequirements"));
+    }
+  }
+
+  /**
+   * Gets the name of this Command.
+   *
+   * <p>By default, the simple class name is used. This can be changed with {@link
+   * #setName(String)}.
+   *
+   * @return The display name of the Command
+   */
+  public String getName() {
+    return SendableRegistry.getName(this);
+  }
+
+  /**
+   * Sets the name of this Command.
+   *
+   * @param name The display name of the Command.
+   */
+  public void setName(String name) {
+    SendableRegistry.setName(this, name);
+  }
+
+  /**
+   * Gets the subsystem name of this Command.
+   *
+   * @return Subsystem name
+   */
+  public String getSubsystem() {
+    return SendableRegistry.getSubsystem(this);
+  }
+
+  /**
+   * Sets the subsystem name of this Command.
+   *
+   * @param subsystem subsystem name
+   */
+  public void setSubsystem(String subsystem) {
+    SendableRegistry.setSubsystem(this, subsystem);
+  }
 
   /**
    * Decorates this command with a timeout. If the specified timeout is exceeded before the command
-   * finishes normally, the command will be interrupted and un-scheduled. Note that the timeout only
-   * applies to the command returned by this method; the calling command is not itself changed.
+   * finishes normally, the command will be interrupted and un-scheduled.
    *
    * <p>Note: This decorator works by adding this command to a composition. The command the
    * decorator was called on cannot be scheduled independently or be added to a different
@@ -76,15 +142,13 @@
    * @param seconds the timeout duration
    * @return the command with the timeout added
    */
-  default ParallelRaceGroup withTimeout(double seconds) {
+  public ParallelRaceGroup withTimeout(double seconds) {
     return raceWith(new WaitCommand(seconds));
   }
 
   /**
    * Decorates this command with an interrupt condition. If the specified condition becomes true
-   * before the command finishes normally, the command will be interrupted and un-scheduled. Note
-   * that this only applies to the command returned by this method; the calling command is not
-   * itself changed.
+   * before the command finishes normally, the command will be interrupted and un-scheduled.
    *
    * <p>Note: This decorator works by adding this command to a composition. The command the
    * decorator was called on cannot be scheduled independently or be added to a different
@@ -94,16 +158,15 @@
    *
    * @param condition the interrupt condition
    * @return the command with the interrupt condition added
+   * @see #onlyWhile(BooleanSupplier)
    */
-  default ParallelRaceGroup until(BooleanSupplier condition) {
+  public ParallelRaceGroup until(BooleanSupplier condition) {
     return raceWith(new WaitUntilCommand(condition));
   }
 
   /**
-   * Decorates this command with an interrupt condition. If the specified condition becomes true
-   * before the command finishes normally, the command will be interrupted and un-scheduled. Note
-   * that this only applies to the command returned by this method; the calling command is not
-   * itself changed.
+   * Decorates this command with a run condition. If the specified condition becomes false before
+   * the command finishes normally, the command will be interrupted and un-scheduled.
    *
    * <p>Note: This decorator works by adding this command to a composition. The command the
    * decorator was called on cannot be scheduled independently or be added to a different
@@ -111,13 +174,12 @@
    * commands with {@link CommandScheduler#removeComposedCommand(Command)}. The command composition
    * returned from this method can be further decorated without issue.
    *
-   * @param condition the interrupt condition
-   * @return the command with the interrupt condition added
-   * @deprecated Replace with {@link #until(BooleanSupplier)}
+   * @param condition the run condition
+   * @return the command with the run condition added
+   * @see #until(BooleanSupplier)
    */
-  @Deprecated(since = "2023")
-  default ParallelRaceGroup withInterrupt(BooleanSupplier condition) {
-    return until(condition);
+  public ParallelRaceGroup onlyWhile(BooleanSupplier condition) {
+    return until(() -> !condition.getAsBoolean());
   }
 
   /**
@@ -133,7 +195,7 @@
    * @param requirements the required subsystems
    * @return the decorated command
    */
-  default SequentialCommandGroup beforeStarting(Runnable toRun, Subsystem... requirements) {
+  public SequentialCommandGroup beforeStarting(Runnable toRun, Subsystem... requirements) {
     return beforeStarting(new InstantCommand(toRun, requirements));
   }
 
@@ -149,7 +211,7 @@
    * @param before the command to run before this one
    * @return the decorated command
    */
-  default SequentialCommandGroup beforeStarting(Command before) {
+  public SequentialCommandGroup beforeStarting(Command before) {
     return new SequentialCommandGroup(before, this);
   }
 
@@ -166,7 +228,7 @@
    * @param requirements the required subsystems
    * @return the decorated command
    */
-  default SequentialCommandGroup andThen(Runnable toRun, Subsystem... requirements) {
+  public SequentialCommandGroup andThen(Runnable toRun, Subsystem... requirements) {
     return andThen(new InstantCommand(toRun, requirements));
   }
 
@@ -183,7 +245,7 @@
    * @param next the commands to run next
    * @return the decorated command
    */
-  default SequentialCommandGroup andThen(Command... next) {
+  public SequentialCommandGroup andThen(Command... next) {
     SequentialCommandGroup group = new SequentialCommandGroup(this);
     group.addCommands(next);
     return group;
@@ -203,7 +265,7 @@
    * @param parallel the commands to run in parallel
    * @return the decorated command
    */
-  default ParallelDeadlineGroup deadlineWith(Command... parallel) {
+  public ParallelDeadlineGroup deadlineWith(Command... parallel) {
     return new ParallelDeadlineGroup(this, parallel);
   }
 
@@ -221,7 +283,7 @@
    * @param parallel the commands to run in parallel
    * @return the decorated command
    */
-  default ParallelCommandGroup alongWith(Command... parallel) {
+  public ParallelCommandGroup alongWith(Command... parallel) {
     ParallelCommandGroup group = new ParallelCommandGroup(this);
     group.addCommands(parallel);
     return group;
@@ -241,35 +303,13 @@
    * @param parallel the commands to run in parallel
    * @return the decorated command
    */
-  default ParallelRaceGroup raceWith(Command... parallel) {
+  public ParallelRaceGroup raceWith(Command... parallel) {
     ParallelRaceGroup group = new ParallelRaceGroup(this);
     group.addCommands(parallel);
     return group;
   }
 
   /**
-   * Decorates this command to run perpetually, ignoring its ordinary end conditions. The decorated
-   * command can still be interrupted or canceled.
-   *
-   * <p>Note: This decorator works by adding this command to a composition. The command the
-   * decorator was called on cannot be scheduled independently or be added to a different
-   * composition (namely, decorators), unless it is manually cleared from the list of composed
-   * commands with {@link CommandScheduler#removeComposedCommand(Command)}. The command composition
-   * returned from this method can be further decorated without issue.
-   *
-   * @return the decorated command
-   * @deprecated PerpetualCommand violates the assumption that execute() doesn't get called after
-   *     isFinished() returns true -- an assumption that should be valid. This was unsafe/undefined
-   *     behavior from the start, and RepeatCommand provides an easy way to achieve similar end
-   *     results with slightly different (and safe) semantics.
-   */
-  @SuppressWarnings("removal") // PerpetualCommand
-  @Deprecated(forRemoval = true, since = "2023")
-  default PerpetualCommand perpetually() {
-    return new PerpetualCommand(this);
-  }
-
-  /**
    * Decorates this command to run repeatedly, restarting it when it ends, until this command is
    * interrupted. The decorated command can still be canceled.
    *
@@ -281,7 +321,7 @@
    *
    * @return the decorated command
    */
-  default RepeatCommand repeatedly() {
+  public RepeatCommand repeatedly() {
     return new RepeatCommand(this);
   }
 
@@ -292,7 +332,7 @@
    *
    * @return the decorated command
    */
-  default ProxyCommand asProxy() {
+  public ProxyCommand asProxy() {
     return new ProxyCommand(this);
   }
 
@@ -301,20 +341,46 @@
    * running and the condition changes to true, the command will not stop running. The requirements
    * of this command will be kept for the new conditional command.
    *
+   * <p>Note: This decorator works by adding this command to a composition. The command the
+   * decorator was called on cannot be scheduled independently or be added to a different
+   * composition (namely, decorators), unless it is manually cleared from the list of composed
+   * commands with {@link CommandScheduler#removeComposedCommand(Command)}. The command composition
+   * returned from this method can be further decorated without issue.
+   *
    * @param condition the condition that will prevent the command from running
    * @return the decorated command
+   * @see #onlyIf(BooleanSupplier)
    */
-  default ConditionalCommand unless(BooleanSupplier condition) {
+  public ConditionalCommand unless(BooleanSupplier condition) {
     return new ConditionalCommand(new InstantCommand(), this, condition);
   }
 
   /**
+   * Decorates this command to only run if this condition is met. If the command is already running
+   * and the condition changes to false, the command will not stop running. The requirements of this
+   * command will be kept for the new conditional command.
+   *
+   * <p>Note: This decorator works by adding this command to a composition. The command the
+   * decorator was called on cannot be scheduled independently or be added to a different
+   * composition (namely, decorators), unless it is manually cleared from the list of composed
+   * commands with {@link CommandScheduler#removeComposedCommand(Command)}. The command composition
+   * returned from this method can be further decorated without issue.
+   *
+   * @param condition the condition that will allow the command to run
+   * @return the decorated command
+   * @see #unless(BooleanSupplier)
+   */
+  public ConditionalCommand onlyIf(BooleanSupplier condition) {
+    return unless(() -> !condition.getAsBoolean());
+  }
+
+  /**
    * Decorates this command to run or stop when disabled.
    *
    * @param doesRunWhenDisabled true to run when disabled.
    * @return the decorated command
    */
-  default WrapperCommand ignoringDisable(boolean doesRunWhenDisabled) {
+  public WrapperCommand ignoringDisable(boolean doesRunWhenDisabled) {
     return new WrapperCommand(this) {
       @Override
       public boolean runsWhenDisabled() {
@@ -329,7 +395,7 @@
    * @param interruptBehavior the desired interrupt behavior
    * @return the decorated command
    */
-  default WrapperCommand withInterruptBehavior(InterruptionBehavior interruptBehavior) {
+  public WrapperCommand withInterruptBehavior(InterruptionBehavior interruptBehavior) {
     return new WrapperCommand(this) {
       @Override
       public InterruptionBehavior getInterruptionBehavior() {
@@ -346,7 +412,7 @@
    *     interrupted.
    * @return the decorated command
    */
-  default WrapperCommand finallyDo(BooleanConsumer end) {
+  public WrapperCommand finallyDo(BooleanConsumer end) {
     requireNonNullParam(end, "end", "Command.finallyDo()");
     return new WrapperCommand(this) {
       @Override
@@ -358,13 +424,25 @@
   }
 
   /**
+   * Decorates this command with a lambda to call on interrupt or end, following the command's
+   * inherent {@link #end(boolean)} method. The provided lambda will run identically in both
+   * interrupt and end cases.
+   *
+   * @param end a lambda to run when the command ends, whether or not it was interrupted.
+   * @return the decorated command
+   */
+  public WrapperCommand finallyDo(Runnable end) {
+    return finallyDo(interrupted -> end.run());
+  }
+
+  /**
    * Decorates this command with a lambda to call on interrupt, following the command's inherent
    * {@link #end(boolean)} method.
    *
    * @param handler a lambda to run when the command is interrupted
    * @return the decorated command
    */
-  default WrapperCommand handleInterrupt(Runnable handler) {
+  public WrapperCommand handleInterrupt(Runnable handler) {
     requireNonNullParam(handler, "handler", "Command.handleInterrupt()");
     return finallyDo(
         interrupted -> {
@@ -375,7 +453,7 @@
   }
 
   /** Schedules this command. */
-  default void schedule() {
+  public void schedule() {
     CommandScheduler.getInstance().schedule(this);
   }
 
@@ -385,7 +463,7 @@
    *
    * @see CommandScheduler#cancel(Command...)
    */
-  default void cancel() {
+  public void cancel() {
     CommandScheduler.getInstance().cancel(this);
   }
 
@@ -395,7 +473,7 @@
    *
    * @return Whether the command is scheduled.
    */
-  default boolean isScheduled() {
+  public boolean isScheduled() {
     return CommandScheduler.getInstance().isScheduled(this);
   }
 
@@ -405,7 +483,7 @@
    * @param requirement the subsystem to inquire about
    * @return whether the subsystem is required
    */
-  default boolean hasRequirement(Subsystem requirement) {
+  public boolean hasRequirement(Subsystem requirement) {
     return getRequirements().contains(requirement);
   }
 
@@ -415,7 +493,7 @@
    * @return a variant of {@link InterruptionBehavior}, defaulting to {@link
    *     InterruptionBehavior#kCancelSelf kCancelSelf}.
    */
-  default InterruptionBehavior getInterruptionBehavior() {
+  public InterruptionBehavior getInterruptionBehavior() {
     return InterruptionBehavior.kCancelSelf;
   }
 
@@ -425,43 +503,52 @@
    *
    * @return whether the command should run when the robot is disabled
    */
-  default boolean runsWhenDisabled() {
+  public boolean runsWhenDisabled() {
     return false;
   }
 
   /**
-   * Gets the name of this Command. Defaults to the simple class name if not overridden.
-   *
-   * @return The display name of the Command
-   */
-  default String getName() {
-    return this.getClass().getSimpleName();
-  }
-
-  /**
-   * Sets the name of this Command. Nullop if not overridden.
-   *
-   * @param name The display name of the Command.
-   */
-  default void setName(String name) {}
-
-  /**
    * Decorates this Command with a name.
    *
    * @param name name
    * @return the decorated Command
    */
-  default WrapperCommand withName(String name) {
+  public WrapperCommand withName(String name) {
     WrapperCommand wrapper = new WrapperCommand(Command.this) {};
     wrapper.setName(name);
     return wrapper;
   }
 
+  @Override
+  public void initSendable(SendableBuilder builder) {
+    builder.setSmartDashboardType("Command");
+    builder.addStringProperty(".name", this::getName, null);
+    builder.addBooleanProperty(
+        "running",
+        this::isScheduled,
+        value -> {
+          if (value) {
+            if (!isScheduled()) {
+              schedule();
+            }
+          } else {
+            if (isScheduled()) {
+              cancel();
+            }
+          }
+        });
+    builder.addBooleanProperty(
+        ".isParented", () -> CommandScheduler.getInstance().isComposed(this), null);
+    builder.addStringProperty(
+        "interruptBehavior", () -> getInterruptionBehavior().toString(), null);
+    builder.addBooleanProperty("runsWhenDisabled", this::runsWhenDisabled, null);
+  }
+
   /**
    * An enum describing the command's behavior when another command with a shared requirement is
    * scheduled.
    */
-  enum InterruptionBehavior {
+  public enum InterruptionBehavior {
     /**
      * This command ends, {@link #end(boolean) end(true)} is called, and the incoming command is
      * scheduled normally.
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/CommandBase.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/CommandBase.java
index 395322a..12243cb 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/CommandBase.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/CommandBase.java
@@ -4,103 +4,16 @@
 
 package edu.wpi.first.wpilibj2.command;
 
-import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
-
 import edu.wpi.first.util.sendable.Sendable;
-import edu.wpi.first.util.sendable.SendableBuilder;
-import edu.wpi.first.util.sendable.SendableRegistry;
-import java.util.HashSet;
-import java.util.Set;
 
 /**
  * A {@link Sendable} base class for {@link Command}s.
  *
  * <p>This class is provided by the NewCommands VendorDep
+ *
+ * @deprecated All functionality provided by {@link CommandBase} has been merged into {@link
+ *     Command}. Use {@link Command} instead.
  */
-public abstract class CommandBase implements Sendable, Command {
-  protected Set<Subsystem> m_requirements = new HashSet<>();
-
-  protected CommandBase() {
-    String name = getClass().getName();
-    SendableRegistry.add(this, name.substring(name.lastIndexOf('.') + 1));
-  }
-
-  /**
-   * Adds the specified requirements to the command.
-   *
-   * @param requirements the requirements to add
-   */
-  public final void addRequirements(Subsystem... requirements) {
-    for (Subsystem requirement : requirements) {
-      m_requirements.add(requireNonNullParam(requirement, "requirement", "addRequirements"));
-    }
-  }
-
-  @Override
-  public Set<Subsystem> getRequirements() {
-    return m_requirements;
-  }
-
-  @Override
-  public String getName() {
-    return SendableRegistry.getName(this);
-  }
-
-  /**
-   * Sets the name of this Command.
-   *
-   * @param name name
-   */
-  @Override
-  public void setName(String name) {
-    SendableRegistry.setName(this, name);
-  }
-
-  /**
-   * Gets the subsystem name of this Command.
-   *
-   * @return Subsystem name
-   */
-  public String getSubsystem() {
-    return SendableRegistry.getSubsystem(this);
-  }
-
-  /**
-   * Sets the subsystem name of this Command.
-   *
-   * @param subsystem subsystem name
-   */
-  public void setSubsystem(String subsystem) {
-    SendableRegistry.setSubsystem(this, subsystem);
-  }
-
-  /**
-   * Initializes this sendable. Useful for allowing implementations to easily extend SendableBase.
-   *
-   * @param builder the builder used to construct this sendable
-   */
-  @Override
-  public void initSendable(SendableBuilder builder) {
-    builder.setSmartDashboardType("Command");
-    builder.addStringProperty(".name", this::getName, null);
-    builder.addBooleanProperty(
-        "running",
-        this::isScheduled,
-        value -> {
-          if (value) {
-            if (!isScheduled()) {
-              schedule();
-            }
-          } else {
-            if (isScheduled()) {
-              cancel();
-            }
-          }
-        });
-    builder.addBooleanProperty(
-        ".isParented", () -> CommandScheduler.getInstance().isComposed(this), null);
-    builder.addStringProperty(
-        "interruptBehavior", () -> getInterruptionBehavior().toString(), null);
-    builder.addBooleanProperty("runsWhenDisabled", this::runsWhenDisabled, null);
-  }
-}
+@Deprecated(since = "2024", forRemoval = true)
+@SuppressWarnings("PMD.AbstractClassWithoutAnyMethod")
+public abstract class CommandBase extends Command {}
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/CommandGroupBase.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/CommandGroupBase.java
deleted file mode 100644
index 73a1342..0000000
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/CommandGroupBase.java
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-package edu.wpi.first.wpilibj2.command;
-
-/**
- * A base for CommandGroups.
- *
- * <p>This class is provided by the NewCommands VendorDep
- *
- * @deprecated This class is an empty abstraction. Inherit directly from CommandBase/Command.
- */
-@Deprecated(forRemoval = true)
-public abstract class CommandGroupBase extends CommandBase {
-  /**
-   * Adds the given commands to the command group.
-   *
-   * @param commands The commands to add.
-   */
-  public abstract void addCommands(Command... commands);
-
-  /**
-   * Factory method for {@link SequentialCommandGroup}, included for brevity/convenience.
-   *
-   * @param commands the commands to include
-   * @return the command group
-   * @deprecated Replace with {@link Commands#sequence(Command...)}
-   */
-  @Deprecated
-  public static SequentialCommandGroup sequence(Command... commands) {
-    return new SequentialCommandGroup(commands);
-  }
-
-  /**
-   * Factory method for {@link ParallelCommandGroup}, included for brevity/convenience.
-   *
-   * @param commands the commands to include
-   * @return the command group
-   * @deprecated Replace with {@link Commands#parallel(Command...)}
-   */
-  @Deprecated
-  public static ParallelCommandGroup parallel(Command... commands) {
-    return new ParallelCommandGroup(commands);
-  }
-
-  /**
-   * Factory method for {@link ParallelRaceGroup}, included for brevity/convenience.
-   *
-   * @param commands the commands to include
-   * @return the command group
-   * @deprecated Replace with {@link Commands#race(Command...)}
-   */
-  @Deprecated
-  public static ParallelRaceGroup race(Command... commands) {
-    return new ParallelRaceGroup(commands);
-  }
-
-  /**
-   * Factory method for {@link ParallelDeadlineGroup}, included for brevity/convenience.
-   *
-   * @param deadline the deadline command
-   * @param commands the commands to include
-   * @return the command group
-   * @deprecated Replace with {@link Commands#deadline(Command, Command...)}
-   */
-  @Deprecated
-  public static ParallelDeadlineGroup deadline(Command deadline, Command... commands) {
-    return new ParallelDeadlineGroup(deadline, commands);
-  }
-}
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/CommandScheduler.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/CommandScheduler.java
index 15c8606..c868539 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/CommandScheduler.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/CommandScheduler.java
@@ -9,13 +9,8 @@
 import edu.wpi.first.hal.FRCNetComm.tInstances;
 import edu.wpi.first.hal.FRCNetComm.tResourceType;
 import edu.wpi.first.hal.HAL;
-import edu.wpi.first.networktables.IntegerArrayEntry;
-import edu.wpi.first.networktables.IntegerArrayPublisher;
-import edu.wpi.first.networktables.IntegerArrayTopic;
-import edu.wpi.first.networktables.NTSendable;
-import edu.wpi.first.networktables.NTSendableBuilder;
-import edu.wpi.first.networktables.StringArrayPublisher;
-import edu.wpi.first.networktables.StringArrayTopic;
+import edu.wpi.first.util.sendable.Sendable;
+import edu.wpi.first.util.sendable.SendableBuilder;
 import edu.wpi.first.util.sendable.SendableRegistry;
 import edu.wpi.first.wpilibj.DriverStation;
 import edu.wpi.first.wpilibj.RobotBase;
@@ -33,8 +28,10 @@
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.WeakHashMap;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 /**
@@ -46,7 +43,7 @@
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-public final class CommandScheduler implements NTSendable, AutoCloseable {
+public final class CommandScheduler implements Sendable, AutoCloseable {
   /** The Singleton Instance. */
   private static CommandScheduler instance;
 
@@ -62,6 +59,8 @@
     return instance;
   }
 
+  private static final Optional<Command> kNoInterruptor = Optional.empty();
+
   private final Set<Command> m_composedCommands = Collections.newSetFromMap(new WeakHashMap<>());
 
   // A set of the currently-running commands.
@@ -84,14 +83,16 @@
   // Lists of user-supplied actions to be executed on scheduling events for every command.
   private final List<Consumer<Command>> m_initActions = new ArrayList<>();
   private final List<Consumer<Command>> m_executeActions = new ArrayList<>();
-  private final List<Consumer<Command>> m_interruptActions = new ArrayList<>();
+  private final List<BiConsumer<Command, Optional<Command>>> m_interruptActions = new ArrayList<>();
   private final List<Consumer<Command>> m_finishActions = new ArrayList<>();
 
   // Flag and queues for avoiding ConcurrentModificationException if commands are
   // scheduled/canceled during run
   private boolean m_inRunLoop;
   private final Set<Command> m_toSchedule = new LinkedHashSet<>();
-  private final List<Command> m_toCancel = new ArrayList<>();
+  private final List<Command> m_toCancelCommands = new ArrayList<>();
+  private final List<Optional<Command>> m_toCancelInterruptors = new ArrayList<>();
+  private final Set<Command> m_endingCommands = new LinkedHashSet<>();
 
   private final Watchdog m_watchdog = new Watchdog(TimedRobot.kDefaultPeriod, () -> {});
 
@@ -152,27 +153,6 @@
   }
 
   /**
-   * Adds a button binding to the scheduler, which will be polled to schedule commands.
-   *
-   * @param button The button to add
-   * @deprecated Use {@link edu.wpi.first.wpilibj2.command.button.Trigger}
-   */
-  @Deprecated(since = "2023")
-  public void addButton(Runnable button) {
-    m_activeButtonLoop.bind(requireNonNullParam(button, "button", "addButton"));
-  }
-
-  /**
-   * Removes all button bindings from the scheduler.
-   *
-   * @deprecated call {@link EventLoop#clear()} on {@link #getActiveButtonLoop()} directly instead.
-   */
-  @Deprecated(since = "2023")
-  public void clearButtons() {
-    m_activeButtonLoop.clear();
-  }
-
-  /**
    * Initializes a given command, adds its requirements to the list, and performs the init actions.
    *
    * @param command The command to initialize
@@ -237,7 +217,7 @@
       for (Subsystem requirement : requirements) {
         Command requiring = requiring(requirement);
         if (requiring != null) {
-          cancel(requiring);
+          cancel(requiring, Optional.of(command));
         }
       }
       initCommand(command, requirements);
@@ -292,18 +272,13 @@
     m_watchdog.addEpoch("buttons.run()");
 
     m_inRunLoop = true;
+    boolean isDisabled = RobotState.isDisabled();
     // Run scheduled commands, remove finished commands.
     for (Iterator<Command> iterator = m_scheduledCommands.iterator(); iterator.hasNext(); ) {
       Command command = iterator.next();
 
-      if (!command.runsWhenDisabled() && RobotState.isDisabled()) {
-        command.end(true);
-        for (Consumer<Command> action : m_interruptActions) {
-          action.accept(command);
-        }
-        m_requirements.keySet().removeAll(command.getRequirements());
-        iterator.remove();
-        m_watchdog.addEpoch(command.getName() + ".end(true)");
+      if (isDisabled && !command.runsWhenDisabled()) {
+        cancel(command, kNoInterruptor);
         continue;
       }
 
@@ -313,10 +288,12 @@
       }
       m_watchdog.addEpoch(command.getName() + ".execute()");
       if (command.isFinished()) {
+        m_endingCommands.add(command);
         command.end(false);
         for (Consumer<Command> action : m_finishActions) {
           action.accept(command);
         }
+        m_endingCommands.remove(command);
         iterator.remove();
 
         m_requirements.keySet().removeAll(command.getRequirements());
@@ -330,12 +307,13 @@
       schedule(command);
     }
 
-    for (Command command : m_toCancel) {
-      cancel(command);
+    for (int i = 0; i < m_toCancelCommands.size(); i++) {
+      cancel(m_toCancelCommands.get(i), m_toCancelInterruptors.get(i));
     }
 
     m_toSchedule.clear();
-    m_toCancel.clear();
+    m_toCancelCommands.clear();
+    m_toCancelInterruptors.clear();
 
     // Add default commands for un-required registered subsystems.
     for (Map.Entry<Subsystem, Command> subsystemCommand : m_subsystems.entrySet()) {
@@ -384,6 +362,15 @@
   }
 
   /**
+   * Un-registers all registered Subsystems with the scheduler. All currently registered subsystems
+   * will no longer have their periodic block called, and will not have their default command
+   * scheduled.
+   */
+  public void unregisterAllSubsystems() {
+    m_subsystems.clear();
+  }
+
+  /**
    * Sets the default command for a subsystem. Registers that subsystem if it is not already
    * registered. Default commands will run whenever there is no other command currently scheduled
    * that requires the subsystem. Default commands should be written to never end (i.e. their {@link
@@ -457,28 +444,47 @@
    * @param commands the commands to cancel
    */
   public void cancel(Command... commands) {
+    for (Command command : commands) {
+      cancel(command, kNoInterruptor);
+    }
+  }
+
+  /**
+   * Cancels a command. The scheduler will only call {@link Command#end(boolean)} method of the
+   * canceled command with {@code true}, indicating they were canceled (as opposed to finishing
+   * normally).
+   *
+   * <p>Commands will be canceled regardless of {@link InterruptionBehavior interruption behavior}.
+   *
+   * @param command the command to cancel
+   * @param interruptor the interrupting command, if any
+   */
+  private void cancel(Command command, Optional<Command> interruptor) {
+    if (command == null) {
+      DriverStation.reportWarning("Tried to cancel a null command", true);
+      return;
+    }
+    if (m_endingCommands.contains(command)) {
+      return;
+    }
     if (m_inRunLoop) {
-      m_toCancel.addAll(List.of(commands));
+      m_toCancelCommands.add(command);
+      m_toCancelInterruptors.add(interruptor);
+      return;
+    }
+    if (!isScheduled(command)) {
       return;
     }
 
-    for (Command command : commands) {
-      if (command == null) {
-        DriverStation.reportWarning("Tried to cancel a null command", true);
-        continue;
-      }
-      if (!isScheduled(command)) {
-        continue;
-      }
-
-      m_scheduledCommands.remove(command);
-      m_requirements.keySet().removeAll(command.getRequirements());
-      command.end(true);
-      for (Consumer<Command> action : m_interruptActions) {
-        action.accept(command);
-      }
-      m_watchdog.addEpoch(command.getName() + ".end(true)");
+    m_endingCommands.add(command);
+    command.end(true);
+    for (BiConsumer<Command, Optional<Command>> action : m_interruptActions) {
+      action.accept(command, interruptor);
     }
+    m_endingCommands.remove(command);
+    m_scheduledCommands.remove(command);
+    m_requirements.keySet().removeAll(command.getRequirements());
+    m_watchdog.addEpoch(command.getName() + ".end(true)");
   }
 
   /** Cancels all commands that are currently scheduled. */
@@ -545,6 +551,19 @@
    * @param action the action to perform
    */
   public void onCommandInterrupt(Consumer<Command> action) {
+    requireNonNullParam(action, "action", "onCommandInterrupt");
+    m_interruptActions.add((command, interruptor) -> action.accept(command));
+  }
+
+  /**
+   * Adds an action to perform on the interruption of any command by the scheduler. The action
+   * receives the interrupted command and an Optional containing the interrupting command, or
+   * Optional.empty() if it was not canceled by a command (e.g., by {@link
+   * CommandScheduler#cancel}).
+   *
+   * @param action the action to perform
+   */
+  public void onCommandInterrupt(BiConsumer<Command, Optional<Command>> action) {
     m_interruptActions.add(requireNonNullParam(action, "action", "onCommandInterrupt"));
   }
 
@@ -602,7 +621,7 @@
   public void requireNotComposed(Command command) {
     if (m_composedCommands.contains(command)) {
       throw new IllegalArgumentException(
-          "Commands that have been composed may not be added to another composition or scheduled"
+          "Commands that have been composed may not be added to another composition or scheduled "
               + "individually!");
     }
   }
@@ -616,7 +635,7 @@
   public void requireNotComposed(Collection<Command> commands) {
     if (!Collections.disjoint(commands, getComposedCommands())) {
       throw new IllegalArgumentException(
-          "Commands that have been composed may not be added to another composition or scheduled"
+          "Commands that have been composed may not be added to another composition or scheduled "
               + "individually!");
     }
   }
@@ -626,7 +645,6 @@
    *
    * @param command The command to check
    * @return true if composed
-   * @throws IllegalArgumentException if the given commands have already been composed.
    */
   public boolean isComposed(Command command) {
     return getComposedCommands().contains(command);
@@ -637,45 +655,46 @@
   }
 
   @Override
-  public void initSendable(NTSendableBuilder builder) {
+  public void initSendable(SendableBuilder builder) {
     builder.setSmartDashboardType("Scheduler");
-    final StringArrayPublisher namesPub = new StringArrayTopic(builder.getTopic("Names")).publish();
-    final IntegerArrayPublisher idsPub = new IntegerArrayTopic(builder.getTopic("Ids")).publish();
-    final IntegerArrayEntry cancelEntry =
-        new IntegerArrayTopic(builder.getTopic("Cancel")).getEntry(new long[] {});
-    builder.addCloseable(namesPub);
-    builder.addCloseable(idsPub);
-    builder.addCloseable(cancelEntry);
-    builder.setUpdateTable(
+    builder.addStringArrayProperty(
+        "Names",
         () -> {
-          if (namesPub == null || idsPub == null || cancelEntry == null) {
-            return;
-          }
-
-          Map<Long, Command> ids = new LinkedHashMap<>();
-          List<String> names = new ArrayList<>();
-          long[] ids2 = new long[m_scheduledCommands.size()];
-
+          String[] names = new String[m_scheduledCommands.size()];
           int i = 0;
           for (Command command : m_scheduledCommands) {
+            names[i] = command.getName();
+            i++;
+          }
+          return names;
+        },
+        null);
+    builder.addIntegerArrayProperty(
+        "Ids",
+        () -> {
+          long[] ids = new long[m_scheduledCommands.size()];
+          int i = 0;
+          for (Command command : m_scheduledCommands) {
+            ids[i] = command.hashCode();
+            i++;
+          }
+          return ids;
+        },
+        null);
+    builder.addIntegerArrayProperty(
+        "Cancel",
+        () -> {
+          return new long[] {};
+        },
+        toCancel -> {
+          Map<Long, Command> ids = new LinkedHashMap<>();
+          for (Command command : m_scheduledCommands) {
             long id = command.hashCode();
             ids.put(id, command);
-            names.add(command.getName());
-            ids2[i] = id;
-            i++;
           }
-
-          long[] toCancel = cancelEntry.get();
-          if (toCancel.length > 0) {
-            for (long hash : toCancel) {
-              cancel(ids.get(hash));
-              ids.remove(hash);
-            }
-            cancelEntry.set(new long[] {});
+          for (long hash : toCancel) {
+            cancel(ids.get(hash));
           }
-
-          namesPub.set(names.toArray(new String[] {}));
-          idsPub.set(ids2);
         });
   }
 }
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/Commands.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/Commands.java
index 737b9cc..7295e3c 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/Commands.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/Commands.java
@@ -7,6 +7,7 @@
 import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
 
 import java.util.Map;
+import java.util.Set;
 import java.util.function.BooleanSupplier;
 import java.util.function.Supplier;
 
@@ -21,10 +22,20 @@
    *
    * @return the command
    */
-  public static CommandBase none() {
+  public static Command none() {
     return new InstantCommand();
   }
 
+  /**
+   * Constructs a command that does nothing until interrupted.
+   *
+   * @param requirements Subsystems to require
+   * @return the command
+   */
+  public static Command idle(Subsystem... requirements) {
+    return run(() -> {}, requirements);
+  }
+
   // Action Commands
 
   /**
@@ -35,7 +46,7 @@
    * @return the command
    * @see InstantCommand
    */
-  public static CommandBase runOnce(Runnable action, Subsystem... requirements) {
+  public static Command runOnce(Runnable action, Subsystem... requirements) {
     return new InstantCommand(action, requirements);
   }
 
@@ -47,7 +58,7 @@
    * @return the command
    * @see RunCommand
    */
-  public static CommandBase run(Runnable action, Subsystem... requirements) {
+  public static Command run(Runnable action, Subsystem... requirements) {
     return new RunCommand(action, requirements);
   }
 
@@ -61,7 +72,7 @@
    * @return the command
    * @see StartEndCommand
    */
-  public static CommandBase startEnd(Runnable start, Runnable end, Subsystem... requirements) {
+  public static Command startEnd(Runnable start, Runnable end, Subsystem... requirements) {
     return new StartEndCommand(start, end, requirements);
   }
 
@@ -74,7 +85,7 @@
    * @param requirements subsystems the action requires
    * @return the command
    */
-  public static CommandBase runEnd(Runnable run, Runnable end, Subsystem... requirements) {
+  public static Command runEnd(Runnable run, Runnable end, Subsystem... requirements) {
     requireNonNullParam(end, "end", "Command.runEnd");
     return new FunctionalCommand(
         () -> {}, run, interrupted -> end.run(), () -> false, requirements);
@@ -87,7 +98,7 @@
    * @return the command
    * @see PrintCommand
    */
-  public static CommandBase print(String message) {
+  public static Command print(String message) {
     return new PrintCommand(message);
   }
 
@@ -100,7 +111,7 @@
    * @return the command
    * @see WaitCommand
    */
-  public static CommandBase waitSeconds(double seconds) {
+  public static Command waitSeconds(double seconds) {
     return new WaitCommand(seconds);
   }
 
@@ -111,7 +122,7 @@
    * @return the command
    * @see WaitUntilCommand
    */
-  public static CommandBase waitUntil(BooleanSupplier condition) {
+  public static Command waitUntil(BooleanSupplier condition) {
     return new WaitUntilCommand(condition);
   }
 
@@ -126,35 +137,61 @@
    * @return the command
    * @see ConditionalCommand
    */
-  public static CommandBase either(Command onTrue, Command onFalse, BooleanSupplier selector) {
+  public static Command either(Command onTrue, Command onFalse, BooleanSupplier selector) {
     return new ConditionalCommand(onTrue, onFalse, selector);
   }
 
   /**
    * Runs one of several commands, based on the selector function.
    *
+   * @param <K> The type of key used to select the command
    * @param selector the selector function
    * @param commands map of commands to select from
    * @return the command
    * @see SelectCommand
    */
-  public static CommandBase select(Map<Object, Command> commands, Supplier<Object> selector) {
-    return new SelectCommand(commands, selector);
+  public static <K> Command select(Map<K, Command> commands, Supplier<? extends K> selector) {
+    return new SelectCommand<>(commands, selector);
   }
 
   /**
+   * Runs the command supplied by the supplier.
+   *
+   * @param supplier the command supplier
+   * @param requirements the set of requirements for this command
+   * @return the command
+   * @see DeferredCommand
+   */
+  public static Command defer(Supplier<Command> supplier, Set<Subsystem> requirements) {
+    return new DeferredCommand(supplier, requirements);
+  }
+
+  /**
+   * Constructs a command that schedules the command returned from the supplier when initialized,
+   * and ends when it is no longer scheduled. The supplier is called when the command is
+   * initialized.
+   *
+   * @param supplier the command supplier
+   * @return the command
+   * @see ProxyCommand
+   */
+  public static Command deferredProxy(Supplier<Command> supplier) {
+    return new ProxyCommand(supplier);
+  }
+
+  // Command Groups
+
+  /**
    * Runs a group of commands in series, one after the other.
    *
    * @param commands the commands to include
    * @return the command group
    * @see SequentialCommandGroup
    */
-  public static CommandBase sequence(Command... commands) {
+  public static Command sequence(Command... commands) {
     return new SequentialCommandGroup(commands);
   }
 
-  // Command Groups
-
   /**
    * Runs a group of commands in series, one after the other. Once the last command ends, the group
    * is restarted.
@@ -164,7 +201,7 @@
    * @see SequentialCommandGroup
    * @see Command#repeatedly()
    */
-  public static CommandBase repeatingSequence(Command... commands) {
+  public static Command repeatingSequence(Command... commands) {
     return sequence(commands).repeatedly();
   }
 
@@ -175,7 +212,7 @@
    * @return the command
    * @see ParallelCommandGroup
    */
-  public static CommandBase parallel(Command... commands) {
+  public static Command parallel(Command... commands) {
     return new ParallelCommandGroup(commands);
   }
 
@@ -187,7 +224,7 @@
    * @return the command group
    * @see ParallelRaceGroup
    */
-  public static CommandBase race(Command... commands) {
+  public static Command race(Command... commands) {
     return new ParallelRaceGroup(commands);
   }
 
@@ -200,7 +237,7 @@
    * @return the command group
    * @see ParallelDeadlineGroup
    */
-  public static CommandBase deadline(Command deadline, Command... commands) {
+  public static Command deadline(Command deadline, Command... commands) {
     return new ParallelDeadlineGroup(deadline, commands);
   }
 
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ConditionalCommand.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ConditionalCommand.java
index 782f217..42db3f0 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ConditionalCommand.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ConditionalCommand.java
@@ -19,7 +19,7 @@
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-public class ConditionalCommand extends CommandBase {
+public class ConditionalCommand extends Command {
   private final Command m_onTrue;
   private final Command m_onFalse;
   private final BooleanSupplier m_condition;
@@ -74,6 +74,16 @@
   }
 
   @Override
+  public InterruptionBehavior getInterruptionBehavior() {
+    if (m_onTrue.getInterruptionBehavior() == InterruptionBehavior.kCancelSelf
+        || m_onFalse.getInterruptionBehavior() == InterruptionBehavior.kCancelSelf) {
+      return InterruptionBehavior.kCancelSelf;
+    } else {
+      return InterruptionBehavior.kCancelIncoming;
+    }
+  }
+
+  @Override
   public void initSendable(SendableBuilder builder) {
     super.initSendable(builder);
     builder.addStringProperty("onTrue", m_onTrue::getName, null);
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/DeferredCommand.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/DeferredCommand.java
new file mode 100644
index 0000000..76a5276
--- /dev/null
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/DeferredCommand.java
@@ -0,0 +1,78 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.wpilibj2.command;
+
+import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
+
+import edu.wpi.first.util.sendable.SendableBuilder;
+import java.util.Set;
+import java.util.function.Supplier;
+
+/**
+ * Defers Command construction to runtime. Runs the command returned by the supplier when this
+ * command is initialized, and ends when it ends. Useful for performing runtime tasks before
+ * creating a new command. If this command is interrupted, it will cancel the command.
+ *
+ * <p>Note that the supplier <i>must</i> create a new Command each call. For selecting one of a
+ * preallocated set of commands, use {@link SelectCommand}.
+ *
+ * <p>This class is provided by the NewCommands VendorDep
+ */
+public class DeferredCommand extends Command {
+  private final Command m_nullCommand =
+      new PrintCommand("[DeferredCommand] Supplied command was null!");
+
+  private final Supplier<Command> m_supplier;
+  private Command m_command = m_nullCommand;
+
+  /**
+   * Creates a new DeferredCommand that runs the supplied command when initialized, and ends when it
+   * ends. Useful for lazily creating commands at runtime. The {@link Supplier} will be called each
+   * time this command is initialized. The Supplier <i>must</i> create a new Command each call.
+   *
+   * @param supplier The command supplier
+   * @param requirements The command requirements. This is a {@link Set} to prevent accidental
+   *     omission of command requirements. Use {@link Set#of()} to easily construct a requirement
+   *     set.
+   */
+  public DeferredCommand(Supplier<Command> supplier, Set<Subsystem> requirements) {
+    m_supplier = requireNonNullParam(supplier, "supplier", "DeferredCommand");
+    addRequirements(requirements.toArray(new Subsystem[0]));
+  }
+
+  @Override
+  public void initialize() {
+    var cmd = m_supplier.get();
+    if (cmd != null) {
+      m_command = cmd;
+      CommandScheduler.getInstance().registerComposedCommands(m_command);
+    }
+    m_command.initialize();
+  }
+
+  @Override
+  public void execute() {
+    m_command.execute();
+  }
+
+  @Override
+  public boolean isFinished() {
+    return m_command.isFinished();
+  }
+
+  @Override
+  public void end(boolean interrupted) {
+    m_command.end(interrupted);
+    m_command = m_nullCommand;
+  }
+
+  @Override
+  @SuppressWarnings("PMD.CompareObjectsWithEquals")
+  public void initSendable(SendableBuilder builder) {
+    super.initSendable(builder);
+    builder.addStringProperty(
+        "deferred", () -> m_command == m_nullCommand ? "null" : m_command.getName(), null);
+  }
+}
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/FunctionalCommand.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/FunctionalCommand.java
index fcd4bd7..dde5ae1 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/FunctionalCommand.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/FunctionalCommand.java
@@ -17,7 +17,7 @@
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-public class FunctionalCommand extends CommandBase {
+public class FunctionalCommand extends Command {
   protected final Runnable m_onInit;
   protected final Runnable m_onExecute;
   protected final Consumer<Boolean> m_onEnd;
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/MecanumControllerCommand.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/MecanumControllerCommand.java
index f6b6668..16d9c8a 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/MecanumControllerCommand.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/MecanumControllerCommand.java
@@ -38,7 +38,7 @@
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-public class MecanumControllerCommand extends CommandBase {
+public class MecanumControllerCommand extends Command {
   private final Timer m_timer = new Timer();
   private final boolean m_usePID;
   private final Trajectory m_trajectory;
@@ -332,8 +332,7 @@
     m_prevSpeeds =
         m_kinematics.toWheelSpeeds(new ChassisSpeeds(initialXVelocity, initialYVelocity, 0.0));
 
-    m_timer.reset();
-    m_timer.start();
+    m_timer.restart();
   }
 
   @Override
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/NotifierCommand.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/NotifierCommand.java
index 730ef1b..ad1a12a 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/NotifierCommand.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/NotifierCommand.java
@@ -17,7 +17,7 @@
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-public class NotifierCommand extends CommandBase {
+public class NotifierCommand extends Command {
   protected final Notifier m_notifier;
   protected final double m_period;
 
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/PIDCommand.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/PIDCommand.java
index ec184c8..c761f3f 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/PIDCommand.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/PIDCommand.java
@@ -18,7 +18,7 @@
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-public class PIDCommand extends CommandBase {
+public class PIDCommand extends Command {
   protected final PIDController m_controller;
   protected DoubleSupplier m_measurement;
   protected DoubleSupplier m_setpoint;
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ParallelCommandGroup.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ParallelCommandGroup.java
index 3b36f42..8604189 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ParallelCommandGroup.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ParallelCommandGroup.java
@@ -17,8 +17,7 @@
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-@SuppressWarnings("removal")
-public class ParallelCommandGroup extends CommandGroupBase {
+public class ParallelCommandGroup extends Command {
   // maps commands in this composition to whether they are still running
   private final Map<Command, Boolean> m_commands = new HashMap<>();
   private boolean m_runWhenDisabled = true;
@@ -35,7 +34,11 @@
     addCommands(commands);
   }
 
-  @Override
+  /**
+   * Adds the given commands to the group.
+   *
+   * @param commands Commands to add to the group.
+   */
   public final void addCommands(Command... commands) {
     if (m_commands.containsValue(true)) {
       throw new IllegalStateException(
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ParallelDeadlineGroup.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ParallelDeadlineGroup.java
index 2e62a9f..6d263e8 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ParallelDeadlineGroup.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ParallelDeadlineGroup.java
@@ -20,8 +20,7 @@
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-@SuppressWarnings("removal")
-public class ParallelDeadlineGroup extends CommandGroupBase {
+public class ParallelDeadlineGroup extends Command {
   // maps commands in this composition to whether they are still running
   private final Map<Command, Boolean> m_commands = new HashMap<>();
   private boolean m_runWhenDisabled = true;
@@ -59,7 +58,11 @@
     m_deadline = deadline;
   }
 
-  @Override
+  /**
+   * Adds the given commands to the group.
+   *
+   * @param commands Commands to add to the group.
+   */
   public final void addCommands(Command... commands) {
     if (!m_finished) {
       throw new IllegalStateException(
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ParallelRaceGroup.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ParallelRaceGroup.java
index e5ba80d..7195cd7 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ParallelRaceGroup.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ParallelRaceGroup.java
@@ -18,8 +18,7 @@
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-@SuppressWarnings("removal")
-public class ParallelRaceGroup extends CommandGroupBase {
+public class ParallelRaceGroup extends Command {
   private final Set<Command> m_commands = new HashSet<>();
   private boolean m_runWhenDisabled = true;
   private boolean m_finished = true;
@@ -36,7 +35,11 @@
     addCommands(commands);
   }
 
-  @Override
+  /**
+   * Adds the given commands to the group.
+   *
+   * @param commands Commands to add to the group.
+   */
   public final void addCommands(Command... commands) {
     if (!m_finished) {
       throw new IllegalStateException(
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/PerpetualCommand.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/PerpetualCommand.java
deleted file mode 100644
index 9fb9019..0000000
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/PerpetualCommand.java
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-package edu.wpi.first.wpilibj2.command;
-
-/**
- * A command that runs another command in perpetuity, ignoring that command's end conditions. While
- * this class does not extend {@link CommandGroupBase}, it is still considered a composition, as it
- * allows one to compose another command within it; the command instances that are passed to it
- * cannot be added to any other groups, or scheduled individually.
- *
- * <p>As a rule, CommandGroups require the union of the requirements of their component commands.
- *
- * <p>This class is provided by the NewCommands VendorDep
- *
- * @deprecated PerpetualCommand violates the assumption that execute() doesn't get called after
- *     isFinished() returns true -- an assumption that should be valid. This was unsafe/undefined
- *     behavior from the start, and RepeatCommand provides an easy way to achieve similar end
- *     results with slightly different (and safe) semantics.
- */
-@Deprecated(forRemoval = true, since = "2023")
-public class PerpetualCommand extends CommandBase {
-  protected final Command m_command;
-
-  /**
-   * Creates a new PerpetualCommand. Will run another command in perpetuity, ignoring that command's
-   * end conditions, unless this command itself is interrupted.
-   *
-   * @param command the command to run perpetually
-   */
-  public PerpetualCommand(Command command) {
-    CommandScheduler.getInstance().registerComposedCommands(command);
-    m_command = command;
-    m_requirements.addAll(command.getRequirements());
-  }
-
-  @Override
-  public void initialize() {
-    m_command.initialize();
-  }
-
-  @Override
-  public void execute() {
-    m_command.execute();
-  }
-
-  @Override
-  public void end(boolean interrupted) {
-    m_command.end(interrupted);
-  }
-
-  @Override
-  public boolean runsWhenDisabled() {
-    return m_command.runsWhenDisabled();
-  }
-}
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ProfiledPIDCommand.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ProfiledPIDCommand.java
index 4344913..f7175fc 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ProfiledPIDCommand.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ProfiledPIDCommand.java
@@ -20,7 +20,7 @@
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-public class ProfiledPIDCommand extends CommandBase {
+public class ProfiledPIDCommand extends Command {
   protected final ProfiledPIDController m_controller;
   protected DoubleSupplier m_measurement;
   protected Supplier<State> m_goal;
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ProxyCommand.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ProxyCommand.java
index ef6ca5b..d6e93e8 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ProxyCommand.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ProxyCommand.java
@@ -15,7 +15,7 @@
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-public class ProxyCommand extends CommandBase {
+public class ProxyCommand extends Command {
   private final Supplier<Command> m_supplier;
   private Command m_command;
 
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ProxyScheduleCommand.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ProxyScheduleCommand.java
deleted file mode 100644
index eec1b18..0000000
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ProxyScheduleCommand.java
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-package edu.wpi.first.wpilibj2.command;
-
-import java.util.Set;
-
-/**
- * Schedules the given commands when this command is initialized, and ends when all the commands are
- * no longer scheduled. Useful for forking off from CommandGroups. If this command is interrupted,
- * it will cancel all the commands.
- *
- * <p>This class is provided by the NewCommands VendorDep
- */
-public class ProxyScheduleCommand extends CommandBase {
-  private final Set<Command> m_toSchedule;
-  private boolean m_finished;
-
-  /**
-   * Creates a new ProxyScheduleCommand that schedules the given commands when initialized, and ends
-   * when they are all no longer scheduled.
-   *
-   * @param toSchedule the commands to schedule
-   * @deprecated Replace with {@link ProxyCommand}, composing multiple of them in a {@link
-   *     ParallelRaceGroup} if needed.
-   */
-  @Deprecated
-  public ProxyScheduleCommand(Command... toSchedule) {
-    m_toSchedule = Set.of(toSchedule);
-  }
-
-  @Override
-  public void initialize() {
-    for (Command command : m_toSchedule) {
-      command.schedule();
-    }
-  }
-
-  @Override
-  public void end(boolean interrupted) {
-    if (interrupted) {
-      for (Command command : m_toSchedule) {
-        command.cancel();
-      }
-    }
-  }
-
-  @Override
-  public void execute() {
-    m_finished = true;
-    for (Command command : m_toSchedule) {
-      m_finished &= !command.isScheduled();
-    }
-  }
-
-  @Override
-  public boolean isFinished() {
-    return m_finished;
-  }
-}
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/RamseteCommand.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/RamseteCommand.java
index 69e271b..9f19e4a 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/RamseteCommand.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/RamseteCommand.java
@@ -33,7 +33,7 @@
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-public class RamseteCommand extends CommandBase {
+public class RamseteCommand extends Command {
   private final Timer m_timer = new Timer();
   private final boolean m_usePID;
   private final Trajectory m_trajectory;
@@ -45,7 +45,7 @@
   private final PIDController m_leftController;
   private final PIDController m_rightController;
   private final BiConsumer<Double, Double> m_output;
-  private DifferentialDriveWheelSpeeds m_prevSpeeds;
+  private DifferentialDriveWheelSpeeds m_prevSpeeds = new DifferentialDriveWheelSpeeds();
   private double m_prevTime;
 
   /**
@@ -143,8 +143,7 @@
                 initialState.velocityMetersPerSecond,
                 0,
                 initialState.curvatureRadPerMeter * initialState.velocityMetersPerSecond));
-    m_timer.reset();
-    m_timer.start();
+    m_timer.restart();
     if (m_usePID) {
       m_leftController.reset();
       m_rightController.reset();
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/RepeatCommand.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/RepeatCommand.java
index 6de06c5..5b49ae0 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/RepeatCommand.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/RepeatCommand.java
@@ -19,7 +19,7 @@
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-public class RepeatCommand extends CommandBase {
+public class RepeatCommand extends Command {
   protected final Command m_command;
   private boolean m_ended;
 
@@ -63,7 +63,12 @@
 
   @Override
   public void end(boolean interrupted) {
-    m_command.end(interrupted);
+    // Make sure we didn't already call end() (which would happen if the command finished in the
+    // last call to our execute())
+    if (!m_ended) {
+      m_command.end(interrupted);
+      m_ended = true;
+    }
   }
 
   @Override
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ScheduleCommand.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ScheduleCommand.java
index a61be2f..cc22341 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ScheduleCommand.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ScheduleCommand.java
@@ -13,7 +13,7 @@
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-public class ScheduleCommand extends CommandBase {
+public class ScheduleCommand extends Command {
   private final Set<Command> m_toSchedule;
 
   /**
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/SelectCommand.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/SelectCommand.java
index d947701..28d06eb 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/SelectCommand.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/SelectCommand.java
@@ -11,38 +11,41 @@
 import java.util.function.Supplier;
 
 /**
- * A command composition that runs one of a selection of commands, either using a selector and a key
- * to command mapping, or a supplier that returns the command directly at runtime.
+ * A command composition that runs one of a selection of commands using a selector and a key to
+ * command mapping.
  *
  * <p>The rules for command compositions apply: command instances that are passed to it cannot be
  * added to any other composition or scheduled individually, and the composition requires all
  * subsystems its components require.
  *
  * <p>This class is provided by the NewCommands VendorDep
+ *
+ * @param <K> The type of key used to select the command
  */
-public class SelectCommand extends CommandBase {
-  private final Map<Object, Command> m_commands;
-  private final Supplier<Object> m_selector;
-  private final Supplier<Command> m_toRun;
+public class SelectCommand<K> extends Command {
+  private final Map<K, Command> m_commands;
+  private final Supplier<? extends K> m_selector;
   private Command m_selectedCommand;
   private boolean m_runsWhenDisabled = true;
   private InterruptionBehavior m_interruptBehavior = InterruptionBehavior.kCancelIncoming;
 
+  private final Command m_defaultCommand =
+      new PrintCommand("SelectCommand selector value does not correspond to any command!");
+
   /**
    * Creates a new SelectCommand.
    *
    * @param commands the map of commands to choose from
    * @param selector the selector to determine which command to run
    */
-  public SelectCommand(Map<Object, Command> commands, Supplier<Object> selector) {
+  public SelectCommand(Map<K, Command> commands, Supplier<? extends K> selector) {
     m_commands = requireNonNullParam(commands, "commands", "SelectCommand");
     m_selector = requireNonNullParam(selector, "selector", "SelectCommand");
 
+    CommandScheduler.getInstance().registerComposedCommands(m_defaultCommand);
     CommandScheduler.getInstance()
         .registerComposedCommands(commands.values().toArray(new Command[] {}));
 
-    m_toRun = null;
-
     for (Command command : m_commands.values()) {
       m_requirements.addAll(command.getRequirements());
       m_runsWhenDisabled &= command.runsWhenDisabled();
@@ -52,36 +55,9 @@
     }
   }
 
-  /**
-   * Creates a new SelectCommand.
-   *
-   * @param toRun a supplier providing the command to run
-   * @deprecated Replace with {@link ProxyCommand}
-   */
-  @Deprecated
-  public SelectCommand(Supplier<Command> toRun) {
-    m_commands = null;
-    m_selector = null;
-    m_toRun = requireNonNullParam(toRun, "toRun", "SelectCommand");
-
-    // we have no way of checking the underlying command, so default.
-    m_runsWhenDisabled = false;
-    m_interruptBehavior = InterruptionBehavior.kCancelSelf;
-  }
-
   @Override
   public void initialize() {
-    if (m_selector != null) {
-      if (!m_commands.containsKey(m_selector.get())) {
-        m_selectedCommand =
-            new PrintCommand(
-                "SelectCommand selector value does not correspond to" + " any command!");
-        return;
-      }
-      m_selectedCommand = m_commands.get(m_selector.get());
-    } else {
-      m_selectedCommand = m_toRun.get();
-    }
+    m_selectedCommand = m_commands.getOrDefault(m_selector.get(), m_defaultCommand);
     m_selectedCommand.initialize();
   }
 
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/SequentialCommandGroup.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/SequentialCommandGroup.java
index 892f94a..c3598cb 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/SequentialCommandGroup.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/SequentialCommandGroup.java
@@ -17,8 +17,7 @@
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-@SuppressWarnings("removal")
-public class SequentialCommandGroup extends CommandGroupBase {
+public class SequentialCommandGroup extends Command {
   private final List<Command> m_commands = new ArrayList<>();
   private int m_currentCommandIndex = -1;
   private boolean m_runWhenDisabled = true;
@@ -34,7 +33,11 @@
     addCommands(commands);
   }
 
-  @Override
+  /**
+   * Adds the given commands to the group.
+   *
+   * @param commands Commands to add, in order of execution.
+   */
   public final void addCommands(Command... commands) {
     if (m_currentCommandIndex != -1) {
       throw new IllegalStateException(
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/Subsystem.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/Subsystem.java
index 0584ced..ac89dbd 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/Subsystem.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/Subsystem.java
@@ -4,6 +4,9 @@
 
 package edu.wpi.first.wpilibj2.command;
 
+import java.util.Set;
+import java.util.function.Supplier;
+
 /**
  * A robot subsystem. Subsystems are the basic unit of robot organization in the Command-based
  * framework; they encapsulate low-level hardware objects (motor controllers, sensors, etc.) and
@@ -51,6 +54,14 @@
   }
 
   /**
+   * Removes the default command for the subsystem. This will not cancel the default command if it
+   * is currently running.
+   */
+  default void removeDefaultCommand() {
+    CommandScheduler.getInstance().removeDefaultCommand(this);
+  }
+
+  /**
    * Gets the default command for this subsystem. Returns null if no default command is currently
    * associated with the subsystem.
    *
@@ -85,7 +96,7 @@
    * @return the command
    * @see InstantCommand
    */
-  default CommandBase runOnce(Runnable action) {
+  default Command runOnce(Runnable action) {
     return Commands.runOnce(action, this);
   }
 
@@ -97,7 +108,7 @@
    * @return the command
    * @see RunCommand
    */
-  default CommandBase run(Runnable action) {
+  default Command run(Runnable action) {
     return Commands.run(action, this);
   }
 
@@ -110,7 +121,7 @@
    * @return the command
    * @see StartEndCommand
    */
-  default CommandBase startEnd(Runnable start, Runnable end) {
+  default Command startEnd(Runnable start, Runnable end) {
     return Commands.startEnd(start, end, this);
   }
 
@@ -122,7 +133,19 @@
    * @param end the action to run on interrupt
    * @return the command
    */
-  default CommandBase runEnd(Runnable run, Runnable end) {
+  default Command runEnd(Runnable run, Runnable end) {
     return Commands.runEnd(run, end, this);
   }
+
+  /**
+   * Constructs a {@link DeferredCommand} with the provided supplier. This subsystem is added as a
+   * requirement.
+   *
+   * @param supplier the command supplier.
+   * @return the command.
+   * @see DeferredCommand
+   */
+  default Command defer(Supplier<Command> supplier) {
+    return Commands.defer(supplier, Set.of(this));
+  }
 }
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/SwerveControllerCommand.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/SwerveControllerCommand.java
index 850699e..3eacac3 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/SwerveControllerCommand.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/SwerveControllerCommand.java
@@ -31,7 +31,7 @@
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-public class SwerveControllerCommand extends CommandBase {
+public class SwerveControllerCommand extends Command {
   private final Timer m_timer = new Timer();
   private final Trajectory m_trajectory;
   private final Supplier<Pose2d> m_pose;
@@ -210,8 +210,7 @@
 
   @Override
   public void initialize() {
-    m_timer.reset();
-    m_timer.start();
+    m_timer.restart();
   }
 
   @Override
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/TrapezoidProfileCommand.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/TrapezoidProfileCommand.java
index 7c13973..57185a9 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/TrapezoidProfileCommand.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/TrapezoidProfileCommand.java
@@ -10,16 +10,19 @@
 import edu.wpi.first.math.trajectory.TrapezoidProfile;
 import edu.wpi.first.wpilibj.Timer;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
 /**
  * A command that runs a {@link TrapezoidProfile}. Useful for smoothly controlling mechanism motion.
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-public class TrapezoidProfileCommand extends CommandBase {
+public class TrapezoidProfileCommand extends Command {
   private final TrapezoidProfile m_profile;
   private final Consumer<State> m_output;
-
+  private final Supplier<State> m_goal;
+  private final Supplier<State> m_currentState;
+  private final boolean m_newAPI; // TODO: Remove
   private final Timer m_timer = new Timer();
 
   /**
@@ -28,24 +31,58 @@
    *
    * @param profile The motion profile to execute.
    * @param output The consumer for the profile output.
+   * @param goal The supplier for the desired state
+   * @param currentState The current state
    * @param requirements The subsystems required by this command.
    */
   public TrapezoidProfileCommand(
+      TrapezoidProfile profile,
+      Consumer<State> output,
+      Supplier<State> goal,
+      Supplier<State> currentState,
+      Subsystem... requirements) {
+    m_profile = requireNonNullParam(profile, "profile", "TrapezoidProfileCommand");
+    m_output = requireNonNullParam(output, "output", "TrapezoidProfileCommand");
+    m_goal = goal;
+    m_currentState = currentState;
+    m_newAPI = true;
+    addRequirements(requirements);
+  }
+
+  /**
+   * Creates a new TrapezoidProfileCommand that will execute the given {@link TrapezoidProfile}.
+   * Output will be piped to the provided consumer function.
+   *
+   * @param profile The motion profile to execute.
+   * @param output The consumer for the profile output.
+   * @param requirements The subsystems required by this command.
+   * @deprecated The new constructor allows you to pass in a supplier for desired and current state.
+   *     This allows you to change goals at runtime.
+   */
+  @Deprecated(since = "2024", forRemoval = true)
+  public TrapezoidProfileCommand(
       TrapezoidProfile profile, Consumer<State> output, Subsystem... requirements) {
     m_profile = requireNonNullParam(profile, "profile", "TrapezoidProfileCommand");
     m_output = requireNonNullParam(output, "output", "TrapezoidProfileCommand");
+    m_newAPI = false;
+    m_goal = null;
+    m_currentState = null;
     addRequirements(requirements);
   }
 
   @Override
   public void initialize() {
-    m_timer.reset();
-    m_timer.start();
+    m_timer.restart();
   }
 
   @Override
+  @SuppressWarnings("removal")
   public void execute() {
-    m_output.accept(m_profile.calculate(m_timer.get()));
+    if (m_newAPI) {
+      m_output.accept(m_profile.calculate(m_timer.get(), m_goal.get(), m_currentState.get()));
+    } else {
+      m_output.accept(m_profile.calculate(m_timer.get()));
+    }
   }
 
   @Override
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/TrapezoidProfileSubsystem.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/TrapezoidProfileSubsystem.java
index 1db6619..35c02e6 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/TrapezoidProfileSubsystem.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/TrapezoidProfileSubsystem.java
@@ -16,7 +16,7 @@
  */
 public abstract class TrapezoidProfileSubsystem extends SubsystemBase {
   private final double m_period;
-  private final TrapezoidProfile.Constraints m_constraints;
+  private final TrapezoidProfile m_profile;
 
   private TrapezoidProfile.State m_state;
   private TrapezoidProfile.State m_goal;
@@ -33,7 +33,8 @@
    */
   public TrapezoidProfileSubsystem(
       TrapezoidProfile.Constraints constraints, double initialPosition, double period) {
-    m_constraints = requireNonNullParam(constraints, "constraints", "TrapezoidProfileSubsystem");
+    requireNonNullParam(constraints, "constraints", "TrapezoidProfileSubsystem");
+    m_profile = new TrapezoidProfile(constraints);
     m_state = new TrapezoidProfile.State(initialPosition, 0);
     setGoal(initialPosition);
     m_period = period;
@@ -62,8 +63,7 @@
 
   @Override
   public void periodic() {
-    var profile = new TrapezoidProfile(m_constraints, m_goal, m_state);
-    m_state = profile.calculate(m_period);
+    m_state = m_profile.calculate(m_period, m_goal, m_state);
     if (m_enabled) {
       useState(m_state);
     }
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/WaitCommand.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/WaitCommand.java
index 647e87e..e7b32be 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/WaitCommand.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/WaitCommand.java
@@ -9,12 +9,11 @@
 import edu.wpi.first.wpilibj.Timer;
 
 /**
- * A command that does nothing but takes a specified amount of time to finish. Useful for
- * CommandGroups. Can also be subclassed to make a command with an internal timer.
+ * A command that does nothing but takes a specified amount of time to finish.
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-public class WaitCommand extends CommandBase {
+public class WaitCommand extends Command {
   protected Timer m_timer = new Timer();
   private final double m_duration;
 
@@ -30,8 +29,7 @@
 
   @Override
   public void initialize() {
-    m_timer.reset();
-    m_timer.start();
+    m_timer.restart();
   }
 
   @Override
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/WaitUntilCommand.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/WaitUntilCommand.java
index 4d51e62..b96bc26 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/WaitUntilCommand.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/WaitUntilCommand.java
@@ -15,7 +15,7 @@
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-public class WaitUntilCommand extends CommandBase {
+public class WaitUntilCommand extends Command {
   private final BooleanSupplier m_condition;
 
   /**
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/WrapperCommand.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/WrapperCommand.java
index c48871c..c687f0a 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/WrapperCommand.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/WrapperCommand.java
@@ -14,7 +14,7 @@
  * added to any other composition or scheduled individually, and the composition requires all
  * subsystems its components require.
  */
-public abstract class WrapperCommand extends CommandBase {
+public abstract class WrapperCommand extends Command {
   protected final Command m_command;
 
   /**
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/Button.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/Button.java
deleted file mode 100644
index 4e4e11a..0000000
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/Button.java
+++ /dev/null
@@ -1,169 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-package edu.wpi.first.wpilibj2.command.button;
-
-import edu.wpi.first.wpilibj2.command.Command;
-import edu.wpi.first.wpilibj2.command.Subsystem;
-import java.util.function.BooleanSupplier;
-
-/**
- * This class provides an easy way to link commands to OI inputs.
- *
- * <p>It is very easy to link a button to a command. For instance, you could link the trigger button
- * of a joystick to a "score" command.
- *
- * <p>This class represents a subclass of Trigger that is specifically aimed at buttons on an
- * operator interface as a common use case of the more generalized Trigger objects. This is a simple
- * wrapper around Trigger with the method names renamed to fit the Button object use.
- *
- * @deprecated Replace with {@link Trigger}.
- */
-@Deprecated
-public class Button extends Trigger {
-  /**
-   * Default constructor; creates a button that is never pressed.
-   *
-   * @deprecated Replace with {@code new Button(() -> false) }.
-   */
-  @Deprecated(since = "2023")
-  public Button() {}
-
-  /**
-   * Creates a new button with the given condition determining whether it is pressed.
-   *
-   * @param isPressed returns whether the trigger should be active
-   * @deprecated Replace with Trigger.
-   */
-  @Deprecated
-  public Button(BooleanSupplier isPressed) {
-    super(isPressed);
-  }
-
-  /**
-   * Starts the given command whenever the button is newly pressed.
-   *
-   * @param command the command to start
-   * @return this button, so calls can be chained
-   * @deprecated Replace with {@link Trigger#onTrue(Command)}
-   */
-  @Deprecated
-  public Button whenPressed(final Command command) {
-    whenActive(command);
-    return this;
-  }
-
-  /**
-   * Runs the given runnable whenever the button is newly pressed.
-   *
-   * @param toRun the runnable to run
-   * @param requirements the required subsystems
-   * @return this button, so calls can be chained
-   * @deprecated Replace with {@link #onTrue(Command)}, creating the InstantCommand manually
-   */
-  @Deprecated
-  public Button whenPressed(final Runnable toRun, Subsystem... requirements) {
-    whenActive(toRun, requirements);
-    return this;
-  }
-
-  /**
-   * Constantly starts the given command while the button is held.
-   *
-   * <p>{@link Command#schedule()} will be called repeatedly while the button is held, and will be
-   * canceled when the button is released.
-   *
-   * @param command the command to start
-   * @return this button, so calls can be chained
-   * @deprecated Use {@link #whileTrue(Command)} with {@link
-   *     edu.wpi.first.wpilibj2.command.RepeatCommand RepeatCommand}.
-   */
-  @Deprecated
-  public Button whileHeld(final Command command) {
-    whileActiveContinuous(command);
-    return this;
-  }
-
-  /**
-   * Constantly runs the given runnable while the button is held.
-   *
-   * @param toRun the runnable to run
-   * @param requirements the required subsystems
-   * @return this button, so calls can be chained
-   * @deprecated Use {@link #whileTrue(Command)} and construct a RunCommand manually
-   */
-  @Deprecated
-  public Button whileHeld(final Runnable toRun, Subsystem... requirements) {
-    whileActiveContinuous(toRun, requirements);
-    return this;
-  }
-
-  /**
-   * Starts the given command when the button is first pressed, and cancels it when it is released,
-   * but does not start it again if it ends or is otherwise interrupted.
-   *
-   * @param command the command to start
-   * @return this button, so calls can be chained
-   * @deprecated Replace with {@link Trigger#whileTrue(Command)}
-   */
-  @Deprecated
-  public Button whenHeld(final Command command) {
-    whileActiveOnce(command);
-    return this;
-  }
-
-  /**
-   * Starts the command when the button is released. The command is set to be interruptible.
-   *
-   * @param command the command to start
-   * @return this button, so calls can be chained
-   * @deprecated Replace with {@link Trigger#onFalse(Command)}
-   */
-  @Deprecated
-  public Button whenReleased(final Command command) {
-    whenInactive(command);
-    return this;
-  }
-
-  /**
-   * Runs the given runnable when the button is released.
-   *
-   * @param toRun the runnable to run
-   * @param requirements the required subsystems
-   * @return this button, so calls can be chained
-   * @deprecated Replace with {@link Trigger#onFalse(Command)}, creating the InstantCommand manually
-   */
-  @Deprecated
-  public Button whenReleased(final Runnable toRun, Subsystem... requirements) {
-    whenInactive(toRun, requirements);
-    return this;
-  }
-
-  /**
-   * Toggles the command whenever the button is pressed (on, then off, then on). The command is set
-   * to be interruptible.
-   *
-   * @param command the command to start
-   * @return this button, so calls can be chained
-   * @deprecated Replace with {@link Trigger#toggleOnTrue(Command)}
-   */
-  @Deprecated
-  public Button toggleWhenPressed(final Command command) {
-    toggleWhenActive(command);
-    return this;
-  }
-
-  /**
-   * Cancels the command when the button is pressed.
-   *
-   * @param command the command to start
-   * @return this button, so calls can be chained
-   * @deprecated Instead, pass this as an end condition to {@link Command#until(BooleanSupplier)}.
-   */
-  @Deprecated
-  public Button cancelWhenPressed(final Command command) {
-    cancelWhenActive(command);
-    return this;
-  }
-}
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/CommandJoystick.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/CommandJoystick.java
index 9f3d48a..f993609 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/CommandJoystick.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/CommandJoystick.java
@@ -9,7 +9,7 @@
 import edu.wpi.first.wpilibj2.command.CommandScheduler;
 
 /**
- * A subclass of {@link Joystick} with {@link Trigger} factories for command-based.
+ * A version of {@link Joystick} with {@link Trigger} factories for command-based.
  *
  * @see Joystick
  */
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/CommandPS5Controller.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/CommandPS5Controller.java
new file mode 100644
index 0000000..6b149d6
--- /dev/null
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/CommandPS5Controller.java
@@ -0,0 +1,389 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.wpilibj2.command.button;
+
+import edu.wpi.first.wpilibj.PS5Controller;
+import edu.wpi.first.wpilibj.event.EventLoop;
+import edu.wpi.first.wpilibj2.command.CommandScheduler;
+
+/**
+ * A version of {@link PS5Controller} with {@link Trigger} factories for command-based.
+ *
+ * @see PS5Controller
+ */
+@SuppressWarnings("MethodName")
+public class CommandPS5Controller extends CommandGenericHID {
+  private final PS5Controller m_hid;
+
+  /**
+   * Construct an instance of a device.
+   *
+   * @param port The port index on the Driver Station that the device is plugged into.
+   */
+  public CommandPS5Controller(int port) {
+    super(port);
+    m_hid = new PS5Controller(port);
+  }
+
+  /**
+   * Get the underlying GenericHID object.
+   *
+   * @return the wrapped GenericHID object
+   */
+  @Override
+  public PS5Controller getHID() {
+    return m_hid;
+  }
+
+  /**
+   * Constructs an event instance around the L2 button's digital signal.
+   *
+   * @return an event instance representing the L2 button's digital signal attached to the {@link
+   *     CommandScheduler#getDefaultButtonLoop() default scheduler button loop}.
+   */
+  public Trigger L2() {
+    return L2(CommandScheduler.getInstance().getDefaultButtonLoop());
+  }
+
+  /**
+   * Constructs an event instance around the L2 button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the L2 button's digital signal attached to the given
+   *     loop.
+   */
+  public Trigger L2(EventLoop loop) {
+    return m_hid.L2(loop).castTo(Trigger::new);
+  }
+
+  /**
+   * Constructs an event instance around the R2 button's digital signal.
+   *
+   * @return an event instance representing the R2 button's digital signal attached to the {@link
+   *     CommandScheduler#getDefaultButtonLoop() default scheduler button loop}.
+   */
+  public Trigger R2() {
+    return R2(CommandScheduler.getInstance().getDefaultButtonLoop());
+  }
+
+  /**
+   * Constructs an event instance around the R2 button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the R2 button's digital signal attached to the given
+   *     loop.
+   */
+  public Trigger R2(EventLoop loop) {
+    return m_hid.R2(loop).castTo(Trigger::new);
+  }
+
+  /**
+   * Constructs an event instance around the L1 button's digital signal.
+   *
+   * @return an event instance representing the L1 button's digital signal attached to the {@link
+   *     CommandScheduler#getDefaultButtonLoop() default scheduler button loop}.
+   */
+  public Trigger L1() {
+    return L1(CommandScheduler.getInstance().getDefaultButtonLoop());
+  }
+
+  /**
+   * Constructs an event instance around the L1 button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the L1 button's digital signal attached to the given
+   *     loop.
+   */
+  public Trigger L1(EventLoop loop) {
+    return m_hid.L1(loop).castTo(Trigger::new);
+  }
+
+  /**
+   * Constructs an event instance around the R1 button's digital signal.
+   *
+   * @return an event instance representing the R1 button's digital signal attached to the {@link
+   *     CommandScheduler#getDefaultButtonLoop() default scheduler button loop}.
+   */
+  public Trigger R1() {
+    return R1(CommandScheduler.getInstance().getDefaultButtonLoop());
+  }
+
+  /**
+   * Constructs an event instance around the R1 button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the R1 button's digital signal attached to the given
+   *     loop.
+   */
+  public Trigger R1(EventLoop loop) {
+    return m_hid.R1(loop).castTo(Trigger::new);
+  }
+
+  /**
+   * Constructs an event instance around the L3 button's digital signal.
+   *
+   * @return an event instance representing the L3 button's digital signal attached to the {@link
+   *     CommandScheduler#getDefaultButtonLoop() default scheduler button loop}.
+   */
+  public Trigger L3() {
+    return L3(CommandScheduler.getInstance().getDefaultButtonLoop());
+  }
+
+  /**
+   * Constructs an event instance around the L3 button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the L3 button's digital signal attached to the given
+   *     loop.
+   */
+  public Trigger L3(EventLoop loop) {
+    return m_hid.L3(loop).castTo(Trigger::new);
+  }
+
+  /**
+   * Constructs an event instance around the R3 button's digital signal.
+   *
+   * @return an event instance representing the R3 button's digital signal attached to the {@link
+   *     CommandScheduler#getDefaultButtonLoop() default scheduler button loop}.
+   */
+  public Trigger R3() {
+    return R3(CommandScheduler.getInstance().getDefaultButtonLoop());
+  }
+
+  /**
+   * Constructs an event instance around the R3 button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the R3 button's digital signal attached to the given
+   *     loop.
+   */
+  public Trigger R3(EventLoop loop) {
+    return m_hid.R3(loop).castTo(Trigger::new);
+  }
+
+  /**
+   * Constructs an event instance around the square button's digital signal.
+   *
+   * @return an event instance representing the square button's digital signal attached to the
+   *     {@link CommandScheduler#getDefaultButtonLoop() default scheduler button loop}.
+   */
+  public Trigger square() {
+    return square(CommandScheduler.getInstance().getDefaultButtonLoop());
+  }
+
+  /**
+   * Constructs an event instance around the square button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the square button's digital signal attached to the given
+   *     loop.
+   */
+  public Trigger square(EventLoop loop) {
+    return m_hid.square(loop).castTo(Trigger::new);
+  }
+
+  /**
+   * Constructs an event instance around the cross button's digital signal.
+   *
+   * @return an event instance representing the cross button's digital signal attached to the {@link
+   *     CommandScheduler#getDefaultButtonLoop() default scheduler button loop}.
+   */
+  public Trigger cross() {
+    return cross(CommandScheduler.getInstance().getDefaultButtonLoop());
+  }
+
+  /**
+   * Constructs an event instance around the cross button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the cross button's digital signal attached to the given
+   *     loop.
+   */
+  public Trigger cross(EventLoop loop) {
+    return m_hid.cross(loop).castTo(Trigger::new);
+  }
+
+  /**
+   * Constructs an event instance around the triangle button's digital signal.
+   *
+   * @return an event instance representing the triangle button's digital signal attached to the
+   *     {@link CommandScheduler#getDefaultButtonLoop() default scheduler button loop}.
+   */
+  public Trigger triangle() {
+    return triangle(CommandScheduler.getInstance().getDefaultButtonLoop());
+  }
+
+  /**
+   * Constructs an event instance around the triangle button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the triangle button's digital signal attached to the
+   *     given loop.
+   */
+  public Trigger triangle(EventLoop loop) {
+    return m_hid.triangle(loop).castTo(Trigger::new);
+  }
+
+  /**
+   * Constructs an event instance around the circle button's digital signal.
+   *
+   * @return an event instance representing the circle button's digital signal attached to the
+   *     {@link CommandScheduler#getDefaultButtonLoop() default scheduler button loop}.
+   */
+  public Trigger circle() {
+    return circle(CommandScheduler.getInstance().getDefaultButtonLoop());
+  }
+
+  /**
+   * Constructs an event instance around the circle button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the circle button's digital signal attached to the given
+   *     loop.
+   */
+  public Trigger circle(EventLoop loop) {
+    return m_hid.circle(loop).castTo(Trigger::new);
+  }
+
+  /**
+   * Constructs an event instance around the create button's digital signal.
+   *
+   * @return an event instance representing the create button's digital signal attached to the
+   *     {@link CommandScheduler#getDefaultButtonLoop() default scheduler button loop}.
+   */
+  public Trigger create() {
+    return create(CommandScheduler.getInstance().getDefaultButtonLoop());
+  }
+
+  /**
+   * Constructs an event instance around the create button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the create button's digital signal attached to the given
+   *     loop.
+   */
+  public Trigger create(EventLoop loop) {
+    return m_hid.create(loop).castTo(Trigger::new);
+  }
+
+  /**
+   * Constructs an event instance around the PS button's digital signal.
+   *
+   * @return an event instance representing the PS button's digital signal attached to the {@link
+   *     CommandScheduler#getDefaultButtonLoop() default scheduler button loop}.
+   */
+  public Trigger PS() {
+    return PS(CommandScheduler.getInstance().getDefaultButtonLoop());
+  }
+
+  /**
+   * Constructs an event instance around the PS button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the PS button's digital signal attached to the given
+   *     loop.
+   */
+  public Trigger PS(EventLoop loop) {
+    return m_hid.PS(loop).castTo(Trigger::new);
+  }
+
+  /**
+   * Constructs an event instance around the options button's digital signal.
+   *
+   * @return an event instance representing the options button's digital signal attached to the
+   *     {@link CommandScheduler#getDefaultButtonLoop() default scheduler button loop}.
+   */
+  public Trigger options() {
+    return options(CommandScheduler.getInstance().getDefaultButtonLoop());
+  }
+
+  /**
+   * Constructs an event instance around the options button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the options button's digital signal attached to the
+   *     given loop.
+   */
+  public Trigger options(EventLoop loop) {
+    return m_hid.options(loop).castTo(Trigger::new);
+  }
+
+  /**
+   * Constructs an event instance around the touchpad's digital signal.
+   *
+   * @return an event instance representing the touchpad's digital signal attached to the {@link
+   *     CommandScheduler#getDefaultButtonLoop() default scheduler button loop}.
+   */
+  public Trigger touchpad() {
+    return touchpad(CommandScheduler.getInstance().getDefaultButtonLoop());
+  }
+
+  /**
+   * Constructs an event instance around the touchpad's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the touchpad's digital signal attached to the given
+   *     loop.
+   */
+  public Trigger touchpad(EventLoop loop) {
+    return m_hid.touchpad(loop).castTo(Trigger::new);
+  }
+
+  /**
+   * Get the X axis value of left side of the controller.
+   *
+   * @return the axis value.
+   */
+  public double getLeftX() {
+    return m_hid.getLeftX();
+  }
+
+  /**
+   * Get the X axis value of right side of the controller.
+   *
+   * @return the axis value.
+   */
+  public double getRightX() {
+    return m_hid.getRightX();
+  }
+
+  /**
+   * Get the Y axis value of left side of the controller.
+   *
+   * @return the axis value.
+   */
+  public double getLeftY() {
+    return m_hid.getLeftY();
+  }
+
+  /**
+   * Get the Y axis value of right side of the controller.
+   *
+   * @return the axis value.
+   */
+  public double getRightY() {
+    return m_hid.getRightY();
+  }
+
+  /**
+   * Get the L2 axis value of the controller. Note that this axis is bound to the range of [0, 1] as
+   * opposed to the usual [-1, 1].
+   *
+   * @return the axis value.
+   */
+  public double getL2Axis() {
+    return m_hid.getL2Axis();
+  }
+
+  /**
+   * Get the R2 axis value of the controller. Note that this axis is bound to the range of [0, 1] as
+   * opposed to the usual [-1, 1].
+   *
+   * @return the axis value.
+   */
+  public double getR2Axis() {
+    return m_hid.getR2Axis();
+  }
+}
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/CommandXboxController.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/CommandXboxController.java
index 1e83e83..6969c98 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/CommandXboxController.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/CommandXboxController.java
@@ -9,7 +9,7 @@
 import edu.wpi.first.wpilibj2.command.CommandScheduler;
 
 /**
- * A subclass of {@link XboxController} with {@link Trigger} factories for command-based.
+ * A version of {@link XboxController} with {@link Trigger} factories for command-based.
  *
  * @see XboxController
  */
@@ -261,13 +261,13 @@
    * Constructs a Trigger instance around the axis value of the left trigger. The returned trigger
    * will be true when the axis value is greater than {@code threshold}.
    *
-   * @param loop the event loop instance to attach the Trigger to.
    * @param threshold the minimum axis value for the returned {@link Trigger} to be true. This value
    *     should be in the range [0, 1] where 0 is the unpressed state of the axis.
+   * @param loop the event loop instance to attach the Trigger to.
    * @return a Trigger instance that is true when the left trigger's axis exceeds the provided
    *     threshold, attached to the given event loop
    */
-  public Trigger leftTrigger(EventLoop loop, double threshold) {
+  public Trigger leftTrigger(double threshold, EventLoop loop) {
     return m_hid.leftTrigger(threshold, loop).castTo(Trigger::new);
   }
 
@@ -282,7 +282,7 @@
    *     button loop}.
    */
   public Trigger leftTrigger(double threshold) {
-    return leftTrigger(CommandScheduler.getInstance().getDefaultButtonLoop(), threshold);
+    return leftTrigger(threshold, CommandScheduler.getInstance().getDefaultButtonLoop());
   }
 
   /**
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/InternalButton.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/InternalButton.java
index 3106264..f4897f2 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/InternalButton.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/InternalButton.java
@@ -12,8 +12,7 @@
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-@SuppressWarnings("deprecation")
-public class InternalButton extends Button {
+public class InternalButton extends Trigger {
   // need to be references, so they can be mutated after being captured in the constructor.
   private final AtomicBoolean m_pressed;
   private final AtomicBoolean m_inverted;
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/JoystickButton.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/JoystickButton.java
index f22d443..e85b666 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/JoystickButton.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/JoystickButton.java
@@ -9,12 +9,11 @@
 import edu.wpi.first.wpilibj.GenericHID;
 
 /**
- * A {@link Button} that gets its state from a {@link GenericHID}.
+ * A {@link Trigger} that gets its state from a {@link GenericHID}.
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-@SuppressWarnings("deprecation")
-public class JoystickButton extends Button {
+public class JoystickButton extends Trigger {
   /**
    * Creates a joystick button for triggering commands.
    *
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/NetworkButton.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/NetworkButton.java
index b21cd97..0fb5571 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/NetworkButton.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/NetworkButton.java
@@ -12,12 +12,11 @@
 import edu.wpi.first.networktables.NetworkTableInstance;
 
 /**
- * A {@link Button} that uses a {@link NetworkTable} boolean field.
+ * A {@link Trigger} that uses a {@link NetworkTable} boolean field.
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-@SuppressWarnings("deprecation")
-public class NetworkButton extends Button {
+public class NetworkButton extends Trigger {
   /**
    * Creates a NetworkButton that commands can be bound to.
    *
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/POVButton.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/POVButton.java
index 28087ae..b8a63e4 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/POVButton.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/POVButton.java
@@ -9,12 +9,11 @@
 import edu.wpi.first.wpilibj.GenericHID;
 
 /**
- * A {@link Button} that gets its state from a POV on a {@link GenericHID}.
+ * A {@link Trigger} that gets its state from a POV on a {@link GenericHID}.
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-@SuppressWarnings("deprecation")
-public class POVButton extends Button {
+public class POVButton extends Trigger {
   /**
    * Creates a POV button for triggering commands.
    *
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/Trigger.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/Trigger.java
index 19b8b20..5bdee37 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/Trigger.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/Trigger.java
@@ -7,12 +7,9 @@
 import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
 
 import edu.wpi.first.math.filter.Debouncer;
-import edu.wpi.first.wpilibj.event.BooleanEvent;
 import edu.wpi.first.wpilibj.event.EventLoop;
 import edu.wpi.first.wpilibj2.command.Command;
 import edu.wpi.first.wpilibj2.command.CommandScheduler;
-import edu.wpi.first.wpilibj2.command.InstantCommand;
-import edu.wpi.first.wpilibj2.command.Subsystem;
 import java.util.function.BooleanSupplier;
 
 /**
@@ -52,12 +49,6 @@
     this(CommandScheduler.getInstance().getDefaultButtonLoop(), condition);
   }
 
-  /** Creates a new trigger that is always `false`. */
-  @Deprecated
-  public Trigger() {
-    this(() -> false);
-  }
-
   /**
    * Starts the given command whenever the condition changes from `false` to `true`.
    *
@@ -65,7 +56,7 @@
    * @return this trigger, so calls can be chained
    */
   public Trigger onTrue(Command command) {
-    requireNonNullParam(command, "command", "onRising");
+    requireNonNullParam(command, "command", "onTrue");
     m_loop.bind(
         new Runnable() {
           private boolean m_pressedLast = m_condition.getAsBoolean();
@@ -91,7 +82,7 @@
    * @return this trigger, so calls can be chained
    */
   public Trigger onFalse(Command command) {
-    requireNonNullParam(command, "command", "onFalling");
+    requireNonNullParam(command, "command", "onFalse");
     m_loop.bind(
         new Runnable() {
           private boolean m_pressedLast = m_condition.getAsBoolean();
@@ -121,7 +112,7 @@
    * @return this trigger, so calls can be chained
    */
   public Trigger whileTrue(Command command) {
-    requireNonNullParam(command, "command", "whileHigh");
+    requireNonNullParam(command, "command", "whileTrue");
     m_loop.bind(
         new Runnable() {
           private boolean m_pressedLast = m_condition.getAsBoolean();
@@ -153,7 +144,7 @@
    * @return this trigger, so calls can be chained
    */
   public Trigger whileFalse(Command command) {
-    requireNonNullParam(command, "command", "whileLow");
+    requireNonNullParam(command, "command", "whileFalse");
     m_loop.bind(
         new Runnable() {
           private boolean m_pressedLast = m_condition.getAsBoolean();
@@ -181,7 +172,7 @@
    * @return this trigger, so calls can be chained
    */
   public Trigger toggleOnTrue(Command command) {
-    requireNonNullParam(command, "command", "toggleOnRising");
+    requireNonNullParam(command, "command", "toggleOnTrue");
     m_loop.bind(
         new Runnable() {
           private boolean m_pressedLast = m_condition.getAsBoolean();
@@ -205,13 +196,13 @@
   }
 
   /**
-   * Toggles a command when the condition changes from `true` to the low state.
+   * Toggles a command when the condition changes from `true` to `false`.
    *
    * @param command the command to toggle
    * @return this trigger, so calls can be chained
    */
   public Trigger toggleOnFalse(Command command) {
-    requireNonNullParam(command, "command", "toggleOnFalling");
+    requireNonNullParam(command, "command", "toggleOnFalse");
     m_loop.bind(
         new Runnable() {
           private boolean m_pressedLast = m_condition.getAsBoolean();
@@ -234,238 +225,6 @@
     return this;
   }
 
-  /**
-   * Starts the given command whenever the trigger just becomes active.
-   *
-   * @param command the command to start
-   * @return this trigger, so calls can be chained
-   * @deprecated Use {@link #onTrue(Command)} instead.
-   */
-  @Deprecated
-  public Trigger whenActive(final Command command) {
-    requireNonNullParam(command, "command", "whenActive");
-
-    m_loop.bind(
-        new Runnable() {
-          private boolean m_pressedLast = m_condition.getAsBoolean();
-
-          @Override
-          public void run() {
-            boolean pressed = m_condition.getAsBoolean();
-
-            if (!m_pressedLast && pressed) {
-              command.schedule();
-            }
-
-            m_pressedLast = pressed;
-          }
-        });
-    return this;
-  }
-
-  /**
-   * Runs the given runnable whenever the trigger just becomes active.
-   *
-   * @param toRun the runnable to run
-   * @param requirements the required subsystems
-   * @return this trigger, so calls can be chained
-   * @deprecated Replace with {@link #onTrue(Command)}, creating the InstantCommand manually
-   */
-  @Deprecated
-  public Trigger whenActive(final Runnable toRun, Subsystem... requirements) {
-    return whenActive(new InstantCommand(toRun, requirements));
-  }
-
-  /**
-   * Constantly starts the given command while the button is held.
-   *
-   * <p>{@link Command#schedule()} will be called repeatedly while the trigger is active, and will
-   * be canceled when the trigger becomes inactive.
-   *
-   * @param command the command to start
-   * @return this trigger, so calls can be chained
-   * @deprecated Use {@link #whileTrue(Command)} with {@link
-   *     edu.wpi.first.wpilibj2.command.RepeatCommand RepeatCommand}, or bind {@link
-   *     Command#schedule() command::schedule} to {@link BooleanEvent#ifHigh(Runnable)} (passing no
-   *     requirements).
-   */
-  @Deprecated
-  public Trigger whileActiveContinuous(final Command command) {
-    requireNonNullParam(command, "command", "whileActiveContinuous");
-
-    m_loop.bind(
-        new Runnable() {
-          private boolean m_pressedLast = m_condition.getAsBoolean();
-
-          @Override
-          public void run() {
-            boolean pressed = m_condition.getAsBoolean();
-
-            if (pressed) {
-              command.schedule();
-            } else if (m_pressedLast) {
-              command.cancel();
-            }
-
-            m_pressedLast = pressed;
-          }
-        });
-
-    return this;
-  }
-
-  /**
-   * Constantly runs the given runnable while the button is held.
-   *
-   * @param toRun the runnable to run
-   * @param requirements the required subsystems
-   * @return this trigger, so calls can be chained
-   * @deprecated Use {@link #whileTrue(Command)} and construct a RunCommand manually
-   */
-  @Deprecated
-  public Trigger whileActiveContinuous(final Runnable toRun, Subsystem... requirements) {
-    return whileActiveContinuous(new InstantCommand(toRun, requirements));
-  }
-
-  /**
-   * Starts the given command when the trigger initially becomes active, and ends it when it becomes
-   * inactive, but does not re-start it in-between.
-   *
-   * @param command the command to start
-   * @return this trigger, so calls can be chained
-   * @deprecated Use {@link #whileTrue(Command)} instead.
-   */
-  @Deprecated
-  public Trigger whileActiveOnce(final Command command) {
-    requireNonNullParam(command, "command", "whileActiveOnce");
-
-    m_loop.bind(
-        new Runnable() {
-          private boolean m_pressedLast = m_condition.getAsBoolean();
-
-          @Override
-          public void run() {
-            boolean pressed = m_condition.getAsBoolean();
-
-            if (!m_pressedLast && pressed) {
-              command.schedule();
-            } else if (m_pressedLast && !pressed) {
-              command.cancel();
-            }
-
-            m_pressedLast = pressed;
-          }
-        });
-    return this;
-  }
-
-  /**
-   * Starts the command when the trigger becomes inactive.
-   *
-   * @param command the command to start
-   * @return this trigger, so calls can be chained
-   * @deprecated Use {@link #onFalse(Command)} instead.
-   */
-  @Deprecated
-  public Trigger whenInactive(final Command command) {
-    requireNonNullParam(command, "command", "whenInactive");
-
-    m_loop.bind(
-        new Runnable() {
-          private boolean m_pressedLast = m_condition.getAsBoolean();
-
-          @Override
-          public void run() {
-            boolean pressed = m_condition.getAsBoolean();
-
-            if (m_pressedLast && !pressed) {
-              command.schedule();
-            }
-
-            m_pressedLast = pressed;
-          }
-        });
-
-    return this;
-  }
-
-  /**
-   * Runs the given runnable when the trigger becomes inactive.
-   *
-   * @param toRun the runnable to run
-   * @param requirements the required subsystems
-   * @return this trigger, so calls can be chained
-   * @deprecated Construct the InstantCommand manually and replace with {@link #onFalse(Command)}
-   */
-  @Deprecated
-  public Trigger whenInactive(final Runnable toRun, Subsystem... requirements) {
-    return whenInactive(new InstantCommand(toRun, requirements));
-  }
-
-  /**
-   * Toggles a command when the trigger becomes active.
-   *
-   * @param command the command to toggle
-   * @return this trigger, so calls can be chained
-   * @deprecated Use {@link #toggleOnTrue(Command)} instead.
-   */
-  @Deprecated
-  public Trigger toggleWhenActive(final Command command) {
-    requireNonNullParam(command, "command", "toggleWhenActive");
-
-    m_loop.bind(
-        new Runnable() {
-          private boolean m_pressedLast = m_condition.getAsBoolean();
-
-          @Override
-          public void run() {
-            boolean pressed = m_condition.getAsBoolean();
-
-            if (!m_pressedLast && pressed) {
-              if (command.isScheduled()) {
-                command.cancel();
-              } else {
-                command.schedule();
-              }
-            }
-
-            m_pressedLast = pressed;
-          }
-        });
-
-    return this;
-  }
-
-  /**
-   * Cancels a command when the trigger becomes active.
-   *
-   * @param command the command to cancel
-   * @return this trigger, so calls can be chained
-   * @deprecated Instead, pass this as an end condition to {@link Command#until(BooleanSupplier)}.
-   */
-  @Deprecated
-  public Trigger cancelWhenActive(final Command command) {
-    requireNonNullParam(command, "command", "cancelWhenActive");
-
-    m_loop.bind(
-        new Runnable() {
-          private boolean m_pressedLast = m_condition.getAsBoolean();
-
-          @Override
-          public void run() {
-            boolean pressed = m_condition.getAsBoolean();
-
-            if (!m_pressedLast && pressed) {
-              command.cancel();
-            }
-
-            m_pressedLast = pressed;
-          }
-        });
-
-    return this;
-  }
-
   @Override
   public boolean getAsBoolean() {
     return m_condition.getAsBoolean();
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/Command.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/Command.cpp
index b173ada..66de555 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/Command.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/Command.cpp
@@ -4,6 +4,9 @@
 
 #include "frc2/command/Command.h"
 
+#include <wpi/sendable/SendableBuilder.h>
+#include <wpi/sendable/SendableRegistry.h>
+
 #include "frc2/command/CommandHelper.h"
 #include "frc2/command/CommandScheduler.h"
 #include "frc2/command/ConditionalCommand.h"
@@ -11,7 +14,6 @@
 #include "frc2/command/ParallelCommandGroup.h"
 #include "frc2/command/ParallelDeadlineGroup.h"
 #include "frc2/command/ParallelRaceGroup.h"
-#include "frc2/command/PerpetualCommand.h"
 #include "frc2/command/RepeatCommand.h"
 #include "frc2/command/SequentialCommandGroup.h"
 #include "frc2/command/WaitCommand.h"
@@ -20,6 +22,10 @@
 
 using namespace frc2;
 
+Command::Command() {
+  wpi::SendableRegistry::Add(this, GetTypeName(*this));
+}
+
 Command::~Command() {
   CommandScheduler::GetInstance().Cancel(this);
 }
@@ -33,6 +39,38 @@
 void Command::Execute() {}
 void Command::End(bool interrupted) {}
 
+wpi::SmallSet<Subsystem*, 4> Command::GetRequirements() const {
+  return m_requirements;
+}
+
+void Command::AddRequirements(Requirements requirements) {
+  m_requirements.insert(requirements.begin(), requirements.end());
+}
+
+void Command::AddRequirements(wpi::SmallSet<Subsystem*, 4> requirements) {
+  m_requirements.insert(requirements.begin(), requirements.end());
+}
+
+void Command::AddRequirements(Subsystem* requirement) {
+  m_requirements.insert(requirement);
+}
+
+void Command::SetName(std::string_view name) {
+  wpi::SendableRegistry::SetName(this, name);
+}
+
+std::string Command::GetName() const {
+  return wpi::SendableRegistry::GetName(this);
+}
+
+std::string Command::GetSubsystem() const {
+  return wpi::SendableRegistry::GetSubsystem(this);
+}
+
+void Command::SetSubsystem(std::string_view subsystem) {
+  wpi::SendableRegistry::SetSubsystem(this, subsystem);
+}
+
 CommandPtr Command::WithTimeout(units::second_t duration) && {
   return std::move(*this).ToPtr().WithTimeout(duration);
 }
@@ -41,6 +79,10 @@
   return std::move(*this).ToPtr().Until(std::move(condition));
 }
 
+CommandPtr Command::OnlyWhile(std::function<bool()> condition) && {
+  return std::move(*this).ToPtr().OnlyWhile(std::move(condition));
+}
+
 CommandPtr Command::IgnoringDisable(bool doesRunWhenDisabled) && {
   return std::move(*this).ToPtr().IgnoringDisable(doesRunWhenDisabled);
 }
@@ -50,40 +92,17 @@
   return std::move(*this).ToPtr().WithInterruptBehavior(interruptBehavior);
 }
 
-CommandPtr Command::WithInterrupt(std::function<bool()> condition) && {
-  return std::move(*this).ToPtr().Until(std::move(condition));
-}
-
-CommandPtr Command::BeforeStarting(
-    std::function<void()> toRun,
-    std::initializer_list<Subsystem*> requirements) && {
-  return std::move(*this).BeforeStarting(
-      std::move(toRun), {requirements.begin(), requirements.end()});
-}
-
-CommandPtr Command::BeforeStarting(
-    std::function<void()> toRun, std::span<Subsystem* const> requirements) && {
+CommandPtr Command::BeforeStarting(std::function<void()> toRun,
+                                   Requirements requirements) && {
   return std::move(*this).ToPtr().BeforeStarting(std::move(toRun),
                                                  requirements);
 }
 
 CommandPtr Command::AndThen(std::function<void()> toRun,
-                            std::initializer_list<Subsystem*> requirements) && {
-  return std::move(*this).AndThen(std::move(toRun),
-                                  {requirements.begin(), requirements.end()});
-}
-
-CommandPtr Command::AndThen(std::function<void()> toRun,
-                            std::span<Subsystem* const> requirements) && {
+                            Requirements requirements) && {
   return std::move(*this).ToPtr().AndThen(std::move(toRun), requirements);
 }
 
-PerpetualCommand Command::Perpetually() && {
-  WPI_IGNORE_DEPRECATED
-  return PerpetualCommand(std::move(*this).TransferOwnership());
-  WPI_UNIGNORE_DEPRECATED
-}
-
 CommandPtr Command::Repeatedly() && {
   return std::move(*this).ToPtr().Repeatedly();
 }
@@ -96,11 +115,19 @@
   return std::move(*this).ToPtr().Unless(std::move(condition));
 }
 
+CommandPtr Command::OnlyIf(std::function<bool()> condition) && {
+  return std::move(*this).ToPtr().OnlyIf(std::move(condition));
+}
+
 CommandPtr Command::FinallyDo(std::function<void(bool)> end) && {
   return std::move(*this).ToPtr().FinallyDo(std::move(end));
 }
 
-CommandPtr Command::HandleInterrupt(std::function<void(void)> handler) && {
+CommandPtr Command::FinallyDo(std::function<void()> end) && {
+  return std::move(*this).ToPtr().FinallyDo(std::move(end));
+}
+
+CommandPtr Command::HandleInterrupt(std::function<void()> handler) && {
   return std::move(*this).ToPtr().HandleInterrupt(std::move(handler));
 }
 
@@ -128,12 +155,6 @@
   return hasRequirement;
 }
 
-std::string Command::GetName() const {
-  return GetTypeName(*this);
-}
-
-void Command::SetName(std::string_view name) {}
-
 bool Command::IsComposed() const {
   return m_isComposed;
 }
@@ -142,12 +163,37 @@
   m_isComposed = isComposed;
 }
 
-bool Command::IsGrouped() const {
-  return IsComposed();
-}
-
-void Command::SetGrouped(bool grouped) {
-  SetComposed(grouped);
+void Command::InitSendable(wpi::SendableBuilder& builder) {
+  builder.SetSmartDashboardType("Command");
+  builder.AddStringProperty(
+      ".name", [this] { return GetName(); }, nullptr);
+  builder.AddBooleanProperty(
+      "running", [this] { return IsScheduled(); },
+      [this](bool value) {
+        bool isScheduled = IsScheduled();
+        if (value && !isScheduled) {
+          Schedule();
+        } else if (!value && isScheduled) {
+          Cancel();
+        }
+      });
+  builder.AddBooleanProperty(
+      ".isParented", [this] { return IsComposed(); }, nullptr);
+  builder.AddStringProperty(
+      "interruptBehavior",
+      [this] {
+        switch (GetInterruptionBehavior()) {
+          case Command::InterruptionBehavior::kCancelIncoming:
+            return "kCancelIncoming";
+          case Command::InterruptionBehavior::kCancelSelf:
+            return "kCancelSelf";
+          default:
+            return "Invalid";
+        }
+      },
+      nullptr);
+  builder.AddBooleanProperty(
+      "runsWhenDisabled", [this] { return RunsWhenDisabled(); }, nullptr);
 }
 
 namespace frc2 {
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandBase.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandBase.cpp
index 416c78b..7e2fad1 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandBase.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandBase.cpp
@@ -3,82 +3,3 @@
 // the WPILib BSD license file in the root directory of this project.
 
 #include "frc2/command/CommandBase.h"
-
-#include <wpi/sendable/SendableBuilder.h>
-#include <wpi/sendable/SendableRegistry.h>
-
-using namespace frc2;
-
-CommandBase::CommandBase() {
-  wpi::SendableRegistry::Add(this, GetTypeName(*this));
-}
-
-void CommandBase::AddRequirements(
-    std::initializer_list<Subsystem*> requirements) {
-  m_requirements.insert(requirements.begin(), requirements.end());
-}
-
-void CommandBase::AddRequirements(std::span<Subsystem* const> requirements) {
-  m_requirements.insert(requirements.begin(), requirements.end());
-}
-
-void CommandBase::AddRequirements(wpi::SmallSet<Subsystem*, 4> requirements) {
-  m_requirements.insert(requirements.begin(), requirements.end());
-}
-
-void CommandBase::AddRequirements(Subsystem* requirement) {
-  m_requirements.insert(requirement);
-}
-
-wpi::SmallSet<Subsystem*, 4> CommandBase::GetRequirements() const {
-  return m_requirements;
-}
-
-void CommandBase::SetName(std::string_view name) {
-  wpi::SendableRegistry::SetName(this, name);
-}
-
-std::string CommandBase::GetName() const {
-  return wpi::SendableRegistry::GetName(this);
-}
-
-std::string CommandBase::GetSubsystem() const {
-  return wpi::SendableRegistry::GetSubsystem(this);
-}
-
-void CommandBase::SetSubsystem(std::string_view subsystem) {
-  wpi::SendableRegistry::SetSubsystem(this, subsystem);
-}
-
-void CommandBase::InitSendable(wpi::SendableBuilder& builder) {
-  builder.SetSmartDashboardType("Command");
-  builder.AddStringProperty(
-      ".name", [this] { return GetName(); }, nullptr);
-  builder.AddBooleanProperty(
-      "running", [this] { return IsScheduled(); },
-      [this](bool value) {
-        bool isScheduled = IsScheduled();
-        if (value && !isScheduled) {
-          Schedule();
-        } else if (!value && isScheduled) {
-          Cancel();
-        }
-      });
-  builder.AddBooleanProperty(
-      ".isParented", [this] { return IsComposed(); }, nullptr);
-  builder.AddStringProperty(
-      "interruptBehavior",
-      [this] {
-        switch (GetInterruptionBehavior()) {
-          case Command::InterruptionBehavior::kCancelIncoming:
-            return "kCancelIncoming";
-          case Command::InterruptionBehavior::kCancelSelf:
-            return "kCancelSelf";
-          default:
-            return "Invalid";
-        }
-      },
-      nullptr);
-  builder.AddBooleanProperty(
-      "runsWhenDisabled", [this] { return RunsWhenDisabled(); }, nullptr);
-}
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandGroupBase.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandGroupBase.cpp
deleted file mode 100644
index eb9c293..0000000
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandGroupBase.cpp
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "frc2/command/CommandGroupBase.h"
-
-using namespace frc2;
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandPtr.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandPtr.cpp
index 44150a2..6f7e418 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandPtr.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandPtr.cpp
@@ -86,15 +86,7 @@
 }
 
 CommandPtr CommandPtr::AndThen(std::function<void()> toRun,
-                               std::span<Subsystem* const> requirements) && {
-  AssertValid();
-  return std::move(*this).AndThen(CommandPtr(
-      std::make_unique<InstantCommand>(std::move(toRun), requirements)));
-}
-
-CommandPtr CommandPtr::AndThen(
-    std::function<void()> toRun,
-    std::initializer_list<Subsystem*> requirements) && {
+                               Requirements requirements) && {
   AssertValid();
   return std::move(*this).AndThen(CommandPtr(
       std::make_unique<InstantCommand>(std::move(toRun), requirements)));
@@ -109,16 +101,8 @@
   return std::move(*this);
 }
 
-CommandPtr CommandPtr::BeforeStarting(
-    std::function<void()> toRun, std::span<Subsystem* const> requirements) && {
-  AssertValid();
-  return std::move(*this).BeforeStarting(CommandPtr(
-      std::make_unique<InstantCommand>(std::move(toRun), requirements)));
-}
-
-CommandPtr CommandPtr::BeforeStarting(
-    std::function<void()> toRun,
-    std::initializer_list<Subsystem*> requirements) && {
+CommandPtr CommandPtr::BeforeStarting(std::function<void()> toRun,
+                                      Requirements requirements) && {
   AssertValid();
   return std::move(*this).BeforeStarting(CommandPtr(
       std::make_unique<InstantCommand>(std::move(toRun), requirements)));
@@ -151,6 +135,11 @@
   return std::move(*this);
 }
 
+CommandPtr CommandPtr::OnlyWhile(std::function<bool()> condition) && {
+  AssertValid();
+  return std::move(*this).Until(std::not_fn(std::move(condition)));
+}
+
 CommandPtr CommandPtr::Unless(std::function<bool()> condition) && {
   AssertValid();
   m_ptr = std::make_unique<ConditionalCommand>(
@@ -159,6 +148,11 @@
   return std::move(*this);
 }
 
+CommandPtr CommandPtr::OnlyIf(std::function<bool()> condition) && {
+  AssertValid();
+  return std::move(*this).Unless(std::not_fn(std::move(condition)));
+}
+
 CommandPtr CommandPtr::DeadlineWith(CommandPtr&& parallel) && {
   AssertValid();
   std::vector<std::unique_ptr<Command>> vec;
@@ -209,7 +203,13 @@
   return std::move(*this);
 }
 
-CommandPtr CommandPtr::HandleInterrupt(std::function<void(void)> handler) && {
+CommandPtr CommandPtr::FinallyDo(std::function<void()> end) && {
+  AssertValid();
+  return std::move(*this).FinallyDo(
+      [endHandler = std::move(end)](bool interrupted) { endHandler(); });
+}
+
+CommandPtr CommandPtr::HandleInterrupt(std::function<void()> handler) && {
   AssertValid();
   return std::move(*this).FinallyDo(
       [handler = std::move(handler)](bool interrupted) {
@@ -226,37 +226,37 @@
   return std::move(wrapper).ToPtr();
 }
 
-CommandBase* CommandPtr::get() const {
+Command* CommandPtr::get() const& {
   AssertValid();
   return m_ptr.get();
 }
 
-std::unique_ptr<CommandBase> CommandPtr::Unwrap() && {
+std::unique_ptr<Command> CommandPtr::Unwrap() && {
   AssertValid();
   return std::move(m_ptr);
 }
 
-void CommandPtr::Schedule() const {
+void CommandPtr::Schedule() const& {
   AssertValid();
   CommandScheduler::GetInstance().Schedule(*this);
 }
 
-void CommandPtr::Cancel() const {
+void CommandPtr::Cancel() const& {
   AssertValid();
   CommandScheduler::GetInstance().Cancel(*this);
 }
 
-bool CommandPtr::IsScheduled() const {
+bool CommandPtr::IsScheduled() const& {
   AssertValid();
   return CommandScheduler::GetInstance().IsScheduled(*this);
 }
 
-bool CommandPtr::HasRequirement(Subsystem* requirement) const {
+bool CommandPtr::HasRequirement(Subsystem* requirement) const& {
   AssertValid();
   return m_ptr->HasRequirement(requirement);
 }
 
-CommandPtr::operator bool() const {
+CommandPtr::operator bool() const& {
   return m_ptr.operator bool();
 }
 
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandScheduler.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandScheduler.cpp
index 5a4782f..d61ffbd 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandScheduler.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandScheduler.cpp
@@ -13,10 +13,10 @@
 #include <hal/FRCUsageReporting.h>
 #include <hal/HALBase.h>
 #include <networktables/IntegerArrayTopic.h>
-#include <networktables/NTSendableBuilder.h>
 #include <networktables/StringArrayTopic.h>
 #include <wpi/DenseMap.h>
 #include <wpi/SmallVector.h>
+#include <wpi/sendable/SendableBuilder.h>
 #include <wpi/sendable/SendableRegistry.h>
 
 #include "frc2/command/CommandPtr.h"
@@ -48,15 +48,16 @@
   // every command.
   wpi::SmallVector<Action, 4> initActions;
   wpi::SmallVector<Action, 4> executeActions;
-  wpi::SmallVector<Action, 4> interruptActions;
+  wpi::SmallVector<InterruptAction, 4> interruptActions;
   wpi::SmallVector<Action, 4> finishActions;
 
   // Flag and queues for avoiding concurrent modification if commands are
   // scheduled/canceled during run
-
   bool inRunLoop = false;
   wpi::SmallVector<Command*, 4> toSchedule;
-  wpi::SmallVector<Command*, 4> toCancel;
+  wpi::SmallVector<Command*, 4> toCancelCommands;
+  wpi::SmallVector<std::optional<Command*>, 4> toCancelInterruptors;
+  wpi::SmallSet<Command*, 4> endingCommands;
 };
 
 template <typename TMap, typename TKey>
@@ -107,10 +108,6 @@
   return &(m_impl->defaultButtonLoop);
 }
 
-void CommandScheduler::ClearButtons() {
-  m_impl->activeButtonLoop->Clear();
-}
-
 void CommandScheduler::Schedule(Command* command) {
   if (m_impl->inRunLoop) {
     m_impl->toSchedule.emplace_back(command);
@@ -142,7 +139,7 @@
   if (isDisjoint || allInterruptible) {
     if (allInterruptible) {
       for (auto&& cmdToCancel : intersection) {
-        Cancel(cmdToCancel);
+        Cancel(cmdToCancel, std::make_optional(command));
       }
     }
     m_impl->scheduledCommands.insert(command);
@@ -197,10 +194,11 @@
   m_watchdog.AddEpoch("buttons.Run()");
 
   m_impl->inRunLoop = true;
+  bool isDisabled = frc::RobotState::IsDisabled();
   // Run scheduled commands, remove finished commands.
   for (Command* command : m_impl->scheduledCommands) {
-    if (!command->RunsWhenDisabled() && frc::RobotState::IsDisabled()) {
-      Cancel(command);
+    if (isDisabled && !command->RunsWhenDisabled()) {
+      Cancel(command, std::nullopt);
       continue;
     }
 
@@ -211,16 +209,18 @@
     m_watchdog.AddEpoch(command->GetName() + ".Execute()");
 
     if (command->IsFinished()) {
+      m_impl->endingCommands.insert(command);
       command->End(false);
       for (auto&& action : m_impl->finishActions) {
         action(*command);
       }
+      m_impl->endingCommands.erase(command);
 
+      m_impl->scheduledCommands.erase(command);
       for (auto&& requirement : command->GetRequirements()) {
         m_impl->requirements.erase(requirement);
       }
 
-      m_impl->scheduledCommands.erase(command);
       m_watchdog.AddEpoch(command->GetName() + ".End(false)");
     }
   }
@@ -230,12 +230,13 @@
     Schedule(command);
   }
 
-  for (auto&& command : m_impl->toCancel) {
-    Cancel(command);
+  for (size_t i = 0; i < m_impl->toCancelCommands.size(); i++) {
+    Cancel(m_impl->toCancelCommands[i], m_impl->toCancelInterruptors[i]);
   }
 
   m_impl->toSchedule.clear();
-  m_impl->toCancel.clear();
+  m_impl->toCancelCommands.clear();
+  m_impl->toCancelInterruptors.clear();
 
   // Add default commands for un-required registered subsystems.
   for (auto&& subsystem : m_impl->subsystems) {
@@ -295,6 +296,10 @@
   }
 }
 
+void CommandScheduler::UnregisterAllSubsystems() {
+  m_impl->subsystems.clear();
+}
+
 void CommandScheduler::SetDefaultCommand(Subsystem* subsystem,
                                          CommandPtr&& defaultCommand) {
   if (!defaultCommand.get()->HasRequirement(subsystem)) {
@@ -319,33 +324,41 @@
   }
 }
 
-void CommandScheduler::Cancel(Command* command) {
+void CommandScheduler::Cancel(Command* command,
+                              std::optional<Command*> interruptor) {
   if (!m_impl) {
     return;
   }
-
+  if (m_impl->endingCommands.contains(command)) {
+    return;
+  }
   if (m_impl->inRunLoop) {
-    m_impl->toCancel.emplace_back(command);
+    m_impl->toCancelCommands.emplace_back(command);
+    m_impl->toCancelInterruptors.emplace_back(interruptor);
     return;
   }
-
-  auto find = m_impl->scheduledCommands.find(command);
-  if (find == m_impl->scheduledCommands.end()) {
+  if (!IsScheduled(command)) {
     return;
   }
-  m_impl->scheduledCommands.erase(*find);
+  m_impl->endingCommands.insert(command);
+  command->End(true);
+  for (auto&& action : m_impl->interruptActions) {
+    action(*command, interruptor);
+  }
+  m_impl->endingCommands.erase(command);
+  m_impl->scheduledCommands.erase(command);
   for (auto&& requirement : m_impl->requirements) {
     if (requirement.second == command) {
       m_impl->requirements.erase(requirement.first);
     }
   }
-  command->End(true);
-  for (auto&& action : m_impl->interruptActions) {
-    action(*command);
-  }
   m_watchdog.AddEpoch(command->GetName() + ".End(true)");
 }
 
+void CommandScheduler::Cancel(Command* command) {
+  Cancel(command, std::nullopt);
+}
+
 void CommandScheduler::Cancel(const CommandPtr& command) {
   Cancel(command.get());
 }
@@ -424,6 +437,14 @@
 }
 
 void CommandScheduler::OnCommandInterrupt(Action action) {
+  m_impl->interruptActions.emplace_back(
+      [action = std::move(action)](const Command& command,
+                                   const std::optional<Command*>& interruptor) {
+        action(command);
+      });
+}
+
+void CommandScheduler::OnCommandInterrupt(InterruptAction action) {
   m_impl->interruptActions.emplace_back(std::move(action));
 }
 
@@ -433,9 +454,10 @@
 
 void CommandScheduler::RequireUngrouped(const Command* command) {
   if (command->IsComposed()) {
-    throw FRC_MakeError(
-        frc::err::CommandIllegalUse,
-        "Commands cannot be added to more than one CommandGroup");
+    throw FRC_MakeError(frc::err::CommandIllegalUse,
+                        "Commands that have been composed may not be added to "
+                        "another composition or scheduled "
+                        "individually!");
   }
 }
 
@@ -453,36 +475,40 @@
   }
 }
 
-void CommandScheduler::InitSendable(nt::NTSendableBuilder& builder) {
+void CommandScheduler::InitSendable(wpi::SendableBuilder& builder) {
   builder.SetSmartDashboardType("Scheduler");
-  builder.SetUpdateTable(
-      [this,
-       namesPub = nt::StringArrayTopic{builder.GetTopic("Names")}.Publish(),
-       idsPub = nt::IntegerArrayTopic{builder.GetTopic("Ids")}.Publish(),
-       cancelEntry = nt::IntegerArrayTopic{builder.GetTopic("Cancel")}.GetEntry(
-           {})]() mutable {
-        auto toCancel = cancelEntry.Get();
-        if (!toCancel.empty()) {
-          for (auto cancel : cancelEntry.Get()) {
-            uintptr_t ptrTmp = static_cast<uintptr_t>(cancel);
-            Command* command = reinterpret_cast<Command*>(ptrTmp);
-            if (m_impl->scheduledCommands.find(command) !=
-                m_impl->scheduledCommands.end()) {
-              Cancel(command);
-            }
-          }
-          cancelEntry.Set({});
-        }
-
-        wpi::SmallVector<std::string, 8> names;
-        wpi::SmallVector<int64_t, 8> ids;
+  builder.AddStringArrayProperty(
+      "Names",
+      [this]() mutable {
+        std::vector<std::string> names;
         for (Command* command : m_impl->scheduledCommands) {
           names.emplace_back(command->GetName());
+        }
+        return names;
+      },
+      nullptr);
+  builder.AddIntegerArrayProperty(
+      "Ids",
+      [this]() mutable {
+        std::vector<int64_t> ids;
+        for (Command* command : m_impl->scheduledCommands) {
           uintptr_t ptrTmp = reinterpret_cast<uintptr_t>(command);
           ids.emplace_back(static_cast<int64_t>(ptrTmp));
         }
-        namesPub.Set(names);
-        idsPub.Set(ids);
+        return ids;
+      },
+      nullptr);
+  builder.AddIntegerArrayProperty(
+      "Cancel", []() { return std::vector<int64_t>{}; },
+      [this](std::span<const int64_t> toCancel) mutable {
+        for (auto cancel : toCancel) {
+          uintptr_t ptrTmp = static_cast<uintptr_t>(cancel);
+          Command* command = reinterpret_cast<Command*>(ptrTmp);
+          if (m_impl->scheduledCommands.find(command) !=
+              m_impl->scheduledCommands.end()) {
+            Cancel(command);
+          }
+        }
       });
 }
 
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/Commands.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/Commands.cpp
index 1932e45..31a4b1f 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/Commands.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/Commands.cpp
@@ -5,12 +5,14 @@
 #include "frc2/command/Commands.h"
 
 #include "frc2/command/ConditionalCommand.h"
+#include "frc2/command/DeferredCommand.h"
 #include "frc2/command/FunctionalCommand.h"
 #include "frc2/command/InstantCommand.h"
 #include "frc2/command/ParallelCommandGroup.h"
 #include "frc2/command/ParallelDeadlineGroup.h"
 #include "frc2/command/ParallelRaceGroup.h"
 #include "frc2/command/PrintCommand.h"
+#include "frc2/command/ProxyCommand.h"
 #include "frc2/command/RunCommand.h"
 #include "frc2/command/SequentialCommandGroup.h"
 #include "frc2/command/WaitCommand.h"
@@ -24,37 +26,21 @@
   return InstantCommand().ToPtr();
 }
 
-CommandPtr cmd::RunOnce(std::function<void()> action,
-                        std::initializer_list<Subsystem*> requirements) {
-  return InstantCommand(std::move(action), requirements).ToPtr();
+CommandPtr cmd::Idle(Requirements requirements) {
+  return Run([] {}, requirements);
 }
 
 CommandPtr cmd::RunOnce(std::function<void()> action,
-                        std::span<Subsystem* const> requirements) {
+                        Requirements requirements) {
   return InstantCommand(std::move(action), requirements).ToPtr();
 }
 
-CommandPtr cmd::Run(std::function<void()> action,
-                    std::initializer_list<Subsystem*> requirements) {
-  return RunCommand(std::move(action), requirements).ToPtr();
-}
-
-CommandPtr cmd::Run(std::function<void()> action,
-                    std::span<Subsystem* const> requirements) {
+CommandPtr cmd::Run(std::function<void()> action, Requirements requirements) {
   return RunCommand(std::move(action), requirements).ToPtr();
 }
 
 CommandPtr cmd::StartEnd(std::function<void()> start, std::function<void()> end,
-                         std::initializer_list<Subsystem*> requirements) {
-  return FunctionalCommand(
-             std::move(start), [] {},
-             [end = std::move(end)](bool interrupted) { end(); },
-             [] { return false; }, requirements)
-      .ToPtr();
-}
-
-CommandPtr cmd::StartEnd(std::function<void()> start, std::function<void()> end,
-                         std::span<Subsystem* const> requirements) {
+                         Requirements requirements) {
   return FunctionalCommand(
              std::move(start), [] {},
              [end = std::move(end)](bool interrupted) { end(); },
@@ -63,15 +49,7 @@
 }
 
 CommandPtr cmd::RunEnd(std::function<void()> run, std::function<void()> end,
-                       std::initializer_list<Subsystem*> requirements) {
-  return FunctionalCommand([] {}, std::move(run),
-                           [end = std::move(end)](bool interrupted) { end(); },
-                           [] { return false; }, requirements)
-      .ToPtr();
-}
-
-CommandPtr cmd::RunEnd(std::function<void()> run, std::function<void()> end,
-                       std::span<Subsystem* const> requirements) {
+                       Requirements requirements) {
   return FunctionalCommand([] {}, std::move(run),
                            [end = std::move(end)](bool interrupted) { end(); },
                            [] { return false; }, requirements)
@@ -82,6 +60,14 @@
   return PrintCommand(msg).ToPtr();
 }
 
+CommandPtr cmd::DeferredProxy(wpi::unique_function<Command*()> supplier) {
+  return ProxyCommand(std::move(supplier)).ToPtr();
+}
+
+CommandPtr cmd::DeferredProxy(wpi::unique_function<CommandPtr()> supplier) {
+  return ProxyCommand(std::move(supplier)).ToPtr();
+}
+
 CommandPtr cmd::Wait(units::second_t duration) {
   return WaitCommand(duration).ToPtr();
 }
@@ -97,6 +83,11 @@
       .ToPtr();
 }
 
+CommandPtr cmd::Defer(wpi::unique_function<CommandPtr()> supplier,
+                      Requirements requirements) {
+  return DeferredCommand(std::move(supplier), requirements).ToPtr();
+}
+
 CommandPtr cmd::Sequence(std::vector<CommandPtr>&& commands) {
   return SequentialCommandGroup(CommandPtr::UnwrapVector(std::move(commands)))
       .ToPtr();
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/ConditionalCommand.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/ConditionalCommand.cpp
index 2610c96..4c07c7a 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/ConditionalCommand.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/ConditionalCommand.cpp
@@ -53,8 +53,20 @@
   return m_runsWhenDisabled;
 }
 
+Command::InterruptionBehavior ConditionalCommand::GetInterruptionBehavior()
+    const {
+  if (m_onTrue->GetInterruptionBehavior() ==
+          InterruptionBehavior::kCancelSelf ||
+      m_onFalse->GetInterruptionBehavior() ==
+          InterruptionBehavior::kCancelSelf) {
+    return InterruptionBehavior::kCancelSelf;
+  } else {
+    return InterruptionBehavior::kCancelIncoming;
+  }
+}
+
 void ConditionalCommand::InitSendable(wpi::SendableBuilder& builder) {
-  CommandBase::InitSendable(builder);
+  Command::InitSendable(builder);
   builder.AddStringProperty(
       "onTrue", [this] { return m_onTrue->GetName(); }, nullptr);
   builder.AddStringProperty(
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/DeferredCommand.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/DeferredCommand.cpp
new file mode 100644
index 0000000..2a7f162
--- /dev/null
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/DeferredCommand.cpp
@@ -0,0 +1,46 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include "frc2/command/DeferredCommand.h"
+
+#include <wpi/sendable/SendableBuilder.h>
+
+#include "frc2/command/Commands.h"
+
+using namespace frc2;
+
+DeferredCommand::DeferredCommand(wpi::unique_function<CommandPtr()> supplier,
+                                 Requirements requirements)
+    : m_supplier{std::move(supplier)} {
+  AddRequirements(requirements);
+}
+
+void DeferredCommand::Initialize() {
+  m_command = m_supplier().Unwrap();
+  CommandScheduler::GetInstance().RequireUngrouped(m_command.get());
+  m_command->SetComposed(true);
+  m_command->Initialize();
+}
+
+void DeferredCommand::Execute() {
+  m_command->Execute();
+}
+
+void DeferredCommand::End(bool interrupted) {
+  m_command->End(interrupted);
+  m_command =
+      cmd::Print("[DeferredCommand] Lifecycle function called out-of-order!")
+          .WithName("none")
+          .Unwrap();
+}
+
+bool DeferredCommand::IsFinished() {
+  return m_command->IsFinished();
+}
+
+void DeferredCommand::InitSendable(wpi::SendableBuilder& builder) {
+  Command::InitSendable(builder);
+  builder.AddStringProperty(
+      "deferred", [this] { return m_command->GetName(); }, nullptr);
+}
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/FunctionalCommand.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/FunctionalCommand.cpp
index 02e8338..e4ff6ea 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/FunctionalCommand.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/FunctionalCommand.cpp
@@ -6,22 +6,11 @@
 
 using namespace frc2;
 
-FunctionalCommand::FunctionalCommand(
-    std::function<void()> onInit, std::function<void()> onExecute,
-    std::function<void(bool)> onEnd, std::function<bool()> isFinished,
-    std::initializer_list<Subsystem*> requirements)
-    : m_onInit{std::move(onInit)},
-      m_onExecute{std::move(onExecute)},
-      m_onEnd{std::move(onEnd)},
-      m_isFinished{std::move(isFinished)} {
-  AddRequirements(requirements);
-}
-
 FunctionalCommand::FunctionalCommand(std::function<void()> onInit,
                                      std::function<void()> onExecute,
                                      std::function<void(bool)> onEnd,
                                      std::function<bool()> isFinished,
-                                     std::span<Subsystem* const> requirements)
+                                     Requirements requirements)
     : m_onInit{std::move(onInit)},
       m_onExecute{std::move(onExecute)},
       m_onEnd{std::move(onEnd)},
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/InstantCommand.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/InstantCommand.cpp
index 7b73239..b78d30e 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/InstantCommand.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/InstantCommand.cpp
@@ -7,13 +7,7 @@
 using namespace frc2;
 
 InstantCommand::InstantCommand(std::function<void()> toRun,
-                               std::initializer_list<Subsystem*> requirements)
-    : CommandHelper(
-          std::move(toRun), [] {}, [](bool interrupted) {}, [] { return true; },
-          requirements) {}
-
-InstantCommand::InstantCommand(std::function<void()> toRun,
-                               std::span<Subsystem* const> requirements)
+                               Requirements requirements)
     : CommandHelper(
           std::move(toRun), [] {}, [](bool interrupted) {}, [] { return true; },
           requirements) {}
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/MecanumControllerCommand.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/MecanumControllerCommand.cpp
index d95d596..902c26b 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/MecanumControllerCommand.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/MecanumControllerCommand.cpp
@@ -11,20 +11,20 @@
 MecanumControllerCommand::MecanumControllerCommand(
     frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
     frc::SimpleMotorFeedforward<units::meters> feedforward,
-    frc::MecanumDriveKinematics kinematics, frc2::PIDController xController,
-    frc2::PIDController yController,
+    frc::MecanumDriveKinematics kinematics, frc::PIDController xController,
+    frc::PIDController yController,
     frc::ProfiledPIDController<units::radians> thetaController,
     std::function<frc::Rotation2d()> desiredRotation,
     units::meters_per_second_t maxWheelVelocity,
     std::function<frc::MecanumDriveWheelSpeeds()> currentWheelSpeeds,
-    frc2::PIDController frontLeftController,
-    frc2::PIDController rearLeftController,
-    frc2::PIDController frontRightController,
-    frc2::PIDController rearRightController,
+    frc::PIDController frontLeftController,
+    frc::PIDController rearLeftController,
+    frc::PIDController frontRightController,
+    frc::PIDController rearRightController,
     std::function<void(units::volt_t, units::volt_t, units::volt_t,
                        units::volt_t)>
         output,
-    std::initializer_list<Subsystem*> requirements)
+    Requirements requirements)
     : m_trajectory(std::move(trajectory)),
       m_pose(std::move(pose)),
       m_feedforward(feedforward),
@@ -33,13 +33,13 @@
       m_desiredRotation(std::move(desiredRotation)),
       m_maxWheelVelocity(maxWheelVelocity),
       m_frontLeftController(
-          std::make_unique<frc2::PIDController>(frontLeftController)),
+          std::make_unique<frc::PIDController>(frontLeftController)),
       m_rearLeftController(
-          std::make_unique<frc2::PIDController>(rearLeftController)),
+          std::make_unique<frc::PIDController>(rearLeftController)),
       m_frontRightController(
-          std::make_unique<frc2::PIDController>(frontRightController)),
+          std::make_unique<frc::PIDController>(frontRightController)),
       m_rearRightController(
-          std::make_unique<frc2::PIDController>(rearRightController)),
+          std::make_unique<frc::PIDController>(rearRightController)),
       m_currentWheelSpeeds(std::move(currentWheelSpeeds)),
       m_outputVolts(std::move(output)),
       m_usePID(true) {
@@ -49,19 +49,19 @@
 MecanumControllerCommand::MecanumControllerCommand(
     frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
     frc::SimpleMotorFeedforward<units::meters> feedforward,
-    frc::MecanumDriveKinematics kinematics, frc2::PIDController xController,
-    frc2::PIDController yController,
+    frc::MecanumDriveKinematics kinematics, frc::PIDController xController,
+    frc::PIDController yController,
     frc::ProfiledPIDController<units::radians> thetaController,
     units::meters_per_second_t maxWheelVelocity,
     std::function<frc::MecanumDriveWheelSpeeds()> currentWheelSpeeds,
-    frc2::PIDController frontLeftController,
-    frc2::PIDController rearLeftController,
-    frc2::PIDController frontRightController,
-    frc2::PIDController rearRightController,
+    frc::PIDController frontLeftController,
+    frc::PIDController rearLeftController,
+    frc::PIDController frontRightController,
+    frc::PIDController rearRightController,
     std::function<void(units::volt_t, units::volt_t, units::volt_t,
                        units::volt_t)>
         output,
-    std::initializer_list<Subsystem*> requirements)
+    Requirements requirements)
     : m_trajectory(std::move(trajectory)),
       m_pose(std::move(pose)),
       m_feedforward(feedforward),
@@ -69,13 +69,13 @@
       m_controller(xController, yController, thetaController),
       m_maxWheelVelocity(maxWheelVelocity),
       m_frontLeftController(
-          std::make_unique<frc2::PIDController>(frontLeftController)),
+          std::make_unique<frc::PIDController>(frontLeftController)),
       m_rearLeftController(
-          std::make_unique<frc2::PIDController>(rearLeftController)),
+          std::make_unique<frc::PIDController>(rearLeftController)),
       m_frontRightController(
-          std::make_unique<frc2::PIDController>(frontRightController)),
+          std::make_unique<frc::PIDController>(frontRightController)),
       m_rearRightController(
-          std::make_unique<frc2::PIDController>(rearRightController)),
+          std::make_unique<frc::PIDController>(rearRightController)),
       m_currentWheelSpeeds(std::move(currentWheelSpeeds)),
       m_outputVolts(std::move(output)),
       m_usePID(true) {
@@ -84,89 +84,15 @@
 
 MecanumControllerCommand::MecanumControllerCommand(
     frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
-    frc::SimpleMotorFeedforward<units::meters> feedforward,
-    frc::MecanumDriveKinematics kinematics, frc2::PIDController xController,
-    frc2::PIDController yController,
-    frc::ProfiledPIDController<units::radians> thetaController,
-    std::function<frc::Rotation2d()> desiredRotation,
-    units::meters_per_second_t maxWheelVelocity,
-    std::function<frc::MecanumDriveWheelSpeeds()> currentWheelSpeeds,
-    frc2::PIDController frontLeftController,
-    frc2::PIDController rearLeftController,
-    frc2::PIDController frontRightController,
-    frc2::PIDController rearRightController,
-    std::function<void(units::volt_t, units::volt_t, units::volt_t,
-                       units::volt_t)>
-        output,
-    std::span<Subsystem* const> requirements)
-    : m_trajectory(std::move(trajectory)),
-      m_pose(std::move(pose)),
-      m_feedforward(feedforward),
-      m_kinematics(kinematics),
-      m_controller(xController, yController, thetaController),
-      m_desiredRotation(std::move(desiredRotation)),
-      m_maxWheelVelocity(maxWheelVelocity),
-      m_frontLeftController(
-          std::make_unique<frc2::PIDController>(frontLeftController)),
-      m_rearLeftController(
-          std::make_unique<frc2::PIDController>(rearLeftController)),
-      m_frontRightController(
-          std::make_unique<frc2::PIDController>(frontRightController)),
-      m_rearRightController(
-          std::make_unique<frc2::PIDController>(rearRightController)),
-      m_currentWheelSpeeds(std::move(currentWheelSpeeds)),
-      m_outputVolts(std::move(output)),
-      m_usePID(true) {
-  AddRequirements(requirements);
-}
-
-MecanumControllerCommand::MecanumControllerCommand(
-    frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
-    frc::SimpleMotorFeedforward<units::meters> feedforward,
-    frc::MecanumDriveKinematics kinematics, frc2::PIDController xController,
-    frc2::PIDController yController,
-    frc::ProfiledPIDController<units::radians> thetaController,
-    units::meters_per_second_t maxWheelVelocity,
-    std::function<frc::MecanumDriveWheelSpeeds()> currentWheelSpeeds,
-    frc2::PIDController frontLeftController,
-    frc2::PIDController rearLeftController,
-    frc2::PIDController frontRightController,
-    frc2::PIDController rearRightController,
-    std::function<void(units::volt_t, units::volt_t, units::volt_t,
-                       units::volt_t)>
-        output,
-    std::span<Subsystem* const> requirements)
-    : m_trajectory(std::move(trajectory)),
-      m_pose(std::move(pose)),
-      m_feedforward(feedforward),
-      m_kinematics(kinematics),
-      m_controller(xController, yController, thetaController),
-      m_maxWheelVelocity(maxWheelVelocity),
-      m_frontLeftController(
-          std::make_unique<frc2::PIDController>(frontLeftController)),
-      m_rearLeftController(
-          std::make_unique<frc2::PIDController>(rearLeftController)),
-      m_frontRightController(
-          std::make_unique<frc2::PIDController>(frontRightController)),
-      m_rearRightController(
-          std::make_unique<frc2::PIDController>(rearRightController)),
-      m_currentWheelSpeeds(std::move(currentWheelSpeeds)),
-      m_outputVolts(std::move(output)),
-      m_usePID(true) {
-  AddRequirements(requirements);
-}
-
-MecanumControllerCommand::MecanumControllerCommand(
-    frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
-    frc::MecanumDriveKinematics kinematics, frc2::PIDController xController,
-    frc2::PIDController yController,
+    frc::MecanumDriveKinematics kinematics, frc::PIDController xController,
+    frc::PIDController yController,
     frc::ProfiledPIDController<units::radians> thetaController,
     std::function<frc::Rotation2d()> desiredRotation,
     units::meters_per_second_t maxWheelVelocity,
     std::function<void(units::meters_per_second_t, units::meters_per_second_t,
                        units::meters_per_second_t, units::meters_per_second_t)>
         output,
-    std::initializer_list<Subsystem*> requirements)
+    Requirements requirements)
     : m_trajectory(std::move(trajectory)),
       m_pose(std::move(pose)),
       m_kinematics(kinematics),
@@ -180,56 +106,14 @@
 
 MecanumControllerCommand::MecanumControllerCommand(
     frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
-    frc::MecanumDriveKinematics kinematics, frc2::PIDController xController,
-    frc2::PIDController yController,
+    frc::MecanumDriveKinematics kinematics, frc::PIDController xController,
+    frc::PIDController yController,
     frc::ProfiledPIDController<units::radians> thetaController,
     units::meters_per_second_t maxWheelVelocity,
     std::function<void(units::meters_per_second_t, units::meters_per_second_t,
                        units::meters_per_second_t, units::meters_per_second_t)>
         output,
-    std::initializer_list<Subsystem*> requirements)
-    : m_trajectory(std::move(trajectory)),
-      m_pose(std::move(pose)),
-      m_kinematics(kinematics),
-      m_controller(xController, yController, thetaController),
-      m_maxWheelVelocity(maxWheelVelocity),
-      m_outputVel(std::move(output)),
-      m_usePID(false) {
-  AddRequirements(requirements);
-}
-
-MecanumControllerCommand::MecanumControllerCommand(
-    frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
-    frc::MecanumDriveKinematics kinematics, frc2::PIDController xController,
-    frc2::PIDController yController,
-    frc::ProfiledPIDController<units::radians> thetaController,
-    std::function<frc::Rotation2d()> desiredRotation,
-    units::meters_per_second_t maxWheelVelocity,
-    std::function<void(units::meters_per_second_t, units::meters_per_second_t,
-                       units::meters_per_second_t, units::meters_per_second_t)>
-        output,
-    std::span<Subsystem* const> requirements)
-    : m_trajectory(std::move(trajectory)),
-      m_pose(std::move(pose)),
-      m_kinematics(kinematics),
-      m_controller(xController, yController, thetaController),
-      m_desiredRotation(std::move(desiredRotation)),
-      m_maxWheelVelocity(maxWheelVelocity),
-      m_outputVel(std::move(output)),
-      m_usePID(false) {
-  AddRequirements(requirements);
-}
-
-MecanumControllerCommand::MecanumControllerCommand(
-    frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
-    frc::MecanumDriveKinematics kinematics, frc2::PIDController xController,
-    frc2::PIDController yController,
-    frc::ProfiledPIDController<units::radians> thetaController,
-    units::meters_per_second_t maxWheelVelocity,
-    std::function<void(units::meters_per_second_t, units::meters_per_second_t,
-                       units::meters_per_second_t, units::meters_per_second_t)>
-        output,
-    std::span<Subsystem* const> requirements)
+    Requirements requirements)
     : m_trajectory(std::move(trajectory)),
       m_pose(std::move(pose)),
       m_kinematics(kinematics),
@@ -257,8 +141,7 @@
   m_prevSpeeds = m_kinematics.ToWheelSpeeds(
       frc::ChassisSpeeds{initialXVelocity, initialYVelocity, 0_rad_per_s});
 
-  m_timer.Reset();
-  m_timer.Start();
+  m_timer.Restart();
   if (m_usePID) {
     m_frontLeftController->Reset();
     m_rearLeftController->Reset();
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/NotifierCommand.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/NotifierCommand.cpp
index 2081d07..5dc5add 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/NotifierCommand.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/NotifierCommand.cpp
@@ -8,14 +8,7 @@
 
 NotifierCommand::NotifierCommand(std::function<void()> toRun,
                                  units::second_t period,
-                                 std::initializer_list<Subsystem*> requirements)
-    : m_toRun(toRun), m_notifier{std::move(toRun)}, m_period{period} {
-  AddRequirements(requirements);
-}
-
-NotifierCommand::NotifierCommand(std::function<void()> toRun,
-                                 units::second_t period,
-                                 std::span<Subsystem* const> requirements)
+                                 Requirements requirements)
     : m_toRun(toRun), m_notifier{std::move(toRun)}, m_period{period} {
   AddRequirements(requirements);
 }
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/PIDCommand.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/PIDCommand.cpp
index 672fa47..361ec84 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/PIDCommand.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/PIDCommand.cpp
@@ -8,11 +8,11 @@
 
 using namespace frc2;
 
-PIDCommand::PIDCommand(PIDController controller,
+PIDCommand::PIDCommand(frc::PIDController controller,
                        std::function<double()> measurementSource,
                        std::function<double()> setpointSource,
                        std::function<void(double)> useOutput,
-                       std::initializer_list<Subsystem*> requirements)
+                       Requirements requirements)
     : m_controller{std::move(controller)},
       m_measurement{std::move(measurementSource)},
       m_setpoint{std::move(setpointSource)},
@@ -20,30 +20,10 @@
   AddRequirements(requirements);
 }
 
-PIDCommand::PIDCommand(PIDController controller,
-                       std::function<double()> measurementSource,
-                       std::function<double()> setpointSource,
-                       std::function<void(double)> useOutput,
-                       std::span<Subsystem* const> requirements)
-    : m_controller{std::move(controller)},
-      m_measurement{std::move(measurementSource)},
-      m_setpoint{std::move(setpointSource)},
-      m_useOutput{std::move(useOutput)} {
-  AddRequirements(requirements);
-}
-
-PIDCommand::PIDCommand(PIDController controller,
+PIDCommand::PIDCommand(frc::PIDController controller,
                        std::function<double()> measurementSource,
                        double setpoint, std::function<void(double)> useOutput,
-                       std::initializer_list<Subsystem*> requirements)
-    : PIDCommand(
-          controller, measurementSource, [setpoint] { return setpoint; },
-          useOutput, requirements) {}
-
-PIDCommand::PIDCommand(PIDController controller,
-                       std::function<double()> measurementSource,
-                       double setpoint, std::function<void(double)> useOutput,
-                       std::span<Subsystem* const> requirements)
+                       Requirements requirements)
     : PIDCommand(
           controller, measurementSource, [setpoint] { return setpoint; },
           useOutput, requirements) {}
@@ -60,6 +40,6 @@
   m_useOutput(0);
 }
 
-PIDController& PIDCommand::GetController() {
+frc::PIDController& PIDCommand::GetController() {
   return m_controller;
 }
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/PIDSubsystem.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/PIDSubsystem.cpp
index 8db032a..0764cb9 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/PIDSubsystem.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/PIDSubsystem.cpp
@@ -8,7 +8,8 @@
 
 using namespace frc2;
 
-PIDSubsystem::PIDSubsystem(PIDController controller, double initialPosition)
+PIDSubsystem::PIDSubsystem(frc::PIDController controller,
+                           double initialPosition)
     : m_controller{std::move(controller)} {
   SetSetpoint(initialPosition);
   AddChild("PID Controller", &m_controller);
@@ -42,6 +43,6 @@
   return m_enabled;
 }
 
-PIDController& PIDSubsystem::GetController() {
+frc::PIDController& PIDSubsystem::GetController() {
   return m_controller;
 }
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/ParallelDeadlineGroup.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/ParallelDeadlineGroup.cpp
index 14c28c3..a6b5c1c 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/ParallelDeadlineGroup.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/ParallelDeadlineGroup.cpp
@@ -97,7 +97,7 @@
 }
 
 void ParallelDeadlineGroup::InitSendable(wpi::SendableBuilder& builder) {
-  CommandBase::InitSendable(builder);
+  Command::InitSendable(builder);
 
   builder.AddStringProperty(
       "deadline", [this] { return m_deadline->GetName(); }, nullptr);
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/PerpetualCommand.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/PerpetualCommand.cpp
deleted file mode 100644
index 2d0af1e..0000000
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/PerpetualCommand.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "frc2/command/PerpetualCommand.h"
-
-using namespace frc2;
-
-PerpetualCommand::PerpetualCommand(std::unique_ptr<Command>&& command) {
-  CommandScheduler::GetInstance().RequireUngrouped(command.get());
-  m_command = std::move(command);
-  m_command->SetComposed(true);
-  AddRequirements(m_command->GetRequirements());
-}
-
-void PerpetualCommand::Initialize() {
-  m_command->Initialize();
-}
-
-void PerpetualCommand::Execute() {
-  m_command->Execute();
-}
-
-void PerpetualCommand::End(bool interrupted) {
-  m_command->End(interrupted);
-}
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/ProxyCommand.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/ProxyCommand.cpp
index cc1b815..eb4fb7c 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/ProxyCommand.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/ProxyCommand.cpp
@@ -11,13 +11,22 @@
 ProxyCommand::ProxyCommand(wpi::unique_function<Command*()> supplier)
     : m_supplier(std::move(supplier)) {}
 
+ProxyCommand::ProxyCommand(wpi::unique_function<CommandPtr()> supplier)
+    : ProxyCommand([supplier = std::move(supplier),
+                    holder = std::optional<CommandPtr>{}]() mutable {
+        holder = supplier();
+        return holder->get();
+      }) {}
+
 ProxyCommand::ProxyCommand(Command* command)
-    : m_supplier([command] { return command; }) {
+    : ProxyCommand([command] { return command; }) {
   SetName(std::string{"Proxy("}.append(command->GetName()).append(")"));
 }
 
-ProxyCommand::ProxyCommand(std::unique_ptr<Command> command)
-    : m_supplier([command = std::move(command)] { return command.get(); }) {}
+ProxyCommand::ProxyCommand(std::unique_ptr<Command> command) {
+  SetName(std::string{"Proxy("}.append(command->GetName()).append(")"));
+  m_supplier = [command = std::move(command)] { return command.get(); };
+}
 
 void ProxyCommand::Initialize() {
   m_command = m_supplier();
@@ -31,8 +40,6 @@
   m_command = nullptr;
 }
 
-void ProxyCommand::Execute() {}
-
 bool ProxyCommand::IsFinished() {
   // because we're between `initialize` and `end`, `m_command` is necessarily
   // not null but if called otherwise and m_command is null, it's UB, so we can
@@ -41,7 +48,7 @@
 }
 
 void ProxyCommand::InitSendable(wpi::SendableBuilder& builder) {
-  CommandBase::InitSendable(builder);
+  Command::InitSendable(builder);
   builder.AddStringProperty(
       "proxied",
       [this] {
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/ProxyScheduleCommand.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/ProxyScheduleCommand.cpp
deleted file mode 100644
index dd0e2a7..0000000
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/ProxyScheduleCommand.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "frc2/command/ProxyScheduleCommand.h"
-
-using namespace frc2;
-
-ProxyScheduleCommand::ProxyScheduleCommand(
-    std::span<Command* const> toSchedule) {
-  SetInsert(m_toSchedule, toSchedule);
-}
-
-ProxyScheduleCommand::ProxyScheduleCommand(Command* toSchedule) {
-  SetInsert(m_toSchedule, {&toSchedule, 1});
-}
-
-void ProxyScheduleCommand::Initialize() {
-  for (auto* command : m_toSchedule) {
-    command->Schedule();
-  }
-}
-
-void ProxyScheduleCommand::End(bool interrupted) {
-  if (interrupted) {
-    for (auto* command : m_toSchedule) {
-      command->Cancel();
-    }
-  }
-}
-
-void ProxyScheduleCommand::Execute() {
-  m_finished = true;
-  for (auto* command : m_toSchedule) {
-    m_finished &= !command->IsScheduled();
-  }
-}
-
-bool ProxyScheduleCommand::IsFinished() {
-  return m_finished;
-}
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/RamseteCommand.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/RamseteCommand.cpp
index d46cdd0..9145985 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/RamseteCommand.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/RamseteCommand.cpp
@@ -16,39 +16,17 @@
     frc::SimpleMotorFeedforward<units::meters> feedforward,
     frc::DifferentialDriveKinematics kinematics,
     std::function<frc::DifferentialDriveWheelSpeeds()> wheelSpeeds,
-    frc2::PIDController leftController, frc2::PIDController rightController,
+    frc::PIDController leftController, frc::PIDController rightController,
     std::function<void(units::volt_t, units::volt_t)> output,
-    std::initializer_list<Subsystem*> requirements)
+    Requirements requirements)
     : m_trajectory(std::move(trajectory)),
       m_pose(std::move(pose)),
       m_controller(controller),
       m_feedforward(feedforward),
-      m_kinematics(kinematics),
+      m_kinematics(std::move(kinematics)),
       m_speeds(std::move(wheelSpeeds)),
-      m_leftController(std::make_unique<frc2::PIDController>(leftController)),
-      m_rightController(std::make_unique<frc2::PIDController>(rightController)),
-      m_outputVolts(std::move(output)),
-      m_usePID(true) {
-  AddRequirements(requirements);
-}
-
-RamseteCommand::RamseteCommand(
-    frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
-    frc::RamseteController controller,
-    frc::SimpleMotorFeedforward<units::meters> feedforward,
-    frc::DifferentialDriveKinematics kinematics,
-    std::function<frc::DifferentialDriveWheelSpeeds()> wheelSpeeds,
-    frc2::PIDController leftController, frc2::PIDController rightController,
-    std::function<void(units::volt_t, units::volt_t)> output,
-    std::span<Subsystem* const> requirements)
-    : m_trajectory(std::move(trajectory)),
-      m_pose(std::move(pose)),
-      m_controller(controller),
-      m_feedforward(feedforward),
-      m_kinematics(kinematics),
-      m_speeds(std::move(wheelSpeeds)),
-      m_leftController(std::make_unique<frc2::PIDController>(leftController)),
-      m_rightController(std::make_unique<frc2::PIDController>(rightController)),
+      m_leftController(std::make_unique<frc::PIDController>(leftController)),
+      m_rightController(std::make_unique<frc::PIDController>(rightController)),
       m_outputVolts(std::move(output)),
       m_usePID(true) {
   AddRequirements(requirements);
@@ -60,27 +38,11 @@
     frc::DifferentialDriveKinematics kinematics,
     std::function<void(units::meters_per_second_t, units::meters_per_second_t)>
         output,
-    std::initializer_list<Subsystem*> requirements)
+    Requirements requirements)
     : m_trajectory(std::move(trajectory)),
       m_pose(std::move(pose)),
       m_controller(controller),
-      m_kinematics(kinematics),
-      m_outputVel(std::move(output)),
-      m_usePID(false) {
-  AddRequirements(requirements);
-}
-
-RamseteCommand::RamseteCommand(
-    frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
-    frc::RamseteController controller,
-    frc::DifferentialDriveKinematics kinematics,
-    std::function<void(units::meters_per_second_t, units::meters_per_second_t)>
-        output,
-    std::span<Subsystem* const> requirements)
-    : m_trajectory(std::move(trajectory)),
-      m_pose(std::move(pose)),
-      m_controller(controller),
-      m_kinematics(kinematics),
+      m_kinematics(std::move(kinematics)),
       m_outputVel(std::move(output)),
       m_usePID(false) {
   AddRequirements(requirements);
@@ -92,8 +54,7 @@
   m_prevSpeeds = m_kinematics.ToWheelSpeeds(
       frc::ChassisSpeeds{initialState.velocity, 0_mps,
                          initialState.velocity * initialState.curvature});
-  m_timer.Reset();
-  m_timer.Start();
+  m_timer.Restart();
   if (m_usePID) {
     m_leftController->Reset();
     m_rightController->Reset();
@@ -162,7 +123,7 @@
 }
 
 void RamseteCommand::InitSendable(wpi::SendableBuilder& builder) {
-  CommandBase::InitSendable(builder);
+  Command::InitSendable(builder);
   builder.AddDoubleProperty(
       "leftVelocity", [this] { return m_prevSpeeds.left.value(); }, nullptr);
   builder.AddDoubleProperty(
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/RepeatCommand.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/RepeatCommand.cpp
index 319a7af..2e74c8c 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/RepeatCommand.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/RepeatCommand.cpp
@@ -39,7 +39,12 @@
 }
 
 void RepeatCommand::End(bool interrupted) {
-  m_command->End(interrupted);
+  // Make sure we didn't already call end() (which would happen if the command
+  // finished in the last call to our execute())
+  if (!m_ended) {
+    m_command->End(interrupted);
+    m_ended = true;
+  }
 }
 
 bool RepeatCommand::RunsWhenDisabled() const {
@@ -51,7 +56,7 @@
 }
 
 void RepeatCommand::InitSendable(wpi::SendableBuilder& builder) {
-  CommandBase::InitSendable(builder);
+  Command::InitSendable(builder);
   builder.AddStringProperty(
       "command", [this] { return m_command->GetName(); }, nullptr);
 }
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/RunCommand.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/RunCommand.cpp
index 342362f..c3378ea 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/RunCommand.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/RunCommand.cpp
@@ -6,12 +6,6 @@
 
 using namespace frc2;
 
-RunCommand::RunCommand(std::function<void()> toRun,
-                       std::initializer_list<Subsystem*> requirements)
-    : CommandHelper([] {}, std::move(toRun), [](bool interrupted) {},
-                    [] { return false; }, requirements) {}
-
-RunCommand::RunCommand(std::function<void()> toRun,
-                       std::span<Subsystem* const> requirements)
+RunCommand::RunCommand(std::function<void()> toRun, Requirements requirements)
     : CommandHelper([] {}, std::move(toRun), [](bool interrupted) {},
                     [] { return false; }, requirements) {}
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/ScheduleCommand.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/ScheduleCommand.cpp
index 54c3042..c96f218 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/ScheduleCommand.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/ScheduleCommand.cpp
@@ -7,11 +7,13 @@
 using namespace frc2;
 
 ScheduleCommand::ScheduleCommand(std::span<Command* const> toSchedule) {
-  SetInsert(m_toSchedule, toSchedule);
+  for (auto cmd : toSchedule) {
+    m_toSchedule.insert(cmd);
+  }
 }
 
 ScheduleCommand::ScheduleCommand(Command* toSchedule) {
-  SetInsert(m_toSchedule, {&toSchedule, 1});
+  m_toSchedule.insert(toSchedule);
 }
 
 void ScheduleCommand::Initialize() {
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/SequentialCommandGroup.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/SequentialCommandGroup.cpp
index 7888c1d..b9ea3d5 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/SequentialCommandGroup.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/SequentialCommandGroup.cpp
@@ -83,7 +83,7 @@
 }
 
 void SequentialCommandGroup::InitSendable(wpi::SendableBuilder& builder) {
-  CommandBase::InitSendable(builder);
+  Command::InitSendable(builder);
   builder.AddIntegerProperty(
       "index", [this] { return m_currentCommandIndex; }, nullptr);
 }
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/StartEndCommand.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/StartEndCommand.cpp
index 3d91ac5..2d257a3 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/StartEndCommand.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/StartEndCommand.cpp
@@ -8,15 +8,7 @@
 
 StartEndCommand::StartEndCommand(std::function<void()> onInit,
                                  std::function<void()> onEnd,
-                                 std::initializer_list<Subsystem*> requirements)
-    : CommandHelper(
-          std::move(onInit), [] {},
-          [onEnd = std::move(onEnd)](bool interrupted) { onEnd(); },
-          [] { return false; }, requirements) {}
-
-StartEndCommand::StartEndCommand(std::function<void()> onInit,
-                                 std::function<void()> onEnd,
-                                 std::span<Subsystem* const> requirements)
+                                 Requirements requirements)
     : CommandHelper(
           std::move(onInit), [] {},
           [onEnd = std::move(onEnd)](bool interrupted) { onEnd(); },
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/Subsystem.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/Subsystem.cpp
index 3c388bb..4c06f1f 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/Subsystem.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/Subsystem.cpp
@@ -21,6 +21,10 @@
                                                     std::move(defaultCommand));
 }
 
+void Subsystem::RemoveDefaultCommand() {
+  CommandScheduler::GetInstance().RemoveDefaultCommand(this);
+}
+
 Command* Subsystem::GetDefaultCommand() const {
   return CommandScheduler::GetInstance().GetDefaultCommand(this);
 }
@@ -50,3 +54,7 @@
                              std::function<void()> end) {
   return cmd::RunEnd(std::move(run), std::move(end), {this});
 }
+
+CommandPtr Subsystem::Defer(wpi::unique_function<CommandPtr()> supplier) {
+  return cmd::Defer(std::move(supplier), {this});
+}
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/WaitCommand.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/WaitCommand.cpp
index 94b8b8a..36b15dd 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/WaitCommand.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/WaitCommand.cpp
@@ -5,7 +5,6 @@
 #include "frc2/command/WaitCommand.h"
 
 #include <fmt/format.h>
-#include <frc/fmt/Units.h>
 #include <wpi/sendable/SendableBuilder.h>
 
 using namespace frc2;
@@ -15,8 +14,7 @@
 }
 
 void WaitCommand::Initialize() {
-  m_timer.Reset();
-  m_timer.Start();
+  m_timer.Restart();
 }
 
 void WaitCommand::End(bool interrupted) {
@@ -32,7 +30,7 @@
 }
 
 void WaitCommand::InitSendable(wpi::SendableBuilder& builder) {
-  CommandBase::InitSendable(builder);
+  Command::InitSendable(builder);
   builder.AddDoubleProperty(
       "duration", [this] { return m_duration.value(); }, nullptr);
 }
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/button/Button.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/button/Button.cpp
deleted file mode 100644
index 787fc09..0000000
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/button/Button.cpp
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "frc2/command/button/Button.h"
-
-using namespace frc2;
-
-Button::Button(std::function<bool()> isPressed) : Trigger(isPressed) {}
-
-Button Button::WhenPressed(Command* command) {
-  WPI_IGNORE_DEPRECATED
-  WhenActive(command);
-  WPI_UNIGNORE_DEPRECATED
-  return *this;
-}
-
-Button Button::WhenPressed(std::function<void()> toRun,
-                           std::initializer_list<Subsystem*> requirements) {
-  WPI_IGNORE_DEPRECATED
-  WhenActive(std::move(toRun), requirements);
-  WPI_UNIGNORE_DEPRECATED
-  return *this;
-}
-
-Button Button::WhenPressed(std::function<void()> toRun,
-                           std::span<Subsystem* const> requirements) {
-  WPI_IGNORE_DEPRECATED
-  WhenActive(std::move(toRun), requirements);
-  WPI_UNIGNORE_DEPRECATED
-  return *this;
-}
-
-Button Button::WhileHeld(Command* command) {
-  WPI_IGNORE_DEPRECATED
-  WhileActiveContinous(command);
-  WPI_UNIGNORE_DEPRECATED
-  return *this;
-}
-
-Button Button::WhileHeld(std::function<void()> toRun,
-                         std::initializer_list<Subsystem*> requirements) {
-  WPI_IGNORE_DEPRECATED
-  WhileActiveContinous(std::move(toRun), requirements);
-  WPI_UNIGNORE_DEPRECATED
-  return *this;
-}
-
-Button Button::WhileHeld(std::function<void()> toRun,
-                         std::span<Subsystem* const> requirements) {
-  WPI_IGNORE_DEPRECATED
-  WhileActiveContinous(std::move(toRun), requirements);
-  WPI_UNIGNORE_DEPRECATED
-  return *this;
-}
-
-Button Button::WhenHeld(Command* command) {
-  WPI_IGNORE_DEPRECATED
-  WhileActiveOnce(command);
-  WPI_UNIGNORE_DEPRECATED
-  return *this;
-}
-
-Button Button::WhenReleased(Command* command) {
-  WPI_IGNORE_DEPRECATED
-  WhenInactive(command);
-  WPI_UNIGNORE_DEPRECATED
-  return *this;
-}
-
-Button Button::WhenReleased(std::function<void()> toRun,
-                            std::initializer_list<Subsystem*> requirements) {
-  WPI_IGNORE_DEPRECATED
-  WhenInactive(std::move(toRun), requirements);
-  WPI_UNIGNORE_DEPRECATED
-  return *this;
-}
-
-Button Button::WhenReleased(std::function<void()> toRun,
-                            std::span<Subsystem* const> requirements) {
-  WPI_IGNORE_DEPRECATED
-  WhenInactive(std::move(toRun), requirements);
-  WPI_UNIGNORE_DEPRECATED
-  return *this;
-}
-
-Button Button::ToggleWhenPressed(Command* command) {
-  WPI_IGNORE_DEPRECATED
-  ToggleWhenActive(command);
-  WPI_UNIGNORE_DEPRECATED
-  return *this;
-}
-
-Button Button::CancelWhenPressed(Command* command) {
-  WPI_IGNORE_DEPRECATED
-  CancelWhenActive(command);
-  WPI_UNIGNORE_DEPRECATED
-  return *this;
-}
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/button/CommandPS5Controller.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/button/CommandPS5Controller.cpp
new file mode 100644
index 0000000..399a771
--- /dev/null
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/button/CommandPS5Controller.cpp
@@ -0,0 +1,63 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include "frc2/command/button/CommandPS5Controller.h"
+
+using namespace frc2;
+
+Trigger CommandPS5Controller::Button(int button, frc::EventLoop* loop) const {
+  return GenericHID::Button(button, loop).CastTo<Trigger>();
+}
+
+Trigger CommandPS5Controller::Square(frc::EventLoop* loop) const {
+  return PS5Controller::Square(loop).CastTo<Trigger>();
+}
+
+Trigger CommandPS5Controller::Cross(frc::EventLoop* loop) const {
+  return PS5Controller::Cross(loop).CastTo<Trigger>();
+}
+
+Trigger CommandPS5Controller::Circle(frc::EventLoop* loop) const {
+  return PS5Controller::Circle(loop).CastTo<Trigger>();
+}
+
+Trigger CommandPS5Controller::Triangle(frc::EventLoop* loop) const {
+  return PS5Controller::Triangle(loop).CastTo<Trigger>();
+}
+
+Trigger CommandPS5Controller::L1(frc::EventLoop* loop) const {
+  return PS5Controller::L1(loop).CastTo<Trigger>();
+}
+
+Trigger CommandPS5Controller::R1(frc::EventLoop* loop) const {
+  return PS5Controller::R1(loop).CastTo<Trigger>();
+}
+
+Trigger CommandPS5Controller::L2(frc::EventLoop* loop) const {
+  return PS5Controller::L2(loop).CastTo<Trigger>();
+}
+
+Trigger CommandPS5Controller::R2(frc::EventLoop* loop) const {
+  return PS5Controller::R2(loop).CastTo<Trigger>();
+}
+
+Trigger CommandPS5Controller::Options(frc::EventLoop* loop) const {
+  return PS5Controller::Options(loop).CastTo<Trigger>();
+}
+
+Trigger CommandPS5Controller::L3(frc::EventLoop* loop) const {
+  return PS5Controller::L3(loop).CastTo<Trigger>();
+}
+
+Trigger CommandPS5Controller::R3(frc::EventLoop* loop) const {
+  return PS5Controller::R3(loop).CastTo<Trigger>();
+}
+
+Trigger CommandPS5Controller::PS(frc::EventLoop* loop) const {
+  return PS5Controller::PS(loop).CastTo<Trigger>();
+}
+
+Trigger CommandPS5Controller::Touchpad(frc::EventLoop* loop) const {
+  return PS5Controller::Touchpad(loop).CastTo<Trigger>();
+}
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/button/NetworkButton.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/button/NetworkButton.cpp
index e26cd14..a0923a3 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/button/NetworkButton.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/button/NetworkButton.cpp
@@ -4,19 +4,15 @@
 
 #include "frc2/command/button/NetworkButton.h"
 
-#include <wpi/deprecated.h>
-
 using namespace frc2;
 
-WPI_IGNORE_DEPRECATED
 NetworkButton::NetworkButton(nt::BooleanTopic topic)
     : NetworkButton(topic.Subscribe(false)) {}
 
 NetworkButton::NetworkButton(nt::BooleanSubscriber sub)
-    : Button([sub = std::make_shared<nt::BooleanSubscriber>(std::move(sub))] {
+    : Trigger([sub = std::make_shared<nt::BooleanSubscriber>(std::move(sub))] {
         return sub->GetTopic().GetInstance().IsConnected() && sub->Get();
       }) {}
-WPI_UNIGNORE_DEPRECATED
 
 NetworkButton::NetworkButton(std::shared_ptr<nt::NetworkTable> table,
                              std::string_view field)
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/button/Trigger.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/button/Trigger.cpp
index 3908daf..38ec741 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/button/Trigger.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/button/Trigger.cpp
@@ -205,124 +205,6 @@
   return *this;
 }
 
-WPI_IGNORE_DEPRECATED
-Trigger Trigger::WhenActive(Command* command) {
-  return OnTrue(command);
-}
-
-Trigger Trigger::WhenActive(std::function<void()> toRun,
-                            std::initializer_list<Subsystem*> requirements) {
-  return WhenActive(std::move(toRun),
-                    {requirements.begin(), requirements.end()});
-}
-
-Trigger Trigger::WhenActive(std::function<void()> toRun,
-                            std::span<Subsystem* const> requirements) {
-  return WhenActive(InstantCommand(std::move(toRun), requirements));
-}
-
-Trigger Trigger::WhileActiveContinous(Command* command) {
-  m_loop->Bind([condition = m_condition, previous = m_condition(),
-                command = std::move(command)]() mutable {
-    bool current = condition();
-
-    if (current) {
-      command->Schedule();
-    } else if (previous && !current) {
-      command->Cancel();
-    }
-
-    previous = current;
-  });
-  return *this;
-}
-
-Trigger Trigger::WhileActiveContinous(
-    std::function<void()> toRun,
-    std::initializer_list<Subsystem*> requirements) {
-  return WhileActiveContinous(std::move(toRun),
-                              {requirements.begin(), requirements.end()});
-}
-
-Trigger Trigger::WhileActiveContinous(
-    std::function<void()> toRun, std::span<Subsystem* const> requirements) {
-  return WhileActiveContinous(InstantCommand(std::move(toRun), requirements));
-}
-
-Trigger Trigger::WhileActiveOnce(Command* command) {
-  m_loop->Bind(
-      [condition = m_condition, previous = m_condition(), command]() mutable {
-        bool current = condition();
-
-        if (!previous && current) {
-          command->Schedule();
-        } else if (previous && !current) {
-          command->Cancel();
-        }
-
-        previous = current;
-      });
-  return *this;
-}
-
-Trigger Trigger::WhenInactive(Command* command) {
-  m_loop->Bind(
-      [condition = m_condition, previous = m_condition(), command]() mutable {
-        bool current = condition();
-
-        if (previous && !current) {
-          command->Schedule();
-        }
-
-        previous = current;
-      });
-  return *this;
-}
-
-Trigger Trigger::WhenInactive(std::function<void()> toRun,
-                              std::initializer_list<Subsystem*> requirements) {
-  return WhenInactive(std::move(toRun),
-                      {requirements.begin(), requirements.end()});
-}
-
-Trigger Trigger::WhenInactive(std::function<void()> toRun,
-                              std::span<Subsystem* const> requirements) {
-  return WhenInactive(InstantCommand(std::move(toRun), requirements));
-}
-
-Trigger Trigger::ToggleWhenActive(Command* command) {
-  m_loop->Bind([condition = m_condition, previous = m_condition(),
-                command = command]() mutable {
-    bool current = condition();
-
-    if (!previous && current) {
-      if (command->IsScheduled()) {
-        command->Cancel();
-      } else {
-        command->Schedule();
-      }
-    }
-
-    previous = current;
-  });
-  return *this;
-}
-
-Trigger Trigger::CancelWhenActive(Command* command) {
-  m_loop->Bind([condition = m_condition, previous = m_condition(),
-                command = std::move(command)]() mutable {
-    bool current = condition();
-
-    if (!previous && current) {
-      command->Cancel();
-    }
-
-    previous = current;
-  });
-  return *this;
-}
-WPI_UNIGNORE_DEPRECATED
-
 Trigger Trigger::Debounce(units::second_t debounceTime,
                           frc::Debouncer::DebounceType type) {
   return Trigger(m_loop, [debouncer = frc::Debouncer(debounceTime, type),
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/Command.h b/wpilibNewCommands/src/main/native/include/frc2/command/Command.h
index 189433b..9a48b2e 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/Command.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/Command.h
@@ -5,16 +5,15 @@
 #pragma once
 
 #include <functional>
-#include <initializer_list>
 #include <memory>
-#include <span>
 #include <string>
 
 #include <units/time.h>
 #include <wpi/Demangle.h>
 #include <wpi/SmallSet.h>
-#include <wpi/deprecated.h>
+#include <wpi/sendable/Sendable.h>
 
+#include "frc2/command/Requirements.h"
 #include "frc2/command/Subsystem.h"
 
 namespace frc2 {
@@ -24,8 +23,6 @@
   return wpi::Demangle(typeid(type).name());
 }
 
-class PerpetualCommand;
-
 /**
  * A state machine representing a complete action to be performed by the robot.
  * Commands are run by the CommandScheduler, and can be composed into
@@ -44,10 +41,9 @@
  * @see CommandScheduler
  * @see CommandHelper
  */
-class Command {
+class Command : public wpi::Sendable, public wpi::SendableHelper<Command> {
  public:
-  Command() = default;
-  virtual ~Command();
+  ~Command() override;
 
   Command(const Command&) = default;
   Command& operator=(const Command& rhs);
@@ -95,7 +91,83 @@
    * @return the set of subsystems that are required
    * @see InterruptionBehavior
    */
-  virtual wpi::SmallSet<Subsystem*, 4> GetRequirements() const = 0;
+  virtual wpi::SmallSet<Subsystem*, 4> GetRequirements() const;
+
+  /**
+   * Adds the specified Subsystem requirements to the command.
+   *
+   * The scheduler will prevent two commands that require the same subsystem
+   * from being scheduled simultaneously.
+   *
+   * Note that the scheduler determines the requirements of a command when it
+   * is scheduled, so this method should normally be called from the command's
+   * constructor.
+   *
+   * While this overload can be used with {@code AddRequirements({&subsystem1,
+   * &subsystem2})}, {@code AddRequirements({&subsystem})} selects the {@code
+   * AddRequirements(Subsystem*)} overload, which will function identically but
+   * may cause warnings about redundant braces.
+   *
+   * @param requirements the Subsystem requirements to add, which can be
+   * implicitly constructed from an initializer list or a span
+   */
+  void AddRequirements(Requirements requirements);
+
+  /**
+   * Adds the specified Subsystem requirements to the command.
+   *
+   * The scheduler will prevent two commands that require the same subsystem
+   * from being scheduled simultaneously.
+   *
+   * Note that the scheduler determines the requirements of a command when it
+   * is scheduled, so this method should normally be called from the command's
+   * constructor.
+   *
+   * @param requirements the Subsystem requirements to add
+   */
+  void AddRequirements(wpi::SmallSet<Subsystem*, 4> requirements);
+
+  /**
+   * Adds the specified Subsystem requirement to the command.
+   *
+   * The scheduler will prevent two commands that require the same subsystem
+   * from being scheduled simultaneously.
+   *
+   * Note that the scheduler determines the requirements of a command when it
+   * is scheduled, so this method should normally be called from the command's
+   * constructor.
+   *
+   * @param requirement the Subsystem requirement to add
+   */
+  void AddRequirements(Subsystem* requirement);
+
+  /**
+   * Gets the name of this Command.
+   *
+   * @return Name
+   */
+  std::string GetName() const;
+
+  /**
+   * Sets the name of this Command.
+   *
+   * @param name name
+   */
+  void SetName(std::string_view name);
+
+  /**
+   * Gets the subsystem name of this Command.
+   *
+   * @return Subsystem name
+   */
+  std::string GetSubsystem() const;
+
+  /**
+   * Sets the subsystem name of this Command.
+   *
+   * @param subsystem subsystem name
+   */
+  void SetSubsystem(std::string_view subsystem);
 
   /**
    * An enum describing the command's behavior when another command with a
@@ -116,39 +188,37 @@
   friend class CommandPtr;
 
   /**
-   * Decorates this command with a timeout.  If the specified timeout is
+   * Decorates this command with a timeout. If the specified timeout is
    * exceeded before the command finishes normally, the command will be
-   * interrupted and un-scheduled.  Note that the timeout only applies to the
-   * command returned by this method; the calling command is not itself changed.
+   * interrupted and un-scheduled.
    *
    * @param duration the timeout duration
    * @return the command with the timeout added
    */
-  [[nodiscard]] CommandPtr WithTimeout(units::second_t duration) &&;
+  [[nodiscard]]
+  CommandPtr WithTimeout(units::second_t duration) &&;
 
   /**
-   * Decorates this command with an interrupt condition.  If the specified
+   * Decorates this command with an interrupt condition. If the specified
    * condition becomes true before the command finishes normally, the command
-   * will be interrupted and un-scheduled. Note that this only applies to the
-   * command returned by this method; the calling command is not itself changed.
+   * will be interrupted and un-scheduled.
    *
    * @param condition the interrupt condition
    * @return the command with the interrupt condition added
    */
-  [[nodiscard]] CommandPtr Until(std::function<bool()> condition) &&;
+  [[nodiscard]]
+  CommandPtr Until(std::function<bool()> condition) &&;
 
   /**
-   * Decorates this command with an interrupt condition.  If the specified
-   * condition becomes true before the command finishes normally, the command
-   * will be interrupted and un-scheduled. Note that this only applies to the
-   * command returned by this method; the calling command is not itself changed.
+   * Decorates this command with a run condition. If the specified condition
+   * becomes false before the command finishes normally, the command will be
+   * interrupted and un-scheduled.
    *
-   * @param condition the interrupt condition
-   * @return the command with the interrupt condition added
-   * @deprecated Replace with Until()
+   * @param condition the run condition
+   * @return the command with the run condition added
    */
-  WPI_DEPRECATED("Replace with Until()")
-  [[nodiscard]] CommandPtr WithInterrupt(std::function<bool()> condition) &&;
+  [[nodiscard]]
+  CommandPtr OnlyWhile(std::function<bool()> condition) &&;
 
   /**
    * Decorates this command with a runnable to run before this command starts.
@@ -157,20 +227,9 @@
    * @param requirements the required subsystems
    * @return the decorated command
    */
-  [[nodiscard]] CommandPtr BeforeStarting(
-      std::function<void()> toRun,
-      std::initializer_list<Subsystem*> requirements) &&;
-
-  /**
-   * Decorates this command with a runnable to run before this command starts.
-   *
-   * @param toRun the Runnable to run
-   * @param requirements the required subsystems
-   * @return the decorated command
-   */
-  [[nodiscard]] CommandPtr BeforeStarting(
-      std::function<void()> toRun,
-      std::span<Subsystem* const> requirements = {}) &&;
+  [[nodiscard]]
+  CommandPtr BeforeStarting(std::function<void()> toRun,
+                            Requirements requirements = {}) &&;
 
   /**
    * Decorates this command with a runnable to run after the command finishes.
@@ -179,40 +238,9 @@
    * @param requirements the required subsystems
    * @return the decorated command
    */
-  [[nodiscard]] CommandPtr AndThen(
-      std::function<void()> toRun,
-      std::initializer_list<Subsystem*> requirements) &&;
-
-  /**
-   * Decorates this command with a runnable to run after the command finishes.
-   *
-   * @param toRun the Runnable to run
-   * @param requirements the required subsystems
-   * @return the decorated command
-   */
-  [[nodiscard]] CommandPtr AndThen(
-      std::function<void()> toRun,
-      std::span<Subsystem* const> requirements = {}) &&;
-
-  /**
-   * Decorates this command to run perpetually, ignoring its ordinary end
-   * conditions.  The decorated command can still be interrupted or canceled.
-   *
-   * @return the decorated command
-   * @deprecated PerpetualCommand violates the assumption that execute() doesn't
-get called after isFinished() returns true -- an assumption that should be
-valid. This was unsafe/undefined behavior from the start, and RepeatCommand
-provides an easy way to achieve similar end results with slightly different (and
-safe) semantics.
-   */
-  WPI_DEPRECATED(
-      "PerpetualCommand violates the assumption that execute() doesn't get "
-      "called after isFinished() returns true -- an assumption that should be "
-      "valid."
-      "This was unsafe/undefined behavior from the start, and RepeatCommand "
-      "provides an easy way to achieve similar end results with slightly "
-      "different (and safe) semantics.")
-  PerpetualCommand Perpetually() &&;
+  [[nodiscard]]
+  CommandPtr AndThen(std::function<void()> toRun,
+                     Requirements requirements = {}) &&;
 
   /**
    * Decorates this command to run repeatedly, restarting it when it ends, until
@@ -220,7 +248,8 @@
    *
    * @return the decorated command
    */
-  [[nodiscard]] CommandPtr Repeatedly() &&;
+  [[nodiscard]]
+  CommandPtr Repeatedly() &&;
 
   /**
    * Decorates this command to run "by proxy" by wrapping it in a
@@ -232,7 +261,8 @@
    *
    * @return the decorated command
    */
-  [[nodiscard]] CommandPtr AsProxy() &&;
+  [[nodiscard]]
+  CommandPtr AsProxy() &&;
 
   /**
    * Decorates this command to only run if this condition is not met. If the
@@ -243,7 +273,20 @@
    * @param condition the condition that will prevent the command from running
    * @return the decorated command
    */
-  [[nodiscard]] CommandPtr Unless(std::function<bool()> condition) &&;
+  [[nodiscard]]
+  CommandPtr Unless(std::function<bool()> condition) &&;
+
+  /**
+   * Decorates this command to only run if this condition is met. If the command
+   * is already running and the condition changes to false, the command will not
+   * stop running. The requirements of this command will be kept for the new
+   * conditional command.
+   *
+   * @param condition the condition that will allow the command to run
+   * @return the decorated command
+   */
+  [[nodiscard]]
+  CommandPtr OnlyIf(std::function<bool()> condition) &&;
 
   /**
    * Decorates this command to run or stop when disabled.
@@ -251,15 +294,17 @@
    * @param doesRunWhenDisabled true to run when disabled.
    * @return the decorated command
    */
-  [[nodiscard]] CommandPtr IgnoringDisable(bool doesRunWhenDisabled) &&;
+  [[nodiscard]]
+  CommandPtr IgnoringDisable(bool doesRunWhenDisabled) &&;
 
   /**
-   * Decorates this command to run or stop when disabled.
+   * Decorates this command to have a different interrupt behavior.
    *
-   * @param interruptBehavior true to run when disabled.
+   * @param interruptBehavior the desired interrupt behavior
    * @return the decorated command
    */
-  [[nodiscard]] CommandPtr WithInterruptBehavior(
+  [[nodiscard]]
+  CommandPtr WithInterruptBehavior(
       Command::InterruptionBehavior interruptBehavior) &&;
 
   /**
@@ -270,7 +315,20 @@
    * command was interrupted.
    * @return the decorated command
    */
-  [[nodiscard]] CommandPtr FinallyDo(std::function<void(bool)> end) &&;
+  [[nodiscard]]
+  CommandPtr FinallyDo(std::function<void(bool)> end) &&;
+
+  /**
+   * Decorates this command with a lambda to call on interrupt or end, following
+   * the command's inherent Command::End(bool) method. The provided lambda will
+   * run identically in both interrupt and end cases.
+   *
+   * @param end a lambda to run when the command ends, whether or not it was
+   * interrupted.
+   * @return the decorated command
+   */
+  [[nodiscard]]
+  CommandPtr FinallyDo(std::function<void()> end) &&;
 
   /**
    * Decorates this command with a lambda to call on interrupt, following the
@@ -279,7 +337,8 @@
    * @param handler a lambda to run when the command is interrupted
    * @return the decorated command
    */
-  [[nodiscard]] CommandPtr HandleInterrupt(std::function<void()> handler) &&;
+  [[nodiscard]]
+  CommandPtr HandleInterrupt(std::function<void()> handler) &&;
 
   /**
    * Decorates this Command with a name.
@@ -287,7 +346,8 @@
    * @param name name
    * @return the decorated Command
    */
-  [[nodiscard]] CommandPtr WithName(std::string_view name) &&;
+  [[nodiscard]]
+  CommandPtr WithName(std::string_view name) &&;
 
   /**
    * Schedules this command.
@@ -333,25 +393,6 @@
   void SetComposed(bool isComposed);
 
   /**
-   * Whether the command is currently grouped in a command group.  Used as extra
-   * insurance to prevent accidental independent use of grouped commands.
-   *
-   * @deprecated Moved to IsComposed()
-   */
-  WPI_DEPRECATED("Moved to IsComposed()")
-  bool IsGrouped() const;
-
-  /**
-   * Sets whether the command is currently grouped in a command group.  Can be
-   * used to "reclaim" a command if a group is no longer going to use it.  NOT
-   * ADVISED!
-   *
-   * @deprecated Moved to SetComposed()
-   */
-  WPI_DEPRECATED("Moved to SetComposed()")
-  void SetGrouped(bool grouped);
-
-  /**
    * Whether the given command should run when the robot is disabled.  Override
    * to return true if the command should run when disabled.
    *
@@ -370,27 +411,18 @@
   }
 
   /**
-   * Gets the name of this Command. Defaults to the simple class name if not
-   * overridden.
-   *
-   * @return The display name of the Command
-   */
-  virtual std::string GetName() const;
-
-  /**
-   * Sets the name of this Command. Nullop if not overridden.
-   *
-   * @param name The display name of the Command.
-   */
-  virtual void SetName(std::string_view name);
-
-  /**
    * Transfers ownership of this command to a unique pointer.  Used for
    * decorator methods.
    */
   virtual CommandPtr ToPtr() && = 0;
 
+  void InitSendable(wpi::SendableBuilder& builder) override;
+
  protected:
+  Command();
+
+  wpi::SmallSet<Subsystem*, 4> m_requirements;
+
   /**
    * Transfers ownership of this command to a unique pointer.  Used for
    * decorator methods.
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/CommandBase.h b/wpilibNewCommands/src/main/native/include/frc2/command/CommandBase.h
index 64bd88b..ea24e42 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/CommandBase.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/CommandBase.h
@@ -4,14 +4,7 @@
 
 #pragma once
 
-#include <initializer_list>
-#include <span>
-#include <string>
-#include <string_view>
-
-#include <wpi/SmallSet.h>
-#include <wpi/sendable/Sendable.h>
-#include <wpi/sendable/SendableHelper.h>
+#include <wpi/deprecated.h>
 
 #include "frc2/command/Command.h"
 
@@ -20,78 +13,13 @@
  * A Sendable base class for Commands.
  *
  * This class is provided by the NewCommands VendorDep
+ *
+ * @deprecated All functionality provided by CommandBase has been merged into
+ * Command. Use Command instead.
  */
-class CommandBase : public Command,
-                    public wpi::Sendable,
-                    public wpi::SendableHelper<CommandBase> {
- public:
-  /**
-   * Adds the specified Subsystem requirements to the command.
-   *
-   * @param requirements the Subsystem requirements to add
-   */
-  void AddRequirements(std::initializer_list<Subsystem*> requirements);
-
-  /**
-   * Adds the specified Subsystem requirements to the command.
-   *
-   * @param requirements the Subsystem requirements to add
-   */
-  void AddRequirements(std::span<Subsystem* const> requirements);
-
-  /**
-   * Adds the specified Subsystem requirements to the command.
-   *
-   * @param requirements the Subsystem requirements to add
-   */
-  void AddRequirements(wpi::SmallSet<Subsystem*, 4> requirements);
-
-  /**
-   * Adds the specified Subsystem requirement to the command.
-   *
-   * @param requirement the Subsystem requirement to add
-   */
-  void AddRequirements(Subsystem* requirement);
-
-  /**
-   * Gets the Subsystem requirements of the command.
-   *
-   * @return the Command's Subsystem requirements
-   */
-  wpi::SmallSet<Subsystem*, 4> GetRequirements() const override;
-
-  /**
-   * Sets the name of this Command.
-   *
-   * @param name name
-   */
-  void SetName(std::string_view name) override;
-
-  /**
-   * Gets the name of this Command.
-   *
-   * @return Name
-   */
-  std::string GetName() const override;
-
-  /**
-   * Gets the subsystem name of this Command.
-   *
-   * @return Subsystem name
-   */
-  std::string GetSubsystem() const;
-
-  /**
-   * Sets the subsystem name of this Command.
-   *
-   * @param subsystem subsystem name
-   */
-  void SetSubsystem(std::string_view subsystem);
-
-  void InitSendable(wpi::SendableBuilder& builder) override;
-
+class [[deprecated("Use Command instead")]] CommandBase : public Command {
  protected:
+  WPI_DEPRECATED("Use Command instead")
   CommandBase();
-  wpi::SmallSet<Subsystem*, 4> m_requirements;
 };
 }  // namespace frc2
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/CommandGroupBase.h b/wpilibNewCommands/src/main/native/include/frc2/command/CommandGroupBase.h
deleted file mode 100644
index 9fe65fa..0000000
--- a/wpilibNewCommands/src/main/native/include/frc2/command/CommandGroupBase.h
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#pragma once
-
-#include <memory>
-#include <vector>
-
-#include <wpi/deprecated.h>
-
-#include "frc2/command/CommandBase.h"
-
-namespace frc2 {
-
-/**
- * A base for CommandGroups.
- *
- * This class is provided by the NewCommands VendorDep
- * @deprecated This class is an empty abstraction. Inherit directly from
- * CommandBase.
- */
-class CommandGroupBase : public CommandBase {
- public:
-  /**
-   * Adds the given commands to the command group.
-   *
-   * @param commands The commands to add.
-   */
-  virtual void AddCommands(
-      std::vector<std::unique_ptr<Command>>&& commands) = 0;
-};
-}  // namespace frc2
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/CommandHelper.h b/wpilibNewCommands/src/main/native/include/frc2/command/CommandHelper.h
index 76ee6cd..ed93b65 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/CommandHelper.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/CommandHelper.h
@@ -4,8 +4,8 @@
 
 #pragma once
 
+#include <concepts>
 #include <memory>
-#include <type_traits>
 #include <utility>
 
 #include "frc2/command/Command.h"
@@ -21,8 +21,7 @@
  *
  * This class is provided by the NewCommands VendorDep
  */
-template <typename Base, typename CRTP,
-          typename = std::enable_if_t<std::is_base_of_v<Command, Base>>>
+template <std::derived_from<Command> Base, typename CRTP>
 class CommandHelper : public Base {
   using Base::Base;
 
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/CommandPtr.h b/wpilibNewCommands/src/main/native/include/frc2/command/CommandPtr.h
index 0ab1e97..ae8c118 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/CommandPtr.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/CommandPtr.h
@@ -4,15 +4,15 @@
 
 #pragma once
 
+#include <concepts>
 #include <functional>
-#include <initializer_list>
 #include <memory>
-#include <span>
 #include <string>
 #include <utility>
 #include <vector>
 
-#include "frc2/command/CommandBase.h"
+#include "frc2/command/Command.h"
+#include "frc2/command/Requirements.h"
 
 namespace frc2 {
 /**
@@ -27,14 +27,14 @@
  */
 class CommandPtr final {
  public:
-  explicit CommandPtr(std::unique_ptr<CommandBase>&& command)
+  explicit CommandPtr(std::unique_ptr<Command>&& command)
       : m_ptr(std::move(command)) {}
 
-  template <class T, typename = std::enable_if_t<std::is_base_of_v<
-                         Command, std::remove_reference_t<T>>>>
+  template <std::derived_from<Command> T>
+  // NOLINTNEXTLINE(bugprone-forwarding-reference-overload)
   explicit CommandPtr(T&& command)
-      : CommandPtr(std::make_unique<std::remove_reference_t<T>>(
-            std::forward<T>(command))) {}
+      : CommandPtr(
+            std::make_unique<std::decay_t<T>>(std::forward<T>(command))) {}
 
   CommandPtr(CommandPtr&&) = default;
   CommandPtr& operator=(CommandPtr&&) = default;
@@ -45,7 +45,8 @@
    *
    * @return the decorated command
    */
-  [[nodiscard]] CommandPtr Repeatedly() &&;
+  [[nodiscard]]
+  CommandPtr Repeatedly() &&;
 
   /**
    * Decorates this command to run "by proxy" by wrapping it in a
@@ -55,23 +56,26 @@
    *
    * @return the decorated command
    */
-  [[nodiscard]] CommandPtr AsProxy() &&;
+  [[nodiscard]]
+  CommandPtr AsProxy() &&;
 
   /**
    * Decorates this command to run or stop when disabled.
    *
-   * @param doesRunWhenDisabled true to run when disabled.
+   * @param doesRunWhenDisabled true to run when disabled
    * @return the decorated command
    */
-  [[nodiscard]] CommandPtr IgnoringDisable(bool doesRunWhenDisabled) &&;
+  [[nodiscard]]
+  CommandPtr IgnoringDisable(bool doesRunWhenDisabled) &&;
 
   /**
-   * Decorates this command to run or stop when disabled.
+   * Decorates this command to have a different interrupt behavior.
    *
-   * @param interruptBehavior true to run when disabled.
+   * @param interruptBehavior the desired interrupt behavior
    * @return the decorated command
    */
-  [[nodiscard]] CommandPtr WithInterruptBehavior(
+  [[nodiscard]]
+  CommandPtr WithInterruptBehavior(
       Command::InterruptionBehavior interruptBehavior) &&;
 
   /**
@@ -81,20 +85,9 @@
    * @param requirements the required subsystems
    * @return the decorated command
    */
-  [[nodiscard]] CommandPtr AndThen(
-      std::function<void()> toRun,
-      std::span<Subsystem* const> requirements = {}) &&;
-
-  /**
-   * Decorates this command with a runnable to run after the command finishes.
-   *
-   * @param toRun the Runnable to run
-   * @param requirements the required subsystems
-   * @return the decorated command
-   */
-  [[nodiscard]] CommandPtr AndThen(
-      std::function<void()> toRun,
-      std::initializer_list<Subsystem*> requirements) &&;
+  [[nodiscard]]
+  CommandPtr AndThen(std::function<void()> toRun,
+                     Requirements requirements = {}) &&;
 
   /**
    * Decorates this command with a set of commands to run after it in sequence.
@@ -104,7 +97,8 @@
    * @param next the commands to run next
    * @return the decorated command
    */
-  [[nodiscard]] CommandPtr AndThen(CommandPtr&& next) &&;
+  [[nodiscard]]
+  CommandPtr AndThen(CommandPtr&& next) &&;
 
   /**
    * Decorates this command with a runnable to run before this command starts.
@@ -113,20 +107,9 @@
    * @param requirements the required subsystems
    * @return the decorated command
    */
-  [[nodiscard]] CommandPtr BeforeStarting(
-      std::function<void()> toRun,
-      std::initializer_list<Subsystem*> requirements) &&;
-
-  /**
-   * Decorates this command with a runnable to run before this command starts.
-   *
-   * @param toRun the Runnable to run
-   * @param requirements the required subsystems
-   * @return the decorated command
-   */
-  [[nodiscard]] CommandPtr BeforeStarting(
-      std::function<void()> toRun,
-      std::span<Subsystem* const> requirements = {}) &&;
+  [[nodiscard]]
+  CommandPtr BeforeStarting(std::function<void()> toRun,
+                            Requirements requirements = {}) &&;
 
   /**
    * Decorates this command with another command to run before this command
@@ -135,29 +118,41 @@
    * @param before the command to run before this one
    * @return the decorated command
    */
-  [[nodiscard]] CommandPtr BeforeStarting(CommandPtr&& before) &&;
+  [[nodiscard]]
+  CommandPtr BeforeStarting(CommandPtr&& before) &&;
 
   /**
-   * Decorates this command with a timeout.  If the specified timeout is
+   * Decorates this command with a timeout. If the specified timeout is
    * exceeded before the command finishes normally, the command will be
-   * interrupted and un-scheduled.  Note that the timeout only applies to the
-   * command returned by this method; the calling command is not itself changed.
+   * interrupted and un-scheduled.
    *
    * @param duration the timeout duration
    * @return the command with the timeout added
    */
-  [[nodiscard]] CommandPtr WithTimeout(units::second_t duration) &&;
+  [[nodiscard]]
+  CommandPtr WithTimeout(units::second_t duration) &&;
 
   /**
-   * Decorates this command with an interrupt condition.  If the specified
+   * Decorates this command with an interrupt condition. If the specified
    * condition becomes true before the command finishes normally, the command
-   * will be interrupted and un-scheduled. Note that this only applies to the
-   * command returned by this method; the calling command is not itself changed.
+   * will be interrupted and un-scheduled.
    *
    * @param condition the interrupt condition
    * @return the command with the interrupt condition added
    */
-  [[nodiscard]] CommandPtr Until(std::function<bool()> condition) &&;
+  [[nodiscard]]
+  CommandPtr Until(std::function<bool()> condition) &&;
+
+  /**
+   * Decorates this command with a run condition. If the specified condition
+   * becomes false before the command finishes normally, the command will be
+   * interrupted and un-scheduled.
+   *
+   * @param condition the run condition
+   * @return the command with the run condition added
+   */
+  [[nodiscard]]
+  CommandPtr OnlyWhile(std::function<bool()> condition) &&;
 
   /**
    * Decorates this command to only run if this condition is not met. If the
@@ -168,7 +163,20 @@
    * @param condition the condition that will prevent the command from running
    * @return the decorated command
    */
-  [[nodiscard]] CommandPtr Unless(std::function<bool()> condition) &&;
+  [[nodiscard]]
+  CommandPtr Unless(std::function<bool()> condition) &&;
+
+  /**
+   * Decorates this command to only run if this condition is met. If the command
+   * is already running and the condition changes to false, the command will not
+   * stop running. The requirements of this command will be kept for the new
+   * conditional command.
+   *
+   * @param condition the condition that will allow the command to run
+   * @return the decorated command
+   */
+  [[nodiscard]]
+  CommandPtr OnlyIf(std::function<bool()> condition) &&;
 
   /**
    * Decorates this command with a set of commands to run parallel to it, ending
@@ -179,7 +187,8 @@
    * @param parallel the commands to run in parallel
    * @return the decorated command
    */
-  [[nodiscard]] CommandPtr DeadlineWith(CommandPtr&& parallel) &&;
+  [[nodiscard]]
+  CommandPtr DeadlineWith(CommandPtr&& parallel) &&;
 
   /**
    * Decorates this command with a set of commands to run parallel to it, ending
@@ -189,7 +198,8 @@
    * @param parallel the commands to run in parallel
    * @return the decorated command
    */
-  [[nodiscard]] CommandPtr AlongWith(CommandPtr&& parallel) &&;
+  [[nodiscard]]
+  CommandPtr AlongWith(CommandPtr&& parallel) &&;
 
   /**
    * Decorates this command with a set of commands to run parallel to it, ending
@@ -199,17 +209,31 @@
    * @param parallel the commands to run in parallel
    * @return the decorated command
    */
-  [[nodiscard]] CommandPtr RaceWith(CommandPtr&& parallel) &&;
+  [[nodiscard]]
+  CommandPtr RaceWith(CommandPtr&& parallel) &&;
 
   /**
    * Decorates this command with a lambda to call on interrupt or end, following
    * the command's inherent Command::End(bool) method.
    *
    * @param end a lambda accepting a boolean parameter specifying whether the
-   * command was interrupted.
+   * command was interrupted
    * @return the decorated command
    */
-  [[nodiscard]] CommandPtr FinallyDo(std::function<void(bool)> end) &&;
+  [[nodiscard]]
+  CommandPtr FinallyDo(std::function<void(bool)> end) &&;
+
+  /**
+   * Decorates this command with a lambda to call on interrupt or end, following
+   * the command's inherent Command::End(bool) method. The provided lambda will
+   * run identically in both interrupt and end cases.
+   *
+   * @param end a lambda to run when the command ends, whether or not it was
+   * interrupted.
+   * @return the decorated command
+   */
+  [[nodiscard]]
+  CommandPtr FinallyDo(std::function<void()> end) &&;
 
   /**
    * Decorates this command with a lambda to call on interrupt, following the
@@ -218,7 +242,8 @@
    * @param handler a lambda to run when the command is interrupted
    * @return the decorated command
    */
-  [[nodiscard]] CommandPtr HandleInterrupt(std::function<void()> handler) &&;
+  [[nodiscard]]
+  CommandPtr HandleInterrupt(std::function<void()> handler) &&;
 
   /**
    * Decorates this Command with a name. Is an inline function for
@@ -227,28 +252,38 @@
    * @param name name
    * @return the decorated Command
    */
-  [[nodiscard]] CommandPtr WithName(std::string_view name) &&;
+  [[nodiscard]]
+  CommandPtr WithName(std::string_view name) &&;
 
   /**
    * Get a raw pointer to the held command.
    */
-  CommandBase* get() const;
+  Command* get() const&;
+
+  // Prevent calls on a temporary, as the returned pointer would be invalid
+  Command* get() && = delete;
 
   /**
    * Convert to the underlying unique_ptr.
    */
-  std::unique_ptr<CommandBase> Unwrap() &&;
+  std::unique_ptr<Command> Unwrap() &&;
 
   /**
    * Schedules this command.
    */
-  void Schedule() const;
+  void Schedule() const&;
+
+  // Prevent calls on a temporary, as the returned pointer would be invalid
+  void Schedule() && = delete;
 
   /**
    * Cancels this command. Will call End(true). Commands will be canceled
    * regardless of interruption behavior.
    */
-  void Cancel() const;
+  void Cancel() const&;
+
+  // Prevent calls on a temporary, as the returned pointer would be invalid
+  void Cancel() && = delete;
 
   /**
    * Whether or not the command is currently scheduled. Note that this does not
@@ -257,7 +292,10 @@
    *
    * @return Whether the command is scheduled.
    */
-  bool IsScheduled() const;
+  bool IsScheduled() const&;
+
+  // Prevent calls on a temporary, as the returned pointer would be invalid
+  void IsScheduled() && = delete;
 
   /**
    * Whether the command requires a given subsystem.  Named "HasRequirement"
@@ -267,12 +305,18 @@
    * @param requirement the subsystem to inquire about
    * @return whether the subsystem is required
    */
-  bool HasRequirement(Subsystem* requirement) const;
+  bool HasRequirement(Subsystem* requirement) const&;
+
+  // Prevent calls on a temporary, as the returned pointer would be invalid
+  void HasRequirement(Subsystem* requirement) && = delete;
 
   /**
    * Check if this CommandPtr object is valid and wasn't moved-from.
    */
-  explicit operator bool() const;
+  explicit operator bool() const&;
+
+  // Prevent calls on a temporary, as the returned pointer would be invalid
+  explicit operator bool() && = delete;
 
   /**
    * Convert a vector of CommandPtr objects to their underlying unique_ptrs.
@@ -281,7 +325,7 @@
       std::vector<CommandPtr>&& vec);
 
  private:
-  std::unique_ptr<CommandBase> m_ptr;
+  std::unique_ptr<Command> m_ptr;
   void AssertValid() const;
 };
 
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/CommandScheduler.h b/wpilibNewCommands/src/main/native/include/frc2/command/CommandScheduler.h
index c0c09c1..ce4dc91 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/CommandScheduler.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/CommandScheduler.h
@@ -4,18 +4,20 @@
 
 #pragma once
 
+#include <concepts>
+#include <functional>
 #include <initializer_list>
 #include <memory>
+#include <optional>
 #include <span>
 #include <utility>
 
 #include <frc/Errors.h>
 #include <frc/Watchdog.h>
 #include <frc/event/EventLoop.h>
-#include <networktables/NTSendable.h>
 #include <units/time.h>
 #include <wpi/FunctionExtras.h>
-#include <wpi/deprecated.h>
+#include <wpi/sendable/Sendable.h>
 #include <wpi/sendable/SendableHelper.h>
 
 namespace frc2 {
@@ -32,7 +34,7 @@
  *
  * This class is provided by the NewCommands VendorDep
  */
-class CommandScheduler final : public nt::NTSendable,
+class CommandScheduler final : public wpi::Sendable,
                                public wpi::SendableHelper<CommandScheduler> {
  public:
   /**
@@ -47,6 +49,8 @@
   CommandScheduler& operator=(const CommandScheduler&) = delete;
 
   using Action = std::function<void(const Command&)>;
+  using InterruptAction =
+      std::function<void(const Command&, const std::optional<Command*>&)>;
 
   /**
    * Changes the period of the loop overrun watchdog. This should be kept in
@@ -78,12 +82,6 @@
   frc::EventLoop* GetDefaultButtonLoop() const;
 
   /**
-   * Removes all button bindings from the scheduler.
-   */
-  WPI_DEPRECATED("Call Clear on the EventLoop instance directly!")
-  void ClearButtons();
-
-  /**
    * Schedules a command for execution. Does nothing if the command is already
    * scheduled. If a command's requirements are not available, it will only be
    * started if all the commands currently using those requirements are
@@ -165,6 +163,13 @@
   void UnregisterSubsystem(std::span<Subsystem* const> subsystems);
 
   /**
+   * Un-registers all registered Subsystems with the scheduler. All currently
+   * registered subsystems will no longer have their periodic block called, and
+   * will not have their default command scheduled.
+   */
+  void UnregisterAllSubsystems();
+
+  /**
    * Sets the default command for a subsystem.  Registers that subsystem if it
    * is not already registered.  Default commands will run whenever there is no
    * other command currently scheduled that requires the subsystem.  Default
@@ -175,16 +180,14 @@
    * @param subsystem      the subsystem whose default command will be set
    * @param defaultCommand the default command to associate with the subsystem
    */
-  template <class T, typename = std::enable_if_t<std::is_base_of_v<
-                         Command, std::remove_reference_t<T>>>>
+  template <std::derived_from<Command> T>
   void SetDefaultCommand(Subsystem* subsystem, T&& defaultCommand) {
     if (!defaultCommand.HasRequirement(subsystem)) {
       throw FRC_MakeError(frc::err::CommandIllegalUse,
                           "Default commands must require their subsystem!");
     }
-    SetDefaultCommandImpl(subsystem,
-                          std::make_unique<std::remove_reference_t<T>>(
-                              std::forward<T>(defaultCommand)));
+    SetDefaultCommandImpl(subsystem, std::make_unique<std::decay_t<T>>(
+                                         std::forward<T>(defaultCommand)));
   }
 
   /**
@@ -354,6 +357,16 @@
   void OnCommandInterrupt(Action action);
 
   /**
+   * Adds an action to perform on the interruption of any command by the
+   * scheduler. The action receives the interrupted command and an optional
+   * containing the interrupting command, or nullopt if it was not canceled by a
+   * command (e.g., by Cancel()).
+   *
+   * @param action the action to perform
+   */
+  void OnCommandInterrupt(InterruptAction action);
+
+  /**
    * Adds an action to perform on the finishing of any command by the scheduler.
    *
    * @param action the action to perform
@@ -388,7 +401,7 @@
    */
   void RequireUngrouped(std::initializer_list<const Command*> commands);
 
-  void InitSendable(nt::NTSendableBuilder& builder) override;
+  void InitSendable(wpi::SendableBuilder& builder) override;
 
  private:
   // Constructor; private as this is a singleton
@@ -397,6 +410,8 @@
   void SetDefaultCommandImpl(Subsystem* subsystem,
                              std::unique_ptr<Command> command);
 
+  void Cancel(Command* command, std::optional<Command*> interruptor);
+
   class Impl;
   std::unique_ptr<Impl> m_impl;
 
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/Commands.h b/wpilibNewCommands/src/main/native/include/frc2/command/Commands.h
index 8b38275..5c1d49a 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/Commands.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/Commands.h
@@ -4,14 +4,16 @@
 
 #pragma once
 
+#include <concepts>
 #include <functional>
-#include <initializer_list>
-#include <span>
+#include <memory>
 #include <string>
+#include <type_traits>
 #include <utility>
 #include <vector>
 
 #include "frc2/command/CommandPtr.h"
+#include "frc2/command/Requirements.h"
 #include "frc2/command/SelectCommand.h"
 
 namespace frc2 {
@@ -25,7 +27,17 @@
 /**
  * Constructs a command that does nothing, finishing immediately.
  */
-[[nodiscard]] CommandPtr None();
+[[nodiscard]]
+CommandPtr None();
+
+/**
+ * Constructs a command that does nothing until interrupted.
+ *
+ * @param requirements Subsystems to require
+ * @return the command
+ */
+[[nodiscard]]
+CommandPtr Idle(Requirements requirements = {});
 
 // Action Commands
 
@@ -35,18 +47,9 @@
  * @param action the action to run
  * @param requirements subsystems the action requires
  */
-[[nodiscard]] CommandPtr RunOnce(
-    std::function<void()> action,
-    std::initializer_list<Subsystem*> requirements);
-
-/**
- * Constructs a command that runs an action once and finishes.
- *
- * @param action the action to run
- * @param requirements subsystems the action requires
- */
-[[nodiscard]] CommandPtr RunOnce(std::function<void()> action,
-                                 std::span<Subsystem* const> requirements = {});
+[[nodiscard]]
+CommandPtr RunOnce(std::function<void()> action,
+                   Requirements requirements = {});
 
 /**
  * Constructs a command that runs an action every iteration until interrupted.
@@ -54,17 +57,8 @@
  * @param action the action to run
  * @param requirements subsystems the action requires
  */
-[[nodiscard]] CommandPtr Run(std::function<void()> action,
-                             std::initializer_list<Subsystem*> requirements);
-
-/**
- * Constructs a command that runs an action every iteration until interrupted.
- *
- * @param action the action to run
- * @param requirements subsystems the action requires
- */
-[[nodiscard]] CommandPtr Run(std::function<void()> action,
-                             std::span<Subsystem* const> requirements = {});
+[[nodiscard]]
+CommandPtr Run(std::function<void()> action, Requirements requirements = {});
 
 /**
  * Constructs a command that runs an action once and another action when the
@@ -74,21 +68,9 @@
  * @param end the action to run on interrupt
  * @param requirements subsystems the action requires
  */
-[[nodiscard]] CommandPtr StartEnd(
-    std::function<void()> start, std::function<void()> end,
-    std::initializer_list<Subsystem*> requirements);
-
-/**
- * Constructs a command that runs an action once and another action when the
- * command is interrupted.
- *
- * @param start the action to run on start
- * @param end the action to run on interrupt
- * @param requirements subsystems the action requires
- */
-[[nodiscard]] CommandPtr StartEnd(
-    std::function<void()> start, std::function<void()> end,
-    std::span<Subsystem* const> requirements = {});
+[[nodiscard]]
+CommandPtr StartEnd(std::function<void()> start, std::function<void()> end,
+                    Requirements requirements = {});
 
 /**
  * Constructs a command that runs an action every iteration until interrupted,
@@ -98,28 +80,17 @@
  * @param end the action to run on interrupt
  * @param requirements subsystems the action requires
  */
-[[nodiscard]] CommandPtr RunEnd(std::function<void()> run,
-                                std::function<void()> end,
-                                std::initializer_list<Subsystem*> requirements);
-
-/**
- * Constructs a command that runs an action every iteration until interrupted,
- * and then runs a second action.
- *
- * @param run the action to run every iteration
- * @param end the action to run on interrupt
- * @param requirements subsystems the action requires
- */
-[[nodiscard]] CommandPtr RunEnd(std::function<void()> run,
-                                std::function<void()> end,
-                                std::span<Subsystem* const> requirements = {});
+[[nodiscard]]
+CommandPtr RunEnd(std::function<void()> run, std::function<void()> end,
+                  Requirements requirements = {});
 
 /**
  * Constructs a command that prints a message and finishes.
  *
  * @param msg the message to print
  */
-[[nodiscard]] CommandPtr Print(std::string_view msg);
+[[nodiscard]]
+CommandPtr Print(std::string_view msg);
 
 // Idling Commands
 
@@ -128,7 +99,8 @@
  *
  * @param duration after how long the command finishes
  */
-[[nodiscard]] CommandPtr Wait(units::second_t duration);
+[[nodiscard]]
+CommandPtr Wait(units::second_t duration);
 
 /**
  * Constructs a command that does nothing, finishing once a condition becomes
@@ -136,7 +108,8 @@
  *
  * @param condition the condition
  */
-[[nodiscard]] CommandPtr WaitUntil(std::function<bool()> condition);
+[[nodiscard]]
+CommandPtr WaitUntil(std::function<bool()> condition);
 
 // Selector Commands
 
@@ -147,8 +120,9 @@
  * @param onFalse the command to run if the selector function returns false
  * @param selector the selector function
  */
-[[nodiscard]] CommandPtr Either(CommandPtr&& onTrue, CommandPtr&& onFalse,
-                                std::function<bool()> selector);
+[[nodiscard]]
+CommandPtr Either(CommandPtr&& onTrue, CommandPtr&& onFalse,
+                  std::function<bool()> selector);
 
 /**
  * Runs one of several commands, based on the selector function.
@@ -156,15 +130,48 @@
  * @param selector the selector function
  * @param commands map of commands to select from
  */
-template <typename Key>
-[[nodiscard]] CommandPtr Select(
-    std::function<Key()> selector,
-    std::vector<std::pair<Key, CommandPtr>> commands) {
-  return SelectCommand(std::move(selector),
-                       CommandPtr::UnwrapVector(std::move(commands)))
-      .ToPtr();
+template <typename Key, std::convertible_to<CommandPtr>... CommandPtrs>
+[[nodiscard]]
+CommandPtr Select(std::function<Key()> selector,
+                  std::pair<Key, CommandPtrs>&&... commands) {
+  std::vector<std::pair<Key, std::unique_ptr<Command>>> vec;
+
+  ((void)vec.emplace_back(commands.first, std::move(commands.second).Unwrap()),
+   ...);
+
+  return SelectCommand(std::move(selector), std::move(vec)).ToPtr();
 }
 
+/**
+ * Runs the command supplied by the supplier.
+ *
+ * @param supplier the command supplier
+ * @param requirements the set of requirements for this command
+ */
+[[nodiscard]]
+CommandPtr Defer(wpi::unique_function<CommandPtr()> supplier,
+                 Requirements requirements);
+
+/**
+ * Constructs a command that schedules the command returned from the supplier
+ * when initialized, and ends when it is no longer scheduled. The supplier is
+ * called when the command is initialized.
+ *
+ * @param supplier the command supplier
+ */
+[[nodiscard]]
+CommandPtr DeferredProxy(wpi::unique_function<Command*()> supplier);
+
+/**
+ * Constructs a command that schedules the command returned from the supplier
+ * when initialized, and ends when it is no longer scheduled. The supplier is
+ * called when the command is initialized.
+ *
+ * @param supplier the command supplier
+ */
+[[nodiscard]]
+CommandPtr DeferredProxy(wpi::unique_function<CommandPtr()> supplier);
+
 // Command Groups
 
 namespace impl {
@@ -172,7 +179,7 @@
 /**
  * Create a vector of commands.
  */
-template <typename... Args>
+template <std::convertible_to<CommandPtr>... Args>
 std::vector<CommandPtr> MakeVector(Args&&... args) {
   std::vector<CommandPtr> data;
   data.reserve(sizeof...(Args));
@@ -185,76 +192,86 @@
 /**
  * Runs a group of commands in series, one after the other.
  */
-[[nodiscard]] CommandPtr Sequence(std::vector<CommandPtr>&& commands);
+[[nodiscard]]
+CommandPtr Sequence(std::vector<CommandPtr>&& commands);
 
 /**
  * Runs a group of commands in series, one after the other.
  */
-template <typename... Args>
-[[nodiscard]] CommandPtr Sequence(Args&&... commands) {
-  return Sequence(impl::MakeVector(std::forward<Args>(commands)...));
+template <std::convertible_to<CommandPtr>... CommandPtrs>
+[[nodiscard]]
+CommandPtr Sequence(CommandPtrs&&... commands) {
+  return Sequence(impl::MakeVector(std::forward<CommandPtrs>(commands)...));
 }
 
 /**
  * Runs a group of commands in series, one after the other. Once the last
  * command ends, the group is restarted.
  */
-[[nodiscard]] CommandPtr RepeatingSequence(std::vector<CommandPtr>&& commands);
+[[nodiscard]]
+CommandPtr RepeatingSequence(std::vector<CommandPtr>&& commands);
 
 /**
  * Runs a group of commands in series, one after the other. Once the last
  * command ends, the group is restarted.
  */
-template <typename... Args>
-[[nodiscard]] CommandPtr RepeatingSequence(Args&&... commands) {
-  return RepeatingSequence(impl::MakeVector(std::forward<Args>(commands)...));
+template <std::convertible_to<CommandPtr>... CommandPtrs>
+[[nodiscard]]
+CommandPtr RepeatingSequence(CommandPtrs&&... commands) {
+  return RepeatingSequence(
+      impl::MakeVector(std::forward<CommandPtrs>(commands)...));
 }
 
 /**
  * Runs a group of commands at the same time. Ends once all commands in the
  * group finish.
  */
-[[nodiscard]] CommandPtr Parallel(std::vector<CommandPtr>&& commands);
+[[nodiscard]]
+CommandPtr Parallel(std::vector<CommandPtr>&& commands);
 
 /**
  * Runs a group of commands at the same time. Ends once all commands in the
  * group finish.
  */
-template <typename... Args>
-[[nodiscard]] CommandPtr Parallel(Args&&... commands) {
-  return Parallel(impl::MakeVector(std::forward<Args>(commands)...));
+template <std::convertible_to<CommandPtr>... CommandPtrs>
+[[nodiscard]]
+CommandPtr Parallel(CommandPtrs&&... commands) {
+  return Parallel(impl::MakeVector(std::forward<CommandPtrs>(commands)...));
 }
 
 /**
  * Runs a group of commands at the same time. Ends once any command in the group
  * finishes, and cancels the others.
  */
-[[nodiscard]] CommandPtr Race(std::vector<CommandPtr>&& commands);
+[[nodiscard]]
+CommandPtr Race(std::vector<CommandPtr>&& commands);
 
 /**
  * Runs a group of commands at the same time. Ends once any command in the group
  * finishes, and cancels the others.
  */
-template <typename... Args>
-[[nodiscard]] CommandPtr Race(Args&&... commands) {
-  return Race(impl::MakeVector(std::forward<Args>(commands)...));
+template <std::convertible_to<CommandPtr>... CommandPtrs>
+[[nodiscard]]
+CommandPtr Race(CommandPtrs&&... commands) {
+  return Race(impl::MakeVector(std::forward<CommandPtrs>(commands)...));
 }
 
 /**
  * Runs a group of commands at the same time. Ends once a specific command
  * finishes, and cancels the others.
  */
-[[nodiscard]] CommandPtr Deadline(CommandPtr&& deadline,
-                                  std::vector<CommandPtr>&& others);
+[[nodiscard]]
+CommandPtr Deadline(CommandPtr&& deadline, std::vector<CommandPtr>&& others);
 
 /**
  * Runs a group of commands at the same time. Ends once a specific command
  * finishes, and cancels the others.
  */
-template <typename... Args>
-[[nodiscard]] CommandPtr Deadline(CommandPtr&& deadline, Args&&... commands) {
+template <std::convertible_to<CommandPtr>... CommandPtrs>
+[[nodiscard]]
+CommandPtr Deadline(CommandPtr&& deadline, CommandPtrs&&... commands) {
   return Deadline(std::move(deadline),
-                  impl::MakeVector(std::forward<Args>(commands)...));
+                  impl::MakeVector(std::forward<CommandPtrs>(commands)...));
 }
 
 }  // namespace cmd
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/ConditionalCommand.h b/wpilibNewCommands/src/main/native/include/frc2/command/ConditionalCommand.h
index 5957950..676ee0c 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/ConditionalCommand.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/ConditionalCommand.h
@@ -4,11 +4,12 @@
 
 #pragma once
 
+#include <concepts>
 #include <functional>
 #include <memory>
 #include <utility>
 
-#include "frc2/command/CommandBase.h"
+#include "frc2/command/Command.h"
 #include "frc2/command/CommandHelper.h"
 
 namespace frc2 {
@@ -25,8 +26,7 @@
  *
  * @see ScheduleCommand
  */
-class ConditionalCommand
-    : public CommandHelper<CommandBase, ConditionalCommand> {
+class ConditionalCommand : public CommandHelper<Command, ConditionalCommand> {
  public:
   /**
    * Creates a new ConditionalCommand.
@@ -35,16 +35,14 @@
    * @param onFalse   the command to run if the condition is false
    * @param condition the condition to determine which command to run
    */
-  template <class T1, class T2,
-            typename = std::enable_if_t<
-                std::is_base_of_v<Command, std::remove_reference_t<T1>>>,
-            typename = std::enable_if_t<
-                std::is_base_of_v<Command, std::remove_reference_t<T2>>>>
-  ConditionalCommand(T1&& onTrue, T2&& onFalse, std::function<bool()> condition)
-      : ConditionalCommand(std::make_unique<std::remove_reference_t<T1>>(
-                               std::forward<T1>(onTrue)),
-                           std::make_unique<std::remove_reference_t<T2>>(
-                               std::forward<T2>(onFalse)),
+  template <std::derived_from<Command> Command1,
+            std::derived_from<Command> Command2>
+  ConditionalCommand(Command1&& onTrue, Command2&& onFalse,
+                     std::function<bool()> condition)
+      : ConditionalCommand(std::make_unique<std::decay_t<Command1>>(
+                               std::forward<Command1>(onTrue)),
+                           std::make_unique<std::decay_t<Command2>>(
+                               std::forward<Command2>(onFalse)),
                            condition) {}
 
   /**
@@ -73,6 +71,8 @@
 
   bool RunsWhenDisabled() const override;
 
+  InterruptionBehavior GetInterruptionBehavior() const override;
+
   void InitSendable(wpi::SendableBuilder& builder) override;
 
  private:
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/DeferredCommand.h b/wpilibNewCommands/src/main/native/include/frc2/command/DeferredCommand.h
new file mode 100644
index 0000000..442076d
--- /dev/null
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/DeferredCommand.h
@@ -0,0 +1,61 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#pragma once
+
+#include <memory>
+#include <span>
+
+#include <wpi/FunctionExtras.h>
+
+#include "frc2/command/Command.h"
+#include "frc2/command/CommandHelper.h"
+#include "frc2/command/PrintCommand.h"
+#include "frc2/command/Requirements.h"
+
+namespace frc2 {
+/**
+ * Defers Command construction to runtime. Runs the command returned by the
+ * supplier when this command is initialized, and ends when it ends. Useful for
+ * performing runtime tasks before creating a new command. If this command is
+ * interrupted, it will cancel the command.
+ *
+ * Note that the supplier <i>must</i> create a new Command each call. For
+ * selecting one of a preallocated set of commands, use SelectCommand.
+ *
+ * <p>This class is provided by the NewCommands VendorDep
+ */
+class DeferredCommand : public CommandHelper<Command, DeferredCommand> {
+ public:
+  /**
+   * Creates a new DeferredCommand that runs the supplied command when
+   * initialized, and ends when it ends. Useful for lazily
+   * creating commands at runtime. The supplier will be called each time this
+   * command is initialized. The supplier <i>must</i> create a new Command each
+   * call.
+   *
+   * @param supplier The command supplier
+   * @param requirements The command requirements.
+   *
+   */
+  DeferredCommand(wpi::unique_function<CommandPtr()> supplier,
+                  Requirements requirements);
+
+  DeferredCommand(DeferredCommand&& other) = default;
+
+  void Initialize() override;
+
+  void Execute() override;
+
+  void End(bool interrupted) override;
+
+  bool IsFinished() override;
+
+  void InitSendable(wpi::SendableBuilder& builder) override;
+
+ private:
+  wpi::unique_function<CommandPtr()> m_supplier;
+  std::unique_ptr<Command> m_command;
+};
+}  // namespace frc2
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/FunctionalCommand.h b/wpilibNewCommands/src/main/native/include/frc2/command/FunctionalCommand.h
index ef2b5b9..ac11765 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/FunctionalCommand.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/FunctionalCommand.h
@@ -5,11 +5,10 @@
 #pragma once
 
 #include <functional>
-#include <initializer_list>
-#include <span>
 
-#include "frc2/command/CommandBase.h"
+#include "frc2/command/Command.h"
 #include "frc2/command/CommandHelper.h"
+#include "frc2/command/Requirements.h"
 
 namespace frc2 {
 /**
@@ -21,7 +20,7 @@
  *
  * This class is provided by the NewCommands VendorDep
  */
-class FunctionalCommand : public CommandHelper<CommandBase, FunctionalCommand> {
+class FunctionalCommand : public CommandHelper<Command, FunctionalCommand> {
  public:
   /**
    * Creates a new FunctionalCommand.
@@ -37,23 +36,7 @@
                     std::function<void()> onExecute,
                     std::function<void(bool)> onEnd,
                     std::function<bool()> isFinished,
-                    std::initializer_list<Subsystem*> requirements);
-
-  /**
-   * Creates a new FunctionalCommand.
-   *
-   * @param onInit       the function to run on command initialization
-   * @param onExecute    the function to run on command execution
-   * @param onEnd        the function to run on command end
-   * @param isFinished   the function that determines whether the command has
-   * finished
-   * @param requirements the subsystems required by this command
-   */
-  FunctionalCommand(std::function<void()> onInit,
-                    std::function<void()> onExecute,
-                    std::function<void(bool)> onEnd,
-                    std::function<bool()> isFinished,
-                    std::span<Subsystem* const> requirements = {});
+                    Requirements requirements = {});
 
   FunctionalCommand(FunctionalCommand&& other) = default;
 
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/InstantCommand.h b/wpilibNewCommands/src/main/native/include/frc2/command/InstantCommand.h
index cae0b3e..3ac32ec 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/InstantCommand.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/InstantCommand.h
@@ -5,11 +5,10 @@
 #pragma once
 
 #include <functional>
-#include <initializer_list>
-#include <span>
 
 #include "frc2/command/CommandHelper.h"
 #include "frc2/command/FunctionalCommand.h"
+#include "frc2/command/Requirements.h"
 
 namespace frc2 {
 /**
@@ -28,18 +27,8 @@
    * @param toRun        the Runnable to run
    * @param requirements the subsystems required by this command
    */
-  InstantCommand(std::function<void()> toRun,
-                 std::initializer_list<Subsystem*> requirements);
-
-  /**
-   * Creates a new InstantCommand that runs the given Runnable with the given
-   * requirements.
-   *
-   * @param toRun        the Runnable to run
-   * @param requirements the subsystems required by this command
-   */
   explicit InstantCommand(std::function<void()> toRun,
-                          std::span<Subsystem* const> requirements = {});
+                          Requirements requirements = {});
 
   InstantCommand(InstantCommand&& other) = default;
 
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/MecanumControllerCommand.h b/wpilibNewCommands/src/main/native/include/frc2/command/MecanumControllerCommand.h
index 8ebdbe0..e189ef1 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/MecanumControllerCommand.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/MecanumControllerCommand.h
@@ -4,9 +4,7 @@
 
 #include <cmath>
 #include <functional>
-#include <initializer_list>
 #include <memory>
-#include <span>
 
 #include <frc/Timer.h>
 #include <frc/controller/HolonomicDriveController.h>
@@ -23,8 +21,9 @@
 #include <units/velocity.h>
 #include <units/voltage.h>
 
-#include "CommandBase.h"
-#include "CommandHelper.h"
+#include "frc2/command/Command.h"
+#include "frc2/command/CommandHelper.h"
+#include "frc2/command/Requirements.h"
 
 #pragma once
 
@@ -51,7 +50,7 @@
  * This class is provided by the NewCommands VendorDep
  */
 class MecanumControllerCommand
-    : public CommandHelper<CommandBase, MecanumControllerCommand> {
+    : public CommandHelper<Command, MecanumControllerCommand> {
  public:
   /**
    * Constructs a new MecanumControllerCommand that when executed will follow
@@ -89,20 +88,20 @@
   MecanumControllerCommand(
       frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
       frc::SimpleMotorFeedforward<units::meters> feedforward,
-      frc::MecanumDriveKinematics kinematics, frc2::PIDController xController,
-      frc2::PIDController yController,
+      frc::MecanumDriveKinematics kinematics, frc::PIDController xController,
+      frc::PIDController yController,
       frc::ProfiledPIDController<units::radians> thetaController,
       std::function<frc::Rotation2d()> desiredRotation,
       units::meters_per_second_t maxWheelVelocity,
       std::function<frc::MecanumDriveWheelSpeeds()> currentWheelSpeeds,
-      frc2::PIDController frontLeftController,
-      frc2::PIDController rearLeftController,
-      frc2::PIDController frontRightController,
-      frc2::PIDController rearRightController,
+      frc::PIDController frontLeftController,
+      frc::PIDController rearLeftController,
+      frc::PIDController frontRightController,
+      frc::PIDController rearRightController,
       std::function<void(units::volt_t, units::volt_t, units::volt_t,
                          units::volt_t)>
           output,
-      std::initializer_list<Subsystem*> requirements);
+      Requirements requirements = {});
 
   /**
    * Constructs a new MecanumControllerCommand that when executed will follow
@@ -143,123 +142,19 @@
   MecanumControllerCommand(
       frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
       frc::SimpleMotorFeedforward<units::meters> feedforward,
-      frc::MecanumDriveKinematics kinematics, frc2::PIDController xController,
-      frc2::PIDController yController,
+      frc::MecanumDriveKinematics kinematics, frc::PIDController xController,
+      frc::PIDController yController,
       frc::ProfiledPIDController<units::radians> thetaController,
       units::meters_per_second_t maxWheelVelocity,
       std::function<frc::MecanumDriveWheelSpeeds()> currentWheelSpeeds,
-      frc2::PIDController frontLeftController,
-      frc2::PIDController rearLeftController,
-      frc2::PIDController frontRightController,
-      frc2::PIDController rearRightController,
+      frc::PIDController frontLeftController,
+      frc::PIDController rearLeftController,
+      frc::PIDController frontRightController,
+      frc::PIDController rearRightController,
       std::function<void(units::volt_t, units::volt_t, units::volt_t,
                          units::volt_t)>
           output,
-      std::initializer_list<Subsystem*> requirements);
-
-  /**
-   * Constructs a new MecanumControllerCommand that when executed will follow
-   * the provided trajectory. PID control and feedforward are handled
-   * internally. Outputs are scaled from -12 to 12 as a voltage output to the
-   * motor.
-   *
-   * <p>Note: The controllers will *not* set the outputVolts to zero upon
-   * completion of the path this is left to the user, since it is not
-   * appropriate for paths with nonstationary endstates.
-   *
-   * @param trajectory           The trajectory to follow.
-   * @param pose                 A function that supplies the robot pose,
-   *                             provided by the odometry class.
-   * @param feedforward          The feedforward to use for the drivetrain.
-   * @param kinematics           The kinematics for the robot drivetrain.
-   * @param xController          The Trajectory Tracker PID controller
-   *                             for the robot's x position.
-   * @param yController          The Trajectory Tracker PID controller
-   *                             for the robot's y position.
-   * @param thetaController      The Trajectory Tracker PID controller
-   *                             for angle for the robot.
-   * @param desiredRotation      The angle that the robot should be facing.
-   *                             This is sampled at each time step.
-   * @param maxWheelVelocity     The maximum velocity of a drivetrain wheel.
-   * @param frontLeftController  The front left wheel velocity PID.
-   * @param rearLeftController   The rear left wheel velocity PID.
-   * @param frontRightController The front right wheel velocity PID.
-   * @param rearRightController  The rear right wheel velocity PID.
-   * @param currentWheelSpeeds   A MecanumDriveWheelSpeeds object containing
-   *                             the current wheel speeds.
-   * @param output               The output of the velocity PIDs.
-   * @param requirements         The subsystems to require.
-   */
-  MecanumControllerCommand(
-      frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
-      frc::SimpleMotorFeedforward<units::meters> feedforward,
-      frc::MecanumDriveKinematics kinematics, frc2::PIDController xController,
-      frc2::PIDController yController,
-      frc::ProfiledPIDController<units::radians> thetaController,
-      std::function<frc::Rotation2d()> desiredRotation,
-      units::meters_per_second_t maxWheelVelocity,
-      std::function<frc::MecanumDriveWheelSpeeds()> currentWheelSpeeds,
-      frc2::PIDController frontLeftController,
-      frc2::PIDController rearLeftController,
-      frc2::PIDController frontRightController,
-      frc2::PIDController rearRightController,
-      std::function<void(units::volt_t, units::volt_t, units::volt_t,
-                         units::volt_t)>
-          output,
-      std::span<Subsystem* const> requirements = {});
-
-  /**
-   * Constructs a new MecanumControllerCommand that when executed will follow
-   * the provided trajectory. PID control and feedforward are handled
-   * internally. Outputs are scaled from -12 to 12 as a voltage output to the
-   * motor.
-   *
-   * <p>Note: The controllers will *not* set the outputVolts to zero upon
-   * completion of the path this is left to the user, since it is not
-   * appropriate for paths with nonstationary endstates.
-   *
-   * <p>Note 2: The final rotation of the robot will be set to the rotation of
-   * the final pose in the trajectory. The robot will not follow the rotations
-   * from the poses at each timestep. If alternate rotation behavior is desired,
-   * the other constructor with a supplier for rotation should be used.
-   *
-   * @param trajectory           The trajectory to follow.
-   * @param pose                 A function that supplies the robot pose,
-   *                             provided by the odometry class.
-   * @param feedforward          The feedforward to use for the drivetrain.
-   * @param kinematics           The kinematics for the robot drivetrain.
-   * @param xController          The Trajectory Tracker PID controller
-   *                             for the robot's x position.
-   * @param yController          The Trajectory Tracker PID controller
-   *                             for the robot's y position.
-   * @param thetaController      The Trajectory Tracker PID controller
-   *                             for angle for the robot.
-   * @param maxWheelVelocity     The maximum velocity of a drivetrain wheel.
-   * @param frontLeftController  The front left wheel velocity PID.
-   * @param rearLeftController   The rear left wheel velocity PID.
-   * @param frontRightController The front right wheel velocity PID.
-   * @param rearRightController  The rear right wheel velocity PID.
-   * @param currentWheelSpeeds   A MecanumDriveWheelSpeeds object containing
-   *                             the current wheel speeds.
-   * @param output               The output of the velocity PIDs.
-   * @param requirements         The subsystems to require.
-   */
-  MecanumControllerCommand(
-      frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
-      frc::SimpleMotorFeedforward<units::meters> feedforward,
-      frc::MecanumDriveKinematics kinematics, frc2::PIDController xController,
-      frc2::PIDController yController,
-      frc::ProfiledPIDController<units::radians> thetaController,
-      units::meters_per_second_t maxWheelVelocity,
-      std::function<frc::MecanumDriveWheelSpeeds()> currentWheelSpeeds,
-      frc2::PIDController frontLeftController,
-      frc2::PIDController rearLeftController,
-      frc2::PIDController frontRightController,
-      frc2::PIDController rearRightController,
-      std::function<void(units::volt_t, units::volt_t, units::volt_t,
-                         units::volt_t)>
-          output,
-      std::span<Subsystem* const> requirements = {});
+      Requirements requirements = {});
 
   /**
    * Constructs a new MecanumControllerCommand that when executed will follow
@@ -288,8 +183,8 @@
    */
   MecanumControllerCommand(
       frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
-      frc::MecanumDriveKinematics kinematics, frc2::PIDController xController,
-      frc2::PIDController yController,
+      frc::MecanumDriveKinematics kinematics, frc::PIDController xController,
+      frc::PIDController yController,
       frc::ProfiledPIDController<units::radians> thetaController,
       std::function<frc::Rotation2d()> desiredRotation,
       units::meters_per_second_t maxWheelVelocity,
@@ -297,7 +192,7 @@
                          units::meters_per_second_t,
                          units::meters_per_second_t)>
           output,
-      std::initializer_list<Subsystem*> requirements);
+      Requirements requirements);
 
   /**
    * Constructs a new MecanumControllerCommand that when executed will follow
@@ -329,93 +224,15 @@
    */
   MecanumControllerCommand(
       frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
-      frc::MecanumDriveKinematics kinematics, frc2::PIDController xController,
-      frc2::PIDController yController,
+      frc::MecanumDriveKinematics kinematics, frc::PIDController xController,
+      frc::PIDController yController,
       frc::ProfiledPIDController<units::radians> thetaController,
       units::meters_per_second_t maxWheelVelocity,
       std::function<void(units::meters_per_second_t, units::meters_per_second_t,
                          units::meters_per_second_t,
                          units::meters_per_second_t)>
           output,
-      std::initializer_list<Subsystem*> requirements);
-
-  /**
-   * Constructs a new MecanumControllerCommand that when executed will follow
-   * the provided trajectory. The user should implement a velocity PID on the
-   * desired output wheel velocities.
-   *
-   * <p>Note: The controllers will *not* set the outputVolts to zero upon
-   * completion of the path - this is left to the user, since it is not
-   * appropriate for paths with nonstationary end-states.
-   *
-   * @param trajectory       The trajectory to follow.
-   * @param pose             A function that supplies the robot pose - use one
-   * of the odometry classes to provide this.
-   * @param kinematics       The kinematics for the robot drivetrain.
-   * @param xController      The Trajectory Tracker PID controller
-   *                         for the robot's x position.
-   * @param yController      The Trajectory Tracker PID controller
-   *                         for the robot's y position.
-   * @param thetaController  The Trajectory Tracker PID controller
-   *                         for angle for the robot.
-   * @param desiredRotation  The angle that the robot should be facing.
-   *                         This is sampled at every time step.
-   * @param maxWheelVelocity The maximum velocity of a drivetrain wheel.
-   * @param output           The output of the position PIDs.
-   * @param requirements     The subsystems to require.
-   */
-  MecanumControllerCommand(
-      frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
-      frc::MecanumDriveKinematics kinematics, frc2::PIDController xController,
-      frc2::PIDController yController,
-      frc::ProfiledPIDController<units::radians> thetaController,
-      std::function<frc::Rotation2d()> desiredRotation,
-      units::meters_per_second_t maxWheelVelocity,
-      std::function<void(units::meters_per_second_t, units::meters_per_second_t,
-                         units::meters_per_second_t,
-                         units::meters_per_second_t)>
-          output,
-      std::span<Subsystem* const> requirements = {});
-
-  /**
-   * Constructs a new MecanumControllerCommand that when executed will follow
-   * the provided trajectory. The user should implement a velocity PID on the
-   * desired output wheel velocities.
-   *
-   * <p>Note: The controllers will *not* set the outputVolts to zero upon
-   * completion of the path - this is left to the user, since it is not
-   * appropriate for paths with nonstationary end-states.
-   *
-   * <p>Note2: The final rotation of the robot will be set to the rotation of
-   * the final pose in the trajectory. The robot will not follow the rotations
-   * from the poses at each timestep. If alternate rotation behavior is desired,
-   * the other constructor with a supplier for rotation should be used.
-   *
-   * @param trajectory       The trajectory to follow.
-   * @param pose             A function that supplies the robot pose - use one
-   * of the odometry classes to provide this.
-   * @param kinematics       The kinematics for the robot drivetrain.
-   * @param xController      The Trajectory Tracker PID controller
-   *                         for the robot's x position.
-   * @param yController      The Trajectory Tracker PID controller
-   *                         for the robot's y position.
-   * @param thetaController  The Trajectory Tracker PID controller
-   *                         for angle for the robot.
-   * @param maxWheelVelocity The maximum velocity of a drivetrain wheel.
-   * @param output           The output of the position PIDs.
-   * @param requirements     The subsystems to require.
-   */
-  MecanumControllerCommand(
-      frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
-      frc::MecanumDriveKinematics kinematics, frc2::PIDController xController,
-      frc2::PIDController yController,
-      frc::ProfiledPIDController<units::radians> thetaController,
-      units::meters_per_second_t maxWheelVelocity,
-      std::function<void(units::meters_per_second_t, units::meters_per_second_t,
-                         units::meters_per_second_t,
-                         units::meters_per_second_t)>
-          output,
-      std::span<Subsystem* const> requirements = {});
+      Requirements requirements = {});
 
   void Initialize() override;
 
@@ -433,10 +250,10 @@
   frc::HolonomicDriveController m_controller;
   std::function<frc::Rotation2d()> m_desiredRotation;
   const units::meters_per_second_t m_maxWheelVelocity;
-  std::unique_ptr<frc2::PIDController> m_frontLeftController;
-  std::unique_ptr<frc2::PIDController> m_rearLeftController;
-  std::unique_ptr<frc2::PIDController> m_frontRightController;
-  std::unique_ptr<frc2::PIDController> m_rearRightController;
+  std::unique_ptr<frc::PIDController> m_frontLeftController;
+  std::unique_ptr<frc::PIDController> m_rearLeftController;
+  std::unique_ptr<frc::PIDController> m_frontRightController;
+  std::unique_ptr<frc::PIDController> m_rearRightController;
   std::function<frc::MecanumDriveWheelSpeeds()> m_currentWheelSpeeds;
   std::function<void(units::meters_per_second_t, units::meters_per_second_t,
                      units::meters_per_second_t, units::meters_per_second_t)>
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/NotifierCommand.h b/wpilibNewCommands/src/main/native/include/frc2/command/NotifierCommand.h
index 607799b..d17867b 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/NotifierCommand.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/NotifierCommand.h
@@ -5,14 +5,13 @@
 #pragma once
 
 #include <functional>
-#include <initializer_list>
-#include <span>
 
 #include <frc/Notifier.h>
 #include <units/time.h>
 
-#include "frc2/command/CommandBase.h"
+#include "frc2/command/Command.h"
 #include "frc2/command/CommandHelper.h"
+#include "frc2/command/Requirements.h"
 
 namespace frc2 {
 /**
@@ -27,7 +26,7 @@
  *
  * This class is provided by the NewCommands VendorDep
  */
-class NotifierCommand : public CommandHelper<CommandBase, NotifierCommand> {
+class NotifierCommand : public CommandHelper<Command, NotifierCommand> {
  public:
   /**
    * Creates a new NotifierCommand.
@@ -37,17 +36,7 @@
    * @param requirements the subsystems required by this command
    */
   NotifierCommand(std::function<void()> toRun, units::second_t period,
-                  std::initializer_list<Subsystem*> requirements);
-
-  /**
-   * Creates a new NotifierCommand.
-   *
-   * @param toRun        the runnable for the notifier to run
-   * @param period       the period at which the notifier should run
-   * @param requirements the subsystems required by this command
-   */
-  NotifierCommand(std::function<void()> toRun, units::second_t period,
-                  std::span<Subsystem* const> requirements = {});
+                  Requirements requirements = {});
 
   NotifierCommand(NotifierCommand&& other);
 
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/PIDCommand.h b/wpilibNewCommands/src/main/native/include/frc2/command/PIDCommand.h
index 818c50b..bede6d0 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/PIDCommand.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/PIDCommand.h
@@ -5,13 +5,12 @@
 #pragma once
 
 #include <functional>
-#include <initializer_list>
-#include <span>
 
 #include <frc/controller/PIDController.h>
 
-#include "frc2/command/CommandBase.h"
+#include "frc2/command/Command.h"
 #include "frc2/command/CommandHelper.h"
+#include "frc2/command/Requirements.h"
 
 namespace frc2 {
 /**
@@ -24,7 +23,7 @@
  *
  * @see PIDController
  */
-class PIDCommand : public CommandHelper<CommandBase, PIDCommand> {
+class PIDCommand : public CommandHelper<Command, PIDCommand> {
  public:
   /**
    * Creates a new PIDCommand, which controls the given output with a
@@ -36,27 +35,11 @@
    * @param useOutput         the controller's output
    * @param requirements      the subsystems required by this command
    */
-  PIDCommand(PIDController controller,
+  PIDCommand(frc::PIDController controller,
              std::function<double()> measurementSource,
              std::function<double()> setpointSource,
              std::function<void(double)> useOutput,
-             std::initializer_list<Subsystem*> requirements);
-
-  /**
-   * Creates a new PIDCommand, which controls the given output with a
-   * PIDController.
-   *
-   * @param controller        the controller that controls the output.
-   * @param measurementSource the measurement of the process variable
-   * @param setpointSource   the controller's reference (aka setpoint)
-   * @param useOutput         the controller's output
-   * @param requirements      the subsystems required by this command
-   */
-  PIDCommand(PIDController controller,
-             std::function<double()> measurementSource,
-             std::function<double()> setpointSource,
-             std::function<void(double)> useOutput,
-             std::span<Subsystem* const> requirements = {});
+             Requirements requirements = {});
 
   /**
    * Creates a new PIDCommand, which controls the given output with a
@@ -68,25 +51,10 @@
    * @param useOutput         the controller's output
    * @param requirements      the subsystems required by this command
    */
-  PIDCommand(PIDController controller,
+  PIDCommand(frc::PIDController controller,
              std::function<double()> measurementSource, double setpoint,
              std::function<void(double)> useOutput,
-             std::initializer_list<Subsystem*> requirements);
-
-  /**
-   * Creates a new PIDCommand, which controls the given output with a
-   * PIDController with a constant setpoint.
-   *
-   * @param controller        the controller that controls the output.
-   * @param measurementSource the measurement of the process variable
-   * @param setpoint         the controller's setpoint (aka setpoint)
-   * @param useOutput         the controller's output
-   * @param requirements      the subsystems required by this command
-   */
-  PIDCommand(PIDController controller,
-             std::function<double()> measurementSource, double setpoint,
-             std::function<void(double)> useOutput,
-             std::span<Subsystem* const> requirements = {});
+             Requirements requirements = {});
 
   PIDCommand(PIDCommand&& other) = default;
 
@@ -103,10 +71,10 @@
    *
    * @return The PIDController
    */
-  PIDController& GetController();
+  frc::PIDController& GetController();
 
  protected:
-  PIDController m_controller;
+  frc::PIDController m_controller;
   std::function<double()> m_measurement;
   std::function<double()> m_setpoint;
   std::function<void(double)> m_useOutput;
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/PIDSubsystem.h b/wpilibNewCommands/src/main/native/include/frc2/command/PIDSubsystem.h
index 426e3ec..af61430 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/PIDSubsystem.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/PIDSubsystem.h
@@ -25,7 +25,8 @@
    * @param controller the PIDController to use
    * @param initialPosition the initial setpoint of the subsystem
    */
-  explicit PIDSubsystem(PIDController controller, double initialPosition = 0);
+  explicit PIDSubsystem(frc::PIDController controller,
+                        double initialPosition = 0);
 
   void Periodic() override;
 
@@ -65,10 +66,10 @@
    *
    * @return The controller.
    */
-  PIDController& GetController();
+  frc::PIDController& GetController();
 
  protected:
-  PIDController m_controller;
+  frc::PIDController m_controller;
   bool m_enabled{false};
 
   /**
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/ParallelCommandGroup.h b/wpilibNewCommands/src/main/native/include/frc2/command/ParallelCommandGroup.h
index 943b62f..bb33a05 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/ParallelCommandGroup.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/ParallelCommandGroup.h
@@ -9,11 +9,15 @@
 #pragma warning(disable : 4521)
 #endif
 
+#include <concepts>
 #include <memory>
+#include <type_traits>
 #include <utility>
 #include <vector>
 
-#include "frc2/command/CommandGroupBase.h"
+#include <wpi/DecayedDerivedFrom.h>
+
+#include "frc2/command/CommandBase.h"
 #include "frc2/command/CommandHelper.h"
 
 namespace frc2 {
@@ -29,7 +33,7 @@
  * This class is provided by the NewCommands VendorDep
  */
 class ParallelCommandGroup
-    : public CommandHelper<CommandGroupBase, ParallelCommandGroup> {
+    : public CommandHelper<Command, ParallelCommandGroup> {
  public:
   /**
    * Creates a new ParallelCommandGroup. The given commands will be executed
@@ -50,11 +54,9 @@
    *
    * @param commands the commands to include in this composition.
    */
-  template <class... Types,
-            typename = std::enable_if_t<std::conjunction_v<
-                std::is_base_of<Command, std::remove_reference_t<Types>>...>>>
-  explicit ParallelCommandGroup(Types&&... commands) {
-    AddCommands(std::forward<Types>(commands)...);
+  template <wpi::DecayedDerivedFrom<Command>... Commands>
+  explicit ParallelCommandGroup(Commands&&... commands) {
+    AddCommands(std::forward<Commands>(commands)...);
   }
 
   ParallelCommandGroup(ParallelCommandGroup&& other) = default;
@@ -65,13 +67,16 @@
   // Prevent template expansion from emulating copy ctor
   ParallelCommandGroup(ParallelCommandGroup&) = delete;
 
-  template <class... Types,
-            typename = std::enable_if_t<std::conjunction_v<
-                std::is_base_of<Command, std::remove_reference_t<Types>>...>>>
-  void AddCommands(Types&&... commands) {
+  /**
+   * Adds the given commands to the group.
+   *
+   * @param commands Commands to add to the group.
+   */
+  template <wpi::DecayedDerivedFrom<Command>... Commands>
+  void AddCommands(Commands&&... commands) {
     std::vector<std::unique_ptr<Command>> foo;
-    ((void)foo.emplace_back(std::make_unique<std::remove_reference_t<Types>>(
-         std::forward<Types>(commands))),
+    ((void)foo.emplace_back(std::make_unique<std::decay_t<Commands>>(
+         std::forward<Commands>(commands))),
      ...);
     AddCommands(std::move(foo));
   }
@@ -89,7 +94,7 @@
   Command::InterruptionBehavior GetInterruptionBehavior() const override;
 
  private:
-  void AddCommands(std::vector<std::unique_ptr<Command>>&& commands) final;
+  void AddCommands(std::vector<std::unique_ptr<Command>>&& commands);
 
   std::vector<std::pair<std::unique_ptr<Command>, bool>> m_commands;
   bool m_runWhenDisabled{true};
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/ParallelDeadlineGroup.h b/wpilibNewCommands/src/main/native/include/frc2/command/ParallelDeadlineGroup.h
index cfe4b3e..9e253fb 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/ParallelDeadlineGroup.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/ParallelDeadlineGroup.h
@@ -9,11 +9,15 @@
 #pragma warning(disable : 4521)
 #endif
 
+#include <concepts>
 #include <memory>
+#include <type_traits>
 #include <utility>
 #include <vector>
 
-#include "frc2/command/CommandGroupBase.h"
+#include <wpi/DecayedDerivedFrom.h>
+
+#include "frc2/command/CommandBase.h"
 #include "frc2/command/CommandHelper.h"
 
 namespace frc2 {
@@ -30,7 +34,7 @@
  * This class is provided by the NewCommands VendorDep
  */
 class ParallelDeadlineGroup
-    : public CommandHelper<CommandGroupBase, ParallelDeadlineGroup> {
+    : public CommandHelper<Command, ParallelDeadlineGroup> {
  public:
   /**
    * Creates a new ParallelDeadlineGroup. The given commands (including the
@@ -54,15 +58,11 @@
    * @param deadline the command that determines when the composition ends
    * @param commands the commands to be executed
    */
-  template <class T, class... Types,
-            typename = std::enable_if_t<
-                std::is_base_of_v<Command, std::remove_reference_t<T>>>,
-            typename = std::enable_if_t<std::conjunction_v<
-                std::is_base_of<Command, std::remove_reference_t<Types>>...>>>
-  explicit ParallelDeadlineGroup(T&& deadline, Types&&... commands) {
-    SetDeadline(std::make_unique<std::remove_reference_t<T>>(
-        std::forward<T>(deadline)));
-    AddCommands(std::forward<Types>(commands)...);
+  template <wpi::DecayedDerivedFrom<Command> T,
+            wpi::DecayedDerivedFrom<Command>... Commands>
+  explicit ParallelDeadlineGroup(T&& deadline, Commands&&... commands) {
+    SetDeadline(std::make_unique<std::decay_t<T>>(std::forward<T>(deadline)));
+    AddCommands(std::forward<Commands>(commands)...);
   }
 
   ParallelDeadlineGroup(ParallelDeadlineGroup&& other) = default;
@@ -73,13 +73,16 @@
   // Prevent template expansion from emulating copy ctor
   ParallelDeadlineGroup(ParallelDeadlineGroup&) = delete;
 
-  template <class... Types,
-            typename = std::enable_if_t<std::conjunction_v<
-                std::is_base_of<Command, std::remove_reference_t<Types>>...>>>
-  void AddCommands(Types&&... commands) {
+  /**
+   * Adds the given commands to the group.
+   *
+   * @param commands Commands to add to the group.
+   */
+  template <wpi::DecayedDerivedFrom<Command>... Commands>
+  void AddCommands(Commands&&... commands) {
     std::vector<std::unique_ptr<Command>> foo;
-    ((void)foo.emplace_back(std::make_unique<std::remove_reference_t<Types>>(
-         std::forward<Types>(commands))),
+    ((void)foo.emplace_back(std::make_unique<std::decay_t<Commands>>(
+         std::forward<Commands>(commands))),
      ...);
     AddCommands(std::move(foo));
   }
@@ -99,7 +102,7 @@
   void InitSendable(wpi::SendableBuilder& builder) override;
 
  private:
-  void AddCommands(std::vector<std::unique_ptr<Command>>&& commands) final;
+  void AddCommands(std::vector<std::unique_ptr<Command>>&& commands);
 
   void SetDeadline(std::unique_ptr<Command>&& deadline);
 
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/ParallelRaceGroup.h b/wpilibNewCommands/src/main/native/include/frc2/command/ParallelRaceGroup.h
index d5412cd..caefa48 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/ParallelRaceGroup.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/ParallelRaceGroup.h
@@ -9,11 +9,15 @@
 #pragma warning(disable : 4521)
 #endif
 
+#include <concepts>
 #include <memory>
+#include <type_traits>
 #include <utility>
 #include <vector>
 
-#include "frc2/command/CommandGroupBase.h"
+#include <wpi/DecayedDerivedFrom.h>
+
+#include "frc2/command/CommandBase.h"
 #include "frc2/command/CommandHelper.h"
 
 namespace frc2 {
@@ -28,8 +32,7 @@
  *
  * This class is provided by the NewCommands VendorDep
  */
-class ParallelRaceGroup
-    : public CommandHelper<CommandGroupBase, ParallelRaceGroup> {
+class ParallelRaceGroup : public CommandHelper<Command, ParallelRaceGroup> {
  public:
   /**
    * Creates a new ParallelCommandRace. The given commands will be executed
@@ -40,11 +43,9 @@
    */
   explicit ParallelRaceGroup(std::vector<std::unique_ptr<Command>>&& commands);
 
-  template <class... Types,
-            typename = std::enable_if_t<std::conjunction_v<
-                std::is_base_of<Command, std::remove_reference_t<Types>>...>>>
-  explicit ParallelRaceGroup(Types&&... commands) {
-    AddCommands(std::forward<Types>(commands)...);
+  template <wpi::DecayedDerivedFrom<Command>... Commands>
+  explicit ParallelRaceGroup(Commands&&... commands) {
+    AddCommands(std::forward<Commands>(commands)...);
   }
 
   ParallelRaceGroup(ParallelRaceGroup&& other) = default;
@@ -55,11 +56,16 @@
   // Prevent template expansion from emulating copy ctor
   ParallelRaceGroup(ParallelRaceGroup&) = delete;
 
-  template <class... Types>
-  void AddCommands(Types&&... commands) {
+  /**
+   * Adds the given commands to the group.
+   *
+   * @param commands Commands to add to the group.
+   */
+  template <wpi::DecayedDerivedFrom<Command>... Commands>
+  void AddCommands(Commands&&... commands) {
     std::vector<std::unique_ptr<Command>> foo;
-    ((void)foo.emplace_back(std::make_unique<std::remove_reference_t<Types>>(
-         std::forward<Types>(commands))),
+    ((void)foo.emplace_back(std::make_unique<std::decay_t<Commands>>(
+         std::forward<Commands>(commands))),
      ...);
     AddCommands(std::move(foo));
   }
@@ -77,7 +83,7 @@
   Command::InterruptionBehavior GetInterruptionBehavior() const override;
 
  private:
-  void AddCommands(std::vector<std::unique_ptr<Command>>&& commands) final;
+  void AddCommands(std::vector<std::unique_ptr<Command>>&& commands);
 
   std::vector<std::unique_ptr<Command>> m_commands;
   bool m_runWhenDisabled{true};
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/PerpetualCommand.h b/wpilibNewCommands/src/main/native/include/frc2/command/PerpetualCommand.h
deleted file mode 100644
index 297cb64..0000000
--- a/wpilibNewCommands/src/main/native/include/frc2/command/PerpetualCommand.h
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#pragma once
-
-#ifdef _WIN32
-#pragma warning(push)
-#pragma warning(disable : 4521)
-#endif
-
-#include <memory>
-#include <utility>
-
-#include "frc2/command/CommandBase.h"
-#include "frc2/command/CommandHelper.h"
-
-namespace frc2 {
-/**
- * A command that runs another command in perpetuity, ignoring that command's
- * end conditions.  While this class does not extend frc2::CommandGroupBase,
- * it is still considered a CommandGroup, as it allows one to compose another
- * command within it; the command instances that are passed to it cannot be
- * added to any other groups, or scheduled individually.
- *
- * <p>As a rule, CommandGroups require the union of the requirements of their
- * component commands.
- *
- * This class is provided by the NewCommands VendorDep
- *
- * @deprecated PerpetualCommand violates the assumption that execute() doesn't
-get called after isFinished() returns true -- an assumption that should be
-valid. This was unsafe/undefined behavior from the start, and RepeatCommand
-provides an easy way to achieve similar end results with slightly different (and
-safe) semantics.
- */
-class PerpetualCommand : public CommandHelper<CommandBase, PerpetualCommand> {
- public:
-  /**
-   * Creates a new PerpetualCommand.  Will run another command in perpetuity,
-   * ignoring that command's end conditions, unless this command itself is
-   * interrupted.
-   *
-   * @param command the command to run perpetually
-   */
-  WPI_DEPRECATED(
-      "PerpetualCommand violates the assumption that execute() doesn't get "
-      "called after isFinished() returns true -- an assumption that should be "
-      "valid."
-      "This was unsafe/undefined behavior from the start, and RepeatCommand "
-      "provides an easy way to achieve similar end results with slightly "
-      "different (and safe) semantics.")
-  explicit PerpetualCommand(std::unique_ptr<Command>&& command);
-  WPI_IGNORE_DEPRECATED
-
-  /**
-   * Creates a new PerpetualCommand.  Will run another command in perpetuity,
-   * ignoring that command's end conditions, unless this command itself is
-   * interrupted.
-   *
-   * @param command the command to run perpetually
-   */
-  template <class T, typename = std::enable_if_t<std::is_base_of_v<
-                         Command, std::remove_reference_t<T>>>>
-  WPI_DEPRECATED(
-      "PerpetualCommand violates the assumption that execute() doesn't get "
-      "called after isFinished() returns true -- an assumption that should be "
-      "valid."
-      "This was unsafe/undefined behavior from the start, and RepeatCommand "
-      "provides an easy way to achieve similar end results with slightly "
-      "different (and safe) semantics.")
-  explicit PerpetualCommand(T&& command)
-      : PerpetualCommand(std::make_unique<std::remove_reference_t<T>>(
-            std::forward<T>(command))) {}
-  WPI_UNIGNORE_DEPRECATED
-
-  PerpetualCommand(PerpetualCommand&& other) = default;
-
-  // No copy constructors for command groups
-  PerpetualCommand(const PerpetualCommand& other) = delete;
-
-  // Prevent template expansion from emulating copy ctor
-  PerpetualCommand(PerpetualCommand&) = delete;
-
-  void Initialize() override;
-
-  void Execute() override;
-
-  void End(bool interrupted) override;
-
- private:
-  std::unique_ptr<Command> m_command;
-};
-}  // namespace frc2
-
-#ifdef _WIN32
-#pragma warning(pop)
-#endif
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/ProfiledPIDCommand.h b/wpilibNewCommands/src/main/native/include/frc2/command/ProfiledPIDCommand.h
index 3fbe726..9ea5db5 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/ProfiledPIDCommand.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/ProfiledPIDCommand.h
@@ -5,15 +5,14 @@
 #pragma once
 
 #include <functional>
-#include <initializer_list>
-#include <span>
 #include <utility>
 
 #include <frc/controller/ProfiledPIDController.h>
 #include <units/time.h>
 
-#include "frc2/command/CommandBase.h"
+#include "frc2/command/Command.h"
 #include "frc2/command/CommandHelper.h"
+#include "frc2/command/Requirements.h"
 
 namespace frc2 {
 /**
@@ -28,7 +27,7 @@
  */
 template <class Distance>
 class ProfiledPIDCommand
-    : public CommandHelper<CommandBase, ProfiledPIDCommand<Distance>> {
+    : public CommandHelper<Command, ProfiledPIDCommand<Distance>> {
   using Distance_t = units::unit_t<Distance>;
   using Velocity =
       units::compound_unit<Distance, units::inverse<units::seconds>>;
@@ -50,29 +49,7 @@
                      std::function<Distance_t()> measurementSource,
                      std::function<State()> goalSource,
                      std::function<void(double, State)> useOutput,
-                     std::initializer_list<Subsystem*> requirements)
-      : m_controller{controller},
-        m_measurement{std::move(measurementSource)},
-        m_goal{std::move(goalSource)},
-        m_useOutput{std::move(useOutput)} {
-    this->AddRequirements(requirements);
-  }
-
-  /**
-   * Creates a new PIDCommand, which controls the given output with a
-   * ProfiledPIDController.
-   *
-   * @param controller        the controller that controls the output.
-   * @param measurementSource the measurement of the process variable
-   * @param goalSource   the controller's goal
-   * @param useOutput         the controller's output
-   * @param requirements      the subsystems required by this command
-   */
-  ProfiledPIDCommand(frc::ProfiledPIDController<Distance> controller,
-                     std::function<Distance_t()> measurementSource,
-                     std::function<State()> goalSource,
-                     std::function<void(double, State)> useOutput,
-                     std::span<Subsystem* const> requirements = {})
+                     Requirements requirements = {})
       : m_controller{controller},
         m_measurement{std::move(measurementSource)},
         m_goal{std::move(goalSource)},
@@ -94,29 +71,7 @@
                      std::function<Distance_t()> measurementSource,
                      std::function<Distance_t()> goalSource,
                      std::function<void(double, State)> useOutput,
-                     std::initializer_list<Subsystem*> requirements)
-      : ProfiledPIDCommand(
-            controller, measurementSource,
-            [goalSource = std::move(goalSource)]() {
-              return State{goalSource(), Velocity_t{0}};
-            },
-            useOutput, requirements) {}
-
-  /**
-   * Creates a new PIDCommand, which controls the given output with a
-   * ProfiledPIDController.
-   *
-   * @param controller        the controller that controls the output.
-   * @param measurementSource the measurement of the process variable
-   * @param goalSource   the controller's goal
-   * @param useOutput         the controller's output
-   * @param requirements      the subsystems required by this command
-   */
-  ProfiledPIDCommand(frc::ProfiledPIDController<Distance> controller,
-                     std::function<Distance_t()> measurementSource,
-                     std::function<Distance_t()> goalSource,
-                     std::function<void(double, State)> useOutput,
-                     std::span<Subsystem* const> requirements = {})
+                     Requirements requirements = {})
       : ProfiledPIDCommand(
             controller, measurementSource,
             [goalSource = std::move(goalSource)]() {
@@ -137,25 +92,7 @@
   ProfiledPIDCommand(frc::ProfiledPIDController<Distance> controller,
                      std::function<Distance_t()> measurementSource, State goal,
                      std::function<void(double, State)> useOutput,
-                     std::initializer_list<Subsystem*> requirements)
-      : ProfiledPIDCommand(
-            controller, measurementSource, [goal] { return goal; }, useOutput,
-            requirements) {}
-
-  /**
-   * Creates a new PIDCommand, which controls the given output with a
-   * ProfiledPIDController with a constant goal.
-   *
-   * @param controller        the controller that controls the output.
-   * @param measurementSource the measurement of the process variable
-   * @param goal         the controller's goal
-   * @param useOutput         the controller's output
-   * @param requirements      the subsystems required by this command
-   */
-  ProfiledPIDCommand(frc::ProfiledPIDController<Distance> controller,
-                     std::function<Distance_t()> measurementSource, State goal,
-                     std::function<void(double, State)> useOutput,
-                     std::span<Subsystem* const> requirements = {})
+                     Requirements requirements = {})
       : ProfiledPIDCommand(
             controller, measurementSource, [goal] { return goal; }, useOutput,
             requirements) {}
@@ -174,26 +111,7 @@
                      std::function<Distance_t()> measurementSource,
                      Distance_t goal,
                      std::function<void(double, State)> useOutput,
-                     std::initializer_list<Subsystem*> requirements)
-      : ProfiledPIDCommand(
-            controller, measurementSource, [goal] { return goal; }, useOutput,
-            requirements) {}
-
-  /**
-   * Creates a new PIDCommand, which controls the given output with a
-   * ProfiledPIDController with a constant goal.
-   *
-   * @param controller        the controller that controls the output.
-   * @param measurementSource the measurement of the process variable
-   * @param goal         the controller's goal
-   * @param useOutput         the controller's output
-   * @param requirements      the subsystems required by this command
-   */
-  ProfiledPIDCommand(frc::ProfiledPIDController<Distance> controller,
-                     std::function<Distance_t()> measurementSource,
-                     Distance_t goal,
-                     std::function<void(double, State)> useOutput,
-                     std::span<Subsystem* const> requirements = {})
+                     Requirements requirements = {})
       : ProfiledPIDCommand(
             controller, measurementSource, [goal] { return goal; }, useOutput,
             requirements) {}
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/ProxyCommand.h b/wpilibNewCommands/src/main/native/include/frc2/command/ProxyCommand.h
index b4a00a5..3b7eecc 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/ProxyCommand.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/ProxyCommand.h
@@ -5,13 +5,11 @@
 #pragma once
 
 #include <memory>
-#include <span>
 
 #include <wpi/FunctionExtras.h>
 
-#include "frc2/command/CommandBase.h"
+#include "frc2/command/Command.h"
 #include "frc2/command/CommandHelper.h"
-#include "frc2/command/SetUtilities.h"
 
 namespace frc2 {
 /**
@@ -21,7 +19,7 @@
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-class ProxyCommand : public CommandHelper<CommandBase, ProxyCommand> {
+class ProxyCommand : public CommandHelper<Command, ProxyCommand> {
  public:
   /**
    * Creates a new ProxyCommand that schedules the supplied command when
@@ -33,6 +31,15 @@
   explicit ProxyCommand(wpi::unique_function<Command*()> supplier);
 
   /**
+   * Creates a new ProxyCommand that schedules the supplied command when
+   * initialized, and ends when it is no longer scheduled. Useful for lazily
+   * creating commands at runtime.
+   *
+   * @param supplier the command supplier
+   */
+  explicit ProxyCommand(wpi::unique_function<CommandPtr()> supplier);
+
+  /**
    * Creates a new ProxyCommand that schedules the given command when
    * initialized, and ends when it is no longer scheduled.
    *
@@ -57,8 +64,6 @@
 
   void End(bool interrupted) override;
 
-  void Execute() override;
-
   bool IsFinished() override;
 
   void InitSendable(wpi::SendableBuilder& builder) override;
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/ProxyScheduleCommand.h b/wpilibNewCommands/src/main/native/include/frc2/command/ProxyScheduleCommand.h
deleted file mode 100644
index f79b549..0000000
--- a/wpilibNewCommands/src/main/native/include/frc2/command/ProxyScheduleCommand.h
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#pragma once
-
-#include <memory>
-#include <span>
-
-#include <wpi/SmallVector.h>
-#include <wpi/deprecated.h>
-
-#include "frc2/command/CommandBase.h"
-#include "frc2/command/CommandHelper.h"
-#include "frc2/command/SetUtilities.h"
-
-namespace frc2 {
-/**
- * Schedules the given commands when this command is initialized, and ends when
- * all the commands are no longer scheduled.  Useful for forking off from
- * CommandGroups.  If this command is interrupted, it will cancel all of the
- * commands.
- *
- * This class is provided by the NewCommands VendorDep
- */
-class ProxyScheduleCommand
-    : public CommandHelper<CommandBase, ProxyScheduleCommand> {
- public:
-  /**
-   * Creates a new ProxyScheduleCommand that schedules the given commands when
-   * initialized, and ends when they are all no longer scheduled.
-   *
-   * @param toSchedule the commands to schedule
-   * @deprecated Replace with {@link ProxyCommand},
-   * composing multiple of them in a {@link ParallelRaceGroup} if needed.
-   */
-  WPI_DEPRECATED("Replace with ProxyCommand")
-  explicit ProxyScheduleCommand(std::span<Command* const> toSchedule);
-
-  explicit ProxyScheduleCommand(Command* toSchedule);
-
-  ProxyScheduleCommand(ProxyScheduleCommand&& other) = default;
-
-  void Initialize() override;
-
-  void End(bool interrupted) override;
-
-  void Execute() override;
-
-  bool IsFinished() override;
-
- private:
-  wpi::SmallVector<Command*, 4> m_toSchedule;
-  std::unique_ptr<Command> m_owning;
-  bool m_finished{false};
-};
-}  // namespace frc2
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/RamseteCommand.h b/wpilibNewCommands/src/main/native/include/frc2/command/RamseteCommand.h
index d375e11..56fb8ff 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/RamseteCommand.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/RamseteCommand.h
@@ -5,9 +5,7 @@
 #pragma once
 
 #include <functional>
-#include <initializer_list>
 #include <memory>
-#include <span>
 
 #include <frc/Timer.h>
 #include <frc/controller/PIDController.h>
@@ -19,8 +17,9 @@
 #include <units/length.h>
 #include <units/voltage.h>
 
-#include "frc2/command/CommandBase.h"
+#include "frc2/command/Command.h"
 #include "frc2/command/CommandHelper.h"
+#include "frc2/command/Requirements.h"
 
 namespace frc2 {
 /**
@@ -42,7 +41,7 @@
  * @see RamseteController
  * @see Trajectory
  */
-class RamseteCommand : public CommandHelper<CommandBase, RamseteCommand> {
+class RamseteCommand : public CommandHelper<Command, RamseteCommand> {
  public:
   /**
    * Constructs a new RamseteCommand that, when executed, will follow the
@@ -76,47 +75,10 @@
                  frc::SimpleMotorFeedforward<units::meters> feedforward,
                  frc::DifferentialDriveKinematics kinematics,
                  std::function<frc::DifferentialDriveWheelSpeeds()> wheelSpeeds,
-                 frc2::PIDController leftController,
-                 frc2::PIDController rightController,
+                 frc::PIDController leftController,
+                 frc::PIDController rightController,
                  std::function<void(units::volt_t, units::volt_t)> output,
-                 std::initializer_list<Subsystem*> requirements);
-
-  /**
-   * Constructs a new RamseteCommand that, when executed, will follow the
-   * provided trajectory. PID control and feedforward are handled internally,
-   * and outputs are scaled -12 to 12 representing units of volts.
-   *
-   * <p>Note: The controller will *not* set the outputVolts to zero upon
-   * completion of the path - this is left to the user, since it is not
-   * appropriate for paths with nonstationary endstates.
-   *
-   * @param trajectory      The trajectory to follow.
-   * @param pose            A function that supplies the robot pose - use one of
-   * the odometry classes to provide this.
-   * @param controller      The RAMSETE controller used to follow the
-   * trajectory.
-   * @param feedforward     A component for calculating the feedforward for the
-   * drive.
-   * @param kinematics      The kinematics for the robot drivetrain.
-   * @param wheelSpeeds     A function that supplies the speeds of the left
-   * and right sides of the robot drive.
-   * @param leftController  The PIDController for the left side of the robot
-   * drive.
-   * @param rightController The PIDController for the right side of the robot
-   * drive.
-   * @param output          A function that consumes the computed left and right
-   * outputs (in volts) for the robot drive.
-   * @param requirements    The subsystems to require.
-   */
-  RamseteCommand(frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
-                 frc::RamseteController controller,
-                 frc::SimpleMotorFeedforward<units::meters> feedforward,
-                 frc::DifferentialDriveKinematics kinematics,
-                 std::function<frc::DifferentialDriveWheelSpeeds()> wheelSpeeds,
-                 frc2::PIDController leftController,
-                 frc2::PIDController rightController,
-                 std::function<void(units::volt_t, units::volt_t)> output,
-                 std::span<Subsystem* const> requirements = {});
+                 Requirements requirements = {});
 
   /**
    * Constructs a new RamseteCommand that, when executed, will follow the
@@ -140,31 +102,7 @@
                  std::function<void(units::meters_per_second_t,
                                     units::meters_per_second_t)>
                      output,
-                 std::initializer_list<Subsystem*> requirements);
-
-  /**
-   * Constructs a new RamseteCommand that, when executed, will follow the
-   * provided trajectory. Performs no PID control and calculates no
-   * feedforwards; outputs are the raw wheel speeds from the RAMSETE controller,
-   * and will need to be converted into a usable form by the user.
-   *
-   * @param trajectory      The trajectory to follow.
-   * @param pose            A function that supplies the robot pose - use one of
-   * the odometry classes to provide this.
-   * @param controller      The RAMSETE controller used to follow the
-   * trajectory.
-   * @param kinematics      The kinematics for the robot drivetrain.
-   * @param output          A function that consumes the computed left and right
-   * wheel speeds.
-   * @param requirements    The subsystems to require.
-   */
-  RamseteCommand(frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
-                 frc::RamseteController controller,
-                 frc::DifferentialDriveKinematics kinematics,
-                 std::function<void(units::meters_per_second_t,
-                                    units::meters_per_second_t)>
-                     output,
-                 std::span<Subsystem* const> requirements = {});
+                 Requirements requirements = {});
 
   void Initialize() override;
 
@@ -183,8 +121,8 @@
   frc::SimpleMotorFeedforward<units::meters> m_feedforward;
   frc::DifferentialDriveKinematics m_kinematics;
   std::function<frc::DifferentialDriveWheelSpeeds()> m_speeds;
-  std::unique_ptr<frc2::PIDController> m_leftController;
-  std::unique_ptr<frc2::PIDController> m_rightController;
+  std::unique_ptr<frc::PIDController> m_leftController;
+  std::unique_ptr<frc::PIDController> m_rightController;
   std::function<void(units::volt_t, units::volt_t)> m_outputVolts;
   std::function<void(units::meters_per_second_t, units::meters_per_second_t)>
       m_outputVel;
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/RepeatCommand.h b/wpilibNewCommands/src/main/native/include/frc2/command/RepeatCommand.h
index 5d353b9..9c1a5d1 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/RepeatCommand.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/RepeatCommand.h
@@ -9,10 +9,11 @@
 #pragma warning(disable : 4521)
 #endif
 
+#include <concepts>
 #include <memory>
 #include <utility>
 
-#include "frc2/command/CommandBase.h"
+#include "frc2/command/Command.h"
 #include "frc2/command/CommandHelper.h"
 
 namespace frc2 {
@@ -28,7 +29,7 @@
  *
  * <p>This class is provided by the NewCommands VendorDep
  */
-class RepeatCommand : public CommandHelper<CommandBase, RepeatCommand> {
+class RepeatCommand : public CommandHelper<Command, RepeatCommand> {
  public:
   /**
    * Creates a new RepeatCommand. Will run another command repeatedly,
@@ -44,11 +45,11 @@
    *
    * @param command the command to run repeatedly
    */
-  template <class T, typename = std::enable_if_t<std::is_base_of_v<
-                         Command, std::remove_reference_t<T>>>>
+  template <std::derived_from<Command> T>
+  // NOLINTNEXTLINE(bugprone-forwarding-reference-overload)
   explicit RepeatCommand(T&& command)
-      : RepeatCommand(std::make_unique<std::remove_reference_t<T>>(
-            std::forward<T>(command))) {}
+      : RepeatCommand(
+            std::make_unique<std::decay_t<T>>(std::forward<T>(command))) {}
 
   RepeatCommand(RepeatCommand&& other) = default;
 
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/Requirements.h b/wpilibNewCommands/src/main/native/include/frc2/command/Requirements.h
new file mode 100644
index 0000000..968917f
--- /dev/null
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/Requirements.h
@@ -0,0 +1,46 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#pragma once
+
+#include <initializer_list>
+#include <span>
+#include <vector>
+
+#include "frc2/command/Subsystem.h"
+
+namespace frc2 {
+
+/**
+ * Represents requirements for a command, which is a set of (pointers to)
+ * subsystems. This class is implicitly convertible from std::initializer_list
+ * and std::span.
+ */
+class Requirements {
+ public:
+  // NOLINTNEXTLINE
+  /*implicit*/ Requirements(std::initializer_list<Subsystem*> requirements)
+      : m_subsystems{requirements.begin(), requirements.end()} {}
+
+  // NOLINTNEXTLINE
+  /*implicit*/ Requirements(std::span<Subsystem* const> requirements)
+      : m_subsystems{requirements.begin(), requirements.end()} {}
+
+  Requirements() = default;
+
+  Requirements(const Requirements&) = default;
+
+  std::vector<Subsystem*>::const_iterator begin() const {
+    return m_subsystems.begin();
+  }
+
+  std::vector<Subsystem*>::const_iterator end() const {
+    return m_subsystems.end();
+  }
+
+ private:
+  std::vector<Subsystem*> m_subsystems;
+};
+
+}  // namespace frc2
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/RunCommand.h b/wpilibNewCommands/src/main/native/include/frc2/command/RunCommand.h
index 3bfecf7..49e4be5 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/RunCommand.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/RunCommand.h
@@ -5,11 +5,10 @@
 #pragma once
 
 #include <functional>
-#include <initializer_list>
-#include <span>
 
 #include "frc2/command/CommandHelper.h"
 #include "frc2/command/FunctionalCommand.h"
+#include "frc2/command/Requirements.h"
 
 namespace frc2 {
 /**
@@ -29,18 +28,8 @@
    * @param toRun        the Runnable to run
    * @param requirements the subsystems to require
    */
-  RunCommand(std::function<void()> toRun,
-             std::initializer_list<Subsystem*> requirements);
-
-  /**
-   * Creates a new RunCommand.  The Runnable will be run continuously until the
-   * command ends.  Does not run when disabled.
-   *
-   * @param toRun        the Runnable to run
-   * @param requirements the subsystems to require
-   */
   explicit RunCommand(std::function<void()> toRun,
-                      std::span<Subsystem* const> requirements = {});
+                      Requirements requirements = {});
 
   RunCommand(RunCommand&& other) = default;
 
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/ScheduleCommand.h b/wpilibNewCommands/src/main/native/include/frc2/command/ScheduleCommand.h
index 63cc3b3..3c960db 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/ScheduleCommand.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/ScheduleCommand.h
@@ -6,11 +6,10 @@
 
 #include <span>
 
-#include <wpi/SmallVector.h>
+#include <wpi/SmallSet.h>
 
-#include "frc2/command/CommandBase.h"
+#include "frc2/command/Command.h"
 #include "frc2/command/CommandHelper.h"
-#include "frc2/command/SetUtilities.h"
 
 namespace frc2 {
 /**
@@ -21,7 +20,7 @@
  *
  * This class is provided by the NewCommands VendorDep
  */
-class ScheduleCommand : public CommandHelper<CommandBase, ScheduleCommand> {
+class ScheduleCommand : public CommandHelper<Command, ScheduleCommand> {
  public:
   /**
    * Creates a new ScheduleCommand that schedules the given commands when
@@ -44,6 +43,6 @@
   bool RunsWhenDisabled() const override;
 
  private:
-  wpi::SmallVector<Command*, 4> m_toSchedule;
+  wpi::SmallSet<Command*, 4> m_toSchedule;
 };
 }  // namespace frc2
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/SelectCommand.h b/wpilibNewCommands/src/main/native/include/frc2/command/SelectCommand.h
index 7079afe..d425d76 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/SelectCommand.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/SelectCommand.h
@@ -9,23 +9,23 @@
 #pragma warning(disable : 4521)
 #endif
 
+#include <concepts>
+#include <functional>
 #include <memory>
 #include <string>
-#include <type_traits>
 #include <unordered_map>
 #include <utility>
 #include <vector>
 
 #include <wpi/sendable/SendableBuilder.h>
 
-#include "frc2/command/CommandBase.h"
+#include "frc2/command/Command.h"
 #include "frc2/command/PrintCommand.h"
 
 namespace frc2 {
 /**
- * A command composition that runs one of a selection of commands, either using
- * a selector and a key to command mapping, or a supplier that returns the
- * command directly at runtime.
+ * A command composition that runs one of a selection of commands using a
+ * selector and a key to command mapping.
  *
  * <p>The rules for command compositions apply: command instances that are
  * passed to it are owned by the composition and cannot be added to any other
@@ -35,7 +35,7 @@
  * This class is provided by the NewCommands VendorDep
  */
 template <typename Key>
-class SelectCommand : public CommandHelper<CommandBase, SelectCommand<Key>> {
+class SelectCommand : public CommandHelper<Command, SelectCommand<Key>> {
  public:
   /**
    * Creates a new SelectCommand.
@@ -43,21 +43,21 @@
    * @param commands the map of commands to choose from
    * @param selector the selector to determine which command to run
    */
-  template <class... Types,
-            typename = std::enable_if_t<std::conjunction_v<
-                std::is_base_of<Command, std::remove_reference_t<Types>>...>>>
+  template <std::derived_from<Command>... Commands>
   explicit SelectCommand(std::function<Key()> selector,
-                         std::pair<Key, Types>... commands)
+                         std::pair<Key, Commands>... commands)
       : m_selector{std::move(selector)} {
     std::vector<std::pair<Key, std::unique_ptr<Command>>> foo;
 
-    ((void)foo.emplace_back(commands.first,
-                            std::make_unique<std::remove_reference_t<Types>>(
-                                std::move(commands.second))),
+    ((void)foo.emplace_back(
+         commands.first,
+         std::make_unique<std::decay_t<Commands>>(std::move(commands.second))),
      ...);
 
+    m_defaultCommand.SetComposed(true);
     for (auto&& command : foo) {
       CommandScheduler::GetInstance().RequireUngrouped(command.second.get());
+      command.second.get()->SetComposed(true);
     }
 
     for (auto&& command : foo) {
@@ -75,8 +75,10 @@
       std::function<Key()> selector,
       std::vector<std::pair<Key, std::unique_ptr<Command>>>&& commands)
       : m_selector{std::move(selector)} {
+    m_defaultCommand.SetComposed(true);
     for (auto&& command : commands) {
       CommandScheduler::GetInstance().RequireUngrouped(command.second.get());
+      command.second.get()->SetComposed(true);
     }
 
     for (auto&& command : commands) {
@@ -96,17 +98,6 @@
   // Prevent template expansion from emulating copy ctor
   SelectCommand(SelectCommand&) = delete;
 
-  /**
-   * Creates a new selectcommand.
-   *
-   * @param toRun a supplier providing the command to run
-   * @deprecated Replace with {@link ProxyCommand},
-   * composing multiple of them in a {@link ParallelRaceGroup} if needed.
-   */
-  WPI_DEPRECATED("Replace with ProxyCommand")
-  explicit SelectCommand(std::function<Command*()> toRun)
-      : m_toRun{std::move(toRun)} {}
-
   SelectCommand(SelectCommand&& other) = default;
 
   void Initialize() override;
@@ -126,7 +117,7 @@
   }
 
   void InitSendable(wpi::SendableBuilder& builder) override {
-    CommandBase::InitSendable(builder);
+    Command::InitSendable(builder);
 
     builder.AddStringProperty(
         "selected",
@@ -148,25 +139,22 @@
  private:
   std::unordered_map<Key, std::unique_ptr<Command>> m_commands;
   std::function<Key()> m_selector;
-  std::function<Command*()> m_toRun;
   Command* m_selectedCommand;
   bool m_runsWhenDisabled = true;
   Command::InterruptionBehavior m_interruptBehavior{
       Command::InterruptionBehavior::kCancelIncoming};
+
+  PrintCommand m_defaultCommand{
+      "SelectCommand selector value does not correspond to any command!"};
 };
 
 template <typename T>
 void SelectCommand<T>::Initialize() {
-  if (m_selector) {
-    auto find = m_commands.find(m_selector());
-    if (find == m_commands.end()) {
-      m_selectedCommand = new PrintCommand(
-          "SelectCommand selector value does not correspond to any command!");
-      return;
-    }
-    m_selectedCommand = find->second.get();
+  auto find = m_commands.find(m_selector());
+  if (find == m_commands.end()) {
+    m_selectedCommand = &m_defaultCommand;
   } else {
-    m_selectedCommand = m_toRun();
+    m_selectedCommand = find->second.get();
   }
   m_selectedCommand->Initialize();
 }
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/SequentialCommandGroup.h b/wpilibNewCommands/src/main/native/include/frc2/command/SequentialCommandGroup.h
index ba85b6a..fdcd1bc 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/SequentialCommandGroup.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/SequentialCommandGroup.h
@@ -9,14 +9,16 @@
 #pragma warning(disable : 4521)
 #endif
 
+#include <concepts>
 #include <limits>
 #include <memory>
-#include <span>
 #include <type_traits>
 #include <utility>
 #include <vector>
 
-#include "frc2/command/CommandGroupBase.h"
+#include <wpi/DecayedDerivedFrom.h>
+
+#include "frc2/command/CommandBase.h"
 #include "frc2/command/CommandHelper.h"
 
 namespace frc2 {
@@ -34,7 +36,7 @@
  * This class is provided by the NewCommands VendorDep
  */
 class SequentialCommandGroup
-    : public CommandHelper<CommandGroupBase, SequentialCommandGroup> {
+    : public CommandHelper<Command, SequentialCommandGroup> {
  public:
   /**
    * Creates a new SequentialCommandGroup. The given commands will be run
@@ -53,11 +55,9 @@
    *
    * @param commands the commands to include in this composition.
    */
-  template <class... Types,
-            typename = std::enable_if_t<std::conjunction_v<
-                std::is_base_of<Command, std::remove_reference_t<Types>>...>>>
-  explicit SequentialCommandGroup(Types&&... commands) {
-    AddCommands(std::forward<Types>(commands)...);
+  template <wpi::DecayedDerivedFrom<Command>... Commands>
+  explicit SequentialCommandGroup(Commands&&... commands) {
+    AddCommands(std::forward<Commands>(commands)...);
   }
 
   SequentialCommandGroup(SequentialCommandGroup&& other) = default;
@@ -68,13 +68,16 @@
   // Prevent template expansion from emulating copy ctor
   SequentialCommandGroup(SequentialCommandGroup&) = delete;
 
-  template <class... Types,
-            typename = std::enable_if_t<std::conjunction_v<
-                std::is_base_of<Command, std::remove_reference_t<Types>>...>>>
-  void AddCommands(Types&&... commands) {
+  /**
+   * Adds the given commands to the group.
+   *
+   * @param commands Commands to add, in order of execution.
+   */
+  template <wpi::DecayedDerivedFrom<Command>... Commands>
+  void AddCommands(Commands&&... commands) {
     std::vector<std::unique_ptr<Command>> foo;
-    ((void)foo.emplace_back(std::make_unique<std::remove_reference_t<Types>>(
-         std::forward<Types>(commands))),
+    ((void)foo.emplace_back(std::make_unique<std::decay_t<Commands>>(
+         std::forward<Commands>(commands))),
      ...);
     AddCommands(std::move(foo));
   }
@@ -94,7 +97,7 @@
   void InitSendable(wpi::SendableBuilder& builder) override;
 
  private:
-  void AddCommands(std::vector<std::unique_ptr<Command>>&& commands) final;
+  void AddCommands(std::vector<std::unique_ptr<Command>>&& commands);
 
   wpi::SmallVector<std::unique_ptr<Command>, 4> m_commands;
   size_t m_currentCommandIndex{invalid_index};
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/SetUtilities.h b/wpilibNewCommands/src/main/native/include/frc2/command/SetUtilities.h
deleted file mode 100644
index e70e17b..0000000
--- a/wpilibNewCommands/src/main/native/include/frc2/command/SetUtilities.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#pragma once
-
-#include <span>
-
-#include <wpi/SmallVector.h>
-
-namespace frc2 {
-template <typename T>
-void SetInsert(wpi::SmallVectorImpl<T*>& vector, std::span<T* const> toAdd) {
-  for (auto addCommand : toAdd) {
-    bool exists = false;
-    for (auto existingCommand : vector) {
-      if (addCommand == existingCommand) {
-        exists = true;
-        break;
-      }
-    }
-    if (!exists) {
-      vector.emplace_back(addCommand);
-    }
-  }
-}
-}  // namespace frc2
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/StartEndCommand.h b/wpilibNewCommands/src/main/native/include/frc2/command/StartEndCommand.h
index b1f56b2..2367be8 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/StartEndCommand.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/StartEndCommand.h
@@ -5,11 +5,10 @@
 #pragma once
 
 #include <functional>
-#include <initializer_list>
-#include <span>
 
 #include "frc2/command/CommandHelper.h"
 #include "frc2/command/FunctionalCommand.h"
+#include "frc2/command/Requirements.h"
 
 namespace frc2 {
 /**
@@ -33,18 +32,7 @@
    * @param requirements the subsystems required by this command
    */
   StartEndCommand(std::function<void()> onInit, std::function<void()> onEnd,
-                  std::initializer_list<Subsystem*> requirements);
-
-  /**
-   * Creates a new StartEndCommand.  Will run the given runnables when the
-   * command starts and when it ends.
-   *
-   * @param onInit       the Runnable to run on command init
-   * @param onEnd        the Runnable to run on command end
-   * @param requirements the subsystems required by this command
-   */
-  StartEndCommand(std::function<void()> onInit, std::function<void()> onEnd,
-                  std::span<Subsystem* const> requirements = {});
+                  Requirements requirements = {});
 
   StartEndCommand(StartEndCommand&& other) = default;
 
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/Subsystem.h b/wpilibNewCommands/src/main/native/include/frc2/command/Subsystem.h
index 0c3a2a3..cdac0c0 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/Subsystem.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/Subsystem.h
@@ -4,9 +4,12 @@
 
 #pragma once
 
-#include <type_traits>
+#include <concepts>
+#include <functional>
 #include <utility>
 
+#include <wpi/FunctionExtras.h>
+
 #include "frc2/command/CommandScheduler.h"
 
 namespace frc2 {
@@ -65,8 +68,7 @@
    *
    * @param defaultCommand the default command to associate with this subsystem
    */
-  template <class T, typename = std::enable_if_t<std::is_base_of_v<
-                         Command, std::remove_reference_t<T>>>>
+  template <std::derived_from<Command> T>
   void SetDefaultCommand(T&& defaultCommand) {
     CommandScheduler::GetInstance().SetDefaultCommand(
         this, std::forward<T>(defaultCommand));
@@ -84,6 +86,12 @@
   void SetDefaultCommand(CommandPtr&& defaultCommand);
 
   /**
+   * Removes the default command for the subsystem.  This will not cancel the
+   * default command if it is currently running.
+   */
+  void RemoveDefaultCommand();
+
+  /**
    * Gets the default command for this subsystem.  Returns null if no default
    * command is currently associated with the subsystem.
    *
@@ -111,7 +119,8 @@
    *
    * @param action the action to run
    */
-  [[nodiscard]] CommandPtr RunOnce(std::function<void()> action);
+  [[nodiscard]]
+  CommandPtr RunOnce(std::function<void()> action);
 
   /**
    * Constructs a command that runs an action every iteration until interrupted.
@@ -119,7 +128,8 @@
    *
    * @param action the action to run
    */
-  [[nodiscard]] CommandPtr Run(std::function<void()> action);
+  [[nodiscard]]
+  CommandPtr Run(std::function<void()> action);
 
   /**
    * Constructs a command that runs an action once and another action when the
@@ -128,8 +138,8 @@
    * @param start the action to run on start
    * @param end the action to run on interrupt
    */
-  [[nodiscard]] CommandPtr StartEnd(std::function<void()> start,
-                                    std::function<void()> end);
+  [[nodiscard]]
+  CommandPtr StartEnd(std::function<void()> start, std::function<void()> end);
 
   /**
    * Constructs a command that runs an action every iteration until interrupted,
@@ -138,7 +148,17 @@
    * @param run the action to run every iteration
    * @param end the action to run on interrupt
    */
-  [[nodiscard]] CommandPtr RunEnd(std::function<void()> run,
-                                  std::function<void()> end);
+  [[nodiscard]]
+  CommandPtr RunEnd(std::function<void()> run, std::function<void()> end);
+
+  /**
+   * Constructs a DeferredCommand with the provided supplier. This subsystem is
+   * added as a requirement.
+   *
+   * @param supplier the command supplier.
+   * @return the command.
+   */
+  [[nodiscard]]
+  CommandPtr Defer(wpi::unique_function<CommandPtr()> supplier);
 };
 }  // namespace frc2
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/SwerveControllerCommand.h b/wpilibNewCommands/src/main/native/include/frc2/command/SwerveControllerCommand.h
index d51ae1e..ae928b8 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/SwerveControllerCommand.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/SwerveControllerCommand.h
@@ -4,9 +4,7 @@
 
 #include <cmath>
 #include <functional>
-#include <initializer_list>
 #include <memory>
-#include <span>
 
 #include <frc/Timer.h>
 #include <frc/controller/HolonomicDriveController.h>
@@ -21,8 +19,9 @@
 #include <units/time.h>
 #include <units/voltage.h>
 
-#include "CommandBase.h"
-#include "CommandHelper.h"
+#include "frc2/command/Command.h"
+#include "frc2/command/CommandHelper.h"
+#include "frc2/command/Requirements.h"
 
 #pragma once
 
@@ -51,7 +50,7 @@
  */
 template <size_t NumModules>
 class SwerveControllerCommand
-    : public CommandHelper<CommandBase, SwerveControllerCommand<NumModules>> {
+    : public CommandHelper<Command, SwerveControllerCommand<NumModules>> {
   using voltsecondspermeter =
       units::compound_unit<units::voltage::volt, units::second,
                            units::inverse<units::meter>>;
@@ -89,12 +88,12 @@
   SwerveControllerCommand(
       frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
       frc::SwerveDriveKinematics<NumModules> kinematics,
-      frc2::PIDController xController, frc2::PIDController yController,
+      frc::PIDController xController, frc::PIDController yController,
       frc::ProfiledPIDController<units::radians> thetaController,
       std::function<frc::Rotation2d()> desiredRotation,
       std::function<void(std::array<frc::SwerveModuleState, NumModules>)>
           output,
-      std::initializer_list<Subsystem*> requirements);
+      Requirements requirements = {});
 
   /**
    * Constructs a new SwerveControllerCommand that when executed will follow the
@@ -128,86 +127,11 @@
   SwerveControllerCommand(
       frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
       frc::SwerveDriveKinematics<NumModules> kinematics,
-      frc2::PIDController xController, frc2::PIDController yController,
+      frc::PIDController xController, frc::PIDController yController,
       frc::ProfiledPIDController<units::radians> thetaController,
       std::function<void(std::array<frc::SwerveModuleState, NumModules>)>
           output,
-      std::initializer_list<Subsystem*> requirements);
-
-  /**
-   * Constructs a new SwerveControllerCommand that when executed will follow the
-   * provided trajectory. This command will not return output voltages but
-   * rather raw module states from the position controllers which need to be put
-   * into a velocity PID.
-   *
-   * <p>Note: The controllers will *not* set the outputVolts to zero upon
-   * completion of the path- this is left to the user, since it is not
-   * appropriate for paths with nonstationary endstates.
-   *
-   *
-   * @param trajectory      The trajectory to follow.
-   * @param pose            A function that supplies the robot pose,
-   *                        provided by the odometry class.
-   * @param kinematics      The kinematics for the robot drivetrain.
-   * @param xController     The Trajectory Tracker PID controller
-   *                        for the robot's x position.
-   * @param yController     The Trajectory Tracker PID controller
-   *                        for the robot's y position.
-   * @param thetaController The Trajectory Tracker PID controller
-   *                        for angle for the robot.
-   * @param desiredRotation The angle that the drivetrain should be
-   *                        facing. This is sampled at each time step.
-   * @param output          The raw output module states from the
-   *                        position controllers.
-   * @param requirements    The subsystems to require.
-   */
-  SwerveControllerCommand(
-      frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
-      frc::SwerveDriveKinematics<NumModules> kinematics,
-      frc2::PIDController xController, frc2::PIDController yController,
-      frc::ProfiledPIDController<units::radians> thetaController,
-      std::function<frc::Rotation2d()> desiredRotation,
-      std::function<void(std::array<frc::SwerveModuleState, NumModules>)>
-          output,
-      std::span<Subsystem* const> requirements = {});
-
-  /**
-   * Constructs a new SwerveControllerCommand that when executed will follow the
-   * provided trajectory. This command will not return output voltages but
-   * rather raw module states from the position controllers which need to be put
-   * into a velocity PID.
-   *
-   * <p>Note: The controllers will *not* set the outputVolts to zero upon
-   * completion of the path- this is left to the user, since it is not
-   * appropriate for paths with nonstationary endstates.
-   *
-   * <p>Note 2: The final rotation of the robot will be set to the rotation of
-   * the final pose in the trajectory. The robot will not follow the rotations
-   * from the poses at each timestep. If alternate rotation behavior is desired,
-   * the other constructor with a supplier for rotation should be used.
-   *
-   * @param trajectory      The trajectory to follow.
-   * @param pose            A function that supplies the robot pose,
-   *                        provided by the odometry class.
-   * @param kinematics      The kinematics for the robot drivetrain.
-   * @param xController     The Trajectory Tracker PID controller
-   *                        for the robot's x position.
-   * @param yController     The Trajectory Tracker PID controller
-   *                        for the robot's y position.
-   * @param thetaController The Trajectory Tracker PID controller
-   *                        for angle for the robot.
-   * @param output          The raw output module states from the
-   *                        position controllers.
-   * @param requirements    The subsystems to require.
-   */
-  SwerveControllerCommand(
-      frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
-      frc::SwerveDriveKinematics<NumModules> kinematics,
-      frc2::PIDController xController, frc2::PIDController yController,
-      frc::ProfiledPIDController<units::radians> thetaController,
-      std::function<void(std::array<frc::SwerveModuleState, NumModules>)>
-          output,
-      std::span<Subsystem* const> requirements = {});
+      Requirements requirements = {});
 
   /**
    * Constructs a new SwerveControllerCommand that when executed will follow the
@@ -237,7 +161,7 @@
       std::function<frc::Rotation2d()> desiredRotation,
       std::function<void(std::array<frc::SwerveModuleState, NumModules>)>
           output,
-      std::initializer_list<Subsystem*> requirements);
+      Requirements requirements = {});
 
   /**
    * Constructs a new SwerveControllerCommand that when executed will follow the
@@ -269,70 +193,7 @@
       frc::HolonomicDriveController controller,
       std::function<void(std::array<frc::SwerveModuleState, NumModules>)>
           output,
-      std::initializer_list<Subsystem*> requirements);
-
-  /**
-   * Constructs a new SwerveControllerCommand that when executed will follow the
-   * provided trajectory. This command will not return output voltages but
-   * rather raw module states from the position controllers which need to be put
-   * into a velocity PID.
-   *
-   * <p>Note: The controllers will *not* set the outputVolts to zero upon
-   * completion of the path- this is left to the user, since it is not
-   * appropriate for paths with nonstationary endstates.
-   *
-   *
-   * @param trajectory      The trajectory to follow.
-   * @param pose            A function that supplies the robot pose,
-   *                        provided by the odometry class.
-   * @param kinematics      The kinematics for the robot drivetrain.
-   * @param controller     The HolonomicDriveController for the robot.
-   * @param desiredRotation The angle that the drivetrain should be
-   *                        facing. This is sampled at each time step.
-   * @param output          The raw output module states from the
-   *                        position controllers.
-   * @param requirements    The subsystems to require.
-   */
-  SwerveControllerCommand(
-      frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
-      frc::SwerveDriveKinematics<NumModules> kinematics,
-      frc::HolonomicDriveController controller,
-      std::function<frc::Rotation2d()> desiredRotation,
-      std::function<void(std::array<frc::SwerveModuleState, NumModules>)>
-          output,
-      std::span<Subsystem* const> requirements = {});
-
-  /**
-   * Constructs a new SwerveControllerCommand that when executed will follow the
-   * provided trajectory. This command will not return output voltages but
-   * rather raw module states from the position controllers which need to be put
-   * into a velocity PID.
-   *
-   * <p>Note: The controllers will *not* set the outputVolts to zero upon
-   * completion of the path- this is left to the user, since it is not
-   * appropriate for paths with nonstationary endstates.
-   *
-   * <p>Note 2: The final rotation of the robot will be set to the rotation of
-   * the final pose in the trajectory. The robot will not follow the rotations
-   * from the poses at each timestep. If alternate rotation behavior is desired,
-   * the other constructor with a supplier for rotation should be used.
-   *
-   * @param trajectory      The trajectory to follow.
-   * @param pose            A function that supplies the robot pose,
-   *                        provided by the odometry class.
-   * @param kinematics      The kinematics for the robot drivetrain.
-   * @param controller      The HolonomicDriveController for the drivetrain.
-   * @param output          The raw output module states from the
-   *                        position controllers.
-   * @param requirements    The subsystems to require.
-   */
-  SwerveControllerCommand(
-      frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
-      frc::SwerveDriveKinematics<NumModules> kinematics,
-      frc::HolonomicDriveController controller,
-      std::function<void(std::array<frc::SwerveModuleState, NumModules>)>
-          output,
-      std::span<Subsystem* const> requirements = {});
+      Requirements requirements = {});
 
   void Initialize() override;
 
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/SwerveControllerCommand.inc b/wpilibNewCommands/src/main/native/include/frc2/command/SwerveControllerCommand.inc
index a644057..d0c34af 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/SwerveControllerCommand.inc
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/SwerveControllerCommand.inc
@@ -4,6 +4,7 @@
 
 #pragma once
 
+#include <functional>
 #include <memory>
 #include <utility>
 
@@ -15,11 +16,11 @@
 SwerveControllerCommand<NumModules>::SwerveControllerCommand(
     frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
     frc::SwerveDriveKinematics<NumModules> kinematics,
-    frc2::PIDController xController, frc2::PIDController yController,
+    frc::PIDController xController, frc::PIDController yController,
     frc::ProfiledPIDController<units::radians> thetaController,
     std::function<frc::Rotation2d()> desiredRotation,
     std::function<void(std::array<frc::SwerveModuleState, NumModules>)> output,
-    std::initializer_list<Subsystem*> requirements)
+    Requirements requirements)
     : m_trajectory(std::move(trajectory)),
       m_pose(std::move(pose)),
       m_kinematics(kinematics),
@@ -33,44 +34,10 @@
 SwerveControllerCommand<NumModules>::SwerveControllerCommand(
     frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
     frc::SwerveDriveKinematics<NumModules> kinematics,
-    frc2::PIDController xController, frc2::PIDController yController,
+    frc::PIDController xController, frc::PIDController yController,
     frc::ProfiledPIDController<units::radians> thetaController,
     std::function<void(std::array<frc::SwerveModuleState, NumModules>)> output,
-    std::initializer_list<Subsystem*> requirements)
-    : m_trajectory(std::move(trajectory)),
-      m_pose(std::move(pose)),
-      m_kinematics(kinematics),
-      m_controller(xController, yController, thetaController),
-      m_outputStates(output) {
-  this->AddRequirements(requirements);
-}
-
-template <size_t NumModules>
-SwerveControllerCommand<NumModules>::SwerveControllerCommand(
-    frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
-    frc::SwerveDriveKinematics<NumModules> kinematics,
-    frc2::PIDController xController, frc2::PIDController yController,
-    frc::ProfiledPIDController<units::radians> thetaController,
-    std::function<frc::Rotation2d()> desiredRotation,
-    std::function<void(std::array<frc::SwerveModuleState, NumModules>)> output,
-    std::span<Subsystem* const> requirements)
-    : m_trajectory(std::move(trajectory)),
-      m_pose(std::move(pose)),
-      m_kinematics(kinematics),
-      m_controller(xController, yController, thetaController),
-      m_desiredRotation(std::move(desiredRotation)),
-      m_outputStates(output) {
-  this->AddRequirements(requirements);
-}
-
-template <size_t NumModules>
-SwerveControllerCommand<NumModules>::SwerveControllerCommand(
-    frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
-    frc::SwerveDriveKinematics<NumModules> kinematics,
-    frc2::PIDController xController, frc2::PIDController yController,
-    frc::ProfiledPIDController<units::radians> thetaController,
-    std::function<void(std::array<frc::SwerveModuleState, NumModules>)> output,
-    std::span<Subsystem* const> requirements)
+    Requirements requirements)
     : m_trajectory(std::move(trajectory)),
       m_pose(std::move(pose)),
       m_kinematics(kinematics),
@@ -86,7 +53,7 @@
     frc::HolonomicDriveController controller,
     std::function<frc::Rotation2d()> desiredRotation,
     std::function<void(std::array<frc::SwerveModuleState, NumModules>)> output,
-    std::initializer_list<Subsystem*> requirements)
+    Requirements requirements)
     : m_trajectory(std::move(trajectory)),
       m_pose(std::move(pose)),
       m_kinematics(kinematics),
@@ -102,39 +69,7 @@
     frc::SwerveDriveKinematics<NumModules> kinematics,
     frc::HolonomicDriveController controller,
     std::function<void(std::array<frc::SwerveModuleState, NumModules>)> output,
-    std::initializer_list<Subsystem*> requirements)
-    : m_trajectory(std::move(trajectory)),
-      m_pose(std::move(pose)),
-      m_kinematics(kinematics),
-      m_controller(std::move(controller)),
-      m_outputStates(output) {
-  this->AddRequirements(requirements);
-}
-
-template <size_t NumModules>
-SwerveControllerCommand<NumModules>::SwerveControllerCommand(
-    frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
-    frc::SwerveDriveKinematics<NumModules> kinematics,
-    frc::HolonomicDriveController controller,
-    std::function<frc::Rotation2d()> desiredRotation,
-    std::function<void(std::array<frc::SwerveModuleState, NumModules>)> output,
-    std::span<Subsystem* const> requirements)
-    : m_trajectory(std::move(trajectory)),
-      m_pose(std::move(pose)),
-      m_kinematics(kinematics),
-      m_controller(std::move(controller)),
-      m_desiredRotation(std::move(desiredRotation)),
-      m_outputStates(output) {
-  this->AddRequirements(requirements);
-}
-
-template <size_t NumModules>
-SwerveControllerCommand<NumModules>::SwerveControllerCommand(
-    frc::Trajectory trajectory, std::function<frc::Pose2d()> pose,
-    frc::SwerveDriveKinematics<NumModules> kinematics,
-    frc::HolonomicDriveController controller,
-    std::function<void(std::array<frc::SwerveModuleState, NumModules>)> output,
-    std::span<Subsystem* const> requirements)
+    Requirements requirements)
     : m_trajectory(std::move(trajectory)),
       m_pose(std::move(pose)),
       m_kinematics(kinematics),
@@ -150,8 +85,7 @@
       return m_trajectory.States().back().pose.Rotation();
     };
   }
-  m_timer.Reset();
-  m_timer.Start();
+  m_timer.Restart();
 }
 
 template <size_t NumModules>
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/TrapezoidProfileCommand.h b/wpilibNewCommands/src/main/native/include/frc2/command/TrapezoidProfileCommand.h
index 413553b..69cc8d8 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/TrapezoidProfileCommand.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/TrapezoidProfileCommand.h
@@ -5,14 +5,13 @@
 #pragma once
 
 #include <functional>
-#include <initializer_list>
-#include <span>
 
 #include <frc/Timer.h>
 #include <frc/trajectory/TrapezoidProfile.h>
 
-#include "frc2/command/CommandBase.h"
+#include "frc2/command/Command.h"
 #include "frc2/command/CommandHelper.h"
+#include "frc2/command/Requirements.h"
 
 namespace frc2 {
 /**
@@ -25,7 +24,7 @@
  */
 template <class Distance>
 class TrapezoidProfileCommand
-    : public CommandHelper<CommandBase, TrapezoidProfileCommand<Distance>> {
+    : public CommandHelper<Command, TrapezoidProfileCommand<Distance>> {
   using Distance_t = units::unit_t<Distance>;
   using Velocity =
       units::compound_unit<Distance, units::inverse<units::seconds>>;
@@ -39,13 +38,21 @@
    *
    * @param profile      The motion profile to execute.
    * @param output       The consumer for the profile output.
+   * @param goal The supplier for the desired state
+   * @param currentState The current state
    * @param requirements The list of requirements.
    */
   TrapezoidProfileCommand(frc::TrapezoidProfile<Distance> profile,
                           std::function<void(State)> output,
-                          std::initializer_list<Subsystem*> requirements)
-      : m_profile(profile), m_output(output) {
+                          std::function<State()> goal,
+                          std::function<State()> currentState,
+                          Requirements requirements = {})
+      : m_profile(profile),
+        m_output(output),
+        m_goal(goal),
+        m_currentState(currentState) {
     this->AddRequirements(requirements);
+    m_newAPI = true;
   }
 
   /**
@@ -55,20 +62,25 @@
    * @param profile      The motion profile to execute.
    * @param output       The consumer for the profile output.
    * @param requirements The list of requirements.
+   * @deprecated The new constructor allows you to pass in a supplier for
+   * desired and current state. This allows you to change goals at runtime.
    */
+  WPI_DEPRECATED(
+      "The new constructor allows you to pass in a supplier for desired and "
+      "current state. This allows you to change goals at runtime.")
   TrapezoidProfileCommand(frc::TrapezoidProfile<Distance> profile,
                           std::function<void(State)> output,
-                          std::span<Subsystem* const> requirements = {})
+                          Requirements requirements = {})
       : m_profile(profile), m_output(output) {
     this->AddRequirements(requirements);
+    m_newAPI = false;
   }
 
-  void Initialize() override {
-    m_timer.Reset();
-    m_timer.Start();
-  }
+  void Initialize() override { m_timer.Restart(); }
 
-  void Execute() override { m_output(m_profile.Calculate(m_timer.Get())); }
+  void Execute() override {
+    m_output(m_profile.Calculate(m_timer.Get(), m_goal(), m_currentState()));
+  }
 
   void End(bool interrupted) override { m_timer.Stop(); }
 
@@ -79,7 +91,9 @@
  private:
   frc::TrapezoidProfile<Distance> m_profile;
   std::function<void(State)> m_output;
-
+  std::function<State()> m_goal;
+  std::function<State()> m_currentState;
+  bool m_newAPI;  // TODO: Remove
   frc::Timer m_timer;
 };
 
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/TrapezoidProfileSubsystem.h b/wpilibNewCommands/src/main/native/include/frc2/command/TrapezoidProfileSubsystem.h
index abce33c..344aefc 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/TrapezoidProfileSubsystem.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/TrapezoidProfileSubsystem.h
@@ -39,15 +39,13 @@
   explicit TrapezoidProfileSubsystem(Constraints constraints,
                                      Distance_t initialPosition = Distance_t{0},
                                      units::second_t period = 20_ms)
-      : m_constraints(constraints),
+      : m_profile(constraints),
         m_state{initialPosition, Velocity_t(0)},
         m_goal{initialPosition, Velocity_t{0}},
         m_period(period) {}
 
   void Periodic() override {
-    auto profile =
-        frc::TrapezoidProfile<Distance>(m_constraints, m_goal, m_state);
-    m_state = profile.Calculate(m_period);
+    m_state = m_profile.Calculate(m_period, m_goal, m_state);
     if (m_enabled) {
       UseState(m_state);
     }
@@ -87,7 +85,7 @@
   void Disable() { m_enabled = false; }
 
  private:
-  Constraints m_constraints;
+  frc::TrapezoidProfile<Distance> m_profile;
   State m_state;
   State m_goal;
   units::second_t m_period;
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/WaitCommand.h b/wpilibNewCommands/src/main/native/include/frc2/command/WaitCommand.h
index 54eddfb..e28615e 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/WaitCommand.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/WaitCommand.h
@@ -7,18 +7,16 @@
 #include <frc/Timer.h>
 #include <units/time.h>
 
-#include "frc2/command/CommandBase.h"
+#include "frc2/command/Command.h"
 #include "frc2/command/CommandHelper.h"
 
 namespace frc2 {
 /**
  * A command that does nothing but takes a specified amount of time to finish.
- * Useful for CommandGroups.  Can also be subclassed to make a command with an
- * internal timer.
  *
  * This class is provided by the NewCommands VendorDep
  */
-class WaitCommand : public CommandHelper<CommandBase, WaitCommand> {
+class WaitCommand : public CommandHelper<Command, WaitCommand> {
  public:
   /**
    * Creates a new WaitCommand.  This command will do nothing, and end after the
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/WaitUntilCommand.h b/wpilibNewCommands/src/main/native/include/frc2/command/WaitUntilCommand.h
index 3b26999..377a1ba 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/WaitUntilCommand.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/WaitUntilCommand.h
@@ -8,7 +8,7 @@
 
 #include <units/time.h>
 
-#include "frc2/command/CommandBase.h"
+#include "frc2/command/Command.h"
 #include "frc2/command/CommandHelper.h"
 
 namespace frc2 {
@@ -18,7 +18,7 @@
  *
  * This class is provided by the NewCommands VendorDep
  */
-class WaitUntilCommand : public CommandHelper<CommandBase, WaitUntilCommand> {
+class WaitUntilCommand : public CommandHelper<Command, WaitUntilCommand> {
  public:
   /**
    * Creates a new WaitUntilCommand that ends after a given condition becomes
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/WrapperCommand.h b/wpilibNewCommands/src/main/native/include/frc2/command/WrapperCommand.h
index 71dd02f..98e8b20 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/WrapperCommand.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/WrapperCommand.h
@@ -9,10 +9,11 @@
 #pragma warning(disable : 4521)
 #endif
 
+#include <concepts>
 #include <memory>
 #include <utility>
 
-#include "frc2/command/CommandBase.h"
+#include "frc2/command/Command.h"
 #include "frc2/command/CommandHelper.h"
 
 namespace frc2 {
@@ -23,7 +24,7 @@
  * <p>Wrapped commands may only be used through the wrapper, trying to directly
  * schedule them or add them to a group will throw an exception.
  */
-class WrapperCommand : public CommandHelper<CommandBase, WrapperCommand> {
+class WrapperCommand : public CommandHelper<Command, WrapperCommand> {
  public:
   /**
    * Wrap a command.
@@ -39,11 +40,11 @@
    * @param command the command being wrapped. Trying to directly schedule this
    * command or add it to a group will throw an exception.
    */
-  template <class T, typename = std::enable_if_t<std::is_base_of_v<
-                         Command, std::remove_reference_t<T>>>>
+  template <std::derived_from<Command> T>
+  // NOLINTNEXTLINE(bugprone-forwarding-reference-overload)
   explicit WrapperCommand(T&& command)
-      : WrapperCommand(std::make_unique<std::remove_reference_t<T>>(
-            std::forward<T>(command))) {}
+      : WrapperCommand(
+            std::make_unique<std::decay_t<T>>(std::forward<T>(command))) {}
 
   WrapperCommand(WrapperCommand&& other) = default;
 
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/button/Button.h b/wpilibNewCommands/src/main/native/include/frc2/command/button/Button.h
deleted file mode 100644
index 0142c44..0000000
--- a/wpilibNewCommands/src/main/native/include/frc2/command/button/Button.h
+++ /dev/null
@@ -1,274 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#pragma once
-
-#include <functional>
-#include <initializer_list>
-#include <span>
-#include <utility>
-
-#include <wpi/deprecated.h>
-
-#include "Trigger.h"
-#include "frc2/command/CommandPtr.h"
-
-namespace frc2 {
-class Command;
-/**
- * A class used to bind command scheduling to button presses.  Can be composed
- * with other buttons with the operators in Trigger.
- *
- * This class is provided by the NewCommands VendorDep
- *
- * @see Trigger
- */
-class Button : public Trigger {
- public:
-  /**
-   * Create a new button that is pressed when the given condition is true.
-   *
-   * @param isPressed Whether the button is pressed.
-   * @deprecated Replace with Trigger
-   */
-  WPI_DEPRECATED("Replace with Trigger")
-  explicit Button(std::function<bool()> isPressed);
-
-  /**
-   * Create a new button that is pressed active (default constructor) - activity
-   *  can be further determined by subclass code.
-   * @deprecated Replace with Trigger
-   */
-  WPI_DEPRECATED("Replace with Trigger")
-  Button() = default;
-
-  /**
-   * Binds a command to start when the button is pressed.  Takes a
-   * raw pointer, and so is non-owning; users are responsible for the lifespan
-   * of the command.
-   *
-   * @param command The command to bind.
-   * @return The trigger, for chained calls.
-   * @deprecated Replace with Trigger::OnTrue()
-   */
-  WPI_DEPRECATED("Replace with Trigger#OnTrue()")
-  Button WhenPressed(Command* command);
-
-  /**
-   * Binds a command to start when the button is pressed.  Transfers
-   * command ownership to the button scheduler, so the user does not have to
-   * worry about lifespan - rvalue refs will be *moved*, lvalue refs will be
-   * *copied.*
-   *
-   * @param command The command to bind.
-   * @return The trigger, for chained calls.
-   * @deprecated Replace with Trigger::OnTrue()
-   */
-  template <class T, typename = std::enable_if_t<std::is_base_of_v<
-                         Command, std::remove_reference_t<T>>>>
-  WPI_DEPRECATED("Replace with Trigger#OnTrue()")
-  Button WhenPressed(T&& command) {
-    WhenActive(std::forward<T>(command));
-    return *this;
-  }
-
-  /**
-   * Binds a runnable to execute when the button is pressed.
-   *
-   * @param toRun the runnable to execute.
-   * @param requirements the required subsystems.
-   * @deprecated Replace with Trigger::OnTrue(cmd::RunOnce())
-   */
-  WPI_DEPRECATED("Replace with Trigger#OnTrue(cmd::RunOnce())")
-  Button WhenPressed(std::function<void()> toRun,
-                     std::initializer_list<Subsystem*> requirements);
-
-  /**
-   * Binds a runnable to execute when the button is pressed.
-   *
-   * @param toRun the runnable to execute.
-   * @param requirements the required subsystems.
-   * @deprecated Replace with Trigger::OnTrue(cmd::RunOnce())
-   */
-  WPI_DEPRECATED("Replace with Trigger#OnTrue(cmd::RunOnce())")
-  Button WhenPressed(std::function<void()> toRun,
-                     std::span<Subsystem* const> requirements = {});
-
-  /**
-   * Binds a command to be started repeatedly while the button is pressed, and
-   * canceled when it is released.  Takes a raw pointer, and so is non-owning;
-   * users are responsible for the lifespan of the command.
-   *
-   * @param command The command to bind.
-   * @return The button, for chained calls.
-   * @deprecated Replace with Trigger::WhileTrue(command.Repeatedly())
-   */
-  WPI_DEPRECATED("Replace with Trigger#WhileTrue(command.Repeatedly())")
-  Button WhileHeld(Command* command);
-
-  /**
-   * Binds a command to be started repeatedly while the button is pressed, and
-   * canceled when it is released.  Transfers command ownership to the button
-   * scheduler, so the user does not have to worry about lifespan - rvalue refs
-   * will be *moved*, lvalue refs will be *copied.*
-   *
-   * @param command The command to bind.
-   * @return The button, for chained calls.
-   * @deprecated Replace with Trigger::WhileTrue(command.Repeatedly())
-   */
-  template <class T, typename = std::enable_if_t<std::is_base_of_v<
-                         Command, std::remove_reference_t<T>>>>
-  WPI_DEPRECATED("Replace with Trigger#WhileTrue(command.Repeatedly())")
-  Button WhileHeld(T&& command) {
-    WhileActiveContinous(std::forward<T>(command));
-    return *this;
-  }
-
-  /**
-   * Binds a runnable to execute repeatedly while the button is pressed.
-   *
-   * @param toRun the runnable to execute.
-   * @param requirements the required subsystems.
-   * @deprecated Replace with Trigger::WhileTrue(cmd::Run())
-   */
-  WPI_DEPRECATED("Replace with Trigger#WhileTrue(cmd::Run())")
-  Button WhileHeld(std::function<void()> toRun,
-                   std::initializer_list<Subsystem*> requirements);
-
-  /**
-   * Binds a runnable to execute repeatedly while the button is pressed.
-   *
-   * @param toRun the runnable to execute.
-   * @param requirements the required subsystems.
-   * @deprecated Replace with Trigger::WhileTrue(cmd::Run())
-   */
-  WPI_DEPRECATED("Replace with Trigger#WhileTrue(cmd::Run())")
-  Button WhileHeld(std::function<void()> toRun,
-                   std::span<Subsystem* const> requirements = {});
-
-  /**
-   * Binds a command to be started when the button is pressed, and canceled
-   * when it is released.  Takes a raw pointer, and so is non-owning; users are
-   * responsible for the lifespan of the command.
-   *
-   * @param command The command to bind.
-   * @return The button, for chained calls.
-   * @deprecated Replace with Trigger::WhileTrue()
-   */
-  WPI_DEPRECATED("Replace with Trigger#WhileTrue()")
-  Button WhenHeld(Command* command);
-
-  /**
-   * Binds a command to be started when the button is pressed, and canceled
-   * when it is released.  Transfers command ownership to the button scheduler,
-   * so the user does not have to worry about lifespan - rvalue refs will be
-   * *moved*, lvalue refs will be *copied.*
-   *
-   * @param command The command to bind.
-   * @return The button, for chained calls.
-   * @deprecated Replace with Trigger::WhileTrue()
-   */
-  template <class T, typename = std::enable_if_t<std::is_base_of_v<
-                         Command, std::remove_reference_t<T>>>>
-  WPI_DEPRECATED("Replace with Trigger#WhileTrue()")
-  Button WhenHeld(T&& command) {
-    WhileActiveOnce(std::forward<T>(command));
-    return *this;
-  }
-
-  /**
-   * Binds a command to start when the button is released.  Takes a
-   * raw pointer, and so is non-owning; users are responsible for the lifespan
-   * of the command.
-   *
-   * @param command The command to bind.
-   * @return The button, for chained calls.
-   * @deprecated Replace with Trigger::OnFalse()
-   */
-  WPI_DEPRECATED("Replace with Trigger#OnFalse()")
-  Button WhenReleased(Command* command);
-
-  /**
-   * Binds a command to start when the button is pressed.  Transfers
-   * command ownership to the button scheduler, so the user does not have to
-   * worry about lifespan - rvalue refs will be *moved*, lvalue refs will be
-   * *copied.*
-   *
-   * @param command The command to bind.
-   * @return The button, for chained calls.
-   * @deprecated Replace with Trigger::OnFalse()
-   */
-  template <class T, typename = std::enable_if_t<std::is_base_of_v<
-                         Command, std::remove_reference_t<T>>>>
-  WPI_DEPRECATED("Replace with Trigger#OnFalse()")
-  Button WhenReleased(T&& command) {
-    WhenInactive(std::forward<T>(command));
-    return *this;
-  }
-
-  /**
-   * Binds a runnable to execute when the button is released.
-   *
-   * @param toRun the runnable to execute.
-   * @param requirements the required subsystems.
-   * @deprecated Replace with Trigger::OnFalse(cmd::RunOnce())
-   */
-  WPI_DEPRECATED("Replace with Trigger#OnFalse(cmd::RunOnce())")
-  Button WhenReleased(std::function<void()> toRun,
-                      std::initializer_list<Subsystem*> requirements);
-
-  /**
-   * Binds a runnable to execute when the button is released.
-   *
-   * @param toRun the runnable to execute.
-   * @param requirements the required subsystems.
-   * @deprecated Replace with Trigger::OnFalse(cmd::RunOnce())
-   */
-  WPI_DEPRECATED("Replace with Trigger#OnFalse(cmd::RunOnce())")
-  Button WhenReleased(std::function<void()> toRun,
-                      std::span<Subsystem* const> requirements = {});
-
-  /**
-   * Binds a command to start when the button is pressed, and be canceled when
-   * it is pressed again.  Takes a raw pointer, and so is non-owning; users are
-   * responsible for the lifespan of the command.
-   *
-   * @param command The command to bind.
-   * @return The button, for chained calls.
-   * @deprecated Replace with Trigger::ToggleOnTrue()
-   */
-  WPI_DEPRECATED("Replace with Trigger#ToggleOnTrue()")
-  Button ToggleWhenPressed(Command* command);
-
-  /**
-   * Binds a command to start when the button is pressed, and be canceled when
-   * it is pressed again.  Transfers command ownership to the button scheduler,
-   * so the user does not have to worry about lifespan - rvalue refs will be
-   * *moved*, lvalue refs will be *copied.*
-   *
-   * @param command The command to bind.
-   * @return The button, for chained calls.
-   * @deprecated Replace with Trigger::ToggleOnTrue()
-   */
-  template <class T, typename = std::enable_if_t<std::is_base_of_v<
-                         Command, std::remove_reference_t<T>>>>
-  WPI_DEPRECATED("Replace with Trigger#ToggleOnTrue()")
-  Button ToggleWhenPressed(T&& command) {
-    ToggleWhenActive(std::forward<T>(command));
-    return *this;
-  }
-
-  /**
-   * Binds a command to be canceled when the button is pressed.  Takes a
-   * raw pointer, and so is non-owning; users are responsible for the lifespan
-   *  and scheduling of the command.
-   *
-   * @param command The command to bind.
-   * @return The button, for chained calls.
-   * @deprecated Pass this as a command end condition with Until() instead.
-   */
-  WPI_DEPRECATED("Pass this as a command end condition with Until() instead.")
-  Button CancelWhenPressed(Command* command);
-};
-}  // namespace frc2
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/button/CommandJoystick.h b/wpilibNewCommands/src/main/native/include/frc2/command/button/CommandJoystick.h
index c335f89..5b51e86 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/button/CommandJoystick.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/button/CommandJoystick.h
@@ -10,7 +10,7 @@
 
 namespace frc2 {
 /**
- * A subclass of {@link Joystick} with {@link Trigger} factories for
+ * A version of {@link Joystick} with {@link Trigger} factories for
  * command-based.
  *
  * @see Joystick
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/button/CommandPS4Controller.h b/wpilibNewCommands/src/main/native/include/frc2/command/button/CommandPS4Controller.h
index befb667..22c54ad 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/button/CommandPS4Controller.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/button/CommandPS4Controller.h
@@ -10,7 +10,7 @@
 
 namespace frc2 {
 /**
- * A subclass of {@link PS4Controller} with {@link Trigger} factories for
+ * A version of {@link PS4Controller} with {@link Trigger} factories for
  * command-based.
  *
  * @see PS4Controller
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/button/CommandPS5Controller.h b/wpilibNewCommands/src/main/native/include/frc2/command/button/CommandPS5Controller.h
new file mode 100644
index 0000000..d86d383
--- /dev/null
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/button/CommandPS5Controller.h
@@ -0,0 +1,178 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#pragma once
+#include <frc/PS5Controller.h>
+
+#include "Trigger.h"
+#include "frc2/command/CommandScheduler.h"
+
+namespace frc2 {
+/**
+ * A version of {@link PS5Controller} with {@link Trigger} factories for
+ * command-based.
+ *
+ * @see PS5Controller
+ */
+class CommandPS5Controller : public frc::PS5Controller {
+ public:
+  using PS5Controller::PS5Controller;
+
+  /**
+   * Constructs an event instance around this button's digital signal.
+   *
+   * @param button the button index
+   * @param loop the event loop instance to attach the event to. Defaults to the
+   * CommandScheduler's default loop.
+   * @return an event instance representing the button's digital signal attached
+   * to the given loop.
+   */
+  Trigger Button(int button,
+                 frc::EventLoop* loop = CommandScheduler::GetInstance()
+                                            .GetDefaultButtonLoop()) const;
+
+  /**
+   * Constructs an event instance around the square button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to. Defaults to the
+   * CommandScheduler's default loop.
+   * @return an event instance representing the square button's digital signal
+   * attached to the given loop.
+   */
+  Trigger Square(frc::EventLoop* loop = CommandScheduler::GetInstance()
+                                            .GetDefaultButtonLoop()) const;
+
+  /**
+   * Constructs an event instance around the cross button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to. Defaults to the
+   * CommandScheduler's default loop.
+   * @return an event instance representing the cross button's digital signal
+   * attached to the given loop.
+   */
+  Trigger Cross(frc::EventLoop* loop = CommandScheduler::GetInstance()
+                                           .GetDefaultButtonLoop()) const;
+
+  /**
+   * Constructs an event instance around the circle button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to. Defaults to the
+   * CommandScheduler's default loop.
+   * @return an event instance representing the circle button's digital signal
+   * attached to the given loop.
+   */
+  Trigger Circle(frc::EventLoop* loop = CommandScheduler::GetInstance()
+                                            .GetDefaultButtonLoop()) const;
+
+  /**
+   * Constructs an event instance around the triangle button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to. Defaults to the
+   * CommandScheduler's default loop.
+   * @return an event instance representing the triangle button's digital signal
+   * attached to the given loop.
+   */
+  Trigger Triangle(frc::EventLoop* loop = CommandScheduler::GetInstance()
+                                              .GetDefaultButtonLoop()) const;
+
+  /**
+   * Constructs an event instance around the L1 button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to. Defaults to the
+   * CommandScheduler's default loop.
+   * @return an event instance representing the L1 button's digital signal
+   * attached to the given loop.
+   */
+  Trigger L1(frc::EventLoop* loop =
+                 CommandScheduler::GetInstance().GetDefaultButtonLoop()) const;
+
+  /**
+   * Constructs an event instance around the R1 button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to. Defaults to the
+   * CommandScheduler's default loop.
+   * @return an event instance representing the R1 button's digital signal
+   * attached to the given loop.
+   */
+  Trigger R1(frc::EventLoop* loop =
+                 CommandScheduler::GetInstance().GetDefaultButtonLoop()) const;
+
+  /**
+   * Constructs an event instance around the L2 button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to. Defaults to the
+   * CommandScheduler's default loop.
+   * @return an event instance representing the L2 button's digital signal
+   * attached to the given loop.
+   */
+  Trigger L2(frc::EventLoop* loop =
+                 CommandScheduler::GetInstance().GetDefaultButtonLoop()) const;
+
+  /**
+   * Constructs an event instance around the R2 button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to. Defaults to the
+   * CommandScheduler's default loop.
+   * @return an event instance representing the R2 button's digital signal
+   * attached to the given loop.
+   */
+  Trigger R2(frc::EventLoop* loop =
+                 CommandScheduler::GetInstance().GetDefaultButtonLoop()) const;
+
+  /**
+   * Constructs an event instance around the options button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to. Defaults to the
+   * CommandScheduler's default loop.
+   * @return an event instance representing the options button's digital signal
+   * attached to the given loop.
+   */
+  Trigger Options(frc::EventLoop* loop = CommandScheduler::GetInstance()
+                                             .GetDefaultButtonLoop()) const;
+
+  /**
+   * Constructs an event instance around the L3 button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to. Defaults to the
+   * CommandScheduler's default loop.
+   * @return an event instance representing the L3 button's digital signal
+   * attached to the given loop.
+   */
+  Trigger L3(frc::EventLoop* loop =
+                 CommandScheduler::GetInstance().GetDefaultButtonLoop()) const;
+
+  /**
+   * Constructs an event instance around the R3 button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to. Defaults to the
+   * CommandScheduler's default loop.
+   * @return an event instance representing the R3 button's digital signal
+   * attached to the given loop.
+   */
+  Trigger R3(frc::EventLoop* loop =
+                 CommandScheduler::GetInstance().GetDefaultButtonLoop()) const;
+
+  /**
+   * Constructs an event instance around the PS button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to. Defaults to the
+   * CommandScheduler's default loop.
+   * @return an event instance representing the PS button's digital signal
+   * attached to the given loop.
+   */
+  Trigger PS(frc::EventLoop* loop =
+                 CommandScheduler::GetInstance().GetDefaultButtonLoop()) const;
+
+  /**
+   * Constructs an event instance around the touchpad's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to. Defaults to the
+   * CommandScheduler's default loop.
+   * @return an event instance representing the touchpad's digital signal
+   * attached to the given loop.
+   */
+  Trigger Touchpad(frc::EventLoop* loop = CommandScheduler::GetInstance()
+                                              .GetDefaultButtonLoop()) const;
+};
+}  // namespace frc2
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/button/CommandXboxController.h b/wpilibNewCommands/src/main/native/include/frc2/command/button/CommandXboxController.h
index 337ec2f..94a3c2c 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/button/CommandXboxController.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/button/CommandXboxController.h
@@ -10,7 +10,7 @@
 
 namespace frc2 {
 /**
- * A subclass of {@link XboxController} with {@link Trigger} factories for
+ * A version of {@link XboxController} with {@link Trigger} factories for
  * command-based.
  *
  * @see XboxController
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/button/JoystickButton.h b/wpilibNewCommands/src/main/native/include/frc2/command/button/JoystickButton.h
index b5280c0..a0d04e7 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/button/JoystickButton.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/button/JoystickButton.h
@@ -4,9 +4,8 @@
 
 #pragma once
 #include <frc/GenericHID.h>
-#include <wpi/deprecated.h>
 
-#include "Button.h"
+#include "Trigger.h"
 
 namespace frc2 {
 /**
@@ -17,7 +16,7 @@
  *
  * @see Trigger
  */
-class JoystickButton : public Button {
+class JoystickButton : public Trigger {
  public:
   /**
    * Creates a JoystickButton that commands can be bound to.
@@ -25,11 +24,9 @@
    * @param joystick The joystick on which the button is located.
    * @param buttonNumber The number of the button on the joystick.
    */
-  WPI_IGNORE_DEPRECATED
   explicit JoystickButton(frc::GenericHID* joystick, int buttonNumber)
-      : Button([joystick, buttonNumber] {
+      : Trigger([joystick, buttonNumber] {
           return joystick->GetRawButton(buttonNumber);
         }) {}
-  WPI_UNIGNORE_DEPRECATED
 };
 }  // namespace frc2
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/button/NetworkButton.h b/wpilibNewCommands/src/main/native/include/frc2/command/button/NetworkButton.h
index 3d0378d..673821e 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/button/NetworkButton.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/button/NetworkButton.h
@@ -11,7 +11,7 @@
 #include <networktables/NetworkTable.h>
 #include <networktables/NetworkTableInstance.h>
 
-#include "Button.h"
+#include "Trigger.h"
 
 namespace frc2 {
 /**
@@ -19,7 +19,7 @@
  *
  * This class is provided by the NewCommands VendorDep
  */
-class NetworkButton : public Button {
+class NetworkButton : public Trigger {
  public:
   /**
    * Creates a NetworkButton that commands can be bound to.
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/button/POVButton.h b/wpilibNewCommands/src/main/native/include/frc2/command/button/POVButton.h
index 7ee1590..00723be 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/button/POVButton.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/button/POVButton.h
@@ -3,10 +3,10 @@
 // the WPILib BSD license file in the root directory of this project.
 
 #pragma once
-#include <frc/GenericHID.h>
-#include <wpi/deprecated.h>
 
-#include "Button.h"
+#include <frc/GenericHID.h>
+
+#include "Trigger.h"
 
 namespace frc2 {
 /**
@@ -17,7 +17,7 @@
  *
  * @see Trigger
  */
-class POVButton : public Button {
+class POVButton : public Trigger {
  public:
   /**
    * Creates a POVButton that commands can be bound to.
@@ -26,11 +26,9 @@
    * @param angle The angle of the POV corresponding to a button press.
    * @param povNumber The number of the POV on the joystick.
    */
-  WPI_IGNORE_DEPRECATED
   POVButton(frc::GenericHID* joystick, int angle, int povNumber = 0)
-      : Button([joystick, angle, povNumber] {
+      : Trigger([joystick, angle, povNumber] {
           return joystick->GetPOV(povNumber) == angle;
         }) {}
-  WPI_UNIGNORE_DEPRECATED
 };
 }  // namespace frc2
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/button/Trigger.h b/wpilibNewCommands/src/main/native/include/frc2/command/button/Trigger.h
index c3a5e86..533e0a6 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/button/Trigger.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/button/Trigger.h
@@ -4,17 +4,15 @@
 
 #pragma once
 
+#include <concepts>
 #include <functional>
-#include <initializer_list>
 #include <memory>
-#include <span>
 #include <utility>
 
 #include <frc/event/BooleanEvent.h>
 #include <frc/event/EventLoop.h>
 #include <frc/filter/Debouncer.h>
 #include <units/time.h>
-#include <wpi/deprecated.h>
 
 #include "frc2/command/Command.h"
 #include "frc2/command/CommandScheduler.h"
@@ -194,8 +192,7 @@
   Trigger ToggleOnFalse(Command* command);
 
   /**
-   * Toggles a command when the condition changes from `true` to the low
-   * state.
+   * Toggles a command when the condition changes from `true` to `false`.
    *
    * <p>Takes a raw pointer, and so is non-owning; users are responsible for the
    * lifespan of the command.
@@ -206,308 +203,6 @@
   Trigger ToggleOnFalse(CommandPtr&& command);
 
   /**
-   * Binds a command to start when the trigger becomes active. Takes a
-   * raw pointer, and so is non-owning; users are responsible for the lifespan
-   * of the command.
-   *
-   * @param command The command to bind.
-   * @return The trigger, for chained calls.
-   * @deprecated Use OnTrue(Command) instead
-   */
-  WPI_DEPRECATED("Use OnTrue(Command) instead")
-  Trigger WhenActive(Command* command);
-
-  /**
-   * Binds a command to start when the trigger becomes active. Transfers
-   * command ownership to the button scheduler, so the user does not have to
-   * worry about lifespan - rvalue refs will be *moved*, lvalue refs will be
-   * *copied.*
-   *
-   * @param command The command to bind.
-   * @return The trigger, for chained calls.
-   * @deprecated Use OnTrue(Command) instead
-   */
-  template <class T, typename = std::enable_if_t<std::is_base_of_v<
-                         Command, std::remove_reference_t<T>>>>
-  WPI_DEPRECATED("Use OnTrue(Command) instead")
-  Trigger WhenActive(T&& command) {
-    m_loop->Bind([condition = m_condition, previous = m_condition(),
-                  command = std::make_unique<std::remove_reference_t<T>>(
-                      std::forward<T>(command))]() mutable {
-      bool current = condition();
-
-      if (!previous && current) {
-        command->Schedule();
-      }
-
-      previous = current;
-    });
-    return *this;
-  }
-
-  /**
-   * Binds a runnable to execute when the trigger becomes active.
-   *
-   * @param toRun the runnable to execute.
-   * @param requirements the required subsystems.
-   * @deprecated Use OnTrue(Command) instead and construct the InstantCommand
-   * manually
-   */
-  WPI_DEPRECATED(
-      "Use OnTrue(Command) instead and construct the InstantCommand manually")
-  Trigger WhenActive(std::function<void()> toRun,
-                     std::initializer_list<Subsystem*> requirements);
-
-  /**
-   * Binds a runnable to execute when the trigger becomes active.
-   *
-   * @param toRun the runnable to execute.
-   * @param requirements the required subsystems.
-   * @deprecated Use OnTrue(Command) instead and construct the InstantCommand
-   * manually
-   */
-  WPI_DEPRECATED(
-      "Use OnTrue(Command) instead and construct the InstantCommand manually")
-  Trigger WhenActive(std::function<void()> toRun,
-                     std::span<Subsystem* const> requirements = {});
-
-  /**
-   * Binds a command to be started repeatedly while the trigger is active, and
-   * canceled when it becomes inactive.  Takes a raw pointer, and so is
-   * non-owning; users are responsible for the lifespan of the command.
-   *
-   * @param command The command to bind.
-   * @return The trigger, for chained calls.
-   * @deprecated Use WhileTrue(Command) with RepeatCommand, or bind
-   command::Schedule with IfHigh(std::function<void()>).
-   */
-  WPI_DEPRECATED(
-      "Use WhileTrue(Command) with RepeatCommand, or bind command::Schedule "
-      "with IfHigh(std::function<void()>).")
-  Trigger WhileActiveContinous(Command* command);
-
-  /**
-   * Binds a command to be started repeatedly while the trigger is active, and
-   * canceled when it becomes inactive.  Transfers command ownership to the
-   * button scheduler, so the user does not have to worry about lifespan -
-   * rvalue refs will be *moved*, lvalue refs will be *copied.*
-   *
-   * @param command The command to bind.
-   * @return The trigger, for chained calls.
-   * @deprecated Use WhileTrue(Command) with RepeatCommand, or bind
-   command::Schedule with IfHigh(std::function<void()>).
-   */
-  template <class T, typename = std::enable_if_t<std::is_base_of_v<
-                         Command, std::remove_reference_t<T>>>>
-  WPI_DEPRECATED(
-      "Use WhileTrue(Command) with RepeatCommand, or bind command::Schedule "
-      "with IfHigh(std::function<void()>).")
-  Trigger WhileActiveContinous(T&& command) {
-    m_loop->Bind([condition = m_condition, previous = m_condition(),
-                  command = std::make_unique<std::remove_reference_t<T>>(
-                      std::forward<T>(command))]() mutable {
-      bool current = condition();
-
-      if (current) {
-        command->Schedule();
-      } else if (previous && !current) {
-        command->Cancel();
-      }
-
-      previous = current;
-    });
-
-    return *this;
-  }
-
-  /**
-   * Binds a runnable to execute repeatedly while the trigger is active.
-   *
-   * @param toRun the runnable to execute.
-   * @param requirements the required subsystems.
-   * @deprecated Use WhileTrue(Command) and construct a RunCommand manually
-   */
-  WPI_DEPRECATED("Use WhileTrue(Command) and construct a RunCommand manually")
-  Trigger WhileActiveContinous(std::function<void()> toRun,
-                               std::initializer_list<Subsystem*> requirements);
-
-  /**
-   * Binds a runnable to execute repeatedly while the trigger is active.
-   *
-   * @param toRun the runnable to execute.
-   * @param requirements the required subsystems.
-   * @deprecated Use WhileTrue(Command) and construct a RunCommand manually
-   */
-  WPI_DEPRECATED("Use WhileTrue(Command) and construct a RunCommand manually")
-  Trigger WhileActiveContinous(std::function<void()> toRun,
-                               std::span<Subsystem* const> requirements = {});
-
-  /**
-   * Binds a command to be started when the trigger becomes active, and
-   * canceled when it becomes inactive.  Takes a raw pointer, and so is
-   * non-owning; users are responsible for the lifespan of the command.
-   *
-   * @param command The command to bind.
-   * @return The trigger, for chained calls.
-   * @deprecated Use WhileTrue(Command) instead.
-   */
-  WPI_DEPRECATED("Use WhileTrue(Command) instead.")
-  Trigger WhileActiveOnce(Command* command);
-
-  /**
-   * Binds a command to be started when the trigger becomes active, and
-   * canceled when it becomes inactive. Transfers command ownership to the
-   * button scheduler, so the user does not have to worry about lifespan -
-   * rvalue refs will be *moved*, lvalue refs will be *copied.*
-   *
-   * @param command The command to bind.
-   * @return The trigger, for chained calls.
-   * @deprecated Use WhileTrue(Command) instead.
-   */
-  template <class T, typename = std::enable_if_t<std::is_base_of_v<
-                         Command, std::remove_reference_t<T>>>>
-  WPI_DEPRECATED("Use WhileTrue(Command) instead.")
-  Trigger WhileActiveOnce(T&& command) {
-    m_loop->Bind([condition = m_condition, previous = m_condition(),
-                  command = std::make_unique<std::remove_reference_t<T>>(
-                      std::forward<T>(command))]() mutable {
-      bool current = condition();
-
-      if (!previous && current) {
-        command->Schedule();
-      } else if (previous && !current) {
-        command->Cancel();
-      }
-
-      previous = current;
-    });
-    return *this;
-  }
-
-  /**
-   * Binds a command to start when the trigger becomes inactive.  Takes a
-   * raw pointer, and so is non-owning; users are responsible for the lifespan
-   * of the command.
-   *
-   * @param command The command to bind.
-   * @return The trigger, for chained calls.
-   * @deprecated Use OnFalse(Command) instead.
-   */
-  WPI_DEPRECATED("Use OnFalse(Command) instead.")
-  Trigger WhenInactive(Command* command);
-
-  /**
-   * Binds a command to start when the trigger becomes inactive.  Transfers
-   * command ownership to the button scheduler, so the user does not have to
-   * worry about lifespan - rvalue refs will be *moved*, lvalue refs will be
-   * *copied.*
-   *
-   * @param command The command to bind.
-   * @return The trigger, for chained calls.
-   * @deprecated Use OnFalse(Command) instead.
-   */
-  template <class T, typename = std::enable_if_t<std::is_base_of_v<
-                         Command, std::remove_reference_t<T>>>>
-  WPI_DEPRECATED("Use OnFalse(Command) instead.")
-  Trigger WhenInactive(T&& command) {
-    m_loop->Bind([condition = m_condition, previous = m_condition(),
-                  command = std::make_unique<std::remove_reference_t<T>>(
-                      std::forward<T>(command))]() mutable {
-      bool current = condition();
-
-      if (previous && !current) {
-        command->Schedule();
-      }
-
-      previous = current;
-    });
-    return *this;
-  }
-
-  /**
-   * Binds a runnable to execute when the trigger becomes inactive.
-   *
-   * @param toRun the runnable to execute.
-   * @param requirements the required subsystems.
-   * @deprecated Use OnFalse(Command) instead and construct the InstantCommand
-   * manually
-   */
-  WPI_DEPRECATED(
-      "Use OnFalse(Command) instead and construct the InstantCommand manually")
-  Trigger WhenInactive(std::function<void()> toRun,
-                       std::initializer_list<Subsystem*> requirements);
-
-  /**
-   * Binds a runnable to execute when the trigger becomes inactive.
-   *
-   * @param toRun the runnable to execute.
-   * @param requirements the required subsystems.
-   * @deprecated Use OnFalse(Command) instead and construct the InstantCommand
-   * manually
-   */
-  WPI_DEPRECATED(
-      "Use OnFalse(Command) instead and construct the InstantCommand manually")
-  Trigger WhenInactive(std::function<void()> toRun,
-                       std::span<Subsystem* const> requirements = {});
-
-  /**
-   * Binds a command to start when the trigger becomes active, and be canceled
-   * when it again becomes active. Takes a raw pointer, and so is non-owning;
-   * users are responsible for the lifespan of the command.
-   *
-   * @param command The command to bind.
-   * @return The trigger, for chained calls.
-   * @deprecated Use ToggleOnTrue(Command) instead.
-   */
-  WPI_DEPRECATED("Use ToggleOnTrue(Command) instead.")
-  Trigger ToggleWhenActive(Command* command);
-
-  /**
-   * Binds a command to start when the trigger becomes active, and be canceled
-   * when it again becomes active.  Transfers command ownership to the button
-   * scheduler, so the user does not have to worry about lifespan - rvalue refs
-   * will be *moved*, lvalue refs will be *copied.*
-   *
-   * @param command The command to bind.
-   * @return The trigger, for chained calls.
-   * @deprecated Use ToggleOnTrue(Command) instead.
-   */
-  template <class T, typename = std::enable_if_t<std::is_base_of_v<
-                         Command, std::remove_reference_t<T>>>>
-  WPI_DEPRECATED("Use ToggleOnTrue(Command) instead.")
-  Trigger ToggleWhenActive(T&& command) {
-    m_loop->Bind([condition = m_condition, previous = m_condition(),
-                  command = std::make_unique<std::remove_reference_t<T>>(
-                      std::forward<T>(command))]() mutable {
-      bool current = condition();
-
-      if (!previous && current) {
-        if (command->IsScheduled()) {
-          command->Cancel();
-        } else {
-          command->Schedule();
-        }
-      }
-
-      previous = current;
-    });
-
-    return *this;
-  }
-
-  /**
-   * Binds a command to be canceled when the trigger becomes active.  Takes a
-   * raw pointer, and so is non-owning; users are responsible for the lifespan
-   *  and scheduling of the command.
-   *
-   * @param command The command to bind.
-   * @return The trigger, for chained calls.
-   * @deprecated Pass this as a command end condition with Until() instead.
-   */
-  WPI_DEPRECATED("Pass this as a command end condition with Until() instead.")
-  Trigger CancelWhenActive(Command* command);
-
-  /**
    * Composes two triggers with logical AND.
    *
    * @return A trigger which is active when both component triggers are active.
diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/CommandDecoratorTest.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/CommandDecoratorTest.java
index 392c23c..0f66acd 100644
--- a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/CommandDecoratorTest.java
+++ b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/CommandDecoratorTest.java
@@ -58,6 +58,22 @@
   }
 
   @Test
+  void onlyWhileTest() {
+    try (CommandScheduler scheduler = new CommandScheduler()) {
+      AtomicBoolean condition = new AtomicBoolean(true);
+
+      Command command = new WaitCommand(10).onlyWhile(condition::get);
+
+      scheduler.schedule(command);
+      scheduler.run();
+      assertTrue(scheduler.isScheduled(command));
+      condition.set(false);
+      scheduler.run();
+      assertFalse(scheduler.isScheduled(command));
+    }
+  }
+
+  @Test
   void ignoringDisableTest() {
     try (CommandScheduler scheduler = new CommandScheduler()) {
       var command = new RunCommand(() -> {}).ignoringDisable(true);
@@ -184,23 +200,6 @@
     }
   }
 
-  @SuppressWarnings("removal") // Command.perpetually()
-  @Test
-  void perpetuallyTest() {
-    try (CommandScheduler scheduler = new CommandScheduler()) {
-      Command command = new InstantCommand();
-
-      Command perpetual = command.perpetually();
-
-      scheduler.schedule(perpetual);
-      scheduler.run();
-      scheduler.run();
-      scheduler.run();
-
-      assertTrue(scheduler.isScheduled(perpetual));
-    }
-  }
-
   @Test
   void unlessTest() {
     try (CommandScheduler scheduler = new CommandScheduler()) {
@@ -222,6 +221,26 @@
   }
 
   @Test
+  void onlyIfTest() {
+    try (CommandScheduler scheduler = new CommandScheduler()) {
+      AtomicBoolean onlyIfCondition = new AtomicBoolean(false);
+      AtomicBoolean hasRunCondition = new AtomicBoolean(false);
+
+      Command command =
+          new InstantCommand(() -> hasRunCondition.set(true)).onlyIf(onlyIfCondition::get);
+
+      scheduler.schedule(command);
+      scheduler.run();
+      assertFalse(hasRunCondition.get());
+
+      onlyIfCondition.set(true);
+      scheduler.schedule(command);
+      scheduler.run();
+      assertTrue(hasRunCondition.get());
+    }
+  }
+
+  @Test
   void finallyDoTest() {
     try (CommandScheduler scheduler = new CommandScheduler()) {
       AtomicInteger first = new AtomicInteger(0);
diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/CommandScheduleTest.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/CommandScheduleTest.java
index c0295f5..793ad95 100644
--- a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/CommandScheduleTest.java
+++ b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/CommandScheduleTest.java
@@ -11,6 +11,8 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import edu.wpi.first.networktables.NetworkTableInstance;
+import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
 import org.junit.jupiter.api.Test;
 
 class CommandScheduleTest extends CommandTestBase {
@@ -116,4 +118,27 @@
       assertDoesNotThrow(() -> scheduler.cancel(mockCommand));
     }
   }
+
+  @Test
+  void smartDashboardCancelTest() {
+    try (CommandScheduler scheduler = new CommandScheduler();
+        var inst = NetworkTableInstance.create()) {
+      SmartDashboard.setNetworkTableInstance(inst);
+      SmartDashboard.putData("Scheduler", scheduler);
+      SmartDashboard.updateValues();
+
+      MockCommandHolder holder = new MockCommandHolder(true);
+      Command mockCommand = holder.getMock();
+      scheduler.schedule(mockCommand);
+      scheduler.run();
+      SmartDashboard.updateValues();
+      assertTrue(scheduler.isScheduled(mockCommand));
+
+      var table = inst.getTable("SmartDashboard");
+      table.getEntry("Scheduler/Cancel").setIntegerArray(new long[] {mockCommand.hashCode()});
+      SmartDashboard.updateValues();
+      scheduler.run();
+      assertFalse(scheduler.isScheduled(mockCommand));
+    }
+  }
 }
diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/CommandSendableButtonTest.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/CommandSendableButtonTest.java
new file mode 100644
index 0000000..c89ed7f
--- /dev/null
+++ b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/CommandSendableButtonTest.java
@@ -0,0 +1,114 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.wpilibj2.command;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import edu.wpi.first.networktables.BooleanPublisher;
+import edu.wpi.first.networktables.NetworkTableInstance;
+import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class CommandSendableButtonTest extends CommandTestBase {
+  private NetworkTableInstance m_inst;
+  private AtomicInteger m_schedule;
+  private AtomicInteger m_cancel;
+  private BooleanPublisher m_publish;
+  private Command m_command;
+
+  @BeforeEach
+  void setUp() {
+    m_inst = NetworkTableInstance.create();
+    SmartDashboard.setNetworkTableInstance(m_inst);
+    m_schedule = new AtomicInteger();
+    m_cancel = new AtomicInteger();
+    m_command = Commands.startEnd(m_schedule::incrementAndGet, m_cancel::incrementAndGet);
+    m_publish = m_inst.getBooleanTopic("/SmartDashboard/command/running").publish();
+    SmartDashboard.putData("command", m_command);
+    SmartDashboard.updateValues();
+  }
+
+  @Test
+  void trueAndNotScheduledSchedules() {
+    // Not scheduled and true -> scheduled
+    CommandScheduler.getInstance().run();
+    SmartDashboard.updateValues();
+    assertFalse(m_command.isScheduled());
+    assertEquals(0, m_schedule.get());
+    assertEquals(0, m_cancel.get());
+
+    m_publish.set(true);
+    SmartDashboard.updateValues();
+    CommandScheduler.getInstance().run();
+    assertTrue(m_command.isScheduled());
+    assertEquals(1, m_schedule.get());
+    assertEquals(0, m_cancel.get());
+  }
+
+  @Test
+  void trueAndScheduledNoOp() {
+    // Scheduled and true -> no-op
+    m_command.schedule();
+    CommandScheduler.getInstance().run();
+    SmartDashboard.updateValues();
+    assertTrue(m_command.isScheduled());
+    assertEquals(1, m_schedule.get());
+    assertEquals(0, m_cancel.get());
+
+    m_publish.set(true);
+    SmartDashboard.updateValues();
+    CommandScheduler.getInstance().run();
+    assertTrue(m_command.isScheduled());
+    assertEquals(1, m_schedule.get());
+    assertEquals(0, m_cancel.get());
+  }
+
+  @Test
+  void falseAndNotScheduledNoOp() {
+    // Not scheduled and false -> no-op
+    CommandScheduler.getInstance().run();
+    SmartDashboard.updateValues();
+    assertFalse(m_command.isScheduled());
+    assertEquals(0, m_schedule.get());
+    assertEquals(0, m_cancel.get());
+
+    m_publish.set(false);
+    SmartDashboard.updateValues();
+    CommandScheduler.getInstance().run();
+    assertFalse(m_command.isScheduled());
+    assertEquals(0, m_schedule.get());
+    assertEquals(0, m_cancel.get());
+  }
+
+  @Test
+  void falseAndScheduledCancel() {
+    // Scheduled and false -> cancel
+    m_command.schedule();
+    CommandScheduler.getInstance().run();
+    SmartDashboard.updateValues();
+    assertTrue(m_command.isScheduled());
+    assertEquals(1, m_schedule.get());
+    assertEquals(0, m_cancel.get());
+
+    m_publish.set(false);
+    SmartDashboard.updateValues();
+    CommandScheduler.getInstance().run();
+    assertFalse(m_command.isScheduled());
+    assertEquals(1, m_schedule.get());
+    assertEquals(1, m_cancel.get());
+  }
+
+  @AfterEach
+  void tearDown() {
+    m_publish.close();
+    m_inst.close();
+    SmartDashboard.setNetworkTableInstance(NetworkTableInstance.getDefault());
+  }
+}
diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/CommandTestBase.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/CommandTestBase.java
index 1d46fb5..13f6597 100644
--- a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/CommandTestBase.java
+++ b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/CommandTestBase.java
@@ -23,6 +23,7 @@
     CommandScheduler.getInstance().enable();
     CommandScheduler.getInstance().getActiveButtonLoop().clear();
     CommandScheduler.getInstance().clearComposedCommands();
+    CommandScheduler.getInstance().unregisterAllSubsystems();
 
     setDSEnabled(true);
   }
diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/ConditionalCommandTest.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/ConditionalCommandTest.java
index 420a8a7..4659f0e 100644
--- a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/ConditionalCommandTest.java
+++ b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/ConditionalCommandTest.java
@@ -4,11 +4,19 @@
 
 package edu.wpi.first.wpilibj2.command;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
+import edu.wpi.first.wpilibj2.command.Command.InterruptionBehavior;
+import java.util.function.BooleanSupplier;
+import java.util.stream.Stream;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 
 class ConditionalCommandTest extends CommandTestBase {
   @Test
@@ -60,4 +68,92 @@
       verify(command2, never()).end(true);
     }
   }
+
+  static Stream<Arguments> interruptible() {
+    return Stream.of(
+        arguments(
+            "AllCancelSelf",
+            InterruptionBehavior.kCancelSelf,
+            new WaitUntilCommand(() -> false)
+                .withInterruptBehavior(InterruptionBehavior.kCancelSelf),
+            new WaitUntilCommand(() -> false)
+                .withInterruptBehavior(InterruptionBehavior.kCancelSelf),
+            (BooleanSupplier) () -> true),
+        arguments(
+            "AllCancelIncoming",
+            InterruptionBehavior.kCancelIncoming,
+            new WaitUntilCommand(() -> false)
+                .withInterruptBehavior(InterruptionBehavior.kCancelIncoming),
+            new WaitUntilCommand(() -> false)
+                .withInterruptBehavior(InterruptionBehavior.kCancelIncoming),
+            (BooleanSupplier) () -> true),
+        arguments(
+            "OneCancelSelfOneIncoming",
+            InterruptionBehavior.kCancelSelf,
+            new WaitUntilCommand(() -> false)
+                .withInterruptBehavior(InterruptionBehavior.kCancelSelf),
+            new WaitUntilCommand(() -> false)
+                .withInterruptBehavior(InterruptionBehavior.kCancelIncoming),
+            (BooleanSupplier) () -> true),
+        arguments(
+            "OneCancelIncomingOneSelf",
+            InterruptionBehavior.kCancelSelf,
+            new WaitUntilCommand(() -> false)
+                .withInterruptBehavior(InterruptionBehavior.kCancelIncoming),
+            new WaitUntilCommand(() -> false)
+                .withInterruptBehavior(InterruptionBehavior.kCancelSelf),
+            (BooleanSupplier) () -> true));
+  }
+
+  @MethodSource
+  @ParameterizedTest(name = "interruptible[{index}]: {0}")
+  void interruptible(
+      @SuppressWarnings("unused") String name,
+      InterruptionBehavior expected,
+      Command command1,
+      Command command2,
+      BooleanSupplier selector) {
+    var command = Commands.either(command1, command2, selector);
+    assertEquals(expected, command.getInterruptionBehavior());
+  }
+
+  static Stream<Arguments> runsWhenDisabled() {
+    return Stream.of(
+        arguments(
+            "AllFalse",
+            false,
+            new WaitUntilCommand(() -> false).ignoringDisable(false),
+            new WaitUntilCommand(() -> false).ignoringDisable(false),
+            (BooleanSupplier) () -> true),
+        arguments(
+            "AllTrue",
+            true,
+            new WaitUntilCommand(() -> false).ignoringDisable(true),
+            new WaitUntilCommand(() -> false).ignoringDisable(true),
+            (BooleanSupplier) () -> true),
+        arguments(
+            "OneTrueOneFalse",
+            false,
+            new WaitUntilCommand(() -> false).ignoringDisable(true),
+            new WaitUntilCommand(() -> false).ignoringDisable(false),
+            (BooleanSupplier) () -> true),
+        arguments(
+            "OneFalseOneTrue",
+            false,
+            new WaitUntilCommand(() -> false).ignoringDisable(false),
+            new WaitUntilCommand(() -> false).ignoringDisable(true),
+            (BooleanSupplier) () -> true));
+  }
+
+  @MethodSource
+  @ParameterizedTest(name = "runsWhenDisabled[{index}]: {0}")
+  void runsWhenDisabled(
+      @SuppressWarnings("unused") String name,
+      boolean expected,
+      Command command1,
+      Command command2,
+      BooleanSupplier selector) {
+    var command = Commands.either(command1, command2, selector);
+    assertEquals(expected, command.runsWhenDisabled());
+  }
 }
diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/DeferredCommandTest.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/DeferredCommandTest.java
new file mode 100644
index 0000000..259ba0f
--- /dev/null
+++ b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/DeferredCommandTest.java
@@ -0,0 +1,85 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.wpilibj2.command;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.only;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Set;
+import java.util.function.Supplier;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class DeferredCommandTest extends CommandTestBase {
+  @ParameterizedTest
+  @ValueSource(booleans = {true, false})
+  void deferredFunctionsTest(boolean interrupted) {
+    MockCommandHolder innerCommand = new MockCommandHolder(false);
+    DeferredCommand command = new DeferredCommand(innerCommand::getMock, Set.of());
+
+    command.initialize();
+    verify(innerCommand.getMock()).initialize();
+
+    command.execute();
+    verify(innerCommand.getMock()).execute();
+
+    assertFalse(command.isFinished());
+    verify(innerCommand.getMock()).isFinished();
+
+    innerCommand.setFinished(true);
+    assertTrue(command.isFinished());
+    verify(innerCommand.getMock(), times(2)).isFinished();
+
+    command.end(interrupted);
+    verify(innerCommand.getMock()).end(interrupted);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Test
+  void deferredSupplierOnlyCalledDuringInit() {
+    try (CommandScheduler scheduler = new CommandScheduler()) {
+      Supplier<Command> supplier = (Supplier<Command>) mock(Supplier.class);
+      when(supplier.get()).thenReturn(Commands.none(), Commands.none());
+
+      DeferredCommand command = new DeferredCommand(supplier, Set.of());
+      verify(supplier, never()).get();
+
+      scheduler.schedule(command);
+      verify(supplier, only()).get();
+      scheduler.run();
+
+      scheduler.schedule(command);
+      verify(supplier, times(2)).get();
+    }
+  }
+
+  @Test
+  void deferredRequirementsTest() {
+    Subsystem subsystem = new Subsystem() {};
+    DeferredCommand command = new DeferredCommand(Commands::none, Set.of(subsystem));
+
+    assertTrue(command.getRequirements().contains(subsystem));
+  }
+
+  @Test
+  void deferredNullCommandTest() {
+    DeferredCommand command = new DeferredCommand(() -> null, Set.of());
+    assertDoesNotThrow(
+        () -> {
+          command.initialize();
+          command.execute();
+          command.isFinished();
+          command.end(false);
+        });
+  }
+}
diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/MecanumControllerCommandTest.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/MecanumControllerCommandTest.java
index b8ccea5..3ca1101 100644
--- a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/MecanumControllerCommandTest.java
+++ b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/MecanumControllerCommandTest.java
@@ -118,8 +118,7 @@
             this::setWheelSpeeds,
             subsystem);
 
-    m_timer.reset();
-    m_timer.start();
+    m_timer.restart();
 
     command.initialize();
     while (!command.isFinished()) {
diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/PerpetualCommandTest.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/PerpetualCommandTest.java
deleted file mode 100644
index 6b9690b..0000000
--- a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/PerpetualCommandTest.java
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-package edu.wpi.first.wpilibj2.command;
-
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import org.junit.jupiter.api.Test;
-
-class PerpetualCommandTest extends CommandTestBase {
-  @SuppressWarnings("removal") // PerpetualCommand
-  @Test
-  void perpetualCommandScheduleTest() {
-    try (CommandScheduler scheduler = new CommandScheduler()) {
-      PerpetualCommand command = new PerpetualCommand(new InstantCommand());
-
-      scheduler.schedule(command);
-      scheduler.run();
-
-      assertTrue(scheduler.isScheduled(command));
-    }
-  }
-}
diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/RepeatCommandTest.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/RepeatCommandTest.java
index f082ddb..53abdd8 100644
--- a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/RepeatCommandTest.java
+++ b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/RepeatCommandTest.java
@@ -14,54 +14,69 @@
     implements SingleCompositionTestBase<RepeatCommand> {
   @Test
   void callsMethodsCorrectly() {
-    var initCounter = new AtomicInteger(0);
-    var exeCounter = new AtomicInteger(0);
-    var isFinishedCounter = new AtomicInteger(0);
-    var endCounter = new AtomicInteger(0);
-    var isFinishedHook = new AtomicBoolean(false);
+    try (CommandScheduler scheduler = new CommandScheduler()) {
+      var initCounter = new AtomicInteger(0);
+      var exeCounter = new AtomicInteger(0);
+      var isFinishedCounter = new AtomicInteger(0);
+      var endCounter = new AtomicInteger(0);
+      var isFinishedHook = new AtomicBoolean(false);
 
-    final var command =
-        new FunctionalCommand(
-                initCounter::incrementAndGet,
-                exeCounter::incrementAndGet,
-                interrupted -> endCounter.incrementAndGet(),
-                () -> {
-                  isFinishedCounter.incrementAndGet();
-                  return isFinishedHook.get();
-                })
-            .repeatedly();
+      final var command =
+          new FunctionalCommand(
+                  initCounter::incrementAndGet,
+                  exeCounter::incrementAndGet,
+                  interrupted -> endCounter.incrementAndGet(),
+                  () -> {
+                    isFinishedCounter.incrementAndGet();
+                    return isFinishedHook.get();
+                  })
+              .repeatedly();
 
-    assertEquals(0, initCounter.get());
-    assertEquals(0, exeCounter.get());
-    assertEquals(0, isFinishedCounter.get());
-    assertEquals(0, endCounter.get());
+      assertEquals(0, initCounter.get());
+      assertEquals(0, exeCounter.get());
+      assertEquals(0, isFinishedCounter.get());
+      assertEquals(0, endCounter.get());
 
-    CommandScheduler.getInstance().schedule(command);
-    assertEquals(1, initCounter.get());
-    assertEquals(0, exeCounter.get());
-    assertEquals(0, isFinishedCounter.get());
-    assertEquals(0, endCounter.get());
+      scheduler.schedule(command);
+      assertEquals(1, initCounter.get());
+      assertEquals(0, exeCounter.get());
+      assertEquals(0, isFinishedCounter.get());
+      assertEquals(0, endCounter.get());
 
-    isFinishedHook.set(false);
-    CommandScheduler.getInstance().run();
-    assertEquals(1, initCounter.get());
-    assertEquals(1, exeCounter.get());
-    assertEquals(1, isFinishedCounter.get());
-    assertEquals(0, endCounter.get());
+      isFinishedHook.set(false);
+      scheduler.run();
+      assertEquals(1, initCounter.get());
+      assertEquals(1, exeCounter.get());
+      assertEquals(1, isFinishedCounter.get());
+      assertEquals(0, endCounter.get());
 
-    isFinishedHook.set(true);
-    CommandScheduler.getInstance().run();
-    assertEquals(1, initCounter.get());
-    assertEquals(2, exeCounter.get());
-    assertEquals(2, isFinishedCounter.get());
-    assertEquals(1, endCounter.get());
+      isFinishedHook.set(true);
+      scheduler.run();
+      assertEquals(1, initCounter.get());
+      assertEquals(2, exeCounter.get());
+      assertEquals(2, isFinishedCounter.get());
+      assertEquals(1, endCounter.get());
 
-    isFinishedHook.set(false);
-    CommandScheduler.getInstance().run();
-    assertEquals(2, initCounter.get());
-    assertEquals(3, exeCounter.get());
-    assertEquals(3, isFinishedCounter.get());
-    assertEquals(1, endCounter.get());
+      isFinishedHook.set(false);
+      scheduler.run();
+      assertEquals(2, initCounter.get());
+      assertEquals(3, exeCounter.get());
+      assertEquals(3, isFinishedCounter.get());
+      assertEquals(1, endCounter.get());
+
+      isFinishedHook.set(true);
+      scheduler.run();
+      assertEquals(2, initCounter.get());
+      assertEquals(4, exeCounter.get());
+      assertEquals(4, isFinishedCounter.get());
+      assertEquals(2, endCounter.get());
+
+      scheduler.cancel(command);
+      assertEquals(2, initCounter.get());
+      assertEquals(4, exeCounter.get());
+      assertEquals(4, isFinishedCounter.get());
+      assertEquals(2, endCounter.get());
+    }
   }
 
   @Override
diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/RobotDisabledCommandTest.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/RobotDisabledCommandTest.java
index 0914c25..a248810 100644
--- a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/RobotDisabledCommandTest.java
+++ b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/RobotDisabledCommandTest.java
@@ -141,8 +141,8 @@
     MockCommandHolder command4Holder = new MockCommandHolder(false);
     Command command4 = command4Holder.getMock();
 
-    Command runWhenDisabled = new SelectCommand(Map.of(1, command1, 2, command2), () -> 1);
-    Command dontRunWhenDisabled = new SelectCommand(Map.of(1, command3, 2, command4), () -> 1);
+    Command runWhenDisabled = new SelectCommand<>(Map.of(1, command1, 2, command2), () -> 1);
+    Command dontRunWhenDisabled = new SelectCommand<>(Map.of(1, command3, 2, command4), () -> 1);
 
     try (CommandScheduler scheduler = new CommandScheduler()) {
       scheduler.schedule(runWhenDisabled, dontRunWhenDisabled);
diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SchedulerTest.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SchedulerTest.java
index 7a62f5e..1e0b333 100644
--- a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SchedulerTest.java
+++ b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SchedulerTest.java
@@ -6,6 +6,9 @@
 
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.util.concurrent.atomic.AtomicInteger;
 import org.junit.jupiter.api.Test;
@@ -44,12 +47,110 @@
   }
 
   @Test
+  void schedulerInterruptNoCauseLambdaTest() {
+    try (CommandScheduler scheduler = new CommandScheduler()) {
+      AtomicInteger counter = new AtomicInteger();
+
+      scheduler.onCommandInterrupt(
+          (interrupted, cause) -> {
+            assertFalse(cause.isPresent());
+            counter.incrementAndGet();
+          });
+
+      Command command = Commands.run(() -> {});
+
+      scheduler.schedule(command);
+      scheduler.cancel(command);
+
+      assertEquals(1, counter.get());
+    }
+  }
+
+  @Test
+  void schedulerInterruptCauseLambdaTest() {
+    try (CommandScheduler scheduler = new CommandScheduler()) {
+      AtomicInteger counter = new AtomicInteger();
+
+      Subsystem subsystem = new Subsystem() {};
+      Command command = subsystem.run(() -> {});
+      Command interruptor = subsystem.runOnce(() -> {});
+
+      scheduler.onCommandInterrupt(
+          (interrupted, cause) -> {
+            assertTrue(cause.isPresent());
+            assertSame(interruptor, cause.get());
+            counter.incrementAndGet();
+          });
+
+      scheduler.schedule(command);
+      scheduler.schedule(interruptor);
+
+      assertEquals(1, counter.get());
+    }
+  }
+
+  @Test
+  void schedulerInterruptCauseLambdaInRunLoopTest() {
+    try (CommandScheduler scheduler = new CommandScheduler()) {
+      AtomicInteger counter = new AtomicInteger();
+
+      Subsystem subsystem = new Subsystem() {};
+      Command command = subsystem.run(() -> {});
+      Command interruptor = subsystem.runOnce(() -> {});
+      // This command will schedule interruptor in execute() inside the run loop
+      Command interruptorScheduler = Commands.runOnce(() -> scheduler.schedule(interruptor));
+
+      scheduler.onCommandInterrupt(
+          (interrupted, cause) -> {
+            assertTrue(cause.isPresent());
+            assertSame(interruptor, cause.get());
+            counter.incrementAndGet();
+          });
+
+      scheduler.schedule(command);
+      scheduler.schedule(interruptorScheduler);
+
+      scheduler.run();
+
+      assertEquals(1, counter.get());
+    }
+  }
+
+  @Test
+  void registerSubsystemTest() {
+    try (CommandScheduler scheduler = new CommandScheduler()) {
+      AtomicInteger counter = new AtomicInteger(0);
+      Subsystem system =
+          new SubsystemBase() {
+            @Override
+            public void periodic() {
+              counter.incrementAndGet();
+            }
+          };
+
+      assertDoesNotThrow(() -> scheduler.registerSubsystem(system));
+
+      scheduler.run();
+      assertEquals(1, counter.get());
+    }
+  }
+
+  @Test
   void unregisterSubsystemTest() {
     try (CommandScheduler scheduler = new CommandScheduler()) {
-      Subsystem system = new SubsystemBase() {};
-
+      AtomicInteger counter = new AtomicInteger(0);
+      Subsystem system =
+          new SubsystemBase() {
+            @Override
+            public void periodic() {
+              counter.incrementAndGet();
+            }
+          };
       scheduler.registerSubsystem(system);
       assertDoesNotThrow(() -> scheduler.unregisterSubsystem(system));
+
+      scheduler.run();
+      assertEquals(0, counter.get());
     }
   }
 
@@ -59,6 +160,7 @@
       AtomicInteger counter = new AtomicInteger();
 
       scheduler.onCommandInterrupt(command -> counter.incrementAndGet());
+      scheduler.onCommandInterrupt((command, interruptor) -> assertFalse(interruptor.isPresent()));
 
       Command command = new WaitCommand(10);
       Command command2 = new WaitCommand(10);
diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SchedulingRecursionTest.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SchedulingRecursionTest.java
index 28e9e6f..5e8fd39 100644
--- a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SchedulingRecursionTest.java
+++ b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SchedulingRecursionTest.java
@@ -25,9 +25,10 @@
   void cancelFromInitialize(InterruptionBehavior interruptionBehavior) {
     try (CommandScheduler scheduler = new CommandScheduler()) {
       AtomicBoolean hasOtherRun = new AtomicBoolean();
+      AtomicInteger counter = new AtomicInteger();
       Subsystem requirement = new SubsystemBase() {};
       Command selfCancels =
-          new CommandBase() {
+          new Command() {
             {
               addRequirements(requirement);
             }
@@ -38,6 +39,11 @@
             }
 
             @Override
+            public void end(boolean interrupted) {
+              counter.incrementAndGet();
+            }
+
+            @Override
             public InterruptionBehavior getInterruptionBehavior() {
               return interruptionBehavior;
             }
@@ -52,6 +58,47 @@
           });
       assertFalse(scheduler.isScheduled(selfCancels));
       assertTrue(scheduler.isScheduled(other));
+      assertEquals(1, counter.get());
+      scheduler.run();
+      assertTrue(hasOtherRun.get());
+    }
+  }
+
+  @EnumSource(InterruptionBehavior.class)
+  @ParameterizedTest
+  void cancelFromInitializeAction(InterruptionBehavior interruptionBehavior) {
+    try (CommandScheduler scheduler = new CommandScheduler()) {
+      AtomicBoolean hasOtherRun = new AtomicBoolean();
+      AtomicInteger counter = new AtomicInteger();
+      Subsystem requirement = new Subsystem() {};
+      Command selfCancels =
+          new Command() {
+            {
+              addRequirements(requirement);
+            }
+
+            @Override
+            public void end(boolean interrupted) {
+              counter.incrementAndGet();
+            }
+
+            @Override
+            public InterruptionBehavior getInterruptionBehavior() {
+              return interruptionBehavior;
+            }
+          };
+      Command other = new RunCommand(() -> hasOtherRun.set(true), requirement);
+
+      assertDoesNotThrow(
+          () -> {
+            scheduler.onCommandInitialize(cmd -> scheduler.cancel(selfCancels));
+            scheduler.schedule(selfCancels);
+            scheduler.run();
+            scheduler.schedule(other);
+          });
+      assertFalse(scheduler.isScheduled(selfCancels));
+      assertTrue(scheduler.isScheduled(other));
+      assertEquals(1, counter.get());
       scheduler.run();
       assertTrue(hasOtherRun.get());
     }
@@ -62,9 +109,10 @@
   void defaultCommandGetsRescheduledAfterSelfCanceling(InterruptionBehavior interruptionBehavior) {
     try (CommandScheduler scheduler = new CommandScheduler()) {
       AtomicBoolean hasOtherRun = new AtomicBoolean();
+      AtomicInteger counter = new AtomicInteger();
       Subsystem requirement = new SubsystemBase() {};
       Command selfCancels =
-          new CommandBase() {
+          new Command() {
             {
               addRequirements(requirement);
             }
@@ -75,6 +123,11 @@
             }
 
             @Override
+            public void end(boolean interrupted) {
+              counter.incrementAndGet();
+            }
+
+            @Override
             public InterruptionBehavior getInterruptionBehavior() {
               return interruptionBehavior;
             }
@@ -90,6 +143,7 @@
       scheduler.run();
       assertFalse(scheduler.isScheduled(selfCancels));
       assertTrue(scheduler.isScheduled(other));
+      assertEquals(1, counter.get());
       scheduler.run();
       assertTrue(hasOtherRun.get());
     }
@@ -100,7 +154,7 @@
     try (CommandScheduler scheduler = new CommandScheduler()) {
       AtomicInteger counter = new AtomicInteger();
       Command selfCancels =
-          new CommandBase() {
+          new Command() {
             @Override
             public void end(boolean interrupted) {
               counter.incrementAndGet();
@@ -116,23 +170,176 @@
   }
 
   @Test
+  void cancelFromInterruptAction() {
+    try (CommandScheduler scheduler = new CommandScheduler()) {
+      AtomicInteger counter = new AtomicInteger();
+      Command selfCancels = new RunCommand(() -> {});
+      scheduler.onCommandInterrupt(
+          cmd -> {
+            counter.incrementAndGet();
+            scheduler.cancel(selfCancels);
+          });
+      scheduler.schedule(selfCancels);
+
+      assertDoesNotThrow(() -> scheduler.cancel(selfCancels));
+      assertEquals(1, counter.get());
+      assertFalse(scheduler.isScheduled(selfCancels));
+    }
+  }
+
+  @Test
+  void cancelFromEndLoop() {
+    try (CommandScheduler scheduler = new CommandScheduler()) {
+      AtomicInteger counter = new AtomicInteger();
+      FunctionalCommand dCancelsAll =
+          new FunctionalCommand(
+              () -> {},
+              () -> {},
+              interrupted -> {
+                counter.incrementAndGet();
+                scheduler.cancelAll();
+              },
+              () -> true);
+      FunctionalCommand cCancelsD =
+          new FunctionalCommand(
+              () -> {},
+              () -> {},
+              interrupted -> {
+                counter.incrementAndGet();
+                scheduler.cancel(dCancelsAll);
+              },
+              () -> true);
+      FunctionalCommand bCancelsC =
+          new FunctionalCommand(
+              () -> {},
+              () -> {},
+              interrupted -> {
+                counter.incrementAndGet();
+                scheduler.cancel(cCancelsD);
+              },
+              () -> true);
+      FunctionalCommand aCancelsB =
+          new FunctionalCommand(
+              () -> {},
+              () -> {},
+              interrupted -> {
+                counter.incrementAndGet();
+                scheduler.cancel(bCancelsC);
+              },
+              () -> true);
+
+      scheduler.schedule(aCancelsB);
+      scheduler.schedule(bCancelsC);
+      scheduler.schedule(cCancelsD);
+      scheduler.schedule(dCancelsAll);
+
+      assertDoesNotThrow(() -> scheduler.cancel(aCancelsB));
+      assertEquals(4, counter.get());
+      assertFalse(scheduler.isScheduled(aCancelsB));
+      assertFalse(scheduler.isScheduled(bCancelsC));
+      assertFalse(scheduler.isScheduled(cCancelsD));
+      assertFalse(scheduler.isScheduled(dCancelsAll));
+    }
+  }
+
+  @Test
+  void cancelFromEndLoopWhileInRunLoop() {
+    try (CommandScheduler scheduler = new CommandScheduler()) {
+      AtomicInteger counter = new AtomicInteger();
+      FunctionalCommand dCancelsAll =
+          new FunctionalCommand(
+              () -> {},
+              () -> {},
+              interrupted -> {
+                counter.incrementAndGet();
+                scheduler.cancelAll();
+              },
+              () -> true);
+      FunctionalCommand cCancelsD =
+          new FunctionalCommand(
+              () -> {},
+              () -> {},
+              interrupted -> {
+                counter.incrementAndGet();
+                scheduler.cancel(dCancelsAll);
+              },
+              () -> true);
+      FunctionalCommand bCancelsC =
+          new FunctionalCommand(
+              () -> {},
+              () -> {},
+              interrupted -> {
+                counter.incrementAndGet();
+                scheduler.cancel(cCancelsD);
+              },
+              () -> true);
+      FunctionalCommand aCancelsB =
+          new FunctionalCommand(
+              () -> {},
+              () -> {},
+              interrupted -> {
+                counter.incrementAndGet();
+                scheduler.cancel(bCancelsC);
+              },
+              () -> true);
+
+      scheduler.schedule(aCancelsB);
+      scheduler.schedule(bCancelsC);
+      scheduler.schedule(cCancelsD);
+      scheduler.schedule(dCancelsAll);
+
+      assertDoesNotThrow(() -> scheduler.run());
+      assertEquals(4, counter.get());
+      assertFalse(scheduler.isScheduled(aCancelsB));
+      assertFalse(scheduler.isScheduled(bCancelsC));
+      assertFalse(scheduler.isScheduled(cCancelsD));
+      assertFalse(scheduler.isScheduled(dCancelsAll));
+    }
+  }
+
+  @Test
+  void multiCancelFromEnd() {
+    try (CommandScheduler scheduler = new CommandScheduler()) {
+      AtomicInteger counter = new AtomicInteger();
+      FunctionalCommand bIncrementsCounter =
+          new FunctionalCommand(
+              () -> {}, () -> {}, interrupted -> counter.incrementAndGet(), () -> true);
+      Command aCancelsB =
+          new Command() {
+            @Override
+            public void end(boolean interrupted) {
+              counter.incrementAndGet();
+              scheduler.cancel(bIncrementsCounter);
+              scheduler.cancel(this);
+            }
+          };
+
+      scheduler.schedule(aCancelsB);
+      scheduler.schedule(bIncrementsCounter);
+
+      assertDoesNotThrow(() -> scheduler.cancel(aCancelsB));
+      assertEquals(2, counter.get());
+      assertFalse(scheduler.isScheduled(aCancelsB));
+      assertFalse(scheduler.isScheduled(bIncrementsCounter));
+    }
+  }
+
+  @Test
   void scheduleFromEndCancel() {
     try (CommandScheduler scheduler = new CommandScheduler()) {
       AtomicInteger counter = new AtomicInteger();
       Subsystem requirement = new SubsystemBase() {};
       InstantCommand other = new InstantCommand(() -> {}, requirement);
-      Command selfCancels =
-          new CommandBase() {
-            {
-              addRequirements(requirement);
-            }
-
-            @Override
-            public void end(boolean interrupted) {
-              counter.incrementAndGet();
-              scheduler.schedule(other);
-            }
-          };
+      FunctionalCommand selfCancels =
+          new FunctionalCommand(
+              () -> {},
+              () -> {},
+              interrupted -> {
+                counter.incrementAndGet();
+                scheduler.schedule(other);
+              },
+              () -> false,
+              requirement);
 
       scheduler.schedule(selfCancels);
 
@@ -148,19 +355,38 @@
       AtomicInteger counter = new AtomicInteger();
       Subsystem requirement = new SubsystemBase() {};
       InstantCommand other = new InstantCommand(() -> {}, requirement);
-      Command selfCancels =
-          new CommandBase() {
-            {
-              addRequirements(requirement);
-            }
+      FunctionalCommand selfCancels =
+          new FunctionalCommand(
+              () -> {},
+              () -> {},
+              interrupted -> {
+                counter.incrementAndGet();
+                scheduler.schedule(other);
+              },
+              () -> false,
+              requirement);
 
-            @Override
-            public void end(boolean interrupted) {
-              counter.incrementAndGet();
-              scheduler.schedule(other);
-            }
-          };
+      scheduler.schedule(selfCancels);
 
+      assertDoesNotThrow(() -> scheduler.schedule(other));
+      assertEquals(1, counter.get());
+      assertFalse(scheduler.isScheduled(selfCancels));
+      assertTrue(scheduler.isScheduled(other));
+    }
+  }
+
+  @Test
+  void scheduleFromEndInterruptAction() {
+    try (CommandScheduler scheduler = new CommandScheduler()) {
+      AtomicInteger counter = new AtomicInteger();
+      Subsystem requirement = new Subsystem() {};
+      InstantCommand other = new InstantCommand(() -> {}, requirement);
+      InstantCommand selfCancels = new InstantCommand(() -> {}, requirement);
+      scheduler.onCommandInterrupt(
+          cmd -> {
+            counter.incrementAndGet();
+            scheduler.schedule(other);
+          });
       scheduler.schedule(selfCancels);
 
       assertDoesNotThrow(() -> scheduler.schedule(other));
@@ -178,18 +404,16 @@
       Subsystem requirement = new SubsystemBase() {};
       Command other =
           new InstantCommand(() -> {}, requirement).withInterruptBehavior(interruptionBehavior);
-      Command defaultCommand =
-          new CommandBase() {
-            {
-              addRequirements(requirement);
-            }
-
-            @Override
-            public void initialize() {
-              counter.incrementAndGet();
-              scheduler.schedule(other);
-            }
-          };
+      FunctionalCommand defaultCommand =
+          new FunctionalCommand(
+              () -> {
+                counter.incrementAndGet();
+                scheduler.schedule(other);
+              },
+              () -> {},
+              interrupted -> {},
+              () -> false,
+              requirement);
 
       scheduler.setDefaultCommand(requirement, defaultCommand);
 
@@ -201,4 +425,41 @@
       assertTrue(scheduler.isScheduled(other));
     }
   }
+
+  @Test
+  void cancelDefaultCommandFromEnd() {
+    try (CommandScheduler scheduler = new CommandScheduler()) {
+      AtomicInteger counter = new AtomicInteger();
+      Subsystem requirement = new Subsystem() {};
+      Command defaultCommand =
+          new FunctionalCommand(
+              () -> {},
+              () -> {},
+              interrupted -> counter.incrementAndGet(),
+              () -> false,
+              requirement);
+      Command other = new InstantCommand(() -> {}, requirement);
+      Command cancelDefaultCommand =
+          new FunctionalCommand(
+              () -> {},
+              () -> {},
+              interrupted -> {
+                counter.incrementAndGet();
+                scheduler.schedule(other);
+              },
+              () -> false);
+
+      assertDoesNotThrow(
+          () -> {
+            scheduler.schedule(cancelDefaultCommand);
+            scheduler.setDefaultCommand(requirement, defaultCommand);
+
+            scheduler.run();
+            scheduler.cancel(cancelDefaultCommand);
+          });
+      assertEquals(2, counter.get());
+      assertFalse(scheduler.isScheduled(defaultCommand));
+      assertTrue(scheduler.isScheduled(other));
+    }
+  }
 }
diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SelectCommandTest.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SelectCommandTest.java
index 736f120..0ce3b79 100644
--- a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SelectCommandTest.java
+++ b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SelectCommandTest.java
@@ -13,7 +13,8 @@
 import java.util.Map;
 import org.junit.jupiter.api.Test;
 
-class SelectCommandTest extends CommandTestBase implements MultiCompositionTestBase<SelectCommand> {
+class SelectCommandTest extends CommandTestBase
+    implements MultiCompositionTestBase<SelectCommand<Integer>> {
   @Test
   void selectCommandTest() {
     try (CommandScheduler scheduler = new CommandScheduler()) {
@@ -25,8 +26,8 @@
       MockCommandHolder command3Holder = new MockCommandHolder(true);
       Command command3 = command3Holder.getMock();
 
-      SelectCommand selectCommand =
-          new SelectCommand(
+      SelectCommand<String> selectCommand =
+          new SelectCommand<>(
               Map.ofEntries(
                   Map.entry("one", command1),
                   Map.entry("two", command2),
@@ -61,8 +62,8 @@
       MockCommandHolder command3Holder = new MockCommandHolder(true);
       Command command3 = command3Holder.getMock();
 
-      SelectCommand selectCommand =
-          new SelectCommand(
+      SelectCommand<String> selectCommand =
+          new SelectCommand<>(
               Map.ofEntries(
                   Map.entry("one", command1),
                   Map.entry("two", command2),
@@ -88,8 +89,8 @@
       MockCommandHolder command3Holder = new MockCommandHolder(true, system3, system4);
       Command command3 = command3Holder.getMock();
 
-      SelectCommand selectCommand =
-          new SelectCommand(
+      SelectCommand<String> selectCommand =
+          new SelectCommand<>(
               Map.ofEntries(
                   Map.entry("one", command1),
                   Map.entry("two", command2),
@@ -108,11 +109,11 @@
   }
 
   @Override
-  public SelectCommand compose(Command... members) {
-    var map = new HashMap<Object, Command>();
+  public SelectCommand<Integer> compose(Command... members) {
+    var map = new HashMap<Integer, Command>();
     for (int i = 0; i < members.length; i++) {
       map.put(i, members[i]);
     }
-    return new SelectCommand(map, () -> 0);
+    return new SelectCommand<>(map, () -> 0);
   }
 }
diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SwerveControllerCommandTest.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SwerveControllerCommandTest.java
index 1367e51..9dd03ae 100644
--- a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SwerveControllerCommandTest.java
+++ b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SwerveControllerCommandTest.java
@@ -114,8 +114,7 @@
             this::setModuleStates,
             subsystem);
 
-    m_timer.reset();
-    m_timer.start();
+    m_timer.restart();
 
     command.initialize();
     while (!command.isFinished()) {
diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/button/TriggerTest.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/button/TriggerTest.java
index 82d359c..87f8d16 100644
--- a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/button/TriggerTest.java
+++ b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/button/TriggerTest.java
@@ -194,34 +194,6 @@
     assertEquals(1, endCounter.get());
   }
 
-  // Binding runnables directly is deprecated -- users should create the command manually
-  @SuppressWarnings("deprecation")
-  @Test
-  void runnableBindingTest() {
-    InternalButton buttonWhenActive = new InternalButton();
-    InternalButton buttonWhileActiveContinuous = new InternalButton();
-    InternalButton buttonWhenInactive = new InternalButton();
-
-    buttonWhenActive.setPressed(false);
-    buttonWhileActiveContinuous.setPressed(true);
-    buttonWhenInactive.setPressed(true);
-
-    AtomicInteger counter = new AtomicInteger(0);
-
-    buttonWhenActive.whenPressed(counter::incrementAndGet);
-    buttonWhileActiveContinuous.whileActiveContinuous(counter::incrementAndGet);
-    buttonWhenInactive.whenInactive(counter::incrementAndGet);
-
-    CommandScheduler scheduler = CommandScheduler.getInstance();
-
-    scheduler.run();
-    buttonWhenActive.setPressed(true);
-    buttonWhenInactive.setPressed(false);
-    scheduler.run();
-
-    assertEquals(counter.get(), 4);
-  }
-
   @Test
   void triggerCompositionTest() {
     InternalButton button1 = new InternalButton();
diff --git a/wpilibNewCommands/src/test/native/cpp/frc2/command/AddRequirementsTest.cpp b/wpilibNewCommands/src/test/native/cpp/frc2/command/AddRequirementsTest.cpp
new file mode 100644
index 0000000..8ef5154
--- /dev/null
+++ b/wpilibNewCommands/src/test/native/cpp/frc2/command/AddRequirementsTest.cpp
@@ -0,0 +1,144 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include <wpi/array.h>
+
+#include "CommandTestBase.h"
+#include "frc2/command/Command.h"
+#include "frc2/command/RunCommand.h"
+
+using namespace frc2;
+
+// Class to verify the overload resolution of Command::AddRequirements. This
+// does not derive from Command because AddRequirements is non-virtual,
+// preventing overriding anyways.
+class MockAddRequirements {
+ public:
+  MOCK_METHOD(void, AddRequirements, (Requirements), ());
+  MOCK_METHOD(void, AddRequirements, ((wpi::SmallSet<Subsystem*, 4>)), ());
+  MOCK_METHOD(void, AddRequirements, (Subsystem*), ());
+};
+
+TEST(AddRequirementsTest, InitializerListOverloadResolution) {
+  TestSubsystem requirement;
+
+  MockAddRequirements overloadResolver;
+
+  EXPECT_CALL(overloadResolver, AddRequirements(testing::An<Requirements>()));
+
+  overloadResolver.AddRequirements({&requirement, &requirement});
+}
+
+TEST(AddRequirementsTest, SpanOverloadResolution) {
+  std::span<Subsystem* const> requirementsSpan;
+
+  MockAddRequirements overloadResolver;
+
+  EXPECT_CALL(overloadResolver, AddRequirements(testing::An<Requirements>()));
+
+  overloadResolver.AddRequirements(requirementsSpan);
+}
+
+TEST(AddRequirementsTest, SmallSetOverloadResolution) {
+  wpi::SmallSet<Subsystem*, 4> requirementsSet;
+
+  MockAddRequirements overloadResolver;
+
+  EXPECT_CALL(overloadResolver,
+              AddRequirements(testing::An<wpi::SmallSet<Subsystem*, 4>>()));
+
+  overloadResolver.AddRequirements(requirementsSet);
+}
+
+TEST(AddRequirementsTest, SubsystemOverloadResolution) {
+  TestSubsystem requirement;
+
+  MockAddRequirements overloadResolver;
+
+  EXPECT_CALL(overloadResolver, AddRequirements(testing::An<Subsystem*>()));
+
+  overloadResolver.AddRequirements(&requirement);
+}
+
+TEST(AddRequirementsTest, InitializerListSemantics) {
+  TestSubsystem requirement1;
+  TestSubsystem requirement2;
+
+  RunCommand command([] {});
+  command.AddRequirements({&requirement1, &requirement2});
+  EXPECT_TRUE(command.HasRequirement(&requirement1));
+  EXPECT_TRUE(command.HasRequirement(&requirement2));
+  EXPECT_EQ(command.GetRequirements().size(), 2u);
+}
+
+TEST(AddRequirementsTest, InitializerListDuplicatesSemantics) {
+  TestSubsystem requirement;
+
+  RunCommand command([] {});
+  command.AddRequirements({&requirement, &requirement});
+  EXPECT_TRUE(command.HasRequirement(&requirement));
+  EXPECT_EQ(command.GetRequirements().size(), 1u);
+}
+
+TEST(AddRequirementsTest, SpanSemantics) {
+  TestSubsystem requirement1;
+  TestSubsystem requirement2;
+
+  wpi::array<Subsystem* const, 2> requirementsArray(&requirement1,
+                                                    &requirement2);
+
+  RunCommand command([] {});
+  command.AddRequirements(std::span{requirementsArray});
+  EXPECT_TRUE(command.HasRequirement(&requirement1));
+  EXPECT_TRUE(command.HasRequirement(&requirement2));
+  EXPECT_EQ(command.GetRequirements().size(), 2u);
+}
+
+TEST(AddRequirementsTest, SpanDuplicatesSemantics) {
+  TestSubsystem requirement;
+
+  wpi::array<Subsystem* const, 2> requirementsArray(&requirement, &requirement);
+
+  RunCommand command([] {});
+  command.AddRequirements(std::span{requirementsArray});
+  EXPECT_TRUE(command.HasRequirement(&requirement));
+  EXPECT_EQ(command.GetRequirements().size(), 1u);
+}
+
+TEST(AddRequirementsTest, SmallSetSemantics) {
+  TestSubsystem requirement1;
+  TestSubsystem requirement2;
+
+  wpi::SmallSet<Subsystem*, 4> requirementsSet;
+  requirementsSet.insert(&requirement1);
+  requirementsSet.insert(&requirement2);
+
+  RunCommand command([] {});
+  command.AddRequirements(requirementsSet);
+  EXPECT_TRUE(command.HasRequirement(&requirement1));
+  EXPECT_TRUE(command.HasRequirement(&requirement2));
+  EXPECT_EQ(command.GetRequirements().size(), 2u);
+}
+
+TEST(AddRequirementsTest, SubsystemPointerSemantics) {
+  TestSubsystem requirement1;
+  TestSubsystem requirement2;
+
+  RunCommand command([] {});
+  command.AddRequirements(&requirement1);
+  command.AddRequirements(&requirement2);
+  EXPECT_TRUE(command.HasRequirement(&requirement1));
+  EXPECT_TRUE(command.HasRequirement(&requirement2));
+  EXPECT_EQ(command.GetRequirements().size(), 2u);
+}
+
+TEST(AddRequirementsTest, SubsystemPointerDuplicatesSemantics) {
+  TestSubsystem requirement;
+
+  RunCommand command([] {});
+  command.AddRequirements(&requirement);
+  command.AddRequirements(&requirement);
+  EXPECT_TRUE(command.HasRequirement(&requirement));
+  EXPECT_EQ(command.GetRequirements().size(), 1u);
+}
diff --git a/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandDecoratorTest.cpp b/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandDecoratorTest.cpp
index 5ab184c..884d504 100644
--- a/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandDecoratorTest.cpp
+++ b/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandDecoratorTest.cpp
@@ -9,7 +9,6 @@
 #include "frc2/command/FunctionalCommand.h"
 #include "frc2/command/InstantCommand.h"
 #include "frc2/command/ParallelRaceGroup.h"
-#include "frc2/command/PerpetualCommand.h"
 #include "frc2/command/RunCommand.h"
 #include "frc2/command/SequentialCommandGroup.h"
 
@@ -54,6 +53,24 @@
   EXPECT_FALSE(scheduler.IsScheduled(command));
 }
 
+TEST_F(CommandDecoratorTest, OnlyWhile) {
+  CommandScheduler scheduler = GetScheduler();
+
+  bool run = true;
+
+  auto command = RunCommand([] {}, {}).OnlyWhile([&run] { return run; });
+
+  scheduler.Schedule(command);
+
+  scheduler.Run();
+  EXPECT_TRUE(scheduler.IsScheduled(command));
+
+  run = false;
+
+  scheduler.Run();
+  EXPECT_FALSE(scheduler.IsScheduled(command));
+}
+
 TEST_F(CommandDecoratorTest, IgnoringDisable) {
   CommandScheduler scheduler = GetScheduler();
 
@@ -104,21 +121,6 @@
   EXPECT_TRUE(finished);
 }
 
-TEST_F(CommandDecoratorTest, Perpetually) {
-  CommandScheduler scheduler = GetScheduler();
-
-  WPI_IGNORE_DEPRECATED
-  auto command = InstantCommand([] {}, {}).Perpetually();
-  WPI_UNIGNORE_DEPRECATED
-
-  scheduler.Schedule(&command);
-
-  scheduler.Run();
-  scheduler.Run();
-
-  EXPECT_TRUE(scheduler.IsScheduled(&command));
-}
-
 TEST_F(CommandDecoratorTest, Unless) {
   CommandScheduler scheduler = GetScheduler();
 
@@ -140,6 +142,27 @@
   EXPECT_TRUE(hasRun);
 }
 
+TEST_F(CommandDecoratorTest, OnlyIf) {
+  CommandScheduler scheduler = GetScheduler();
+
+  bool hasRun = false;
+  bool onlyIfBool = false;
+
+  auto command =
+      InstantCommand([&hasRun] { hasRun = true; }, {}).OnlyIf([&onlyIfBool] {
+        return onlyIfBool;
+      });
+
+  scheduler.Schedule(command);
+  scheduler.Run();
+  EXPECT_FALSE(hasRun);
+
+  onlyIfBool = true;
+  scheduler.Schedule(command);
+  scheduler.Run();
+  EXPECT_TRUE(hasRun);
+}
+
 TEST_F(CommandDecoratorTest, FinallyDo) {
   CommandScheduler scheduler = GetScheduler();
   int first = 0;
diff --git a/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandPtrTest.cpp b/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandPtrTest.cpp
index 26077f2..6c57c7f 100644
--- a/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandPtrTest.cpp
+++ b/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandPtrTest.cpp
@@ -27,6 +27,7 @@
   EXPECT_NO_FATAL_FAILURE(scheduler.Cancel(movedTo));
 
   EXPECT_THROW(scheduler.Schedule(movedFrom), frc::RuntimeError);
+  // NOLINTNEXTLINE(clang-analyzer-cplusplus.Move)
   EXPECT_THROW(movedFrom.IsScheduled(), frc::RuntimeError);
   EXPECT_THROW(static_cast<void>(std::move(movedFrom).Repeatedly()),
                frc::RuntimeError);
diff --git a/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandRequirementsTest.cpp b/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandRequirementsTest.cpp
index 79a472f..b46bb8b 100644
--- a/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandRequirementsTest.cpp
+++ b/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandRequirementsTest.cpp
@@ -12,7 +12,6 @@
 #include "frc2/command/ParallelCommandGroup.h"
 #include "frc2/command/ParallelDeadlineGroup.h"
 #include "frc2/command/ParallelRaceGroup.h"
-#include "frc2/command/SelectCommand.h"
 #include "frc2/command/SequentialCommandGroup.h"
 
 using namespace frc2;
diff --git a/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandScheduleTest.cpp b/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandScheduleTest.cpp
index cb16b48..9178677 100644
--- a/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandScheduleTest.cpp
+++ b/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandScheduleTest.cpp
@@ -2,6 +2,9 @@
 // Open Source Software; you can modify and/or share it under the terms of
 // the WPILib BSD license file in the root directory of this project.
 
+#include <frc/smartdashboard/SmartDashboard.h>
+#include <networktables/NetworkTableInstance.h>
+
 #include "CommandTestBase.h"
 
 using namespace frc2;
@@ -98,3 +101,24 @@
 
   EXPECT_NO_FATAL_FAILURE(scheduler.Cancel(&command));
 }
+
+TEST_F(CommandScheduleTest, SmartDashboardCancel) {
+  CommandScheduler scheduler = GetScheduler();
+  frc::SmartDashboard::PutData("Scheduler", &scheduler);
+  frc::SmartDashboard::UpdateValues();
+
+  MockCommand command;
+  scheduler.Schedule(&command);
+  scheduler.Run();
+  frc::SmartDashboard::UpdateValues();
+  EXPECT_TRUE(scheduler.IsScheduled(&command));
+
+  uintptr_t ptrTmp = reinterpret_cast<uintptr_t>(&command);
+  nt::NetworkTableInstance::GetDefault()
+      .GetEntry("/SmartDashboard/Scheduler/Cancel")
+      .SetIntegerArray(
+          std::span<const int64_t>{{static_cast<int64_t>(ptrTmp)}});
+  frc::SmartDashboard::UpdateValues();
+  scheduler.Run();
+  EXPECT_FALSE(scheduler.IsScheduled(&command));
+}
diff --git a/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandSendableButtonTest.cpp b/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandSendableButtonTest.cpp
new file mode 100644
index 0000000..08caa5c
--- /dev/null
+++ b/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandSendableButtonTest.cpp
@@ -0,0 +1,98 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include <frc2/command/Commands.h>
+
+#include <frc/smartdashboard/SmartDashboard.h>
+#include <networktables/BooleanTopic.h>
+#include <networktables/NetworkTableInstance.h>
+
+#include "CommandTestBase.h"
+
+using namespace frc2;
+
+class CommandSendableButtonTest : public CommandTestBase {
+ protected:
+  int m_schedule;
+  int m_cancel;
+  nt::BooleanPublisher m_publish;
+  std::optional<CommandPtr> m_command;
+
+  void SetUp() override {
+    m_schedule = 0;
+    m_cancel = 0;
+    m_command = cmd::StartEnd([this] { m_schedule++; }, [this] { m_cancel++; });
+    m_publish = nt::NetworkTableInstance::GetDefault()
+                    .GetBooleanTopic("/SmartDashboard/command/running")
+                    .Publish();
+    frc::SmartDashboard::PutData("command", m_command->get());
+    frc::SmartDashboard::UpdateValues();
+  }
+};
+
+TEST_F(CommandSendableButtonTest, trueAndNotScheduledSchedules) {
+  // Not scheduled and true -> scheduled
+  GetScheduler().Run();
+  frc::SmartDashboard::UpdateValues();
+  EXPECT_FALSE(m_command->IsScheduled());
+  EXPECT_EQ(0, m_schedule);
+  EXPECT_EQ(0, m_cancel);
+
+  m_publish.Set(true);
+  frc::SmartDashboard::UpdateValues();
+  GetScheduler().Run();
+  EXPECT_TRUE(m_command->IsScheduled());
+  EXPECT_EQ(1, m_schedule);
+  EXPECT_EQ(0, m_cancel);
+}
+
+TEST_F(CommandSendableButtonTest, trueAndScheduledNoOp) {
+  // Scheduled and true -> no-op
+  m_command->Schedule();
+  GetScheduler().Run();
+  frc::SmartDashboard::UpdateValues();
+  EXPECT_TRUE(m_command->IsScheduled());
+  EXPECT_EQ(1, m_schedule);
+  EXPECT_EQ(0, m_cancel);
+
+  m_publish.Set(true);
+  frc::SmartDashboard::UpdateValues();
+  GetScheduler().Run();
+  EXPECT_TRUE(m_command->IsScheduled());
+  EXPECT_EQ(1, m_schedule);
+  EXPECT_EQ(0, m_cancel);
+}
+
+TEST_F(CommandSendableButtonTest, falseAndNotScheduledNoOp) {
+  // Not scheduled and false -> no-op
+  GetScheduler().Run();
+  frc::SmartDashboard::UpdateValues();
+  EXPECT_FALSE(m_command->IsScheduled());
+  EXPECT_EQ(0, m_schedule);
+  EXPECT_EQ(0, m_cancel);
+
+  m_publish.Set(false);
+  frc::SmartDashboard::UpdateValues();
+  GetScheduler().Run();
+  EXPECT_FALSE(m_command->IsScheduled());
+  EXPECT_EQ(0, m_schedule);
+  EXPECT_EQ(0, m_cancel);
+}
+
+TEST_F(CommandSendableButtonTest, falseAndScheduledCancel) {
+  // Scheduled and false -> cancel
+  m_command->Schedule();
+  GetScheduler().Run();
+  frc::SmartDashboard::UpdateValues();
+  EXPECT_TRUE(m_command->IsScheduled());
+  EXPECT_EQ(1, m_schedule);
+  EXPECT_EQ(0, m_cancel);
+
+  m_publish.Set(false);
+  frc::SmartDashboard::UpdateValues();
+  GetScheduler().Run();
+  EXPECT_FALSE(m_command->IsScheduled());
+  EXPECT_EQ(1, m_schedule);
+  EXPECT_EQ(1, m_cancel);
+}
diff --git a/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandTestBase.cpp b/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandTestBase.cpp
index 6e27d24..1bd180c 100644
--- a/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandTestBase.cpp
+++ b/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandTestBase.cpp
@@ -11,22 +11,22 @@
   scheduler.CancelAll();
   scheduler.Enable();
   scheduler.GetActiveButtonLoop()->Clear();
+  scheduler.UnregisterAllSubsystems();
+
+  SetDSEnabled(true);
+}
+
+CommandTestBase::~CommandTestBase() {
+  CommandScheduler::GetInstance().GetActiveButtonLoop()->Clear();
+  CommandScheduler::GetInstance().UnregisterAllSubsystems();
 }
 
 CommandScheduler CommandTestBase::GetScheduler() {
   return CommandScheduler();
 }
 
-void CommandTestBase::SetUp() {
-  frc::sim::DriverStationSim::SetEnabled(true);
-  frc::sim::DriverStationSim::NotifyNewData();
-}
-
-void CommandTestBase::TearDown() {
-  CommandScheduler::GetInstance().GetActiveButtonLoop()->Clear();
-}
-
 void CommandTestBase::SetDSEnabled(bool enabled) {
+  frc::sim::DriverStationSim::SetDsAttached(true);
   frc::sim::DriverStationSim::SetEnabled(enabled);
   frc::sim::DriverStationSim::NotifyNewData();
 }
diff --git a/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandTestBase.h b/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandTestBase.h
index a1ab1de..586432d 100644
--- a/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandTestBase.h
+++ b/wpilibNewCommands/src/test/native/cpp/frc2/command/CommandTestBase.h
@@ -4,92 +4,99 @@
 
 #pragma once
 
+#include <functional>
 #include <memory>
 #include <utility>
 
 #include <frc/simulation/DriverStationSim.h>
+#include <gtest/gtest.h>
 
 #include "frc2/command/CommandHelper.h"
 #include "frc2/command/CommandScheduler.h"
-#include "frc2/command/SetUtilities.h"
+#include "frc2/command/Requirements.h"
 #include "frc2/command/SubsystemBase.h"
 #include "gmock/gmock.h"
-#include "gtest/gtest.h"
 #include "make_vector.h"
 
 namespace frc2 {
 
+class TestSubsystem : public SubsystemBase {
+ public:
+  explicit TestSubsystem(std::function<void()> periodic = [] {})
+      : m_periodic{periodic} {}
+  void Periodic() override { m_periodic(); }
+
+ private:
+  std::function<void()> m_periodic;
+};
+
+/**
+ * NOTE: Moving mock objects causes EXPECT_CALL to not work correctly!
+ */
+class MockCommand : public CommandHelper<Command, MockCommand> {
+ public:
+  MOCK_CONST_METHOD0(GetRequirements, wpi::SmallSet<Subsystem*, 4>());
+  MOCK_METHOD0(IsFinished, bool());
+  MOCK_CONST_METHOD0(RunsWhenDisabled, bool());
+  MOCK_METHOD0(Initialize, void());
+  MOCK_METHOD0(Execute, void());
+  MOCK_METHOD1(End, void(bool interrupted));
+
+  MockCommand() {
+    m_requirements = {};
+    EXPECT_CALL(*this, GetRequirements())
+        .WillRepeatedly(::testing::Return(m_requirements));
+    EXPECT_CALL(*this, IsFinished()).WillRepeatedly(::testing::Return(false));
+    EXPECT_CALL(*this, RunsWhenDisabled())
+        .WillRepeatedly(::testing::Return(true));
+  }
+
+  explicit MockCommand(Requirements requirements, bool finished = false,
+                       bool runWhenDisabled = true) {
+    m_requirements.insert(requirements.begin(), requirements.end());
+    EXPECT_CALL(*this, GetRequirements())
+        .WillRepeatedly(::testing::Return(m_requirements));
+    EXPECT_CALL(*this, IsFinished())
+        .WillRepeatedly(::testing::Return(finished));
+    EXPECT_CALL(*this, RunsWhenDisabled())
+        .WillRepeatedly(::testing::Return(runWhenDisabled));
+  }
+
+  MockCommand(MockCommand&& other) {
+    EXPECT_CALL(*this, IsFinished())
+        .WillRepeatedly(::testing::Return(other.IsFinished()));
+    EXPECT_CALL(*this, RunsWhenDisabled())
+        .WillRepeatedly(::testing::Return(other.RunsWhenDisabled()));
+    std::swap(m_requirements, other.m_requirements);
+    EXPECT_CALL(*this, GetRequirements())
+        .WillRepeatedly(::testing::Return(m_requirements));
+  }
+
+  MockCommand(const MockCommand& other) : CommandHelper{other} {}
+
+  void SetFinished(bool finished) {
+    EXPECT_CALL(*this, IsFinished())
+        .WillRepeatedly(::testing::Return(finished));
+  }
+
+  ~MockCommand() {  // NOLINT
+    auto& scheduler = CommandScheduler::GetInstance();
+    scheduler.Cancel(this);
+  }
+
+ private:
+  wpi::SmallSet<Subsystem*, 4> m_requirements;
+};
+
 class CommandTestBase : public ::testing::Test {
  public:
   CommandTestBase();
 
-  class TestSubsystem : public SubsystemBase {};
+  ~CommandTestBase() override;
 
  protected:
-  /**
-   * NOTE: Moving mock objects causes EXPECT_CALL to not work correctly!
-   */
-  class MockCommand : public CommandHelper<CommandBase, MockCommand> {
-   public:
-    MOCK_CONST_METHOD0(GetRequirements, wpi::SmallSet<Subsystem*, 4>());
-    MOCK_METHOD0(IsFinished, bool());
-    MOCK_CONST_METHOD0(RunsWhenDisabled, bool());
-    MOCK_METHOD0(Initialize, void());
-    MOCK_METHOD0(Execute, void());
-    MOCK_METHOD1(End, void(bool interrupted));
-
-    MockCommand() {
-      m_requirements = {};
-      EXPECT_CALL(*this, GetRequirements())
-          .WillRepeatedly(::testing::Return(m_requirements));
-      EXPECT_CALL(*this, IsFinished()).WillRepeatedly(::testing::Return(false));
-      EXPECT_CALL(*this, RunsWhenDisabled())
-          .WillRepeatedly(::testing::Return(true));
-    }
-
-    MockCommand(std::initializer_list<Subsystem*> requirements,
-                bool finished = false, bool runWhenDisabled = true) {
-      m_requirements.insert(requirements.begin(), requirements.end());
-      EXPECT_CALL(*this, GetRequirements())
-          .WillRepeatedly(::testing::Return(m_requirements));
-      EXPECT_CALL(*this, IsFinished())
-          .WillRepeatedly(::testing::Return(finished));
-      EXPECT_CALL(*this, RunsWhenDisabled())
-          .WillRepeatedly(::testing::Return(runWhenDisabled));
-    }
-
-    MockCommand(MockCommand&& other) {
-      EXPECT_CALL(*this, IsFinished())
-          .WillRepeatedly(::testing::Return(other.IsFinished()));
-      EXPECT_CALL(*this, RunsWhenDisabled())
-          .WillRepeatedly(::testing::Return(other.RunsWhenDisabled()));
-      std::swap(m_requirements, other.m_requirements);
-      EXPECT_CALL(*this, GetRequirements())
-          .WillRepeatedly(::testing::Return(m_requirements));
-    }
-
-    MockCommand(const MockCommand& other) : CommandHelper{other} {}
-
-    void SetFinished(bool finished) {
-      EXPECT_CALL(*this, IsFinished())
-          .WillRepeatedly(::testing::Return(finished));
-    }
-
-    ~MockCommand() {  // NOLINT
-      auto& scheduler = CommandScheduler::GetInstance();
-      scheduler.Cancel(this);
-    }
-
-   private:
-    wpi::SmallSet<Subsystem*, 4> m_requirements;
-  };
-
   CommandScheduler GetScheduler();
 
-  void SetUp() override;
-
-  void TearDown() override;
-
   void SetDSEnabled(bool enabled);
 };
 
@@ -101,81 +108,23 @@
     scheduler.CancelAll();
     scheduler.Enable();
     scheduler.GetActiveButtonLoop()->Clear();
+    scheduler.UnregisterAllSubsystems();
+
+    SetDSEnabled(true);
   }
 
-  class TestSubsystem : public SubsystemBase {};
+  ~CommandTestBaseWithParam() override {
+    CommandScheduler::GetInstance().GetActiveButtonLoop()->Clear();
+    CommandScheduler::GetInstance().UnregisterAllSubsystems();
+  }
 
  protected:
-  class MockCommand : public Command {
-   public:
-    MOCK_CONST_METHOD0(GetRequirements, wpi::SmallSet<Subsystem*, 4>());
-    MOCK_METHOD0(IsFinished, bool());
-    MOCK_CONST_METHOD0(RunsWhenDisabled, bool());
-    MOCK_METHOD0(Initialize, void());
-    MOCK_METHOD0(Execute, void());
-    MOCK_METHOD1(End, void(bool interrupted));
-
-    MockCommand() {
-      m_requirements = {};
-      EXPECT_CALL(*this, GetRequirements())
-          .WillRepeatedly(::testing::Return(m_requirements));
-      EXPECT_CALL(*this, IsFinished()).WillRepeatedly(::testing::Return(false));
-      EXPECT_CALL(*this, RunsWhenDisabled())
-          .WillRepeatedly(::testing::Return(true));
-    }
-
-    MockCommand(std::initializer_list<Subsystem*> requirements,
-                bool finished = false, bool runWhenDisabled = true) {
-      m_requirements.insert(requirements.begin(), requirements.end());
-      EXPECT_CALL(*this, GetRequirements())
-          .WillRepeatedly(::testing::Return(m_requirements));
-      EXPECT_CALL(*this, IsFinished())
-          .WillRepeatedly(::testing::Return(finished));
-      EXPECT_CALL(*this, RunsWhenDisabled())
-          .WillRepeatedly(::testing::Return(runWhenDisabled));
-    }
-
-    MockCommand(MockCommand&& other) {
-      EXPECT_CALL(*this, IsFinished())
-          .WillRepeatedly(::testing::Return(other.IsFinished()));
-      EXPECT_CALL(*this, RunsWhenDisabled())
-          .WillRepeatedly(::testing::Return(other.RunsWhenDisabled()));
-      std::swap(m_requirements, other.m_requirements);
-      EXPECT_CALL(*this, GetRequirements())
-          .WillRepeatedly(::testing::Return(m_requirements));
-    }
-
-    MockCommand(const MockCommand& other) : Command{other} {}
-
-    void SetFinished(bool finished) {
-      EXPECT_CALL(*this, IsFinished())
-          .WillRepeatedly(::testing::Return(finished));
-    }
-
-    ~MockCommand() {  // NOLINT
-      auto& scheduler = CommandScheduler::GetInstance();
-      scheduler.Cancel(this);
-    }
-
-   protected:
-    std::unique_ptr<Command> TransferOwnership() && {  // NOLINT
-      return std::make_unique<MockCommand>(std::move(*this));
-    }
-
-   private:
-    wpi::SmallSet<Subsystem*, 4> m_requirements;
-  };
-
   CommandScheduler GetScheduler() { return CommandScheduler(); }
 
-  void SetUp() override { frc::sim::DriverStationSim::SetEnabled(true); }
-
-  void TearDown() override {
-    CommandScheduler::GetInstance().GetActiveButtonLoop()->Clear();
-  }
-
   void SetDSEnabled(bool enabled) {
+    frc::sim::DriverStationSim::SetDsAttached(true);
     frc::sim::DriverStationSim::SetEnabled(enabled);
+    frc::sim::DriverStationSim::NotifyNewData();
   }
 };
 
diff --git a/wpilibNewCommands/src/test/native/cpp/frc2/command/CompositionTestBase.h b/wpilibNewCommands/src/test/native/cpp/frc2/command/CompositionTestBase.h
index c12922f..58fbcc3 100644
--- a/wpilibNewCommands/src/test/native/cpp/frc2/command/CompositionTestBase.h
+++ b/wpilibNewCommands/src/test/native/cpp/frc2/command/CompositionTestBase.h
@@ -7,9 +7,10 @@
 #include <memory>
 #include <utility>
 
+#include <gtest/gtest.h>
+
 #include "CommandTestBase.h"
 #include "frc2/command/Commands.h"
-#include "gtest/gtest.h"
 #include "make_vector.h"
 
 namespace frc2 {
diff --git a/wpilibNewCommands/src/test/native/cpp/frc2/command/ConditionalCommandTest.cpp b/wpilibNewCommands/src/test/native/cpp/frc2/command/ConditionalCommandTest.cpp
index 27b89e5..d7bbd26 100644
--- a/wpilibNewCommands/src/test/native/cpp/frc2/command/ConditionalCommandTest.cpp
+++ b/wpilibNewCommands/src/test/native/cpp/frc2/command/ConditionalCommandTest.cpp
@@ -3,9 +3,9 @@
 // the WPILib BSD license file in the root directory of this project.
 
 #include "CommandTestBase.h"
+#include "frc2/command/Commands.h"
 #include "frc2/command/ConditionalCommand.h"
 #include "frc2/command/InstantCommand.h"
-#include "frc2/command/SelectCommand.h"
 
 using namespace frc2;
 class ConditionalCommandTest : public CommandTestBase {};
@@ -51,3 +51,87 @@
   EXPECT_TRUE(scheduler.IsScheduled(&command3));
   EXPECT_FALSE(scheduler.IsScheduled(&conditional));
 }
+
+TEST_F(ConditionalCommandTest, AllTrue) {
+  CommandPtr command =
+      cmd::Either(cmd::WaitUntil([] { return false; }).IgnoringDisable(true),
+                  cmd::WaitUntil([] { return false; }).IgnoringDisable(true),
+                  [] { return true; });
+  EXPECT_EQ(true, command.get()->RunsWhenDisabled());
+}
+
+TEST_F(ConditionalCommandTest, AllFalse) {
+  CommandPtr command =
+      cmd::Either(cmd::WaitUntil([] { return false; }).IgnoringDisable(false),
+                  cmd::WaitUntil([] { return false; }).IgnoringDisable(false),
+                  [] { return true; });
+  EXPECT_EQ(false, command.get()->RunsWhenDisabled());
+}
+
+TEST_F(ConditionalCommandTest, OneTrueOneFalse) {
+  CommandPtr command =
+      cmd::Either(cmd::WaitUntil([] { return false; }).IgnoringDisable(true),
+                  cmd::WaitUntil([] { return false; }).IgnoringDisable(false),
+                  [] { return true; });
+  EXPECT_EQ(false, command.get()->RunsWhenDisabled());
+}
+
+TEST_F(ConditionalCommandTest, TwoFalseOneTrue) {
+  CommandPtr command =
+      cmd::Either(cmd::WaitUntil([] { return false; }).IgnoringDisable(false),
+                  cmd::WaitUntil([] { return false; }).IgnoringDisable(true),
+                  [] { return true; });
+  EXPECT_EQ(false, command.get()->RunsWhenDisabled());
+}
+
+TEST_F(ConditionalCommandTest, AllCancelSelf) {
+  CommandPtr command = cmd::Either(
+      cmd::WaitUntil([] {
+        return false;
+      }).WithInterruptBehavior(Command::InterruptionBehavior::kCancelSelf),
+      cmd::WaitUntil([] {
+        return false;
+      }).WithInterruptBehavior(Command::InterruptionBehavior::kCancelSelf),
+      [] { return true; });
+  EXPECT_EQ(Command::InterruptionBehavior::kCancelSelf,
+            command.get()->GetInterruptionBehavior());
+}
+
+TEST_F(ConditionalCommandTest, AllCancelIncoming) {
+  CommandPtr command = cmd::Either(
+      cmd::WaitUntil([] {
+        return false;
+      }).WithInterruptBehavior(Command::InterruptionBehavior::kCancelIncoming),
+      cmd::WaitUntil([] {
+        return false;
+      }).WithInterruptBehavior(Command::InterruptionBehavior::kCancelIncoming),
+      [] { return false; });
+  EXPECT_EQ(Command::InterruptionBehavior::kCancelIncoming,
+            command.get()->GetInterruptionBehavior());
+}
+
+TEST_F(ConditionalCommandTest, OneCancelSelfOneIncoming) {
+  CommandPtr command = cmd::Either(
+      cmd::WaitUntil([] {
+        return false;
+      }).WithInterruptBehavior(Command::InterruptionBehavior::kCancelSelf),
+      cmd::WaitUntil([] {
+        return false;
+      }).WithInterruptBehavior(Command::InterruptionBehavior::kCancelIncoming),
+      [] { return false; });
+  EXPECT_EQ(Command::InterruptionBehavior::kCancelSelf,
+            command.get()->GetInterruptionBehavior());
+}
+
+TEST_F(ConditionalCommandTest, OneCancelIncomingOneSelf) {
+  CommandPtr command = cmd::Either(
+      cmd::WaitUntil([] {
+        return false;
+      }).WithInterruptBehavior(Command::InterruptionBehavior::kCancelIncoming),
+      cmd::WaitUntil([] {
+        return false;
+      }).WithInterruptBehavior(Command::InterruptionBehavior::kCancelSelf),
+      [] { return false; });
+  EXPECT_EQ(Command::InterruptionBehavior::kCancelSelf,
+            command.get()->GetInterruptionBehavior());
+}
diff --git a/wpilibNewCommands/src/test/native/cpp/frc2/command/DeferredCommandTest.cpp b/wpilibNewCommands/src/test/native/cpp/frc2/command/DeferredCommandTest.cpp
new file mode 100644
index 0000000..1af8ae4
--- /dev/null
+++ b/wpilibNewCommands/src/test/native/cpp/frc2/command/DeferredCommandTest.cpp
@@ -0,0 +1,75 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include "CommandTestBase.h"
+#include "frc2/command/Commands.h"
+#include "frc2/command/DeferredCommand.h"
+#include "frc2/command/FunctionalCommand.h"
+
+using namespace frc2;
+
+class DeferredFunctionsTest : public CommandTestBaseWithParam<bool> {};
+
+TEST_P(DeferredFunctionsTest, DeferredFunctions) {
+  int initializeCount = 0;
+  int executeCount = 0;
+  int isFinishedCount = 0;
+  int endCount = 0;
+  bool finished = false;
+
+  DeferredCommand deferred{[&] {
+                             return FunctionalCommand{
+                                 [&] { initializeCount++; },
+                                 [&] { executeCount++; },
+                                 [&](bool interrupted) {
+                                   EXPECT_EQ(interrupted, GetParam());
+                                   endCount++;
+                                 },
+                                 [&] {
+                                   isFinishedCount++;
+                                   return finished;
+                                 }}
+                                 .ToPtr();
+                           },
+                           {}};
+
+  deferred.Initialize();
+  EXPECT_EQ(1, initializeCount);
+  deferred.Execute();
+  EXPECT_EQ(1, executeCount);
+  EXPECT_FALSE(deferred.IsFinished());
+  EXPECT_EQ(1, isFinishedCount);
+  finished = true;
+  EXPECT_TRUE(deferred.IsFinished());
+  EXPECT_EQ(2, isFinishedCount);
+  deferred.End(GetParam());
+  EXPECT_EQ(1, endCount);
+}
+
+INSTANTIATE_TEST_SUITE_P(DeferredCommandTests, DeferredFunctionsTest,
+                         testing::Values(true, false));
+
+TEST(DeferredCommandTest, DeferredSupplierOnlyCalledDuringInit) {
+  int count = 0;
+  DeferredCommand command{[&count] {
+                            count++;
+                            return cmd::None();
+                          },
+                          {}};
+
+  EXPECT_EQ(0, count);
+  command.Initialize();
+  EXPECT_EQ(1, count);
+  command.Execute();
+  command.IsFinished();
+  command.End(false);
+  EXPECT_EQ(1, count);
+}
+
+TEST(DeferredCommandTest, DeferredRequirements) {
+  TestSubsystem subsystem;
+  DeferredCommand command{cmd::None, {&subsystem}};
+
+  EXPECT_TRUE(command.GetRequirements().contains(&subsystem));
+}
diff --git a/wpilibNewCommands/src/test/native/cpp/frc2/command/MecanumControllerCommandTest.cpp b/wpilibNewCommands/src/test/native/cpp/frc2/command/MecanumControllerCommandTest.cpp
index 7f5b590..c79432d 100644
--- a/wpilibNewCommands/src/test/native/cpp/frc2/command/MecanumControllerCommandTest.cpp
+++ b/wpilibNewCommands/src/test/native/cpp/frc2/command/MecanumControllerCommandTest.cpp
@@ -16,8 +16,9 @@
 #include <frc/kinematics/MecanumDriveOdometry.h>
 #include <frc/simulation/SimHooks.h>
 #include <frc/trajectory/TrajectoryGenerator.h>
+#include <gtest/gtest.h>
 
-#include "gtest/gtest.h"
+#include "CommandTestBase.h"
 
 #define EXPECT_NEAR_UNITS(val1, val2, eps) \
   EXPECT_LE(units::math::abs(val1 - val2), eps)
@@ -87,7 +88,7 @@
 };
 
 TEST_F(MecanumControllerCommandTest, ReachesReference) {
-  frc2::Subsystem subsystem;
+  frc2::TestSubsystem subsystem;
 
   auto waypoints =
       std::vector{frc::Pose2d{0_m, 0_m, 0_rad}, frc::Pose2d{1_m, 5_m, 3_rad}};
@@ -99,7 +100,7 @@
   auto command = frc2::MecanumControllerCommand(
       trajectory, [&]() { return getRobotPose(); }, m_kinematics,
 
-      frc2::PIDController(0.6, 0, 0), frc2::PIDController(0.6, 0, 0),
+      frc::PIDController(0.6, 0, 0), frc::PIDController(0.6, 0, 0),
       m_rotController, 8.8_mps,
       [&](units::meters_per_second_t frontLeft,
           units::meters_per_second_t rearLeft,
@@ -112,8 +113,7 @@
       },
       {&subsystem});
 
-  m_timer.Reset();
-  m_timer.Start();
+  m_timer.Restart();
 
   command.Initialize();
   while (!command.IsFinished()) {
diff --git a/wpilibNewCommands/src/test/native/cpp/frc2/command/POVButtonTest.cpp b/wpilibNewCommands/src/test/native/cpp/frc2/command/POVButtonTest.cpp
index bcb7dec..035fd49 100644
--- a/wpilibNewCommands/src/test/native/cpp/frc2/command/POVButtonTest.cpp
+++ b/wpilibNewCommands/src/test/native/cpp/frc2/command/POVButtonTest.cpp
@@ -4,13 +4,13 @@
 
 #include <frc/Joystick.h>
 #include <frc/simulation/JoystickSim.h>
+#include <gtest/gtest.h>
 
 #include "CommandTestBase.h"
 #include "frc2/command/CommandScheduler.h"
 #include "frc2/command/RunCommand.h"
 #include "frc2/command/WaitUntilCommand.h"
 #include "frc2/command/button/POVButton.h"
-#include "gtest/gtest.h"
 
 using namespace frc2;
 class POVButtonTest : public CommandTestBase {};
diff --git a/wpilibNewCommands/src/test/native/cpp/frc2/command/PerpetualCommandTest.cpp b/wpilibNewCommands/src/test/native/cpp/frc2/command/PerpetualCommandTest.cpp
deleted file mode 100644
index b53835b..0000000
--- a/wpilibNewCommands/src/test/native/cpp/frc2/command/PerpetualCommandTest.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "CommandTestBase.h"
-#include "frc2/command/InstantCommand.h"
-#include "frc2/command/PerpetualCommand.h"
-
-using namespace frc2;
-class PerpetualCommandTest : public CommandTestBase {};
-
-TEST_F(PerpetualCommandTest, PerpetualCommandSchedule) {
-  CommandScheduler scheduler = GetScheduler();
-
-  bool check = false;
-
-  WPI_IGNORE_DEPRECATED
-  PerpetualCommand command{InstantCommand([&check] { check = true; }, {})};
-  WPI_UNIGNORE_DEPRECATED
-
-  scheduler.Schedule(&command);
-  scheduler.Run();
-  EXPECT_TRUE(scheduler.IsScheduled(&command));
-  EXPECT_TRUE(check);
-}
diff --git a/wpilibNewCommands/src/test/native/cpp/frc2/command/RepeatCommandTest.cpp b/wpilibNewCommands/src/test/native/cpp/frc2/command/RepeatCommandTest.cpp
index b715983..b5456fa 100644
--- a/wpilibNewCommands/src/test/native/cpp/frc2/command/RepeatCommandTest.cpp
+++ b/wpilibNewCommands/src/test/native/cpp/frc2/command/RepeatCommandTest.cpp
@@ -60,6 +60,19 @@
   EXPECT_EQ(3, exeCounter);
   EXPECT_EQ(3, isFinishedCounter);
   EXPECT_EQ(1, endCounter);
+
+  isFinishedHook = true;
+  scheduler.Run();
+  EXPECT_EQ(2, initCounter);
+  EXPECT_EQ(4, exeCounter);
+  EXPECT_EQ(4, isFinishedCounter);
+  EXPECT_EQ(2, endCounter);
+
+  command.Cancel();
+  EXPECT_EQ(2, initCounter);
+  EXPECT_EQ(4, exeCounter);
+  EXPECT_EQ(4, isFinishedCounter);
+  EXPECT_EQ(2, endCounter);
 }
 
 INSTANTIATE_SINGLE_COMMAND_COMPOSITION_TEST_SUITE(RepeatCommandTest,
diff --git a/wpilibNewCommands/src/test/native/cpp/frc2/command/SchedulerTest.cpp b/wpilibNewCommands/src/test/native/cpp/frc2/command/SchedulerTest.cpp
index ce02ba0..ef97bb0 100644
--- a/wpilibNewCommands/src/test/native/cpp/frc2/command/SchedulerTest.cpp
+++ b/wpilibNewCommands/src/test/native/cpp/frc2/command/SchedulerTest.cpp
@@ -43,14 +43,98 @@
   EXPECT_EQ(counter, 1);
 }
 
+TEST_F(SchedulerTest, SchedulerLambdaInterruptNoCause) {
+  CommandScheduler scheduler = GetScheduler();
+
+  int counter = 0;
+
+  scheduler.OnCommandInterrupt(
+      [&counter](const Command&, const std::optional<Command*>& interruptor) {
+        EXPECT_FALSE(interruptor);
+        counter++;
+      });
+
+  RunCommand command([] {});
+
+  scheduler.Schedule(&command);
+  scheduler.Cancel(&command);
+
+  EXPECT_EQ(1, counter);
+}
+
+TEST_F(SchedulerTest, SchedulerLambdaInterruptCause) {
+  CommandScheduler scheduler = GetScheduler();
+
+  int counter = 0;
+
+  TestSubsystem subsystem{};
+  RunCommand command([] {}, {&subsystem});
+  InstantCommand interruptor([] {}, {&subsystem});
+
+  scheduler.OnCommandInterrupt(
+      [&](const Command&, const std::optional<Command*>& cause) {
+        ASSERT_TRUE(cause);
+        EXPECT_EQ(&interruptor, *cause);
+        counter++;
+      });
+
+  scheduler.Schedule(&command);
+  scheduler.Schedule(&interruptor);
+
+  EXPECT_EQ(1, counter);
+}
+
+TEST_F(SchedulerTest, SchedulerLambdaInterruptCauseInRunLoop) {
+  CommandScheduler scheduler = GetScheduler();
+
+  int counter = 0;
+
+  TestSubsystem subsystem{};
+  RunCommand command([] {}, {&subsystem});
+  InstantCommand interruptor([] {}, {&subsystem});
+  // This command will schedule interruptor in execute() inside the run loop
+  InstantCommand interruptorScheduler(
+      [&] { scheduler.Schedule(&interruptor); });
+
+  scheduler.OnCommandInterrupt(
+      [&](const Command&, const std::optional<Command*>& cause) {
+        ASSERT_TRUE(cause);
+        EXPECT_EQ(&interruptor, *cause);
+        counter++;
+      });
+
+  scheduler.Schedule(&command);
+  scheduler.Schedule(&interruptorScheduler);
+
+  scheduler.Run();
+
+  EXPECT_EQ(1, counter);
+}
+
+TEST_F(SchedulerTest, RegisterSubsystem) {
+  CommandScheduler scheduler = GetScheduler();
+
+  int counter = 0;
+  TestSubsystem system{[&counter] { counter++; }};
+
+  EXPECT_NO_FATAL_FAILURE(scheduler.RegisterSubsystem(&system));
+
+  scheduler.Run();
+  EXPECT_EQ(counter, 1);
+}
+
 TEST_F(SchedulerTest, UnregisterSubsystem) {
   CommandScheduler scheduler = GetScheduler();
 
-  TestSubsystem system;
+  int counter = 0;
+  TestSubsystem system{[&counter] { counter++; }};
 
   scheduler.RegisterSubsystem(&system);
 
   EXPECT_NO_FATAL_FAILURE(scheduler.UnregisterSubsystem(&system));
+
+  scheduler.Run();
+  ASSERT_EQ(counter, 0);
 }
 
 TEST_F(SchedulerTest, SchedulerCancelAll) {
@@ -62,6 +146,10 @@
   int counter = 0;
 
   scheduler.OnCommandInterrupt([&counter](const Command&) { counter++; });
+  scheduler.OnCommandInterrupt(
+      [](const Command&, const std::optional<Command*>& interruptor) {
+        EXPECT_FALSE(interruptor);
+      });
 
   scheduler.Schedule(&command);
   scheduler.Schedule(&command2);
diff --git a/wpilibNewCommands/src/test/native/cpp/frc2/command/SchedulingRecursionTest.cpp b/wpilibNewCommands/src/test/native/cpp/frc2/command/SchedulingRecursionTest.cpp
index 4aa5199..3735303 100644
--- a/wpilibNewCommands/src/test/native/cpp/frc2/command/SchedulingRecursionTest.cpp
+++ b/wpilibNewCommands/src/test/native/cpp/frc2/command/SchedulingRecursionTest.cpp
@@ -2,11 +2,13 @@
 // Open Source Software; you can modify and/or share it under the terms of
 // the WPILib BSD license file in the root directory of this project.
 
+#include <gtest/gtest.h>
+
 #include "CommandTestBase.h"
 #include "frc2/command/Command.h"
 #include "frc2/command/CommandHelper.h"
+#include "frc2/command/FunctionalCommand.h"
 #include "frc2/command/RunCommand.h"
-#include "gtest/gtest.h"
 
 using namespace frc2;
 
@@ -14,23 +16,29 @@
     : public CommandTestBaseWithParam<Command::InterruptionBehavior> {};
 
 class SelfCancellingCommand
-    : public CommandHelper<CommandBase, SelfCancellingCommand> {
+    : public CommandHelper<Command, SelfCancellingCommand> {
  public:
-  SelfCancellingCommand(CommandScheduler* scheduler, Subsystem* requirement,
+  SelfCancellingCommand(CommandScheduler* scheduler, int& counter,
+                        Subsystem* requirement,
                         Command::InterruptionBehavior interruptionBehavior =
                             Command::InterruptionBehavior::kCancelSelf)
-      : m_scheduler(scheduler), m_interrupt(interruptionBehavior) {
+      : m_scheduler(scheduler),
+        m_counter(counter),
+        m_interrupt(interruptionBehavior) {
     AddRequirements(requirement);
   }
 
   void Initialize() override { m_scheduler->Cancel(this); }
 
+  void End(bool interrupted) override { m_counter++; }
+
   InterruptionBehavior GetInterruptionBehavior() const override {
     return m_interrupt;
   }
 
  private:
   CommandScheduler* m_scheduler;
+  int& m_counter;
   InterruptionBehavior m_interrupt;
 };
 
@@ -38,13 +46,14 @@
  * Checks <a
  * href="https://github.com/wpilibsuite/allwpilib/issues/4259">wpilibsuite/allwpilib#4259</a>.
  */
-TEST_F(SchedulingRecursionTest, CancelFromInitialize) {
+TEST_P(SchedulingRecursionTest, CancelFromInitialize) {
   CommandScheduler scheduler = GetScheduler();
   bool hasOtherRun = false;
+  int counter = 0;
   TestSubsystem requirement;
-  auto selfCancels = SelfCancellingCommand(&scheduler, &requirement);
-  RunCommand other =
-      RunCommand([&hasOtherRun] { hasOtherRun = true; }, {&requirement});
+  SelfCancellingCommand selfCancels{&scheduler, counter, &requirement,
+                                    GetParam()};
+  RunCommand other{[&hasOtherRun] { hasOtherRun = true; }, {&requirement}};
 
   scheduler.Schedule(&selfCancels);
   scheduler.Run();
@@ -52,19 +61,45 @@
 
   EXPECT_FALSE(scheduler.IsScheduled(&selfCancels));
   EXPECT_TRUE(scheduler.IsScheduled(&other));
+  EXPECT_EQ(1, counter);
+  scheduler.Run();
+  EXPECT_TRUE(hasOtherRun);
+}
+
+TEST_F(SchedulingRecursionTest, CancelFromInitializeAction) {
+  CommandScheduler scheduler = GetScheduler();
+  bool hasOtherRun = false;
+  int counter = 0;
+  TestSubsystem requirement;
+  FunctionalCommand selfCancels{[] {},
+                                [] {},
+                                [&counter](bool) { counter++; },
+                                [] { return false; },
+                                {&requirement}};
+  RunCommand other{[&hasOtherRun] { hasOtherRun = true; }, {&requirement}};
+  scheduler.OnCommandInitialize([&scheduler, &selfCancels](const Command&) {
+    scheduler.Cancel(&selfCancels);
+  });
+  scheduler.Schedule(&selfCancels);
+  scheduler.Run();
+  scheduler.Schedule(&other);
+
+  EXPECT_FALSE(scheduler.IsScheduled(&selfCancels));
+  EXPECT_TRUE(scheduler.IsScheduled(&other));
+  EXPECT_EQ(1, counter);
   scheduler.Run();
   EXPECT_TRUE(hasOtherRun);
 }
 
 TEST_P(SchedulingRecursionTest,
-       DISABLED_DefaultCommandGetsRescheduledAfterSelfCanceling) {
+       DefaultCommandGetsRescheduledAfterSelfCanceling) {
   CommandScheduler scheduler = GetScheduler();
   bool hasOtherRun = false;
+  int counter = 0;
   TestSubsystem requirement;
-  auto selfCancels =
-      SelfCancellingCommand(&scheduler, &requirement, GetParam());
-  RunCommand other =
-      RunCommand([&hasOtherRun] { hasOtherRun = true; }, {&requirement});
+  SelfCancellingCommand selfCancels{&scheduler, counter, &requirement,
+                                    GetParam()};
+  RunCommand other{[&hasOtherRun] { hasOtherRun = true; }, {&requirement}};
   scheduler.SetDefaultCommand(&requirement, std::move(other));
 
   scheduler.Schedule(&selfCancels);
@@ -72,11 +107,12 @@
   scheduler.Run();
   EXPECT_FALSE(scheduler.IsScheduled(&selfCancels));
   EXPECT_TRUE(scheduler.IsScheduled(scheduler.GetDefaultCommand(&requirement)));
+  EXPECT_EQ(1, counter);
   scheduler.Run();
   EXPECT_TRUE(hasOtherRun);
 }
 
-class CancelEndCommand : public CommandHelper<CommandBase, CancelEndCommand> {
+class CancelEndCommand : public CommandHelper<Command, CancelEndCommand> {
  public:
   CancelEndCommand(CommandScheduler* scheduler, int& counter)
       : m_scheduler(scheduler), m_counter(counter) {}
@@ -103,6 +139,204 @@
   EXPECT_FALSE(scheduler.IsScheduled(&selfCancels));
 }
 
+TEST_F(SchedulingRecursionTest, CancelFromInterruptAction) {
+  CommandScheduler scheduler = GetScheduler();
+  int counter = 0;
+  FunctionalCommand selfCancels{[] {}, [] {}, [](bool) {},
+                                [] { return false; }};
+  scheduler.OnCommandInterrupt([&](const Command&) {
+    counter++;
+    scheduler.Cancel(&selfCancels);
+  });
+  scheduler.Schedule(&selfCancels);
+
+  EXPECT_NO_THROW({ scheduler.Cancel(&selfCancels); });
+  EXPECT_EQ(1, counter);
+  EXPECT_FALSE(scheduler.IsScheduled(&selfCancels));
+}
+
+class EndCommand : public CommandHelper<Command, EndCommand> {
+ public:
+  explicit EndCommand(std::function<void(bool)> end) : m_end(end) {}
+  void End(bool interrupted) override { m_end(interrupted); }
+  bool IsFinished() override { return true; }
+
+ private:
+  std::function<void(bool)> m_end;
+};
+
+TEST_F(SchedulingRecursionTest, CancelFromEndLoop) {
+  CommandScheduler scheduler = GetScheduler();
+  int counter = 0;
+  EndCommand dCancelsAll([&](bool) {
+    counter++;
+    scheduler.CancelAll();
+  });
+  EndCommand cCancelsD([&](bool) {
+    counter++;
+    scheduler.Cancel(&dCancelsAll);
+  });
+  EndCommand bCancelsC([&](bool) {
+    counter++;
+    scheduler.Cancel(&cCancelsD);
+  });
+  EndCommand aCancelsB([&](bool) {
+    counter++;
+    scheduler.Cancel(&bCancelsC);
+  });
+  scheduler.Schedule(&aCancelsB);
+  scheduler.Schedule(&bCancelsC);
+  scheduler.Schedule(&cCancelsD);
+  scheduler.Schedule(&dCancelsAll);
+
+  EXPECT_NO_THROW({ scheduler.Cancel(&aCancelsB); });
+  EXPECT_EQ(4, counter);
+  EXPECT_FALSE(scheduler.IsScheduled(&aCancelsB));
+  EXPECT_FALSE(scheduler.IsScheduled(&bCancelsC));
+  EXPECT_FALSE(scheduler.IsScheduled(&cCancelsD));
+  EXPECT_FALSE(scheduler.IsScheduled(&dCancelsAll));
+}
+
+TEST_F(SchedulingRecursionTest, CancelFromEndLoopWhileInRunLoop) {
+  CommandScheduler scheduler = GetScheduler();
+  int counter = 0;
+  EndCommand dCancelsAll([&](bool) {
+    counter++;
+    scheduler.CancelAll();
+  });
+  EndCommand cCancelsD([&](bool) {
+    counter++;
+    scheduler.Cancel(&dCancelsAll);
+  });
+  EndCommand bCancelsC([&](bool) {
+    counter++;
+    scheduler.Cancel(&cCancelsD);
+  });
+  EndCommand aCancelsB([&](bool) {
+    counter++;
+    scheduler.Cancel(&bCancelsC);
+  });
+  scheduler.Schedule(&aCancelsB);
+  scheduler.Schedule(&bCancelsC);
+  scheduler.Schedule(&cCancelsD);
+  scheduler.Schedule(&dCancelsAll);
+
+  EXPECT_NO_THROW({ scheduler.Run(); });
+  EXPECT_EQ(4, counter);
+  EXPECT_FALSE(scheduler.IsScheduled(&aCancelsB));
+  EXPECT_FALSE(scheduler.IsScheduled(&bCancelsC));
+  EXPECT_FALSE(scheduler.IsScheduled(&cCancelsD));
+  EXPECT_FALSE(scheduler.IsScheduled(&dCancelsAll));
+}
+
+class MultiCancelCommand : public CommandHelper<Command, MultiCancelCommand> {
+ public:
+  MultiCancelCommand(CommandScheduler* scheduler, int& counter,
+                     Command* command)
+      : m_scheduler(scheduler), m_counter(counter), m_command(command) {}
+
+  void End(bool interrupted) override {
+    m_counter++;
+    m_scheduler->Cancel(m_command);
+    m_scheduler->Cancel(this);
+  }
+
+ private:
+  CommandScheduler* m_scheduler;
+  int& m_counter;
+  Command* m_command;
+};
+
+TEST_F(SchedulingRecursionTest, MultiCancelFromEnd) {
+  CommandScheduler scheduler = GetScheduler();
+  int counter = 0;
+  EndCommand bIncrementsCounter([&counter](bool) { counter++; });
+  MultiCancelCommand aCancelsB{&scheduler, counter, &bIncrementsCounter};
+
+  scheduler.Schedule(&aCancelsB);
+  scheduler.Schedule(&bIncrementsCounter);
+
+  EXPECT_NO_THROW({ scheduler.Cancel(&aCancelsB); });
+  EXPECT_EQ(2, counter);
+  EXPECT_FALSE(scheduler.IsScheduled(&aCancelsB));
+  EXPECT_FALSE(scheduler.IsScheduled(&bIncrementsCounter));
+}
+
+TEST_P(SchedulingRecursionTest, ScheduleFromEndCancel) {
+  CommandScheduler scheduler = GetScheduler();
+  int counter = 0;
+  TestSubsystem requirement;
+  SelfCancellingCommand selfCancels{&scheduler, counter, &requirement,
+                                    GetParam()};
+  RunCommand other{[] {}, {&requirement}};
+
+  scheduler.Schedule(&selfCancels);
+  EXPECT_NO_THROW({ scheduler.Cancel(&selfCancels); });
+  EXPECT_EQ(1, counter);
+  EXPECT_FALSE(scheduler.IsScheduled(&selfCancels));
+}
+
+TEST_P(SchedulingRecursionTest, ScheduleFromEndInterrupt) {
+  CommandScheduler scheduler = GetScheduler();
+  int counter = 0;
+  TestSubsystem requirement;
+  SelfCancellingCommand selfCancels{&scheduler, counter, &requirement,
+                                    GetParam()};
+  RunCommand other{[] {}, {&requirement}};
+
+  scheduler.Schedule(&selfCancels);
+  EXPECT_NO_THROW({ scheduler.Schedule(&other); });
+  EXPECT_EQ(1, counter);
+  EXPECT_FALSE(scheduler.IsScheduled(&selfCancels));
+  EXPECT_TRUE(scheduler.IsScheduled(&other));
+}
+
+TEST_F(SchedulingRecursionTest, ScheduleFromEndInterruptAction) {
+  CommandScheduler scheduler = GetScheduler();
+  int counter = 0;
+  TestSubsystem requirement;
+  RunCommand selfCancels{[] {}, {&requirement}};
+  RunCommand other{[] {}, {&requirement}};
+  scheduler.OnCommandInterrupt([&](const Command&) {
+    counter++;
+    scheduler.Schedule(&other);
+  });
+  scheduler.Schedule(&selfCancels);
+  EXPECT_NO_THROW({ scheduler.Schedule(&other); });
+  EXPECT_EQ(1, counter);
+  EXPECT_FALSE(scheduler.IsScheduled(&selfCancels));
+  EXPECT_TRUE(scheduler.IsScheduled(&other));
+}
+
+TEST_F(SchedulingRecursionTest, CancelDefaultCommandFromEnd) {
+  CommandScheduler scheduler = GetScheduler();
+  int counter = 0;
+  TestSubsystem requirement;
+  FunctionalCommand defaultCommand{[] {},
+                                   [] {},
+                                   [&counter](bool) { counter++; },
+                                   [] { return false; },
+                                   {&requirement}};
+  RunCommand other{[] {}, {&requirement}};
+  FunctionalCommand cancelDefaultCommand{[] {}, [] {},
+                                         [&](bool) {
+                                           counter++;
+                                           scheduler.Schedule(&other);
+                                         },
+                                         [] { return false; }};
+
+  EXPECT_NO_THROW({
+    scheduler.Schedule(&cancelDefaultCommand);
+    scheduler.SetDefaultCommand(&requirement, std::move(defaultCommand));
+
+    scheduler.Run();
+    scheduler.Cancel(&cancelDefaultCommand);
+  });
+  EXPECT_EQ(2, counter);
+  EXPECT_FALSE(scheduler.IsScheduled(&defaultCommand));
+  EXPECT_TRUE(scheduler.IsScheduled(&other));
+}
+
 INSTANTIATE_TEST_SUITE_P(
     SchedulingRecursionTests, SchedulingRecursionTest,
     testing::Values(Command::InterruptionBehavior::kCancelSelf,
diff --git a/wpilibNewCommands/src/test/native/cpp/frc2/command/SwerveControllerCommandTest.cpp b/wpilibNewCommands/src/test/native/cpp/frc2/command/SwerveControllerCommandTest.cpp
index 531e9d2..b96a1e1 100644
--- a/wpilibNewCommands/src/test/native/cpp/frc2/command/SwerveControllerCommandTest.cpp
+++ b/wpilibNewCommands/src/test/native/cpp/frc2/command/SwerveControllerCommandTest.cpp
@@ -17,8 +17,9 @@
 #include <frc/kinematics/SwerveModuleState.h>
 #include <frc/simulation/SimHooks.h>
 #include <frc/trajectory/TrajectoryGenerator.h>
+#include <gtest/gtest.h>
 
-#include "gtest/gtest.h"
+#include "CommandTestBase.h"
 
 #define EXPECT_NEAR_UNITS(val1, val2, eps) \
   EXPECT_LE(units::math::abs(val1 - val2), eps)
@@ -72,7 +73,7 @@
 };
 
 TEST_F(SwerveControllerCommandTest, ReachesReference) {
-  frc2::Subsystem subsystem;
+  frc2::TestSubsystem subsystem;
 
   auto waypoints =
       std::vector{frc::Pose2d{0_m, 0_m, 0_rad}, frc::Pose2d{1_m, 5_m, 3_rad}};
@@ -84,12 +85,11 @@
   auto command = frc2::SwerveControllerCommand<4>(
       trajectory, [&]() { return getRobotPose(); }, m_kinematics,
 
-      frc2::PIDController(0.6, 0, 0), frc2::PIDController(0.6, 0, 0),
+      frc::PIDController(0.6, 0, 0), frc::PIDController(0.6, 0, 0),
       m_rotController,
       [&](auto moduleStates) { m_moduleStates = moduleStates; }, {&subsystem});
 
-  m_timer.Reset();
-  m_timer.Start();
+  m_timer.Restart();
 
   command.Initialize();
   while (!command.IsFinished()) {
diff --git a/wpilibNewCommands/src/test/native/cpp/frc2/command/button/NetworkButtonTest.cpp b/wpilibNewCommands/src/test/native/cpp/frc2/command/button/NetworkButtonTest.cpp
index 51da1d2..3f969ac 100644
--- a/wpilibNewCommands/src/test/native/cpp/frc2/command/button/NetworkButtonTest.cpp
+++ b/wpilibNewCommands/src/test/native/cpp/frc2/command/button/NetworkButtonTest.cpp
@@ -2,6 +2,7 @@
 // Open Source Software; you can modify and/or share it under the terms of
 // the WPILib BSD license file in the root directory of this project.
 
+#include <gtest/gtest.h>
 #include <networktables/NetworkTableInstance.h>
 
 #include "../CommandTestBase.h"
@@ -9,7 +10,6 @@
 #include "frc2/command/RunCommand.h"
 #include "frc2/command/WaitUntilCommand.h"
 #include "frc2/command/button/NetworkButton.h"
-#include "gtest/gtest.h"
 
 using namespace frc2;
 
diff --git a/wpilibNewCommands/src/test/native/cpp/frc2/command/button/TriggerTest.cpp b/wpilibNewCommands/src/test/native/cpp/frc2/command/button/TriggerTest.cpp
index 1a0af51..4acaa66 100644
--- a/wpilibNewCommands/src/test/native/cpp/frc2/command/button/TriggerTest.cpp
+++ b/wpilibNewCommands/src/test/native/cpp/frc2/command/button/TriggerTest.cpp
@@ -3,6 +3,7 @@
 // the WPILib BSD license file in the root directory of this project.
 
 #include <frc/simulation/SimHooks.h>
+#include <gtest/gtest.h>
 
 #include "../CommandTestBase.h"
 #include "frc2/command/CommandPtr.h"
@@ -11,7 +12,6 @@
 #include "frc2/command/RunCommand.h"
 #include "frc2/command/WaitUntilCommand.h"
 #include "frc2/command/button/Trigger.h"
-#include "gtest/gtest.h"
 
 using namespace frc2;
 class TriggerTest : public CommandTestBase {};
@@ -206,24 +206,6 @@
   EXPECT_TRUE(scheduler.IsScheduled(&command));
 }
 
-// this type of binding is deprecated and identical to OnTrue
-WPI_IGNORE_DEPRECATED
-TEST_F(TriggerTest, RValueTrigger) {
-  auto& scheduler = CommandScheduler::GetInstance();
-  int counter = 0;
-  bool pressed = false;
-
-  RunCommand command([&counter] { counter++; }, {});
-
-  Trigger([&pressed] { return pressed; }).WhenActive(std::move(command));
-  scheduler.Run();
-  EXPECT_EQ(counter, 0);
-  pressed = true;
-  scheduler.Run();
-  EXPECT_EQ(counter, 1);
-}
-WPI_UNIGNORE_DEPRECATED
-
 TEST_F(TriggerTest, Debounce) {
   auto& scheduler = CommandScheduler::GetInstance();
   bool pressed = false;
diff --git a/wpilibNewCommands/src/test/native/cpp/frc2/command/make_vector.h b/wpilibNewCommands/src/test/native/cpp/frc2/command/make_vector.h
index 05adf8e..996ddba 100644
--- a/wpilibNewCommands/src/test/native/cpp/frc2/command/make_vector.h
+++ b/wpilibNewCommands/src/test/native/cpp/frc2/command/make_vector.h
@@ -34,8 +34,12 @@
           std::is_constructible_v<T, First> && std::is_convertible_v<First, T>,
           all_constructible_and_convertible<T, Rest...>, std::false_type> {};
 
-template <typename T, typename... Args,
-          typename std::enable_if_t<!std::is_trivially_copyable_v<T>, int> = 0>
+template <typename T, typename First, typename... Rest>
+inline constexpr bool all_constructible_and_convertible_v =
+    all_constructible_and_convertible<T, First, Rest...>::value;
+
+template <typename T, typename... Args>
+  requires(!std::is_trivially_copyable_v<T>)
 std::vector<T> make_vector_impl(Args&&... args) {
   std::vector<T> vec;
   vec.reserve(sizeof...(Args));
@@ -44,19 +48,17 @@
   return vec;
 }
 
-template <typename T, typename... Args,
-          typename std::enable_if_t<std::is_trivially_copyable_v<T>, int> = 0>
+template <typename T, typename... Args>
+  requires std::is_trivially_copyable_v<T>
 std::vector<T> make_vector_impl(Args&&... args) {
   return std::vector<T>{std::forward<Args>(args)...};
 }
 
 }  // namespace detail
 
-template <
-    typename T = void, typename... Args,
-    typename V = detail::vec_type_helper_t<T, Args...>,
-    typename std::enable_if_t<
-        detail::all_constructible_and_convertible<V, Args...>::value, int> = 0>
+template <typename T = void, typename... Args,
+          typename V = detail::vec_type_helper_t<T, Args...>>
+  requires detail::all_constructible_and_convertible_v<V, Args...>
 std::vector<V> make_vector(Args&&... args) {
   return detail::make_vector_impl<V>(std::forward<Args>(args)...);
 }
diff --git a/wpilibNewCommands/src/test/native/cpp/main.cpp b/wpilibNewCommands/src/test/native/cpp/main.cpp
index 6aea19a..d181e39 100644
--- a/wpilibNewCommands/src/test/native/cpp/main.cpp
+++ b/wpilibNewCommands/src/test/native/cpp/main.cpp
@@ -2,10 +2,9 @@
 // Open Source Software; you can modify and/or share it under the terms of
 // the WPILib BSD license file in the root directory of this project.
 
+#include <gtest/gtest.h>
 #include <hal/HALBase.h>
 
-#include "gtest/gtest.h"
-
 int main(int argc, char** argv) {
   HAL_Initialize(500, 0);
   ::testing::InitGoogleTest(&argc, argv);
diff --git a/wpilibNewCommands/wpilibNewCommands-config.cmake.in b/wpilibNewCommands/wpilibNewCommands-config.cmake.in
index 75aa6ad..8a8d8d8 100644
--- a/wpilibNewCommands/wpilibNewCommands-config.cmake.in
+++ b/wpilibNewCommands/wpilibNewCommands-config.cmake.in
@@ -1,5 +1,4 @@
 include(CMakeFindDependencyMacro)
- @FILENAME_DEP_REPLACE@
  @WPIUTIL_DEP_REPLACE@
  @NTCORE_DEP_REPLACE@
  @CSCORE_DEP_REPLACE@
@@ -8,4 +7,5 @@
  @WPILIBC_DEP_REPLACE@
  @WPIMATH_DEP_REPLACE@
 
+ @FILENAME_DEP_REPLACE@
  include(${SELF_DIR}/wpilibNewCommands.cmake)