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

83f1860047 [wpilib] Add/update documentation to PneumaticBase and subclasses (NFC) (#4881)
9872e676d8 [commands] Make Subsystem destructor virtual (#4892)
25db20e49d [hal] Fix segfault in various HAL functions (#4891)
b0c6724eed [glass] Add hamburger menu icon to titlebars (#4874)
f0fa8205ac Add missing compiler flags and fix warnings (#4889)
42fc4cb6bc [wpiutil] SafeThread: Provide start/stop hooks (#4880)
cc166c98d2 [templates] Add Command-based skeleton template (#4861)
3f51f10ad3 [build] Update to 2023v3 image (#4886)
1562eae74a [ntcore] Refactor meta-topic decoding from glass (#4809)
b632b288a3 Fix usages of std::min and std::max to be windows safe (#4887)
c11bd2720f [wpilibc] Add internal function to reset Shuffleboard instance (#4884)
f1151d375f [ntcore] Add method to get server time offset (#4847)
fe1b62647f [hal,wpilib] Update documentation for getComments (NFC) (#4879)
c49a45abbd [build] Fix examples linking in incorrect jni library (#4873)
bc3d01a721 [build] Add platform check to doxygen plugin (#4862)
bc473240ae Add Jetbrains Fleet folder to .gitignore (#4872)
2121bd5fb8 [wpimath] Remove RKF45 (#4870)
835f8470d6 [build] Fix roborio cross-compiler on arm hosts (#4864)
6cfe5de00d [ntcore] Don't deadlock server on early destroy (#4863)
2ac41f3edc [hal, wpilib] Add RobotController.getComments()  (#4463)
26bdbf3d41 Java optimization and formatting fixes (#4857)
92149efa11 Spelling and grammar cleanups (#4849)
176fddeb4c [commands] Add functions to HID classes to allow use of axes as BooleanEvents/Triggers (#4762)
87a34af367 [templates] Add bindings to command-based template (#4838)
4534e75787 [examples] Remove redundant MotorControl example (#4837)
1cbebaa2f7 [commands] Remove final semicolon from test macro definition (#4859)
6efb9ee405 [commands] Add constructor for SwerveControllerCommand that takes a HolonomicDriveController (#4785)
1e7fcd5637 [cscore] Change run loop functions to not be mac specific (#4854)
1f940e2b60 [apriltag] Add C++ wrappers, rewrite Java/JNI to match (#4842)
a6d127aedf [build] Add missing task dependency in wpilibjExamples (#4852)
b893b3d6d3 [cscore] Add support for USB cameras on macOS (#4846)
1696a490fa [glass] Add support for alternate NT ports (#4848)
40a22d69bc [glass] Add support for alternate NT ports (#4848)
e84dbfede0 [wpilib] GenericHID: Add rumble both option (#4843)
8aa9dbfa90 [examples] Link apriltag package in examples build.gradle (#4845)
eda2fa8a17 [build] Update Spotless (#4840)
d20594db0d Fix typos (#4839)
dd8ecfdd54 [commands] Fix typo in waitUntil docs (NFC) (#4841)
17ceebfff4 [apriltag] Clean up apriltag JNI (#4823)
8b74ab389d [examples] RapidReactCommandBot: Fix array indices (#4833)
1aad3489c2 [sim] Implement PD total current and power (#4830)
2744991771 [wpimath] Fix docs in SwerveModulePosition (#4825)
ffbf6a1fa2 [commands] Disable regularly failing unit test (#4824)
fbabd0ef15 [commands] Enhance Command Sendable implementations (#4822)
7713f68772 [hal] Use atomic rather then mutex for DS Data updates (#4787)
701995d6cc [examples] Update Command-based starter project (#4778)
bf7068ac27 [wpilibc] Add missing PPS implementation for C++ (#4821)
aae0f52ca6 [ntcore] NetworkTable: fix visibility of get/set value (#4820)
ee02fb7ba7 [hal] Add support for Pulse-Per-Second signal (#4819)
518916ba02 [wpilib] Fix DS mode thread event being manual reset accidentally (#4818)
3997c6635b [hal] Update to new image, use new TCP notify callback and new duty cycle API (#4774)
cc8675a4e5 [examples] Add comment on how to view elevator sim (NFC) (#4482)
fb2c170b6e [ntcore] Simplify local startup (#4803)
7ba8a9ee1f [wpimath] ProfiledPIDController: Add to SendableRegistry (#4656)
c569d8e523 [wpilib] Joystick.getMagnitude(): use hypot() function (#4816)
2a5e89fa97 [apriltag] Improve description of pose coordinates (NFC) (#4810)
cc003c6c38 [apriltag] Fix AprilTagFieldLayout JSON name (#4814)
5522916123 [commands] CommandXBoxController bumper documentation fix (NFC) (#4815)
967b30de3a [glass] Fix NT view UpdateClients() bug (#4808)
3270d4fc86 [wpimath] Rewrite pose estimator docs (#4807)
be39678447 [apriltag] Add test to ensure apriltagjni loads (#4805)
61c75deb2a [commands] Test no-op behavior of scheduling a scheduled command (#4806)
a865f48e96 [ntcore] Pass pub/sub options as a unified PubSubOptions struct (#4794)
f66a667321 [commands] Fix incorrect Trigger docs (NFC) (#4792)
f8d4e9866e [ntcore] Clean up ntcore_test.h (#4804)
7e84ea891f [wpimath] Fix ComputerVisionUtil transform example in parameter docs (NFC) (#4800)
da3ec1be10 [wpimath] Change terminology for ArmFeedforward gravity gain (NFC) (#4791)
944dd7265d [wpilibc] Add C++ Notifier error handling, update java notifier error message (#4795)
6948cea67a [wpiutil] Fix MemoryBuffer initialization (#4797)
a31459bce6 [wpiutil] Fix UnescapeCString overflow when inputSize < 2 (#4796)
4a0ad6b48c [wpimath] Rotation2d: Add reference to angleModulus in docs (NFC) (#4786)
e6552d272e [ntcore] Remove table multi-subscriber (#4789)
bde383f763 [hal] Replace const char* with std::string_view in Driver Station sim functions (#4532)
5a52b51443 [hal] Add RobotController.getSerialNumber() (#4783)
69a66ec5ec [wpilib] Fix multiple motor safety issues (#4784)
989c9fb29a [wpimath] Revert Rotation2D change that limits angles (#4781)
0f5b08ec69 [wpigui] Update imgui to 1.89.1+ (#4780)
fba191099c [examples] AddressableLED: Add unit test (#4779)
b390cad095 [wpilibj] Consistently use ErrorMessages.requireNonNullParam (#4776)
b9772214d9 [wpilib] Sendable: Don't call setter for getter changes
342c375a71 [ntcore] Add subscriber option to exclude single publisher
b0e4053087 [ntcore] Use int for options instead of double
f3e666b7bb [cscore] Convert YUYV and UYVY directly to grayscale (#4777)
b300518bd1 [hal] Add CAN Stream API to Java through JNI bindings (#4193)
be27171236 [wpilibj] Shuffleboard: Check for null sendable (#4772)
4bbdbdfb48 [commands] Move GroupedCommands to CommandScheduler (#4728)
f18fd41ac3 [wpimath] Remove broken and obsoleted ComputerVisionUtil functions (#4775)
2d0faecf4f [glass] DataSource: Add spinlock to protect value (#4771)
348bd107fc [hal] Add CANManufacturer for The Thrifty Bot (#4773)
3149dc64b8 [examples] HatchbotInlined: Use Subsystem factories (#4765)
8618dd4160 [glass, wpilib] Replace remaining references to Speed Controller with Motor Controller (#4769)
72e21a1ed1 [apriltag] Use wpilibsuite fork of apriltag (#4764)
eab0d929e6 [commands] CommandGenericHID POV methods: Fix docs (NFC) (#4760)
6789869663 [wpilib] Call set(0) rather than disable for stopMotor (#4763)
c9dea2968d [cscore] Emit warning that USB Camera isn't supported on OSX (#4766)
8f402645f5 [commands] Fix PIDSubsystem setSetpoint behavior (#4759)
f24ad1d715 [build] Upgrade to googletest 1.12.1 (#4752)
ff88756864 [wpimath] Add new DCMotor functions for alternative calculations and reduction calculation (#4749)
f58873db8e [wpimath] Remove extra terms in matrix for pose estimator docs (#4756)
37e969b41a [wpimath] Add constructors to pose estimators with default standard deviations (#4754)
13cdc29382 [ci] Rename comment command from "/wpiformat" to "/format" (#4755)
6e23985ae6 [examples] Add main include directory to test builds (#4751)
66bb0ffb2c [examples] Add unit testing infrastructure (#4646)
74cc86c4c5 [wpimath] Make transform tests use pose/transform equality operators (#4675)
e22d8cc343 [wpimath] Use Odometry for internal state in Pose Estimation (#4668)
68dba92630 [ci] Update mac and windows builds to Java 17 (#4750)
23bfc2d9ab [sim] Remove unmaintained Gazebo support (#4736)
1f1461e254 [wpilib] Add method to enable/disable LiveWindow in test mode (#4678)
eae68fc165 [wpimath] Add tolerance for Rotation3d rotation matrix special orthogonality (#4744)
4c4545fb4b [apriltag] Suppress warning (#4743)
16ffaa754d [docs] Generate docs for apriltag subproject (#4745)
5e74ff26d8 [apriltag, build] Update native utils, add apriltag impl and JNI (#4733)
53875419a1 [hal] Allow overriding stderr printing by HAL_SendError (#4742)
aa6499e920 [ntcore] Fix special topic multi-subscriber handling (#4740)
df70351107 [build] Fix cmake install of thirdparty includes (#4741)
e9bd50ff9b [glass] NT view: clear meta-topic info on disconnect (#4732)
9b319fd56b [ntcore] Add sub option for local vs remote changes (#4731)
18d28ec5e3 [ntcore] Remove duplicate value checking from ClientImpl
bdfb625211 [ntcore] Send duplicate values to network if necessary
21003e34eb [commands] Update Subsystem factories and example to return CommandBase (#4729)
70080457d5 [commands] Refactor ProxyScheduleCommand, SelectCommand into ProxyCommand (#4534)
e82cd5147b [wpilib] Tweak Color HSV formula and use in AddressableLED (#4724)
ec124bb662 [commands] Allow unsetting a subsystem's default command (#4621)
2b2aa8eef7 [examples] Update all examples to use NWU coordinate conventions (#4725)
cb38bacfe8 [commands] Revert to original Trigger implementation (#4673)
15561338d5 [commands] Remove one more default command isFinished check (#4727)
ca35a2e097 Add simgui files to .gitignore (#4726)
20dbae0cee [examples] Renovate command-based examples (#4409)
1a59737f40 [commands] Add convenience factories (#4460)
42b6d4e3f7 Use defaulted comparison operators in C++ (#4723)
135c13958f [wpigui] Add FontAwesome (#4713)
ffbfc61532 [ntcore] Add NetworkTable table-specific listeners (#4640)
8958b2a4da [commands] Add property tests for command compositions (#4715)
e4ac09077c [wpilib] Add link to MotorSafety article (#4720)
f40de0c120 [commands] Add C++ factory templates (#4686)
51fa3e851f [build] cmake: Use FetchContent instead of ExternalProject (#4714)
1da84b2255 [wpigui] Reload fonts to scale rather than preloading (#4712)
e43e2fbc84 [wpiutil] StringExtras: Add UnescapeCString (#4707)
5804d8fa84 [ntcore] Server: Properly handle multiple subscribers (#4717)
169ef5fabf [glass] Update NT view for topicsOnly and sendAll changes (#4718)
148759ef54 [examples] CANPDP: Expand properties shown (#4687)
58ed112b51 [commands] RepeatCommand: restart on following iteration (#4706)
dd1da77d20 [readme] Fix broken CI badge (#4710)
7cda85df20 [build] Check Gradle plugin repo last to fix CI (#4711)
7ed9b13277 [build] Bump version plugin to fix null tag (#4705)
6b4f26225d [apriltag] Fix pluralization of apriltag artifacts (#4671)
b2d2924b72 [cscore] Add Y16 image support (#4702)
34ec89c041 [wpilibc] Shuffleboard SimpleWidget: Return pointer instead of reference (#4703)
e15200068d [ci] Disable HW testbench runs (#4704)
d5200db6cd [wpimath] Rename HolonomicDriveController.calculate params (#4683)
2ee3d86de4 [wpimath] Clarify Rotation3d roll-pitch-yaw direction (#4699)
9f0a8b930f [cscore] Use MFVideoFormat_L8 for Gray on Windows (#4701)
2bca43779e [cscore] Add UYVY image support (#4700)
4307d0ee8b [glass] Plot: allow for more than 11 plots (#4685)
3fe8d355a1 [examples] StateSpaceDifferentialDriveSimulation: Use encoder reversed constants (#4682)
b44034dadc [ntcore] Allow duplicate client IDs on server (#4676)
52d2c53888 [commands] Rename Java factory wait() to waitSeconds() (#4684)
76e918f71e [build] Fix JNI artifacts linking to incorrect libraries (#4680)
0bee875aff [commands] Change C++ CommandPtr to use CommandBase (#4677)
98e922313b [glass] Don't check IsConnected for NT widgets (#4674)
9a36373b8f [apriltag] Switch 2022 apriltag layout length and width values (#4670)
cf8faa9e67 [wpilib] Update values on controllable sendables (#4667)
5ec067c1f8 [ntcore] Implement keep duplicates pub/sub flag (#4666)
e962fd2916 [ntcore] Allow numeric-compatible value sets (#4620)
88bd67e7de [ci] Update clang repositories to jammy (#4665)
902e8686d3 [wpimath] Rework odometry APIs to improve feature parity (#4645)
e2d49181da Update to native utils 2023.8.0 (#4664)
149bac55b1 [cscore] Add Arducam OV9281 exposure quirk (#4663)
88f7a3ccb9 [wpimath] Fix Pose relativeTo documentation (#4661)
8acce443f0 [examples] Fix swerve examples to use getDistance for turning encoder (#4652)
295a1f8f3b [ntcore] Fix WaitForListenerQueue (#4662)
388e7a4265 [ntcore] Provide mechanism to reset internals of NT instance (#4653)
13aceea8dc [apriltag] Fix FieldDimensions argument order (#4659)
c203f3f0a9 [apriltag] Fix documentation for AprilTagFieldLayout (#4657)
f54d495c90 Fix non initialized hal functionality during motor safety init (#4658)
e6392a1570 [cmd] Change factories return type to CommandBase (#4655)
53904e7cf4 [apriltag] Split AprilTag functionality to a separate library (#4578)
2e88a496c2 [wpimath] Add support for swerve joystick normalization (#4516)
ce4c45df13 [wpimath] Rework function signatures for Pose Estimation / Odometry (#4642)
0401597d3b [readme] Add wpinet to MavenArtifacts.md (#4651)
2e5f9e45bb [wpimath] Remove encoder reset comments on Swerve, Mecanum Odometry and Pose Estimation (#4643)
e4b5795fc7 [docs] Disable Doxygen for memory to fix search (#4636)
03d0ea188c [build] cmake: Add missing wpinet to installed config file (#4637)
3082bd236b [build] Move version file to its own source set (#4638)
b7ca860417 [build] Use build cache for sign step (#4635)
64838e6367 [commands] Remove unsafe default command isFinished check (#4411)
1269d2b901 [myRobot] Disable spotbugs (#4565)
14d8506b72 [wpimath] Fix units docs for LinearSystemId::IdentifyDrivetrainSystem() (#4600)
d1d458db2b [wpimath] Constrain Rotation2d range to -pi to pi (#4611)
f656e99245 [readme] Add links to development build documentation (#4481)
6dd937cef7 [commands] Fix Trigger API docs (NFC) (#4599)
49047c85b9 [commands] Report error on C++ CommandPtr use-after-move (#4575)
d07267fed1 [ci] Upgrade containers to Ubuntu 22.04 and remove libclang installation (#4633)
b53ce1d3f0 [build, wpiutil] Switch macos to universal binaries (#4628)
5a320c326b [upstream_util, wpiutil] Refactor python scripts (#4614)
c4e526d315 [glass] Fix NT Mechanism2D (#4626)
d122e4254f [ci] Run spotlessApply after wpiformat in comment command (#4623)
5a1e7ea036 [wpilibj] FieldObject2d: Add null check to close() (#4619)
179f569113 [ntcore] Notify locally on SetDefault (#4617)
b0f6dc199d [wpilibc] ShuffleboardComponent.WithProperties: Update type (#4615)
7836f661cd [wpimath] Add missing open curly brace to units/base.h (#4613)
dbcc1de37f [wpimath] Add DifferentialDriveFeedforward classes which wrap LinearPlantInversionFeedforward (#4598)
93890c528b [wpimath] Add additional angular acceleration units (#4610)
3d8d5936f9 [wpimath] Add macro for disabling units fmt support (#4609)
2b04159dec [wpimath] Update units/base.h license header (#4608)
2764004fad [wpinet] Fix incorrect jni definitions (#4605)
85f1bb8f2b [wpiutil] Reenable jni check task (#4606)
231ae2c353 [glass] Plot: Fix Y-axis not being saved (#4594)
e92b6dd5f9 [wpilib] Fix AprilTagFieldLayout JSON property name typos (#4597)
2a8e0e1cc8 Update all dependencies that use grgit (#4596)
7d06e517e9 [commands] Move SelectCommand factory impl to header (#4581)
323524fed6 [wpimath] Remove deprecated units/units.h header (#4572)
d426873ed1 [commands] Add missing PS4 triangle methods (#4576)
5be5869b2f [apriltags] Use map as internal data model (#4577)
b1b4c1e9e7 [wpimath] Fix Pose3d transformBy rotation type (#4545)
a4054d702f [commands] Allow composing two triggers directly (#4580)
0190301e09 [wpilibc] Explicitly mark EventLoop as non-copyable/non-movable (#4579)
9d1ce6a6d9 [ntcore] Catch file open error when saving preferences (#4571)
5005e2ca04 [ntcore] Change Java event mask to EnumSet (#4564)
fa44a07938 [upstream-utils][mpack] Add upstream util for mpack (#4500)
4ba16db645 [ntcore] Various fixes and cleanups (#4544)
837415abfd [hal] Fix joysticks either crashing or returning 0 (#4570)
2c20fd0d09 [wpilib] SingleJointedArmSim: Check angle equals limit on wouldHit (#4567)
64a7136e08 [wpimath] SwerveDrivePoseEstimator: Restore comment about encoder reset (#4569)
b2b473b24a [wpilib] Add AprilTag and AprilTagFieldLayout (#4421)
7aab8fa93a [build] Update to Native Utils 2023.6.0 (#4563)
12c2851856 [commands] WrapperCommand: inherit from CommandBase (#4561)
0da169dd84 [wpimath] Remove template argument from ElevatorFeedforward (#4554)
2416827c25 [wpimath] Fix docs for pose estimator local measurement models (#4558)
1177a3522e [wpilib] Fix Xbox/PS4 POV sim for port number constructors (#4548)
102344e27a [commands] HID classes: Add missing methods, tweak return types (#4557)
1831ef3e19 [wpilib] Fix Shuffleboard SuppliedValueWidget (#4559)
a9606ce870 [wpilib] Fix Xbox/PS4 POV sim (#4546)
6c80d5eab3 [wpimath] Remove unused SymbolExports.h include from units/base.h (#4541)
b114006543 [ntcore] Unify listeners (#4536)
32fbfb7da6 [build] cmake: Install ntcore generated include files (#4540)
02465920fb [build] Update native utils to 2023.4.0 (#4539)
3a5a376465 [wpimath] Increase constexpr support in geometry data types (#4231)
1c3c86e9f1 [ntcore] Cache GetEntry(name) values (#4531)
dcda09f90a [command] Rename trigger methods (#4210)
66157397c1 [wpilib] Make drive classes follow NWU axes convention (#4079)
9e22ffbebf [ntcore] Fix null deref in NT3 client (#4530)
648ab6115c [wpigui,dlt,glass,ov] Support arm in GUI tools (#4527)
8bc3b04f5b [wpimath] Make ComputerVisionUtil use 3D geometry classes (#4528)
cfb84a6083 [wpilibc] Don't hang waiting for NT server to start (#4524)
02c47726e1 [wpimath] Remove unused odometry instance from DifferentialDrivePoseEstimator test (#4522)
b2a0093294 [ci] Revert upgrade of github-pages-deploy-action (#4521)
2a98d6b5d7 [wpimath] PIDController: Add getters for position & velocity tolerances (#4458)
9f36301dc8 [ci] Write wpiformat patch to job summary (#4519)
901fc555f4 [wpimath] Position Delta Odometry for Mecanum (#4514)
4170ec6107 [wpimath] Position Delta Odometry for Swerve (#4493)
fe400f68c5 [docs] Add wpinet to docs build (#4517)
794669b346 [ntcore] Revamp listeners (#4511)
dcfa85a5d5 [ci] Build sanitizers with clang-14 (#4518)
15ad855f1d [ntcore] Add UnitTopic<T> (C++ only) (#4497)
11244a49d9 [wpilib] Add IsConnected function to all gyros (#4465)
1d2e8eb153 [build] Update myRobot deployment (#4515)
ad53fb19b4 [hal] Use new HMB api for addressable LED (#4479)
ba850bac3b [hal] Add more shutdown checks and motor safety shutdown (#4510)
023a5989f8 [ntcore] Fix typo in NetworkServer client connect message (#4512)
c970011ccc [docs] Add Doxygen aliases used by Foonathan memory (#4509)
07a43c3d9a [readme] Document clang-format version and /wpiformat (#4503)
a05b212b04 [ci] Revert changes to wpiformat task from #4501 (#4508)
09faf31b67 [commands] Replace Command HID inheritance with delegation (#4470)
9e1f9c1133 [commands] Add command factories (#4476)
f19d2b9b84 [ci] Add NUMBER environment variable to comment command commit script (#4507)
a28f93863c [ci] Push comment command commit directly to PR (#4506)
c9f61669b8 [ci] Fix comment command commit push (#4505)
dcce5ad3b3 [ci] Update github-script API usage (#4504)
6836e5923d [wpilibc] Restore get duty cycle scale factor (#4502)
335188c652 [dlt] Add deselect/select all buttons to download view (#4499)
60a29dcb99 [glass] Field2D: Add "hidden" option for objects (#4498)
b55d5b3034 [ci] Update deprecated github actions (#4501)
10ed4b3969 [ntcore] Various NT4 fixes (#4474)
4a401b89d7 [hal, wpilib] New DS thread model and implementation (#3787)
c195b4fc46 [wpimath] Clean up PoseEstimator nominal dt docs (#4496)
8f2e34c6a3 [build] Remove wpilib prefix from CMake flat install (#4492)
150d692df7 [wpimath] Remove unused private PoseEstimator function (#4495)
3e5bfff1b5 [wpimath] FromFieldRelativeSpeeds: Add ChassisSpeeds overload (#4494)
9c7e66a27d [commands] C++: Add CommandPtr overload for SetDefaultCommand (#4488)
0ca274866b [build] Fix CMake system library opt-ins (#4487)
dc037f8d41 [commands] Remove EndlessCommand (#4483)
16cdc741cf [wpimath] Add Pose3d(Pose2d) constructor (#4485)
9d5055176d [build] cmake: Allow disabling ntcore build (#4486)
d1e66e1296 [build] Compile all java code with inline string concatenation (#4490)
1fc098e696 Enable log macros to work with no args (#4475)
878cc8defb [wpilib] LiveWindow: Add enableAllTelemetry() (#4480)
8153911160 [build] Fix MSVC runtime archiver to grab default runtime (#4478)
fbdc810887 Upgrade to C++20 (#4239)
396143004c [ntcore] Add ntcoreffi binary (#4471)
1f45732700 [build] Update to 2023.2.4 native-utils and new dependencies (#4473)
574cb41c18 [ntcore] Various fixes (#4469)
d9d6c425e7 [build] Force Java 11 source compatibility (#4472)
58b6484dbe Switch away from NI interrupt manager to custom implementation (#3705)
ca43fe2798 [wpimath] Use Units conversions in ComputerVisionUtil docs (NFC) (#4464)
87a64ccedc [hal] Convert DutyCycle Raw output to be a high time measurement (#4466)
89a3d00297 [commands] Add FinallyDo and HandleInterrupt decorators (#4412)
1497665f96 [commands] Add C++ versions of Java-only decorators (#4457)
27b173374e [wpimath] Add minLinearAccel parameter to DifferentialDriveAccelerationLimiter (#4422)
2a13dba8ac [wpilib] TrajectoryUtil: Fix ambiguous documentation (NFC) (#4461)
77301b126c [ntcore] NetworkTables 4 (#3217)
90cfa00115 [build] cmake: Fix libssh include directory order (#4459)
5cf961edb9 [commands] Refactor lambda-based commands to inherit FunctionalCommand (#4451)
b2276e47de [wpimath] Enable continuous angle input for HolonomicDriveController (#4453)
893b46139a [fieldImages] Add utilities to simplify loading of fields (#4456)
60e29627c0 [commands] C++ unique_ptr migration (#4319)
3b81cf6c35 [wpilib] Improve Color.toString (#4450)
5c067d30a0 [wpinet] WebSocket: Add SendFrames() (#4445)
ceaf493811 [wpiutil] MakeJByteArray: Use span<uint8> instead of string_view (#4446)
10e04e2b13 [examples] FrisbeeBot: Fix reference capture (#4449)
726f67c64b [build] Add exeSplitSetup (#4444)
c7b7624c1c [wpiutil] Add MessagePack utility functions (#4448)
d600529ec0 [wpinet] uv::Async: Add UnsafeSend() (#4447)
b53b3526a2 [wpimath] Add CoordinateSystem conversion for Transform3d (#4443)
38bb23eb18 [wpimath] Add scalar multiply and divide operators to all geometry classes (#4438)
3937ff8221 [wpilib] Remove deprecated Controller class (#4440)
abbfe244b5 [wpilib] Improve Color FromHSV (#4439)
4ddb8aa0dd [sim] Provide function that resets all simulation data (#4016)
a791470de7 Clean up Java warning suppressions (#4433)
17f504f548 [hal,wpilib] Fix SPI Mode Setting (#4434)
773198537c [wpiutil] Add wpi::scope_exit (#4432)
5ac658c8f0 [wpiutil] Logger: Conditionalize around WPI_LOG (#4431)
8767e4a941 [wpiutil] DataLog: Fix SetMetadata output (#4430)
8c4af073f4 [wpiutil] Synchronization: shutdown race protection (#4429)
c79f38584a [build] Fix Java integration tests (#4428)
36c08dd97c [build] Fix cmake install of fmtlib (#4426)
69b7b3dd7d [ci] Remove the Windows cmake job (#4425)
738c75fed8 [readme] Fix formatting/linting link (#4423)
4eb1d03fb3 [wpimath] Document C++ LinearFilter exception (#4417)
ba4ec6c967 [build] Fix clang-tidy false positive on Linux (#4406)
97836f0e55 [commands] Fix ProfiledPIDSubsystem setGoal behavior (#4414)
fdfb85f695 [wpimath] Remove Java LQR constructor that takes a controller gain matrix (#4419)
ab1baf4832 [wpimath] Add rotation matrix constructor to Rotation3d (#4413)
9730032866 [wpimath] Document LQR and KalmanFilter exceptions (#4418)
5b656eecf6 [wpimath] Fix HTML5 entity (#4420)
9ae38eaa7c [commands] Add owning overload to ProxyScheduleCommand (#4405)
cb33bd71df [commands] deprecate withInterrupt decorator (#4407)
d9b4e7b8bf [commands] Revert "Change grouping decorator impl to flatten nested group structures (#3335)" (#4402)
0389bf5214 [hal] REVPH: Improve handling of disconnected CAN Bus (#4169)
4267fa08d1 [wpilibc] ADIS IMUs: Fix memory leak (#4170)
65c8fbd452 [wpilib] MotorControllerGroup: Override setVoltage (#4403)
f36162fddc [wpimath] Improve Discretization internal docs (#4400)
5149f7d894 [wpimath] Add two-vector Rotation3d constructor (#4398)
20b5bed1cb [wpimath] Clean up Java Quaternion class (#4399)
f18dd1905d [build] Include all thirdparty sources in distribution (#4397)
aa9d7f1cdc [wpiutil] Import foonathan memory (#4306)
2742662254 [ci] Remove a couple of obsolete clang-tidy checks (#4396)
a5df391166 [hal, wpilib] Fix up DIO pulse API (#4387)
59e6706b75 [glass] Turn on docking by default
8461bb1e03 [glass] Add support for saving docking info
b873e208b4 [wpigui] Add support for imgui config flags
873e72df8c [build] Update imgui to 1.88 docking branch
c8bd6fc5b4 [ci] Fix comment-command (take 2) (#4395)
fed68b83b4 [ci] Fix comment-command action not running runners (#4393)
0ef8a4e1df [wpimath] Support formatting more Eigen types (#4391)
c393b3b367 [build] Update to native utils 2023.1.0 and Gradle 7.5.1 (#4392)
b5a17f762c [wpimath] Add direction to slew rate limiter (#4377)
fafc81ed1a [wpiutil] Upgrade to fmt 9.1.0 (#4389)
cc56bdc787 [wpiutil] SafeThread: Add Synchronization object variant (#4382)
4254438d8d [commands] Mark command group lifecycle methods as final (#4385)
97c15af238 [wpimath] LinearSystemId: Fix docs, move C++ impls out of header (#4388)
d22ff8a158 [wpiutil] Add JNI access to C++ stderr (#4381)
fdb5a2791f [wpiutil] jni_util: Add Mac-friendly MakeJLongArray/JArrayRef (#4383)
c3a93fb995 [commands] Revamp Interruptible (#4192)
f2a8d38d2a [commands] Rename Command.repeat to repeatedly (#4379)
9e24c6eac0 [wpiutil] Logger: paren-protect instance usage in macro (#4384)
fe4d12ce22 [wpimath] Add LTV controller derivations and make enums private (#4380)
eb08486039 [build] Fix MacOS binary rpath generation (#4376)
ccf83c634a [build] Use native-utils platform names instead of raw strings (#4375)
3fd69749e7 [docs] Upgrade to doxygen 1.9.4 (#4370)
594df5fc08 [wpinet] uv/util.h: Pull in ws2_32.lib on Windows for ntohs (#4371)
539070820d [ci] Enable asan for wpinet and wpiutil (#4369)
564a56d99b [wpinet] Fix memory leak in WorkerThreadTest (#4368)
5adf50d93c [upstream_utils] Refactor upstream_utils scripts (#4367)
d80e8039d7 [wpiutil] Suppress fmtlib clang-tidy warning in C++20 consteval contexts (#4364)
0e6d67b23b [upstream_utils] Remove yapf format disable comment (#4366)
be5270697a [build] Suppress enum-enum deprecation warning in OpenCV (#4365)
8d28851263 Add Rosetta install command to build requirements (#4363)
3d2115c93e [wpinet] include-what-you-use in MulticastTest (#4360)
91002ae3cc [wpimath] Upgrade to Drake 1.6.0 (#4361)
148c18e658 [wpinet] Upgrade to libuv 1.44.2 (#4362)
a2a5c926b6 Fix clang-tidy warnings (#4359)
ea6b1d8449 [wpiutil] Remove unused ManagedStatic class (#4358)
ac9be78e27 Use stricter C++ type conversions (#4357)
151dabb2af [wpiutil] Upgrade to fmt 9.0.0 (#4337)
340465c929 [ci] Upgrade to clang-format and clang-tidy 14 (NFC) (#4347)
d45bcddd15 [examples] Add comments to StateSpaceDifferentialDrive (#4341)
0e0786331a Update LLVM libraries to 14.0.6 (#4350)
c5db23f296 [wpimath] Add Eigen sparse matrix and iterative solver support (#4349)
44abc8dfa6 [upstream_utils] Remove git version from upstream patches (#4351)
3fdb2f767d [wpimath] Add comments with Ramsete equations (#4348)
0485f05da9 [wpilibjExamples] Upgrade jacoco to match allwpilib (#4346)
0a5eb65231 [wpinet] Handle empty txt block for mdns announcer (#4072)
19ffebaf3e [wpilib] Add reference to I2C Lockup to API Docs (NFC) (#4340)
ce1a90d639 [hal] Replace SerialHelper "goto done" with continue (#4342)
d25af48797 [ci] Make upstream_utils CI fail on untracked files (#4339)
ebb836dacb [examples] Fix negations in event loop examples (#4334)
d83e202f00 [upstream_utils] Update paths in update_fmt.py (#4338)
3ccf806064 [wpimath] Remove redundant LinearFilter.finiteDifference() argument (#4335)
6f1e01f8bd [wpimath] Document example of online filtering for LinearFilter.finiteDifference() (#4336)
1023c34b1c [readme] Update location of ni-libraries (#4333)
faa29d596c [wpilib] Improve Notifier docs (NFC) (#4326)
add00a96ed [wpimath] Improve DifferentialDriveAccelerationLimiter docs (NFC) (#4323)
82fac41244 [wpimath] Better document trackwidth parameters (NFC) (#4324)
5eb44e22a9 Format Python scripts with black (NFC) (#4325)
2e09fa7325 [build] Fix mpack cmake (#4322)
fe3c24b1ee [command] Add ignoringDisable decorator (#4305)
aa221597bc [build] Add M1 builds, change arm name, update to 2023 deps (#4315)
579a8ee229 [ci] Use one worker for Windows release Gradle build (#4318)
5105c5eab6 [wpilibj] Change "final" to "exit" in the IterativeRobotBase JavaDoc (NFC) (#4317)
787fe6e7a5 [wpiutil] Separate third party libraries (#4190)
6671f8d099 [wpigui] Update portable file dialogs (#4316)
9ac9b69aa2 [command] Reorder Scheduler operations (#4261)
e61028cb18 [build] halsim_gui: Add wpinet dependency (#4313)
661d23eaf5 [glass] Add precision setting for NetworkTable view (#4311)
666040e3e5 [hal] Throw exceptions for invalid sizes in I2C and SPI JNI (#4312)
aebc272449 [build] Upgrade to spotbugs Gradle plugin 5.0.8 (#4310)
fd884581e4 [wpilib] Add BooleanEvent/Trigger factories on HID classes (#4247)
9b1bf5c7f1 [wpimath] Move Drake and Eigen to thirdparty folders (#4307)
c9e620a920 [wpilibc] Change EventLoop data structure to vector (#4304)
41d40dd62f [wpinet] Fix libuv unused variable warning on Mac (#4299)
30f5b68264 [wpinet] Fix JNI loading error (#4295)
f7b3f4b90e [examples] Getting Started: Change Joystick to XboxController (#4194)
a99c11c14c [wpimath] Replace UKF implementation with square root form (#4168)
45b7fc445b [wpilib] Add EventLoop (#4104)
16a4888c52 [wpilib] Default off LiveWindow telemetry (#4301)
17752f1337 [ci] Split debug and release Windows builds (#4277)
abb45a68db [commands] Remove custom test wrappers (#4296)
1280a54ef3 [upstream_utils]: Make work with Python 3.8 (#4298)
f2d243fa68 [build] Change defaults for Java lints (#4300)
a4787130f4 Update using development build to work with 2023 gradlerio (#4294)
af7985e46c [wpiutil] Use invoke_result_t instead of result_of in future.h (#4293)
e9d1b5c2d0 [hal] Remove deprecated SimDevice functions (#4209)
45b598d236 [wpilibj] Add toString() methods to Color and Color8Bit (#4286)
fc37265da5 [wpimath] Add angle measurement convention to ArmFeedforward docs (NFC) (#4285)
a4ec13eb0e [wpilibjexamples] Remove unnecessary voltage desaturation
2fa52007af [wpilibc] Use GetBatteryVoltage() in MotorController::SetVoltage
d9f9cd1140 [wpimath] Reset prev_time on pose estimator reset (#4283)
8b6df88783 [wpilibj] Tachometer.getFrequency(): Fix bug (#4281)
345cff08c0 [wpiutil] Make wpi::array constexpr (#4278)
57428112ac [wpimath] Upgrade to Drake v1.3.0 (#4279)
a18d4ff154 [build] Fix tools not being copied when built with -Ponly* (#4276)
d1cd07b9f3 [wpigui] Add OpenURL (#4273)
e67f8e917a [glass] Use glfwSetKeyCallback for Enter key remap (#4275)
be2fedfe50 [wpimath] Add stdexcept include for std::invalid_argument (IWYU) (#4274)
7ad2be172e [build] Update native-utils to 2023.0.1 (#4272)
abc605c9c9 [ci] Update workflows to 20.04 base image (#4271)
3e94805220 [wpiutil] Reduce llvm collections patches (#4268)
db2e1d170e [upstream_utils] Document how to update thirdparty libraries (#4253)
96ebdcaf16 [wpimath] Remove unused Eigen AutoDiff module (#4267)
553b2a3b12 [upstream_utils] Fix stackwalker (#4265)
3e13ef42eb [wpilibc] Add missing std::array #include (include-what-you-use) (#4266)
d651a1fcec Fix internal deprecation warnings (#4257)
b193b318c1 [commands] Add unless() decorator (#4244)
ef3714223b [commands] Remove docs reference to obsolete interrupted() method (NFC) (#4262)
3d8dbbbac3 [readme] Add quickstart (#4225)
013efdde25 [wpinet] Wrap a number of newer libuv features (#4260)
816aa4e465 [wpilib] Add Pneumatics sim classes (#4033)
046c2c8972 [wpilibc] Rename SpeedControllerGroupTest.cpp (#4258)
d80e9cdf64 [upstream_utils] Use shallow clones for thirdparty repos (#4255)
7576136b4a [upstream_utils] Make update_llvm.py executable (#4254)
c3b223ce60 [wpiutil] Vendor llvm and update to 13.0.0 (#4224)
5aa67f56e6 [wpimath] Clean up math comments (#4252)
fff4d1f44e [wpimath] Extend Eigen warning suppression to GCC 12 (#4251)
0d9956273c [wpimath] Add CoordinateSystem.convert() translation and rotation overloads (#4227)
3fada4e0b4 [wpinet] Update to libuv 1.44.1 (#4232)
65b23ac45e [wpilibc] Fix return value of DriverStation::GetJoystickAxisType() (#4230)
4ac34c0141 [upstream_utils] Cleanup update_libuv.py (#4249)
8bd614bb1e [upstream_utils] Use "git am" instead of "git apply" for patches (#4248)
4253d6d5f0 [upstream_utils] Apply "git am" patches individually (#4250)
6a4752dcdc Fix GCC 12.1 warning false positives (#4246)
5876b40f08 [wpimath] Memoize CoordinateSystem and CoordinateAxis statics (#4241)
5983434a70 [cameraserver] Replace IterativeRobot in comment sample code with TimedRobot (#4238)
a3d44a1e69 [wpimath] Add Translation2d.getAngle() (#4217)
d364bbd5a7 [upstream_utils] Give vendor update scripts execute permissions (#4226)
f341e1b2be [wpimath] Document standard coordinate systems better (NFC) (#4228)
9af389b200 [wpinet] AddrToName: Initialize name (#4229)
2ae4adf2d7 [ci] Add wpiformat command to PRs (#4223)
178b2a1e88 Contributing.md: Correct version of clang-format used (#4222)
18db343cdc [wpiutil, wpinet] Vendor libuv, stack walker (#4219)
f0c821282a [build] Use artifactory mirror (#4220)
d673ead481 [wpinet] Move network portions of wpiutil into new wpinet library (#4077)
b33715db15 [wpimath] Add CoordinateSystem class (#4214)
99424ad562 [sim] Allow creating a PWMSim object from a PWMMotorController (#4039)
dc6f641fd2 [wpimath] PIDController: Reset position and velocity error when reset() is called. (#4064)
f20a20f3f1 [wpimath] Add 3D geometry classes (#4175)
708a4bc3bc [wpimath] Conserve previously calculated swerve module angles when updating states for stationary ChassisSpeeds (#4208)
ef7ed21a9d [wpimath] Improve accuracy of ComputerVisionUtil.calculateDistanceToTarget() (#4215)
b1abf455c1 [wpimath] LTVUnicycleController: Use LUT, provide default hyperparameters (#4213)
d5456cf278 [wpimath] LTVDifferentialDriveController: Remove unused variable (#4212)
99343d40ba [command] Remove old command-based framework (#4211)
ee03a7ad3b Remove most 2022 deprecations (#4205)
ce1a7d698a [wpimath] Refactor WheelVoltages inner class to a separate file (#4203)
87bf70fa8e [wpimath] Add LTV controllers (#4094)
ebd2a303bf [wpimath] Remove deprecated MakeMatrix() function (#4202)
e28776d361 [wpimath] LinearSystemLoop: Add extern templates for common cases
dac1429aa9 [wpimath] LQR: Use extern template instead of Impl class
e767605e94 [wpimath] Add typedefs for common types
97c493241f [wpimath] UnscentedKalmanFilter: Move implementation out-of-line
8ea90d8bc9 [wpimath] ExtendedKalmanFilter: Move implementation out-of-line
ae7b1851ec [wpimath] KalmanFilter: Use extern template instead of Impl class
e3d62c22d3 [wpimath] Add extern templates for common cases
7200c4951d [wpiutil] SymbolExports: Add WPILIB_IMPORTS for dllimport
84056c9347 [wpiutil] SymbolExports: Add EXPORT_TEMPLATE_DECLARE/DEFINE
09cf6eeecb [wpimath] ApplyDeadband: add a scale param (#3865)
03230fc842 [build,ci] Enable artifactory build cache (#4200)
63cf3aaa3f [examples] Don't square ArcadeDrive inputs in auto (#4201)
18ff694f02 [wpimath] Add Rotation2d.fromRadians factory (#4178)
4f79ceedd9 [wpilibc] Add missing #include (#4198)
f7ca72fb41 [command] Rename PerpetualCommand to EndlessCommand (#4177)
a06b3f0307 [hal] Correct documentation on updateNotifierAlarm (#4156)
d926dd1610 [wpimath] Fix pose estimator performance (#4111)
51bc893bc5 [wpiutil] CircularBuffer: Change Java package-private methods to public (#4181)
fbe761f7f6 [build] Increase Gradle JVM heap size (#4172)
5ebe911933 [wpimath] Add DifferentialDriveAccelerationLimiter (#4091)
3919250da2 [wpilibj] Remove finalizers (#4158)
b3aee28388 [commands] Allow BooleanSupplier for Trigger operations (#4103)
9d20ab3024 [wpilib] Allow disabling ElevatorSim gravity (#4145)
aaa69f6717 [ci] Remove 32-bit Windows builds (#4078)
355a11a414 Update Java linters and fix new PMD errors (#4157)
ffc69d406c [examples] Reduce suggested acceleration in Ramsete example (#4171)
922d50079a [wpimath] Units: fix comment in degreesToRotations (NFC) (#4159)
dd163b62ae [wpimath] Rotation2d: Add factory method that uses rotations (#4166)
bd80e220b9 [ci] Upgrade CMake actions (#4161)
aef4b16d4c [wpimath] Remove unnecessary NOLINT in LinearPlantInversionFeedforward (NFC) (#4155)
975171609e [wpilib] Compressor: Rename enabled to isEnabled (#4147)
5bf46a9093 [wpimath] Add ComputerVisionUtil (#4124)
f27a1f9bfb [commands] Fix JoystickButton.getAsBoolean (#4131)
1b26e2d5da [commands] Add RepeatCommand (#4009)
88222daa3d [hal] Fix misspelling in AnalogInput/Output docs (NFC) (#4153)
81c5b41ce1 [wpilibj] Document MechanismLigament2d angle unit (NFC) (#4142)
9650e6733e [wpiutil] DataLog: Document finish and thread safety (NFC) (#4140)
c8905ec29a [wpimath] Remove ImplicitModelFollower dt argument (#4119)
b4620f01f9 [wpimath] Fix Rotation2d interpolation in Java (#4125)
2e462a19d3 [wpimath] Constexprify units unary operators (#4138)
069f932e59 [build] Fix gl3w cmake build (#4139)
126e3de91a [wpilibc] Remove unused SetPriority() call from Ultrasonic (#4123)
ba0dccaae4 [wpimath] Fix reference to Rotation2d.fromRadians() (#4118)
e1b6e5f212 [wpilib] Improve MotorSafety documentation (NFC) (#4120)
8d79dc8738 [wpimath] Add ImplicitModelFollower (#4056)
78108c2aba [wpimath] Fix PIDController having incorrect error after calling SetSetpoint() (#4070)
cdafc723fb [examples] Remove unused LinearPlantInversionFeedforward includes (#4069)
0d70884dce [wpimath] Add InterpolatedTreeMap (#4073)
765efa325e [wpimath] Remove redundant column index from vectors (#4116)
89ffcbbe41 [wpimath] Update TrapezoidProfile class name in comment (NFC) (#4107)
95ae23b0e7 [wpimath] Improve EKF numerical stability (#4093)
d5cb6fed67 [wpimath] Support zero cost entries in MakeCostMatrix() (#4100)
d0fef18378 [wpimath] Remove redundant `this.` from ExtendedKalmanFilter.java (#4115)
d640c0f41f [wpimath] Fix pose estimator local measurement standard deviation docs (NFC) (#4113)
a2fa5e3ff7 [wpilibc] BatterySim: Provide non-initializer list versions of Calculate (#4076)
a3eea9958e [hal] Add link to FRC CAN Spec (NFC) (#4086)
db27331d7b [wpilib] Update DifferentialDrive docs (NFC) (#4085)
fdfb31f164 [dlt] Export boolean[] values (#4082)
f93c3331b3 [wpigui] disable changing directory when initializing on MacOS (#4092)
ab7ac4fbb9 [build] Fix various warnings in cmake builds (#4081)
bc39a1a293 [wpilibc] Fix moved pneumatics objects not destructing properly (#4068)
2668130e70 [wpimath] Remove SwerveDrivePoseEstimator encoder reset warning (#4066)
d27ed3722b [ci] Set actions workflow concurrency (#4060)
dae18308c9 [wpimath] Minor fixes to Rotation2d docs (NFC) (#4055)
d66555e42f [datalogtool] Add datalogtool
9f52d8a3b1 [wpilib] DriverStation: Add DataLog support for modes and joystick data
757ea91932 [wpilib] Add DataLogManager
02a804f1c5 [ntcore] Add DataLog support
9b500df0d9 [wpiutil] Add high speed data logging
5a89575b3a [wpiutil] Import customized LLVM MemoryBuffer
b8c4d7527b [wpiutil] Add MappedFileRegion
ac5d46cfa7 [wpilibc] Fix ProfiledPID SetTolerance default velocity value (#4054)
bc9e96e86f [wpilib] Absolute Encoder API and behavior fixes (#4052)
f88c435dd0 [hal] Add mechanism to cancel all periodic callbacks (#4049)

Change-Id: I49aa5b08abbefc7a045e99e19d48ce2cd8fc4d1b
git-subtree-dir: third_party/allwpilib
git-subtree-split: 83f1860047c86aa3330fcb41caf3b2047e074804
Signed-off-by: James Kuszmaul <jabukuszmaul+collab@gmail.com>
diff --git a/wpilibc/src/dev/native/cpp/main.cpp b/wpilibc/src/dev/native/cpp/main.cpp
index 28fac9d..fcc55b1 100644
--- a/wpilibc/src/dev/native/cpp/main.cpp
+++ b/wpilibc/src/dev/native/cpp/main.cpp
@@ -9,6 +9,6 @@
 
 int main() {
   fmt::print("Hello World\n");
-  fmt::print("{}\n", HAL_GetRuntimeType());
+  fmt::print("{}\n", static_cast<int32_t>(HAL_GetRuntimeType()));
   fmt::print("{}\n", GetWPILibVersion());
 }
diff --git a/wpilibc/src/main/native/cpp/ADIS16448_IMU.cpp b/wpilibc/src/main/native/cpp/ADIS16448_IMU.cpp
index 25d674f..7048720 100644
--- a/wpilibc/src/main/native/cpp/ADIS16448_IMU.cpp
+++ b/wpilibc/src/main/native/cpp/ADIS16448_IMU.cpp
@@ -22,11 +22,11 @@
 
 #include <algorithm>
 #include <cmath>
+#include <numbers>
 #include <string>
 
 #include <hal/HAL.h>
 #include <networktables/NTSendableBuilder.h>
-#include <wpi/numbers>
 #include <wpi/sendable/SendableRegistry.h>
 
 #include "frc/Errors.h"
@@ -56,14 +56,14 @@
                             const char* function, const S& format,
                             Args&&... args) {
   frc::ReportErrorV(status, file, line, function, format,
-                    fmt::make_args_checked<Args...>(format, args...));
+                    fmt::make_format_args(args...));
 }
 }  // namespace
 
 #define REPORT_WARNING(msg) \
-  ADISReportError(warn::Warning, __FILE__, __LINE__, __FUNCTION__, "{}", msg);
+  ADISReportError(warn::Warning, __FILE__, __LINE__, __FUNCTION__, msg);
 #define REPORT_ERROR(msg) \
-  ADISReportError(err::Error, __FILE__, __LINE__, __FUNCTION__, "{}", msg)
+  ADISReportError(err::Error, __FILE__, __LINE__, __FUNCTION__, msg)
 
 ADIS16448_IMU::ADIS16448_IMU()
     : ADIS16448_IMU(kZ, SPI::Port::kMXP, CalibrationTime::_512ms) {}
@@ -74,6 +74,8 @@
       m_spi_port(port),
       m_simDevice("Gyro:ADIS16448", port) {
   if (m_simDevice) {
+    m_connected =
+        m_simDevice.CreateBoolean("connected", hal::SimDevice::kInput, true);
     m_simGyroAngleX =
         m_simDevice.CreateDouble("gyro_angle_x", hal::SimDevice::kInput, 0.0);
     m_simGyroAngleY =
@@ -102,8 +104,8 @@
     DigitalOutput* m_reset_out = new DigitalOutput(18);  // Drive MXP DIO8 low
     Wait(10_ms);                                         // Wait 10ms
     delete m_reset_out;
-    new DigitalInput(18);  // Set MXP DIO8 high
-    Wait(500_ms);          // Wait 500ms for reset to complete
+    m_reset_in = new DigitalInput(18);  // Set MXP DIO8 high
+    Wait(500_ms);                       // Wait 500ms for reset to complete
 
     ConfigCalTime(cal_time);
 
@@ -143,13 +145,21 @@
 
     // TODO: Find what the proper pin is to turn this LED
     //  Drive MXP PWM5 (IMU ready LED) low (active low)
-    new DigitalOutput(19);
+    m_status_led = new DigitalOutput(19);
   }
 
   // Report usage and post data to DS
   HAL_Report(HALUsageReporting::kResourceType_ADIS16448, 0);
 
   wpi::SendableRegistry::AddLW(this, "ADIS16448", port);
+  m_connected = true;
+}
+
+bool ADIS16448_IMU::IsConnected() const {
+  if (m_simConnected) {
+    return m_simConnected.Get();
+  }
+  return m_connected;
 }
 
 /**
@@ -186,7 +196,7 @@
       while (data_count > 0) {
         /* Dequeue 200 at a time, or the remainder of the buffer if less than
          * 200 */
-        m_spi->ReadAutoReceivedData(trashBuffer, std::min(200, data_count),
+        m_spi->ReadAutoReceivedData(trashBuffer, (std::min)(200, data_count),
                                     0_s);
         /* Update remaining buffer count */
         data_count = m_spi->ReadAutoReceivedData(trashBuffer, 0, 0_s);
@@ -197,9 +207,7 @@
   if (m_spi == nullptr) {
     m_spi = new SPI(m_spi_port);
     m_spi->SetClockRate(1000000);
-    m_spi->SetMSBFirst();
-    m_spi->SetSampleDataOnTrailingEdge();
-    m_spi->SetClockActiveLow();
+    m_spi->SetMode(frc::SPI::Mode::kMode3);
     m_spi->SetChipSelectActiveLow();
     ReadRegister(PROD_ID);  // Dummy read
 
@@ -323,7 +331,7 @@
 void ADIS16448_IMU::Calibrate() {
   std::scoped_lock sync(m_mutex);
   // Calculate the running average
-  int gyroAverageSize = std::min(m_accum_count, m_avg_size);
+  int gyroAverageSize = (std::min)(m_accum_count, m_avg_size);
   double accum_gyro_rate_x = 0.0;
   double accum_gyro_rate_y = 0.0;
   double accum_gyro_rate_z = 0.0;
@@ -387,6 +395,14 @@
 }
 
 void ADIS16448_IMU::Close() {
+  if (m_reset_in != nullptr) {
+    delete m_reset_in;
+    m_reset_in = nullptr;
+  }
+  if (m_status_led != nullptr) {
+    delete m_status_led;
+    m_status_led = nullptr;
+  }
   if (m_thread_active) {
     m_thread_active = false;
     if (m_acquire_task.joinable()) {
@@ -624,29 +640,29 @@
 
 /* Complementary filter functions */
 double ADIS16448_IMU::FormatFastConverge(double compAngle, double accAngle) {
-  if (compAngle > accAngle + wpi::numbers::pi) {
-    compAngle = compAngle - 2.0 * wpi::numbers::pi;
-  } else if (accAngle > compAngle + wpi::numbers::pi) {
-    compAngle = compAngle + 2.0 * wpi::numbers::pi;
+  if (compAngle > accAngle + std::numbers::pi) {
+    compAngle = compAngle - 2.0 * std::numbers::pi;
+  } else if (accAngle > compAngle + std::numbers::pi) {
+    compAngle = compAngle + 2.0 * std::numbers::pi;
   }
   return compAngle;
 }
 
 double ADIS16448_IMU::FormatRange0to2PI(double compAngle) {
-  while (compAngle >= 2 * wpi::numbers::pi) {
-    compAngle = compAngle - 2.0 * wpi::numbers::pi;
+  while (compAngle >= 2 * std::numbers::pi) {
+    compAngle = compAngle - 2.0 * std::numbers::pi;
   }
   while (compAngle < 0.0) {
-    compAngle = compAngle + 2.0 * wpi::numbers::pi;
+    compAngle = compAngle + 2.0 * std::numbers::pi;
   }
   return compAngle;
 }
 
 double ADIS16448_IMU::FormatAccelRange(double accelAngle, double accelZ) {
   if (accelZ < 0.0) {
-    accelAngle = wpi::numbers::pi - accelAngle;
+    accelAngle = std::numbers::pi - accelAngle;
   } else if (accelZ > 0.0 && accelAngle < 0.0) {
-    accelAngle = 2.0 * wpi::numbers::pi + accelAngle;
+    accelAngle = 2.0 * std::numbers::pi + accelAngle;
   }
   return accelAngle;
 }
@@ -657,8 +673,8 @@
   compAngle =
       m_alpha * (compAngle + omega * m_dt) + (1.0 - m_alpha) * accelAngle;
   compAngle = FormatRange0to2PI(compAngle);
-  if (compAngle > wpi::numbers::pi) {
-    compAngle = compAngle - 2.0 * wpi::numbers::pi;
+  if (compAngle > std::numbers::pi) {
+    compAngle = compAngle - 2.0 * std::numbers::pi;
   }
   return compAngle;
 }
@@ -673,7 +689,8 @@
 
   /* Check max */
   if (DecimationSetting > 9) {
-    REPORT_ERROR("Attemted to write an invalid decimation value. Capping at 9");
+    REPORT_ERROR(
+        "Attempted to write an invalid decimation value. Capping at 9");
     DecimationSetting = 9;
   }
 
@@ -867,8 +884,6 @@
  **/
 void ADIS16448_IMU::InitSendable(nt::NTSendableBuilder& builder) {
   builder.SetSmartDashboardType("ADIS16448 IMU");
-  auto yaw_angle = builder.GetEntry("Yaw Angle").GetHandle();
-  builder.SetUpdateTable([=]() {
-    nt::NetworkTableEntry(yaw_angle).SetDouble(GetAngle().value());
-  });
+  builder.AddDoubleProperty(
+      "Yaw Angle", [=, this] { return GetAngle().value(); }, nullptr);
 }
diff --git a/wpilibc/src/main/native/cpp/ADIS16470_IMU.cpp b/wpilibc/src/main/native/cpp/ADIS16470_IMU.cpp
index f48d827..e1cd986 100644
--- a/wpilibc/src/main/native/cpp/ADIS16470_IMU.cpp
+++ b/wpilibc/src/main/native/cpp/ADIS16470_IMU.cpp
@@ -19,11 +19,11 @@
 #include <frc/Timer.h>
 
 #include <cmath>
+#include <numbers>
 #include <string>
 
 #include <hal/HAL.h>
 #include <networktables/NTSendableBuilder.h>
-#include <wpi/numbers>
 #include <wpi/sendable/SendableRegistry.h>
 
 #include "frc/Errors.h"
@@ -50,14 +50,14 @@
                             const char* function, const S& format,
                             Args&&... args) {
   frc::ReportErrorV(status, file, line, function, format,
-                    fmt::make_args_checked<Args...>(format, args...));
+                    fmt::make_format_args(args...));
 }
 }  // namespace
 
 #define REPORT_WARNING(msg) \
-  ADISReportError(warn::Warning, __FILE__, __LINE__, __FUNCTION__, "{}", msg);
+  ADISReportError(warn::Warning, __FILE__, __LINE__, __FUNCTION__, msg);
 #define REPORT_ERROR(msg) \
-  ADISReportError(err::Error, __FILE__, __LINE__, __FUNCTION__, "{}", msg)
+  ADISReportError(err::Error, __FILE__, __LINE__, __FUNCTION__, msg)
 
 /**
  * Constructor.
@@ -72,6 +72,8 @@
       m_calibration_time(static_cast<uint16_t>(cal_time)),
       m_simDevice("Gyro:ADIS16470", port) {
   if (m_simDevice) {
+    m_connected =
+        m_simDevice.CreateBoolean("connected", hal::SimDevice::kInput, true);
     m_simGyroAngleX =
         m_simDevice.CreateDouble("gyro_angle_x", hal::SimDevice::kInput, 0.0);
     m_simGyroAngleY =
@@ -101,8 +103,8 @@
         new DigitalOutput(27);  // Drive SPI CS2 (IMU RST) low
     Wait(10_ms);                // Wait 10ms
     delete m_reset_out;
-    new DigitalInput(27);  // Set SPI CS2 (IMU RST) high
-    Wait(500_ms);          // Wait 500ms for reset to complete
+    m_reset_in = new DigitalInput(27);  // Set SPI CS2 (IMU RST) high
+    Wait(500_ms);                       // Wait 500ms for reset to complete
 
     // Configure standard SPI
     if (!SwitchToStandardSPI()) {
@@ -140,13 +142,21 @@
     REPORT_WARNING("ADIS16470 IMU Successfully Initialized!");
 
     // Drive SPI CS3 (IMU ready LED) low (active low)
-    new DigitalOutput(28);
+    m_status_led = new DigitalOutput(28);
   }
 
   // Report usage and post data to DS
   HAL_Report(HALUsageReporting::kResourceType_ADIS16470, 0);
 
   wpi::SendableRegistry::AddLW(this, "ADIS16470", port);
+  m_connected = true;
+}
+
+bool ADIS16470_IMU::IsConnected() const {
+  if (m_simConnected) {
+    return m_simConnected.Get();
+  }
+  return m_connected;
 }
 
 /**
@@ -183,9 +193,9 @@
       while (data_count > 0) {
         /* Receive data, max of 200 words at a time (prevent potential segfault)
          */
-        m_spi->ReadAutoReceivedData(trashBuffer, std::min(data_count, 200),
+        m_spi->ReadAutoReceivedData(trashBuffer, (std::min)(data_count, 200),
                                     0_s);
-        /*Get the reamining data count */
+        /*Get the remaining data count */
         data_count = m_spi->ReadAutoReceivedData(trashBuffer, 0, 0_s);
       }
     }
@@ -194,9 +204,7 @@
   if (m_spi == nullptr) {
     m_spi = new SPI(m_spi_port);
     m_spi->SetClockRate(2000000);
-    m_spi->SetMSBFirst();
-    m_spi->SetSampleDataOnTrailingEdge();
-    m_spi->SetClockActiveLow();
+    m_spi->SetMode(frc::SPI::Mode::kMode3);
     m_spi->SetChipSelectActiveLow();
     ReadRegister(PROD_ID);  // Dummy read
 
@@ -441,6 +449,14 @@
 }
 
 void ADIS16470_IMU::Close() {
+  if (m_reset_in != nullptr) {
+    delete m_reset_in;
+    m_reset_in = nullptr;
+  }
+  if (m_status_led != nullptr) {
+    delete m_status_led;
+    m_status_led = nullptr;
+  }
   if (m_thread_active) {
     m_thread_active = false;
     if (m_acquire_task.joinable()) {
@@ -469,7 +485,7 @@
  * @brief Main acquisition loop. Typically called asynchronously and free-wheels
  *while the robot code is active.
  *
- * This is the main acquisiton loop for the IMU. During each iteration, data
+ * This is the main acquisition loop for the IMU. During each iteration, data
  *read using auto SPI is extracted from the FPGA FIFO, split, scaled, and
  *integrated. Each X, Y, and Z value is 32-bits split across 4 indices (bytes)
  *in the buffer. Auto SPI puts one byte in each index location. Each index is
@@ -641,29 +657,29 @@
 
 /* Complementary filter functions */
 double ADIS16470_IMU::FormatFastConverge(double compAngle, double accAngle) {
-  if (compAngle > accAngle + wpi::numbers::pi) {
-    compAngle = compAngle - 2.0 * wpi::numbers::pi;
-  } else if (accAngle > compAngle + wpi::numbers::pi) {
-    compAngle = compAngle + 2.0 * wpi::numbers::pi;
+  if (compAngle > accAngle + std::numbers::pi) {
+    compAngle = compAngle - 2.0 * std::numbers::pi;
+  } else if (accAngle > compAngle + std::numbers::pi) {
+    compAngle = compAngle + 2.0 * std::numbers::pi;
   }
   return compAngle;
 }
 
 double ADIS16470_IMU::FormatRange0to2PI(double compAngle) {
-  while (compAngle >= 2 * wpi::numbers::pi) {
-    compAngle = compAngle - 2.0 * wpi::numbers::pi;
+  while (compAngle >= 2 * std::numbers::pi) {
+    compAngle = compAngle - 2.0 * std::numbers::pi;
   }
   while (compAngle < 0.0) {
-    compAngle = compAngle + 2.0 * wpi::numbers::pi;
+    compAngle = compAngle + 2.0 * std::numbers::pi;
   }
   return compAngle;
 }
 
 double ADIS16470_IMU::FormatAccelRange(double accelAngle, double accelZ) {
   if (accelZ < 0.0) {
-    accelAngle = wpi::numbers::pi - accelAngle;
+    accelAngle = std::numbers::pi - accelAngle;
   } else if (accelZ > 0.0 && accelAngle < 0.0) {
-    accelAngle = 2.0 * wpi::numbers::pi + accelAngle;
+    accelAngle = 2.0 * std::numbers::pi + accelAngle;
   }
   return accelAngle;
 }
@@ -674,8 +690,8 @@
   compAngle =
       m_alpha * (compAngle + omega * m_dt) + (1.0 - m_alpha) * accelAngle;
   compAngle = FormatRange0to2PI(compAngle);
-  if (compAngle > wpi::numbers::pi) {
-    compAngle = compAngle - 2.0 * wpi::numbers::pi;
+  if (compAngle > std::numbers::pi) {
+    compAngle = compAngle - 2.0 * std::numbers::pi;
   }
   return compAngle;
 }
@@ -802,8 +818,6 @@
  **/
 void ADIS16470_IMU::InitSendable(nt::NTSendableBuilder& builder) {
   builder.SetSmartDashboardType("ADIS16470 IMU");
-  auto yaw_angle = builder.GetEntry("Yaw Angle").GetHandle();
-  builder.SetUpdateTable([=]() {
-    nt::NetworkTableEntry(yaw_angle).SetDouble(GetAngle().value());
-  });
+  builder.AddDoubleProperty(
+      "Yaw Angle", [=, this] { return GetAngle().value(); }, nullptr);
 }
diff --git a/wpilibc/src/main/native/cpp/ADXL345_I2C.cpp b/wpilibc/src/main/native/cpp/ADXL345_I2C.cpp
index 484e690..a23846d 100644
--- a/wpilibc/src/main/native/cpp/ADXL345_I2C.cpp
+++ b/wpilibc/src/main/native/cpp/ADXL345_I2C.cpp
@@ -5,6 +5,7 @@
 #include "frc/ADXL345_I2C.h"
 
 #include <hal/FRCUsageReporting.h>
+#include <networktables/DoubleTopic.h>
 #include <networktables/NTSendableBuilder.h>
 #include <wpi/sendable/SendableRegistry.h>
 
@@ -93,13 +94,13 @@
 
 void ADXL345_I2C::InitSendable(nt::NTSendableBuilder& builder) {
   builder.SetSmartDashboardType("3AxisAccelerometer");
-  auto x = builder.GetEntry("X").GetHandle();
-  auto y = builder.GetEntry("Y").GetHandle();
-  auto z = builder.GetEntry("Z").GetHandle();
-  builder.SetUpdateTable([=] {
-    auto data = GetAccelerations();
-    nt::NetworkTableEntry(x).SetDouble(data.XAxis);
-    nt::NetworkTableEntry(y).SetDouble(data.YAxis);
-    nt::NetworkTableEntry(z).SetDouble(data.ZAxis);
-  });
+  builder.SetUpdateTable(
+      [this, x = nt::DoubleTopic{builder.GetTopic("X")}.Publish(),
+       y = nt::DoubleTopic{builder.GetTopic("Y")}.Publish(),
+       z = nt::DoubleTopic{builder.GetTopic("Z")}.Publish()]() mutable {
+        auto data = GetAccelerations();
+        x.Set(data.XAxis);
+        y.Set(data.YAxis);
+        z.Set(data.ZAxis);
+      });
 }
diff --git a/wpilibc/src/main/native/cpp/ADXL345_SPI.cpp b/wpilibc/src/main/native/cpp/ADXL345_SPI.cpp
index 9a95bcc..1dc4c5b 100644
--- a/wpilibc/src/main/native/cpp/ADXL345_SPI.cpp
+++ b/wpilibc/src/main/native/cpp/ADXL345_SPI.cpp
@@ -5,6 +5,7 @@
 #include "frc/ADXL345_SPI.h"
 
 #include <hal/FRCUsageReporting.h>
+#include <networktables/DoubleTopic.h>
 #include <networktables/NTSendableBuilder.h>
 #include <wpi/sendable/SendableRegistry.h>
 
@@ -21,9 +22,7 @@
     m_simZ = m_simDevice.CreateDouble("z", hal::SimDevice::kInput, 0.0);
   }
   m_spi.SetClockRate(500000);
-  m_spi.SetMSBFirst();
-  m_spi.SetSampleDataOnTrailingEdge();
-  m_spi.SetClockActiveLow();
+  m_spi.SetMode(frc::SPI::Mode::kMode3);
   m_spi.SetChipSelectActiveHigh();
 
   uint8_t commands[2];
@@ -120,13 +119,13 @@
 
 void ADXL345_SPI::InitSendable(nt::NTSendableBuilder& builder) {
   builder.SetSmartDashboardType("3AxisAccelerometer");
-  auto x = builder.GetEntry("X").GetHandle();
-  auto y = builder.GetEntry("Y").GetHandle();
-  auto z = builder.GetEntry("Z").GetHandle();
-  builder.SetUpdateTable([=] {
-    auto data = GetAccelerations();
-    nt::NetworkTableEntry(x).SetDouble(data.XAxis);
-    nt::NetworkTableEntry(y).SetDouble(data.YAxis);
-    nt::NetworkTableEntry(z).SetDouble(data.ZAxis);
-  });
+  builder.SetUpdateTable(
+      [this, x = nt::DoubleTopic{builder.GetTopic("X")}.Publish(),
+       y = nt::DoubleTopic{builder.GetTopic("Y")}.Publish(),
+       z = nt::DoubleTopic{builder.GetTopic("Z")}.Publish()]() mutable {
+        auto data = GetAccelerations();
+        x.Set(data.XAxis);
+        y.Set(data.YAxis);
+        z.Set(data.ZAxis);
+      });
 }
diff --git a/wpilibc/src/main/native/cpp/ADXL362.cpp b/wpilibc/src/main/native/cpp/ADXL362.cpp
index deda965..fa0a8a3 100644
--- a/wpilibc/src/main/native/cpp/ADXL362.cpp
+++ b/wpilibc/src/main/native/cpp/ADXL362.cpp
@@ -5,6 +5,7 @@
 #include "frc/ADXL362.h"
 
 #include <hal/FRCUsageReporting.h>
+#include <networktables/DoubleTopic.h>
 #include <networktables/NTSendableBuilder.h>
 #include <wpi/sendable/SendableRegistry.h>
 
@@ -43,9 +44,7 @@
   }
 
   m_spi.SetClockRate(3000000);
-  m_spi.SetMSBFirst();
-  m_spi.SetSampleDataOnTrailingEdge();
-  m_spi.SetClockActiveLow();
+  m_spi.SetMode(frc::SPI::Mode::kMode3);
   m_spi.SetChipSelectActiveLow();
 
   uint8_t commands[3];
@@ -56,7 +55,7 @@
     commands[2] = 0;
     m_spi.Transaction(commands, commands, 3);
     if (commands[2] != 0xF2) {
-      FRC_ReportError(err::Error, "{}", "could not find ADXL362");
+      FRC_ReportError(err::Error, "could not find ADXL362");
       m_gsPerLSB = 0.0;
       return;
     }
@@ -184,13 +183,13 @@
 
 void ADXL362::InitSendable(nt::NTSendableBuilder& builder) {
   builder.SetSmartDashboardType("3AxisAccelerometer");
-  auto x = builder.GetEntry("X").GetHandle();
-  auto y = builder.GetEntry("Y").GetHandle();
-  auto z = builder.GetEntry("Z").GetHandle();
-  builder.SetUpdateTable([=] {
-    auto data = GetAccelerations();
-    nt::NetworkTableEntry(x).SetDouble(data.XAxis);
-    nt::NetworkTableEntry(y).SetDouble(data.YAxis);
-    nt::NetworkTableEntry(z).SetDouble(data.ZAxis);
-  });
+  builder.SetUpdateTable(
+      [this, x = nt::DoubleTopic{builder.GetTopic("X")}.Publish(),
+       y = nt::DoubleTopic{builder.GetTopic("Y")}.Publish(),
+       z = nt::DoubleTopic{builder.GetTopic("Z")}.Publish()]() mutable {
+        auto data = GetAccelerations();
+        x.Set(data.XAxis);
+        y.Set(data.YAxis);
+        z.Set(data.ZAxis);
+      });
 }
diff --git a/wpilibc/src/main/native/cpp/ADXRS450_Gyro.cpp b/wpilibc/src/main/native/cpp/ADXRS450_Gyro.cpp
index 5a2f625..4d4b22d 100644
--- a/wpilibc/src/main/native/cpp/ADXRS450_Gyro.cpp
+++ b/wpilibc/src/main/native/cpp/ADXRS450_Gyro.cpp
@@ -32,21 +32,21 @@
 ADXRS450_Gyro::ADXRS450_Gyro(SPI::Port port)
     : m_spi(port), m_port(port), m_simDevice("Gyro:ADXRS450", port) {
   if (m_simDevice) {
+    m_simConnected =
+        m_simDevice.CreateBoolean("connected", hal::SimDevice::kInput, true);
     m_simAngle =
         m_simDevice.CreateDouble("angle_x", hal::SimDevice::kInput, 0.0);
     m_simRate = m_simDevice.CreateDouble("rate_x", hal::SimDevice::kInput, 0.0);
   }
 
   m_spi.SetClockRate(3000000);
-  m_spi.SetMSBFirst();
-  m_spi.SetSampleDataOnLeadingEdge();
-  m_spi.SetClockActiveHigh();
+  m_spi.SetMode(frc::SPI::Mode::kMode0);
   m_spi.SetChipSelectActiveLow();
 
   if (!m_simDevice) {
     // Validate the part ID
     if ((ReadRegister(kPIDRegister) & 0xff00) != 0x5200) {
-      FRC_ReportError(err::Error, "{}", "could not find ADXRS450 gyro");
+      FRC_ReportError(err::Error, "could not find ADXRS450 gyro");
       return;
     }
 
@@ -59,6 +59,14 @@
   HAL_Report(HALUsageReporting::kResourceType_ADXRS450, port + 1);
 
   wpi::SendableRegistry::AddLW(this, "ADXRS450_Gyro", port);
+  m_connected = true;
+}
+
+bool ADXRS450_Gyro::IsConnected() const {
+  if (m_simConnected) {
+    return m_simConnected.Get();
+  }
+  return m_connected;
 }
 
 static bool CalcParity(int v) {
@@ -139,5 +147,5 @@
 void ADXRS450_Gyro::InitSendable(wpi::SendableBuilder& builder) {
   builder.SetSmartDashboardType("Gyro");
   builder.AddDoubleProperty(
-      "Value", [=] { return GetAngle(); }, nullptr);
+      "Value", [=, this] { return GetAngle(); }, nullptr);
 }
diff --git a/wpilibc/src/main/native/cpp/AddressableLED.cpp b/wpilibc/src/main/native/cpp/AddressableLED.cpp
index 759c386..538dc10 100644
--- a/wpilibc/src/main/native/cpp/AddressableLED.cpp
+++ b/wpilibc/src/main/native/cpp/AddressableLED.cpp
@@ -51,9 +51,9 @@
 static_assert(sizeof(AddressableLED::LEDData) == sizeof(HAL_AddressableLEDData),
               "LED Structs MUST be the same size");
 
-void AddressableLED::SetData(wpi::span<const LEDData> ledData) {
+void AddressableLED::SetData(std::span<const LEDData> ledData) {
   int32_t status = 0;
-  HAL_WriteAddressableLEDData(m_handle, ledData.begin(), ledData.size(),
+  HAL_WriteAddressableLEDData(m_handle, ledData.data(), ledData.size(),
                               &status);
   FRC_CheckErrorStatus(status, "Port {}", m_port);
 }
diff --git a/wpilibc/src/main/native/cpp/AnalogAccelerometer.cpp b/wpilibc/src/main/native/cpp/AnalogAccelerometer.cpp
index 0b39e64..ca8af7d 100644
--- a/wpilibc/src/main/native/cpp/AnalogAccelerometer.cpp
+++ b/wpilibc/src/main/native/cpp/AnalogAccelerometer.cpp
@@ -21,7 +21,7 @@
 AnalogAccelerometer::AnalogAccelerometer(AnalogInput* channel)
     : m_analogInput(channel, wpi::NullDeleter<AnalogInput>()) {
   if (!channel) {
-    throw FRC_MakeError(err::NullParameter, "{}", "channel");
+    throw FRC_MakeError(err::NullParameter, "channel");
   }
   InitAccelerometer();
 }
@@ -29,7 +29,7 @@
 AnalogAccelerometer::AnalogAccelerometer(std::shared_ptr<AnalogInput> channel)
     : m_analogInput(channel) {
   if (!channel) {
-    throw FRC_MakeError(err::NullParameter, "{}", "channel");
+    throw FRC_MakeError(err::NullParameter, "channel");
   }
   InitAccelerometer();
 }
@@ -49,7 +49,7 @@
 void AnalogAccelerometer::InitSendable(wpi::SendableBuilder& builder) {
   builder.SetSmartDashboardType("Accelerometer");
   builder.AddDoubleProperty(
-      "Value", [=] { return GetAcceleration(); }, nullptr);
+      "Value", [=, this] { return GetAcceleration(); }, nullptr);
 }
 
 void AnalogAccelerometer::InitAccelerometer() {
diff --git a/wpilibc/src/main/native/cpp/AnalogEncoder.cpp b/wpilibc/src/main/native/cpp/AnalogEncoder.cpp
index 5f26e87..0c29bea 100644
--- a/wpilibc/src/main/native/cpp/AnalogEncoder.cpp
+++ b/wpilibc/src/main/native/cpp/AnalogEncoder.cpp
@@ -10,6 +10,7 @@
 #include "frc/AnalogInput.h"
 #include "frc/Counter.h"
 #include "frc/Errors.h"
+#include "frc/RobotController.h"
 
 using namespace frc;
 
@@ -42,6 +43,8 @@
 
   if (m_simDevice) {
     m_simPosition = m_simDevice.CreateDouble("Position", false, 0.0);
+    m_simAbsolutePosition =
+        m_simDevice.CreateDouble("absPosition", hal::SimDevice::kInput, 0.0);
   }
 
   m_analogTrigger.SetLimitsVoltage(1.25, 3.75);
@@ -54,6 +57,11 @@
                                m_analogInput->GetChannel());
 }
 
+static bool DoubleEquals(double a, double b) {
+  constexpr double epsilon = 0.00001;
+  return std::abs(a - b) < epsilon;
+}
+
 units::turn_t AnalogEncoder::Get() const {
   if (m_simPosition) {
     return units::turn_t{m_simPosition.Get()};
@@ -66,7 +74,8 @@
     auto pos = m_analogInput->GetVoltage();
     auto counter2 = m_counter.Get();
     auto pos2 = m_analogInput->GetVoltage();
-    if (counter == counter2 && pos == pos2) {
+    if (counter == counter2 && DoubleEquals(pos, pos2)) {
+      pos = pos / frc::RobotController::GetVoltage5V();
       units::turn_t turns{counter + pos - m_positionOffset};
       m_lastPosition = turns;
       return turns;
@@ -74,16 +83,28 @@
   }
 
   FRC_ReportError(
-      warn::Warning, "{}",
+      warn::Warning,
       "Failed to read Analog Encoder. Potential Speed Overrun. Returning last "
       "value");
   return m_lastPosition;
 }
 
+double AnalogEncoder::GetAbsolutePosition() const {
+  if (m_simAbsolutePosition) {
+    return m_simAbsolutePosition.Get();
+  }
+
+  return m_analogInput->GetVoltage() / frc::RobotController::GetVoltage5V();
+}
+
 double AnalogEncoder::GetPositionOffset() const {
   return m_positionOffset;
 }
 
+void AnalogEncoder::SetPositionOffset(double offset) {
+  m_positionOffset = std::clamp(offset, 0.0, 1.0);
+}
+
 void AnalogEncoder::SetDistancePerRotation(double distancePerRotation) {
   m_distancePerRotation = distancePerRotation;
 }
@@ -98,7 +119,8 @@
 
 void AnalogEncoder::Reset() {
   m_counter.Reset();
-  m_positionOffset = m_analogInput->GetVoltage();
+  m_positionOffset =
+      m_analogInput->GetVoltage() / frc::RobotController::GetVoltage5V();
 }
 
 int AnalogEncoder::GetChannel() const {
diff --git a/wpilibc/src/main/native/cpp/AnalogGyro.cpp b/wpilibc/src/main/native/cpp/AnalogGyro.cpp
index 106293b..cab9f94 100644
--- a/wpilibc/src/main/native/cpp/AnalogGyro.cpp
+++ b/wpilibc/src/main/native/cpp/AnalogGyro.cpp
@@ -33,7 +33,7 @@
 AnalogGyro::AnalogGyro(std::shared_ptr<AnalogInput> channel)
     : m_analog(channel) {
   if (!channel) {
-    throw FRC_MakeError(err::NullParameter, "{}", "channel");
+    throw FRC_MakeError(err::NullParameter, "channel");
   }
   InitGyro();
   Calibrate();
@@ -48,7 +48,7 @@
                        double offset)
     : m_analog(channel) {
   if (!channel) {
-    throw FRC_MakeError(err::NullParameter, "{}", "channel");
+    throw FRC_MakeError(err::NullParameter, "channel");
   }
   InitGyro();
   int32_t status = 0;
@@ -140,5 +140,5 @@
 void AnalogGyro::InitSendable(wpi::SendableBuilder& builder) {
   builder.SetSmartDashboardType("Gyro");
   builder.AddDoubleProperty(
-      "Value", [=] { return GetAngle(); }, nullptr);
+      "Value", [=, this] { return GetAngle(); }, nullptr);
 }
diff --git a/wpilibc/src/main/native/cpp/AnalogInput.cpp b/wpilibc/src/main/native/cpp/AnalogInput.cpp
index dfa5764..2ecb2df 100644
--- a/wpilibc/src/main/native/cpp/AnalogInput.cpp
+++ b/wpilibc/src/main/native/cpp/AnalogInput.cpp
@@ -180,13 +180,13 @@
 void AnalogInput::SetSampleRate(double samplesPerSecond) {
   int32_t status = 0;
   HAL_SetAnalogSampleRate(samplesPerSecond, &status);
-  FRC_CheckErrorStatus(status, "{}", "SetSampleRate");
+  FRC_CheckErrorStatus(status, "SetSampleRate");
 }
 
 double AnalogInput::GetSampleRate() {
   int32_t status = 0;
   double sampleRate = HAL_GetAnalogSampleRate(&status);
-  FRC_CheckErrorStatus(status, "{}", "GetSampleRate");
+  FRC_CheckErrorStatus(status, "GetSampleRate");
   return sampleRate;
 }
 
@@ -197,5 +197,5 @@
 void AnalogInput::InitSendable(wpi::SendableBuilder& builder) {
   builder.SetSmartDashboardType("Analog Input");
   builder.AddDoubleProperty(
-      "Value", [=] { return GetAverageVoltage(); }, nullptr);
+      "Value", [=, this] { return GetAverageVoltage(); }, nullptr);
 }
diff --git a/wpilibc/src/main/native/cpp/AnalogOutput.cpp b/wpilibc/src/main/native/cpp/AnalogOutput.cpp
index a391271..90f538b 100644
--- a/wpilibc/src/main/native/cpp/AnalogOutput.cpp
+++ b/wpilibc/src/main/native/cpp/AnalogOutput.cpp
@@ -61,6 +61,6 @@
 void AnalogOutput::InitSendable(wpi::SendableBuilder& builder) {
   builder.SetSmartDashboardType("Analog Output");
   builder.AddDoubleProperty(
-      "Value", [=] { return GetVoltage(); },
-      [=](double value) { SetVoltage(value); });
+      "Value", [=, this] { return GetVoltage(); },
+      [=, this](double value) { SetVoltage(value); });
 }
diff --git a/wpilibc/src/main/native/cpp/AnalogPotentiometer.cpp b/wpilibc/src/main/native/cpp/AnalogPotentiometer.cpp
index ba94613..d43c84f 100644
--- a/wpilibc/src/main/native/cpp/AnalogPotentiometer.cpp
+++ b/wpilibc/src/main/native/cpp/AnalogPotentiometer.cpp
@@ -46,5 +46,5 @@
 void AnalogPotentiometer::InitSendable(wpi::SendableBuilder& builder) {
   builder.SetSmartDashboardType("Analog Input");
   builder.AddDoubleProperty(
-      "Value", [=] { return Get(); }, nullptr);
+      "Value", [=, this] { return Get(); }, nullptr);
 }
diff --git a/wpilibc/src/main/native/cpp/AnalogTriggerOutput.cpp b/wpilibc/src/main/native/cpp/AnalogTriggerOutput.cpp
index 93d1969..ccb44b5 100644
--- a/wpilibc/src/main/native/cpp/AnalogTriggerOutput.cpp
+++ b/wpilibc/src/main/native/cpp/AnalogTriggerOutput.cpp
@@ -18,7 +18,7 @@
   bool result = HAL_GetAnalogTriggerOutput(
       m_trigger->m_trigger, static_cast<HAL_AnalogTriggerType>(m_outputType),
       &status);
-  FRC_CheckErrorStatus(status, "{}", "Get");
+  FRC_CheckErrorStatus(status, "Get");
   return result;
 }
 
diff --git a/wpilibc/src/main/native/cpp/BuiltInAccelerometer.cpp b/wpilibc/src/main/native/cpp/BuiltInAccelerometer.cpp
index 1b8da85..4284e89 100644
--- a/wpilibc/src/main/native/cpp/BuiltInAccelerometer.cpp
+++ b/wpilibc/src/main/native/cpp/BuiltInAccelerometer.cpp
@@ -23,7 +23,7 @@
 
 void BuiltInAccelerometer::SetRange(Range range) {
   if (range == kRange_16G) {
-    throw FRC_MakeError(err::ParameterOutOfRange, "{}",
+    throw FRC_MakeError(err::ParameterOutOfRange,
                         "16G range not supported (use k2G, k4G, or k8G)");
   }
 
@@ -47,9 +47,9 @@
 void BuiltInAccelerometer::InitSendable(wpi::SendableBuilder& builder) {
   builder.SetSmartDashboardType("3AxisAccelerometer");
   builder.AddDoubleProperty(
-      "X", [=] { return GetX(); }, nullptr);
+      "X", [=, this] { return GetX(); }, nullptr);
   builder.AddDoubleProperty(
-      "Y", [=] { return GetY(); }, nullptr);
+      "Y", [=, this] { return GetY(); }, nullptr);
   builder.AddDoubleProperty(
-      "Z", [=] { return GetZ(); }, nullptr);
+      "Z", [=, this] { return GetZ(); }, nullptr);
 }
diff --git a/wpilibc/src/main/native/cpp/CAN.cpp b/wpilibc/src/main/native/cpp/CAN.cpp
index e26ca37..fc4a821 100644
--- a/wpilibc/src/main/native/cpp/CAN.cpp
+++ b/wpilibc/src/main/native/cpp/CAN.cpp
@@ -45,20 +45,20 @@
 void CAN::WritePacket(const uint8_t* data, int length, int apiId) {
   int32_t status = 0;
   HAL_WriteCANPacket(m_handle, data, length, apiId, &status);
-  FRC_CheckErrorStatus(status, "{}", "WritePacket");
+  FRC_CheckErrorStatus(status, "WritePacket");
 }
 
 void CAN::WritePacketRepeating(const uint8_t* data, int length, int apiId,
                                int repeatMs) {
   int32_t status = 0;
   HAL_WriteCANPacketRepeating(m_handle, data, length, apiId, repeatMs, &status);
-  FRC_CheckErrorStatus(status, "{}", "WritePacketRepeating");
+  FRC_CheckErrorStatus(status, "WritePacketRepeating");
 }
 
 void CAN::WriteRTRFrame(int length, int apiId) {
   int32_t status = 0;
   HAL_WriteCANRTRFrame(m_handle, length, apiId, &status);
-  FRC_CheckErrorStatus(status, "{}", "WriteRTRFrame");
+  FRC_CheckErrorStatus(status, "WriteRTRFrame");
 }
 
 int CAN::WritePacketNoError(const uint8_t* data, int length, int apiId) {
@@ -83,7 +83,7 @@
 void CAN::StopPacketRepeating(int apiId) {
   int32_t status = 0;
   HAL_StopCANPacketRepeating(m_handle, apiId, &status);
-  FRC_CheckErrorStatus(status, "{}", "StopPacketRepeating");
+  FRC_CheckErrorStatus(status, "StopPacketRepeating");
 }
 
 bool CAN::ReadPacketNew(int apiId, CANData* data) {
@@ -94,7 +94,7 @@
     return false;
   }
   if (status != 0) {
-    FRC_CheckErrorStatus(status, "{}", "ReadPacketNew");
+    FRC_CheckErrorStatus(status, "ReadPacketNew");
     return false;
   } else {
     return true;
@@ -109,7 +109,7 @@
     return false;
   }
   if (status != 0) {
-    FRC_CheckErrorStatus(status, "{}", "ReadPacketLatest");
+    FRC_CheckErrorStatus(status, "ReadPacketLatest");
     return false;
   } else {
     return true;
@@ -125,7 +125,7 @@
     return false;
   }
   if (status != 0) {
-    FRC_CheckErrorStatus(status, "{}", "ReadPacketTimeout");
+    FRC_CheckErrorStatus(status, "ReadPacketTimeout");
     return false;
   } else {
     return true;
diff --git a/wpilibc/src/main/native/cpp/Compressor.cpp b/wpilibc/src/main/native/cpp/Compressor.cpp
index d994b14..2479ffc 100644
--- a/wpilibc/src/main/native/cpp/Compressor.cpp
+++ b/wpilibc/src/main/native/cpp/Compressor.cpp
@@ -29,18 +29,16 @@
     : Compressor{PneumaticsBase::GetDefaultForType(moduleType), moduleType} {}
 
 Compressor::~Compressor() {
-  m_module->UnreserveCompressor();
-}
-
-void Compressor::Start() {
-  EnableDigital();
-}
-
-void Compressor::Stop() {
-  Disable();
+  if (m_module) {
+    m_module->UnreserveCompressor();
+  }
 }
 
 bool Compressor::Enabled() const {
+  return IsEnabled();
+}
+
+bool Compressor::IsEnabled() const {
   return m_module->GetCompressor();
 }
 
@@ -85,7 +83,7 @@
 void Compressor::InitSendable(wpi::SendableBuilder& builder) {
   builder.SetSmartDashboardType("Compressor");
   builder.AddBooleanProperty(
-      "Enabled", [=] { return Enabled(); }, nullptr);
+      "Enabled", [this] { return IsEnabled(); }, nullptr);
   builder.AddBooleanProperty(
-      "Pressure switch", [=]() { return GetPressureSwitchValue(); }, nullptr);
+      "Pressure switch", [this] { return GetPressureSwitchValue(); }, nullptr);
 }
diff --git a/wpilibc/src/main/native/cpp/Counter.cpp b/wpilibc/src/main/native/cpp/Counter.cpp
index a1ad9e7..2e9c916 100644
--- a/wpilibc/src/main/native/cpp/Counter.cpp
+++ b/wpilibc/src/main/native/cpp/Counter.cpp
@@ -22,7 +22,7 @@
   int32_t status = 0;
   m_counter = HAL_InitializeCounter(static_cast<HAL_Counter_Mode>(mode),
                                     &m_index, &status);
-  FRC_CheckErrorStatus(status, "{}", "InitializeCounter");
+  FRC_CheckErrorStatus(status, "InitializeCounter");
 
   SetMaxPeriod(0.5_s);
 
@@ -64,7 +64,7 @@
                  std::shared_ptr<DigitalSource> downSource, bool inverted)
     : Counter(kExternalDirection) {
   if (encodingType != k1X && encodingType != k2X) {
-    throw FRC_MakeError(err::ParameterOutOfRange, "{}",
+    throw FRC_MakeError(err::ParameterOutOfRange,
                         "Counter only supports 1X and 2X quadrature decoding");
   }
   SetUpSource(upSource);
@@ -79,7 +79,7 @@
     HAL_SetCounterAverageSize(m_counter, 2, &status);
   }
 
-  FRC_CheckErrorStatus(status, "{}", "Counter constructor");
+  FRC_CheckErrorStatus(status, "Counter constructor");
   SetDownSourceEdge(inverted, true);
 }
 
@@ -92,7 +92,7 @@
 
   int32_t status = 0;
   HAL_FreeCounter(m_counter, &status);
-  FRC_ReportError(status, "{}", "Counter destructor");
+  FRC_ReportError(status, "Counter destructor");
 }
 
 void Counter::SetUpSource(int channel) {
@@ -124,7 +124,7 @@
                          static_cast<HAL_AnalogTriggerType>(
                              source->GetAnalogTriggerTypeForRouting()),
                          &status);
-  FRC_CheckErrorStatus(status, "{}", "SetUpSource");
+  FRC_CheckErrorStatus(status, "SetUpSource");
 }
 
 void Counter::SetUpSource(DigitalSource& source) {
@@ -135,19 +135,19 @@
 void Counter::SetUpSourceEdge(bool risingEdge, bool fallingEdge) {
   if (m_upSource == nullptr) {
     throw FRC_MakeError(
-        err::NullParameter, "{}",
+        err::NullParameter,
         "Must set non-nullptr UpSource before setting UpSourceEdge");
   }
   int32_t status = 0;
   HAL_SetCounterUpSourceEdge(m_counter, risingEdge, fallingEdge, &status);
-  FRC_CheckErrorStatus(status, "{}", "SetUpSourceEdge");
+  FRC_CheckErrorStatus(status, "SetUpSourceEdge");
 }
 
 void Counter::ClearUpSource() {
   m_upSource.reset();
   int32_t status = 0;
   HAL_ClearCounterUpSource(m_counter, &status);
-  FRC_CheckErrorStatus(status, "{}", "ClearUpSource");
+  FRC_CheckErrorStatus(status, "ClearUpSource");
 }
 
 void Counter::SetDownSource(int channel) {
@@ -184,37 +184,37 @@
                            static_cast<HAL_AnalogTriggerType>(
                                source->GetAnalogTriggerTypeForRouting()),
                            &status);
-  FRC_CheckErrorStatus(status, "{}", "SetDownSource");
+  FRC_CheckErrorStatus(status, "SetDownSource");
 }
 
 void Counter::SetDownSourceEdge(bool risingEdge, bool fallingEdge) {
   if (m_downSource == nullptr) {
     throw FRC_MakeError(
-        err::NullParameter, "{}",
+        err::NullParameter,
         "Must set non-nullptr DownSource before setting DownSourceEdge");
   }
   int32_t status = 0;
   HAL_SetCounterDownSourceEdge(m_counter, risingEdge, fallingEdge, &status);
-  FRC_CheckErrorStatus(status, "{}", "SetDownSourceEdge");
+  FRC_CheckErrorStatus(status, "SetDownSourceEdge");
 }
 
 void Counter::ClearDownSource() {
   m_downSource.reset();
   int32_t status = 0;
   HAL_ClearCounterDownSource(m_counter, &status);
-  FRC_CheckErrorStatus(status, "{}", "ClearDownSource");
+  FRC_CheckErrorStatus(status, "ClearDownSource");
 }
 
 void Counter::SetUpDownCounterMode() {
   int32_t status = 0;
   HAL_SetCounterUpDownMode(m_counter, &status);
-  FRC_CheckErrorStatus(status, "{}", "SetUpDownCounterMode");
+  FRC_CheckErrorStatus(status, "SetUpDownCounterMode");
 }
 
 void Counter::SetExternalDirectionMode() {
   int32_t status = 0;
   HAL_SetCounterExternalDirectionMode(m_counter, &status);
-  FRC_CheckErrorStatus(status, "{}", "SetExternalDirectionMode");
+  FRC_CheckErrorStatus(status, "SetExternalDirectionMode");
 }
 
 void Counter::SetSemiPeriodMode(bool highSemiPeriod) {
@@ -227,7 +227,7 @@
 void Counter::SetPulseLengthMode(double threshold) {
   int32_t status = 0;
   HAL_SetCounterPulseLengthMode(m_counter, threshold, &status);
-  FRC_CheckErrorStatus(status, "{}", "SetPulseLengthMode");
+  FRC_CheckErrorStatus(status, "SetPulseLengthMode");
 }
 
 void Counter::SetReverseDirection(bool reverseDirection) {
@@ -252,7 +252,7 @@
 int Counter::GetSamplesToAverage() const {
   int32_t status = 0;
   int samples = HAL_GetCounterSamplesToAverage(m_counter, &status);
-  FRC_CheckErrorStatus(status, "{}", "GetSamplesToAverage");
+  FRC_CheckErrorStatus(status, "GetSamplesToAverage");
   return samples;
 }
 
@@ -263,51 +263,51 @@
 int Counter::Get() const {
   int32_t status = 0;
   int value = HAL_GetCounter(m_counter, &status);
-  FRC_CheckErrorStatus(status, "{}", "Get");
+  FRC_CheckErrorStatus(status, "Get");
   return value;
 }
 
 void Counter::Reset() {
   int32_t status = 0;
   HAL_ResetCounter(m_counter, &status);
-  FRC_CheckErrorStatus(status, "{}", "Reset");
+  FRC_CheckErrorStatus(status, "Reset");
 }
 
 units::second_t Counter::GetPeriod() const {
   int32_t status = 0;
   double value = HAL_GetCounterPeriod(m_counter, &status);
-  FRC_CheckErrorStatus(status, "{}", "GetPeriod");
+  FRC_CheckErrorStatus(status, "GetPeriod");
   return units::second_t{value};
 }
 
 void Counter::SetMaxPeriod(units::second_t maxPeriod) {
   int32_t status = 0;
   HAL_SetCounterMaxPeriod(m_counter, maxPeriod.value(), &status);
-  FRC_CheckErrorStatus(status, "{}", "SetMaxPeriod");
+  FRC_CheckErrorStatus(status, "SetMaxPeriod");
 }
 
 void Counter::SetUpdateWhenEmpty(bool enabled) {
   int32_t status = 0;
   HAL_SetCounterUpdateWhenEmpty(m_counter, enabled, &status);
-  FRC_CheckErrorStatus(status, "{}", "SetUpdateWhenEmpty");
+  FRC_CheckErrorStatus(status, "SetUpdateWhenEmpty");
 }
 
 bool Counter::GetStopped() const {
   int32_t status = 0;
   bool value = HAL_GetCounterStopped(m_counter, &status);
-  FRC_CheckErrorStatus(status, "{}", "GetStopped");
+  FRC_CheckErrorStatus(status, "GetStopped");
   return value;
 }
 
 bool Counter::GetDirection() const {
   int32_t status = 0;
   bool value = HAL_GetCounterDirection(m_counter, &status);
-  FRC_CheckErrorStatus(status, "{}", "GetDirection");
+  FRC_CheckErrorStatus(status, "GetDirection");
   return value;
 }
 
 void Counter::InitSendable(wpi::SendableBuilder& builder) {
   builder.SetSmartDashboardType("Counter");
   builder.AddDoubleProperty(
-      "Value", [=] { return Get(); }, nullptr);
+      "Value", [=, this] { return Get(); }, nullptr);
 }
diff --git a/wpilibc/src/main/native/cpp/DMA.cpp b/wpilibc/src/main/native/cpp/DMA.cpp
index 80c0adb..ce6c532 100644
--- a/wpilibc/src/main/native/cpp/DMA.cpp
+++ b/wpilibc/src/main/native/cpp/DMA.cpp
@@ -27,7 +27,7 @@
 DMA::DMA() {
   int32_t status = 0;
   dmaHandle = HAL_InitializeDMA(&status);
-  FRC_CheckErrorStatus(status, "{}", "InitializeDMA");
+  FRC_CheckErrorStatus(status, "InitializeDMA");
 }
 
 DMA::~DMA() {
@@ -37,74 +37,74 @@
 void DMA::SetPause(bool pause) {
   int32_t status = 0;
   HAL_SetDMAPause(dmaHandle, pause, &status);
-  FRC_CheckErrorStatus(status, "{}", "SetPause");
+  FRC_CheckErrorStatus(status, "SetPause");
 }
 
 void DMA::SetTimedTrigger(units::second_t seconds) {
   int32_t status = 0;
   HAL_SetDMATimedTrigger(dmaHandle, seconds.value(), &status);
-  FRC_CheckErrorStatus(status, "{}", "SetTimedTrigger");
+  FRC_CheckErrorStatus(status, "SetTimedTrigger");
 }
 
 void DMA::SetTimedTriggerCycles(int cycles) {
   int32_t status = 0;
   HAL_SetDMATimedTriggerCycles(dmaHandle, cycles, &status);
-  FRC_CheckErrorStatus(status, "{}", "SetTimedTriggerCycles");
+  FRC_CheckErrorStatus(status, "SetTimedTriggerCycles");
 }
 
 void DMA::AddEncoder(const Encoder* encoder) {
   int32_t status = 0;
   HAL_AddDMAEncoder(dmaHandle, encoder->m_encoder, &status);
-  FRC_CheckErrorStatus(status, "{}", "AddEncoder");
+  FRC_CheckErrorStatus(status, "AddEncoder");
 }
 
 void DMA::AddEncoderPeriod(const Encoder* encoder) {
   int32_t status = 0;
   HAL_AddDMAEncoderPeriod(dmaHandle, encoder->m_encoder, &status);
-  FRC_CheckErrorStatus(status, "{}", "AddEncoderPeriod");
+  FRC_CheckErrorStatus(status, "AddEncoderPeriod");
 }
 
 void DMA::AddCounter(const Counter* counter) {
   int32_t status = 0;
   HAL_AddDMACounter(dmaHandle, counter->m_counter, &status);
-  FRC_CheckErrorStatus(status, "{}", "AddCounter");
+  FRC_CheckErrorStatus(status, "AddCounter");
 }
 
 void DMA::AddCounterPeriod(const Counter* counter) {
   int32_t status = 0;
   HAL_AddDMACounterPeriod(dmaHandle, counter->m_counter, &status);
-  FRC_CheckErrorStatus(status, "{}", "AddCounterPeriod");
+  FRC_CheckErrorStatus(status, "AddCounterPeriod");
 }
 
 void DMA::AddDigitalSource(const DigitalSource* digitalSource) {
   int32_t status = 0;
   HAL_AddDMADigitalSource(dmaHandle, digitalSource->GetPortHandleForRouting(),
                           &status);
-  FRC_CheckErrorStatus(status, "{}", "AddDigitalSource");
+  FRC_CheckErrorStatus(status, "AddDigitalSource");
 }
 
 void DMA::AddDutyCycle(const DutyCycle* dutyCycle) {
   int32_t status = 0;
   HAL_AddDMADutyCycle(dmaHandle, dutyCycle->m_handle, &status);
-  FRC_CheckErrorStatus(status, "{}", "AddDutyCycle");
+  FRC_CheckErrorStatus(status, "AddDutyCycle");
 }
 
 void DMA::AddAnalogInput(const AnalogInput* analogInput) {
   int32_t status = 0;
   HAL_AddDMAAnalogInput(dmaHandle, analogInput->m_port, &status);
-  FRC_CheckErrorStatus(status, "{}", "AddAnalogInput");
+  FRC_CheckErrorStatus(status, "AddAnalogInput");
 }
 
 void DMA::AddAveragedAnalogInput(const AnalogInput* analogInput) {
   int32_t status = 0;
   HAL_AddDMAAveragedAnalogInput(dmaHandle, analogInput->m_port, &status);
-  FRC_CheckErrorStatus(status, "{}", "AddAveragedAnalogInput");
+  FRC_CheckErrorStatus(status, "AddAveragedAnalogInput");
 }
 
 void DMA::AddAnalogAccumulator(const AnalogInput* analogInput) {
   int32_t status = 0;
   HAL_AddDMAAnalogAccumulator(dmaHandle, analogInput->m_port, &status);
-  FRC_CheckErrorStatus(status, "{}", "AddAnalogAccumulator");
+  FRC_CheckErrorStatus(status, "AddAnalogAccumulator");
 }
 
 int DMA::SetExternalTrigger(DigitalSource* source, bool rising, bool falling) {
@@ -114,7 +114,7 @@
                                 static_cast<HAL_AnalogTriggerType>(
                                     source->GetAnalogTriggerTypeForRouting()),
                                 rising, falling, &status);
-  FRC_CheckErrorStatus(status, "{}", "SetExternalTrigger");
+  FRC_CheckErrorStatus(status, "SetExternalTrigger");
   return idx;
 }
 
@@ -124,7 +124,7 @@
   int32_t idx = HAL_SetDMAExternalTrigger(
       dmaHandle, source->GetPwm()->m_handle,
       HAL_AnalogTriggerType::HAL_Trigger_kInWindow, rising, falling, &status);
-  FRC_CheckErrorStatus(status, "{}", "SetPWmEdgeTrigger");
+  FRC_CheckErrorStatus(status, "SetPWmEdgeTrigger");
   return idx;
 }
 
@@ -133,30 +133,30 @@
   int32_t idx = HAL_SetDMAExternalTrigger(
       dmaHandle, source->m_handle, HAL_AnalogTriggerType::HAL_Trigger_kInWindow,
       rising, falling, &status);
-  FRC_CheckErrorStatus(status, "{}", "SetPWmEdgeTrigger");
+  FRC_CheckErrorStatus(status, "SetPWmEdgeTrigger");
   return idx;
 }
 
 void DMA::ClearSensors() {
   int32_t status = 0;
   HAL_ClearDMASensors(dmaHandle, &status);
-  FRC_CheckErrorStatus(status, "{}", "ClearSensors");
+  FRC_CheckErrorStatus(status, "ClearSensors");
 }
 
 void DMA::ClearExternalTriggers() {
   int32_t status = 0;
   HAL_ClearDMAExternalTriggers(dmaHandle, &status);
-  FRC_CheckErrorStatus(status, "{}", "ClearExternalTriggers");
+  FRC_CheckErrorStatus(status, "ClearExternalTriggers");
 }
 
 void DMA::Start(int queueDepth) {
   int32_t status = 0;
   HAL_StartDMA(dmaHandle, queueDepth, &status);
-  FRC_CheckErrorStatus(status, "{}", "Start");
+  FRC_CheckErrorStatus(status, "Start");
 }
 
 void DMA::Stop() {
   int32_t status = 0;
   HAL_StopDMA(dmaHandle, &status);
-  FRC_CheckErrorStatus(status, "{}", "Stop");
+  FRC_CheckErrorStatus(status, "Stop");
 }
diff --git a/wpilibc/src/main/native/cpp/DataLogManager.cpp b/wpilibc/src/main/native/cpp/DataLogManager.cpp
new file mode 100644
index 0000000..b92faa3
--- /dev/null
+++ b/wpilibc/src/main/native/cpp/DataLogManager.cpp
@@ -0,0 +1,317 @@
+// 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 "frc/DataLogManager.h"
+
+#include <algorithm>
+#include <ctime>
+#include <random>
+
+#include <fmt/chrono.h>
+#include <fmt/format.h>
+#include <networktables/NetworkTableInstance.h>
+#include <wpi/DataLog.h>
+#include <wpi/SafeThread.h>
+#include <wpi/StringExtras.h>
+#include <wpi/fs.h>
+#include <wpi/timestamp.h>
+
+#include "frc/DriverStation.h"
+#include "frc/Filesystem.h"
+
+using namespace frc;
+
+namespace {
+
+struct Thread final : public wpi::SafeThread {
+  Thread(std::string_view dir, std::string_view filename, double period);
+
+  void Main() final;
+
+  void StartNTLog();
+  void StopNTLog();
+
+  std::string m_logDir;
+  bool m_filenameOverride;
+  wpi::log::DataLog m_log;
+  bool m_ntLoggerEnabled = false;
+  NT_DataLogger m_ntEntryLogger = 0;
+  NT_ConnectionDataLogger m_ntConnLogger = 0;
+  wpi::log::StringLogEntry m_messageLog;
+};
+
+struct Instance {
+  Instance(std::string_view dir, std::string_view filename, double period);
+  wpi::SafeThreadOwner<Thread> owner;
+};
+
+}  // namespace
+
+// if less than this much free space, delete log files until there is this much
+// free space OR there are this many files remaining.
+static constexpr uintmax_t kFreeSpaceThreshold = 50000000;
+static constexpr int kFileCountThreshold = 10;
+
+static std::string MakeLogDir(std::string_view dir) {
+  if (!dir.empty()) {
+    return std::string{dir};
+  }
+#ifdef __FRC_ROBORIO__
+  // prefer a mounted USB drive if one is accessible
+  constexpr std::string_view usbDir{"/u"};
+  std::error_code ec;
+  auto s = fs::status(usbDir, ec);
+  if (!ec && fs::is_directory(s) &&
+      (s.permissions() & fs::perms::others_write) != fs::perms::none) {
+    return std::string{usbDir};
+  }
+#endif
+  return frc::filesystem::GetOperatingDirectory();
+}
+
+static std::string MakeLogFilename(std::string_view filenameOverride) {
+  if (!filenameOverride.empty()) {
+    return std::string{filenameOverride};
+  }
+  static std::random_device dev;
+  static std::mt19937 rng(dev());
+  std::uniform_int_distribution<int> dist(0, 15);
+  const char* v = "0123456789abcdef";
+  std::string filename = "FRC_TBD_";
+  for (int i = 0; i < 16; i++) {
+    filename += v[dist(rng)];
+  }
+  filename += ".wpilog";
+  return filename;
+}
+
+Thread::Thread(std::string_view dir, std::string_view filename, double period)
+    : m_logDir{dir},
+      m_filenameOverride{!filename.empty()},
+      m_log{dir, MakeLogFilename(filename), period},
+      m_messageLog{m_log, "messages"} {
+  StartNTLog();
+}
+
+void Thread::Main() {
+  // based on free disk space, scan for "old" FRC_*.wpilog files and remove
+  {
+    uintmax_t freeSpace = fs::space(m_logDir).free;
+    if (freeSpace < kFreeSpaceThreshold) {
+      // Delete oldest FRC_*.wpilog files (ignore FRC_TBD_*.wpilog as we just
+      // created one)
+      std::vector<fs::directory_entry> entries;
+      std::error_code ec;
+      for (auto&& entry : fs::directory_iterator{m_logDir, ec}) {
+        auto stem = entry.path().stem().string();
+        if (wpi::starts_with(stem, "FRC_") &&
+            entry.path().extension() == ".wpilog" &&
+            !wpi::starts_with(stem, "FRC_TBD_")) {
+          entries.emplace_back(entry);
+        }
+      }
+      std::sort(entries.begin(), entries.end(),
+                [](const auto& a, const auto& b) {
+                  return a.last_write_time() < b.last_write_time();
+                });
+
+      int count = entries.size();
+      for (auto&& entry : entries) {
+        --count;
+        if (count < kFileCountThreshold) {
+          break;
+        }
+        auto size = entry.file_size();
+        if (fs::remove(entry.path(), ec)) {
+          freeSpace += size;
+          if (freeSpace >= kFreeSpaceThreshold) {
+            break;
+          }
+        } else {
+          fmt::print(stderr, "DataLogManager: could not delete {}\n",
+                     entry.path().string());
+        }
+      }
+    }
+  }
+
+  int timeoutCount = 0;
+  bool paused = false;
+  int dsAttachCount = 0;
+  int fmsAttachCount = 0;
+  bool dsRenamed = m_filenameOverride;
+  bool fmsRenamed = m_filenameOverride;
+  int sysTimeCount = 0;
+  wpi::log::IntegerLogEntry sysTimeEntry{
+      m_log, "systemTime",
+      "{\"source\":\"DataLogManager\",\"format\":\"time_t_us\"}"};
+
+  wpi::Event newDataEvent;
+  DriverStation::ProvideRefreshedDataEventHandle(newDataEvent.GetHandle());
+
+  for (;;) {
+    bool timedOut = false;
+    bool newData =
+        wpi::WaitForObject(newDataEvent.GetHandle(), 0.25, &timedOut);
+    if (!m_active) {
+      break;
+    }
+    if (!newData) {
+      ++timeoutCount;
+      // pause logging after being disconnected for 10 seconds
+      if (timeoutCount > 40 && !paused) {
+        timeoutCount = 0;
+        paused = true;
+        m_log.Pause();
+      }
+      continue;
+    }
+    // when we connect to the DS, resume logging
+    timeoutCount = 0;
+    if (paused) {
+      paused = false;
+      m_log.Resume();
+    }
+
+    if (!dsRenamed) {
+      // track DS attach
+      if (DriverStation::IsDSAttached()) {
+        ++dsAttachCount;
+      } else {
+        dsAttachCount = 0;
+      }
+      if (dsAttachCount > 50) {  // 1 second
+        std::time_t now = std::time(nullptr);
+        auto tm = std::gmtime(&now);
+        if (tm->tm_year > 100) {
+          // assume local clock is now synchronized to DS, so rename based on
+          // local time
+          m_log.SetFilename(fmt::format("FRC_{:%Y%m%d_%H%M%S}.wpilog", *tm));
+          dsRenamed = true;
+        } else {
+          dsAttachCount = 0;  // wait a bit and try again
+        }
+      }
+    }
+
+    if (!fmsRenamed) {
+      // track FMS attach
+      if (DriverStation::IsFMSAttached()) {
+        ++fmsAttachCount;
+      } else {
+        fmsAttachCount = 0;
+      }
+      if (fmsAttachCount > 100) {  // 2 seconds
+        // match info comes through TCP, so we need to double-check we've
+        // actually received it
+        auto matchType = DriverStation::GetMatchType();
+        if (matchType != DriverStation::kNone) {
+          // rename per match info
+          char matchTypeChar;
+          switch (matchType) {
+            case DriverStation::kPractice:
+              matchTypeChar = 'P';
+              break;
+            case DriverStation::kQualification:
+              matchTypeChar = 'Q';
+              break;
+            case DriverStation::kElimination:
+              matchTypeChar = 'E';
+              break;
+            default:
+              matchTypeChar = '_';
+              break;
+          }
+          std::time_t now = std::time(nullptr);
+          m_log.SetFilename(
+              fmt::format("FRC_{:%Y%m%d_%H%M%S}_{}_{}{}.wpilog",
+                          *std::gmtime(&now), DriverStation::GetEventName(),
+                          matchTypeChar, DriverStation::GetMatchNumber()));
+          fmsRenamed = true;
+          dsRenamed = true;  // don't override FMS rename
+        }
+      }
+    }
+
+    // Write system time every ~5 seconds
+    ++sysTimeCount;
+    if (sysTimeCount >= 250) {
+      sysTimeCount = 0;
+      sysTimeEntry.Append(wpi::GetSystemTime(), wpi::Now());
+    }
+  }
+  DriverStation::RemoveRefreshedDataEventHandle(newDataEvent.GetHandle());
+}
+
+void Thread::StartNTLog() {
+  if (!m_ntLoggerEnabled) {
+    m_ntLoggerEnabled = true;
+    auto inst = nt::NetworkTableInstance::GetDefault();
+    m_ntEntryLogger = inst.StartEntryDataLog(m_log, "", "NT:");
+    m_ntConnLogger = inst.StartConnectionDataLog(m_log, "NTConnection");
+  }
+}
+
+void Thread::StopNTLog() {
+  if (m_ntLoggerEnabled) {
+    m_ntLoggerEnabled = false;
+    nt::NetworkTableInstance::StopEntryDataLog(m_ntEntryLogger);
+    nt::NetworkTableInstance::StopConnectionDataLog(m_ntConnLogger);
+  }
+}
+
+Instance::Instance(std::string_view dir, std::string_view filename,
+                   double period) {
+  // Delete all previously existing FRC_TBD_*.wpilog files. These only exist
+  // when the robot never connects to the DS, so they are very unlikely to
+  // have useful data and just clutter the filesystem.
+  auto logDir = MakeLogDir(dir);
+  std::error_code ec;
+  for (auto&& entry : fs::directory_iterator{logDir, ec}) {
+    if (wpi::starts_with(entry.path().stem().string(), "FRC_TBD_") &&
+        entry.path().extension() == ".wpilog") {
+      if (!fs::remove(entry, ec)) {
+        fmt::print(stderr, "DataLogManager: could not delete {}\n",
+                   entry.path().string());
+      }
+    }
+  }
+
+  owner.Start(logDir, filename, period);
+}
+
+static Instance& GetInstance(std::string_view dir = "",
+                             std::string_view filename = "",
+                             double period = 0.25) {
+  static Instance instance(dir, filename, period);
+  return instance;
+}
+
+void DataLogManager::Start(std::string_view dir, std::string_view filename,
+                           double period) {
+  GetInstance(dir, filename, period);
+}
+
+void DataLogManager::Log(std::string_view message) {
+  GetInstance().owner.GetThread()->m_messageLog.Append(message);
+  fmt::print("{}\n", message);
+}
+
+wpi::log::DataLog& DataLogManager::GetLog() {
+  return GetInstance().owner.GetThread()->m_log;
+}
+
+std::string DataLogManager::GetLogDir() {
+  return GetInstance().owner.GetThread()->m_logDir;
+}
+
+void DataLogManager::LogNetworkTables(bool enabled) {
+  if (auto thr = GetInstance().owner.GetThread()) {
+    if (enabled) {
+      thr->StartNTLog();
+    } else if (!enabled) {
+      thr->StopNTLog();
+    }
+  }
+}
diff --git a/wpilibc/src/main/native/cpp/DigitalGlitchFilter.cpp b/wpilibc/src/main/native/cpp/DigitalGlitchFilter.cpp
index 3294a99..4abb7cf 100644
--- a/wpilibc/src/main/native/cpp/DigitalGlitchFilter.cpp
+++ b/wpilibc/src/main/native/cpp/DigitalGlitchFilter.cpp
@@ -56,7 +56,7 @@
     // We don't support GlitchFilters on AnalogTriggers.
     if (input->IsAnalogTrigger()) {
       throw FRC_MakeError(
-          -1, "{}", "Analog Triggers not supported for DigitalGlitchFilters");
+          -1, "Analog Triggers not supported for DigitalGlitchFilters");
     }
     int32_t status = 0;
     HAL_SetFilterSelect(input->GetPortHandleForRouting(), requestedIndex,
diff --git a/wpilibc/src/main/native/cpp/DigitalInput.cpp b/wpilibc/src/main/native/cpp/DigitalInput.cpp
index 86f1c3a..0275741 100644
--- a/wpilibc/src/main/native/cpp/DigitalInput.cpp
+++ b/wpilibc/src/main/native/cpp/DigitalInput.cpp
@@ -69,5 +69,5 @@
 void DigitalInput::InitSendable(wpi::SendableBuilder& builder) {
   builder.SetSmartDashboardType("Digital Input");
   builder.AddBooleanProperty(
-      "Value", [=] { return Get(); }, nullptr);
+      "Value", [=, this] { return Get(); }, nullptr);
 }
diff --git a/wpilibc/src/main/native/cpp/DigitalOutput.cpp b/wpilibc/src/main/native/cpp/DigitalOutput.cpp
index 5810e30..fc69e35 100644
--- a/wpilibc/src/main/native/cpp/DigitalOutput.cpp
+++ b/wpilibc/src/main/native/cpp/DigitalOutput.cpp
@@ -76,9 +76,9 @@
   return m_channel;
 }
 
-void DigitalOutput::Pulse(double length) {
+void DigitalOutput::Pulse(units::second_t pulseLength) {
   int32_t status = 0;
-  HAL_Pulse(m_handle, length, &status);
+  HAL_Pulse(m_handle, pulseLength.to<double>(), &status);
   FRC_CheckErrorStatus(status, "Channel {}", m_channel);
 }
 
@@ -95,6 +95,23 @@
   FRC_CheckErrorStatus(status, "Channel {}", m_channel);
 }
 
+void DigitalOutput::EnablePPS(double dutyCycle) {
+  if (m_pwmGenerator != HAL_kInvalidHandle) {
+    return;
+  }
+
+  int32_t status = 0;
+
+  m_pwmGenerator = HAL_AllocateDigitalPWM(&status);
+  FRC_CheckErrorStatus(status, "Channel {}", m_channel);
+
+  HAL_SetDigitalPWMPPS(m_pwmGenerator, dutyCycle, &status);
+  FRC_CheckErrorStatus(status, "Channel {}", m_channel);
+
+  HAL_SetDigitalPWMOutputChannel(m_pwmGenerator, m_channel, &status);
+  FRC_CheckErrorStatus(status, "Channel {}", m_channel);
+}
+
 void DigitalOutput::EnablePWM(double initialDutyCycle) {
   if (m_pwmGenerator != HAL_kInvalidHandle) {
     return;
@@ -143,5 +160,6 @@
 void DigitalOutput::InitSendable(wpi::SendableBuilder& builder) {
   builder.SetSmartDashboardType("Digital Output");
   builder.AddBooleanProperty(
-      "Value", [=] { return Get(); }, [=](bool value) { Set(value); });
+      "Value", [=, this] { return Get(); },
+      [=, this](bool value) { Set(value); });
 }
diff --git a/wpilibc/src/main/native/cpp/DoubleSolenoid.cpp b/wpilibc/src/main/native/cpp/DoubleSolenoid.cpp
index 212673a..c111622 100644
--- a/wpilibc/src/main/native/cpp/DoubleSolenoid.cpp
+++ b/wpilibc/src/main/native/cpp/DoubleSolenoid.cpp
@@ -65,7 +65,9 @@
                      forwardChannel, reverseChannel} {}
 
 DoubleSolenoid::~DoubleSolenoid() {
-  m_module->UnreserveSolenoids(m_mask);
+  if (m_module) {
+    m_module->UnreserveSolenoids(m_mask);
+  }
 }
 
 void DoubleSolenoid::Set(Value value) {
@@ -127,10 +129,10 @@
 void DoubleSolenoid::InitSendable(wpi::SendableBuilder& builder) {
   builder.SetSmartDashboardType("Double Solenoid");
   builder.SetActuator(true);
-  builder.SetSafeState([=] { Set(kOff); });
+  builder.SetSafeState([=, this] { Set(kOff); });
   builder.AddSmallStringProperty(
       "Value",
-      [=](wpi::SmallVectorImpl<char>& buf) -> std::string_view {
+      [=, this](wpi::SmallVectorImpl<char>& buf) -> std::string_view {
         switch (Get()) {
           case kForward:
             return "Forward";
@@ -140,7 +142,7 @@
             return "Off";
         }
       },
-      [=](std::string_view value) {
+      [=, this](std::string_view value) {
         Value lvalue = kOff;
         if (value == "Forward") {
           lvalue = kForward;
diff --git a/wpilibc/src/main/native/cpp/DriverStation.cpp b/wpilibc/src/main/native/cpp/DriverStation.cpp
index 37fc65f..aead343 100644
--- a/wpilibc/src/main/native/cpp/DriverStation.cpp
+++ b/wpilibc/src/main/native/cpp/DriverStation.cpp
@@ -9,6 +9,7 @@
 #include <array>
 #include <atomic>
 #include <chrono>
+#include <span>
 #include <string>
 #include <string_view>
 #include <thread>
@@ -19,11 +20,16 @@
 #include <hal/DriverStationTypes.h>
 #include <hal/HALBase.h>
 #include <hal/Power.h>
+#include <networktables/BooleanTopic.h>
+#include <networktables/IntegerTopic.h>
 #include <networktables/NetworkTable.h>
-#include <networktables/NetworkTableEntry.h>
 #include <networktables/NetworkTableInstance.h>
+#include <networktables/StringTopic.h>
+#include <wpi/DataLog.h>
+#include <wpi/EventVector.h>
 #include <wpi/condition_variable.h>
 #include <wpi/mutex.h>
+#include <wpi/timestamp.h>
 
 #include "frc/Errors.h"
 #include "frc/MotorSafety.h"
@@ -34,63 +40,87 @@
 namespace {
 // A simple class which caches the previous value written to an NT entry
 // Used to prevent redundant, repeated writes of the same value
-template <class T>
+template <typename Topic>
 class MatchDataSenderEntry {
  public:
   MatchDataSenderEntry(const std::shared_ptr<nt::NetworkTable>& table,
-                       std::string_view key, const T& initialVal) {
-    static_assert(std::is_same_v<T, bool> || std::is_same_v<T, double> ||
-                      std::is_same_v<T, std::string>,
-                  "Invalid type for MatchDataSenderEntry - must be "
-                  "to bool, double or std::string");
-
-    ntEntry = table->GetEntry(key);
-    if constexpr (std::is_same_v<T, bool>) {
-      ntEntry.ForceSetBoolean(initialVal);
-    } else if constexpr (std::is_same_v<T, double>) {
-      ntEntry.ForceSetDouble(initialVal);
-    } else if constexpr (std::is_same_v<T, std::string>) {
-      ntEntry.ForceSetString(initialVal);
-    }
-    prevVal = initialVal;
+                       std::string_view key,
+                       typename Topic::ParamType initialVal)
+      : publisher{Topic{table->GetTopic(key)}.Publish()}, prevVal{initialVal} {
+    publisher.Set(initialVal);
   }
 
-  void Set(const T& val) {
+  void Set(typename Topic::ParamType val) {
     if (val != prevVal) {
-      SetValue(val);
+      publisher.Set(val);
       prevVal = val;
     }
   }
 
  private:
-  nt::NetworkTableEntry ntEntry;
-  T prevVal;
-
-  void SetValue(bool val) { ntEntry.SetBoolean(val); }
-  void SetValue(double val) { ntEntry.SetDouble(val); }
-  void SetValue(std::string_view val) { ntEntry.SetString(val); }
+  typename Topic::PublisherType publisher;
+  typename Topic::ValueType prevVal;
 };
 
 struct MatchDataSender {
   std::shared_ptr<nt::NetworkTable> table =
       nt::NetworkTableInstance::GetDefault().GetTable("FMSInfo");
-  MatchDataSenderEntry<std::string> typeMetaData{table, ".type", "FMSInfo"};
-  MatchDataSenderEntry<std::string> gameSpecificMessage{
+  MatchDataSenderEntry<nt::StringTopic> typeMetaData{table, ".type", "FMSInfo"};
+  MatchDataSenderEntry<nt::StringTopic> gameSpecificMessage{
       table, "GameSpecificMessage", ""};
-  MatchDataSenderEntry<std::string> eventName{table, "EventName", ""};
-  MatchDataSenderEntry<double> matchNumber{table, "MatchNumber", 0.0};
-  MatchDataSenderEntry<double> replayNumber{table, "ReplayNumber", 0.0};
-  MatchDataSenderEntry<double> matchType{table, "MatchType", 0.0};
-  MatchDataSenderEntry<bool> alliance{table, "IsRedAlliance", true};
-  MatchDataSenderEntry<double> station{table, "StationNumber", 1.0};
-  MatchDataSenderEntry<double> controlWord{table, "FMSControlData", 0.0};
+  MatchDataSenderEntry<nt::StringTopic> eventName{table, "EventName", ""};
+  MatchDataSenderEntry<nt::IntegerTopic> matchNumber{table, "MatchNumber", 0};
+  MatchDataSenderEntry<nt::IntegerTopic> replayNumber{table, "ReplayNumber", 0};
+  MatchDataSenderEntry<nt::IntegerTopic> matchType{table, "MatchType", 0};
+  MatchDataSenderEntry<nt::BooleanTopic> alliance{table, "IsRedAlliance", true};
+  MatchDataSenderEntry<nt::IntegerTopic> station{table, "StationNumber", 1};
+  MatchDataSenderEntry<nt::IntegerTopic> controlWord{table, "FMSControlData",
+                                                     0};
+};
+
+class JoystickLogSender {
+ public:
+  void Init(wpi::log::DataLog& log, unsigned int stick, int64_t timestamp);
+  void Send(uint64_t timestamp);
+
+ private:
+  void AppendButtons(HAL_JoystickButtons buttons, uint64_t timestamp);
+  void AppendPOVs(const HAL_JoystickPOVs& povs, uint64_t timestamp);
+
+  unsigned int m_stick;
+  HAL_JoystickButtons m_prevButtons;
+  HAL_JoystickAxes m_prevAxes;
+  HAL_JoystickPOVs m_prevPOVs;
+  wpi::log::BooleanArrayLogEntry m_logButtons;
+  wpi::log::FloatArrayLogEntry m_logAxes;
+  wpi::log::IntegerArrayLogEntry m_logPOVs;
+};
+
+class DataLogSender {
+ public:
+  void Init(wpi::log::DataLog& log, bool logJoysticks, int64_t timestamp);
+  void Send(uint64_t timestamp);
+
+ private:
+  std::atomic_bool m_initialized{false};
+
+  HAL_ControlWord m_prevControlWord;
+  wpi::log::BooleanLogEntry m_logEnabled;
+  wpi::log::BooleanLogEntry m_logAutonomous;
+  wpi::log::BooleanLogEntry m_logTest;
+  wpi::log::BooleanLogEntry m_logEstop;
+
+  bool m_logJoysticks;
+  std::array<JoystickLogSender, DriverStation::kJoystickPorts> m_joysticks;
 };
 
 struct Instance {
   Instance();
   ~Instance();
 
+  wpi::EventVector refreshEvents;
   MatchDataSender matchDataSender;
+  std::atomic<DataLogSender*> dataLogSender{nullptr};
 
   // Joystick button rising/falling edge flags
   wpi::mutex buttonEdgeMutex;
@@ -99,14 +129,6 @@
   std::array<uint32_t, DriverStation::kJoystickPorts> joystickButtonsPressed;
   std::array<uint32_t, DriverStation::kJoystickPorts> joystickButtonsReleased;
 
-  // Internal Driver Station thread
-  std::thread dsThread;
-  std::atomic<bool> isRunning{false};
-
-  mutable wpi::mutex waitForDataMutex;
-  wpi::condition_variable waitForDataCond;
-  int waitForDataCounter = 0;
-
   bool silenceJoystickWarning = false;
 
   // Robot state status variables
@@ -126,7 +148,6 @@
   return instance;
 }
 
-static void Run();
 static void SendMatchData();
 
 /**
@@ -140,8 +161,7 @@
 template <typename S, typename... Args>
 static inline void ReportJoystickUnpluggedError(const S& format,
                                                 Args&&... args) {
-  ReportJoystickUnpluggedErrorV(
-      format, fmt::make_args_checked<Args...>(format, args...));
+  ReportJoystickUnpluggedErrorV(format, fmt::make_format_args(args...));
 }
 
 /**
@@ -155,17 +175,7 @@
 template <typename S, typename... Args>
 static inline void ReportJoystickUnpluggedWarning(const S& format,
                                                   Args&&... args) {
-  ReportJoystickUnpluggedWarningV(
-      format, fmt::make_args_checked<Args...>(format, args...));
-}
-
-static int& GetDSLastCount() {
-  // There is a rollover error condition here. At Packet# = n * (uintmax), this
-  // will return false when instead it should return true. However, this at a
-  // 20ms rate occurs once every 2.7 years of DS connected runtime, so not
-  // worth the cycles to check.
-  thread_local int lastCount{0};
-  return lastCount;
+  ReportJoystickUnpluggedWarningV(format, fmt::make_format_args(args...));
 }
 
 Instance::Instance() {
@@ -179,21 +189,12 @@
     previousButtonStates[i].count = 0;
     previousButtonStates[i].buttons = 0;
   }
-
-  dsThread = std::thread(&Run);
 }
 
 Instance::~Instance() {
-  isRunning = false;
-  // Trigger a DS mutex release in case there is no driver station running.
-  HAL_ReleaseDSMutex();
-  dsThread.join();
-}
-
-DriverStation& DriverStation::GetInstance() {
-  ::GetInstance();
-  static DriverStation instance;
-  return instance;
+  if (dataLogSender) {
+    delete dataLogSender.load();
+  }
 }
 
 bool DriverStation::GetStickButton(int stick, int button) {
@@ -419,11 +420,15 @@
     FRC_ReportError(warn::BadJoystickIndex, "stick {} out of range", stick);
     return -1;
   }
+  if (axis < 0 || axis >= HAL_kMaxJoystickAxes) {
+    FRC_ReportError(warn::BadJoystickAxis, "axis {} out of range", axis);
+    return -1;
+  }
 
   HAL_JoystickDescriptor descriptor;
   HAL_GetJoystickDescriptor(stick, &descriptor);
 
-  return static_cast<bool>(descriptor.axisTypes);
+  return descriptor.axisTypes[axis];
 }
 
 bool DriverStation::IsJoystickConnected(int stick) {
@@ -461,20 +466,12 @@
   return controlWord.autonomous && controlWord.enabled;
 }
 
-bool DriverStation::IsOperatorControl() {
-  return IsTeleop();
-}
-
 bool DriverStation::IsTeleop() {
   HAL_ControlWord controlWord;
   HAL_GetControlWord(&controlWord);
   return !(controlWord.autonomous || controlWord.test);
 }
 
-bool DriverStation::IsOperatorControlEnabled() {
-  return IsTeleopEnabled();
-}
-
 bool DriverStation::IsTeleopEnabled() {
   HAL_ControlWord controlWord;
   HAL_GetControlWord(&controlWord);
@@ -493,18 +490,6 @@
   return controlWord.dsAttached;
 }
 
-bool DriverStation::IsNewControlData() {
-  auto& inst = ::GetInstance();
-  std::unique_lock lock(inst.waitForDataMutex);
-  int& lastCount = GetDSLastCount();
-  int currentCount = inst.waitForDataCounter;
-  if (lastCount == currentCount) {
-    return false;
-  }
-  lastCount = currentCount;
-  return true;
-}
-
 bool DriverStation::IsFMSAttached() {
   HAL_ControlWord controlWord;
   HAL_GetControlWord(&controlWord);
@@ -577,36 +562,6 @@
   }
 }
 
-void DriverStation::WaitForData() {
-  WaitForData(0_s);
-}
-
-bool DriverStation::WaitForData(units::second_t timeout) {
-  auto& inst = ::GetInstance();
-  auto timeoutTime = std::chrono::steady_clock::now() +
-                     std::chrono::steady_clock::duration{timeout};
-
-  std::unique_lock lock(inst.waitForDataMutex);
-  int& lastCount = GetDSLastCount();
-  int currentCount = inst.waitForDataCounter;
-  if (lastCount != currentCount) {
-    lastCount = currentCount;
-    return true;
-  }
-  while (inst.waitForDataCounter == currentCount) {
-    if (timeout > 0_s) {
-      auto timedOut = inst.waitForDataCond.wait_until(lock, timeoutTime);
-      if (timedOut == std::cv_status::timeout) {
-        return false;
-      }
-    } else {
-      inst.waitForDataCond.wait(lock);
-    }
-  }
-  lastCount = inst.waitForDataCounter;
-  return true;
-}
-
 double DriverStation::GetMatchTime() {
   int32_t status = 0;
   return HAL_GetMatchTime(&status);
@@ -615,46 +570,19 @@
 double DriverStation::GetBatteryVoltage() {
   int32_t status = 0;
   double voltage = HAL_GetVinVoltage(&status);
-  FRC_CheckErrorStatus(status, "{}", "getVinVoltage");
+  FRC_CheckErrorStatus(status, "getVinVoltage");
 
   return voltage;
 }
 
-void DriverStation::InDisabled(bool entering) {
-  ::GetInstance().userInDisabled = entering;
-}
-
-void DriverStation::InAutonomous(bool entering) {
-  ::GetInstance().userInAutonomous = entering;
-}
-
-void DriverStation::InOperatorControl(bool entering) {
-  InTeleop(entering);
-}
-
-void DriverStation::InTeleop(bool entering) {
-  ::GetInstance().userInTeleop = entering;
-}
-
-void DriverStation::InTest(bool entering) {
-  ::GetInstance().userInTest = entering;
-}
-
-void DriverStation::WakeupWaitForData() {
-  auto& inst = ::GetInstance();
-  std::scoped_lock waitLock(inst.waitForDataMutex);
-  // Nofify all threads
-  inst.waitForDataCounter++;
-  inst.waitForDataCond.notify_all();
-}
-
 /**
  * Copy data from the DS task for the user.
  *
  * If no new data exists, it will just be returned, otherwise
  * the data will be copied from the DS polling loop.
  */
-void GetData() {
+void DriverStation::RefreshData() {
+  HAL_RefreshDSData();
   auto& inst = ::GetInstance();
   {
     // Compute the pressed and released buttons
@@ -676,8 +604,22 @@
     }
   }
 
-  DriverStation::WakeupWaitForData();
+  inst.refreshEvents.Wakeup();
+
   SendMatchData();
+  if (auto sender = inst.dataLogSender.load()) {
+    sender->Send(wpi::Now());
+  }
+}
+
+void DriverStation::ProvideRefreshedDataEventHandle(WPI_EventHandle handle) {
+  auto& inst = ::GetInstance();
+  inst.refreshEvents.Add(handle);
+}
+
+void DriverStation::RemoveRefreshedDataEventHandle(WPI_EventHandle handle) {
+  auto& inst = ::GetInstance();
+  inst.refreshEvents.Remove(handle);
 }
 
 void DriverStation::SilenceJoystickConnectionWarning(bool silence) {
@@ -688,6 +630,24 @@
   return !IsFMSAttached() && ::GetInstance().silenceJoystickWarning;
 }
 
+void DriverStation::StartDataLog(wpi::log::DataLog& log, bool logJoysticks) {
+  auto& inst = ::GetInstance();
+  // Note: cannot safely replace, because we wouldn't know when to delete the
+  // "old" one. Instead do a compare and exchange with nullptr. We check first
+  // with a simple load to avoid the new in the common case.
+  if (inst.dataLogSender.load()) {
+    return;
+  }
+  DataLogSender* oldSender = nullptr;
+  DataLogSender* newSender = new DataLogSender;
+  inst.dataLogSender.compare_exchange_strong(oldSender, newSender);
+  if (oldSender) {
+    delete newSender;  // already had a sender
+  } else {
+    newSender->Init(log, logJoysticks, wpi::Now());
+  }
+}
+
 void ReportJoystickUnpluggedErrorV(fmt::string_view format,
                                    fmt::format_args args) {
   auto& inst = GetInstance();
@@ -710,37 +670,6 @@
   }
 }
 
-void Run() {
-  auto& inst = GetInstance();
-  inst.isRunning = true;
-  int safetyCounter = 0;
-  while (inst.isRunning) {
-    HAL_WaitForDSData();
-    GetData();
-
-    if (DriverStation::IsDisabled()) {
-      safetyCounter = 0;
-    }
-
-    if (++safetyCounter >= 4) {
-      MotorSafety::CheckMotors();
-      safetyCounter = 0;
-    }
-    if (inst.userInDisabled) {
-      HAL_ObserveUserProgramDisabled();
-    }
-    if (inst.userInAutonomous) {
-      HAL_ObserveUserProgramAutonomous();
-    }
-    if (inst.userInTeleop) {
-      HAL_ObserveUserProgramTeleop();
-    }
-    if (inst.userInTest) {
-      HAL_ObserveUserProgramTest();
-    }
-  }
-}
-
 void SendMatchData() {
   int32_t status = 0;
   HAL_AllianceStationID alliance = HAL_GetAllianceStation(&status);
@@ -793,3 +722,131 @@
   std::memcpy(&wordInt, &ctlWord, sizeof(wordInt));
   inst.matchDataSender.controlWord.Set(wordInt);
 }
+
+void JoystickLogSender::Init(wpi::log::DataLog& log, unsigned int stick,
+                             int64_t timestamp) {
+  m_stick = stick;
+
+  m_logButtons = wpi::log::BooleanArrayLogEntry{
+      log, fmt::format("DS:joystick{}/buttons", stick), timestamp};
+  m_logAxes = wpi::log::FloatArrayLogEntry{
+      log, fmt::format("DS:joystick{}/axes", stick), timestamp};
+  m_logPOVs = wpi::log::IntegerArrayLogEntry{
+      log, fmt::format("DS:joystick{}/povs", stick), timestamp};
+
+  HAL_GetJoystickButtons(m_stick, &m_prevButtons);
+  HAL_GetJoystickAxes(m_stick, &m_prevAxes);
+  HAL_GetJoystickPOVs(m_stick, &m_prevPOVs);
+  AppendButtons(m_prevButtons, timestamp);
+  m_logAxes.Append(
+      std::span<const float>{m_prevAxes.axes,
+                             static_cast<size_t>(m_prevAxes.count)},
+      timestamp);
+  AppendPOVs(m_prevPOVs, timestamp);
+}
+
+void JoystickLogSender::Send(uint64_t timestamp) {
+  HAL_JoystickButtons buttons;
+  HAL_GetJoystickButtons(m_stick, &buttons);
+  if (buttons.count != m_prevButtons.count ||
+      buttons.buttons != m_prevButtons.buttons) {
+    AppendButtons(buttons, timestamp);
+  }
+  m_prevButtons = buttons;
+
+  HAL_JoystickAxes axes;
+  HAL_GetJoystickAxes(m_stick, &axes);
+  if (axes.count != m_prevAxes.count ||
+      std::memcmp(axes.axes, m_prevAxes.axes,
+                  sizeof(axes.axes[0]) * axes.count) != 0) {
+    m_logAxes.Append(
+        std::span<const float>{axes.axes, static_cast<size_t>(axes.count)},
+        timestamp);
+  }
+  m_prevAxes = axes;
+
+  HAL_JoystickPOVs povs;
+  HAL_GetJoystickPOVs(m_stick, &povs);
+  if (povs.count != m_prevPOVs.count ||
+      std::memcmp(povs.povs, m_prevPOVs.povs,
+                  sizeof(povs.povs[0]) * povs.count) != 0) {
+    AppendPOVs(povs, timestamp);
+  }
+  m_prevPOVs = povs;
+}
+
+void JoystickLogSender::AppendButtons(HAL_JoystickButtons buttons,
+                                      uint64_t timestamp) {
+  uint8_t buttonsArr[32];
+  for (unsigned int i = 0; i < buttons.count; ++i) {
+    buttonsArr[i] = (buttons.buttons & (1u << i)) != 0;
+  }
+  m_logButtons.Append(std::span<const uint8_t>{buttonsArr, buttons.count},
+                      timestamp);
+}
+
+void JoystickLogSender::AppendPOVs(const HAL_JoystickPOVs& povs,
+                                   uint64_t timestamp) {
+  int64_t povsArr[HAL_kMaxJoystickPOVs];
+  for (int i = 0; i < povs.count; ++i) {
+    povsArr[i] = povs.povs[i];
+  }
+  m_logPOVs.Append(
+      std::span<const int64_t>{povsArr, static_cast<size_t>(povs.count)},
+      timestamp);
+}
+
+void DataLogSender::Init(wpi::log::DataLog& log, bool logJoysticks,
+                         int64_t timestamp) {
+  m_logEnabled = wpi::log::BooleanLogEntry{log, "DS:enabled", timestamp};
+  m_logAutonomous = wpi::log::BooleanLogEntry{log, "DS:autonomous", timestamp};
+  m_logTest = wpi::log::BooleanLogEntry{log, "DS:test", timestamp};
+  m_logEstop = wpi::log::BooleanLogEntry{log, "DS:estop", timestamp};
+
+  // append initial control word values
+  HAL_GetControlWord(&m_prevControlWord);
+  m_logEnabled.Append(m_prevControlWord.enabled, timestamp);
+  m_logAutonomous.Append(m_prevControlWord.autonomous, timestamp);
+  m_logTest.Append(m_prevControlWord.test, timestamp);
+  m_logEstop.Append(m_prevControlWord.eStop, timestamp);
+
+  m_logJoysticks = logJoysticks;
+  if (logJoysticks) {
+    unsigned int i = 0;
+    for (auto&& joystick : m_joysticks) {
+      joystick.Init(log, i++, timestamp);
+    }
+  }
+
+  m_initialized = true;
+}
+
+void DataLogSender::Send(uint64_t timestamp) {
+  if (!m_initialized) {
+    return;
+  }
+
+  // append control word value changes
+  HAL_ControlWord ctlWord;
+  HAL_GetControlWord(&ctlWord);
+  if (ctlWord.enabled != m_prevControlWord.enabled) {
+    m_logEnabled.Append(ctlWord.enabled, timestamp);
+  }
+  if (ctlWord.autonomous != m_prevControlWord.autonomous) {
+    m_logAutonomous.Append(ctlWord.autonomous, timestamp);
+  }
+  if (ctlWord.test != m_prevControlWord.test) {
+    m_logTest.Append(ctlWord.test, timestamp);
+  }
+  if (ctlWord.eStop != m_prevControlWord.eStop) {
+    m_logEstop.Append(ctlWord.eStop, timestamp);
+  }
+  m_prevControlWord = ctlWord;
+
+  if (m_logJoysticks) {
+    // append joystick value changes
+    for (auto&& joystick : m_joysticks) {
+      joystick.Send(timestamp);
+    }
+  }
+}
diff --git a/wpilibc/src/main/native/cpp/DutyCycle.cpp b/wpilibc/src/main/native/cpp/DutyCycle.cpp
index a8375e0..88f6b56 100644
--- a/wpilibc/src/main/native/cpp/DutyCycle.cpp
+++ b/wpilibc/src/main/native/cpp/DutyCycle.cpp
@@ -17,7 +17,7 @@
 DutyCycle::DutyCycle(DigitalSource* source)
     : m_source{source, wpi::NullDeleter<DigitalSource>()} {
   if (!m_source) {
-    throw FRC_MakeError(err::NullParameter, "{}", "source");
+    throw FRC_MakeError(err::NullParameter, "source");
   }
   InitDutyCycle();
 }
@@ -30,7 +30,7 @@
 DutyCycle::DutyCycle(std::shared_ptr<DigitalSource> source)
     : m_source{std::move(source)} {
   if (!m_source) {
-    throw FRC_MakeError(err::NullParameter, "{}", "source");
+    throw FRC_MakeError(err::NullParameter, "source");
   }
   InitDutyCycle();
 }
@@ -73,11 +73,11 @@
   return retVal;
 }
 
-unsigned int DutyCycle::GetOutputRaw() const {
+units::second_t DutyCycle::GetHighTime() const {
   int32_t status = 0;
-  auto retVal = HAL_GetDutyCycleOutputRaw(m_handle, &status);
+  auto retVal = HAL_GetDutyCycleHighTime(m_handle, &status);
   FRC_CheckErrorStatus(status, "Channel {}", GetSourceChannel());
-  return retVal;
+  return units::nanosecond_t{static_cast<double>(retVal)};
 }
 
 unsigned int DutyCycle::GetOutputScaleFactor() const {
diff --git a/wpilibc/src/main/native/cpp/DutyCycleEncoder.cpp b/wpilibc/src/main/native/cpp/DutyCycleEncoder.cpp
index 452f5cd..b1c943e 100644
--- a/wpilibc/src/main/native/cpp/DutyCycleEncoder.cpp
+++ b/wpilibc/src/main/native/cpp/DutyCycleEncoder.cpp
@@ -60,6 +60,8 @@
         m_simDevice.CreateDouble("position", hal::SimDevice::kInput, 0.0);
     m_simDistancePerRotation = m_simDevice.CreateDouble(
         "distance_per_rot", hal::SimDevice::kOutput, 1.0);
+    m_simAbsolutePosition =
+        m_simDevice.CreateDouble("absPosition", hal::SimDevice::kInput, 0.0);
     m_simIsConnected =
         m_simDevice.CreateBoolean("connected", hal::SimDevice::kInput, true);
   } else {
@@ -76,6 +78,11 @@
                                m_dutyCycle->GetSourceChannel());
 }
 
+static bool DoubleEquals(double a, double b) {
+  constexpr double epsilon = 0.00001;
+  return std::abs(a - b) < epsilon;
+}
+
 units::turn_t DutyCycleEncoder::Get() const {
   if (m_simPosition) {
     return units::turn_t{m_simPosition.Get()};
@@ -88,15 +95,9 @@
     auto pos = m_dutyCycle->GetOutput();
     auto counter2 = m_counter->Get();
     auto pos2 = m_dutyCycle->GetOutput();
-    if (counter == counter2 && pos == pos2) {
+    if (counter == counter2 && DoubleEquals(pos, pos2)) {
       // map sensor range
-      if (pos < m_sensorMin) {
-        pos = m_sensorMin;
-      }
-      if (pos > m_sensorMax) {
-        pos = m_sensorMax;
-      }
-      pos = (pos - m_sensorMin) / (m_sensorMax - m_sensorMin);
+      pos = MapSensorRange(pos);
       units::turn_t turns{counter + pos - m_positionOffset};
       m_lastPosition = turns;
       return turns;
@@ -104,12 +105,39 @@
   }
 
   FRC_ReportError(
-      warn::Warning, "{}",
+      warn::Warning,
       "Failed to read DutyCycle Encoder. Potential Speed Overrun. Returning "
       "last value");
   return m_lastPosition;
 }
 
+double DutyCycleEncoder::MapSensorRange(double pos) const {
+  if (pos < m_sensorMin) {
+    pos = m_sensorMin;
+  }
+  if (pos > m_sensorMax) {
+    pos = m_sensorMax;
+  }
+  pos = (pos - m_sensorMin) / (m_sensorMax - m_sensorMin);
+  return pos;
+}
+
+double DutyCycleEncoder::GetAbsolutePosition() const {
+  if (m_simAbsolutePosition) {
+    return m_simAbsolutePosition.Get();
+  }
+
+  return MapSensorRange(m_dutyCycle->GetOutput());
+}
+
+double DutyCycleEncoder::GetPositionOffset() const {
+  return m_positionOffset;
+}
+
+void DutyCycleEncoder::SetPositionOffset(double offset) {
+  m_positionOffset = std::clamp(offset, 0.0, 1.0);
+}
+
 void DutyCycleEncoder::SetDutyCycleRange(double min, double max) {
   m_sensorMin = std::clamp(min, 0.0, 1.0);
   m_sensorMax = std::clamp(max, 0.0, 1.0);
diff --git a/wpilibc/src/main/native/cpp/Encoder.cpp b/wpilibc/src/main/native/cpp/Encoder.cpp
index bef6d76..c644070 100644
--- a/wpilibc/src/main/native/cpp/Encoder.cpp
+++ b/wpilibc/src/main/native/cpp/Encoder.cpp
@@ -31,10 +31,10 @@
     : m_aSource(aSource, wpi::NullDeleter<DigitalSource>()),
       m_bSource(bSource, wpi::NullDeleter<DigitalSource>()) {
   if (!m_aSource) {
-    throw FRC_MakeError(err::NullParameter, "{}", "aSource");
+    throw FRC_MakeError(err::NullParameter, "aSource");
   }
   if (!m_bSource) {
-    throw FRC_MakeError(err::NullParameter, "{}", "bSource");
+    throw FRC_MakeError(err::NullParameter, "bSource");
   }
   InitEncoder(reverseDirection, encodingType);
 }
@@ -51,10 +51,10 @@
                  EncodingType encodingType)
     : m_aSource(std::move(aSource)), m_bSource(std::move(bSource)) {
   if (!m_aSource) {
-    throw FRC_MakeError(err::NullParameter, "{}", "aSource");
+    throw FRC_MakeError(err::NullParameter, "aSource");
   }
   if (!m_bSource) {
-    throw FRC_MakeError(err::NullParameter, "{}", "bSource");
+    throw FRC_MakeError(err::NullParameter, "bSource");
   }
   InitEncoder(reverseDirection, encodingType);
 }
@@ -62,100 +62,100 @@
 Encoder::~Encoder() {
   int32_t status = 0;
   HAL_FreeEncoder(m_encoder, &status);
-  FRC_ReportError(status, "{}", "FreeEncoder");
+  FRC_ReportError(status, "FreeEncoder");
 }
 
 int Encoder::Get() const {
   int32_t status = 0;
   int value = HAL_GetEncoder(m_encoder, &status);
-  FRC_CheckErrorStatus(status, "{}", "Get");
+  FRC_CheckErrorStatus(status, "Get");
   return value;
 }
 
 void Encoder::Reset() {
   int32_t status = 0;
   HAL_ResetEncoder(m_encoder, &status);
-  FRC_CheckErrorStatus(status, "{}", "Reset");
+  FRC_CheckErrorStatus(status, "Reset");
 }
 
 units::second_t Encoder::GetPeriod() const {
   int32_t status = 0;
   double value = HAL_GetEncoderPeriod(m_encoder, &status);
-  FRC_CheckErrorStatus(status, "{}", "GetPeriod");
+  FRC_CheckErrorStatus(status, "GetPeriod");
   return units::second_t{value};
 }
 
 void Encoder::SetMaxPeriod(units::second_t maxPeriod) {
   int32_t status = 0;
   HAL_SetEncoderMaxPeriod(m_encoder, maxPeriod.value(), &status);
-  FRC_CheckErrorStatus(status, "{}", "SetMaxPeriod");
+  FRC_CheckErrorStatus(status, "SetMaxPeriod");
 }
 
 bool Encoder::GetStopped() const {
   int32_t status = 0;
   bool value = HAL_GetEncoderStopped(m_encoder, &status);
-  FRC_CheckErrorStatus(status, "{}", "GetStopped");
+  FRC_CheckErrorStatus(status, "GetStopped");
   return value;
 }
 
 bool Encoder::GetDirection() const {
   int32_t status = 0;
   bool value = HAL_GetEncoderDirection(m_encoder, &status);
-  FRC_CheckErrorStatus(status, "{}", "GetDirection");
+  FRC_CheckErrorStatus(status, "GetDirection");
   return value;
 }
 
 int Encoder::GetRaw() const {
   int32_t status = 0;
   int value = HAL_GetEncoderRaw(m_encoder, &status);
-  FRC_CheckErrorStatus(status, "{}", "GetRaw");
+  FRC_CheckErrorStatus(status, "GetRaw");
   return value;
 }
 
 int Encoder::GetEncodingScale() const {
   int32_t status = 0;
   int val = HAL_GetEncoderEncodingScale(m_encoder, &status);
-  FRC_CheckErrorStatus(status, "{}", "GetEncodingScale");
+  FRC_CheckErrorStatus(status, "GetEncodingScale");
   return val;
 }
 
 double Encoder::GetDistance() const {
   int32_t status = 0;
   double value = HAL_GetEncoderDistance(m_encoder, &status);
-  FRC_CheckErrorStatus(status, "{}", "GetDistance");
+  FRC_CheckErrorStatus(status, "GetDistance");
   return value;
 }
 
 double Encoder::GetRate() const {
   int32_t status = 0;
   double value = HAL_GetEncoderRate(m_encoder, &status);
-  FRC_CheckErrorStatus(status, "{}", "GetRate");
+  FRC_CheckErrorStatus(status, "GetRate");
   return value;
 }
 
 void Encoder::SetMinRate(double minRate) {
   int32_t status = 0;
   HAL_SetEncoderMinRate(m_encoder, minRate, &status);
-  FRC_CheckErrorStatus(status, "{}", "SetMinRate");
+  FRC_CheckErrorStatus(status, "SetMinRate");
 }
 
 void Encoder::SetDistancePerPulse(double distancePerPulse) {
   int32_t status = 0;
   HAL_SetEncoderDistancePerPulse(m_encoder, distancePerPulse, &status);
-  FRC_CheckErrorStatus(status, "{}", "SetDistancePerPulse");
+  FRC_CheckErrorStatus(status, "SetDistancePerPulse");
 }
 
 double Encoder::GetDistancePerPulse() const {
   int32_t status = 0;
   double distancePerPulse = HAL_GetEncoderDistancePerPulse(m_encoder, &status);
-  FRC_CheckErrorStatus(status, "{}", "GetDistancePerPulse");
+  FRC_CheckErrorStatus(status, "GetDistancePerPulse");
   return distancePerPulse;
 }
 
 void Encoder::SetReverseDirection(bool reverseDirection) {
   int32_t status = 0;
   HAL_SetEncoderReverseDirection(m_encoder, reverseDirection, &status);
-  FRC_CheckErrorStatus(status, "{}", "SetReverseDirection");
+  FRC_CheckErrorStatus(status, "SetReverseDirection");
 }
 
 void Encoder::SetSamplesToAverage(int samplesToAverage) {
@@ -167,13 +167,13 @@
   }
   int32_t status = 0;
   HAL_SetEncoderSamplesToAverage(m_encoder, samplesToAverage, &status);
-  FRC_CheckErrorStatus(status, "{}", "SetSamplesToAverage");
+  FRC_CheckErrorStatus(status, "SetSamplesToAverage");
 }
 
 int Encoder::GetSamplesToAverage() const {
   int32_t status = 0;
   int result = HAL_GetEncoderSamplesToAverage(m_encoder, &status);
-  FRC_CheckErrorStatus(status, "{}", "GetSamplesToAverage");
+  FRC_CheckErrorStatus(status, "GetSamplesToAverage");
   return result;
 }
 
@@ -192,7 +192,7 @@
                                 source.GetAnalogTriggerTypeForRouting()),
                             static_cast<HAL_EncoderIndexingType>(type),
                             &status);
-  FRC_CheckErrorStatus(status, "{}", "SetIndexSource");
+  FRC_CheckErrorStatus(status, "SetIndexSource");
 }
 
 void Encoder::SetSimDevice(HAL_SimDeviceHandle device) {
@@ -202,14 +202,14 @@
 int Encoder::GetFPGAIndex() const {
   int32_t status = 0;
   int val = HAL_GetEncoderFPGAIndex(m_encoder, &status);
-  FRC_CheckErrorStatus(status, "{}", "GetFPGAIndex");
+  FRC_CheckErrorStatus(status, "GetFPGAIndex");
   return val;
 }
 
 void Encoder::InitSendable(wpi::SendableBuilder& builder) {
   int32_t status = 0;
   HAL_EncoderEncodingType type = HAL_GetEncoderEncodingType(m_encoder, &status);
-  FRC_CheckErrorStatus(status, "{}", "GetEncodingType");
+  FRC_CheckErrorStatus(status, "GetEncodingType");
   if (type == HAL_EncoderEncodingType::HAL_Encoder_k4X) {
     builder.SetSmartDashboardType("Quadrature Encoder");
   } else {
@@ -217,11 +217,12 @@
   }
 
   builder.AddDoubleProperty(
-      "Speed", [=] { return GetRate(); }, nullptr);
+      "Speed", [=, this] { return GetRate(); }, nullptr);
   builder.AddDoubleProperty(
-      "Distance", [=] { return GetDistance(); }, nullptr);
+      "Distance", [=, this] { return GetDistance(); }, nullptr);
   builder.AddDoubleProperty(
-      "Distance per Tick", [=] { return GetDistancePerPulse(); }, nullptr);
+      "Distance per Tick", [=, this] { return GetDistancePerPulse(); },
+      nullptr);
 }
 
 void Encoder::InitEncoder(bool reverseDirection, EncodingType encodingType) {
@@ -235,7 +236,7 @@
           m_bSource->GetAnalogTriggerTypeForRouting()),
       reverseDirection, static_cast<HAL_EncoderEncodingType>(encodingType),
       &status);
-  FRC_CheckErrorStatus(status, "{}", "InitEncoder");
+  FRC_CheckErrorStatus(status, "InitEncoder");
 
   HAL_Report(HALUsageReporting::kResourceType_Encoder, GetFPGAIndex() + 1,
              encodingType);
@@ -245,6 +246,6 @@
 double Encoder::DecodingScaleFactor() const {
   int32_t status = 0;
   double val = HAL_GetEncoderDecodingScaleFactor(m_encoder, &status);
-  FRC_CheckErrorStatus(status, "{}", "DecodingScaleFactor");
+  FRC_CheckErrorStatus(status, "DecodingScaleFactor");
   return val;
 }
diff --git a/wpilibc/src/main/native/cpp/GenericHID.cpp b/wpilibc/src/main/native/cpp/GenericHID.cpp
index 6c186eb..f4d26b6 100644
--- a/wpilibc/src/main/native/cpp/GenericHID.cpp
+++ b/wpilibc/src/main/native/cpp/GenericHID.cpp
@@ -8,6 +8,7 @@
 
 #include "frc/DriverStation.h"
 #include "frc/Errors.h"
+#include "frc/event/BooleanEvent.h"
 
 using namespace frc;
 
@@ -30,6 +31,11 @@
   return DriverStation::GetStickButtonReleased(m_port, button);
 }
 
+BooleanEvent GenericHID::Button(int button, EventLoop* loop) const {
+  return BooleanEvent(loop,
+                      [this, button]() { return this->GetRawButton(button); });
+}
+
 double GenericHID::GetRawAxis(int axis) const {
   return DriverStation::GetStickAxis(m_port, axis);
 }
@@ -38,6 +44,65 @@
   return DriverStation::GetStickPOV(m_port, pov);
 }
 
+BooleanEvent GenericHID::POV(int angle, EventLoop* loop) const {
+  return POV(0, angle, loop);
+}
+
+BooleanEvent GenericHID::POV(int pov, int angle, EventLoop* loop) const {
+  return BooleanEvent(
+      loop, [this, pov, angle] { return this->GetPOV(pov) == angle; });
+}
+
+BooleanEvent GenericHID::POVUp(EventLoop* loop) const {
+  return POV(0, loop);
+}
+
+BooleanEvent GenericHID::POVUpRight(EventLoop* loop) const {
+  return POV(45, loop);
+}
+
+BooleanEvent GenericHID::POVRight(EventLoop* loop) const {
+  return POV(90, loop);
+}
+
+BooleanEvent GenericHID::POVDownRight(EventLoop* loop) const {
+  return POV(135, loop);
+}
+
+BooleanEvent GenericHID::POVDown(EventLoop* loop) const {
+  return POV(180, loop);
+}
+
+BooleanEvent GenericHID::POVDownLeft(EventLoop* loop) const {
+  return POV(225, loop);
+}
+
+BooleanEvent GenericHID::POVLeft(EventLoop* loop) const {
+  return POV(270, loop);
+}
+
+BooleanEvent GenericHID::POVUpLeft(EventLoop* loop) const {
+  return POV(315, loop);
+}
+
+BooleanEvent GenericHID::POVCenter(EventLoop* loop) const {
+  return POV(360, loop);
+}
+
+BooleanEvent GenericHID::AxisLessThan(int axis, double threshold,
+                                      EventLoop* loop) const {
+  return BooleanEvent(loop, [this, axis, threshold]() {
+    return this->GetRawAxis(axis) < threshold;
+  });
+}
+
+BooleanEvent GenericHID::AxisGreaterThan(int axis, double threshold,
+                                         EventLoop* loop) const {
+  return BooleanEvent(loop, [this, axis, threshold]() {
+    return this->GetRawAxis(axis) > threshold;
+  });
+}
+
 int GenericHID::GetAxisCount() const {
   return DriverStation::GetStickAxisCount(m_port);
 }
@@ -83,15 +148,17 @@
 }
 
 void GenericHID::SetRumble(RumbleType type, double value) {
-  if (value < 0) {
-    value = 0;
-  } else if (value > 1) {
-    value = 1;
-  }
+  value = std::clamp(value, 0.0, 1.0);
+  double rumbleValue = value * 65535;
+
   if (type == kLeftRumble) {
-    m_leftRumble = value * 65535;
+    m_leftRumble = rumbleValue;
+  } else if (type == kRightRumble) {
+    m_rightRumble = rumbleValue;
   } else {
-    m_rightRumble = value * 65535;
+    m_leftRumble = rumbleValue;
+    m_rightRumble = rumbleValue;
   }
+
   HAL_SetJoystickOutputs(m_port, m_outputs, m_leftRumble, m_rightRumble);
 }
diff --git a/wpilibc/src/main/native/cpp/I2C.cpp b/wpilibc/src/main/native/cpp/I2C.cpp
index dcfb2b8..da26c0c 100644
--- a/wpilibc/src/main/native/cpp/I2C.cpp
+++ b/wpilibc/src/main/native/cpp/I2C.cpp
@@ -18,14 +18,14 @@
   int32_t status = 0;
 
   if (port == I2C::Port::kOnboard) {
-    FRC_ReportError(warn::Warning, "{}",
+    FRC_ReportError(warn::Warning,
                     "Onboard I2C port is subject to system lockups. See Known "
                     "Issues page for "
                     "details");
   }
 
   HAL_InitializeI2C(m_port, &status);
-  FRC_CheckErrorStatus(status, "Port {}", port);
+  FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(port));
 
   HAL_Report(HALUsageReporting::kResourceType_I2C, deviceAddress);
 }
@@ -74,7 +74,7 @@
     throw FRC_MakeError(err::ParameterOutOfRange, "count {}", count);
   }
   if (!buffer) {
-    throw FRC_MakeError(err::NullParameter, "{}", "buffer");
+    throw FRC_MakeError(err::NullParameter, "buffer");
   }
   uint8_t regAddr = registerAddress;
   return Transaction(&regAddr, sizeof(regAddr), buffer, count);
@@ -85,7 +85,7 @@
     throw FRC_MakeError(err::ParameterOutOfRange, "count {}", count);
   }
   if (!buffer) {
-    throw FRC_MakeError(err::NullParameter, "{}", "buffer");
+    throw FRC_MakeError(err::NullParameter, "buffer");
   }
   return HAL_ReadI2C(m_port, m_deviceAddress, buffer, count) < 0;
 }
diff --git a/wpilibc/src/main/native/cpp/IterativeRobotBase.cpp b/wpilibc/src/main/native/cpp/IterativeRobotBase.cpp
index 47eb299..0bccb0b 100644
--- a/wpilibc/src/main/native/cpp/IterativeRobotBase.cpp
+++ b/wpilibc/src/main/native/cpp/IterativeRobotBase.cpp
@@ -4,6 +4,8 @@
 
 #include "frc/IterativeRobotBase.h"
 
+#include <frc/DriverStation.h>
+
 #include <fmt/format.h>
 #include <hal/DriverStation.h>
 #include <networktables/NetworkTableInstance.h>
@@ -16,9 +18,6 @@
 
 using namespace frc;
 
-IterativeRobotBase::IterativeRobotBase(double period)
-    : IterativeRobotBase(units::second_t(period)) {}
-
 IterativeRobotBase::IterativeRobotBase(units::second_t period)
     : m_period(period),
       m_watchdog(period, [this] { PrintLoopOverrunMessage(); }) {}
@@ -95,11 +94,24 @@
   m_ntFlushEnabled = enabled;
 }
 
+void IterativeRobotBase::EnableLiveWindowInTest(bool testLW) {
+  if (IsTest()) {
+    throw FRC_MakeError(err::IncompatibleMode,
+                        "Can't configure test mode while in test mode!");
+  }
+  m_lwEnabledInTest = testLW;
+}
+
+bool IterativeRobotBase::IsLiveWindowEnabledInTest() {
+  return m_lwEnabledInTest;
+}
+
 units::second_t IterativeRobotBase::GetPeriod() const {
   return m_period;
 }
 
 void IterativeRobotBase::LoopFunc() {
+  DriverStation::RefreshData();
   m_watchdog.Reset();
 
   // Get current mode
@@ -125,8 +137,10 @@
     } else if (m_lastMode == Mode::kTeleop) {
       TeleopExit();
     } else if (m_lastMode == Mode::kTest) {
-      LiveWindow::SetEnabled(false);
-      Shuffleboard::DisableActuatorWidgets();
+      if (m_lwEnabledInTest) {
+        LiveWindow::SetEnabled(false);
+        Shuffleboard::DisableActuatorWidgets();
+      }
       TestExit();
     }
 
@@ -141,8 +155,10 @@
       TeleopInit();
       m_watchdog.AddEpoch("TeleopInit()");
     } else if (mode == Mode::kTest) {
-      LiveWindow::SetEnabled(true);
-      Shuffleboard::EnableActuatorWidgets();
+      if (m_lwEnabledInTest) {
+        LiveWindow::SetEnabled(true);
+        Shuffleboard::EnableActuatorWidgets();
+      }
       TestInit();
       m_watchdog.AddEpoch("TestInit()");
     }
@@ -190,7 +206,7 @@
 
   // Flush NetworkTables
   if (m_ntFlushEnabled) {
-    nt::NetworkTableInstance::GetDefault().Flush();
+    nt::NetworkTableInstance::GetDefault().FlushLocal();
   }
 
   // Warn on loop time overruns
diff --git a/wpilibc/src/main/native/cpp/Joystick.cpp b/wpilibc/src/main/native/cpp/Joystick.cpp
index 48f0c77..0eff226 100644
--- a/wpilibc/src/main/native/cpp/Joystick.cpp
+++ b/wpilibc/src/main/native/cpp/Joystick.cpp
@@ -5,9 +5,11 @@
 #include "frc/Joystick.h"
 
 #include <cmath>
+#include <numbers>
 
 #include <hal/FRCUsageReporting.h>
-#include <wpi/numbers>
+
+#include "frc/event/BooleanEvent.h"
 
 using namespace frc;
 
@@ -93,6 +95,10 @@
   return GetRawButtonReleased(Button::kTrigger);
 }
 
+BooleanEvent Joystick::Trigger(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetTrigger(); });
+}
+
 bool Joystick::GetTop() const {
   return GetRawButton(Button::kTop);
 }
@@ -105,8 +111,12 @@
   return GetRawButtonReleased(Button::kTop);
 }
 
+BooleanEvent Joystick::Top(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetTop(); });
+}
+
 double Joystick::GetMagnitude() const {
-  return std::sqrt(std::pow(GetX(), 2) + std::pow(GetY(), 2));
+  return std::hypot(GetX(), GetY());
 }
 
 double Joystick::GetDirectionRadians() const {
@@ -114,5 +124,5 @@
 }
 
 double Joystick::GetDirectionDegrees() const {
-  return (180 / wpi::numbers::pi) * GetDirectionRadians();
+  return (180 / std::numbers::pi) * GetDirectionRadians();
 }
diff --git a/wpilibc/src/main/native/cpp/MotorSafety.cpp b/wpilibc/src/main/native/cpp/MotorSafety.cpp
index 388a887..368092b 100644
--- a/wpilibc/src/main/native/cpp/MotorSafety.cpp
+++ b/wpilibc/src/main/native/cpp/MotorSafety.cpp
@@ -7,6 +7,8 @@
 #include <algorithm>
 #include <utility>
 
+#include <hal/DriverStation.h>
+#include <wpi/SafeThread.h>
 #include <wpi/SmallPtrSet.h>
 
 #include "frc/DriverStation.h"
@@ -14,26 +16,87 @@
 
 using namespace frc;
 
-static wpi::SmallPtrSet<MotorSafety*, 32> instanceList;
-static wpi::mutex listMutex;
+namespace {
+class Thread : public wpi::SafeThread {
+ public:
+  Thread() {}
+  void Main() override;
+};
+
+void Thread::Main() {
+  wpi::Event event{false, false};
+  HAL_ProvideNewDataEventHandle(event.GetHandle());
+
+  int safetyCounter = 0;
+  while (m_active) {
+    bool timedOut = false;
+    bool signaled = wpi::WaitForObject(event.GetHandle(), 0.1, &timedOut);
+    if (signaled) {
+      HAL_ControlWord controlWord;
+      std::memset(&controlWord, 0, sizeof(controlWord));
+      HAL_GetControlWord(&controlWord);
+      if (!(controlWord.enabled && controlWord.dsAttached)) {
+        safetyCounter = 0;
+      }
+      if (++safetyCounter >= 4) {
+        MotorSafety::CheckMotors();
+        safetyCounter = 0;
+      }
+    } else {
+      safetyCounter = 0;
+    }
+  }
+
+  HAL_RemoveNewDataEventHandle(event.GetHandle());
+}
+}  // namespace
+
+static std::atomic_bool gShutdown{false};
+
+namespace {
+struct MotorSafetyManager {
+  ~MotorSafetyManager() { gShutdown = true; }
+
+  wpi::SafeThreadOwner<Thread> thread;
+  wpi::SmallPtrSet<MotorSafety*, 32> instanceList;
+  wpi::mutex listMutex;
+  bool threadStarted = false;
+};
+}  // namespace
+
+static MotorSafetyManager& GetManager() {
+  static MotorSafetyManager manager;
+  return manager;
+}
 
 #ifndef __FRC_ROBORIO__
 namespace frc::impl {
 void ResetMotorSafety() {
-  std::scoped_lock lock(listMutex);
-  instanceList.clear();
+  auto& manager = GetManager();
+  std::scoped_lock lock(manager.listMutex);
+  manager.instanceList.clear();
+  manager.thread.Stop();
+  manager.thread.Join();
+  manager.thread = wpi::SafeThreadOwner<Thread>{};
+  manager.threadStarted = false;
 }
 }  // namespace frc::impl
 #endif
 
 MotorSafety::MotorSafety() {
-  std::scoped_lock lock(listMutex);
-  instanceList.insert(this);
+  auto& manager = GetManager();
+  std::scoped_lock lock(manager.listMutex);
+  manager.instanceList.insert(this);
+  if (!manager.threadStarted) {
+    manager.threadStarted = true;
+    manager.thread.Start();
+  }
 }
 
 MotorSafety::~MotorSafety() {
-  std::scoped_lock lock(listMutex);
-  instanceList.erase(this);
+  auto& manager = GetManager();
+  std::scoped_lock lock(manager.listMutex);
+  manager.instanceList.erase(this);
 }
 
 MotorSafety::MotorSafety(MotorSafety&& rhs)
@@ -96,7 +159,9 @@
   }
 
   if (stopTime < Timer::GetFPGATimestamp()) {
-    FRC_ReportError(err::Timeout, "{}... Output not updated often enough",
+    FRC_ReportError(err::Timeout,
+                    "{}... Output not updated often enough. See "
+                    "https://docs.wpilib.org/motorsafety for more information.",
                     GetDescription());
 
     try {
@@ -111,8 +176,9 @@
 }
 
 void MotorSafety::CheckMotors() {
-  std::scoped_lock lock(listMutex);
-  for (auto elem : instanceList) {
+  auto& manager = GetManager();
+  std::scoped_lock lock(manager.listMutex);
+  for (auto elem : manager.instanceList) {
     elem->Check();
   }
 }
diff --git a/wpilibc/src/main/native/cpp/Notifier.cpp b/wpilibc/src/main/native/cpp/Notifier.cpp
index 441f750..d837752 100644
--- a/wpilibc/src/main/native/cpp/Notifier.cpp
+++ b/wpilibc/src/main/native/cpp/Notifier.cpp
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include <fmt/format.h>
+#include <hal/DriverStation.h>
 #include <hal/FRCUsageReporting.h>
 #include <hal/Notifier.h>
 #include <hal/Threads.h>
@@ -18,14 +19,14 @@
 
 Notifier::Notifier(std::function<void()> handler) {
   if (!handler) {
-    throw FRC_MakeError(err::NullParameter, "{}", "handler");
+    throw FRC_MakeError(err::NullParameter, "handler");
   }
   m_handler = handler;
   int32_t status = 0;
   m_notifier = HAL_InitializeNotifier(&status);
-  FRC_CheckErrorStatus(status, "{}", "InitializeNotifier");
+  FRC_CheckErrorStatus(status, "InitializeNotifier");
 
-  m_thread = std::thread([=] {
+  m_thread = std::thread([=, this] {
     for (;;) {
       int32_t status = 0;
       HAL_NotifierHandle notifier = m_notifier.load();
@@ -60,14 +61,14 @@
 
 Notifier::Notifier(int priority, std::function<void()> handler) {
   if (!handler) {
-    throw FRC_MakeError(err::NullParameter, "{}", "handler");
+    throw FRC_MakeError(err::NullParameter, "handler");
   }
   m_handler = handler;
   int32_t status = 0;
   m_notifier = HAL_InitializeNotifier(&status);
-  FRC_CheckErrorStatus(status, "{}", "InitializeNotifier");
+  FRC_CheckErrorStatus(status, "InitializeNotifier");
 
-  m_thread = std::thread([=] {
+  m_thread = std::thread([=, this] {
     int32_t status = 0;
     HAL_SetCurrentThreadPriority(true, priority, &status);
     for (;;) {
@@ -95,7 +96,21 @@
 
       // call callback
       if (handler) {
-        handler();
+        try {
+          handler();
+        } catch (const frc::RuntimeError& e) {
+          e.Report();
+          FRC_ReportError(
+              err::Error,
+              "Error in Notifier thread."
+              "  The above stacktrace can help determine where the error "
+              "occurred.\n"
+              "  See https://wpilib.org/stacktrace for more information.\n");
+          throw;
+        } catch (const std::exception& e) {
+          HAL_SendError(1, err::Error, 0, e.what(), "", "", 1);
+          throw;
+        }
       }
     }
   });
@@ -106,7 +121,7 @@
   // atomically set handle to 0, then clean
   HAL_NotifierHandle handle = m_notifier.exchange(0);
   HAL_StopNotifier(handle, &status);
-  FRC_ReportError(status, "{}", "StopNotifier");
+  FRC_ReportError(status, "StopNotifier");
 
   // Join the thread to ensure the handler has exited.
   if (m_thread.joinable()) {
@@ -172,7 +187,7 @@
   m_periodic = false;
   int32_t status = 0;
   HAL_CancelNotifierAlarm(m_notifier, &status);
-  FRC_CheckErrorStatus(status, "{}", "CancelNotifierAlarm");
+  FRC_CheckErrorStatus(status, "CancelNotifierAlarm");
 }
 
 void Notifier::UpdateAlarm(uint64_t triggerTime) {
@@ -183,7 +198,7 @@
     return;
   }
   HAL_UpdateNotifierAlarm(notifier, triggerTime, &status);
-  FRC_CheckErrorStatus(status, "{}", "UpdateNotifierAlarm");
+  FRC_CheckErrorStatus(status, "UpdateNotifierAlarm");
 }
 
 void Notifier::UpdateAlarm() {
diff --git a/wpilibc/src/main/native/cpp/PS4Controller.cpp b/wpilibc/src/main/native/cpp/PS4Controller.cpp
index 91fd304..e59e18c 100644
--- a/wpilibc/src/main/native/cpp/PS4Controller.cpp
+++ b/wpilibc/src/main/native/cpp/PS4Controller.cpp
@@ -6,6 +6,8 @@
 
 #include <hal/FRCUsageReporting.h>
 
+#include "frc/event/BooleanEvent.h"
+
 using namespace frc;
 
 PS4Controller::PS4Controller(int port) : GenericHID(port) {
@@ -48,6 +50,10 @@
   return GetRawButtonReleased(Button::kSquare);
 }
 
+BooleanEvent PS4Controller::Square(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetSquareButton(); });
+}
+
 bool PS4Controller::GetCrossButton() const {
   return GetRawButton(Button::kCross);
 }
@@ -60,6 +66,10 @@
   return GetRawButtonReleased(Button::kCross);
 }
 
+BooleanEvent PS4Controller::Cross(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetCrossButton(); });
+}
+
 bool PS4Controller::GetCircleButton() const {
   return GetRawButton(Button::kCircle);
 }
@@ -72,6 +82,10 @@
   return GetRawButtonReleased(Button::kCircle);
 }
 
+BooleanEvent PS4Controller::Circle(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetCircleButton(); });
+}
+
 bool PS4Controller::GetTriangleButton() const {
   return GetRawButton(Button::kTriangle);
 }
@@ -84,6 +98,10 @@
   return GetRawButtonReleased(Button::kTriangle);
 }
 
+BooleanEvent PS4Controller::Triangle(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetTriangleButton(); });
+}
+
 bool PS4Controller::GetL1Button() const {
   return GetRawButton(Button::kL1);
 }
@@ -96,6 +114,10 @@
   return GetRawButtonReleased(Button::kL1);
 }
 
+BooleanEvent PS4Controller::L1(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetL1Button(); });
+}
+
 bool PS4Controller::GetR1Button() const {
   return GetRawButton(Button::kR1);
 }
@@ -108,6 +130,10 @@
   return GetRawButtonReleased(Button::kR1);
 }
 
+BooleanEvent PS4Controller::R1(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetR1Button(); });
+}
+
 bool PS4Controller::GetL2Button() const {
   return GetRawButton(Button::kL2);
 }
@@ -120,6 +146,10 @@
   return GetRawButtonReleased(Button::kL2);
 }
 
+BooleanEvent PS4Controller::L2(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetL2Button(); });
+}
+
 bool PS4Controller::GetR2Button() const {
   return GetRawButton(Button::kR2);
 }
@@ -132,6 +162,10 @@
   return GetRawButtonReleased(Button::kR2);
 }
 
+BooleanEvent PS4Controller::R2(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetR2Button(); });
+}
+
 bool PS4Controller::GetShareButton() const {
   return GetRawButton(Button::kShare);
 }
@@ -144,6 +178,10 @@
   return GetRawButtonReleased(Button::kShare);
 }
 
+BooleanEvent PS4Controller::Share(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetShareButton(); });
+}
+
 bool PS4Controller::GetOptionsButton() const {
   return GetRawButton(Button::kOptions);
 }
@@ -156,6 +194,10 @@
   return GetRawButtonReleased(Button::kOptions);
 }
 
+BooleanEvent PS4Controller::Options(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetOptionsButton(); });
+}
+
 bool PS4Controller::GetL3Button() const {
   return GetRawButton(Button::kL3);
 }
@@ -168,6 +210,10 @@
   return GetRawButtonReleased(Button::kL3);
 }
 
+BooleanEvent PS4Controller::L3(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetL3Button(); });
+}
+
 bool PS4Controller::GetR3Button() const {
   return GetRawButton(Button::kR3);
 }
@@ -180,6 +226,10 @@
   return GetRawButtonReleased(Button::kR3);
 }
 
+BooleanEvent PS4Controller::R3(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetR3Button(); });
+}
+
 bool PS4Controller::GetPSButton() const {
   return GetRawButton(Button::kPS);
 }
@@ -192,6 +242,10 @@
   return GetRawButtonReleased(Button::kPS);
 }
 
+BooleanEvent PS4Controller::PS(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetPSButton(); });
+}
+
 bool PS4Controller::GetTouchpad() const {
   return GetRawButton(Button::kTouchpad);
 }
@@ -203,3 +257,7 @@
 bool PS4Controller::GetTouchpadReleased() {
   return GetRawButtonReleased(Button::kTouchpad);
 }
+
+BooleanEvent PS4Controller::Touchpad(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetTouchpad(); });
+}
diff --git a/wpilibc/src/main/native/cpp/PWM.cpp b/wpilibc/src/main/native/cpp/PWM.cpp
index 393accc..5c5e6f5 100644
--- a/wpilibc/src/main/native/cpp/PWM.cpp
+++ b/wpilibc/src/main/native/cpp/PWM.cpp
@@ -117,7 +117,7 @@
       break;
     default:
       throw FRC_MakeError(err::InvalidParameter, "PeriodMultiplier value {}",
-                          mult);
+                          static_cast<int>(mult));
   }
 
   FRC_CheckErrorStatus(status, "Channel {}", m_channel);
@@ -166,7 +166,8 @@
 void PWM::InitSendable(wpi::SendableBuilder& builder) {
   builder.SetSmartDashboardType("PWM");
   builder.SetActuator(true);
-  builder.SetSafeState([=] { SetDisabled(); });
+  builder.SetSafeState([=, this] { SetDisabled(); });
   builder.AddDoubleProperty(
-      "Value", [=] { return GetRaw(); }, [=](double value) { SetRaw(value); });
+      "Value", [=, this] { return GetRaw(); },
+      [=, this](double value) { SetRaw(value); });
 }
diff --git a/wpilibc/src/main/native/cpp/PneumaticHub.cpp b/wpilibc/src/main/native/cpp/PneumaticHub.cpp
index 0f32e1e..ec008cf 100644
--- a/wpilibc/src/main/native/cpp/PneumaticHub.cpp
+++ b/wpilibc/src/main/native/cpp/PneumaticHub.cpp
@@ -4,6 +4,8 @@
 
 #include "frc/PneumaticHub.h"
 
+#include <array>
+
 #include <fmt/format.h>
 #include <hal/REVPH.h>
 #include <wpi/NullDeleter.h>
@@ -146,7 +148,7 @@
     units::pounds_per_square_inch_t minPressure,
     units::pounds_per_square_inch_t maxPressure) {
   if (minPressure >= maxPressure) {
-    throw FRC_MakeError(err::InvalidParameter, "{}",
+    throw FRC_MakeError(err::InvalidParameter,
                         "maxPressure must be greater than minPresure");
   }
   if (minPressure < 0_psi || minPressure > 120_psi) {
@@ -171,7 +173,7 @@
     units::pounds_per_square_inch_t minPressure,
     units::pounds_per_square_inch_t maxPressure) {
   if (minPressure >= maxPressure) {
-    throw FRC_MakeError(err::InvalidParameter, "{}",
+    throw FRC_MakeError(err::InvalidParameter,
                         "maxPressure must be greater than minPresure");
   }
   if (minPressure < 0_psi || minPressure > 120_psi) {
diff --git a/wpilibc/src/main/native/cpp/PowerDistribution.cpp b/wpilibc/src/main/native/cpp/PowerDistribution.cpp
index 21101f2..ba4cb38 100644
--- a/wpilibc/src/main/native/cpp/PowerDistribution.cpp
+++ b/wpilibc/src/main/native/cpp/PowerDistribution.cpp
@@ -195,7 +195,7 @@
   for (int i = 0; i < numChannels; ++i) {
     builder.AddDoubleProperty(
         fmt::format("Chan{}", i),
-        [=] {
+        [=, this] {
           int32_t lStatus = 0;
           return HAL_GetPowerDistributionChannelCurrent(m_handle, i, &lStatus);
         },
@@ -203,25 +203,25 @@
   }
   builder.AddDoubleProperty(
       "Voltage",
-      [=] {
+      [=, this] {
         int32_t lStatus = 0;
         return HAL_GetPowerDistributionVoltage(m_handle, &lStatus);
       },
       nullptr);
   builder.AddDoubleProperty(
       "TotalCurrent",
-      [=] {
+      [=, this] {
         int32_t lStatus = 0;
         return HAL_GetPowerDistributionTotalCurrent(m_handle, &lStatus);
       },
       nullptr);
   builder.AddBooleanProperty(
       "SwitchableChannel",
-      [=] {
+      [=, this] {
         int32_t lStatus = 0;
         return HAL_GetPowerDistributionSwitchableChannel(m_handle, &lStatus);
       },
-      [=](bool value) {
+      [=, this](bool value) {
         int32_t lStatus = 0;
         HAL_SetPowerDistributionSwitchableChannel(m_handle, value, &lStatus);
       });
diff --git a/wpilibc/src/main/native/cpp/Preferences.cpp b/wpilibc/src/main/native/cpp/Preferences.cpp
index 4663875..cfb5964 100644
--- a/wpilibc/src/main/native/cpp/Preferences.cpp
+++ b/wpilibc/src/main/native/cpp/Preferences.cpp
@@ -6,9 +6,13 @@
 
 #include <algorithm>
 
+#include <fmt/format.h>
 #include <hal/FRCUsageReporting.h>
+#include <networktables/MultiSubscriber.h>
 #include <networktables/NetworkTable.h>
 #include <networktables/NetworkTableInstance.h>
+#include <networktables/NetworkTableListener.h>
+#include <networktables/StringTopic.h>
 
 using namespace frc;
 
@@ -21,7 +25,10 @@
 
   std::shared_ptr<nt::NetworkTable> table{
       nt::NetworkTableInstance::GetDefault().GetTable(kTableName)};
-  NT_EntryListener listener;
+  nt::StringPublisher typePublisher{table->GetStringTopic(".type").Publish()};
+  nt::MultiSubscriber tableSubscriber{nt::NetworkTableInstance::GetDefault(),
+                                      {{fmt::format("{}/", table->GetPath())}}};
+  nt::NetworkTableListener listener;
 };
 }  // namespace
 
@@ -38,40 +45,33 @@
 }  // namespace frc::impl
 #endif
 
-Preferences* Preferences::GetInstance() {
-  ::GetInstance();
-  static Preferences instance;
-  return &instance;
-}
-
 std::vector<std::string> Preferences::GetKeys() {
   return ::GetInstance().table->GetKeys();
 }
 
 std::string Preferences::GetString(std::string_view key,
                                    std::string_view defaultValue) {
-  return ::GetInstance().table->GetString(key, defaultValue);
+  return ::GetInstance().table->GetEntry(key).GetString(defaultValue);
 }
 
 int Preferences::GetInt(std::string_view key, int defaultValue) {
-  return static_cast<int>(::GetInstance().table->GetNumber(key, defaultValue));
+  return ::GetInstance().table->GetEntry(key).GetInteger(defaultValue);
 }
 
 double Preferences::GetDouble(std::string_view key, double defaultValue) {
-  return ::GetInstance().table->GetNumber(key, defaultValue);
+  return ::GetInstance().table->GetEntry(key).GetDouble(defaultValue);
 }
 
 float Preferences::GetFloat(std::string_view key, float defaultValue) {
-  return ::GetInstance().table->GetNumber(key, defaultValue);
+  return ::GetInstance().table->GetEntry(key).GetFloat(defaultValue);
 }
 
 bool Preferences::GetBoolean(std::string_view key, bool defaultValue) {
-  return ::GetInstance().table->GetBoolean(key, defaultValue);
+  return ::GetInstance().table->GetEntry(key).GetBoolean(defaultValue);
 }
 
 int64_t Preferences::GetLong(std::string_view key, int64_t defaultValue) {
-  return static_cast<int64_t>(
-      ::GetInstance().table->GetNumber(key, defaultValue));
+  return ::GetInstance().table->GetEntry(key).GetInteger(defaultValue);
 }
 
 void Preferences::SetString(std::string_view key, std::string_view value) {
@@ -80,10 +80,6 @@
   entry.SetPersistent();
 }
 
-void Preferences::PutString(std::string_view key, std::string_view value) {
-  SetString(key, value);
-}
-
 void Preferences::InitString(std::string_view key, std::string_view value) {
   auto entry = ::GetInstance().table->GetEntry(key);
   entry.SetDefaultString(value);
@@ -92,17 +88,13 @@
 
 void Preferences::SetInt(std::string_view key, int value) {
   auto entry = ::GetInstance().table->GetEntry(key);
-  entry.SetDouble(value);
+  entry.SetInteger(value);
   entry.SetPersistent();
 }
 
-void Preferences::PutInt(std::string_view key, int value) {
-  SetInt(key, value);
-}
-
 void Preferences::InitInt(std::string_view key, int value) {
   auto entry = ::GetInstance().table->GetEntry(key);
-  entry.SetDefaultDouble(value);
+  entry.SetDefaultInteger(value);
   entry.SetPersistent();
 }
 
@@ -112,10 +104,6 @@
   entry.SetPersistent();
 }
 
-void Preferences::PutDouble(std::string_view key, double value) {
-  SetDouble(key, value);
-}
-
 void Preferences::InitDouble(std::string_view key, double value) {
   auto entry = ::GetInstance().table->GetEntry(key);
   entry.SetDefaultDouble(value);
@@ -124,17 +112,13 @@
 
 void Preferences::SetFloat(std::string_view key, float value) {
   auto entry = ::GetInstance().table->GetEntry(key);
-  entry.SetDouble(value);
+  entry.SetFloat(value);
   entry.SetPersistent();
 }
 
-void Preferences::PutFloat(std::string_view key, float value) {
-  SetFloat(key, value);
-}
-
 void Preferences::InitFloat(std::string_view key, float value) {
   auto entry = ::GetInstance().table->GetEntry(key);
-  entry.SetDefaultDouble(value);
+  entry.SetDefaultFloat(value);
   entry.SetPersistent();
 }
 
@@ -144,10 +128,6 @@
   entry.SetPersistent();
 }
 
-void Preferences::PutBoolean(std::string_view key, bool value) {
-  SetBoolean(key, value);
-}
-
 void Preferences::InitBoolean(std::string_view key, bool value) {
   auto entry = ::GetInstance().table->GetEntry(key);
   entry.SetDefaultBoolean(value);
@@ -156,17 +136,13 @@
 
 void Preferences::SetLong(std::string_view key, int64_t value) {
   auto entry = ::GetInstance().table->GetEntry(key);
-  entry.SetDouble(value);
+  entry.SetInteger(value);
   entry.SetPersistent();
 }
 
-void Preferences::PutLong(std::string_view key, int64_t value) {
-  SetLong(key, value);
-}
-
 void Preferences::InitLong(std::string_view key, int64_t value) {
   auto entry = ::GetInstance().table->GetEntry(key);
-  entry.SetDefaultDouble(value);
+  entry.SetDefaultInteger(value);
   entry.SetPersistent();
 }
 
@@ -175,7 +151,9 @@
 }
 
 void Preferences::Remove(std::string_view key) {
-  ::GetInstance().table->Delete(key);
+  auto entry = ::GetInstance().table->GetEntry(key);
+  entry.ClearPersistent();
+  entry.Unpublish();
 }
 
 void Preferences::RemoveAll() {
@@ -187,11 +165,15 @@
 }
 
 Instance::Instance() {
-  table->GetEntry(".type").SetString("RobotPreferences");
-  listener = table->AddEntryListener(
-      [=](nt::NetworkTable* table, std::string_view name,
-          nt::NetworkTableEntry entry, std::shared_ptr<nt::Value> value,
-          int flags) { entry.SetPersistent(); },
-      NT_NOTIFY_NEW | NT_NOTIFY_IMMEDIATE);
+  typePublisher.Set("RobotPreferences");
+  listener = nt::NetworkTableListener::CreateListener(
+      tableSubscriber, NT_EVENT_PUBLISH | NT_EVENT_IMMEDIATE,
+      [typeTopic = typePublisher.GetTopic().GetHandle()](auto& event) {
+        if (auto topicInfo = event.GetTopicInfo()) {
+          if (topicInfo->topic != typeTopic) {
+            nt::SetTopicPersistent(topicInfo->topic, true);
+          }
+        }
+      });
   HAL_Report(HALUsageReporting::kResourceType_Preferences, 0);
 }
diff --git a/wpilibc/src/main/native/cpp/Relay.cpp b/wpilibc/src/main/native/cpp/Relay.cpp
index 9bec566..3dfbe64 100644
--- a/wpilibc/src/main/native/cpp/Relay.cpp
+++ b/wpilibc/src/main/native/cpp/Relay.cpp
@@ -175,10 +175,10 @@
 void Relay::InitSendable(wpi::SendableBuilder& builder) {
   builder.SetSmartDashboardType("Relay");
   builder.SetActuator(true);
-  builder.SetSafeState([=] { Set(kOff); });
+  builder.SetSafeState([=, this] { Set(kOff); });
   builder.AddSmallStringProperty(
       "Value",
-      [=](wpi::SmallVectorImpl<char>& buf) -> std::string_view {
+      [=, this](wpi::SmallVectorImpl<char>& buf) -> std::string_view {
         switch (Get()) {
           case kOn:
             return "On";
@@ -190,7 +190,7 @@
             return "Off";
         }
       },
-      [=](std::string_view value) {
+      [=, this](std::string_view value) {
         if (value == "Off") {
           Set(kOff);
         } else if (value == "Forward") {
diff --git a/wpilibc/src/main/native/cpp/RobotController.cpp b/wpilibc/src/main/native/cpp/RobotController.cpp
index 8bc2d6b..da6e364 100644
--- a/wpilibc/src/main/native/cpp/RobotController.cpp
+++ b/wpilibc/src/main/native/cpp/RobotController.cpp
@@ -4,6 +4,8 @@
 
 #include "frc/RobotController.h"
 
+#include <cstddef>
+
 #include <hal/CAN.h>
 #include <hal/HALBase.h>
 #include <hal/Power.h>
@@ -15,161 +17,174 @@
 int RobotController::GetFPGAVersion() {
   int32_t status = 0;
   int version = HAL_GetFPGAVersion(&status);
-  FRC_CheckErrorStatus(status, "{}", "GetFPGAVersion");
+  FRC_CheckErrorStatus(status, "GetFPGAVersion");
   return version;
 }
 
 int64_t RobotController::GetFPGARevision() {
   int32_t status = 0;
   int64_t revision = HAL_GetFPGARevision(&status);
-  FRC_CheckErrorStatus(status, "{}", "GetFPGARevision");
+  FRC_CheckErrorStatus(status, "GetFPGARevision");
   return revision;
 }
 
+std::string RobotController::GetSerialNumber() {
+  // Serial number is 8 characters
+  char serialNum[9];
+  size_t len = HAL_GetSerialNumber(serialNum, sizeof(serialNum));
+  return std::string(serialNum, len);
+}
+
+std::string RobotController::GetComments() {
+  char comments[65];
+  size_t len = HAL_GetComments(comments, sizeof(comments));
+  return std::string(comments, len);
+}
+
 uint64_t RobotController::GetFPGATime() {
   int32_t status = 0;
   uint64_t time = HAL_GetFPGATime(&status);
-  FRC_CheckErrorStatus(status, "{}", "GetFPGATime");
+  FRC_CheckErrorStatus(status, "GetFPGATime");
   return time;
 }
 
 bool RobotController::GetUserButton() {
   int32_t status = 0;
   bool value = HAL_GetFPGAButton(&status);
-  FRC_CheckErrorStatus(status, "{}", "GetUserButton");
+  FRC_CheckErrorStatus(status, "GetUserButton");
   return value;
 }
 
 units::volt_t RobotController::GetBatteryVoltage() {
   int32_t status = 0;
   double retVal = HAL_GetVinVoltage(&status);
-  FRC_CheckErrorStatus(status, "{}", "GetBatteryVoltage");
+  FRC_CheckErrorStatus(status, "GetBatteryVoltage");
   return units::volt_t{retVal};
 }
 
 bool RobotController::IsSysActive() {
   int32_t status = 0;
   bool retVal = HAL_GetSystemActive(&status);
-  FRC_CheckErrorStatus(status, "{}", "IsSysActive");
+  FRC_CheckErrorStatus(status, "IsSysActive");
   return retVal;
 }
 
 bool RobotController::IsBrownedOut() {
   int32_t status = 0;
   bool retVal = HAL_GetBrownedOut(&status);
-  FRC_CheckErrorStatus(status, "{}", "IsBrownedOut");
+  FRC_CheckErrorStatus(status, "IsBrownedOut");
   return retVal;
 }
 
 double RobotController::GetInputVoltage() {
   int32_t status = 0;
   double retVal = HAL_GetVinVoltage(&status);
-  FRC_CheckErrorStatus(status, "{}", "GetInputVoltage");
+  FRC_CheckErrorStatus(status, "GetInputVoltage");
   return retVal;
 }
 
 double RobotController::GetInputCurrent() {
   int32_t status = 0;
   double retVal = HAL_GetVinCurrent(&status);
-  FRC_CheckErrorStatus(status, "{}", "GetInputCurrent");
+  FRC_CheckErrorStatus(status, "GetInputCurrent");
   return retVal;
 }
 
 double RobotController::GetVoltage3V3() {
   int32_t status = 0;
   double retVal = HAL_GetUserVoltage3V3(&status);
-  FRC_CheckErrorStatus(status, "{}", "GetVoltage3V3");
+  FRC_CheckErrorStatus(status, "GetVoltage3V3");
   return retVal;
 }
 
 double RobotController::GetCurrent3V3() {
   int32_t status = 0;
   double retVal = HAL_GetUserCurrent3V3(&status);
-  FRC_CheckErrorStatus(status, "{}", "GetCurrent3V3");
+  FRC_CheckErrorStatus(status, "GetCurrent3V3");
   return retVal;
 }
 
 bool RobotController::GetEnabled3V3() {
   int32_t status = 0;
   bool retVal = HAL_GetUserActive3V3(&status);
-  FRC_CheckErrorStatus(status, "{}", "GetEnabled3V3");
+  FRC_CheckErrorStatus(status, "GetEnabled3V3");
   return retVal;
 }
 
 int RobotController::GetFaultCount3V3() {
   int32_t status = 0;
   int retVal = HAL_GetUserCurrentFaults3V3(&status);
-  FRC_CheckErrorStatus(status, "{}", "GetFaultCount3V3");
+  FRC_CheckErrorStatus(status, "GetFaultCount3V3");
   return retVal;
 }
 
 double RobotController::GetVoltage5V() {
   int32_t status = 0;
   double retVal = HAL_GetUserVoltage5V(&status);
-  FRC_CheckErrorStatus(status, "{}", "GetVoltage5V");
+  FRC_CheckErrorStatus(status, "GetVoltage5V");
   return retVal;
 }
 
 double RobotController::GetCurrent5V() {
   int32_t status = 0;
   double retVal = HAL_GetUserCurrent5V(&status);
-  FRC_CheckErrorStatus(status, "{}", "GetCurrent5V");
+  FRC_CheckErrorStatus(status, "GetCurrent5V");
   return retVal;
 }
 
 bool RobotController::GetEnabled5V() {
   int32_t status = 0;
   bool retVal = HAL_GetUserActive5V(&status);
-  FRC_CheckErrorStatus(status, "{}", "GetEnabled5V");
+  FRC_CheckErrorStatus(status, "GetEnabled5V");
   return retVal;
 }
 
 int RobotController::GetFaultCount5V() {
   int32_t status = 0;
   int retVal = HAL_GetUserCurrentFaults5V(&status);
-  FRC_CheckErrorStatus(status, "{}", "GetFaultCount5V");
+  FRC_CheckErrorStatus(status, "GetFaultCount5V");
   return retVal;
 }
 
 double RobotController::GetVoltage6V() {
   int32_t status = 0;
   double retVal = HAL_GetUserVoltage6V(&status);
-  FRC_CheckErrorStatus(status, "{}", "GetVoltage6V");
+  FRC_CheckErrorStatus(status, "GetVoltage6V");
   return retVal;
 }
 
 double RobotController::GetCurrent6V() {
   int32_t status = 0;
   double retVal = HAL_GetUserCurrent6V(&status);
-  FRC_CheckErrorStatus(status, "{}", "GetCurrent6V");
+  FRC_CheckErrorStatus(status, "GetCurrent6V");
   return retVal;
 }
 
 bool RobotController::GetEnabled6V() {
   int32_t status = 0;
   bool retVal = HAL_GetUserActive6V(&status);
-  FRC_CheckErrorStatus(status, "{}", "GetEnabled6V");
+  FRC_CheckErrorStatus(status, "GetEnabled6V");
   return retVal;
 }
 
 int RobotController::GetFaultCount6V() {
   int32_t status = 0;
   int retVal = HAL_GetUserCurrentFaults6V(&status);
-  FRC_CheckErrorStatus(status, "{}", "GetFaultCount6V");
+  FRC_CheckErrorStatus(status, "GetFaultCount6V");
   return retVal;
 }
 
 units::volt_t RobotController::GetBrownoutVoltage() {
   int32_t status = 0;
   double retVal = HAL_GetBrownoutVoltage(&status);
-  FRC_CheckErrorStatus(status, "{}", "GetBrownoutVoltage");
-  return units::volt_t(retVal);
+  FRC_CheckErrorStatus(status, "GetBrownoutVoltage");
+  return units::volt_t{retVal};
 }
 
 void RobotController::SetBrownoutVoltage(units::volt_t brownoutVoltage) {
   int32_t status = 0;
   HAL_SetBrownoutVoltage(brownoutVoltage.value(), &status);
-  FRC_CheckErrorStatus(status, "{}", "SetBrownoutVoltage");
+  FRC_CheckErrorStatus(status, "SetBrownoutVoltage");
 }
 
 CANStatus RobotController::GetCANStatus() {
@@ -181,7 +196,7 @@
   uint32_t transmitErrorCount = 0;
   HAL_CAN_GetCANStatus(&percentBusUtilization, &busOffCount, &txFullCount,
                        &receiveErrorCount, &transmitErrorCount, &status);
-  FRC_CheckErrorStatus(status, "{}", "GetCANStatus");
+  FRC_CheckErrorStatus(status, "GetCANStatus");
   return {percentBusUtilization, static_cast<int>(busOffCount),
           static_cast<int>(txFullCount), static_cast<int>(receiveErrorCount),
           static_cast<int>(transmitErrorCount)};
diff --git a/wpilibc/src/main/native/cpp/RobotState.cpp b/wpilibc/src/main/native/cpp/RobotState.cpp
index 651a644..ede314c 100644
--- a/wpilibc/src/main/native/cpp/RobotState.cpp
+++ b/wpilibc/src/main/native/cpp/RobotState.cpp
@@ -20,10 +20,6 @@
   return DriverStation::IsEStopped();
 }
 
-bool RobotState::IsOperatorControl() {
-  return IsTeleop();
-}
-
 bool RobotState::IsTeleop() {
   return DriverStation::IsTeleop();
 }
diff --git a/wpilibc/src/main/native/cpp/SPI.cpp b/wpilibc/src/main/native/cpp/SPI.cpp
index bdcee45..29cd006 100644
--- a/wpilibc/src/main/native/cpp/SPI.cpp
+++ b/wpilibc/src/main/native/cpp/SPI.cpp
@@ -25,7 +25,7 @@
  public:
   Accumulator(HAL_SPIPort port, int xferSize, int validMask, int validValue,
               int dataShift, int dataSize, bool isSigned, bool bigEndian)
-      : m_notifier([=] {
+      : m_notifier([=, this] {
           std::scoped_lock lock(m_mutex);
           Update();
         }),
@@ -77,7 +77,7 @@
     // get amount of data available
     int32_t numToRead =
         HAL_ReadSPIAutoReceivedData(m_port, m_buf, 0, 0, &status);
-    FRC_CheckErrorStatus(status, "Port {}", m_port);
+    FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
 
     // only get whole responses; +1 is for timestamp
     numToRead -= numToRead % m_xferSize;
@@ -91,7 +91,7 @@
 
     // read buffered data
     HAL_ReadSPIAutoReceivedData(m_port, m_buf, numToRead, 0, &status);
-    FRC_CheckErrorStatus(status, "Port {}", m_port);
+    FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
 
     // loop over all responses
     for (int32_t off = 0; off < numToRead; off += m_xferSize) {
@@ -158,7 +158,8 @@
 SPI::SPI(Port port) : m_port(static_cast<HAL_SPIPort>(port)) {
   int32_t status = 0;
   HAL_InitializeSPI(m_port, &status);
-  FRC_CheckErrorStatus(status, "Port {}", m_port);
+  HAL_SetSPIMode(m_port, m_mode);
+  FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
 
   HAL_Report(HALUsageReporting::kResourceType_SPI,
              static_cast<uint8_t>(port) + 1);
@@ -177,55 +178,58 @@
 }
 
 void SPI::SetMSBFirst() {
-  m_msbFirst = true;
-  HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
+  FRC_ReportError(1, "SetMSBFirst not supported by roboRIO {}",
+                  static_cast<int>(m_port));
 }
 
 void SPI::SetLSBFirst() {
-  m_msbFirst = false;
-  HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
+  FRC_ReportError(1, "SetLSBFirst not supported by roboRIO {}",
+                  static_cast<int>(m_port));
 }
 
 void SPI::SetSampleDataOnLeadingEdge() {
-  m_sampleOnTrailing = false;
-  HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
+  int mode = m_mode;
+  mode &= 2;
+  m_mode = static_cast<HAL_SPIMode>(mode);
+  HAL_SetSPIMode(m_port, m_mode);
 }
 
 void SPI::SetSampleDataOnTrailingEdge() {
-  m_sampleOnTrailing = true;
-  HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
-}
-
-void SPI::SetSampleDataOnFalling() {
-  m_sampleOnTrailing = true;
-  HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
-}
-
-void SPI::SetSampleDataOnRising() {
-  m_sampleOnTrailing = false;
-  HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
+  int mode = m_mode;
+  mode |= 2;
+  m_mode = static_cast<HAL_SPIMode>(mode);
+  HAL_SetSPIMode(m_port, m_mode);
 }
 
 void SPI::SetClockActiveLow() {
-  m_clockIdleHigh = true;
-  HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
+  int mode = m_mode;
+  mode |= 1;
+  m_mode = static_cast<HAL_SPIMode>(mode);
+  HAL_SetSPIMode(m_port, m_mode);
 }
 
 void SPI::SetClockActiveHigh() {
-  m_clockIdleHigh = false;
-  HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
+  int mode = m_mode;
+  mode &= 1;
+  m_mode = static_cast<HAL_SPIMode>(mode);
+  HAL_SetSPIMode(m_port, m_mode);
+}
+
+void SPI::SetMode(Mode mode) {
+  m_mode = static_cast<HAL_SPIMode>(mode & 0x3);
+  HAL_SetSPIMode(m_port, m_mode);
 }
 
 void SPI::SetChipSelectActiveHigh() {
   int32_t status = 0;
   HAL_SetSPIChipSelectActiveHigh(m_port, &status);
-  FRC_CheckErrorStatus(status, "Port {}", m_port);
+  FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
 }
 
 void SPI::SetChipSelectActiveLow() {
   int32_t status = 0;
   HAL_SetSPIChipSelectActiveLow(m_port, &status);
-  FRC_CheckErrorStatus(status, "Port {}", m_port);
+  FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
 }
 
 int SPI::Write(uint8_t* data, int size) {
@@ -255,31 +259,27 @@
 void SPI::InitAuto(int bufferSize) {
   int32_t status = 0;
   HAL_InitSPIAuto(m_port, bufferSize, &status);
-  FRC_CheckErrorStatus(status, "Port {}", m_port);
+  FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
 }
 
 void SPI::FreeAuto() {
   int32_t status = 0;
   HAL_FreeSPIAuto(m_port, &status);
-  FRC_CheckErrorStatus(status, "Port {}", m_port);
+  FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
 }
 
-void SPI::SetAutoTransmitData(wpi::span<const uint8_t> dataToSend,
+void SPI::SetAutoTransmitData(std::span<const uint8_t> dataToSend,
                               int zeroSize) {
   int32_t status = 0;
   HAL_SetSPIAutoTransmitData(m_port, dataToSend.data(), dataToSend.size(),
                              zeroSize, &status);
-  FRC_CheckErrorStatus(status, "Port {}", m_port);
+  FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
 }
 
 void SPI::StartAutoRate(units::second_t period) {
   int32_t status = 0;
   HAL_StartSPIAutoRate(m_port, period.value(), &status);
-  FRC_CheckErrorStatus(status, "Port {}", m_port);
-}
-
-void SPI::StartAutoRate(double period) {
-  StartAutoRate(units::second_t(period));
+  FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
 }
 
 void SPI::StartAutoTrigger(DigitalSource& source, bool rising, bool falling) {
@@ -288,19 +288,19 @@
                           static_cast<HAL_AnalogTriggerType>(
                               source.GetAnalogTriggerTypeForRouting()),
                           rising, falling, &status);
-  FRC_CheckErrorStatus(status, "Port {}", m_port);
+  FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
 }
 
 void SPI::StopAuto() {
   int32_t status = 0;
   HAL_StopSPIAuto(m_port, &status);
-  FRC_CheckErrorStatus(status, "Port {}", m_port);
+  FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
 }
 
 void SPI::ForceAutoRead() {
   int32_t status = 0;
   HAL_ForceSPIAutoRead(m_port, &status);
-  FRC_CheckErrorStatus(status, "Port {}", m_port);
+  FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
 }
 
 int SPI::ReadAutoReceivedData(uint32_t* buffer, int numToRead,
@@ -308,14 +308,14 @@
   int32_t status = 0;
   int32_t val = HAL_ReadSPIAutoReceivedData(m_port, buffer, numToRead,
                                             timeout.value(), &status);
-  FRC_CheckErrorStatus(status, "Port {}", m_port);
+  FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
   return val;
 }
 
 int SPI::GetAutoDroppedCount() {
   int32_t status = 0;
   int32_t val = HAL_GetSPIAutoDroppedCount(m_port, &status);
-  FRC_CheckErrorStatus(status, "Port {}", m_port);
+  FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
   return val;
 }
 
@@ -324,7 +324,7 @@
   int32_t status = 0;
   HAL_ConfigureSPIAutoStall(m_port, csToSclkTicks, stallTicks, pow2BytesPerRead,
                             &status);
-  FRC_CheckErrorStatus(status, "Port {}", m_port);
+  FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
 }
 
 void SPI::InitAccumulator(units::second_t period, int cmd, int xferSize,
@@ -355,13 +355,6 @@
   m_accum->m_notifier.StartPeriodic(period * kAccumulateDepth / 2);
 }
 
-void SPI::InitAccumulator(double period, int cmd, int xferSize, int validMask,
-                          int validValue, int dataShift, int dataSize,
-                          bool isSigned, bool bigEndian) {
-  InitAccumulator(units::second_t(period), cmd, xferSize, validMask, validValue,
-                  dataShift, dataSize, isSigned, bigEndian);
-}
-
 void SPI::FreeAccumulator() {
   m_accum.reset(nullptr);
   FreeAuto();
diff --git a/wpilibc/src/main/native/cpp/SerialPort.cpp b/wpilibc/src/main/native/cpp/SerialPort.cpp
index fb984f1..698a440 100644
--- a/wpilibc/src/main/native/cpp/SerialPort.cpp
+++ b/wpilibc/src/main/native/cpp/SerialPort.cpp
@@ -20,15 +20,16 @@
 
   m_portHandle =
       HAL_InitializeSerialPort(static_cast<HAL_SerialPort>(port), &status);
-  FRC_CheckErrorStatus(status, "Port {}", port);
+  FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(port));
   HAL_SetSerialBaudRate(m_portHandle, baudRate, &status);
   FRC_CheckErrorStatus(status, "SetSerialBaudRate {}", baudRate);
   HAL_SetSerialDataBits(m_portHandle, dataBits, &status);
   FRC_CheckErrorStatus(status, "SetSerialDataBits {}", dataBits);
   HAL_SetSerialParity(m_portHandle, parity, &status);
-  FRC_CheckErrorStatus(status, "SetSerialParity {}", parity);
+  FRC_CheckErrorStatus(status, "SetSerialParity {}", static_cast<int>(parity));
   HAL_SetSerialStopBits(m_portHandle, stopBits, &status);
-  FRC_CheckErrorStatus(status, "SetSerialStopBits {}", stopBits);
+  FRC_CheckErrorStatus(status, "SetSerialStopBits {}",
+                       static_cast<int>(stopBits));
 
   // Set the default timeout to 5 seconds.
   SetTimeout(5_s);
@@ -50,15 +51,16 @@
   m_portHandle =
       HAL_InitializeSerialPortDirect(static_cast<HAL_SerialPort>(port),
                                      std::string(portName).c_str(), &status);
-  FRC_CheckErrorStatus(status, "Port {}", port);
+  FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(port));
   HAL_SetSerialBaudRate(m_portHandle, baudRate, &status);
   FRC_CheckErrorStatus(status, "SetSerialBaudRate {}", baudRate);
   HAL_SetSerialDataBits(m_portHandle, dataBits, &status);
   FRC_CheckErrorStatus(status, "SetSerialDataBits {}", dataBits);
   HAL_SetSerialParity(m_portHandle, parity, &status);
-  FRC_CheckErrorStatus(status, "SetSerialParity {}", parity);
+  FRC_CheckErrorStatus(status, "SetSerialParity {}", static_cast<int>(parity));
   HAL_SetSerialStopBits(m_portHandle, stopBits, &status);
-  FRC_CheckErrorStatus(status, "SetSerialStopBits {}", stopBits);
+  FRC_CheckErrorStatus(status, "SetSerialStopBits {}",
+                       static_cast<int>(stopBits));
 
   // Set the default timeout to 5 seconds.
   SetTimeout(5_s);
@@ -75,13 +77,14 @@
 SerialPort::~SerialPort() {
   int32_t status = 0;
   HAL_CloseSerial(m_portHandle, &status);
-  FRC_ReportError(status, "{}", "CloseSerial");
+  FRC_ReportError(status, "CloseSerial");
 }
 
 void SerialPort::SetFlowControl(SerialPort::FlowControl flowControl) {
   int32_t status = 0;
   HAL_SetSerialFlowControl(m_portHandle, flowControl, &status);
-  FRC_CheckErrorStatus(status, "SetFlowControl {}", flowControl);
+  FRC_CheckErrorStatus(status, "SetFlowControl {}",
+                       static_cast<int>(flowControl));
 }
 
 void SerialPort::EnableTermination(char terminator) {
@@ -93,20 +96,20 @@
 void SerialPort::DisableTermination() {
   int32_t status = 0;
   HAL_DisableSerialTermination(m_portHandle, &status);
-  FRC_CheckErrorStatus(status, "{}", "DisableTermination");
+  FRC_CheckErrorStatus(status, "DisableTermination");
 }
 
 int SerialPort::GetBytesReceived() {
   int32_t status = 0;
   int retVal = HAL_GetSerialBytesReceived(m_portHandle, &status);
-  FRC_CheckErrorStatus(status, "{}", "GetBytesReceived");
+  FRC_CheckErrorStatus(status, "GetBytesReceived");
   return retVal;
 }
 
 int SerialPort::Read(char* buffer, int count) {
   int32_t status = 0;
   int retVal = HAL_ReadSerial(m_portHandle, buffer, count, &status);
-  FRC_CheckErrorStatus(status, "{}", "Read");
+  FRC_CheckErrorStatus(status, "Read");
   return retVal;
 }
 
@@ -118,14 +121,14 @@
   int32_t status = 0;
   int retVal =
       HAL_WriteSerial(m_portHandle, buffer.data(), buffer.size(), &status);
-  FRC_CheckErrorStatus(status, "{}", "Write");
+  FRC_CheckErrorStatus(status, "Write");
   return retVal;
 }
 
 void SerialPort::SetTimeout(units::second_t timeout) {
   int32_t status = 0;
   HAL_SetSerialTimeout(m_portHandle, timeout.value(), &status);
-  FRC_CheckErrorStatus(status, "{}", "SetTimeout");
+  FRC_CheckErrorStatus(status, "SetTimeout");
 }
 
 void SerialPort::SetReadBufferSize(int size) {
@@ -143,17 +146,17 @@
 void SerialPort::SetWriteBufferMode(SerialPort::WriteBufferMode mode) {
   int32_t status = 0;
   HAL_SetSerialWriteMode(m_portHandle, mode, &status);
-  FRC_CheckErrorStatus(status, "SetWriteBufferMode {}", mode);
+  FRC_CheckErrorStatus(status, "SetWriteBufferMode {}", static_cast<int>(mode));
 }
 
 void SerialPort::Flush() {
   int32_t status = 0;
   HAL_FlushSerial(m_portHandle, &status);
-  FRC_CheckErrorStatus(status, "{}", "Flush");
+  FRC_CheckErrorStatus(status, "Flush");
 }
 
 void SerialPort::Reset() {
   int32_t status = 0;
   HAL_ClearSerial(m_portHandle, &status);
-  FRC_CheckErrorStatus(status, "{}", "Reset");
+  FRC_CheckErrorStatus(status, "Reset");
 }
diff --git a/wpilibc/src/main/native/cpp/Servo.cpp b/wpilibc/src/main/native/cpp/Servo.cpp
index c410bff..4a292b9 100644
--- a/wpilibc/src/main/native/cpp/Servo.cpp
+++ b/wpilibc/src/main/native/cpp/Servo.cpp
@@ -64,7 +64,8 @@
 void Servo::InitSendable(wpi::SendableBuilder& builder) {
   builder.SetSmartDashboardType("Servo");
   builder.AddDoubleProperty(
-      "Value", [=] { return Get(); }, [=](double value) { Set(value); });
+      "Value", [=, this] { return Get(); },
+      [=, this](double value) { Set(value); });
 }
 
 double Servo::GetServoAngleRange() const {
diff --git a/wpilibc/src/main/native/cpp/Solenoid.cpp b/wpilibc/src/main/native/cpp/Solenoid.cpp
index 49000c2..819f79a 100644
--- a/wpilibc/src/main/native/cpp/Solenoid.cpp
+++ b/wpilibc/src/main/native/cpp/Solenoid.cpp
@@ -39,7 +39,9 @@
                channel} {}
 
 Solenoid::~Solenoid() {
-  m_module->UnreserveSolenoids(m_mask);
+  if (m_module) {
+    m_module->UnreserveSolenoids(m_mask);
+  }
 }
 
 void Solenoid::Set(bool on) {
@@ -75,7 +77,8 @@
 void Solenoid::InitSendable(wpi::SendableBuilder& builder) {
   builder.SetSmartDashboardType("Solenoid");
   builder.SetActuator(true);
-  builder.SetSafeState([=] { Set(false); });
+  builder.SetSafeState([=, this] { Set(false); });
   builder.AddBooleanProperty(
-      "Value", [=] { return Get(); }, [=](bool value) { Set(value); });
+      "Value", [=, this] { return Get(); },
+      [=, this](bool value) { Set(value); });
 }
diff --git a/wpilibc/src/main/native/cpp/SpeedControllerGroup.cpp b/wpilibc/src/main/native/cpp/SpeedControllerGroup.cpp
deleted file mode 100644
index c3704df..0000000
--- a/wpilibc/src/main/native/cpp/SpeedControllerGroup.cpp
+++ /dev/null
@@ -1,69 +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 "frc/SpeedControllerGroup.h"
-
-#include <wpi/sendable/SendableBuilder.h>
-#include <wpi/sendable/SendableRegistry.h>
-
-using namespace frc;
-
-// Can't use a delegated constructor here because of an MSVC bug.
-// https://developercommunity.visualstudio.com/content/problem/583/compiler-bug-with-delegating-a-constructor.html
-
-SpeedControllerGroup::SpeedControllerGroup(
-    std::vector<std::reference_wrapper<SpeedController>>&& speedControllers)
-    : m_speedControllers(std::move(speedControllers)) {
-  Initialize();
-}
-
-void SpeedControllerGroup::Initialize() {
-  for (auto& speedController : m_speedControllers) {
-    wpi::SendableRegistry::AddChild(this, &speedController.get());
-  }
-  static int instances = 0;
-  ++instances;
-  wpi::SendableRegistry::Add(this, "SpeedControllerGroup", instances);
-}
-
-void SpeedControllerGroup::Set(double speed) {
-  for (auto speedController : m_speedControllers) {
-    speedController.get().Set(m_isInverted ? -speed : speed);
-  }
-}
-
-double SpeedControllerGroup::Get() const {
-  if (!m_speedControllers.empty()) {
-    return m_speedControllers.front().get().Get() * (m_isInverted ? -1 : 1);
-  }
-  return 0.0;
-}
-
-void SpeedControllerGroup::SetInverted(bool isInverted) {
-  m_isInverted = isInverted;
-}
-
-bool SpeedControllerGroup::GetInverted() const {
-  return m_isInverted;
-}
-
-void SpeedControllerGroup::Disable() {
-  for (auto speedController : m_speedControllers) {
-    speedController.get().Disable();
-  }
-}
-
-void SpeedControllerGroup::StopMotor() {
-  for (auto speedController : m_speedControllers) {
-    speedController.get().StopMotor();
-  }
-}
-
-void SpeedControllerGroup::InitSendable(wpi::SendableBuilder& builder) {
-  builder.SetSmartDashboardType("Speed Controller");
-  builder.SetActuator(true);
-  builder.SetSafeState([=] { StopMotor(); });
-  builder.AddDoubleProperty(
-      "Value", [=] { return Get(); }, [=](double value) { Set(value); });
-}
diff --git a/wpilibc/src/main/native/cpp/SynchronousInterrupt.cpp b/wpilibc/src/main/native/cpp/SynchronousInterrupt.cpp
index 49acd84..a6ab212 100644
--- a/wpilibc/src/main/native/cpp/SynchronousInterrupt.cpp
+++ b/wpilibc/src/main/native/cpp/SynchronousInterrupt.cpp
@@ -21,7 +21,7 @@
 SynchronousInterrupt::SynchronousInterrupt(DigitalSource* source)
     : m_source{source, wpi::NullDeleter<DigitalSource>()} {
   if (m_source == nullptr) {
-    FRC_CheckErrorStatus(frc::err::NullParameter, "{}", "Source is null");
+    FRC_CheckErrorStatus(frc::err::NullParameter, "Source is null");
   } else {
     InitSynchronousInterrupt();
   }
@@ -30,7 +30,7 @@
     std::shared_ptr<DigitalSource> source)
     : m_source{std::move(source)} {
   if (m_source == nullptr) {
-    FRC_CheckErrorStatus(frc::err::NullParameter, "{}", "Source is null");
+    FRC_CheckErrorStatus(frc::err::NullParameter, "Source is null");
   } else {
     InitSynchronousInterrupt();
   }
@@ -39,14 +39,14 @@
 void SynchronousInterrupt::InitSynchronousInterrupt() {
   int32_t status = 0;
   m_handle = HAL_InitializeInterrupts(&status);
-  FRC_CheckErrorStatus(status, "{}", "Interrupt failed to initialize");
+  FRC_CheckErrorStatus(status, "Interrupt failed to initialize");
   HAL_RequestInterrupts(m_handle, m_source->GetPortHandleForRouting(),
                         static_cast<HAL_AnalogTriggerType>(
                             m_source->GetAnalogTriggerTypeForRouting()),
                         &status);
-  FRC_CheckErrorStatus(status, "{}", "Interrupt request failed");
+  FRC_CheckErrorStatus(status, "Interrupt request failed");
   HAL_SetInterruptUpSourceEdge(m_handle, true, false, &status);
-  FRC_CheckErrorStatus(status, "{}", "Interrupt setting up source edge failed");
+  FRC_CheckErrorStatus(status, "Interrupt setting up source edge failed");
 }
 
 SynchronousInterrupt::~SynchronousInterrupt() {
@@ -79,19 +79,19 @@
                                              bool fallingEdge) {
   int32_t status = 0;
   HAL_SetInterruptUpSourceEdge(m_handle, risingEdge, fallingEdge, &status);
-  FRC_CheckErrorStatus(status, "{}", "Interrupt setting edges failed");
+  FRC_CheckErrorStatus(status, "Interrupt setting edges failed");
 }
 
 void SynchronousInterrupt::WakeupWaitingInterrupt() {
   int32_t status = 0;
   HAL_ReleaseWaitingInterrupt(m_handle, &status);
-  FRC_CheckErrorStatus(status, "{}", "Interrupt wakeup failed");
+  FRC_CheckErrorStatus(status, "Interrupt wakeup failed");
 }
 
 units::second_t SynchronousInterrupt::GetRisingTimestamp() {
   int32_t status = 0;
   auto ts = HAL_ReadInterruptRisingTimestamp(m_handle, &status);
-  FRC_CheckErrorStatus(status, "{}", "Interrupt rising timestamp failed");
+  FRC_CheckErrorStatus(status, "Interrupt rising timestamp failed");
   units::microsecond_t ms{static_cast<double>(ts)};
   return ms;
 }
@@ -99,7 +99,7 @@
 units::second_t SynchronousInterrupt::GetFallingTimestamp() {
   int32_t status = 0;
   auto ts = HAL_ReadInterruptFallingTimestamp(m_handle, &status);
-  FRC_CheckErrorStatus(status, "{}", "Interrupt falling timestamp failed");
+  FRC_CheckErrorStatus(status, "Interrupt falling timestamp failed");
   units::microsecond_t ms{static_cast<double>(ts)};
   return ms;
 }
diff --git a/wpilibc/src/main/native/cpp/Threads.cpp b/wpilibc/src/main/native/cpp/Threads.cpp
index 2b9c151..3ecab55 100644
--- a/wpilibc/src/main/native/cpp/Threads.cpp
+++ b/wpilibc/src/main/native/cpp/Threads.cpp
@@ -16,7 +16,7 @@
   HAL_Bool rt = false;
   auto native = thread.native_handle();
   auto ret = HAL_GetThreadPriority(&native, &rt, &status);
-  FRC_CheckErrorStatus(status, "{}", "GetThreadPriority");
+  FRC_CheckErrorStatus(status, "GetThreadPriority");
   *isRealTime = rt;
   return ret;
 }
@@ -25,7 +25,7 @@
   int32_t status = 0;
   HAL_Bool rt = false;
   auto ret = HAL_GetCurrentThreadPriority(&rt, &status);
-  FRC_CheckErrorStatus(status, "{}", "GetCurrentThreadPriority");
+  FRC_CheckErrorStatus(status, "GetCurrentThreadPriority");
   *isRealTime = rt;
   return ret;
 }
@@ -34,14 +34,14 @@
   int32_t status = 0;
   auto native = thread.native_handle();
   auto ret = HAL_SetThreadPriority(&native, realTime, priority, &status);
-  FRC_CheckErrorStatus(status, "{}", "SetThreadPriority");
+  FRC_CheckErrorStatus(status, "SetThreadPriority");
   return ret;
 }
 
 bool SetCurrentThreadPriority(bool realTime, int priority) {
   int32_t status = 0;
   auto ret = HAL_SetCurrentThreadPriority(realTime, priority, &status);
-  FRC_CheckErrorStatus(status, "{}", "SetCurrentThreadPriority");
+  FRC_CheckErrorStatus(status, "SetCurrentThreadPriority");
   return ret;
 }
 
diff --git a/wpilibc/src/main/native/cpp/TimedRobot.cpp b/wpilibc/src/main/native/cpp/TimedRobot.cpp
index 93e6698..0b7b9cb 100644
--- a/wpilibc/src/main/native/cpp/TimedRobot.cpp
+++ b/wpilibc/src/main/native/cpp/TimedRobot.cpp
@@ -40,7 +40,7 @@
     HAL_UpdateNotifierAlarm(
         m_notifier, static_cast<uint64_t>(callback.expirationTime * 1e6),
         &status);
-    FRC_CheckErrorStatus(status, "{}", "UpdateNotifierAlarm");
+    FRC_CheckErrorStatus(status, "UpdateNotifierAlarm");
 
     uint64_t curTime = HAL_WaitForNotifierAlarm(m_notifier, &status);
     if (curTime == 0 || status != 0) {
@@ -70,15 +70,13 @@
   HAL_StopNotifier(m_notifier, &status);
 }
 
-TimedRobot::TimedRobot(double period) : TimedRobot(units::second_t(period)) {}
-
 TimedRobot::TimedRobot(units::second_t period) : IterativeRobotBase(period) {
   m_startTime = Timer::GetFPGATimestamp();
-  AddPeriodic([=] { LoopFunc(); }, period);
+  AddPeriodic([=, this] { LoopFunc(); }, period);
 
   int32_t status = 0;
   m_notifier = HAL_InitializeNotifier(&status);
-  FRC_CheckErrorStatus(status, "{}", "InitializeNotifier");
+  FRC_CheckErrorStatus(status, "InitializeNotifier");
   HAL_SetNotifierName(m_notifier, "TimedRobot", &status);
 
   HAL_Report(HALUsageReporting::kResourceType_Framework,
@@ -89,7 +87,7 @@
   int32_t status = 0;
 
   HAL_StopNotifier(m_notifier, &status);
-  FRC_ReportError(status, "{}", "StopNotifier");
+  FRC_ReportError(status, "StopNotifier");
 
   HAL_CleanNotifier(m_notifier, &status);
 }
diff --git a/wpilibc/src/main/native/cpp/Timer.cpp b/wpilibc/src/main/native/cpp/Timer.cpp
index bbd2262..3863de4 100644
--- a/wpilibc/src/main/native/cpp/Timer.cpp
+++ b/wpilibc/src/main/native/cpp/Timer.cpp
@@ -21,9 +21,9 @@
   using std::chrono::duration_cast;
   using std::chrono::system_clock;
 
-  return units::second_t(
+  return units::second_t{
       duration_cast<duration<double>>(system_clock::now().time_since_epoch())
-          .count());
+          .count()};
 }
 
 }  // namespace frc
@@ -65,10 +65,6 @@
   return Get() >= period;
 }
 
-bool Timer::HasPeriodPassed(units::second_t period) {
-  return AdvanceIfElapsed(period);
-}
-
 bool Timer::AdvanceIfElapsed(units::second_t period) {
   if (Get() >= period) {
     // Advance the start time by the period.
@@ -82,9 +78,9 @@
 
 units::second_t Timer::GetFPGATimestamp() {
   // FPGA returns the timestamp in microseconds
-  return units::second_t(frc::RobotController::GetFPGATime() * 1.0e-6);
+  return units::second_t{frc::RobotController::GetFPGATime() * 1.0e-6};
 }
 
 units::second_t Timer::GetMatchTime() {
-  return units::second_t(frc::DriverStation::GetMatchTime());
+  return units::second_t{frc::DriverStation::GetMatchTime()};
 }
diff --git a/wpilibc/src/main/native/cpp/Ultrasonic.cpp b/wpilibc/src/main/native/cpp/Ultrasonic.cpp
index 02035dd..a034fc3 100644
--- a/wpilibc/src/main/native/cpp/Ultrasonic.cpp
+++ b/wpilibc/src/main/native/cpp/Ultrasonic.cpp
@@ -39,10 +39,10 @@
       m_echoChannel(echoChannel, wpi::NullDeleter<DigitalInput>()),
       m_counter(m_echoChannel) {
   if (!pingChannel) {
-    throw FRC_MakeError(err::NullParameter, "{}", "pingChannel");
+    throw FRC_MakeError(err::NullParameter, "pingChannel");
   }
   if (!echoChannel) {
-    throw FRC_MakeError(err::NullParameter, "{}", "echoChannel");
+    throw FRC_MakeError(err::NullParameter, "echoChannel");
   }
   Initialize();
 }
@@ -86,7 +86,7 @@
 
 void Ultrasonic::Ping() {
   if (m_automaticEnabled) {
-    throw FRC_MakeError(err::IncompatibleMode, "{}",
+    throw FRC_MakeError(err::IncompatibleMode,
                         "cannot call Ping() in automatic mode");
   }
 
@@ -120,11 +120,6 @@
     }
 
     m_thread = std::thread(&Ultrasonic::UltrasonicChecker);
-
-    // TODO: Currently, lvuser does not have permissions to set task priorities.
-    // Until that is the case, uncommenting this will break user code that calls
-    // Ultrasonic::SetAutomicMode().
-    // m_task.SetPriority(kPriority);
   } else {
     // Wait for background task to stop running
     if (m_thread.joinable()) {
@@ -162,7 +157,8 @@
 void Ultrasonic::InitSendable(wpi::SendableBuilder& builder) {
   builder.SetSmartDashboardType("Ultrasonic");
   builder.AddDoubleProperty(
-      "Value", [=] { return units::inch_t{GetRange()}.value(); }, nullptr);
+      "Value", [=, this] { return units::inch_t{GetRange()}.value(); },
+      nullptr);
 }
 
 void Ultrasonic::Initialize() {
diff --git a/wpilibc/src/main/native/cpp/Watchdog.cpp b/wpilibc/src/main/native/cpp/Watchdog.cpp
index 854f9e9..37e2536 100644
--- a/wpilibc/src/main/native/cpp/Watchdog.cpp
+++ b/wpilibc/src/main/native/cpp/Watchdog.cpp
@@ -47,10 +47,10 @@
 Watchdog::Impl::Impl() {
   int32_t status = 0;
   m_notifier = HAL_InitializeNotifier(&status);
-  FRC_CheckErrorStatus(status, "{}", "starting watchdog notifier");
+  FRC_CheckErrorStatus(status, "starting watchdog notifier");
   HAL_SetNotifierName(m_notifier, "Watchdog", &status);
 
-  m_thread = std::thread([=] { Main(); });
+  m_thread = std::thread([=, this] { Main(); });
 }
 
 Watchdog::Impl::~Impl() {
@@ -58,7 +58,7 @@
   // atomically set handle to 0, then clean
   HAL_NotifierHandle handle = m_notifier.exchange(0);
   HAL_StopNotifier(handle, &status);
-  FRC_ReportError(status, "{}", "stopping watchdog notifier");
+  FRC_ReportError(status, "stopping watchdog notifier");
 
   // Join the thread to ensure the handler has exited.
   if (m_thread.joinable()) {
@@ -84,7 +84,7 @@
                               1e6),
         &status);
   }
-  FRC_CheckErrorStatus(status, "{}", "updating watchdog notifier alarm");
+  FRC_CheckErrorStatus(status, "updating watchdog notifier alarm");
 }
 
 void Watchdog::Impl::Main() {
diff --git a/wpilibc/src/main/native/cpp/XboxController.cpp b/wpilibc/src/main/native/cpp/XboxController.cpp
index a08225b..f10419f 100644
--- a/wpilibc/src/main/native/cpp/XboxController.cpp
+++ b/wpilibc/src/main/native/cpp/XboxController.cpp
@@ -6,6 +6,8 @@
 
 #include <hal/FRCUsageReporting.h>
 
+#include "frc/event/BooleanEvent.h"
+
 using namespace frc;
 
 XboxController::XboxController(int port) : GenericHID(port) {
@@ -60,6 +62,14 @@
   return GetRawButtonReleased(Button::kRightBumper);
 }
 
+BooleanEvent XboxController::LeftBumper(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetLeftBumper(); });
+}
+
+BooleanEvent XboxController::RightBumper(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetRightBumper(); });
+}
+
 bool XboxController::GetLeftStickButton() const {
   return GetRawButton(Button::kLeftStick);
 }
@@ -84,6 +94,14 @@
   return GetRawButtonReleased(Button::kRightStick);
 }
 
+BooleanEvent XboxController::LeftStick(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetLeftStickButton(); });
+}
+
+BooleanEvent XboxController::RightStick(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetRightStickButton(); });
+}
+
 bool XboxController::GetAButton() const {
   return GetRawButton(Button::kA);
 }
@@ -96,6 +114,10 @@
   return GetRawButtonReleased(Button::kA);
 }
 
+BooleanEvent XboxController::A(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetAButton(); });
+}
+
 bool XboxController::GetBButton() const {
   return GetRawButton(Button::kB);
 }
@@ -108,6 +130,10 @@
   return GetRawButtonReleased(Button::kB);
 }
 
+BooleanEvent XboxController::B(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetBButton(); });
+}
+
 bool XboxController::GetXButton() const {
   return GetRawButton(Button::kX);
 }
@@ -120,6 +146,10 @@
   return GetRawButtonReleased(Button::kX);
 }
 
+BooleanEvent XboxController::X(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetXButton(); });
+}
+
 bool XboxController::GetYButton() const {
   return GetRawButton(Button::kY);
 }
@@ -132,6 +162,10 @@
   return GetRawButtonReleased(Button::kY);
 }
 
+BooleanEvent XboxController::Y(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetYButton(); });
+}
+
 bool XboxController::GetBackButton() const {
   return GetRawButton(Button::kBack);
 }
@@ -144,6 +178,10 @@
   return GetRawButtonReleased(Button::kBack);
 }
 
+BooleanEvent XboxController::Back(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetBackButton(); });
+}
+
 bool XboxController::GetStartButton() const {
   return GetRawButton(Button::kStart);
 }
@@ -155,3 +193,29 @@
 bool XboxController::GetStartButtonReleased() {
   return GetRawButtonReleased(Button::kStart);
 }
+
+BooleanEvent XboxController::Start(EventLoop* loop) const {
+  return BooleanEvent(loop, [this]() { return this->GetStartButton(); });
+}
+
+BooleanEvent XboxController::LeftTrigger(double threshold,
+                                         EventLoop* loop) const {
+  return BooleanEvent(loop, [this, threshold]() {
+    return this->GetLeftTriggerAxis() > threshold;
+  });
+}
+
+BooleanEvent XboxController::LeftTrigger(EventLoop* loop) const {
+  return this->LeftTrigger(0.5, loop);
+}
+
+BooleanEvent XboxController::RightTrigger(double threshold,
+                                          EventLoop* loop) const {
+  return BooleanEvent(loop, [this, threshold]() {
+    return this->GetRightTriggerAxis() > threshold;
+  });
+}
+
+BooleanEvent XboxController::RightTrigger(EventLoop* loop) const {
+  return this->RightTrigger(0.5, loop);
+}
diff --git a/wpilibc/src/main/native/cpp/counter/ExternalDirectionCounter.cpp b/wpilibc/src/main/native/cpp/counter/ExternalDirectionCounter.cpp
index 8c7bb58..24709aa 100644
--- a/wpilibc/src/main/native/cpp/counter/ExternalDirectionCounter.cpp
+++ b/wpilibc/src/main/native/cpp/counter/ExternalDirectionCounter.cpp
@@ -24,10 +24,10 @@
     std::shared_ptr<DigitalSource> countSource,
     std::shared_ptr<DigitalSource> directionSource) {
   if (countSource == nullptr) {
-    throw FRC_MakeError(err::NullParameter, "{}", "countSource");
+    throw FRC_MakeError(err::NullParameter, "countSource");
   }
   if (directionSource == nullptr) {
-    throw FRC_MakeError(err::NullParameter, "{}", "directionSource");
+    throw FRC_MakeError(err::NullParameter, "directionSource");
   }
 
   m_countSource = countSource;
diff --git a/wpilibc/src/main/native/cpp/counter/Tachometer.cpp b/wpilibc/src/main/native/cpp/counter/Tachometer.cpp
index a52bea5..90324f5 100644
--- a/wpilibc/src/main/native/cpp/counter/Tachometer.cpp
+++ b/wpilibc/src/main/native/cpp/counter/Tachometer.cpp
@@ -19,7 +19,7 @@
     : Tachometer({&source, wpi::NullDeleter<DigitalSource>()}) {}
 Tachometer::Tachometer(std::shared_ptr<DigitalSource> source) {
   if (source == nullptr) {
-    throw FRC_MakeError(err::NullParameter, "{}", "source");
+    throw FRC_MakeError(err::NullParameter, "source");
   }
 
   m_source = source;
diff --git a/wpilibc/src/main/native/cpp/drive/DifferentialDrive.cpp b/wpilibc/src/main/native/cpp/drive/DifferentialDrive.cpp
index eed9495..8cce62e 100644
--- a/wpilibc/src/main/native/cpp/drive/DifferentialDrive.cpp
+++ b/wpilibc/src/main/native/cpp/drive/DifferentialDrive.cpp
@@ -12,20 +12,12 @@
 #include <wpi/sendable/SendableRegistry.h>
 
 #include "frc/MathUtil.h"
-#include "frc/SpeedController.h"
+#include "frc/motorcontrol/MotorController.h"
 
 using namespace frc;
 
-#if defined(_MSC_VER)
-#pragma warning(disable : 4996)  // was declared deprecated
-#elif defined(__clang__)
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-#elif defined(__GNUC__)
-#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
-#endif
-
-DifferentialDrive::DifferentialDrive(SpeedController& leftMotor,
-                                     SpeedController& rightMotor)
+DifferentialDrive::DifferentialDrive(MotorController& leftMotor,
+                                     MotorController& rightMotor)
     : m_leftMotor(&leftMotor), m_rightMotor(&rightMotor) {
   wpi::SendableRegistry::AddChild(this, m_leftMotor);
   wpi::SendableRegistry::AddChild(this, m_rightMotor);
@@ -106,39 +98,19 @@
     zRotation = std::copysign(zRotation * zRotation, zRotation);
   }
 
-  double leftSpeed;
-  double rightSpeed;
+  double leftSpeed = xSpeed - zRotation;
+  double rightSpeed = xSpeed + zRotation;
 
-  double maxInput =
-      std::copysign(std::max(std::abs(xSpeed), std::abs(zRotation)), xSpeed);
-
-  // Sign is used because `xSpeed >= 0.0` succeeds for -0.0
-  if (!std::signbit(xSpeed)) {
-    // First quadrant, else second quadrant
-    if (!std::signbit(zRotation)) {
-      leftSpeed = maxInput;
-      rightSpeed = xSpeed - zRotation;
-    } else {
-      leftSpeed = xSpeed + zRotation;
-      rightSpeed = maxInput;
-    }
-  } else {
-    // Third quadrant, else fourth quadrant
-    if (!std::signbit(zRotation)) {
-      leftSpeed = xSpeed + zRotation;
-      rightSpeed = maxInput;
-    } else {
-      leftSpeed = maxInput;
-      rightSpeed = xSpeed - zRotation;
-    }
+  // Find the maximum possible value of (throttle + turn) along the vector that
+  // the joystick is pointing, then desaturate the wheel speeds
+  double greaterInput = (std::max)(std::abs(xSpeed), std::abs(zRotation));
+  double lesserInput = (std::min)(std::abs(xSpeed), std::abs(zRotation));
+  if (greaterInput == 0.0) {
+    return {0.0, 0.0};
   }
-
-  // Normalize the wheel speeds
-  double maxMagnitude = std::max(std::abs(leftSpeed), std::abs(rightSpeed));
-  if (maxMagnitude > 1.0) {
-    leftSpeed /= maxMagnitude;
-    rightSpeed /= maxMagnitude;
-  }
+  double saturatedInput = (greaterInput + lesserInput) / greaterInput;
+  leftSpeed /= saturatedInput;
+  rightSpeed /= saturatedInput;
 
   return {leftSpeed, rightSpeed};
 }
@@ -152,14 +124,14 @@
   double rightSpeed = 0.0;
 
   if (allowTurnInPlace) {
-    leftSpeed = xSpeed + zRotation;
-    rightSpeed = xSpeed - zRotation;
+    leftSpeed = xSpeed - zRotation;
+    rightSpeed = xSpeed + zRotation;
   } else {
-    leftSpeed = xSpeed + std::abs(xSpeed) * zRotation;
-    rightSpeed = xSpeed - std::abs(xSpeed) * zRotation;
+    leftSpeed = xSpeed - std::abs(xSpeed) * zRotation;
+    rightSpeed = xSpeed + std::abs(xSpeed) * zRotation;
   }
 
-  // Normalize wheel speeds
+  // Desaturate wheel speeds
   double maxMagnitude = std::max(std::abs(leftSpeed), std::abs(rightSpeed));
   if (maxMagnitude > 1.0) {
     leftSpeed /= maxMagnitude;
@@ -197,11 +169,11 @@
 void DifferentialDrive::InitSendable(wpi::SendableBuilder& builder) {
   builder.SetSmartDashboardType("DifferentialDrive");
   builder.SetActuator(true);
-  builder.SetSafeState([=] { StopMotor(); });
+  builder.SetSafeState([=, this] { StopMotor(); });
   builder.AddDoubleProperty(
-      "Left Motor Speed", [=] { return m_leftMotor->Get(); },
-      [=](double value) { m_leftMotor->Set(value); });
+      "Left Motor Speed", [=, this] { return m_leftMotor->Get(); },
+      [=, this](double value) { m_leftMotor->Set(value); });
   builder.AddDoubleProperty(
-      "Right Motor Speed", [=] { return m_rightMotor->Get(); },
-      [=](double value) { m_rightMotor->Set(value); });
+      "Right Motor Speed", [=, this] { return m_rightMotor->Get(); },
+      [=, this](double value) { m_rightMotor->Set(value); });
 }
diff --git a/wpilibc/src/main/native/cpp/drive/KilloughDrive.cpp b/wpilibc/src/main/native/cpp/drive/KilloughDrive.cpp
deleted file mode 100644
index 268ae64..0000000
--- a/wpilibc/src/main/native/cpp/drive/KilloughDrive.cpp
+++ /dev/null
@@ -1,134 +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 "frc/drive/KilloughDrive.h"
-
-#include <algorithm>
-#include <cmath>
-
-#include <hal/FRCUsageReporting.h>
-#include <wpi/numbers>
-#include <wpi/sendable/SendableBuilder.h>
-#include <wpi/sendable/SendableRegistry.h>
-
-#include "frc/MathUtil.h"
-#include "frc/SpeedController.h"
-
-using namespace frc;
-
-#if defined(_MSC_VER)
-#pragma warning(disable : 4996)  // was declared deprecated
-#elif defined(__clang__)
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-#elif defined(__GNUC__)
-#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
-#endif
-
-KilloughDrive::KilloughDrive(SpeedController& leftMotor,
-                             SpeedController& rightMotor,
-                             SpeedController& backMotor)
-    : KilloughDrive(leftMotor, rightMotor, backMotor, kDefaultLeftMotorAngle,
-                    kDefaultRightMotorAngle, kDefaultBackMotorAngle) {}
-
-KilloughDrive::KilloughDrive(SpeedController& leftMotor,
-                             SpeedController& rightMotor,
-                             SpeedController& backMotor, double leftMotorAngle,
-                             double rightMotorAngle, double backMotorAngle)
-    : m_leftMotor(&leftMotor),
-      m_rightMotor(&rightMotor),
-      m_backMotor(&backMotor) {
-  m_leftVec = {std::cos(leftMotorAngle * (wpi::numbers::pi / 180.0)),
-               std::sin(leftMotorAngle * (wpi::numbers::pi / 180.0))};
-  m_rightVec = {std::cos(rightMotorAngle * (wpi::numbers::pi / 180.0)),
-                std::sin(rightMotorAngle * (wpi::numbers::pi / 180.0))};
-  m_backVec = {std::cos(backMotorAngle * (wpi::numbers::pi / 180.0)),
-               std::sin(backMotorAngle * (wpi::numbers::pi / 180.0))};
-  wpi::SendableRegistry::AddChild(this, m_leftMotor);
-  wpi::SendableRegistry::AddChild(this, m_rightMotor);
-  wpi::SendableRegistry::AddChild(this, m_backMotor);
-  static int instances = 0;
-  ++instances;
-  wpi::SendableRegistry::AddLW(this, "KilloughDrive", instances);
-}
-
-void KilloughDrive::DriveCartesian(double ySpeed, double xSpeed,
-                                   double zRotation, double gyroAngle) {
-  if (!reported) {
-    HAL_Report(HALUsageReporting::kResourceType_RobotDrive,
-               HALUsageReporting::kRobotDrive2_KilloughCartesian, 3);
-    reported = true;
-  }
-
-  ySpeed = ApplyDeadband(ySpeed, m_deadband);
-  xSpeed = ApplyDeadband(xSpeed, m_deadband);
-
-  auto [left, right, back] =
-      DriveCartesianIK(ySpeed, xSpeed, zRotation, gyroAngle);
-
-  m_leftMotor->Set(left * m_maxOutput);
-  m_rightMotor->Set(right * m_maxOutput);
-  m_backMotor->Set(back * m_maxOutput);
-
-  Feed();
-}
-
-void KilloughDrive::DrivePolar(double magnitude, double angle,
-                               double zRotation) {
-  if (!reported) {
-    HAL_Report(HALUsageReporting::kResourceType_RobotDrive,
-               HALUsageReporting::kRobotDrive2_KilloughPolar, 3);
-    reported = true;
-  }
-
-  DriveCartesian(magnitude * std::sin(angle * (wpi::numbers::pi / 180.0)),
-                 magnitude * std::cos(angle * (wpi::numbers::pi / 180.0)),
-                 zRotation, 0.0);
-}
-
-KilloughDrive::WheelSpeeds KilloughDrive::DriveCartesianIK(double ySpeed,
-                                                           double xSpeed,
-                                                           double zRotation,
-                                                           double gyroAngle) {
-  ySpeed = std::clamp(ySpeed, -1.0, 1.0);
-  xSpeed = std::clamp(xSpeed, -1.0, 1.0);
-
-  // Compensate for gyro angle.
-  Vector2d input{ySpeed, xSpeed};
-  input.Rotate(-gyroAngle);
-
-  double wheelSpeeds[3];
-  wheelSpeeds[kLeft] = input.ScalarProject(m_leftVec) + zRotation;
-  wheelSpeeds[kRight] = input.ScalarProject(m_rightVec) + zRotation;
-  wheelSpeeds[kBack] = input.ScalarProject(m_backVec) + zRotation;
-
-  Desaturate(wheelSpeeds);
-
-  return {wheelSpeeds[kLeft], wheelSpeeds[kRight], wheelSpeeds[kBack]};
-}
-
-void KilloughDrive::StopMotor() {
-  m_leftMotor->StopMotor();
-  m_rightMotor->StopMotor();
-  m_backMotor->StopMotor();
-  Feed();
-}
-
-std::string KilloughDrive::GetDescription() const {
-  return "KilloughDrive";
-}
-
-void KilloughDrive::InitSendable(wpi::SendableBuilder& builder) {
-  builder.SetSmartDashboardType("KilloughDrive");
-  builder.SetActuator(true);
-  builder.SetSafeState([=] { StopMotor(); });
-  builder.AddDoubleProperty(
-      "Left Motor Speed", [=] { return m_leftMotor->Get(); },
-      [=](double value) { m_leftMotor->Set(value); });
-  builder.AddDoubleProperty(
-      "Right Motor Speed", [=] { return m_rightMotor->Get(); },
-      [=](double value) { m_rightMotor->Set(value); });
-  builder.AddDoubleProperty(
-      "Back Motor Speed", [=] { return m_backMotor->Get(); },
-      [=](double value) { m_backMotor->Set(value); });
-}
diff --git a/wpilibc/src/main/native/cpp/drive/MecanumDrive.cpp b/wpilibc/src/main/native/cpp/drive/MecanumDrive.cpp
index 70e90d4..2bf6a3f 100644
--- a/wpilibc/src/main/native/cpp/drive/MecanumDrive.cpp
+++ b/wpilibc/src/main/native/cpp/drive/MecanumDrive.cpp
@@ -5,31 +5,21 @@
 #include "frc/drive/MecanumDrive.h"
 
 #include <algorithm>
-#include <cmath>
 
 #include <hal/FRCUsageReporting.h>
-#include <wpi/numbers>
 #include <wpi/sendable/SendableBuilder.h>
 #include <wpi/sendable/SendableRegistry.h>
 
 #include "frc/MathUtil.h"
-#include "frc/SpeedController.h"
-#include "frc/drive/Vector2d.h"
+#include "frc/geometry/Translation2d.h"
+#include "frc/motorcontrol/MotorController.h"
 
 using namespace frc;
 
-#if defined(_MSC_VER)
-#pragma warning(disable : 4996)  // was declared deprecated
-#elif defined(__clang__)
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-#elif defined(__GNUC__)
-#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
-#endif
-
-MecanumDrive::MecanumDrive(SpeedController& frontLeftMotor,
-                           SpeedController& rearLeftMotor,
-                           SpeedController& frontRightMotor,
-                           SpeedController& rearRightMotor)
+MecanumDrive::MecanumDrive(MotorController& frontLeftMotor,
+                           MotorController& rearLeftMotor,
+                           MotorController& frontRightMotor,
+                           MotorController& rearRightMotor)
     : m_frontLeftMotor(&frontLeftMotor),
       m_rearLeftMotor(&rearLeftMotor),
       m_frontRightMotor(&frontRightMotor),
@@ -43,19 +33,19 @@
   wpi::SendableRegistry::AddLW(this, "MecanumDrive", instances);
 }
 
-void MecanumDrive::DriveCartesian(double ySpeed, double xSpeed,
-                                  double zRotation, double gyroAngle) {
+void MecanumDrive::DriveCartesian(double xSpeed, double ySpeed,
+                                  double zRotation, Rotation2d gyroAngle) {
   if (!reported) {
     HAL_Report(HALUsageReporting::kResourceType_RobotDrive,
                HALUsageReporting::kRobotDrive2_MecanumCartesian, 4);
     reported = true;
   }
 
-  ySpeed = ApplyDeadband(ySpeed, m_deadband);
   xSpeed = ApplyDeadband(xSpeed, m_deadband);
+  ySpeed = ApplyDeadband(ySpeed, m_deadband);
 
   auto [frontLeft, frontRight, rearLeft, rearRight] =
-      DriveCartesianIK(ySpeed, xSpeed, zRotation, gyroAngle);
+      DriveCartesianIK(xSpeed, ySpeed, zRotation, gyroAngle);
 
   m_frontLeftMotor->Set(frontLeft * m_maxOutput);
   m_frontRightMotor->Set(frontRight * m_maxOutput);
@@ -65,7 +55,7 @@
   Feed();
 }
 
-void MecanumDrive::DrivePolar(double magnitude, double angle,
+void MecanumDrive::DrivePolar(double magnitude, Rotation2d angle,
                               double zRotation) {
   if (!reported) {
     HAL_Report(HALUsageReporting::kResourceType_RobotDrive,
@@ -73,9 +63,8 @@
     reported = true;
   }
 
-  DriveCartesian(magnitude * std::cos(angle * (wpi::numbers::pi / 180.0)),
-                 magnitude * std::sin(angle * (wpi::numbers::pi / 180.0)),
-                 zRotation, 0.0);
+  DriveCartesian(magnitude * angle.Cos(), magnitude * angle.Sin(), zRotation,
+                 0_rad);
 }
 
 void MecanumDrive::StopMotor() {
@@ -86,22 +75,23 @@
   Feed();
 }
 
-MecanumDrive::WheelSpeeds MecanumDrive::DriveCartesianIK(double ySpeed,
-                                                         double xSpeed,
+MecanumDrive::WheelSpeeds MecanumDrive::DriveCartesianIK(double xSpeed,
+                                                         double ySpeed,
                                                          double zRotation,
-                                                         double gyroAngle) {
-  ySpeed = std::clamp(ySpeed, -1.0, 1.0);
+                                                         Rotation2d gyroAngle) {
   xSpeed = std::clamp(xSpeed, -1.0, 1.0);
+  ySpeed = std::clamp(ySpeed, -1.0, 1.0);
 
   // Compensate for gyro angle.
-  Vector2d input{ySpeed, xSpeed};
-  input.Rotate(-gyroAngle);
+  auto input =
+      Translation2d{units::meter_t{xSpeed}, units::meter_t{ySpeed}}.RotateBy(
+          -gyroAngle);
 
   double wheelSpeeds[4];
-  wheelSpeeds[kFrontLeft] = input.x + input.y + zRotation;
-  wheelSpeeds[kFrontRight] = input.x - input.y - zRotation;
-  wheelSpeeds[kRearLeft] = input.x - input.y + zRotation;
-  wheelSpeeds[kRearRight] = input.x + input.y - zRotation;
+  wheelSpeeds[kFrontLeft] = input.X().value() + input.Y().value() + zRotation;
+  wheelSpeeds[kFrontRight] = input.X().value() - input.Y().value() - zRotation;
+  wheelSpeeds[kRearLeft] = input.X().value() - input.Y().value() + zRotation;
+  wheelSpeeds[kRearRight] = input.X().value() + input.Y().value() - zRotation;
 
   Desaturate(wheelSpeeds);
 
@@ -116,17 +106,17 @@
 void MecanumDrive::InitSendable(wpi::SendableBuilder& builder) {
   builder.SetSmartDashboardType("MecanumDrive");
   builder.SetActuator(true);
-  builder.SetSafeState([=] { StopMotor(); });
+  builder.SetSafeState([=, this] { StopMotor(); });
   builder.AddDoubleProperty(
-      "Front Left Motor Speed", [=] { return m_frontLeftMotor->Get(); },
-      [=](double value) { m_frontLeftMotor->Set(value); });
+      "Front Left Motor Speed", [=, this] { return m_frontLeftMotor->Get(); },
+      [=, this](double value) { m_frontLeftMotor->Set(value); });
   builder.AddDoubleProperty(
-      "Front Right Motor Speed", [=] { return m_frontRightMotor->Get(); },
-      [=](double value) { m_frontRightMotor->Set(value); });
+      "Front Right Motor Speed", [=, this] { return m_frontRightMotor->Get(); },
+      [=, this](double value) { m_frontRightMotor->Set(value); });
   builder.AddDoubleProperty(
-      "Rear Left Motor Speed", [=] { return m_rearLeftMotor->Get(); },
-      [=](double value) { m_rearLeftMotor->Set(value); });
+      "Rear Left Motor Speed", [=, this] { return m_rearLeftMotor->Get(); },
+      [=, this](double value) { m_rearLeftMotor->Set(value); });
   builder.AddDoubleProperty(
-      "Rear Right Motor Speed", [=] { return m_rearRightMotor->Get(); },
-      [=](double value) { m_rearRightMotor->Set(value); });
+      "Rear Right Motor Speed", [=, this] { return m_rearRightMotor->Get(); },
+      [=, this](double value) { m_rearRightMotor->Set(value); });
 }
diff --git a/wpilibc/src/main/native/cpp/drive/RobotDriveBase.cpp b/wpilibc/src/main/native/cpp/drive/RobotDriveBase.cpp
index 97ea0ee..fbd0c6e 100644
--- a/wpilibc/src/main/native/cpp/drive/RobotDriveBase.cpp
+++ b/wpilibc/src/main/native/cpp/drive/RobotDriveBase.cpp
@@ -10,7 +10,6 @@
 
 #include <hal/FRCUsageReporting.h>
 
-#include "frc/MathUtil.h"
 #include "frc/motorcontrol/MotorController.h"
 
 using namespace frc;
@@ -31,11 +30,7 @@
   Feed();
 }
 
-double RobotDriveBase::ApplyDeadband(double value, double deadband) {
-  return frc::ApplyDeadband(value, deadband);
-}
-
-void RobotDriveBase::Desaturate(wpi::span<double> wheelSpeeds) {
+void RobotDriveBase::Desaturate(std::span<double> wheelSpeeds) {
   double maxMagnitude = std::abs(wheelSpeeds[0]);
   for (size_t i = 1; i < wheelSpeeds.size(); i++) {
     double temp = std::abs(wheelSpeeds[i]);
diff --git a/wpilibc/src/main/native/cpp/drive/Vector2d.cpp b/wpilibc/src/main/native/cpp/drive/Vector2d.cpp
deleted file mode 100644
index a342dc2..0000000
--- a/wpilibc/src/main/native/cpp/drive/Vector2d.cpp
+++ /dev/null
@@ -1,38 +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 "frc/drive/Vector2d.h"
-
-#include <cmath>
-
-#include <wpi/numbers>
-
-using namespace frc;
-
-Vector2d::Vector2d(double x, double y) {
-  this->x = x;
-  this->y = y;
-}
-
-void Vector2d::Rotate(double angle) {
-  double cosA = std::cos(angle * (wpi::numbers::pi / 180.0));
-  double sinA = std::sin(angle * (wpi::numbers::pi / 180.0));
-  double out[2];
-  out[0] = x * cosA - y * sinA;
-  out[1] = x * sinA + y * cosA;
-  x = out[0];
-  y = out[1];
-}
-
-double Vector2d::Dot(const Vector2d& vec) const {
-  return x * vec.x + y * vec.y;
-}
-
-double Vector2d::Magnitude() const {
-  return std::sqrt(x * x + y * y);
-}
-
-double Vector2d::ScalarProject(const Vector2d& vec) const {
-  return Dot(vec) / vec.Magnitude();
-}
diff --git a/wpilibc/src/main/native/cpp/event/BooleanEvent.cpp b/wpilibc/src/main/native/cpp/event/BooleanEvent.cpp
new file mode 100644
index 0000000..5b8ce63
--- /dev/null
+++ b/wpilibc/src/main/native/cpp/event/BooleanEvent.cpp
@@ -0,0 +1,68 @@
+// 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 "frc/event/BooleanEvent.h"
+
+using namespace frc;
+
+BooleanEvent::BooleanEvent(EventLoop* loop, std::function<bool()> condition)
+    : m_loop(loop), m_condition(std::move(condition)) {}
+
+BooleanEvent::operator std::function<bool()>() {
+  return m_condition;
+}
+
+bool BooleanEvent::GetAsBoolean() const {
+  return m_condition();
+}
+
+void BooleanEvent::IfHigh(std::function<void()> action) {
+  m_loop->Bind([condition = m_condition, action = std::move(action)] {
+    if (condition()) {
+      action();
+    }
+  });
+}
+
+BooleanEvent BooleanEvent::operator!() {
+  return BooleanEvent(this->m_loop, [lhs = m_condition] { return !lhs(); });
+}
+
+BooleanEvent BooleanEvent::operator&&(std::function<bool()> rhs) {
+  return BooleanEvent(this->m_loop,
+                      [lhs = m_condition, rhs] { return lhs() && rhs(); });
+}
+
+BooleanEvent BooleanEvent::operator||(std::function<bool()> rhs) {
+  return BooleanEvent(this->m_loop,
+                      [lhs = m_condition, rhs] { return lhs() || rhs(); });
+}
+
+BooleanEvent BooleanEvent::Rising() {
+  return BooleanEvent(
+      this->m_loop, [lhs = m_condition, m_previous = m_condition()]() mutable {
+        bool present = lhs();
+        bool past = m_previous;
+        m_previous = present;
+        return !past && present;
+      });
+}
+
+BooleanEvent BooleanEvent::Falling() {
+  return BooleanEvent(
+      this->m_loop, [lhs = m_condition, m_previous = m_condition()]() mutable {
+        bool present = lhs();
+        bool past = m_previous;
+        m_previous = present;
+        return past && !present;
+      });
+}
+
+BooleanEvent BooleanEvent::Debounce(units::second_t debounceTime,
+                                    frc::Debouncer::DebounceType type) {
+  return BooleanEvent(
+      this->m_loop,
+      [debouncer = frc::Debouncer(debounceTime, type),
+       lhs = m_condition]() mutable { return debouncer.Calculate(lhs()); });
+}
diff --git a/wpilibc/src/main/native/cpp/event/EventLoop.cpp b/wpilibc/src/main/native/cpp/event/EventLoop.cpp
new file mode 100644
index 0000000..5af79c9
--- /dev/null
+++ b/wpilibc/src/main/native/cpp/event/EventLoop.cpp
@@ -0,0 +1,23 @@
+// 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 "frc/event/EventLoop.h"
+
+using namespace frc;
+
+EventLoop::EventLoop() {}
+
+void EventLoop::Bind(wpi::unique_function<void()> action) {
+  m_bindings.emplace_back(std::move(action));
+}
+
+void EventLoop::Poll() {
+  for (wpi::unique_function<void()>& action : m_bindings) {
+    action();
+  }
+}
+
+void EventLoop::Clear() {
+  m_bindings.clear();
+}
diff --git a/wpilibc/src/main/native/cpp/event/NetworkBooleanEvent.cpp b/wpilibc/src/main/native/cpp/event/NetworkBooleanEvent.cpp
new file mode 100644
index 0000000..d910361
--- /dev/null
+++ b/wpilibc/src/main/native/cpp/event/NetworkBooleanEvent.cpp
@@ -0,0 +1,40 @@
+// 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 "frc/event/NetworkBooleanEvent.h"
+
+#include <networktables/BooleanTopic.h>
+#include <networktables/NetworkTable.h>
+#include <networktables/NetworkTableInstance.h>
+
+using namespace frc;
+
+NetworkBooleanEvent::NetworkBooleanEvent(EventLoop* loop,
+                                         nt::BooleanTopic topic)
+    : NetworkBooleanEvent{loop, topic.Subscribe(false)} {}
+
+NetworkBooleanEvent::NetworkBooleanEvent(EventLoop* loop,
+                                         nt::BooleanSubscriber sub)
+    : BooleanEvent{
+          loop,
+          [sub = std::make_shared<nt::BooleanSubscriber>(std::move(sub))] {
+            return sub->GetTopic().GetInstance().IsConnected() && sub->Get();
+          }} {}
+
+NetworkBooleanEvent::NetworkBooleanEvent(
+    EventLoop* loop, std::shared_ptr<nt::NetworkTable> table,
+    std::string_view topicName)
+    : NetworkBooleanEvent{loop, table->GetBooleanTopic(topicName)} {}
+
+NetworkBooleanEvent::NetworkBooleanEvent(EventLoop* loop,
+                                         std::string_view tableName,
+                                         std::string_view topicName)
+    : NetworkBooleanEvent{loop, nt::NetworkTableInstance::GetDefault(),
+                          tableName, topicName} {}
+
+NetworkBooleanEvent::NetworkBooleanEvent(EventLoop* loop,
+                                         nt::NetworkTableInstance inst,
+                                         std::string_view tableName,
+                                         std::string_view topicName)
+    : NetworkBooleanEvent{loop, inst.GetTable(tableName), topicName} {}
diff --git a/wpilibc/src/main/native/cpp/internal/DriverStationModeThread.cpp b/wpilibc/src/main/native/cpp/internal/DriverStationModeThread.cpp
new file mode 100644
index 0000000..4085658
--- /dev/null
+++ b/wpilibc/src/main/native/cpp/internal/DriverStationModeThread.cpp
@@ -0,0 +1,64 @@
+// 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 "frc/internal/DriverStationModeThread.h"
+
+#include <hal/DriverStation.h>
+#include <wpi/Synchronization.h>
+
+#include "frc/DriverStation.h"
+
+using namespace frc::internal;
+
+DriverStationModeThread::DriverStationModeThread() {
+  m_keepAlive = true;
+  m_thread = std::thread{[&] { Run(); }};
+}
+
+DriverStationModeThread::~DriverStationModeThread() {
+  m_keepAlive = false;
+  if (m_thread.joinable()) {
+    m_thread.join();
+  }
+}
+
+void DriverStationModeThread::InAutonomous(bool entering) {
+  m_userInAutonomous = entering;
+}
+void DriverStationModeThread::InDisabled(bool entering) {
+  m_userInDisabled = entering;
+}
+
+void DriverStationModeThread::InTeleop(bool entering) {
+  m_userInTeleop = entering;
+}
+
+void DriverStationModeThread::InTest(bool entering) {
+  m_userInTest = entering;
+}
+
+void DriverStationModeThread::Run() {
+  wpi::Event event{false, false};
+  HAL_ProvideNewDataEventHandle(event.GetHandle());
+
+  while (m_keepAlive.load()) {
+    bool timedOut = false;
+    wpi::WaitForObject(event.GetHandle(), 0.1, &timedOut);
+    frc::DriverStation::RefreshData();
+    if (m_userInDisabled) {
+      HAL_ObserveUserProgramDisabled();
+    }
+    if (m_userInAutonomous) {
+      HAL_ObserveUserProgramAutonomous();
+    }
+    if (m_userInTeleop) {
+      HAL_ObserveUserProgramTeleop();
+    }
+    if (m_userInTest) {
+      HAL_ObserveUserProgramTest();
+    }
+  }
+
+  HAL_RemoveNewDataEventHandle(event.GetHandle());
+}
diff --git a/wpilibc/src/main/native/cpp/livewindow/LiveWindow.cpp b/wpilibc/src/main/native/cpp/livewindow/LiveWindow.cpp
index ac1c8ad..0a4e1b8 100644
--- a/wpilibc/src/main/native/cpp/livewindow/LiveWindow.cpp
+++ b/wpilibc/src/main/native/cpp/livewindow/LiveWindow.cpp
@@ -4,9 +4,10 @@
 
 #include "frc/livewindow/LiveWindow.h"
 
+#include <networktables/BooleanTopic.h>
 #include <networktables/NetworkTable.h>
-#include <networktables/NetworkTableEntry.h>
 #include <networktables/NetworkTableInstance.h>
+#include <networktables/StringTopic.h>
 #include <wpi/mutex.h>
 #include <wpi/sendable/Sendable.h>
 #include <wpi/sendable/SendableRegistry.h>
@@ -18,13 +19,16 @@
 namespace {
 struct Component {
   bool firstTime = true;
-  bool telemetryEnabled = true;
+  bool telemetryEnabled = false;
+  nt::StringPublisher namePub;
+  nt::StringPublisher typePub;
 };
 
 struct Instance {
   Instance() {
     wpi::SendableRegistry::SetLiveWindowBuilderFactory(
         [] { return std::make_unique<SendableBuilderImpl>(); });
+    enabledPub.Set(false);
   }
 
   wpi::mutex mutex;
@@ -35,11 +39,12 @@
       nt::NetworkTableInstance::GetDefault().GetTable("LiveWindow");
   std::shared_ptr<nt::NetworkTable> statusTable =
       liveWindowTable->GetSubTable(".status");
-  nt::NetworkTableEntry enabledEntry = statusTable->GetEntry("LW Enabled");
+  nt::BooleanPublisher enabledPub =
+      statusTable->GetBooleanTopic("LW Enabled").Publish();
 
   bool startLiveWindow = false;
   bool liveWindowEnabled = false;
-  bool telemetryEnabled = true;
+  bool telemetryEnabled = false;
 
   std::function<void()> enabled;
   std::function<void()> disabled;
@@ -75,12 +80,6 @@
   return data;
 }
 
-LiveWindow* LiveWindow::GetInstance() {
-  ::GetInstance();
-  static LiveWindow instance;
-  return &instance;
-}
-
 void LiveWindow::SetEnabledCallback(std::function<void()> func) {
   ::GetInstance().enabled = func;
 }
@@ -115,6 +114,18 @@
   });
 }
 
+void LiveWindow::EnableAllTelemetry() {
+  auto& inst = ::GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  inst.telemetryEnabled = true;
+  wpi::SendableRegistry::ForeachLiveWindow(inst.dataHandle, [&](auto& cbdata) {
+    if (!cbdata.data) {
+      cbdata.data = std::make_shared<Component>();
+    }
+    std::static_pointer_cast<Component>(cbdata.data)->telemetryEnabled = true;
+  });
+}
+
 bool LiveWindow::IsEnabled() {
   auto& inst = ::GetInstance();
   std::scoped_lock lock(inst.mutex);
@@ -145,7 +156,7 @@
       inst.disabled();
     }
   }
-  inst.enabledEntry.SetBoolean(enabled);
+  inst.enabledPub.Set(enabled);
 }
 
 void LiveWindow::UpdateValues() {
@@ -192,10 +203,12 @@
       } else {
         table = ssTable->GetSubTable(cbdata.name);
       }
-      table->GetEntry(".name").SetString(cbdata.name);
+      comp.namePub = nt::StringTopic{table->GetTopic(".name")}.Publish();
+      comp.namePub.Set(cbdata.name);
       static_cast<SendableBuilderImpl&>(cbdata.builder).SetTable(table);
       cbdata.sendable->InitSendable(cbdata.builder);
-      ssTable->GetEntry(".type").SetString("LW Subsystem");
+      comp.typePub = nt::StringTopic{ssTable->GetTopic(".type")}.Publish();
+      comp.typePub.Set("LW Subsystem");
 
       comp.firstTime = false;
     }
diff --git a/wpilibc/src/main/native/cpp/SpeedController.cpp b/wpilibc/src/main/native/cpp/motorcontrol/MotorController.cpp
similarity index 62%
rename from wpilibc/src/main/native/cpp/SpeedController.cpp
rename to wpilibc/src/main/native/cpp/motorcontrol/MotorController.cpp
index e0b0cb2..9d20144 100644
--- a/wpilibc/src/main/native/cpp/SpeedController.cpp
+++ b/wpilibc/src/main/native/cpp/motorcontrol/MotorController.cpp
@@ -2,12 +2,12 @@
 // 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/SpeedController.h"
+#include "frc/motorcontrol/MotorController.h"
 
 #include <frc/RobotController.h>
 
 using namespace frc;
 
-void SpeedController::SetVoltage(units::volt_t output) {
-  Set(output / units::volt_t(RobotController::GetInputVoltage()));
+void MotorController::SetVoltage(units::volt_t output) {
+  Set(output / RobotController::GetBatteryVoltage());
 }
diff --git a/wpilibc/src/main/native/cpp/motorcontrol/MotorControllerGroup.cpp b/wpilibc/src/main/native/cpp/motorcontrol/MotorControllerGroup.cpp
index b7f26d1..f855d14 100644
--- a/wpilibc/src/main/native/cpp/motorcontrol/MotorControllerGroup.cpp
+++ b/wpilibc/src/main/native/cpp/motorcontrol/MotorControllerGroup.cpp
@@ -33,6 +33,12 @@
   }
 }
 
+void MotorControllerGroup::SetVoltage(units::volt_t output) {
+  for (auto motorController : m_motorControllers) {
+    motorController.get().SetVoltage(m_isInverted ? -output : output);
+  }
+}
+
 double MotorControllerGroup::Get() const {
   if (!m_motorControllers.empty()) {
     return m_motorControllers.front().get().Get() * (m_isInverted ? -1 : 1);
@@ -63,7 +69,8 @@
 void MotorControllerGroup::InitSendable(wpi::SendableBuilder& builder) {
   builder.SetSmartDashboardType("Motor Controller");
   builder.SetActuator(true);
-  builder.SetSafeState([=] { StopMotor(); });
+  builder.SetSafeState([=, this] { StopMotor(); });
   builder.AddDoubleProperty(
-      "Value", [=] { return Get(); }, [=](double value) { Set(value); });
+      "Value", [=, this] { return Get(); },
+      [=, this](double value) { Set(value); });
 }
diff --git a/wpilibc/src/main/native/cpp/motorcontrol/NidecBrushless.cpp b/wpilibc/src/main/native/cpp/motorcontrol/NidecBrushless.cpp
index 21164eb..01c70d0 100644
--- a/wpilibc/src/main/native/cpp/motorcontrol/NidecBrushless.cpp
+++ b/wpilibc/src/main/native/cpp/motorcontrol/NidecBrushless.cpp
@@ -73,7 +73,8 @@
 void NidecBrushless::InitSendable(wpi::SendableBuilder& builder) {
   builder.SetSmartDashboardType("Nidec Brushless");
   builder.SetActuator(true);
-  builder.SetSafeState([=] { StopMotor(); });
+  builder.SetSafeState([=, this] { StopMotor(); });
   builder.AddDoubleProperty(
-      "Value", [=] { return Get(); }, [=](double value) { Set(value); });
+      "Value", [=, this] { return Get(); },
+      [=, this](double value) { Set(value); });
 }
diff --git a/wpilibc/src/main/native/cpp/motorcontrol/PWMMotorController.cpp b/wpilibc/src/main/native/cpp/motorcontrol/PWMMotorController.cpp
index f9ff4b8..3692f75 100644
--- a/wpilibc/src/main/native/cpp/motorcontrol/PWMMotorController.cpp
+++ b/wpilibc/src/main/native/cpp/motorcontrol/PWMMotorController.cpp
@@ -12,6 +12,7 @@
 
 void PWMMotorController::Set(double speed) {
   m_pwm.SetSpeed(m_isInverted ? -speed : speed);
+  Feed();
 }
 
 double PWMMotorController::Get() const {
@@ -31,7 +32,8 @@
 }
 
 void PWMMotorController::StopMotor() {
-  Disable();
+  // Don't use Set(0) as that will feed the watch kitty
+  m_pwm.SetSpeed(0);
 }
 
 std::string PWMMotorController::GetDescription() const {
@@ -54,7 +56,8 @@
 void PWMMotorController::InitSendable(wpi::SendableBuilder& builder) {
   builder.SetSmartDashboardType("Motor Controller");
   builder.SetActuator(true);
-  builder.SetSafeState([=] { Disable(); });
+  builder.SetSafeState([=, this] { Disable(); });
   builder.AddDoubleProperty(
-      "Value", [=] { return Get(); }, [=](double value) { Set(value); });
+      "Value", [=, this] { return Get(); },
+      [=, this](double value) { Set(value); });
 }
diff --git a/wpilibc/src/main/native/cpp/shuffleboard/RecordingController.cpp b/wpilibc/src/main/native/cpp/shuffleboard/RecordingController.cpp
index 834c4be..fb1ef9c 100644
--- a/wpilibc/src/main/native/cpp/shuffleboard/RecordingController.cpp
+++ b/wpilibc/src/main/native/cpp/shuffleboard/RecordingController.cpp
@@ -9,40 +9,40 @@
 using namespace frc;
 using namespace frc::detail;
 
-RecordingController::RecordingController(nt::NetworkTableInstance ntInstance)
-    : m_recordingControlEntry(), m_recordingFileNameFormatEntry() {
+RecordingController::RecordingController(nt::NetworkTableInstance ntInstance) {
   m_recordingControlEntry =
-      ntInstance.GetEntry("/Shuffleboard/.recording/RecordData");
+      ntInstance.GetBooleanTopic("/Shuffleboard/.recording/RecordData")
+          .Publish();
   m_recordingFileNameFormatEntry =
-      ntInstance.GetEntry("/Shuffleboard/.recording/FileNameFormat");
+      ntInstance.GetStringTopic("/Shuffleboard/.recording/FileNameFormat")
+          .Publish();
   m_eventsTable = ntInstance.GetTable("/Shuffleboard/.recording/events");
 }
 
 void RecordingController::StartRecording() {
-  m_recordingControlEntry.SetBoolean(true);
+  m_recordingControlEntry.Set(true);
 }
 
 void RecordingController::StopRecording() {
-  m_recordingControlEntry.SetBoolean(false);
+  m_recordingControlEntry.Set(false);
 }
 
 void RecordingController::SetRecordingFileNameFormat(std::string_view format) {
-  m_recordingFileNameFormatEntry.SetString(format);
+  m_recordingFileNameFormatEntry.Set(format);
 }
 
 void RecordingController::ClearRecordingFileNameFormat() {
-  m_recordingFileNameFormatEntry.Delete();
+  m_recordingFileNameFormatEntry.Set("");
 }
 
 void RecordingController::AddEventMarker(
     std::string_view name, std::string_view description,
     ShuffleboardEventImportance importance) {
   if (name.empty()) {
-    FRC_ReportError(err::Error, "{}",
-                    "Shuffleboard event name was not specified");
+    FRC_ReportError(err::Error, "Shuffleboard event name was not specified");
     return;
   }
   m_eventsTable->GetSubTable(name)->GetEntry("Info").SetStringArray(
-      {std::string{description},
-       std::string{ShuffleboardEventImportanceName(importance)}});
+      {{std::string{description},
+        std::string{ShuffleboardEventImportanceName(importance)}}});
 }
diff --git a/wpilibc/src/main/native/cpp/shuffleboard/Shuffleboard.cpp b/wpilibc/src/main/native/cpp/shuffleboard/Shuffleboard.cpp
index 16b404c..9135012 100644
--- a/wpilibc/src/main/native/cpp/shuffleboard/Shuffleboard.cpp
+++ b/wpilibc/src/main/native/cpp/shuffleboard/Shuffleboard.cpp
@@ -63,10 +63,24 @@
   AddEventMarker(name, "", importance);
 }
 
-detail::ShuffleboardInstance& Shuffleboard::GetInstance() {
-  static detail::ShuffleboardInstance inst(
+static std::unique_ptr<detail::ShuffleboardInstance>& GetInstanceHolder() {
+  static std::unique_ptr<detail::ShuffleboardInstance> instance =
+      std::make_unique<detail::ShuffleboardInstance>(
+          nt::NetworkTableInstance::GetDefault());
+  return instance;
+}
+
+#ifndef __FRC_ROBORIO__
+namespace frc::impl {
+void ResetShuffleboardInstance() {
+  GetInstanceHolder() = std::make_unique<detail::ShuffleboardInstance>(
       nt::NetworkTableInstance::GetDefault());
-  return inst;
+}
+}  // namespace frc::impl
+#endif
+
+detail::ShuffleboardInstance& Shuffleboard::GetInstance() {
+  return *GetInstanceHolder();
 }
 
 detail::RecordingController& Shuffleboard::GetRecordingController() {
diff --git a/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardComponentBase.cpp b/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardComponentBase.cpp
index 940f6ab..c43fc90 100644
--- a/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardComponentBase.cpp
+++ b/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardComponentBase.cpp
@@ -22,27 +22,21 @@
     return;
   }
   // Component type
-  if (GetType() == "") {
-    metaTable->GetEntry("PreferredComponent").Delete();
-  } else {
-    metaTable->GetEntry("PreferredComponent").ForceSetString(GetType());
+  if (!GetType().empty()) {
+    metaTable->GetEntry("PreferredComponent").SetString(GetType());
   }
 
   // Tile size
-  if (m_width <= 0 || m_height <= 0) {
-    metaTable->GetEntry("Size").Delete();
-  } else {
+  if (m_width > 0 && m_height > 0) {
     metaTable->GetEntry("Size").SetDoubleArray(
-        {static_cast<double>(m_width), static_cast<double>(m_height)});
+        {{static_cast<double>(m_width), static_cast<double>(m_height)}});
   }
 
   // Tile position
-  if (m_column < 0 || m_row < 0) {
-    metaTable->GetEntry("Position").Delete();
-  } else {
+  if (m_column >= 0 && m_row >= 0) {
     metaTable->GetEntry("Position")
         .SetDoubleArray(
-            {static_cast<double>(m_column), static_cast<double>(m_row)});
+            {{static_cast<double>(m_column), static_cast<double>(m_row)}});
   }
 
   // Custom properties
@@ -63,7 +57,7 @@
   return m_type;
 }
 
-const wpi::StringMap<std::shared_ptr<nt::Value>>&
-ShuffleboardComponentBase::GetProperties() const {
+const wpi::StringMap<nt::Value>& ShuffleboardComponentBase::GetProperties()
+    const {
   return m_properties;
 }
diff --git a/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardContainer.cpp b/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardContainer.cpp
index 004f7c5..ca3c6d6 100644
--- a/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardContainer.cpp
+++ b/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardContainer.cpp
@@ -4,6 +4,7 @@
 
 #include "frc/shuffleboard/ShuffleboardContainer.h"
 
+#include <ntcore_cpp.h>
 #include <wpi/sendable/SendableRegistry.h>
 
 #include "frc/Errors.h"
@@ -69,19 +70,20 @@
 ComplexWidget& ShuffleboardContainer::Add(wpi::Sendable& sendable) {
   auto name = wpi::SendableRegistry::GetName(&sendable);
   if (name.empty()) {
-    FRC_ReportError(err::Error, "{}", "Sendable must have a name");
+    FRC_ReportError(err::Error, "Sendable must have a name");
   }
   return Add(name, sendable);
 }
 
-SimpleWidget& ShuffleboardContainer::Add(
-    std::string_view title, std::shared_ptr<nt::Value> defaultValue) {
+SimpleWidget& ShuffleboardContainer::Add(std::string_view title,
+                                         const nt::Value& defaultValue) {
   CheckTitle(title);
 
   auto widget = std::make_unique<SimpleWidget>(*this, title);
   auto ptr = widget.get();
   m_components.emplace_back(std::move(widget));
-  ptr->GetEntry().SetDefaultValue(defaultValue);
+  ptr->GetEntry(nt::GetStringFromType(defaultValue.type()))
+      ->SetDefault(defaultValue);
   return *ptr;
 }
 
@@ -96,8 +98,13 @@
 }
 
 SimpleWidget& ShuffleboardContainer::Add(std::string_view title,
+                                         float defaultValue) {
+  return Add(title, nt::Value::MakeFloat(defaultValue));
+}
+
+SimpleWidget& ShuffleboardContainer::Add(std::string_view title,
                                          int defaultValue) {
-  return Add(title, nt::Value::MakeDouble(defaultValue));
+  return Add(title, nt::Value::MakeInteger(defaultValue));
 }
 
 SimpleWidget& ShuffleboardContainer::Add(std::string_view title,
@@ -111,29 +118,39 @@
 }
 
 SimpleWidget& ShuffleboardContainer::Add(std::string_view title,
-                                         wpi::span<const bool> defaultValue) {
+                                         std::span<const bool> defaultValue) {
   return Add(title, nt::Value::MakeBooleanArray(defaultValue));
 }
 
 SimpleWidget& ShuffleboardContainer::Add(std::string_view title,
-                                         wpi::span<const double> defaultValue) {
+                                         std::span<const double> defaultValue) {
   return Add(title, nt::Value::MakeDoubleArray(defaultValue));
 }
 
+SimpleWidget& ShuffleboardContainer::Add(std::string_view title,
+                                         std::span<const float> defaultValue) {
+  return Add(title, nt::Value::MakeFloatArray(defaultValue));
+}
+
 SimpleWidget& ShuffleboardContainer::Add(
-    std::string_view title, wpi::span<const std::string> defaultValue) {
+    std::string_view title, std::span<const int64_t> defaultValue) {
+  return Add(title, nt::Value::MakeIntegerArray(defaultValue));
+}
+
+SimpleWidget& ShuffleboardContainer::Add(
+    std::string_view title, std::span<const std::string> defaultValue) {
   return Add(title, nt::Value::MakeStringArray(defaultValue));
 }
 
 SuppliedValueWidget<std::string>& ShuffleboardContainer::AddString(
     std::string_view title, std::function<std::string()> supplier) {
-  static auto setter = [](nt::NetworkTableEntry entry, std::string value) {
+  static auto setter = [](nt::GenericPublisher& entry, std::string value) {
     entry.SetString(value);
   };
 
   CheckTitle(title);
   auto widget = std::make_unique<SuppliedValueWidget<std::string>>(
-      *this, title, supplier, setter);
+      *this, title, "string", supplier, setter);
   auto ptr = widget.get();
   m_components.emplace_back(std::move(widget));
   return *ptr;
@@ -141,13 +158,46 @@
 
 SuppliedValueWidget<double>& ShuffleboardContainer::AddNumber(
     std::string_view title, std::function<double()> supplier) {
-  static auto setter = [](nt::NetworkTableEntry entry, double value) {
+  return AddDouble(title, std::move(supplier));
+}
+
+SuppliedValueWidget<double>& ShuffleboardContainer::AddDouble(
+    std::string_view title, std::function<double()> supplier) {
+  static auto setter = [](nt::GenericPublisher& entry, double value) {
     entry.SetDouble(value);
   };
 
   CheckTitle(title);
-  auto widget = std::make_unique<SuppliedValueWidget<double>>(*this, title,
-                                                              supplier, setter);
+  auto widget = std::make_unique<SuppliedValueWidget<double>>(
+      *this, title, "double", supplier, setter);
+  auto ptr = widget.get();
+  m_components.emplace_back(std::move(widget));
+  return *ptr;
+}
+
+SuppliedValueWidget<float>& ShuffleboardContainer::AddFloat(
+    std::string_view title, std::function<float()> supplier) {
+  static auto setter = [](nt::GenericPublisher& entry, float value) {
+    entry.SetFloat(value);
+  };
+
+  CheckTitle(title);
+  auto widget = std::make_unique<SuppliedValueWidget<float>>(
+      *this, title, "float", supplier, setter);
+  auto ptr = widget.get();
+  m_components.emplace_back(std::move(widget));
+  return *ptr;
+}
+
+SuppliedValueWidget<int64_t>& ShuffleboardContainer::AddInteger(
+    std::string_view title, std::function<int64_t()> supplier) {
+  static auto setter = [](nt::GenericPublisher& entry, int64_t value) {
+    entry.SetInteger(value);
+  };
+
+  CheckTitle(title);
+  auto widget = std::make_unique<SuppliedValueWidget<int64_t>>(
+      *this, title, "int", supplier, setter);
   auto ptr = widget.get();
   m_components.emplace_back(std::move(widget));
   return *ptr;
@@ -155,13 +205,13 @@
 
 SuppliedValueWidget<bool>& ShuffleboardContainer::AddBoolean(
     std::string_view title, std::function<bool()> supplier) {
-  static auto setter = [](nt::NetworkTableEntry entry, bool value) {
+  static auto setter = [](nt::GenericPublisher& entry, bool value) {
     entry.SetBoolean(value);
   };
 
   CheckTitle(title);
-  auto widget = std::make_unique<SuppliedValueWidget<bool>>(*this, title,
-                                                            supplier, setter);
+  auto widget = std::make_unique<SuppliedValueWidget<bool>>(
+      *this, title, "boolean", supplier, setter);
   auto ptr = widget.get();
   m_components.emplace_back(std::move(widget));
   return *ptr;
@@ -171,14 +221,14 @@
 ShuffleboardContainer::AddStringArray(
     std::string_view title,
     std::function<std::vector<std::string>()> supplier) {
-  static auto setter = [](nt::NetworkTableEntry entry,
+  static auto setter = [](nt::GenericPublisher& entry,
                           std::vector<std::string> value) {
     entry.SetStringArray(value);
   };
 
   CheckTitle(title);
   auto widget = std::make_unique<SuppliedValueWidget<std::vector<std::string>>>(
-      *this, title, supplier, setter);
+      *this, title, "string[]", supplier, setter);
   auto ptr = widget.get();
   m_components.emplace_back(std::move(widget));
   return *ptr;
@@ -186,14 +236,50 @@
 
 SuppliedValueWidget<std::vector<double>>& ShuffleboardContainer::AddNumberArray(
     std::string_view title, std::function<std::vector<double>()> supplier) {
-  static auto setter = [](nt::NetworkTableEntry entry,
+  return AddDoubleArray(title, std::move(supplier));
+}
+
+SuppliedValueWidget<std::vector<double>>& ShuffleboardContainer::AddDoubleArray(
+    std::string_view title, std::function<std::vector<double>()> supplier) {
+  static auto setter = [](nt::GenericPublisher& entry,
                           std::vector<double> value) {
     entry.SetDoubleArray(value);
   };
 
   CheckTitle(title);
   auto widget = std::make_unique<SuppliedValueWidget<std::vector<double>>>(
-      *this, title, supplier, setter);
+      *this, title, "double[]", supplier, setter);
+  auto ptr = widget.get();
+  m_components.emplace_back(std::move(widget));
+  return *ptr;
+}
+
+SuppliedValueWidget<std::vector<float>>& ShuffleboardContainer::AddFloatArray(
+    std::string_view title, std::function<std::vector<float>()> supplier) {
+  static auto setter = [](nt::GenericPublisher& entry,
+                          std::vector<float> value) {
+    entry.SetFloatArray(value);
+  };
+
+  CheckTitle(title);
+  auto widget = std::make_unique<SuppliedValueWidget<std::vector<float>>>(
+      *this, title, "float[]", supplier, setter);
+  auto ptr = widget.get();
+  m_components.emplace_back(std::move(widget));
+  return *ptr;
+}
+
+SuppliedValueWidget<std::vector<int64_t>>&
+ShuffleboardContainer::AddIntegerArray(
+    std::string_view title, std::function<std::vector<int64_t>()> supplier) {
+  static auto setter = [](nt::GenericPublisher& entry,
+                          std::vector<int64_t> value) {
+    entry.SetIntegerArray(value);
+  };
+
+  CheckTitle(title);
+  auto widget = std::make_unique<SuppliedValueWidget<std::vector<int64_t>>>(
+      *this, title, "int[]", supplier, setter);
   auto ptr = widget.get();
   m_components.emplace_back(std::move(widget));
   return *ptr;
@@ -201,36 +287,43 @@
 
 SuppliedValueWidget<std::vector<int>>& ShuffleboardContainer::AddBooleanArray(
     std::string_view title, std::function<std::vector<int>()> supplier) {
-  static auto setter = [](nt::NetworkTableEntry entry, std::vector<int> value) {
+  static auto setter = [](nt::GenericPublisher& entry, std::vector<int> value) {
     entry.SetBooleanArray(value);
   };
 
   CheckTitle(title);
   auto widget = std::make_unique<SuppliedValueWidget<std::vector<int>>>(
-      *this, title, supplier, setter);
+      *this, title, "boolean[]", supplier, setter);
   auto ptr = widget.get();
   m_components.emplace_back(std::move(widget));
   return *ptr;
 }
 
-SuppliedValueWidget<std::string_view>& ShuffleboardContainer::AddRaw(
-    std::string_view title, std::function<std::string_view()> supplier) {
-  static auto setter = [](nt::NetworkTableEntry entry, std::string_view value) {
-    entry.SetRaw(value);
-  };
+SuppliedValueWidget<std::vector<uint8_t>>& ShuffleboardContainer::AddRaw(
+    std::string_view title, std::function<std::vector<uint8_t>()> supplier) {
+  return AddRaw(title, "raw", std::move(supplier));
+}
+
+SuppliedValueWidget<std::vector<uint8_t>>& ShuffleboardContainer::AddRaw(
+    std::string_view title, std::string_view typeString,
+    std::function<std::vector<uint8_t>()> supplier) {
+  static auto setter = [](nt::GenericPublisher& entry,
+                          std::vector<uint8_t> value) { entry.SetRaw(value); };
 
   CheckTitle(title);
-  auto widget = std::make_unique<SuppliedValueWidget<std::string_view>>(
-      *this, title, supplier, setter);
+  auto widget = std::make_unique<SuppliedValueWidget<std::vector<uint8_t>>>(
+      *this, title, typeString, supplier, setter);
   auto ptr = widget.get();
   m_components.emplace_back(std::move(widget));
   return *ptr;
 }
 
 SimpleWidget& ShuffleboardContainer::AddPersistent(
-    std::string_view title, std::shared_ptr<nt::Value> defaultValue) {
+    std::string_view title, const nt::Value& defaultValue) {
   auto& widget = Add(title, defaultValue);
-  widget.GetEntry().SetPersistent();
+  widget.GetEntry(nt::GetStringFromType(defaultValue.type()))
+      ->GetTopic()
+      .SetPersistent(true);
   return widget;
 }
 
@@ -245,8 +338,13 @@
 }
 
 SimpleWidget& ShuffleboardContainer::AddPersistent(std::string_view title,
+                                                   float defaultValue) {
+  return AddPersistent(title, nt::Value::MakeFloat(defaultValue));
+}
+
+SimpleWidget& ShuffleboardContainer::AddPersistent(std::string_view title,
                                                    int defaultValue) {
-  return AddPersistent(title, nt::Value::MakeDouble(defaultValue));
+  return AddPersistent(title, nt::Value::MakeInteger(defaultValue));
 }
 
 SimpleWidget& ShuffleboardContainer::AddPersistent(
@@ -255,17 +353,27 @@
 }
 
 SimpleWidget& ShuffleboardContainer::AddPersistent(
-    std::string_view title, wpi::span<const bool> defaultValue) {
+    std::string_view title, std::span<const bool> defaultValue) {
   return AddPersistent(title, nt::Value::MakeBooleanArray(defaultValue));
 }
 
 SimpleWidget& ShuffleboardContainer::AddPersistent(
-    std::string_view title, wpi::span<const double> defaultValue) {
+    std::string_view title, std::span<const double> defaultValue) {
   return AddPersistent(title, nt::Value::MakeDoubleArray(defaultValue));
 }
 
 SimpleWidget& ShuffleboardContainer::AddPersistent(
-    std::string_view title, wpi::span<const std::string> defaultValue) {
+    std::string_view title, std::span<const float> defaultValue) {
+  return AddPersistent(title, nt::Value::MakeFloatArray(defaultValue));
+}
+
+SimpleWidget& ShuffleboardContainer::AddPersistent(
+    std::string_view title, std::span<const int64_t> defaultValue) {
+  return AddPersistent(title, nt::Value::MakeIntegerArray(defaultValue));
+}
+
+SimpleWidget& ShuffleboardContainer::AddPersistent(
+    std::string_view title, std::span<const std::string> defaultValue) {
   return AddPersistent(title, nt::Value::MakeStringArray(defaultValue));
 }
 
diff --git a/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardInstance.cpp b/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardInstance.cpp
index 083b4a2..a315b90 100644
--- a/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardInstance.cpp
+++ b/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardInstance.cpp
@@ -46,7 +46,7 @@
     for (auto& entry : m_impl->tabs) {
       tabTitles.emplace_back(entry.second->GetTitle());
     }
-    m_impl->rootMetaTable->GetEntry("Tabs").ForceSetStringArray(tabTitles);
+    m_impl->rootMetaTable->GetEntry("Tabs").SetStringArray(tabTitles);
     m_impl->tabsChanged = false;
   }
   for (auto& entry : m_impl->tabs) {
@@ -75,9 +75,9 @@
 }
 
 void ShuffleboardInstance::SelectTab(int index) {
-  m_impl->rootMetaTable->GetEntry("Selected").ForceSetDouble(index);
+  m_impl->rootMetaTable->GetEntry("Selected").SetDouble(index);
 }
 
 void ShuffleboardInstance::SelectTab(std::string_view title) {
-  m_impl->rootMetaTable->GetEntry("Selected").ForceSetString(title);
+  m_impl->rootMetaTable->GetEntry("Selected").SetString(title);
 }
diff --git a/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardWidget.cpp b/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardWidget.cpp
index 31e4b11..bb475fd 100644
--- a/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardWidget.cpp
+++ b/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardWidget.cpp
@@ -20,7 +20,7 @@
     "ComboBox Chooser",
     "Split Button Chooser",
     "Encoder",
-    "Speed Controller",
+    "Motor Controller",
     "Command",
     "PID Command",
     "PID Controller",
diff --git a/wpilibc/src/main/native/cpp/shuffleboard/SimpleWidget.cpp b/wpilibc/src/main/native/cpp/shuffleboard/SimpleWidget.cpp
index 390c9c4..c62b76d 100644
--- a/wpilibc/src/main/native/cpp/shuffleboard/SimpleWidget.cpp
+++ b/wpilibc/src/main/native/cpp/shuffleboard/SimpleWidget.cpp
@@ -14,18 +14,26 @@
                            std::string_view title)
     : ShuffleboardValue(title), ShuffleboardWidget(parent, title), m_entry() {}
 
-nt::NetworkTableEntry SimpleWidget::GetEntry() {
+nt::GenericEntry* SimpleWidget::GetEntry() {
   if (!m_entry) {
     ForceGenerate();
   }
-  return m_entry;
+  return &m_entry;
+}
+
+nt::GenericEntry* SimpleWidget::GetEntry(std::string_view typeString) {
+  if (!m_entry) {
+    m_typeString = typeString;
+    ForceGenerate();
+  }
+  return &m_entry;
 }
 
 void SimpleWidget::BuildInto(std::shared_ptr<nt::NetworkTable> parentTable,
                              std::shared_ptr<nt::NetworkTable> metaTable) {
   BuildMetadata(metaTable);
   if (!m_entry) {
-    m_entry = parentTable->GetEntry(GetTitle());
+    m_entry = parentTable->GetTopic(GetTitle()).GetGenericEntry(m_typeString);
   }
 }
 
diff --git a/wpilibc/src/main/native/cpp/simulation/AnalogEncoderSim.cpp b/wpilibc/src/main/native/cpp/simulation/AnalogEncoderSim.cpp
index 049241e..435e29e 100644
--- a/wpilibc/src/main/native/cpp/simulation/AnalogEncoderSim.cpp
+++ b/wpilibc/src/main/native/cpp/simulation/AnalogEncoderSim.cpp
@@ -27,6 +27,5 @@
 }
 
 frc::Rotation2d AnalogEncoderSim::GetPosition() {
-  units::radian_t rads = GetTurns();
-  return frc::Rotation2d{rads};
+  return units::radian_t{GetTurns()};
 }
diff --git a/wpilibc/src/main/native/cpp/simulation/CTREPCMSim.cpp b/wpilibc/src/main/native/cpp/simulation/CTREPCMSim.cpp
index 9c7450b..0081928 100644
--- a/wpilibc/src/main/native/cpp/simulation/CTREPCMSim.cpp
+++ b/wpilibc/src/main/native/cpp/simulation/CTREPCMSim.cpp
@@ -14,12 +14,13 @@
 using namespace frc;
 using namespace frc::sim;
 
-CTREPCMSim::CTREPCMSim() : m_index{SensorUtil::GetDefaultCTREPCMModule()} {}
+CTREPCMSim::CTREPCMSim()
+    : PneumaticsBaseSim{SensorUtil::GetDefaultCTREPCMModule()} {}
 
-CTREPCMSim::CTREPCMSim(int module) : m_index{module} {}
+CTREPCMSim::CTREPCMSim(int module) : PneumaticsBaseSim{module} {}
 
 CTREPCMSim::CTREPCMSim(const PneumaticsBase& pneumatics)
-    : m_index{pneumatics.GetModuleNumber()} {}
+    : PneumaticsBaseSim{pneumatics.GetModuleNumber()} {}
 
 std::unique_ptr<CallbackStore> CTREPCMSim::RegisterInitializedCallback(
     NotifyCallback callback, bool initialNotify) {
diff --git a/wpilibc/src/main/native/cpp/simulation/DCMotorSim.cpp b/wpilibc/src/main/native/cpp/simulation/DCMotorSim.cpp
index 7860e32..bda2020 100644
--- a/wpilibc/src/main/native/cpp/simulation/DCMotorSim.cpp
+++ b/wpilibc/src/main/native/cpp/simulation/DCMotorSim.cpp
@@ -42,5 +42,5 @@
 }
 
 void DCMotorSim::SetInputVoltage(units::volt_t voltage) {
-  SetInput(Eigen::Vector<double, 1>{voltage.value()});
+  SetInput(Vectord<1>{voltage.value()});
 }
diff --git a/wpilibc/src/main/native/cpp/simulation/DifferentialDrivetrainSim.cpp b/wpilibc/src/main/native/cpp/simulation/DifferentialDrivetrainSim.cpp
index 78b9dc7..b6c95dc 100644
--- a/wpilibc/src/main/native/cpp/simulation/DifferentialDrivetrainSim.cpp
+++ b/wpilibc/src/main/native/cpp/simulation/DifferentialDrivetrainSim.cpp
@@ -41,8 +41,7 @@
               driveMotor, mass, wheelRadius, trackWidth / 2.0, J, gearing),
           trackWidth, driveMotor, gearing, wheelRadius, measurementStdDevs) {}
 
-Eigen::Vector<double, 2> DifferentialDrivetrainSim::ClampInput(
-    const Eigen::Vector<double, 2>& u) {
+Vectord<2> DifferentialDrivetrainSim::ClampInput(const Vectord<2>& u) {
   return frc::DesaturateInputVector<2>(u,
                                        frc::RobotController::GetInputVoltage());
 }
@@ -66,11 +65,11 @@
   return m_currentGearing;
 }
 
-Eigen::Vector<double, 7> DifferentialDrivetrainSim::GetOutput() const {
+Vectord<7> DifferentialDrivetrainSim::GetOutput() const {
   return m_y;
 }
 
-Eigen::Vector<double, 7> DifferentialDrivetrainSim::GetState() const {
+Vectord<7> DifferentialDrivetrainSim::GetState() const {
   return m_x;
 }
 
@@ -83,20 +82,20 @@
 }
 
 Rotation2d DifferentialDrivetrainSim::GetHeading() const {
-  return Rotation2d(units::radian_t(GetOutput(State::kHeading)));
+  return units::radian_t{GetOutput(State::kHeading)};
 }
 
 Pose2d DifferentialDrivetrainSim::GetPose() const {
-  return Pose2d(units::meter_t(GetOutput(State::kX)),
-                units::meter_t(GetOutput(State::kY)), GetHeading());
+  return Pose2d{units::meter_t{GetOutput(State::kX)},
+                units::meter_t{GetOutput(State::kY)}, GetHeading()};
 }
 
 units::ampere_t DifferentialDrivetrainSim::GetLeftCurrentDraw() const {
   auto loadIleft =
       m_motor.Current(
-          units::radians_per_second_t(m_x(State::kLeftVelocity) *
-                                      m_currentGearing / m_wheelRadius.value()),
-          units::volt_t(m_u(0))) *
+          units::radians_per_second_t{m_x(State::kLeftVelocity) *
+                                      m_currentGearing / m_wheelRadius.value()},
+          units::volt_t{m_u(0)}) *
       wpi::sgn(m_u(0));
 
   return loadIleft;
@@ -105,9 +104,9 @@
 units::ampere_t DifferentialDrivetrainSim::GetRightCurrentDraw() const {
   auto loadIRight =
       m_motor.Current(
-          units::radians_per_second_t(m_x(State::kRightVelocity) *
-                                      m_currentGearing / m_wheelRadius.value()),
-          units::volt_t(m_u(1))) *
+          units::radians_per_second_t{m_x(State::kRightVelocity) *
+                                      m_currentGearing / m_wheelRadius.value()},
+          units::volt_t{m_u(1)}) *
       wpi::sgn(m_u(1));
 
   return loadIRight;
@@ -116,8 +115,7 @@
   return GetLeftCurrentDraw() + GetRightCurrentDraw();
 }
 
-void DifferentialDrivetrainSim::SetState(
-    const Eigen::Vector<double, 7>& state) {
+void DifferentialDrivetrainSim::SetState(const Vectord<7>& state) {
   m_x = state;
 }
 
@@ -129,19 +127,19 @@
   m_x(State::kRightPosition) = 0;
 }
 
-Eigen::Vector<double, 7> DifferentialDrivetrainSim::Dynamics(
-    const Eigen::Vector<double, 7>& x, const Eigen::Vector<double, 2>& u) {
-  // Because G^2 can be factored out of A, we can divide by the old ratio
+Vectord<7> DifferentialDrivetrainSim::Dynamics(const Vectord<7>& x,
+                                               const Vectord<2>& u) {
+  // Because G² can be factored out of A, we can divide by the old ratio
   // squared and multiply by the new ratio squared to get a new drivetrain
   // model.
-  Eigen::Matrix<double, 4, 2> B;
+  Matrixd<4, 2> B;
   B.block<2, 2>(0, 0) = m_plant.B() * m_currentGearing * m_currentGearing /
                         m_originalGearing / m_originalGearing;
   B.block<2, 2>(2, 0).setZero();
 
   // Because G can be factored out of B, we can divide by the old ratio and
   // multiply by the new ratio to get a new drivetrain model.
-  Eigen::Matrix<double, 4, 4> A;
+  Matrixd<4, 4> A;
   A.block<2, 2>(0, 0) = m_plant.A() * m_currentGearing / m_originalGearing;
 
   A.block<2, 2>(2, 0).setIdentity();
@@ -149,7 +147,7 @@
 
   double v = (x(State::kLeftVelocity) + x(State::kRightVelocity)) / 2.0;
 
-  Eigen::Vector<double, 7> xdot;
+  Vectord<7> xdot;
   xdot(0) = v * std::cos(x(State::kHeading));
   xdot(1) = v * std::sin(x(State::kHeading));
   xdot(2) =
diff --git a/wpilibc/src/main/native/cpp/simulation/DoubleSolenoidSim.cpp b/wpilibc/src/main/native/cpp/simulation/DoubleSolenoidSim.cpp
new file mode 100644
index 0000000..da99867
--- /dev/null
+++ b/wpilibc/src/main/native/cpp/simulation/DoubleSolenoidSim.cpp
@@ -0,0 +1,48 @@
+// 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 "frc/simulation/DoubleSolenoidSim.h"
+
+#include "frc/PneumaticsBase.h"
+
+using namespace frc;
+using namespace frc::sim;
+
+DoubleSolenoidSim::DoubleSolenoidSim(
+    std::shared_ptr<PneumaticsBaseSim> moduleSim, int fwd, int rev)
+    : m_module{std::move(moduleSim)}, m_fwd{fwd}, m_rev{rev} {}
+
+DoubleSolenoidSim::DoubleSolenoidSim(int module, PneumaticsModuleType type,
+                                     int fwd, int rev)
+    : m_module{PneumaticsBaseSim::GetForType(module, type)},
+      m_fwd{fwd},
+      m_rev{rev} {}
+
+DoubleSolenoidSim::DoubleSolenoidSim(PneumaticsModuleType type, int fwd,
+                                     int rev)
+    : m_module{PneumaticsBaseSim::GetForType(
+          PneumaticsBase::GetDefaultForType(type), type)},
+      m_fwd{fwd},
+      m_rev{rev} {}
+
+DoubleSolenoid::Value DoubleSolenoidSim::Get() const {
+  bool fwdState = m_module->GetSolenoidOutput(m_fwd);
+  bool revState = m_module->GetSolenoidOutput(m_rev);
+  if (fwdState && !revState) {
+    return DoubleSolenoid::Value::kForward;
+  } else if (!fwdState && revState) {
+    return DoubleSolenoid::Value::kReverse;
+  } else {
+    return DoubleSolenoid::Value::kOff;
+  }
+}
+
+void DoubleSolenoidSim::Set(DoubleSolenoid::Value output) {
+  m_module->SetSolenoidOutput(m_fwd, output == DoubleSolenoid::Value::kForward);
+  m_module->SetSolenoidOutput(m_rev, output == DoubleSolenoid::Value::kReverse);
+}
+
+std::shared_ptr<PneumaticsBaseSim> DoubleSolenoidSim::GetModuleSim() const {
+  return m_module;
+}
diff --git a/wpilibc/src/main/native/cpp/simulation/DriverStationSim.cpp b/wpilibc/src/main/native/cpp/simulation/DriverStationSim.cpp
index 5f2c645..345204a 100644
--- a/wpilibc/src/main/native/cpp/simulation/DriverStationSim.cpp
+++ b/wpilibc/src/main/native/cpp/simulation/DriverStationSim.cpp
@@ -7,6 +7,7 @@
 #include <memory>
 #include <utility>
 
+#include <hal/DriverStation.h>
 #include <hal/simulation/DriverStationData.h>
 #include <hal/simulation/MockHooks.h>
 
@@ -154,8 +155,12 @@
 }
 
 void DriverStationSim::NotifyNewData() {
+  wpi::Event waitEvent{true};
+  HAL_ProvideNewDataEventHandle(waitEvent.GetHandle());
   HALSIM_NotifyDriverStationNewData();
-  DriverStation::WaitForData();
+  wpi::WaitForObject(waitEvent.GetHandle());
+  HAL_RemoveNewDataEventHandle(waitEvent.GetHandle());
+  frc::DriverStation::RefreshData();
 }
 
 void DriverStationSim::SetSendError(bool shouldSend) {
@@ -229,20 +234,20 @@
   HALSIM_SetJoystickType(stick, type);
 }
 
-void DriverStationSim::SetJoystickName(int stick, const char* name) {
-  HALSIM_SetJoystickName(stick, name);
+void DriverStationSim::SetJoystickName(int stick, std::string_view name) {
+  HALSIM_SetJoystickName(stick, name.data(), name.size());
 }
 
 void DriverStationSim::SetJoystickAxisType(int stick, int axis, int type) {
   HALSIM_SetJoystickAxisType(stick, axis, type);
 }
 
-void DriverStationSim::SetGameSpecificMessage(const char* message) {
-  HALSIM_SetGameSpecificMessage(message);
+void DriverStationSim::SetGameSpecificMessage(std::string_view message) {
+  HALSIM_SetGameSpecificMessage(message.data(), message.size());
 }
 
-void DriverStationSim::SetEventName(const char* name) {
-  HALSIM_SetEventName(name);
+void DriverStationSim::SetEventName(std::string_view name) {
+  HALSIM_SetEventName(name.data(), name.size());
 }
 
 void DriverStationSim::SetMatchType(DriverStation::MatchType type) {
diff --git a/wpilibc/src/main/native/cpp/simulation/ElevatorSim.cpp b/wpilibc/src/main/native/cpp/simulation/ElevatorSim.cpp
index a2d5c66..529fb1a 100644
--- a/wpilibc/src/main/native/cpp/simulation/ElevatorSim.cpp
+++ b/wpilibc/src/main/native/cpp/simulation/ElevatorSim.cpp
@@ -15,19 +15,20 @@
 ElevatorSim::ElevatorSim(const LinearSystem<2, 1, 1>& plant,
                          const DCMotor& gearbox, double gearing,
                          units::meter_t drumRadius, units::meter_t minHeight,
-                         units::meter_t maxHeight,
+                         units::meter_t maxHeight, bool simulateGravity,
                          const std::array<double, 1>& measurementStdDevs)
     : LinearSystemSim(plant, measurementStdDevs),
       m_gearbox(gearbox),
       m_drumRadius(drumRadius),
       m_minHeight(minHeight),
       m_maxHeight(maxHeight),
-      m_gearing(gearing) {}
+      m_gearing(gearing),
+      m_simulateGravity(simulateGravity) {}
 
 ElevatorSim::ElevatorSim(const DCMotor& gearbox, double gearing,
                          units::kilogram_t carriageMass,
                          units::meter_t drumRadius, units::meter_t minHeight,
-                         units::meter_t maxHeight,
+                         units::meter_t maxHeight, bool simulateGravity,
                          const std::array<double, 1>& measurementStdDevs)
     : LinearSystemSim(LinearSystemId::ElevatorSystem(gearbox, carriageMass,
                                                      drumRadius, gearing),
@@ -36,7 +37,8 @@
       m_drumRadius(drumRadius),
       m_minHeight(minHeight),
       m_maxHeight(maxHeight),
-      m_gearing(gearing) {}
+      m_gearing(gearing),
+      m_simulateGravity(simulateGravity) {}
 
 bool ElevatorSim::WouldHitLowerLimit(units::meter_t elevatorHeight) const {
   return elevatorHeight < m_minHeight;
@@ -47,11 +49,11 @@
 }
 
 bool ElevatorSim::HasHitLowerLimit() const {
-  return WouldHitLowerLimit(units::meter_t(m_y(0)));
+  return WouldHitLowerLimit(units::meter_t{m_y(0)});
 }
 
 bool ElevatorSim::HasHitUpperLimit() const {
-  return WouldHitUpperLimit(units::meter_t(m_y(0)));
+  return WouldHitUpperLimit(units::meter_t{m_y(0)});
 }
 
 units::meter_t ElevatorSim::GetPosition() const {
@@ -78,25 +80,27 @@
 }
 
 void ElevatorSim::SetInputVoltage(units::volt_t voltage) {
-  SetInput(Eigen::Vector<double, 1>{voltage.value()});
+  SetInput(Vectord<1>{voltage.value()});
 }
 
-Eigen::Vector<double, 2> ElevatorSim::UpdateX(
-    const Eigen::Vector<double, 2>& currentXhat,
-    const Eigen::Vector<double, 1>& u, units::second_t dt) {
+Vectord<2> ElevatorSim::UpdateX(const Vectord<2>& currentXhat,
+                                const Vectord<1>& u, units::second_t dt) {
   auto updatedXhat = RKDP(
-      [&](const Eigen::Vector<double, 2>& x,
-          const Eigen::Vector<double, 1>& u_) -> Eigen::Vector<double, 2> {
-        return m_plant.A() * x + m_plant.B() * u_ +
-               Eigen::Vector<double, 2>{0.0, -9.8};
+      [&](const Vectord<2>& x, const Vectord<1>& u_) -> Vectord<2> {
+        Vectord<2> xdot = m_plant.A() * x + m_plant.B() * u;
+
+        if (m_simulateGravity) {
+          xdot += Vectord<2>{0.0, -9.8};
+        }
+        return xdot;
       },
       currentXhat, u, dt);
   // Check for collision after updating x-hat.
-  if (WouldHitLowerLimit(units::meter_t(updatedXhat(0)))) {
-    return Eigen::Vector<double, 2>{m_minHeight.value(), 0.0};
+  if (WouldHitLowerLimit(units::meter_t{updatedXhat(0)})) {
+    return Vectord<2>{m_minHeight.value(), 0.0};
   }
-  if (WouldHitUpperLimit(units::meter_t(updatedXhat(0)))) {
-    return Eigen::Vector<double, 2>{m_maxHeight.value(), 0.0};
+  if (WouldHitUpperLimit(units::meter_t{updatedXhat(0)})) {
+    return Vectord<2>{m_maxHeight.value(), 0.0};
   }
   return updatedXhat;
 }
diff --git a/wpilibc/src/main/native/cpp/simulation/FlywheelSim.cpp b/wpilibc/src/main/native/cpp/simulation/FlywheelSim.cpp
index f4371b1..95dcb9e 100644
--- a/wpilibc/src/main/native/cpp/simulation/FlywheelSim.cpp
+++ b/wpilibc/src/main/native/cpp/simulation/FlywheelSim.cpp
@@ -38,5 +38,5 @@
 }
 
 void FlywheelSim::SetInputVoltage(units::volt_t voltage) {
-  SetInput(Eigen::Vector<double, 1>{voltage.value()});
+  SetInput(Vectord<1>{voltage.value()});
 }
diff --git a/wpilibc/src/main/native/cpp/simulation/PS4ControllerSim.cpp b/wpilibc/src/main/native/cpp/simulation/PS4ControllerSim.cpp
index 3403755..fecac9d 100644
--- a/wpilibc/src/main/native/cpp/simulation/PS4ControllerSim.cpp
+++ b/wpilibc/src/main/native/cpp/simulation/PS4ControllerSim.cpp
@@ -13,11 +13,13 @@
     : GenericHIDSim{joystick} {
   SetAxisCount(6);
   SetButtonCount(14);
+  SetPOVCount(1);
 }
 
 PS4ControllerSim::PS4ControllerSim(int port) : GenericHIDSim{port} {
   SetAxisCount(6);
   SetButtonCount(14);
+  SetPOVCount(1);
 }
 
 void PS4ControllerSim::SetLeftX(double value) {
diff --git a/wpilibc/src/main/native/cpp/simulation/PWMSim.cpp b/wpilibc/src/main/native/cpp/simulation/PWMSim.cpp
index f5d69db..3fafa8e 100644
--- a/wpilibc/src/main/native/cpp/simulation/PWMSim.cpp
+++ b/wpilibc/src/main/native/cpp/simulation/PWMSim.cpp
@@ -10,12 +10,16 @@
 #include <hal/simulation/PWMData.h>
 
 #include "frc/PWM.h"
+#include "frc/motorcontrol/PWMMotorController.h"
 
 using namespace frc;
 using namespace frc::sim;
 
 PWMSim::PWMSim(const PWM& pwm) : m_index{pwm.GetChannel()} {}
 
+PWMSim::PWMSim(const PWMMotorController& motorctrl)
+    : m_index{motorctrl.GetChannel()} {}
+
 PWMSim::PWMSim(int channel) : m_index{channel} {}
 
 std::unique_ptr<CallbackStore> PWMSim::RegisterInitializedCallback(
diff --git a/wpilibc/src/main/native/cpp/simulation/PneumaticsBaseSim.cpp b/wpilibc/src/main/native/cpp/simulation/PneumaticsBaseSim.cpp
new file mode 100644
index 0000000..476d07a
--- /dev/null
+++ b/wpilibc/src/main/native/cpp/simulation/PneumaticsBaseSim.cpp
@@ -0,0 +1,33 @@
+// 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 "frc/simulation/PneumaticsBaseSim.h"
+
+#include "frc/Errors.h"
+#include "frc/PneumaticsModuleType.h"
+#include "frc/simulation/CTREPCMSim.h"
+#include "frc/simulation/REVPHSim.h"
+
+using namespace frc;
+using namespace frc::sim;
+
+PneumaticsBaseSim::PneumaticsBaseSim(int module) : m_index{module} {}
+
+PneumaticsBaseSim::PneumaticsBaseSim(const PneumaticsBase& module)
+    : m_index{module.GetModuleNumber()} {}
+
+std::shared_ptr<PneumaticsBaseSim> PneumaticsBaseSim::GetForType(
+    int module, PneumaticsModuleType type) {
+  switch (type) {
+    case PneumaticsModuleType::REVPH:
+      return std::make_shared<REVPHSim>(module);
+
+    case PneumaticsModuleType::CTREPCM:
+      return std::make_shared<CTREPCMSim>(module);
+
+    default:
+      throw FRC_MakeError(err::InvalidParameter, "{}",
+                          static_cast<int>(module));
+  }
+}
diff --git a/wpilibc/src/main/native/cpp/simulation/REVPHSim.cpp b/wpilibc/src/main/native/cpp/simulation/REVPHSim.cpp
index ca7384a..df7bc02 100644
--- a/wpilibc/src/main/native/cpp/simulation/REVPHSim.cpp
+++ b/wpilibc/src/main/native/cpp/simulation/REVPHSim.cpp
@@ -14,12 +14,12 @@
 using namespace frc;
 using namespace frc::sim;
 
-REVPHSim::REVPHSim() : m_index{SensorUtil::GetDefaultREVPHModule()} {}
+REVPHSim::REVPHSim() : PneumaticsBaseSim{SensorUtil::GetDefaultREVPHModule()} {}
 
-REVPHSim::REVPHSim(int module) : m_index{module} {}
+REVPHSim::REVPHSim(int module) : PneumaticsBaseSim{module} {}
 
 REVPHSim::REVPHSim(const PneumaticsBase& pneumatics)
-    : m_index{pneumatics.GetModuleNumber()} {}
+    : PneumaticsBaseSim{pneumatics} {}
 
 std::unique_ptr<CallbackStore> REVPHSim::RegisterInitializedCallback(
     NotifyCallback callback, bool initialNotify) {
diff --git a/wpilibc/src/main/native/cpp/simulation/RoboRioSim.cpp b/wpilibc/src/main/native/cpp/simulation/RoboRioSim.cpp
index dc2b557..6d0f809 100644
--- a/wpilibc/src/main/native/cpp/simulation/RoboRioSim.cpp
+++ b/wpilibc/src/main/native/cpp/simulation/RoboRioSim.cpp
@@ -39,7 +39,7 @@
 }
 
 units::volt_t RoboRioSim::GetVInVoltage() {
-  return units::volt_t(HALSIM_GetRoboRioVInVoltage());
+  return units::volt_t{HALSIM_GetRoboRioVInVoltage()};
 }
 
 void RoboRioSim::SetVInVoltage(units::volt_t vInVoltage) {
@@ -56,7 +56,7 @@
 }
 
 units::ampere_t RoboRioSim::GetVInCurrent() {
-  return units::ampere_t(HALSIM_GetRoboRioVInCurrent());
+  return units::ampere_t{HALSIM_GetRoboRioVInCurrent()};
 }
 
 void RoboRioSim::SetVInCurrent(units::ampere_t vInCurrent) {
@@ -73,7 +73,7 @@
 }
 
 units::volt_t RoboRioSim::GetUserVoltage6V() {
-  return units::volt_t(HALSIM_GetRoboRioUserVoltage6V());
+  return units::volt_t{HALSIM_GetRoboRioUserVoltage6V()};
 }
 
 void RoboRioSim::SetUserVoltage6V(units::volt_t userVoltage6V) {
@@ -90,7 +90,7 @@
 }
 
 units::ampere_t RoboRioSim::GetUserCurrent6V() {
-  return units::ampere_t(HALSIM_GetRoboRioUserCurrent6V());
+  return units::ampere_t{HALSIM_GetRoboRioUserCurrent6V()};
 }
 
 void RoboRioSim::SetUserCurrent6V(units::ampere_t userCurrent6V) {
@@ -124,7 +124,7 @@
 }
 
 units::volt_t RoboRioSim::GetUserVoltage5V() {
-  return units::volt_t(HALSIM_GetRoboRioUserVoltage5V());
+  return units::volt_t{HALSIM_GetRoboRioUserVoltage5V()};
 }
 
 void RoboRioSim::SetUserVoltage5V(units::volt_t userVoltage5V) {
@@ -141,7 +141,7 @@
 }
 
 units::ampere_t RoboRioSim::GetUserCurrent5V() {
-  return units::ampere_t(HALSIM_GetRoboRioUserCurrent5V());
+  return units::ampere_t{HALSIM_GetRoboRioUserCurrent5V()};
 }
 
 void RoboRioSim::SetUserCurrent5V(units::ampere_t userCurrent5V) {
@@ -175,7 +175,7 @@
 }
 
 units::volt_t RoboRioSim::GetUserVoltage3V3() {
-  return units::volt_t(HALSIM_GetRoboRioUserVoltage3V3());
+  return units::volt_t{HALSIM_GetRoboRioUserVoltage3V3()};
 }
 
 void RoboRioSim::SetUserVoltage3V3(units::volt_t userVoltage3V3) {
@@ -192,7 +192,7 @@
 }
 
 units::ampere_t RoboRioSim::GetUserCurrent3V3() {
-  return units::ampere_t(HALSIM_GetRoboRioUserCurrent3V3());
+  return units::ampere_t{HALSIM_GetRoboRioUserCurrent3V3()};
 }
 
 void RoboRioSim::SetUserCurrent3V3(units::ampere_t userCurrent3V3) {
@@ -277,13 +277,33 @@
 }
 
 units::volt_t RoboRioSim::GetBrownoutVoltage() {
-  return units::volt_t(HALSIM_GetRoboRioBrownoutVoltage());
+  return units::volt_t{HALSIM_GetRoboRioBrownoutVoltage()};
 }
 
 void RoboRioSim::SetBrownoutVoltage(units::volt_t vInVoltage) {
   HALSIM_SetRoboRioBrownoutVoltage(vInVoltage.value());
 }
 
+std::string RoboRioSim::GetSerialNumber() {
+  char serialNum[9];
+  size_t len = HALSIM_GetRoboRioSerialNumber(serialNum, sizeof(serialNum));
+  return std::string(serialNum, len);
+}
+
+void RoboRioSim::SetSerialNumber(std::string_view serialNumber) {
+  HALSIM_SetRoboRioSerialNumber(serialNumber.data(), serialNumber.size());
+}
+
+std::string RoboRioSim::GetComments() {
+  char comments[65];
+  size_t len = HALSIM_GetRoboRioComments(comments, sizeof(comments));
+  return std::string(comments, len);
+}
+
+void RoboRioSim::SetComments(std::string_view comments) {
+  HALSIM_SetRoboRioComments(comments.data(), comments.size());
+}
+
 void RoboRioSim::ResetData() {
   HALSIM_ResetRoboRioData();
 }
diff --git a/wpilibc/src/main/native/cpp/simulation/SingleJointedArmSim.cpp b/wpilibc/src/main/native/cpp/simulation/SingleJointedArmSim.cpp
index db56434..88e45c6 100644
--- a/wpilibc/src/main/native/cpp/simulation/SingleJointedArmSim.cpp
+++ b/wpilibc/src/main/native/cpp/simulation/SingleJointedArmSim.cpp
@@ -18,13 +18,13 @@
 SingleJointedArmSim::SingleJointedArmSim(
     const LinearSystem<2, 1, 1>& system, const DCMotor& gearbox, double gearing,
     units::meter_t armLength, units::radian_t minAngle,
-    units::radian_t maxAngle, units::kilogram_t mass, bool simulateGravity,
+    units::radian_t maxAngle, units::kilogram_t armMass, bool simulateGravity,
     const std::array<double, 1>& measurementStdDevs)
     : LinearSystemSim<2, 1, 1>(system, measurementStdDevs),
       m_r(armLength),
       m_minAngle(minAngle),
       m_maxAngle(maxAngle),
-      m_mass(mass),
+      m_armMass(armMass),
       m_gearbox(gearbox),
       m_gearing(gearing),
       m_simulateGravity(simulateGravity) {}
@@ -32,27 +32,27 @@
 SingleJointedArmSim::SingleJointedArmSim(
     const DCMotor& gearbox, double gearing, units::kilogram_square_meter_t moi,
     units::meter_t armLength, units::radian_t minAngle,
-    units::radian_t maxAngle, units::kilogram_t mass, bool simulateGravity,
+    units::radian_t maxAngle, units::kilogram_t armMass, bool simulateGravity,
     const std::array<double, 1>& measurementStdDevs)
     : SingleJointedArmSim(
           LinearSystemId::SingleJointedArmSystem(gearbox, moi, gearing),
-          gearbox, gearing, armLength, minAngle, maxAngle, mass,
+          gearbox, gearing, armLength, minAngle, maxAngle, armMass,
           simulateGravity, measurementStdDevs) {}
 
 bool SingleJointedArmSim::WouldHitLowerLimit(units::radian_t armAngle) const {
-  return armAngle < m_minAngle;
+  return armAngle <= m_minAngle;
 }
 
 bool SingleJointedArmSim::WouldHitUpperLimit(units::radian_t armAngle) const {
-  return armAngle > m_maxAngle;
+  return armAngle >= m_maxAngle;
 }
 
 bool SingleJointedArmSim::HasHitLowerLimit() const {
-  return WouldHitLowerLimit(units::radian_t(m_y(0)));
+  return WouldHitLowerLimit(units::radian_t{m_y(0)});
 }
 
 bool SingleJointedArmSim::HasHitUpperLimit() const {
-  return WouldHitUpperLimit(units::radian_t(m_y(0)));
+  return WouldHitUpperLimit(units::radian_t{m_y(0)});
 }
 
 units::radian_t SingleJointedArmSim::GetAngle() const {
@@ -72,12 +72,12 @@
 }
 
 void SingleJointedArmSim::SetInputVoltage(units::volt_t voltage) {
-  SetInput(Eigen::Vector<double, 1>{voltage.value()});
+  SetInput(Vectord<1>{voltage.value()});
 }
 
-Eigen::Vector<double, 2> SingleJointedArmSim::UpdateX(
-    const Eigen::Vector<double, 2>& currentXhat,
-    const Eigen::Vector<double, 1>& u, units::second_t dt) {
+Vectord<2> SingleJointedArmSim::UpdateX(const Vectord<2>& currentXhat,
+                                        const Vectord<1>& u,
+                                        units::second_t dt) {
   // Horizontal case:
   // Torque = F * r = I * alpha
   // alpha = F * r / I
@@ -88,25 +88,24 @@
   // We therefore find that f(x, u) = Ax + Bu + [[0] [m * g * r / I *
   // std::cos(theta)]]
 
-  Eigen::Vector<double, 2> updatedXhat = RKDP(
-      [&](const auto& x, const auto& u) -> Eigen::Vector<double, 2> {
-        Eigen::Vector<double, 2> xdot = m_plant.A() * x + m_plant.B() * u;
+  Vectord<2> updatedXhat = RKDP(
+      [&](const auto& x, const auto& u) -> Vectord<2> {
+        Vectord<2> xdot = m_plant.A() * x + m_plant.B() * u;
 
         if (m_simulateGravity) {
-          xdot += Eigen::Vector<double, 2>{
-              0.0, (m_mass * m_r * -9.8 * 3.0 / (m_mass * m_r * m_r) *
-                    std::cos(x(0)))
-                       .value()};
+          xdot += Vectord<2>{0.0, (m_armMass * m_r * -9.8 * 3.0 /
+                                   (m_armMass * m_r * m_r) * std::cos(x(0)))
+                                      .value()};
         }
         return xdot;
       },
       currentXhat, u, dt);
 
   // Check for collisions.
-  if (WouldHitLowerLimit(units::radian_t(updatedXhat(0)))) {
-    return Eigen::Vector<double, 2>{m_minAngle.value(), 0.0};
-  } else if (WouldHitUpperLimit(units::radian_t(updatedXhat(0)))) {
-    return Eigen::Vector<double, 2>{m_maxAngle.value(), 0.0};
+  if (WouldHitLowerLimit(units::radian_t{updatedXhat(0)})) {
+    return Vectord<2>{m_minAngle.value(), 0.0};
+  } else if (WouldHitUpperLimit(units::radian_t{updatedXhat(0)})) {
+    return Vectord<2>{m_maxAngle.value(), 0.0};
   }
   return updatedXhat;
 }
diff --git a/wpilibc/src/main/native/cpp/simulation/SolenoidSim.cpp b/wpilibc/src/main/native/cpp/simulation/SolenoidSim.cpp
new file mode 100644
index 0000000..29d8207
--- /dev/null
+++ b/wpilibc/src/main/native/cpp/simulation/SolenoidSim.cpp
@@ -0,0 +1,41 @@
+// 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 "frc/simulation/SolenoidSim.h"
+
+#include "frc/PneumaticsBase.h"
+
+using namespace frc;
+using namespace frc::sim;
+
+SolenoidSim::SolenoidSim(std::shared_ptr<PneumaticsBaseSim> moduleSim,
+                         int channel)
+    : m_module{std::move(moduleSim)}, m_channel{channel} {}
+
+SolenoidSim::SolenoidSim(int module, PneumaticsModuleType type, int channel)
+    : m_module{PneumaticsBaseSim::GetForType(module, type)},
+      m_channel{channel} {}
+
+SolenoidSim::SolenoidSim(PneumaticsModuleType type, int channel)
+    : m_module{PneumaticsBaseSim::GetForType(
+          PneumaticsBase::GetDefaultForType(type), type)},
+      m_channel{channel} {}
+
+bool SolenoidSim::GetOutput() const {
+  return m_module->GetSolenoidOutput(m_channel);
+}
+
+void SolenoidSim::SetOutput(bool output) {
+  m_module->SetSolenoidOutput(m_channel, output);
+}
+
+std::unique_ptr<CallbackStore> SolenoidSim::RegisterOutputCallback(
+    NotifyCallback callback, bool initialNotify) {
+  return m_module->RegisterSolenoidOutputCallback(m_channel, callback,
+                                                  initialNotify);
+}
+
+std::shared_ptr<PneumaticsBaseSim> SolenoidSim::GetModuleSim() const {
+  return m_module;
+}
diff --git a/wpilibc/src/main/native/cpp/simulation/XboxControllerSim.cpp b/wpilibc/src/main/native/cpp/simulation/XboxControllerSim.cpp
index 393c41a..daf48b7 100644
--- a/wpilibc/src/main/native/cpp/simulation/XboxControllerSim.cpp
+++ b/wpilibc/src/main/native/cpp/simulation/XboxControllerSim.cpp
@@ -13,11 +13,13 @@
     : GenericHIDSim{joystick} {
   SetAxisCount(6);
   SetButtonCount(10);
+  SetPOVCount(1);
 }
 
 XboxControllerSim::XboxControllerSim(int port) : GenericHIDSim{port} {
   SetAxisCount(6);
   SetButtonCount(10);
+  SetPOVCount(1);
 }
 
 void XboxControllerSim::SetLeftX(double value) {
diff --git a/wpilibc/src/main/native/cpp/smartdashboard/Field2d.cpp b/wpilibc/src/main/native/cpp/smartdashboard/Field2d.cpp
index 2915a12..ec52e9c 100644
--- a/wpilibc/src/main/native/cpp/smartdashboard/Field2d.cpp
+++ b/wpilibc/src/main/native/cpp/smartdashboard/Field2d.cpp
@@ -4,6 +4,7 @@
 
 #include "frc/smartdashboard/Field2d.h"
 
+#include <networktables/DoubleArrayTopic.h>
 #include <networktables/NTSendableBuilder.h>
 #include <wpi/sendable/SendableRegistry.h>
 
@@ -57,7 +58,7 @@
       std::make_unique<FieldObject2d>(name, FieldObject2d::private_init{}));
   auto obj = m_objects.back().get();
   if (m_table) {
-    obj->m_entry = m_table->GetEntry(obj->m_name);
+    obj->m_entry = m_table->GetDoubleArrayTopic(obj->m_name).GetEntry({});
   }
   return obj;
 }
@@ -74,7 +75,7 @@
   m_table = builder.GetTable();
   for (auto&& obj : m_objects) {
     std::scoped_lock lock2(obj->m_mutex);
-    obj->m_entry = m_table->GetEntry(obj->m_name);
+    obj->m_entry = m_table->GetDoubleArrayTopic(obj->m_name).GetEntry({});
     obj->UpdateEntry(true);
   }
 }
diff --git a/wpilibc/src/main/native/cpp/smartdashboard/FieldObject2d.cpp b/wpilibc/src/main/native/cpp/smartdashboard/FieldObject2d.cpp
index 93d6d7e..3895e87 100644
--- a/wpilibc/src/main/native/cpp/smartdashboard/FieldObject2d.cpp
+++ b/wpilibc/src/main/native/cpp/smartdashboard/FieldObject2d.cpp
@@ -6,9 +6,6 @@
 
 #include <vector>
 
-#include <wpi/Endian.h>
-#include <wpi/MathExtras.h>
-
 #include "frc/trajectory/Trajectory.h"
 
 using namespace frc;
@@ -45,7 +42,7 @@
   return m_poses[0];
 }
 
-void FieldObject2d::SetPoses(wpi::span<const Pose2d> poses) {
+void FieldObject2d::SetPoses(std::span<const Pose2d> poses) {
   std::scoped_lock lock(m_mutex);
   m_poses.assign(poses.begin(), poses.end());
   UpdateEntry();
@@ -71,7 +68,7 @@
   return std::vector<Pose2d>(m_poses.begin(), m_poses.end());
 }
 
-wpi::span<const Pose2d> FieldObject2d::GetPoses(
+std::span<const Pose2d> FieldObject2d::GetPoses(
     wpi::SmallVectorImpl<Pose2d>& out) const {
   std::scoped_lock lock(m_mutex);
   UpdateFromEntry();
@@ -83,41 +80,17 @@
   if (!m_entry) {
     return;
   }
-  if (m_poses.size() < (255 / 3)) {
-    wpi::SmallVector<double, 9> arr;
-    for (auto&& pose : m_poses) {
-      auto& translation = pose.Translation();
-      arr.push_back(translation.X().value());
-      arr.push_back(translation.Y().value());
-      arr.push_back(pose.Rotation().Degrees().value());
-    }
-    if (setDefault) {
-      m_entry.SetDefaultDoubleArray(arr);
-    } else {
-      m_entry.ForceSetDoubleArray(arr);
-    }
+  wpi::SmallVector<double, 9> arr;
+  for (auto&& pose : m_poses) {
+    auto& translation = pose.Translation();
+    arr.push_back(translation.X().value());
+    arr.push_back(translation.Y().value());
+    arr.push_back(pose.Rotation().Degrees().value());
+  }
+  if (setDefault) {
+    m_entry.SetDefault(arr);
   } else {
-    // send as raw array of doubles if too big for NT array
-    std::vector<char> arr;
-    arr.resize(m_poses.size() * 3 * 8);
-    char* p = arr.data();
-    for (auto&& pose : m_poses) {
-      auto& translation = pose.Translation();
-      wpi::support::endian::write64be(
-          p, wpi::DoubleToBits(translation.X().value()));
-      p += 8;
-      wpi::support::endian::write64be(
-          p, wpi::DoubleToBits(translation.Y().value()));
-      p += 8;
-      wpi::support::endian::write64be(
-          p, wpi::DoubleToBits(pose.Rotation().Degrees().value()));
-      p += 8;
-    }
-    if (setDefault) {
-      m_entry.SetDefaultRaw({arr.data(), arr.size()});
-    } else {
-      m_entry.ForceSetRaw({arr.data(), arr.size()});
-    }
+    m_entry.Set(arr);
   }
 }
 
@@ -125,46 +98,15 @@
   if (!m_entry) {
     return;
   }
-  auto val = m_entry.GetValue();
-  if (!val) {
+  auto arr = m_entry.Get();
+  auto size = arr.size();
+  if ((size % 3) != 0) {
     return;
   }
-
-  if (val->IsDoubleArray()) {
-    auto arr = val->GetDoubleArray();
-    auto size = arr.size();
-    if ((size % 3) != 0) {
-      return;
-    }
-    m_poses.resize(size / 3);
-    for (size_t i = 0; i < size / 3; ++i) {
-      m_poses[i] =
-          Pose2d{units::meter_t{arr[i * 3 + 0]}, units::meter_t{arr[i * 3 + 1]},
-                 Rotation2d{units::degree_t{arr[i * 3 + 2]}}};
-    }
-  } else if (val->IsRaw()) {
-    // treat it simply as an array of doubles
-    std::string_view data = val->GetRaw();
-
-    // must be triples of doubles
-    auto size = data.size();
-    if ((size % (3 * 8)) != 0) {
-      return;
-    }
-    m_poses.resize(size / (3 * 8));
-    const char* p = data.data();
-    for (size_t i = 0; i < size / (3 * 8); ++i) {
-      double x = wpi::BitsToDouble(
-          wpi::support::endian::readNext<uint64_t, wpi::support::big,
-                                         wpi::support::unaligned>(p));
-      double y = wpi::BitsToDouble(
-          wpi::support::endian::readNext<uint64_t, wpi::support::big,
-                                         wpi::support::unaligned>(p));
-      double rot = wpi::BitsToDouble(
-          wpi::support::endian::readNext<uint64_t, wpi::support::big,
-                                         wpi::support::unaligned>(p));
-      m_poses[i] = Pose2d{units::meter_t{x}, units::meter_t{y},
-                          Rotation2d{units::degree_t{rot}}};
-    }
+  m_poses.resize(size / 3);
+  for (size_t i = 0; i < size / 3; ++i) {
+    m_poses[i] =
+        Pose2d{units::meter_t{arr[i * 3 + 0]}, units::meter_t{arr[i * 3 + 1]},
+               units::degree_t{arr[i * 3 + 2]}};
   }
 }
diff --git a/wpilibc/src/main/native/cpp/smartdashboard/Mechanism2d.cpp b/wpilibc/src/main/native/cpp/smartdashboard/Mechanism2d.cpp
index bfa827a..355cbc6 100644
--- a/wpilibc/src/main/native/cpp/smartdashboard/Mechanism2d.cpp
+++ b/wpilibc/src/main/native/cpp/smartdashboard/Mechanism2d.cpp
@@ -5,13 +5,14 @@
 #include "frc/smartdashboard/Mechanism2d.h"
 
 #include <cstdio>
+#include <string_view>
 
 #include <networktables/NTSendableBuilder.h>
 
 using namespace frc;
 
-static constexpr char kBackgroundColor[] = "backgroundColor";
-static constexpr char kDims[] = "dims";
+static constexpr std::string_view kBackgroundColor = "backgroundColor";
+static constexpr std::string_view kDims = "dims";
 
 Mechanism2d::Mechanism2d(double width, double height,
                          const Color8Bit& backgroundColor)
@@ -34,10 +35,9 @@
 }
 
 void Mechanism2d::SetBackgroundColor(const Color8Bit& color) {
-  std::snprintf(m_color, sizeof(m_color), "#%02X%02X%02X", color.red,
-                color.green, color.blue);
-  if (m_table) {
-    m_table->GetEntry(kBackgroundColor).SetString(m_color);
+  m_color = color.HexString();
+  if (m_colorPub) {
+    m_colorPub.Set(m_color);
   }
 }
 
@@ -46,8 +46,10 @@
 
   std::scoped_lock lock(m_mutex);
   m_table = builder.GetTable();
-  m_table->GetEntry(kDims).SetDoubleArray({m_width, m_height});
-  m_table->GetEntry(kBackgroundColor).SetString(m_color);
+  m_dimsPub = m_table->GetDoubleArrayTopic(kDims).Publish();
+  m_dimsPub.Set({{m_width, m_height}});
+  m_colorPub = m_table->GetStringTopic(kBackgroundColor).Publish();
+  m_colorPub.Set(m_color);
   for (const auto& entry : m_roots) {
     const auto& root = entry.getValue().get();
     root->Update(m_table->GetSubTable(entry.getKey()));
diff --git a/wpilibc/src/main/native/cpp/smartdashboard/MechanismLigament2d.cpp b/wpilibc/src/main/native/cpp/smartdashboard/MechanismLigament2d.cpp
index b49bc99..a43aa16 100644
--- a/wpilibc/src/main/native/cpp/smartdashboard/MechanismLigament2d.cpp
+++ b/wpilibc/src/main/native/cpp/smartdashboard/MechanismLigament2d.cpp
@@ -12,7 +12,7 @@
                                          units::degree_t angle,
                                          double lineWeight,
                                          const frc::Color8Bit& color)
-    : MechanismObject2d(name),
+    : MechanismObject2d{name},
       m_length{length},
       m_angle{angle.value()},
       m_weight{lineWeight} {
@@ -21,38 +21,48 @@
 
 void MechanismLigament2d::UpdateEntries(
     std::shared_ptr<nt::NetworkTable> table) {
-  table->GetEntry(".type").SetString("line");
+  m_typePub = table->GetStringTopic(".type").Publish();
+  m_typePub.Set("line");
 
-  m_colorEntry = table->GetEntry("color");
-  m_angleEntry = table->GetEntry("angle");
-  m_weightEntry = table->GetEntry("weight");
-  m_lengthEntry = table->GetEntry("length");
-  Flush();
+  m_colorEntry = table->GetStringTopic("color").GetEntry("");
+  m_colorEntry.Set(m_color);
+  m_angleEntry = table->GetDoubleTopic("angle").GetEntry(0.0);
+  m_angleEntry.Set(m_angle);
+  m_weightEntry = table->GetDoubleTopic("weight").GetEntry(0.0);
+  m_weightEntry.Set(m_weight);
+  m_lengthEntry = table->GetDoubleTopic("length").GetEntry(0.0);
+  m_lengthEntry.Set(m_length);
 }
 
 void MechanismLigament2d::SetColor(const Color8Bit& color) {
   std::scoped_lock lock(m_mutex);
   std::snprintf(m_color, sizeof(m_color), "#%02X%02X%02X", color.red,
                 color.green, color.blue);
-  Flush();
+  if (m_colorEntry) {
+    m_colorEntry.Set(m_color);
+  }
 }
 
 void MechanismLigament2d::SetAngle(units::degree_t angle) {
   std::scoped_lock lock(m_mutex);
   m_angle = angle.value();
-  Flush();
+  if (m_angleEntry) {
+    m_angleEntry.Set(m_angle);
+  }
 }
 
 void MechanismLigament2d::SetLineWeight(double lineWidth) {
   std::scoped_lock lock(m_mutex);
   m_weight = lineWidth;
-  Flush();
+  if (m_weightEntry) {
+    m_weightEntry.Set(m_weight);
+  }
 }
 
 Color8Bit MechanismLigament2d::GetColor() {
   std::scoped_lock lock(m_mutex);
   if (m_colorEntry) {
-    auto color = m_colorEntry.GetString("");
+    auto color = m_colorEntry.Get();
     std::strncpy(m_color, color.c_str(), sizeof(m_color));
     m_color[sizeof(m_color) - 1] = '\0';
   }
@@ -64,7 +74,7 @@
 double MechanismLigament2d::GetAngle() {
   std::scoped_lock lock(m_mutex);
   if (m_angleEntry) {
-    m_angle = m_angleEntry.GetDouble(0.0);
+    m_angle = m_angleEntry.Get();
   }
   return m_angle;
 }
@@ -72,7 +82,7 @@
 double MechanismLigament2d::GetLength() {
   std::scoped_lock lock(m_mutex);
   if (m_lengthEntry) {
-    m_length = m_lengthEntry.GetDouble(0.0);
+    m_length = m_lengthEntry.Get();
   }
   return m_length;
 }
@@ -80,7 +90,7 @@
 double MechanismLigament2d::GetLineWeight() {
   std::scoped_lock lock(m_mutex);
   if (m_weightEntry) {
-    m_weight = m_weightEntry.GetDouble(0.0);
+    m_weight = m_weightEntry.Get();
   }
   return m_weight;
 }
@@ -88,16 +98,7 @@
 void MechanismLigament2d::SetLength(double length) {
   std::scoped_lock lock(m_mutex);
   m_length = length;
-  Flush();
-}
-
-#define SAFE_WRITE(data, Type)           \
-  if (m_##data##Entry) {                 \
-    m_##data##Entry.Set##Type(m_##data); \
+  if (m_lengthEntry) {
+    m_lengthEntry.Set(length);
   }
-void MechanismLigament2d::Flush() {
-  SAFE_WRITE(color, String)
-  SAFE_WRITE(angle, Double)
-  SAFE_WRITE(length, Double)
-  SAFE_WRITE(weight, Double)
 }
diff --git a/wpilibc/src/main/native/cpp/smartdashboard/MechanismRoot2d.cpp b/wpilibc/src/main/native/cpp/smartdashboard/MechanismRoot2d.cpp
index 637b24b..a40f5ee 100644
--- a/wpilibc/src/main/native/cpp/smartdashboard/MechanismRoot2d.cpp
+++ b/wpilibc/src/main/native/cpp/smartdashboard/MechanismRoot2d.cpp
@@ -10,7 +10,7 @@
 
 MechanismRoot2d::MechanismRoot2d(std::string_view name, double x, double y,
                                  const private_init&)
-    : MechanismObject2d(name), m_x{x}, m_y{y} {}
+    : MechanismObject2d{name}, m_x{x}, m_y{y} {}
 
 void MechanismRoot2d::SetPosition(double x, double y) {
   std::scoped_lock lock(m_mutex);
@@ -20,16 +20,16 @@
 }
 
 void MechanismRoot2d::UpdateEntries(std::shared_ptr<nt::NetworkTable> table) {
-  m_xEntry = table->GetEntry("x");
-  m_yEntry = table->GetEntry("y");
+  m_xPub = table->GetDoubleTopic("x").Publish();
+  m_yPub = table->GetDoubleTopic("y").Publish();
   Flush();
 }
 
 inline void MechanismRoot2d::Flush() {
-  if (m_xEntry) {
-    m_xEntry.SetDouble(m_x);
+  if (m_xPub) {
+    m_xPub.Set(m_x);
   }
-  if (m_yEntry) {
-    m_yEntry.SetDouble(m_y);
+  if (m_yPub) {
+    m_yPub.Set(m_y);
   }
 }
diff --git a/wpilibc/src/main/native/cpp/smartdashboard/SendableBuilderImpl.cpp b/wpilibc/src/main/native/cpp/smartdashboard/SendableBuilderImpl.cpp
index 48af198..79b0e6e 100644
--- a/wpilibc/src/main/native/cpp/smartdashboard/SendableBuilderImpl.cpp
+++ b/wpilibc/src/main/native/cpp/smartdashboard/SendableBuilderImpl.cpp
@@ -4,16 +4,38 @@
 
 #include "frc/smartdashboard/SendableBuilderImpl.h"
 
+#include <networktables/BooleanArrayTopic.h>
+#include <networktables/BooleanTopic.h>
+#include <networktables/DoubleArrayTopic.h>
+#include <networktables/DoubleTopic.h>
+#include <networktables/FloatArrayTopic.h>
+#include <networktables/FloatTopic.h>
+#include <networktables/IntegerArrayTopic.h>
+#include <networktables/IntegerTopic.h>
+#include <networktables/RawTopic.h>
+#include <networktables/StringArrayTopic.h>
 #include <ntcore_cpp.h>
-#include <wpi/SmallString.h>
+#include <wpi/SmallVector.h>
 
 #include "frc/smartdashboard/SmartDashboard.h"
 
 using namespace frc;
 
+template <typename Topic>
+void SendableBuilderImpl::PropertyImpl<Topic>::Update(bool controllable,
+                                                      int64_t time) {
+  if (controllable && sub && updateLocal) {
+    updateLocal(sub);
+  }
+  if (pub && updateNetwork) {
+    updateNetwork(pub, time);
+  }
+}
+
 void SendableBuilderImpl::SetTable(std::shared_ptr<nt::NetworkTable> table) {
   m_table = table;
-  m_controllableEntry = table->GetEntry(".controllable");
+  m_controllablePublisher = table->GetBooleanTopic(".controllable").Publish();
+  m_controllablePublisher.SetDefault(false);
 }
 
 std::shared_ptr<nt::NetworkTable> SendableBuilderImpl::GetTable() {
@@ -31,9 +53,7 @@
 void SendableBuilderImpl::Update() {
   uint64_t time = nt::Now();
   for (auto& property : m_properties) {
-    if (property.update) {
-      property.update(property.entry, time);
-    }
+    property->Update(m_controllable, time);
   }
   for (auto& updateTable : m_updateTables) {
     updateTable();
@@ -41,20 +61,16 @@
 }
 
 void SendableBuilderImpl::StartListeners() {
-  for (auto& property : m_properties) {
-    property.StartListener();
-  }
-  if (m_controllableEntry) {
-    m_controllableEntry.SetBoolean(true);
+  m_controllable = true;
+  if (m_controllablePublisher) {
+    m_controllablePublisher.Set(true);
   }
 }
 
 void SendableBuilderImpl::StopListeners() {
-  for (auto& property : m_properties) {
-    property.StopListener();
-  }
-  if (m_controllableEntry) {
-    m_controllableEntry.SetBoolean(false);
+  m_controllable = false;
+  if (m_controllablePublisher) {
+    m_controllablePublisher.Set(false);
   }
 }
 
@@ -77,11 +93,17 @@
 }
 
 void SendableBuilderImpl::SetSmartDashboardType(std::string_view type) {
-  m_table->GetEntry(".type").SetString(type);
+  if (!m_typePublisher) {
+    m_typePublisher = m_table->GetStringTopic(".type").Publish();
+  }
+  m_typePublisher.Set(type);
 }
 
 void SendableBuilderImpl::SetActuator(bool value) {
-  m_table->GetEntry(".actuator").SetBoolean(value);
+  if (!m_actuatorPublisher) {
+    m_actuatorPublisher = m_table->GetBooleanTopic(".actuator").Publish();
+  }
+  m_actuatorPublisher.Set(value);
   m_actuator = value;
 }
 
@@ -89,357 +111,229 @@
   m_safeState = func;
 }
 
-void SendableBuilderImpl::SetUpdateTable(std::function<void()> func) {
+void SendableBuilderImpl::SetUpdateTable(wpi::unique_function<void()> func) {
   m_updateTables.emplace_back(std::move(func));
 }
 
-nt::NetworkTableEntry SendableBuilderImpl::GetEntry(std::string_view key) {
-  return m_table->GetEntry(key);
+nt::Topic SendableBuilderImpl::GetTopic(std::string_view key) {
+  return m_table->GetTopic(key);
+}
+
+template <typename Topic, typename Getter, typename Setter>
+void SendableBuilderImpl::AddPropertyImpl(Topic topic, Getter getter,
+                                          Setter setter) {
+  auto prop = std::make_unique<PropertyImpl<Topic>>();
+  if (getter) {
+    prop->pub = topic.Publish();
+    prop->updateNetwork = [=](auto& pub, int64_t time) {
+      pub.Set(getter(), time);
+    };
+  }
+  if (setter) {
+    prop->sub =
+        topic.Subscribe({}, {.excludePublisher = prop->pub.GetHandle()});
+    prop->updateLocal = [=](auto& sub) {
+      for (auto&& val : sub.ReadQueue()) {
+        setter(val.value);
+      }
+    };
+  }
+  m_properties.emplace_back(std::move(prop));
 }
 
 void SendableBuilderImpl::AddBooleanProperty(std::string_view key,
                                              std::function<bool()> getter,
                                              std::function<void(bool)> setter) {
-  m_properties.emplace_back(*m_table, key);
-  if (getter) {
-    m_properties.back().update = [=](nt::NetworkTableEntry entry,
-                                     uint64_t time) {
-      entry.SetValue(nt::Value::MakeBoolean(getter(), time));
-    };
-  }
-  if (setter) {
-    m_properties.back().createListener =
-        [=](nt::NetworkTableEntry entry) -> NT_EntryListener {
-      return entry.AddListener(
-          [=](const nt::EntryNotification& event) {
-            if (!event.value->IsBoolean()) {
-              return;
-            }
-            SmartDashboard::PostListenerTask(
-                [=] { setter(event.value->GetBoolean()); });
-          },
-          NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE);
-    };
-  }
+  AddPropertyImpl(m_table->GetBooleanTopic(key), std::move(getter),
+                  std::move(setter));
+}
+
+void SendableBuilderImpl::AddIntegerProperty(
+    std::string_view key, std::function<int64_t()> getter,
+    std::function<void(int64_t)> setter) {
+  AddPropertyImpl(m_table->GetIntegerTopic(key), std::move(getter),
+                  std::move(setter));
+}
+
+void SendableBuilderImpl::AddFloatProperty(std::string_view key,
+                                           std::function<float()> getter,
+                                           std::function<void(float)> setter) {
+  AddPropertyImpl(m_table->GetFloatTopic(key), std::move(getter),
+                  std::move(setter));
 }
 
 void SendableBuilderImpl::AddDoubleProperty(
     std::string_view key, std::function<double()> getter,
     std::function<void(double)> setter) {
-  m_properties.emplace_back(*m_table, key);
-  if (getter) {
-    m_properties.back().update = [=](nt::NetworkTableEntry entry,
-                                     uint64_t time) {
-      entry.SetValue(nt::Value::MakeDouble(getter(), time));
-    };
-  }
-  if (setter) {
-    m_properties.back().createListener =
-        [=](nt::NetworkTableEntry entry) -> NT_EntryListener {
-      return entry.AddListener(
-          [=](const nt::EntryNotification& event) {
-            if (!event.value->IsDouble()) {
-              return;
-            }
-            SmartDashboard::PostListenerTask(
-                [=] { setter(event.value->GetDouble()); });
-          },
-          NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE);
-    };
-  }
+  AddPropertyImpl(m_table->GetDoubleTopic(key), std::move(getter),
+                  std::move(setter));
 }
 
 void SendableBuilderImpl::AddStringProperty(
     std::string_view key, std::function<std::string()> getter,
     std::function<void(std::string_view)> setter) {
-  m_properties.emplace_back(*m_table, key);
-  if (getter) {
-    m_properties.back().update = [=](nt::NetworkTableEntry entry,
-                                     uint64_t time) {
-      entry.SetValue(nt::Value::MakeString(getter(), time));
-    };
-  }
-  if (setter) {
-    m_properties.back().createListener =
-        [=](nt::NetworkTableEntry entry) -> NT_EntryListener {
-      return entry.AddListener(
-          [=](const nt::EntryNotification& event) {
-            if (!event.value->IsString()) {
-              return;
-            }
-            SmartDashboard::PostListenerTask(
-                [=] { setter(event.value->GetString()); });
-          },
-          NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE);
-    };
-  }
+  AddPropertyImpl(m_table->GetStringTopic(key), std::move(getter),
+                  std::move(setter));
 }
 
 void SendableBuilderImpl::AddBooleanArrayProperty(
     std::string_view key, std::function<std::vector<int>()> getter,
-    std::function<void(wpi::span<const int>)> setter) {
-  m_properties.emplace_back(*m_table, key);
-  if (getter) {
-    m_properties.back().update = [=](nt::NetworkTableEntry entry,
-                                     uint64_t time) {
-      entry.SetValue(nt::Value::MakeBooleanArray(getter(), time));
-    };
-  }
-  if (setter) {
-    m_properties.back().createListener =
-        [=](nt::NetworkTableEntry entry) -> NT_EntryListener {
-      return entry.AddListener(
-          [=](const nt::EntryNotification& event) {
-            if (!event.value->IsBooleanArray()) {
-              return;
-            }
-            SmartDashboard::PostListenerTask(
-                [=] { setter(event.value->GetBooleanArray()); });
-          },
-          NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE);
-    };
-  }
+    std::function<void(std::span<const int>)> setter) {
+  AddPropertyImpl(m_table->GetBooleanArrayTopic(key), std::move(getter),
+                  std::move(setter));
+}
+
+void SendableBuilderImpl::AddIntegerArrayProperty(
+    std::string_view key, std::function<std::vector<int64_t>()> getter,
+    std::function<void(std::span<const int64_t>)> setter) {
+  AddPropertyImpl(m_table->GetIntegerArrayTopic(key), std::move(getter),
+                  std::move(setter));
+}
+
+void SendableBuilderImpl::AddFloatArrayProperty(
+    std::string_view key, std::function<std::vector<float>()> getter,
+    std::function<void(std::span<const float>)> setter) {
+  AddPropertyImpl(m_table->GetFloatArrayTopic(key), std::move(getter),
+                  std::move(setter));
 }
 
 void SendableBuilderImpl::AddDoubleArrayProperty(
     std::string_view key, std::function<std::vector<double>()> getter,
-    std::function<void(wpi::span<const double>)> setter) {
-  m_properties.emplace_back(*m_table, key);
-  if (getter) {
-    m_properties.back().update = [=](nt::NetworkTableEntry entry,
-                                     uint64_t time) {
-      entry.SetValue(nt::Value::MakeDoubleArray(getter(), time));
-    };
-  }
-  if (setter) {
-    m_properties.back().createListener =
-        [=](nt::NetworkTableEntry entry) -> NT_EntryListener {
-      return entry.AddListener(
-          [=](const nt::EntryNotification& event) {
-            if (!event.value->IsDoubleArray()) {
-              return;
-            }
-            SmartDashboard::PostListenerTask(
-                [=] { setter(event.value->GetDoubleArray()); });
-          },
-          NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE);
-    };
-  }
+    std::function<void(std::span<const double>)> setter) {
+  AddPropertyImpl(m_table->GetDoubleArrayTopic(key), std::move(getter),
+                  std::move(setter));
 }
 
 void SendableBuilderImpl::AddStringArrayProperty(
     std::string_view key, std::function<std::vector<std::string>()> getter,
-    std::function<void(wpi::span<const std::string>)> setter) {
-  m_properties.emplace_back(*m_table, key);
-  if (getter) {
-    m_properties.back().update = [=](nt::NetworkTableEntry entry,
-                                     uint64_t time) {
-      entry.SetValue(nt::Value::MakeStringArray(getter(), time));
-    };
-  }
-  if (setter) {
-    m_properties.back().createListener =
-        [=](nt::NetworkTableEntry entry) -> NT_EntryListener {
-      return entry.AddListener(
-          [=](const nt::EntryNotification& event) {
-            if (!event.value->IsStringArray()) {
-              return;
-            }
-            SmartDashboard::PostListenerTask(
-                [=] { setter(event.value->GetStringArray()); });
-          },
-          NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE);
-    };
-  }
+    std::function<void(std::span<const std::string>)> setter) {
+  AddPropertyImpl(m_table->GetStringArrayTopic(key), std::move(getter),
+                  std::move(setter));
 }
 
 void SendableBuilderImpl::AddRawProperty(
-    std::string_view key, std::function<std::string()> getter,
-    std::function<void(std::string_view)> setter) {
-  m_properties.emplace_back(*m_table, key);
+    std::string_view key, std::string_view typeString,
+    std::function<std::vector<uint8_t>()> getter,
+    std::function<void(std::span<const uint8_t>)> setter) {
+  auto topic = m_table->GetRawTopic(key);
+  auto prop = std::make_unique<PropertyImpl<nt::RawTopic>>();
   if (getter) {
-    m_properties.back().update = [=](nt::NetworkTableEntry entry,
-                                     uint64_t time) {
-      entry.SetValue(nt::Value::MakeRaw(getter(), time));
+    prop->pub = topic.Publish(typeString);
+    prop->updateNetwork = [=](auto& pub, int64_t time) {
+      pub.Set(getter(), time);
     };
   }
   if (setter) {
-    m_properties.back().createListener =
-        [=](nt::NetworkTableEntry entry) -> NT_EntryListener {
-      return entry.AddListener(
-          [=](const nt::EntryNotification& event) {
-            if (!event.value->IsRaw()) {
-              return;
-            }
-            SmartDashboard::PostListenerTask(
-                [=] { setter(event.value->GetRaw()); });
-          },
-          NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE);
+    prop->sub = topic.Subscribe(typeString, {},
+                                {.excludePublisher = prop->pub.GetHandle()});
+    prop->updateLocal = [=](auto& sub) {
+      for (auto&& val : sub.ReadQueue()) {
+        setter(val.value);
+      }
     };
   }
+  m_properties.emplace_back(std::move(prop));
 }
 
-void SendableBuilderImpl::AddValueProperty(
-    std::string_view key, std::function<std::shared_ptr<nt::Value>()> getter,
-    std::function<void(std::shared_ptr<nt::Value>)> setter) {
-  m_properties.emplace_back(*m_table, key);
+template <typename T, size_t Size, typename Topic, typename Getter,
+          typename Setter>
+void SendableBuilderImpl::AddSmallPropertyImpl(Topic topic, Getter getter,
+                                               Setter setter) {
+  auto prop = std::make_unique<PropertyImpl<Topic>>();
   if (getter) {
-    m_properties.back().update = [=](nt::NetworkTableEntry entry,
-                                     uint64_t time) {
-      entry.SetValue(getter());
+    prop->pub = topic.Publish();
+    prop->updateNetwork = [=](auto& pub, int64_t time) {
+      wpi::SmallVector<T, Size> buf;
+      pub.Set(getter(buf), time);
     };
   }
   if (setter) {
-    m_properties.back().createListener =
-        [=](nt::NetworkTableEntry entry) -> NT_EntryListener {
-      return entry.AddListener(
-          [=](const nt::EntryNotification& event) {
-            SmartDashboard::PostListenerTask([=] { setter(event.value); });
-          },
-          NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE);
+    prop->sub =
+        topic.Subscribe({}, {.excludePublisher = prop->pub.GetHandle()});
+    prop->updateLocal = [=](auto& sub) {
+      for (auto&& val : sub.ReadQueue()) {
+        setter(val.value);
+      }
     };
   }
+  m_properties.emplace_back(std::move(prop));
 }
 
 void SendableBuilderImpl::AddSmallStringProperty(
     std::string_view key,
     std::function<std::string_view(wpi::SmallVectorImpl<char>& buf)> getter,
     std::function<void(std::string_view)> setter) {
-  m_properties.emplace_back(*m_table, key);
-  if (getter) {
-    m_properties.back().update = [=](nt::NetworkTableEntry entry,
-                                     uint64_t time) {
-      wpi::SmallString<128> buf;
-      entry.SetValue(nt::Value::MakeString(getter(buf), time));
-    };
-  }
-  if (setter) {
-    m_properties.back().createListener =
-        [=](nt::NetworkTableEntry entry) -> NT_EntryListener {
-      return entry.AddListener(
-          [=](const nt::EntryNotification& event) {
-            if (!event.value->IsString()) {
-              return;
-            }
-            SmartDashboard::PostListenerTask(
-                [=] { setter(event.value->GetString()); });
-          },
-          NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE);
-    };
-  }
+  AddSmallPropertyImpl<char, 128>(m_table->GetStringTopic(key),
+                                  std::move(getter), std::move(setter));
 }
 
 void SendableBuilderImpl::AddSmallBooleanArrayProperty(
     std::string_view key,
-    std::function<wpi::span<const int>(wpi::SmallVectorImpl<int>& buf)> getter,
-    std::function<void(wpi::span<const int>)> setter) {
-  m_properties.emplace_back(*m_table, key);
-  if (getter) {
-    m_properties.back().update = [=](nt::NetworkTableEntry entry,
-                                     uint64_t time) {
-      wpi::SmallVector<int, 16> buf;
-      entry.SetValue(nt::Value::MakeBooleanArray(getter(buf), time));
-    };
-  }
-  if (setter) {
-    m_properties.back().createListener =
-        [=](nt::NetworkTableEntry entry) -> NT_EntryListener {
-      return entry.AddListener(
-          [=](const nt::EntryNotification& event) {
-            if (!event.value->IsBooleanArray()) {
-              return;
-            }
-            SmartDashboard::PostListenerTask(
-                [=] { setter(event.value->GetBooleanArray()); });
-          },
-          NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE);
-    };
-  }
+    std::function<std::span<const int>(wpi::SmallVectorImpl<int>& buf)> getter,
+    std::function<void(std::span<const int>)> setter) {
+  AddSmallPropertyImpl<int, 16>(m_table->GetBooleanArrayTopic(key),
+                                std::move(getter), std::move(setter));
+}
+
+void SendableBuilderImpl::AddSmallIntegerArrayProperty(
+    std::string_view key,
+    std::function<std::span<const int64_t>(wpi::SmallVectorImpl<int64_t>& buf)>
+        getter,
+    std::function<void(std::span<const int64_t>)> setter) {
+  AddSmallPropertyImpl<int64_t, 16>(m_table->GetIntegerArrayTopic(key),
+                                    std::move(getter), std::move(setter));
+}
+
+void SendableBuilderImpl::AddSmallFloatArrayProperty(
+    std::string_view key,
+    std::function<std::span<const float>(wpi::SmallVectorImpl<float>& buf)>
+        getter,
+    std::function<void(std::span<const float>)> setter) {
+  AddSmallPropertyImpl<float, 16>(m_table->GetFloatArrayTopic(key),
+                                  std::move(getter), std::move(setter));
 }
 
 void SendableBuilderImpl::AddSmallDoubleArrayProperty(
     std::string_view key,
-    std::function<wpi::span<const double>(wpi::SmallVectorImpl<double>& buf)>
+    std::function<std::span<const double>(wpi::SmallVectorImpl<double>& buf)>
         getter,
-    std::function<void(wpi::span<const double>)> setter) {
-  m_properties.emplace_back(*m_table, key);
-  if (getter) {
-    m_properties.back().update = [=](nt::NetworkTableEntry entry,
-                                     uint64_t time) {
-      wpi::SmallVector<double, 16> buf;
-      entry.SetValue(nt::Value::MakeDoubleArray(getter(buf), time));
-    };
-  }
-  if (setter) {
-    m_properties.back().createListener =
-        [=](nt::NetworkTableEntry entry) -> NT_EntryListener {
-      return entry.AddListener(
-          [=](const nt::EntryNotification& event) {
-            if (!event.value->IsDoubleArray()) {
-              return;
-            }
-            SmartDashboard::PostListenerTask(
-                [=] { setter(event.value->GetDoubleArray()); });
-          },
-          NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE);
-    };
-  }
+    std::function<void(std::span<const double>)> setter) {
+  AddSmallPropertyImpl<double, 16>(m_table->GetDoubleArrayTopic(key),
+                                   std::move(getter), std::move(setter));
 }
 
 void SendableBuilderImpl::AddSmallStringArrayProperty(
     std::string_view key,
     std::function<
-        wpi::span<const std::string>(wpi::SmallVectorImpl<std::string>& buf)>
+        std::span<const std::string>(wpi::SmallVectorImpl<std::string>& buf)>
         getter,
-    std::function<void(wpi::span<const std::string>)> setter) {
-  m_properties.emplace_back(*m_table, key);
-  if (getter) {
-    m_properties.back().update = [=](nt::NetworkTableEntry entry,
-                                     uint64_t time) {
-      wpi::SmallVector<std::string, 16> buf;
-      entry.SetValue(nt::Value::MakeStringArray(getter(buf), time));
-    };
-  }
-  if (setter) {
-    m_properties.back().createListener =
-        [=](nt::NetworkTableEntry entry) -> NT_EntryListener {
-      return entry.AddListener(
-          [=](const nt::EntryNotification& event) {
-            if (!event.value->IsStringArray()) {
-              return;
-            }
-            SmartDashboard::PostListenerTask(
-                [=] { setter(event.value->GetStringArray()); });
-          },
-          NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE);
-    };
-  }
+    std::function<void(std::span<const std::string>)> setter) {
+  AddSmallPropertyImpl<std::string, 16>(m_table->GetStringArrayTopic(key),
+                                        std::move(getter), std::move(setter));
 }
 
 void SendableBuilderImpl::AddSmallRawProperty(
-    std::string_view key,
-    std::function<std::string_view(wpi::SmallVectorImpl<char>& buf)> getter,
-    std::function<void(std::string_view)> setter) {
-  m_properties.emplace_back(*m_table, key);
+    std::string_view key, std::string_view typeString,
+    std::function<std::span<uint8_t>(wpi::SmallVectorImpl<uint8_t>& buf)>
+        getter,
+    std::function<void(std::span<const uint8_t>)> setter) {
+  auto topic = m_table->GetRawTopic(key);
+  auto prop = std::make_unique<PropertyImpl<nt::RawTopic>>();
   if (getter) {
-    m_properties.back().update = [=](nt::NetworkTableEntry entry,
-                                     uint64_t time) {
-      wpi::SmallVector<char, 128> buf;
-      entry.SetValue(nt::Value::MakeRaw(getter(buf), time));
+    prop->pub = topic.Publish(typeString);
+    prop->updateNetwork = [=](auto& pub, int64_t time) {
+      wpi::SmallVector<uint8_t, 128> buf;
+      pub.Set(getter(buf), time);
     };
   }
   if (setter) {
-    m_properties.back().createListener =
-        [=](nt::NetworkTableEntry entry) -> NT_EntryListener {
-      return entry.AddListener(
-          [=](const nt::EntryNotification& event) {
-            if (!event.value->IsRaw()) {
-              return;
-            }
-            SmartDashboard::PostListenerTask(
-                [=] { setter(event.value->GetRaw()); });
-          },
-          NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE);
+    prop->sub = topic.Subscribe(typeString, {},
+                                {.excludePublisher = prop->pub.GetHandle()});
+    prop->updateLocal = [=](auto& sub) {
+      for (auto&& val : sub.ReadQueue()) {
+        setter(val.value);
+      }
     };
   }
+  m_properties.emplace_back(std::move(prop));
 }
diff --git a/wpilibc/src/main/native/cpp/smartdashboard/SendableChooserBase.cpp b/wpilibc/src/main/native/cpp/smartdashboard/SendableChooserBase.cpp
index 550d40c..9fa5b69 100644
--- a/wpilibc/src/main/native/cpp/smartdashboard/SendableChooserBase.cpp
+++ b/wpilibc/src/main/native/cpp/smartdashboard/SendableChooserBase.cpp
@@ -19,7 +19,8 @@
       m_defaultChoice(std::move(oth.m_defaultChoice)),
       m_selected(std::move(oth.m_selected)),
       m_haveSelected(std::move(oth.m_haveSelected)),
-      m_activeEntries(std::move(oth.m_activeEntries)),
+      m_instancePubs(std::move(oth.m_instancePubs)),
+      m_activePubs(std::move(oth.m_activePubs)),
       m_instance(std::move(oth.m_instance)) {}
 
 SendableChooserBase& SendableChooserBase::operator=(SendableChooserBase&& oth) {
@@ -28,7 +29,8 @@
   m_defaultChoice = std::move(oth.m_defaultChoice);
   m_selected = std::move(oth.m_selected);
   m_haveSelected = std::move(oth.m_haveSelected);
-  m_activeEntries = std::move(oth.m_activeEntries);
+  m_instancePubs = std::move(oth.m_instancePubs);
+  m_activePubs = std::move(oth.m_activePubs);
   m_instance = std::move(oth.m_instance);
   return *this;
 }
diff --git a/wpilibc/src/main/native/cpp/smartdashboard/SmartDashboard.cpp b/wpilibc/src/main/native/cpp/smartdashboard/SmartDashboard.cpp
index 5088394..e6991d4 100644
--- a/wpilibc/src/main/native/cpp/smartdashboard/SmartDashboard.cpp
+++ b/wpilibc/src/main/native/cpp/smartdashboard/SmartDashboard.cpp
@@ -70,29 +70,13 @@
   return GetInstance().table->GetEntry(key).IsPersistent();
 }
 
-void SmartDashboard::SetFlags(std::string_view key, unsigned int flags) {
-  GetInstance().table->GetEntry(key).SetFlags(flags);
-}
-
-void SmartDashboard::ClearFlags(std::string_view key, unsigned int flags) {
-  GetInstance().table->GetEntry(key).ClearFlags(flags);
-}
-
-unsigned int SmartDashboard::GetFlags(std::string_view key) {
-  return GetInstance().table->GetEntry(key).GetFlags();
-}
-
-void SmartDashboard::Delete(std::string_view key) {
-  GetInstance().table->Delete(key);
-}
-
 nt::NetworkTableEntry SmartDashboard::GetEntry(std::string_view key) {
   return GetInstance().table->GetEntry(key);
 }
 
 void SmartDashboard::PutData(std::string_view key, wpi::Sendable* data) {
   if (!data) {
-    throw FRC_MakeError(err::NullParameter, "{}", "value");
+    throw FRC_MakeError(err::NullParameter, "value");
   }
   auto& inst = GetInstance();
   std::scoped_lock lock(inst.tablesToDataMutex);
@@ -112,7 +96,7 @@
 
 void SmartDashboard::PutData(wpi::Sendable* value) {
   if (!value) {
-    throw FRC_MakeError(err::NullParameter, "{}", "value");
+    throw FRC_MakeError(err::NullParameter, "value");
   }
   auto name = wpi::SendableRegistry::GetName(value);
   if (!name.empty()) {
@@ -173,76 +157,77 @@
 }
 
 bool SmartDashboard::PutBooleanArray(std::string_view key,
-                                     wpi::span<const int> value) {
+                                     std::span<const int> value) {
   return GetInstance().table->GetEntry(key).SetBooleanArray(value);
 }
 
 bool SmartDashboard::SetDefaultBooleanArray(std::string_view key,
-                                            wpi::span<const int> defaultValue) {
+                                            std::span<const int> defaultValue) {
   return GetInstance().table->GetEntry(key).SetDefaultBooleanArray(
       defaultValue);
 }
 
 std::vector<int> SmartDashboard::GetBooleanArray(
-    std::string_view key, wpi::span<const int> defaultValue) {
+    std::string_view key, std::span<const int> defaultValue) {
   return GetInstance().table->GetEntry(key).GetBooleanArray(defaultValue);
 }
 
 bool SmartDashboard::PutNumberArray(std::string_view key,
-                                    wpi::span<const double> value) {
+                                    std::span<const double> value) {
   return GetInstance().table->GetEntry(key).SetDoubleArray(value);
 }
 
 bool SmartDashboard::SetDefaultNumberArray(
-    std::string_view key, wpi::span<const double> defaultValue) {
+    std::string_view key, std::span<const double> defaultValue) {
   return GetInstance().table->GetEntry(key).SetDefaultDoubleArray(defaultValue);
 }
 
 std::vector<double> SmartDashboard::GetNumberArray(
-    std::string_view key, wpi::span<const double> defaultValue) {
+    std::string_view key, std::span<const double> defaultValue) {
   return GetInstance().table->GetEntry(key).GetDoubleArray(defaultValue);
 }
 
 bool SmartDashboard::PutStringArray(std::string_view key,
-                                    wpi::span<const std::string> value) {
+                                    std::span<const std::string> value) {
   return GetInstance().table->GetEntry(key).SetStringArray(value);
 }
 
 bool SmartDashboard::SetDefaultStringArray(
-    std::string_view key, wpi::span<const std::string> defaultValue) {
+    std::string_view key, std::span<const std::string> defaultValue) {
   return GetInstance().table->GetEntry(key).SetDefaultStringArray(defaultValue);
 }
 
 std::vector<std::string> SmartDashboard::GetStringArray(
-    std::string_view key, wpi::span<const std::string> defaultValue) {
+    std::string_view key, std::span<const std::string> defaultValue) {
   return GetInstance().table->GetEntry(key).GetStringArray(defaultValue);
 }
 
-bool SmartDashboard::PutRaw(std::string_view key, std::string_view value) {
+bool SmartDashboard::PutRaw(std::string_view key,
+                            std::span<const uint8_t> value) {
   return GetInstance().table->GetEntry(key).SetRaw(value);
 }
 
 bool SmartDashboard::SetDefaultRaw(std::string_view key,
-                                   std::string_view defaultValue) {
+                                   std::span<const uint8_t> defaultValue) {
   return GetInstance().table->GetEntry(key).SetDefaultRaw(defaultValue);
 }
 
-std::string SmartDashboard::GetRaw(std::string_view key,
-                                   std::string_view defaultValue) {
+std::vector<uint8_t> SmartDashboard::GetRaw(
+    std::string_view key, std::span<const uint8_t> defaultValue) {
   return GetInstance().table->GetEntry(key).GetRaw(defaultValue);
 }
 
 bool SmartDashboard::PutValue(std::string_view keyName,
-                              std::shared_ptr<nt::Value> value) {
+                              const nt::Value& value) {
   return GetInstance().table->GetEntry(keyName).SetValue(value);
 }
 
 bool SmartDashboard::SetDefaultValue(std::string_view key,
-                                     std::shared_ptr<nt::Value> defaultValue) {
+                                     const nt::Value& defaultValue) {
   return GetInstance().table->GetEntry(key).SetDefaultValue(defaultValue);
 }
 
-std::shared_ptr<nt::Value> SmartDashboard::GetValue(std::string_view keyName) {
+nt::Value SmartDashboard::GetValue(std::string_view keyName) {
   return GetInstance().table->GetEntry(keyName).GetValue();
 }
 
diff --git a/wpilibc/src/main/native/cpp/util/Color.cpp b/wpilibc/src/main/native/cpp/util/Color.cpp
new file mode 100644
index 0000000..e3adaf2
--- /dev/null
+++ b/wpilibc/src/main/native/cpp/util/Color.cpp
@@ -0,0 +1,15 @@
+// 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 "frc/util/Color.h"
+
+#include <fmt/format.h>
+
+using namespace frc;
+
+std::string Color::HexString() const {
+  return fmt::format("#{:02X}{:02X}{:02X}", static_cast<int>(255.0 * red),
+                     static_cast<int>(255.0 * green),
+                     static_cast<int>(255.0 * blue));
+}
diff --git a/wpilibc/src/main/native/cpp/util/Color8Bit.cpp b/wpilibc/src/main/native/cpp/util/Color8Bit.cpp
new file mode 100644
index 0000000..af200a2
--- /dev/null
+++ b/wpilibc/src/main/native/cpp/util/Color8Bit.cpp
@@ -0,0 +1,13 @@
+// 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 "frc/util/Color8Bit.h"
+
+#include <fmt/format.h>
+
+using namespace frc;
+
+std::string Color8Bit::HexString() const {
+  return fmt::format("#{:02X}{:02X}{:02X}", red, green, blue);
+}
diff --git a/wpilibc/src/main/native/cppcs/RobotBase.cpp b/wpilibc/src/main/native/cppcs/RobotBase.cpp
index 3433a02..535d20d 100644
--- a/wpilibc/src/main/native/cppcs/RobotBase.cpp
+++ b/wpilibc/src/main/native/cppcs/RobotBase.cpp
@@ -45,7 +45,7 @@
              HALUsageReporting::kLanguage_CPlusPlus, 0, GetWPILibVersion());
 
   if (!frc::Notifier::SetHALThreadPriority(true, 40)) {
-    FRC_ReportError(warn::Warning, "{}",
+    FRC_ReportError(warn::Warning,
                     "Setting HAL Notifier RT priority to 40 failed\n");
   }
 
@@ -192,18 +192,10 @@
   return DriverStation::IsAutonomousEnabled();
 }
 
-bool RobotBase::IsOperatorControl() const {
-  return DriverStation::IsTeleop();
-}
-
 bool RobotBase::IsTeleop() const {
   return DriverStation::IsTeleop();
 }
 
-bool RobotBase::IsOperatorControlEnabled() const {
-  return DriverStation::IsTeleopEnabled();
-}
-
 bool RobotBase::IsTeleopEnabled() const {
   return DriverStation::IsTeleopEnabled();
 }
@@ -212,10 +204,6 @@
   return DriverStation::IsTest();
 }
 
-bool RobotBase::IsNewDataAvailable() const {
-  return DriverStation::IsNewControlData();
-}
-
 std::thread::id RobotBase::GetThreadId() {
   return m_threadId;
 }
@@ -231,13 +219,26 @@
   SetupMathShared();
 
   auto inst = nt::NetworkTableInstance::GetDefault();
-  inst.SetNetworkIdentity("Robot");
+  // subscribe to "" to force persistent values to progagate to local
+  nt::SubscribeMultiple(inst.GetHandle(), {{std::string_view{}}});
 #ifdef __FRC_ROBORIO__
-  inst.StartServer("/home/lvuser/networktables.ini");
+  inst.StartServer("/home/lvuser/networktables.json");
 #else
   inst.StartServer();
 #endif
 
+  // wait for the NT server to actually start
+  int count = 0;
+  while ((inst.GetNetworkMode() & NT_NET_MODE_STARTING) != 0) {
+    using namespace std::chrono_literals;
+    std::this_thread::sleep_for(10ms);
+    ++count;
+    if (count > 100) {
+      fmt::print(stderr, "timed out while waiting for NT server to start\n");
+      break;
+    }
+  }
+
   SmartDashboard::init();
 
   if (IsReal()) {
@@ -251,14 +252,9 @@
     }
   }
 
-  // Call DriverStation::InDisabled() to kick off DS thread
-  DriverStation::InDisabled(true);
+  // Call DriverStation::RefreshData() to kick things off
+  DriverStation::RefreshData();
 
   // First and one-time initialization
-  inst.GetTable("LiveWindow")
-      ->GetSubTable(".status")
-      ->GetEntry("LW Enabled")
-      .SetBoolean(false);
-
   LiveWindow::SetEnabled(false);
 }
diff --git a/wpilibc/src/main/native/include/frc/ADIS16448_IMU.h b/wpilibc/src/main/native/include/frc/ADIS16448_IMU.h
index 0dbfa68..60d57a9 100644
--- a/wpilibc/src/main/native/include/frc/ADIS16448_IMU.h
+++ b/wpilibc/src/main/native/include/frc/ADIS16448_IMU.h
@@ -205,6 +205,8 @@
 
   int SetYawAxis(IMUAxis yaw_axis);
 
+  bool IsConnected() const;
+
   int ConfigDecRate(uint16_t DecimationRate);
 
   /**
@@ -285,6 +287,10 @@
     double gyro_rate_z = 0.0;
   };
 
+  /** @brief Internal Resources **/
+  DigitalInput* m_reset_in;
+  DigitalOutput* m_status_led;
+
   bool SwitchToStandardSPI();
 
   bool SwitchToAutoSPI();
@@ -357,10 +363,12 @@
   CalibrationTime m_calibration_time{0};
   SPI* m_spi = nullptr;
   DigitalInput* m_auto_interrupt = nullptr;
+  bool m_connected{false};
 
   std::thread m_acquire_task;
 
   hal::SimDevice m_simDevice;
+  hal::SimBoolean m_simConnected;
   hal::SimDouble m_simGyroAngleX;
   hal::SimDouble m_simGyroAngleY;
   hal::SimDouble m_simGyroAngleZ;
diff --git a/wpilibc/src/main/native/include/frc/ADIS16470_IMU.h b/wpilibc/src/main/native/include/frc/ADIS16470_IMU.h
index e6f3ce7..4619819 100644
--- a/wpilibc/src/main/native/include/frc/ADIS16470_IMU.h
+++ b/wpilibc/src/main/native/include/frc/ADIS16470_IMU.h
@@ -93,7 +93,8 @@
                          CalibrationTime cal_time);
 
   /**
-   * @brief Destructor. Kills the acquisiton loop and closes the SPI peripheral.
+   * @brief Destructor. Kills the acquisition loop and closes the SPI
+   * peripheral.
    */
   ~ADIS16470_IMU() override;
 
@@ -159,6 +160,8 @@
 
   int SetYawAxis(IMUAxis yaw_axis);
 
+  bool IsConnected() const;
+
   // IMU yaw axis
   IMUAxis m_yaw_axis;
 
@@ -296,6 +299,10 @@
   static constexpr double deg_to_rad = 0.0174532;
   static constexpr double grav = 9.81;
 
+  /** @brief Resources **/
+  DigitalInput* m_reset_in;
+  DigitalOutput* m_status_led;
+
   /**
    * @brief Switches to standard SPI operation. Primarily used when exiting auto
    * SPI mode.
@@ -378,10 +385,12 @@
   SPI* m_spi = nullptr;
   DigitalInput* m_auto_interrupt = nullptr;
   double m_scaled_sample_rate = 2500.0;  // Default sample rate setting
+  bool m_connected{false};
 
   std::thread m_acquire_task;
 
   hal::SimDevice m_simDevice;
+  hal::SimBoolean m_simConnected;
   hal::SimDouble m_simGyroAngleX;
   hal::SimDouble m_simGyroAngleY;
   hal::SimDouble m_simGyroAngleZ;
diff --git a/wpilibc/src/main/native/include/frc/ADXL345_I2C.h b/wpilibc/src/main/native/include/frc/ADXL345_I2C.h
index c997327..6b6b76c 100644
--- a/wpilibc/src/main/native/include/frc/ADXL345_I2C.h
+++ b/wpilibc/src/main/native/include/frc/ADXL345_I2C.h
@@ -19,6 +19,10 @@
  * This class allows access to a Analog Devices ADXL345 3-axis accelerometer on
  * an I2C bus. This class assumes the default (not alternate) sensor address of
  * 0x1D (7-bit address).
+ *
+ * The Onboard I2C port is subject to system lockups. See <a
+ * href="https://docs.wpilib.org/en/stable/docs/yearly-overview/known-issues.html#onboard-i2c-causing-system-lockups">
+ * WPILib Known Issues</a> page for details.
  */
 class ADXL345_I2C : public Accelerometer,
                     public nt::NTSendable,
diff --git a/wpilibc/src/main/native/include/frc/ADXRS450_Gyro.h b/wpilibc/src/main/native/include/frc/ADXRS450_Gyro.h
index cb0dff8..55414d4 100644
--- a/wpilibc/src/main/native/include/frc/ADXRS450_Gyro.h
+++ b/wpilibc/src/main/native/include/frc/ADXRS450_Gyro.h
@@ -49,6 +49,8 @@
   ADXRS450_Gyro(ADXRS450_Gyro&&) = default;
   ADXRS450_Gyro& operator=(ADXRS450_Gyro&&) = default;
 
+  bool IsConnected() const;
+
   /**
    * Return the actual angle in degrees that the robot is currently facing.
    *
@@ -105,8 +107,10 @@
  private:
   SPI m_spi;
   SPI::Port m_port;
+  bool m_connected{false};
 
   hal::SimDevice m_simDevice;
+  hal::SimBoolean m_simConnected;
   hal::SimDouble m_simAngle;
   hal::SimDouble m_simRate;
 
diff --git a/wpilibc/src/main/native/include/frc/AddressableLED.h b/wpilibc/src/main/native/include/frc/AddressableLED.h
index 561eb37..e6adfca 100644
--- a/wpilibc/src/main/native/include/frc/AddressableLED.h
+++ b/wpilibc/src/main/native/include/frc/AddressableLED.h
@@ -5,11 +5,11 @@
 #pragma once
 
 #include <initializer_list>
+#include <span>
 
 #include <hal/AddressableLEDTypes.h>
 #include <hal/Types.h>
 #include <units/time.h>
-#include <wpi/span.h>
 
 #include "util/Color.h"
 #include "util/Color8Bit.h"
@@ -107,7 +107,7 @@
    *
    * @param ledData the buffer to write
    */
-  void SetData(wpi::span<const LEDData> ledData);
+  void SetData(std::span<const LEDData> ledData);
 
   /**
    * Sets the led output data.
diff --git a/wpilibc/src/main/native/include/frc/AnalogEncoder.h b/wpilibc/src/main/native/include/frc/AnalogEncoder.h
index 6f12cb8..1860273 100644
--- a/wpilibc/src/main/native/include/frc/AnalogEncoder.h
+++ b/wpilibc/src/main/native/include/frc/AnalogEncoder.h
@@ -72,9 +72,23 @@
   units::turn_t Get() const;
 
   /**
+   * Get the absolute position of the analog encoder.
+   *
+   * <p>GetAbsolutePosition() - GetPositionOffset() will give an encoder
+   * absolute position relative to the last reset. This could potentially be
+   * negative, which needs to be accounted for.
+   *
+   * <p>This will not account for rollovers, and will always be just the raw
+   * absolute position.
+   *
+   * @return the absolute position
+   */
+  double GetAbsolutePosition() const;
+
+  /**
    * Get the offset of position relative to the last reset.
    *
-   * GetPositionInRotation() - GetPositionOffset() will give an encoder absolute
+   * GetAbsolutePosition() - GetPositionOffset() will give an encoder absolute
    * position relative to the last reset. This could potentially be negative,
    * which needs to be accounted for.
    *
@@ -83,6 +97,15 @@
   double GetPositionOffset() const;
 
   /**
+   * Set the position offset.
+   *
+   * <p>This must be in the range of 0-1.
+   *
+   * @param offset the offset
+   */
+  void SetPositionOffset(double offset);
+
+  /**
    * Set the distance per rotation of the encoder. This sets the multiplier used
    * to determine the distance driven based on the rotation value from the
    * encoder. Set this value based on the how far the mechanism travels in 1
@@ -131,5 +154,6 @@
 
   hal::SimDevice m_simDevice;
   hal::SimDouble m_simPosition;
+  hal::SimDouble m_simAbsolutePosition;
 };
 }  // namespace frc
diff --git a/wpilibc/src/main/native/include/frc/AsynchronousInterrupt.h b/wpilibc/src/main/native/include/frc/AsynchronousInterrupt.h
index 4531dfd..0dea5bc 100644
--- a/wpilibc/src/main/native/include/frc/AsynchronousInterrupt.h
+++ b/wpilibc/src/main/native/include/frc/AsynchronousInterrupt.h
@@ -82,7 +82,8 @@
    * @param source the DigitalSource the interrupts are triggered from
    * @param f the callback function to call when the interrupt is triggered
    * @param arg the first argument, interrupt was triggered on rising edge
-   * @param args the remaing arguments, interrupt was triggered on falling edge
+   * @param args the remaining arguments, interrupt was triggered on falling
+   * edge
    */
   template <typename Callable, typename Arg, typename... Args>
   AsynchronousInterrupt(DigitalSource& source, Callable&& f, Arg&& arg,
@@ -99,7 +100,8 @@
    * @param source the DigitalSource the interrupts are triggered from
    * @param f the callback function to call when the interrupt is triggered
    * @param arg the first argument, interrupt was triggered on rising edge
-   * @param args the remaing arguments, interrupt was triggered on falling edge
+   * @param args the remaining arguments, interrupt was triggered on falling
+   * edge
    */
   template <typename Callable, typename Arg, typename... Args>
   AsynchronousInterrupt(DigitalSource* source, Callable&& f, Arg&& arg,
@@ -116,7 +118,8 @@
    * @param source the DigitalSource the interrupts are triggered from
    * @param f the callback function to call when the interrupt is triggered
    * @param arg the first argument, interrupt was triggered on rising edge
-   * @param args the remaing arguments, interrupt was triggered on falling edge
+   * @param args the remaining arguments, interrupt was triggered on falling
+   * edge
    */
   template <typename Callable, typename Arg, typename... Args>
   AsynchronousInterrupt(std::shared_ptr<DigitalSource> source, Callable&& f,
diff --git a/wpilibc/src/main/native/include/frc/Compressor.h b/wpilibc/src/main/native/include/frc/Compressor.h
index d8125ed..1329a3b 100644
--- a/wpilibc/src/main/native/include/frc/Compressor.h
+++ b/wpilibc/src/main/native/include/frc/Compressor.h
@@ -59,56 +59,59 @@
   Compressor& operator=(Compressor&&) = default;
 
   /**
-   * Starts closed-loop control. Note that closed loop control is enabled by
-   * default.
-   *
-   * @deprecated Use EnableDigital() instead.
-   */
-  WPI_DEPRECATED("Use EnableDigital() instead")
-  void Start();
-
-  /**
-   * Stops closed-loop control. Note that closed loop control is enabled by
-   * default.
-   *
-   * @deprecated Use Disable() instead.
-   */
-  WPI_DEPRECATED("Use Disable() instead")
-  void Stop();
-
-  /**
    * Check if compressor output is active.
+   * To (re)enable the compressor use EnableDigital() or EnableAnalog(...).
    *
-   * @return true if the compressor is on
+   * @return true if the compressor is on.
+   * @deprecated To avoid confusion in thinking this (re)enables the compressor
+   * use IsEnabled().
    */
+  WPI_DEPRECATED(
+      "To avoid confusion in thinking this (re)enables the compressor use "
+      "IsEnabled()")
   bool Enabled() const;
 
   /**
-   * Check if the pressure switch is triggered.
+   * Returns whether the compressor is active or not.
    *
-   * @return true if pressure is low
+   * @return true if the compressor is on - otherwise false.
+   */
+  bool IsEnabled() const;
+
+  /**
+   * Returns the state of the pressure switch.
+   *
+   * @return True if pressure switch indicates that the system is not full,
+   * otherwise false.
    */
   bool GetPressureSwitchValue() const;
 
   /**
-   * Query how much current the compressor is drawing.
+   * Get the current drawn by the compressor.
    *
-   * @return The current through the compressor, in amps
+   * @return Current drawn by the compressor.
    */
   units::ampere_t GetCurrent() const;
 
   /**
-   * Query the analog input voltage (on channel 0) (if supported).
+   * If supported by the device, returns the analog input voltage (on channel
+   * 0).
    *
-   * @return The analog input voltage, in volts
+   * This function is only supported by the REV PH. On CTRE PCM, this will
+   * return 0.
+   *
+   * @return The analog input voltage, in volts.
    */
   units::volt_t GetAnalogVoltage() const;
 
   /**
-   * Query the analog sensor pressure (on channel 0) (if supported). Note this
-   * is only for use with the REV Analog Pressure Sensor.
+   * If supported by the device, returns the pressure read by the analog
+   * pressure sensor (on channel 0).
    *
-   * @return The analog sensor pressure, in PSI
+   * This function is only supported by the REV PH with the REV Analog Pressure
+   * Sensor. On CTRE PCM, this will return 0.
+   *
+   * @return The pressure read by the analog pressure sensor.
    */
   units::pounds_per_square_inch_t GetPressure() const;
 
@@ -118,34 +121,68 @@
   void Disable();
 
   /**
-   * Enable compressor closed loop control using digital input.
+   * Enables the compressor in digital mode using the digital pressure switch.
+   * The compressor will turn on when the pressure switch indicates that the
+   * system is not full, and will turn off when the pressure switch indicates
+   * that the system is full.
    */
   void EnableDigital();
 
   /**
-   * Enable compressor closed loop control using analog input. Note this is only
-   * for use with the REV Analog Pressure Sensor.
+   * If supported by the device, enables the compressor in analog mode. This
+   * mode uses an analog pressure sensor connected to analog channel 0 to cycle
+   * the compressor. The compressor will turn on when the pressure drops below
+   * {@code minPressure} and will turn off when the pressure reaches {@code
+   * maxPressure}. This mode is only supported by the REV PH with the REV Analog
+   * Pressure Sensor connected to analog channel 0.
    *
-   * <p>On CTRE PCM, this will enable digital control.
+   * On CTRE PCM, this will enable digital control.
    *
-   * @param minPressure The minimum pressure in PSI to enable compressor
-   * @param maxPressure The maximum pressure in PSI to disable compressor
+   * @param minPressure The minimum pressure. The compressor will turn on when
+   * the pressure drops below this value.
+   * @param maxPressure The maximum pressure. The compressor will turn off when
+   * the pressure reaches this value.
    */
   void EnableAnalog(units::pounds_per_square_inch_t minPressure,
                     units::pounds_per_square_inch_t maxPressure);
 
   /**
-   * Enable compressor closed loop control using hybrid input. Note this is only
-   * for use with the REV Analog Pressure Sensor.
+   * If supported by the device, enables the compressor in hybrid mode. This
+   * mode uses both a digital pressure switch and an analog pressure sensor
+   * connected to analog channel 0 to cycle the compressor. This mode is only
+   * supported by the REV PH with the REV Analog Pressure Sensor connected to
+   * analog channel 0.
+   *
+   * The compressor will turn on when \a both:
+   *
+   * - The digital pressure switch indicates the system is not full AND
+   * - The analog pressure sensor indicates that the pressure in the system
+   * is below the specified minimum pressure.
+   *
+   * The compressor will turn off when \a either:
+   *
+   * - The digital pressure switch is disconnected or indicates that the system
+   * is full OR
+   * - The pressure detected by the analog sensor is greater than the specified
+   * maximum pressure.
    *
    * On CTRE PCM, this will enable digital control.
    *
-   * @param minPressure The minimum pressure in PSI to enable compressor
-   * @param maxPressure The maximum pressure in PSI to disable compressor
+   * @param minPressure The minimum pressure. The compressor will turn on
+   * when the pressure drops below this value and the pressure switch indicates
+   * that the system is not full.
+   * @param maxPressure The maximum pressure. The compressor will turn
+   * off when the pressure reaches this value or the pressure switch is
+   * disconnected or indicates that the system is full.
    */
   void EnableHybrid(units::pounds_per_square_inch_t minPressure,
                     units::pounds_per_square_inch_t maxPressure);
 
+  /**
+   * Returns the active compressor configuration.
+   *
+   * @return The active compressor configuration.
+   */
   CompressorConfigType GetConfigType() const;
 
   void InitSendable(wpi::SendableBuilder& builder) override;
diff --git a/wpilibc/src/main/native/include/frc/Controller.h b/wpilibc/src/main/native/include/frc/Controller.h
deleted file mode 100644
index d750a0e..0000000
--- a/wpilibc/src/main/native/include/frc/Controller.h
+++ /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.
-
-#pragma once
-
-#include <wpi/deprecated.h>
-
-namespace frc {
-
-/**
- * Interface for Controllers.
- *
- * Common interface for controllers. Controllers run control loops, the most
- * common are PID controllers and their variants, but this includes anything
- * that is controlling an actuator in a separate thread.
- *
- * @deprecated Only used by the deprecated PIDController
- */
-class Controller {
- public:
-  WPI_DEPRECATED("Only used by the deprecated PIDController")
-  Controller() = default;
-  virtual ~Controller() = default;
-
-  Controller(Controller&&) = default;
-  Controller& operator=(Controller&&) = default;
-
-  /**
-   * Allows the control loop to run
-   */
-  virtual void Enable() = 0;
-
-  /**
-   * Stops the control loop from running until explicitly re-enabled by calling
-   * enable()
-   */
-  virtual void Disable() = 0;
-};
-
-}  // namespace frc
diff --git a/wpilibc/src/main/native/include/frc/DataLogManager.h b/wpilibc/src/main/native/include/frc/DataLogManager.h
new file mode 100644
index 0000000..fa7abba
--- /dev/null
+++ b/wpilibc/src/main/native/include/frc/DataLogManager.h
@@ -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.
+
+#pragma once
+
+#include <string>
+#include <string_view>
+
+namespace wpi::log {
+class DataLog;
+}  // namespace wpi::log
+
+namespace frc {
+
+/**
+ * Centralized data log that provides automatic data log file management. It
+ * automatically cleans up old files when disk space is low and renames the file
+ * based either on current date/time or (if available) competition match number.
+ * The deta file will be saved to a USB flash drive if one is attached, or to
+ * /home/lvuser otherwise.
+ *
+ * Log files are initially named "FRC_TBD_{random}.wpilog" until the DS
+ * connects. After the DS connects, the log file is renamed to
+ * "FRC_yyyyMMdd_HHmmss.wpilog" (where the date/time is UTC). If the FMS is
+ * connected and provides a match number, the log file is renamed to
+ * "FRC_yyyyMMdd_HHmmss_{event}_{match}.wpilog".
+ *
+ * On startup, all existing FRC_TBD log files are deleted. If there is less than
+ * 50 MB of free space on the target storage, FRC_ log files are deleted (oldest
+ * to newest) until there is 50 MB free OR there are 10 files remaining.
+ *
+ * By default, all NetworkTables value changes are stored to the data log.
+ */
+class DataLogManager final {
+ public:
+  DataLogManager() = delete;
+
+  /**
+   * Start data log manager. The parameters have no effect if the data log
+   * manager was already started (e.g. by calling another static function).
+   *
+   * @param dir if not empty, directory to use for data log storage
+   * @param filename filename to use; if none provided, the filename is
+   *     automatically generated
+   * @param period time between automatic flushes to disk, in seconds;
+   *               this is a time/storage tradeoff
+   */
+  static void Start(std::string_view dir = "", std::string_view filename = "",
+                    double period = 0.25);
+
+  /**
+   * Log a message to the "messages" entry. The message is also printed to
+   * standard output (followed by a newline).
+   *
+   * @param message message
+   */
+  static void Log(std::string_view message);
+
+  /**
+   * Get the managed data log (for custom logging). Starts the data log manager
+   * if not already started.
+   *
+   * @return data log
+   */
+  static wpi::log::DataLog& GetLog();
+
+  /**
+   * Get the log directory.
+   *
+   * @return log directory
+   */
+  static std::string GetLogDir();
+
+  /**
+   * Enable or disable logging of NetworkTables data. Note that unlike the
+   * network interface for NetworkTables, this will capture every value change.
+   * Defaults to enabled.
+   *
+   * @param enabled true to enable, false to disable
+   */
+  static void LogNetworkTables(bool enabled);
+};
+
+}  // namespace frc
diff --git a/wpilibc/src/main/native/include/frc/DigitalOutput.h b/wpilibc/src/main/native/include/frc/DigitalOutput.h
index 0e124f1..d2981fa 100644
--- a/wpilibc/src/main/native/include/frc/DigitalOutput.h
+++ b/wpilibc/src/main/native/include/frc/DigitalOutput.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <hal/Types.h>
+#include <units/time.h>
 #include <wpi/sendable/Sendable.h>
 #include <wpi/sendable/SendableHelper.h>
 
@@ -79,11 +80,11 @@
    * Output a single pulse on the digital output line.
    *
    * Send a single pulse on the digital output line where the pulse duration is
-   * specified in seconds. Maximum pulse length is 0.0016 seconds.
+   * specified in seconds. Maximum of 65535 microseconds.
    *
-   * @param length The pulse length in seconds
+   * @param pulseLength The pulse length in seconds
    */
-  void Pulse(double length);
+  void Pulse(units::second_t pulseLength);
 
   /**
    * Determine if the pulse is still going.
@@ -105,6 +106,19 @@
   void SetPWMRate(double rate);
 
   /**
+   * Enable a PWM PPS (Pulse Per Second) Output on this line.
+   *
+   * Allocate one of the 6 DO PWM generator resources from this module.
+   *
+   * Supply the duty-cycle to output.
+   *
+   * The resolution of the duty cycle is 8-bit.
+   *
+   * @param dutyCycle The duty-cycle to start generating. [0..1]
+   */
+  void EnablePPS(double dutyCycle);
+
+  /**
    * Enable a PWM Output on this line.
    *
    * Allocate one of the 6 DO PWM generator resources from this module.
diff --git a/wpilibc/src/main/native/include/frc/DriverStation.h b/wpilibc/src/main/native/include/frc/DriverStation.h
index 30bf4fc..4cbf6da 100644
--- a/wpilibc/src/main/native/include/frc/DriverStation.h
+++ b/wpilibc/src/main/native/include/frc/DriverStation.h
@@ -7,7 +7,11 @@
 #include <string>
 
 #include <units/time.h>
-#include <wpi/deprecated.h>
+#include <wpi/Synchronization.h>
+
+namespace wpi::log {
+class DataLog;
+}  // namespace wpi::log
 
 namespace frc {
 
@@ -15,20 +19,11 @@
  * Provide access to the network communication data to / from the Driver
  * Station.
  */
-class DriverStation {
+class DriverStation final {
  public:
   enum Alliance { kRed, kBlue, kInvalid };
   enum MatchType { kNone, kPractice, kQualification, kElimination };
 
-  /**
-   * Return a reference to the singleton DriverStation.
-   *
-   * @return Reference to the DS instance
-   * @deprecated Use the static methods
-   */
-  WPI_DEPRECATED("Use static methods")
-  static DriverStation& GetInstance();
-
   static constexpr int kJoystickPorts = 6;
 
   /**
@@ -196,15 +191,6 @@
    * Check if the DS is commanding teleop mode.
    *
    * @return True if the robot is being commanded to be in teleop mode
-   * @deprecated Use IsTeleop() instead.
-   */
-  WPI_DEPRECATED("Use IsTeleop() instead")
-  static bool IsOperatorControl();
-
-  /**
-   * Check if the DS is commanding teleop mode.
-   *
-   * @return True if the robot is being commanded to be in teleop mode
    */
   static bool IsTeleop();
 
@@ -213,16 +199,6 @@
    *
    * @return True if the robot is being commanded to be in teleop mode and
    * enabled.
-   * @deprecated Use IsTeleopEnabled() instead.
-   */
-  WPI_DEPRECATED("Use IsTeleopEnabled() instead")
-  static bool IsOperatorControlEnabled();
-
-  /**
-   * Check if the DS is commanding teleop mode and if it has enabled the robot.
-   *
-   * @return True if the robot is being commanded to be in teleop mode and
-   * enabled.
    */
   static bool IsTeleopEnabled();
 
@@ -241,17 +217,6 @@
   static bool IsDSAttached();
 
   /**
-   * Has a new control packet from the driver station arrived since the last
-   * time this function was called?
-   *
-   * Warning: If you call this function from more than one place at the same
-   * time, you will not get the intended behavior.
-   *
-   * @return True if the control data has been updated since the last call.
-   */
-  static bool IsNewControlData();
-
-  /**
    * Is the driver station attached to a Field Management System?
    *
    * @return True if the robot is competing on a field being controlled by a
@@ -315,41 +280,6 @@
   static int GetLocation();
 
   /**
-   * Wait until a new packet comes from the driver station.
-   *
-   * This blocks on a semaphore, so the waiting is efficient.
-   *
-   * This is a good way to delay processing until there is new driver station
-   * data to act on.
-   *
-   * Checks if new control data has arrived since the last waitForData call
-   * on the current thread. If new data has not arrived, returns immediately.
-   */
-  static void WaitForData();
-
-  /**
-   * Wait until a new packet comes from the driver station, or wait for a
-   * timeout.
-   *
-   * Checks if new control data has arrived since the last waitForData call
-   * on the current thread. If new data has not arrived, returns immediately.
-   *
-   * If the timeout is less then or equal to 0, wait indefinitely.
-   *
-   * Timeout is in milliseconds
-   *
-   * This blocks on a semaphore, so the waiting is efficient.
-   *
-   * This is a good way to delay processing until there is new driver station
-   * data to act on.
-   *
-   * @param timeout Timeout
-   *
-   * @return true if new data, otherwise false
-   */
-  static bool WaitForData(units::second_t timeout);
-
-  /**
    * Return the approximate match time.
    *
    * The FMS does not send an official match time to the robots, but does send
@@ -373,56 +303,10 @@
    */
   static double GetBatteryVoltage();
 
-  /**
-   * Only to be used to tell the Driver Station what code you claim to be
-   * executing for diagnostic purposes only.
-   *
-   * @param entering If true, starting disabled code; if false, leaving disabled
-   *                 code.
-   */
-  static void InDisabled(bool entering);
+  static void RefreshData();
 
-  /**
-   * Only to be used to tell the Driver Station what code you claim to be
-   * executing for diagnostic purposes only.
-   *
-   * @param entering If true, starting autonomous code; if false, leaving
-   *                 autonomous code.
-   */
-  static void InAutonomous(bool entering);
-
-  /**
-   * Only to be used to tell the Driver Station what code you claim to be
-   * executing for diagnostic purposes only.
-   *
-   * @param entering If true, starting teleop code; if false, leaving teleop
-   *                 code.
-   * @deprecated Use InTeleop() instead.
-   */
-  WPI_DEPRECATED("Use InTeleop() instead")
-  static void InOperatorControl(bool entering);
-
-  /**
-   * Only to be used to tell the Driver Station what code you claim to be
-   * executing for diagnostic purposes only.
-   *
-   * @param entering If true, starting teleop code; if false, leaving teleop
-   *                 code.
-   */
-  static void InTeleop(bool entering);
-
-  /**
-   * Only to be used to tell the Driver Station what code you claim to be
-   * executing for diagnostic purposes only.
-   *
-   * @param entering If true, starting test code; if false, leaving test code.
-   */
-  static void InTest(bool entering);
-
-  /**
-   * Forces WaitForData() to return immediately.
-   */
-  static void WakeupWaitForData();
+  static void ProvideRefreshedDataEventHandle(WPI_EventHandle handle);
+  static void RemoveRefreshedDataEventHandle(WPI_EventHandle handle);
 
   /**
    * Allows the user to specify whether they want joystick connection warnings
@@ -441,6 +325,14 @@
    */
   static bool IsJoystickConnectionWarningSilenced();
 
+  /**
+   * Starts logging DriverStation data to data log. Repeated calls are ignored.
+   *
+   * @param log data log
+   * @param logJoysticks if true, log joystick data
+   */
+  static void StartDataLog(wpi::log::DataLog& log, bool logJoysticks = true);
+
  private:
   DriverStation() = default;
 };
diff --git a/wpilibc/src/main/native/include/frc/DutyCycle.h b/wpilibc/src/main/native/include/frc/DutyCycle.h
index 7f45d06..e222045 100644
--- a/wpilibc/src/main/native/include/frc/DutyCycle.h
+++ b/wpilibc/src/main/native/include/frc/DutyCycle.h
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include <hal/Types.h>
+#include <units/time.h>
 #include <wpi/sendable/Sendable.h>
 #include <wpi/sendable/SendableHelper.h>
 
@@ -83,21 +84,18 @@
   double GetOutput() const;
 
   /**
-   * Get the raw output ratio of the duty cycle signal.
+   * Get the raw high time of the duty cycle signal.
    *
-   * <p> 0 means always low, an output equal to
-   * GetOutputScaleFactor() means always high.
-   *
-   * @return output ratio in raw units
+   * @return high time of last pulse
    */
-  unsigned int GetOutputRaw() const;
+  units::second_t GetHighTime() const;
 
   /**
    * Get the scale factor of the output.
    *
    * <p> An output equal to this value is always high, and then linearly scales
-   * down to 0. Divide the result of getOutputRaw by this in order to get the
-   * percentage between 0 and 1.
+   * down to 0. Divide a raw result by this in order to get the
+   * percentage between 0 and 1. Used by DMA.
    *
    * @return the output scale factor
    */
diff --git a/wpilibc/src/main/native/include/frc/DutyCycleEncoder.h b/wpilibc/src/main/native/include/frc/DutyCycleEncoder.h
index ab7ded9..5d04ada 100644
--- a/wpilibc/src/main/native/include/frc/DutyCycleEncoder.h
+++ b/wpilibc/src/main/native/include/frc/DutyCycleEncoder.h
@@ -122,6 +122,40 @@
   units::turn_t Get() const;
 
   /**
+   * Get the absolute position of the duty cycle encoder encoder.
+   *
+   * <p>GetAbsolutePosition() - GetPositionOffset() will give an encoder
+   * absolute position relative to the last reset. This could potentially be
+   * negative, which needs to be accounted for.
+   *
+   * <p>This will not account for rollovers, and will always be just the raw
+   * absolute position.
+   *
+   * @return the absolute position
+   */
+  double GetAbsolutePosition() const;
+
+  /**
+   * Get the offset of position relative to the last reset.
+   *
+   * GetAbsolutePosition() - GetPositionOffset() will give an encoder absolute
+   * position relative to the last reset. This could potentially be negative,
+   * which needs to be accounted for.
+   *
+   * @return the position offset
+   */
+  double GetPositionOffset() const;
+
+  /**
+   * Set the position offset.
+   *
+   * <p>This must be in the range of 0-1.
+   *
+   * @param offset the offset
+   */
+  void SetPositionOffset(double offset);
+
+  /**
    * Set the encoder duty cycle range. As the encoder needs to maintain a duty
    * cycle, the duty cycle cannot go all the way to 0% or all the way to 100%.
    * For example, an encoder with a 4096 us period might have a minimum duty
@@ -182,6 +216,7 @@
 
  private:
   void Init();
+  double MapSensorRange(double pos) const;
 
   std::shared_ptr<DutyCycle> m_dutyCycle;
   std::unique_ptr<AnalogTrigger> m_analogTrigger;
@@ -195,6 +230,7 @@
 
   hal::SimDevice m_simDevice;
   hal::SimDouble m_simPosition;
+  hal::SimDouble m_simAbsolutePosition;
   hal::SimDouble m_simDistancePerRotation;
   hal::SimBoolean m_simIsConnected;
 };
diff --git a/wpilibc/src/main/native/include/frc/Encoder.h b/wpilibc/src/main/native/include/frc/Encoder.h
index f6753c2..deb36c0 100644
--- a/wpilibc/src/main/native/include/frc/Encoder.h
+++ b/wpilibc/src/main/native/include/frc/Encoder.h
@@ -166,7 +166,9 @@
    * scaled using the value from SetDistancePerPulse().
    *
    * @return Period in seconds of the most recent pulse.
+   * @deprecated Use getRate() in favor of this method.
    */
+  WPI_DEPRECATED("Use GetRate() in favor of this method")
   units::second_t GetPeriod() const override;
 
   /**
@@ -177,13 +179,12 @@
    * to determine if the wheels or other shaft has stopped rotating.
    * This method compensates for the decoding type.
    *
-   * @deprecated Use SetMinRate() in favor of this method.  This takes unscaled
-   *             periods and SetMinRate() scales using value from
-   *             SetDistancePerPulse().
-   *
    * @param maxPeriod The maximum time between rising and falling edges before
    *                  the FPGA will report the device stopped. This is expressed
    *                  in seconds.
+   * @deprecated Use SetMinRate() in favor of this method.  This takes unscaled
+   *             periods and SetMinRate() scales using value from
+   *             SetDistancePerPulse().
    */
   WPI_DEPRECATED(
       "Use SetMinRate() in favor of this method.  This takes unscaled periods "
diff --git a/wpilibc/src/main/native/include/frc/Errors.h b/wpilibc/src/main/native/include/frc/Errors.h
index 84edc18..252a335 100644
--- a/wpilibc/src/main/native/include/frc/Errors.h
+++ b/wpilibc/src/main/native/include/frc/Errors.h
@@ -74,11 +74,12 @@
  * @param[in]  format error message format
  * @param[in]  args error message format args
  */
-template <typename S, typename... Args>
+template <typename... Args>
 inline void ReportError(int32_t status, const char* fileName, int lineNumber,
-                        const char* funcName, const S& format, Args&&... args) {
+                        const char* funcName, fmt::string_view format,
+                        Args&&... args) {
   ReportErrorV(status, fileName, lineNumber, funcName, format,
-               fmt::make_args_checked<Args...>(format, args...));
+               fmt::make_format_args(args...));
 }
 
 /**
@@ -99,14 +100,12 @@
                                       fmt::string_view format,
                                       fmt::format_args args);
 
-template <typename S, typename... Args>
-[[nodiscard]] inline RuntimeError MakeError(int32_t status,
-                                            const char* fileName,
-                                            int lineNumber,
-                                            const char* funcName,
-                                            const S& format, Args&&... args) {
+template <typename... Args>
+[[nodiscard]] inline RuntimeError MakeError(
+    int32_t status, const char* fileName, int lineNumber, const char* funcName,
+    fmt::string_view format, Args&&... args) {
   return MakeErrorV(status, fileName, lineNumber, funcName, format,
-                    fmt::make_args_checked<Args...>(format, args...));
+                    fmt::make_format_args(args...));
 }
 
 namespace err {
@@ -122,18 +121,24 @@
 }  // namespace warn
 }  // namespace frc
 
+// C++20 relaxed the number of arguments to variadics, but Apple Clang's
+// warnings haven't caught up yet: https://stackoverflow.com/a/67996331
+#ifdef __clang__
+#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
+#endif
+
 /**
  * Reports an error to the driver station (using HAL_SendError).
  *
  * @param[out] status error code
  * @param[in]  format error message format
  */
-#define FRC_ReportError(status, format, ...)                       \
-  do {                                                             \
-    if ((status) != 0) {                                           \
-      ::frc::ReportError(status, __FILE__, __LINE__, __FUNCTION__, \
-                         FMT_STRING(format), __VA_ARGS__);         \
-    }                                                              \
+#define FRC_ReportError(status, format, ...)                             \
+  do {                                                                   \
+    if ((status) != 0) {                                                 \
+      ::frc::ReportError(status, __FILE__, __LINE__, __FUNCTION__,       \
+                         FMT_STRING(format) __VA_OPT__(, ) __VA_ARGS__); \
+    }                                                                    \
   } while (0)
 
 /**
@@ -146,7 +151,7 @@
  */
 #define FRC_MakeError(status, format, ...)                   \
   ::frc::MakeError(status, __FILE__, __LINE__, __FUNCTION__, \
-                   FMT_STRING(format), __VA_ARGS__)
+                   FMT_STRING(format) __VA_OPT__(, ) __VA_ARGS__)
 
 /**
  * Checks a status code and depending on its value, either throws a
@@ -155,23 +160,24 @@
  * @param[out] status error code
  * @param[in]  format error message format
  */
-#define FRC_CheckErrorStatus(status, format, ...)                      \
-  do {                                                                 \
-    if ((status) < 0) {                                                \
-      throw ::frc::MakeError(status, __FILE__, __LINE__, __FUNCTION__, \
-                             FMT_STRING(format), __VA_ARGS__);         \
-    } else if ((status) > 0) {                                         \
-      ::frc::ReportError(status, __FILE__, __LINE__, __FUNCTION__,     \
-                         FMT_STRING(format), __VA_ARGS__);             \
-    }                                                                  \
+#define FRC_CheckErrorStatus(status, format, ...)                            \
+  do {                                                                       \
+    if ((status) < 0) {                                                      \
+      throw ::frc::MakeError(status, __FILE__, __LINE__, __FUNCTION__,       \
+                             FMT_STRING(format) __VA_OPT__(, ) __VA_ARGS__); \
+    } else if ((status) > 0) {                                               \
+      ::frc::ReportError(status, __FILE__, __LINE__, __FUNCTION__,           \
+                         FMT_STRING(format) __VA_OPT__(, ) __VA_ARGS__);     \
+    }                                                                        \
   } while (0)
 
 #define FRC_AssertMessage(condition, format, ...)                            \
   do {                                                                       \
     if (!(condition)) {                                                      \
       throw ::frc::MakeError(err::AssertionFailure, __FILE__, __LINE__,      \
-                             __FUNCTION__, FMT_STRING(format), __VA_ARGS__); \
+                             __FUNCTION__,                                   \
+                             FMT_STRING(format) __VA_OPT__(, ) __VA_ARGS__); \
     }                                                                        \
   } while (0)
 
-#define FRC_Assert(condition) FRC_AssertMessage(condition, "{}", #condition)
+#define FRC_Assert(condition) FRC_AssertMessage(condition, #condition)
diff --git a/wpilibc/src/main/native/include/frc/GenericHID.h b/wpilibc/src/main/native/include/frc/GenericHID.h
index b6c18d3..f6db94c 100644
--- a/wpilibc/src/main/native/include/frc/GenericHID.h
+++ b/wpilibc/src/main/native/include/frc/GenericHID.h
@@ -10,7 +10,8 @@
 
 namespace frc {
 
-class DriverStation;
+class BooleanEvent;
+class EventLoop;
 
 /**
  * Handle input from standard HID devices connected to the Driver Station.
@@ -22,7 +23,7 @@
  */
 class GenericHID {
  public:
-  enum RumbleType { kLeftRumble, kRightRumble };
+  enum RumbleType { kLeftRumble, kRightRumble, kBothRumble };
 
   enum HIDType {
     kUnknown = -1,
@@ -92,6 +93,16 @@
   bool GetRawButtonReleased(int button);
 
   /**
+   * 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.
+   * @return an event instance representing the button's digital signal attached
+   * to the given loop.
+   */
+  BooleanEvent Button(int button, EventLoop* loop) const;
+
+  /**
    * Get the value of the axis.
    *
    * @param axis The axis to read, starting at 0.
@@ -111,6 +122,141 @@
   int GetPOV(int pov = 0) const;
 
   /**
+   * Constructs a BooleanEvent instance based around this angle of a POV on the
+   * HID.
+   *
+   * <p>The POV angles start at 0 in the up direction, and increase clockwise
+   * (eg right is 90, upper-left is 315).
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @param angle POV angle in degrees, or -1 for the center / not pressed.
+   * @return a BooleanEvent instance based around this angle of a POV on the
+   * HID.
+   */
+  BooleanEvent POV(int angle, EventLoop* loop) const;
+
+  /**
+   * Constructs a BooleanEvent instance based around this angle of a POV on the
+   * HID.
+   *
+   * <p>The POV angles start at 0 in the up direction, and increase clockwise
+   * (eg right is 90, upper-left is 315).
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @param pov   index of the POV to read (starting at 0). Defaults to 0.
+   * @param angle POV angle in degrees, or -1 for the center / not pressed.
+   * @return a BooleanEvent instance based around this angle of a POV on the
+   * HID.
+   */
+  BooleanEvent POV(int pov, int angle, EventLoop* loop) const;
+
+  /**
+   * Constructs a BooleanEvent instance based around the 0 degree angle (up) of
+   * the default (index 0) POV on the HID.
+   *
+   * @return a BooleanEvent instance based around the 0 degree angle of a POV on
+   * the HID.
+   */
+  BooleanEvent POVUp(EventLoop* loop) const;
+
+  /**
+   * Constructs a BooleanEvent instance based around the 45 degree angle (right
+   * up) of the default (index 0) POV on the HID.
+   *
+   * @return a BooleanEvent instance based around the 45 degree angle of a POV
+   * on the HID.
+   */
+  BooleanEvent POVUpRight(EventLoop* loop) const;
+
+  /**
+   * Constructs a BooleanEvent instance based around the 90 degree angle (right)
+   * of the default (index 0) POV on the HID.
+   *
+   * @return a BooleanEvent instance based around the 90 degree angle of a POV
+   * on the HID.
+   */
+  BooleanEvent POVRight(EventLoop* loop) const;
+
+  /**
+   * Constructs a BooleanEvent instance based around the 135 degree angle (right
+   * down) of the default (index 0) POV on the HID.
+   *
+   * @return a BooleanEvent instance based around the 135 degree angle of a POV
+   * on the HID.
+   */
+  BooleanEvent POVDownRight(EventLoop* loop) const;
+
+  /**
+   * Constructs a BooleanEvent instance based around the 180 degree angle (down)
+   * of the default (index 0) POV on the HID.
+   *
+   * @return a BooleanEvent instance based around the 180 degree angle of a POV
+   * on the HID.
+   */
+  BooleanEvent POVDown(EventLoop* loop) const;
+
+  /**
+   * Constructs a BooleanEvent instance based around the 225 degree angle (down
+   * left) of the default (index 0) POV on the HID.
+   *
+   * @return a BooleanEvent instance based around the 225 degree angle of a POV
+   * on the HID.
+   */
+  BooleanEvent POVDownLeft(EventLoop* loop) const;
+
+  /**
+   * Constructs a BooleanEvent instance based around the 270 degree angle (left)
+   * of the default (index 0) POV on the HID.
+   *
+   * @return a BooleanEvent instance based around the 270 degree angle of a POV
+   * on the HID.
+   */
+  BooleanEvent POVLeft(EventLoop* loop) const;
+
+  /**
+   * Constructs a BooleanEvent instance based around the 315 degree angle (left
+   * up) of the default (index 0) POV on the HID.
+   *
+   * @return a BooleanEvent instance based around the 315 degree angle of a POV
+   * on the HID.
+   */
+  BooleanEvent POVUpLeft(EventLoop* loop) const;
+
+  /**
+   * Constructs a BooleanEvent instance based around the center (not pressed) of
+   * the default (index 0) POV on the HID.
+   *
+   * @return a BooleanEvent instance based around the center of a POV on the
+   * HID.
+   */
+  BooleanEvent POVCenter(EventLoop* loop) const;
+
+  /**
+   * Constructs an event instance that is true when the axis value is less than
+   * threshold
+   *
+   * @param axis The axis to read, starting at 0.
+   * @param threshold The value below which this trigger should return true.
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance that is true when the axis value is less than the
+   * provided threshold.
+   */
+  BooleanEvent AxisLessThan(int axis, double threshold, EventLoop* loop) const;
+
+  /**
+   * Constructs an event instance that is true when the axis value is greater
+   * than threshold
+   *
+   * @param axis The axis to read, starting at 0.
+   * @param threshold The value above which this trigger should return true.
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance that is true when the axis value is greater than
+   * the provided threshold.
+   */
+  BooleanEvent AxisGreaterThan(int axis, double threshold,
+                               EventLoop* loop) const;
+
+  /**
    * Get the number of axes for the HID.
    *
    * @return the number of axis for the current HID
diff --git a/wpilibc/src/main/native/include/frc/I2C.h b/wpilibc/src/main/native/include/frc/I2C.h
index d874a46..9489fcf 100644
--- a/wpilibc/src/main/native/include/frc/I2C.h
+++ b/wpilibc/src/main/native/include/frc/I2C.h
@@ -15,6 +15,10 @@
  *
  * This class is intended to be used by sensor (and other I2C device) drivers.
  * It probably should not be used directly.
+ *
+ * The Onboard I2C port is subject to system lockups. See <a
+ * href="https://docs.wpilib.org/en/stable/docs/yearly-overview/known-issues.html#onboard-i2c-causing-system-lockups">
+ * WPILib Known Issues</a> page for details.
  */
 class I2C {
  public:
diff --git a/wpilibc/src/main/native/include/frc/IterativeRobotBase.h b/wpilibc/src/main/native/include/frc/IterativeRobotBase.h
index c4253ef..77ed197 100644
--- a/wpilibc/src/main/native/include/frc/IterativeRobotBase.h
+++ b/wpilibc/src/main/native/include/frc/IterativeRobotBase.h
@@ -5,7 +5,6 @@
 #pragma once
 
 #include <units/time.h>
-#include <wpi/deprecated.h>
 
 #include "frc/RobotBase.h"
 #include "frc/Watchdog.h"
@@ -196,13 +195,26 @@
 
   /**
    * Enables or disables flushing NetworkTables every loop iteration.
-   * By default, this is disabled.
+   * By default, this is enabled.
    *
    * @param enabled True to enable, false to disable
    */
   void SetNetworkTablesFlushEnabled(bool enabled);
 
   /**
+   * Sets whether LiveWindow operation is enabled during test mode.
+   *
+   * @param testLW True to enable, false to disable. Defaults to true.
+   * @throws if called in test mode.
+   */
+  void EnableLiveWindowInTest(bool testLW);
+
+  /**
+   * Whether LiveWindow operation is enabled during test mode.
+   */
+  bool IsLiveWindowEnabledInTest();
+
+  /**
    * Gets time period between calls to Periodic() functions.
    */
   units::second_t GetPeriod() const;
@@ -210,17 +222,6 @@
   /**
    * Constructor for IterativeRobotBase.
    *
-   * @param period Period in seconds.
-   *
-   * @deprecated Use IterativeRobotBase(units::second_t period) with unit-safety
-   * instead
-   */
-  WPI_DEPRECATED("Use constructor with unit-safety instead.")
-  explicit IterativeRobotBase(double period);
-
-  /**
-   * Constructor for IterativeRobotBase.
-   *
    * @param period Period.
    */
   explicit IterativeRobotBase(units::second_t period);
@@ -239,7 +240,8 @@
   Mode m_lastMode = Mode::kNone;
   units::second_t m_period;
   Watchdog m_watchdog;
-  bool m_ntFlushEnabled = false;
+  bool m_ntFlushEnabled = true;
+  bool m_lwEnabledInTest = true;
 
   void PrintLoopOverrunMessage();
 };
diff --git a/wpilibc/src/main/native/include/frc/Joystick.h b/wpilibc/src/main/native/include/frc/Joystick.h
index 0d63e4e..fc0df35 100644
--- a/wpilibc/src/main/native/include/frc/Joystick.h
+++ b/wpilibc/src/main/native/include/frc/Joystick.h
@@ -173,6 +173,15 @@
   bool GetTriggerReleased();
 
   /**
+   * Constructs an event instance around the trigger button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the trigger button's digital signal
+   * attached to the given loop.
+   */
+  BooleanEvent Trigger(EventLoop* loop) const;
+
+  /**
    * Read the state of the top button on the joystick.
    *
    * Look up which button has been assigned to the top and read its state.
@@ -196,6 +205,15 @@
   bool GetTopReleased();
 
   /**
+   * Constructs an event instance around the top button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the top button's digital signal
+   * attached to the given loop.
+   */
+  BooleanEvent Top(EventLoop* loop) const;
+
+  /**
    * Get the magnitude of the direction vector formed by the joystick's
    * current position relative to its origin.
    *
diff --git a/wpilibc/src/main/native/include/frc/MotorSafety.h b/wpilibc/src/main/native/include/frc/MotorSafety.h
index 1bfd34a..a17cdc0 100644
--- a/wpilibc/src/main/native/include/frc/MotorSafety.h
+++ b/wpilibc/src/main/native/include/frc/MotorSafety.h
@@ -14,8 +14,11 @@
 namespace frc {
 
 /**
- * This base class runs a watchdog timer and calls the subclass's StopMotor()
- * function if the timeout expires.
+ * The Motor Safety feature acts as a watchdog timer for an individual motor. It
+ * operates by maintaining a timer that tracks how long it has been since the
+ * feed() method has been called for that actuator. Code in the Driver Station
+ * class initiates a comparison of these timers to the timeout values for any
+ * actuator with safety enabled every 5 received packets (100ms nominal).
  *
  * The subclass should call Feed() whenever the motor value is updated.
  */
diff --git a/wpilibc/src/main/native/include/frc/Notifier.h b/wpilibc/src/main/native/include/frc/Notifier.h
index 61b7fbb..bda685c 100644
--- a/wpilibc/src/main/native/include/frc/Notifier.h
+++ b/wpilibc/src/main/native/include/frc/Notifier.h
@@ -19,6 +19,13 @@
 
 namespace frc {
 
+/**
+ * Notifiers run a callback function on a separate thread at a specified period.
+ *
+ * If StartSingle() is used, the callback will run once. If StartPeriodic() is
+ * used, the callback will run repeatedly with the given period until stop() is
+ * called.
+ */
 class Notifier {
  public:
   /**
@@ -95,6 +102,9 @@
    * interrupt occurs, the event will be immediately requeued for the same time
    * interval.
    *
+   * The user-provided callback should be written in a nonblocking manner so the
+   * callback can be recalled at the next periodic event notification.
+   *
    * @param period Period to call the handler starting one period
    *               after the call to this method.
    */
diff --git a/wpilibc/src/main/native/include/frc/PS4Controller.h b/wpilibc/src/main/native/include/frc/PS4Controller.h
index ee87501..9487634 100644
--- a/wpilibc/src/main/native/include/frc/PS4Controller.h
+++ b/wpilibc/src/main/native/include/frc/PS4Controller.h
@@ -99,6 +99,15 @@
   bool GetSquareButtonReleased();
 
   /**
+   * 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.
+   */
+  BooleanEvent Square(EventLoop* loop) const;
+
+  /**
    * Read the value of the Cross button on the controller.
    *
    * @return The state of the button.
@@ -120,6 +129,15 @@
   bool GetCrossButtonReleased();
 
   /**
+   * 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.
+   */
+  BooleanEvent Cross(EventLoop* loop) const;
+
+  /**
    * Read the value of the Circle button on the controller.
    *
    * @return The state of the button.
@@ -141,6 +159,15 @@
   bool GetCircleButtonReleased();
 
   /**
+   * 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.
+   */
+  BooleanEvent Circle(EventLoop* loop) const;
+
+  /**
    * Read the value of the Triangle button on the controller.
    *
    * @return The state of the button.
@@ -162,6 +189,15 @@
   bool GetTriangleButtonReleased();
 
   /**
+   * 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.
+   */
+  BooleanEvent Triangle(EventLoop* loop) const;
+
+  /**
    * Read the value of the L1 button on the controller.
    *
    * @return The state of the button.
@@ -183,6 +219,15 @@
   bool GetL1ButtonReleased();
 
   /**
+   * 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.
+   */
+  BooleanEvent L1(EventLoop* loop) const;
+
+  /**
    * Read the value of the R1 button on the controller.
    *
    * @return The state of the button.
@@ -204,6 +249,15 @@
   bool GetR1ButtonReleased();
 
   /**
+   * 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.
+   */
+  BooleanEvent R1(EventLoop* loop) const;
+
+  /**
    * Read the value of the L2 button on the controller.
    *
    * @return The state of the button.
@@ -225,6 +279,15 @@
   bool GetL2ButtonReleased();
 
   /**
+   * 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.
+   */
+  BooleanEvent L2(EventLoop* loop) const;
+
+  /**
    * Read the value of the R2 button on the controller.
    *
    * @return The state of the button.
@@ -246,6 +309,15 @@
   bool GetR2ButtonReleased();
 
   /**
+   * 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.
+   */
+  BooleanEvent R2(EventLoop* loop) const;
+
+  /**
    * Read the value of the Share button on the controller.
    *
    * @return The state of the button.
@@ -267,6 +339,15 @@
   bool GetShareButtonReleased();
 
   /**
+   * Constructs an event instance around the share button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the share button's digital signal
+   * attached to the given loop.
+   */
+  BooleanEvent Share(EventLoop* loop) const;
+
+  /**
    * Read the value of the Options button on the controller.
    *
    * @return The state of the button.
@@ -288,6 +369,15 @@
   bool GetOptionsButtonReleased();
 
   /**
+   * 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.
+   */
+  BooleanEvent Options(EventLoop* loop) const;
+
+  /**
    * Read the value of the L3 button (pressing the left analog stick) on the
    * controller.
    *
@@ -310,6 +400,15 @@
   bool GetL3ButtonReleased();
 
   /**
+   * 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.
+   */
+  BooleanEvent L3(EventLoop* loop) const;
+
+  /**
    * Read the value of the R3 button (pressing the right analog stick) on the
    * controller.
    *
@@ -332,6 +431,15 @@
   bool GetR3ButtonReleased();
 
   /**
+   * 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.
+   */
+  BooleanEvent R3(EventLoop* loop) const;
+
+  /**
    * Read the value of the PS button on the controller.
    *
    * @return The state of the button.
@@ -353,6 +461,15 @@
   bool GetPSButtonReleased();
 
   /**
+   * 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.
+   */
+  BooleanEvent PS(EventLoop* loop) const;
+
+  /**
    * Read the value of the touchpad button on the controller.
    *
    * @return The state of the button.
@@ -373,6 +490,15 @@
    */
   bool GetTouchpadReleased();
 
+  /**
+   * 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.
+   */
+  BooleanEvent Touchpad(EventLoop* loop) const;
+
   struct Button {
     static constexpr int kSquare = 1;
     static constexpr int kCross = 2;
diff --git a/wpilibc/src/main/native/include/frc/PWM.h b/wpilibc/src/main/native/include/frc/PWM.h
index efff540..e508915 100644
--- a/wpilibc/src/main/native/include/frc/PWM.h
+++ b/wpilibc/src/main/native/include/frc/PWM.h
@@ -166,7 +166,7 @@
   /**
    * Optionally eliminate the deadband from a motor controller.
    *
-   * @param eliminateDeadband If true, set the motor curve on the speed
+   * @param eliminateDeadband If true, set the motor curve on the motor
    *                          controller to eliminate the deadband in the middle
    *                          of the range. Otherwise, keep the full range
    *                          without modifying any values.
diff --git a/wpilibc/src/main/native/include/frc/PneumaticHub.h b/wpilibc/src/main/native/include/frc/PneumaticHub.h
index d412bb6..f876b7a 100644
--- a/wpilibc/src/main/native/include/frc/PneumaticHub.h
+++ b/wpilibc/src/main/native/include/frc/PneumaticHub.h
@@ -13,23 +13,72 @@
 #include "PneumaticsBase.h"
 
 namespace frc {
+/** Module class for controlling a REV Robotics Pneumatic Hub. */
 class PneumaticHub : public PneumaticsBase {
  public:
+  /** Constructs a PneumaticHub with the default ID (1). */
   PneumaticHub();
+
+  /**
+   * Constructs a PneumaticHub.
+   *
+   * @param module module number to construct
+   */
   explicit PneumaticHub(int module);
 
   ~PneumaticHub() override = default;
 
   bool GetCompressor() const override;
 
+  /**
+   * Disables the compressor. The compressor will not turn on until
+   * EnableCompressorDigital(), EnableCompressorAnalog(), or
+   * EnableCompressorHybrid() are called.
+   */
   void DisableCompressor() override;
 
   void EnableCompressorDigital() override;
 
+  /**
+   * Enables the compressor in analog mode. This mode uses an analog pressure
+   * sensor connected to analog channel 0 to cycle the compressor. The
+   * compressor will turn on when the pressure drops below {@code minPressure}
+   * and will turn off when the pressure reaches {@code maxPressure}.
+   *
+   * @param minPressure The minimum pressure. The compressor will turn on when
+   * the pressure drops below this value.
+   * @param maxPressure The maximum pressure. The compressor will turn off when
+   * the pressure reaches this value.
+   */
   void EnableCompressorAnalog(
       units::pounds_per_square_inch_t minPressure,
       units::pounds_per_square_inch_t maxPressure) override;
 
+  /**
+   * Enables the compressor in hybrid mode. This mode uses both a digital
+   * pressure switch and an analog pressure sensor connected to analog channel 0
+   * to cycle the compressor.
+   *
+   * The compressor will turn on when \a both:
+   *
+   * - The digital pressure switch indicates the system is not full AND
+   * - The analog pressure sensor indicates that the pressure in the system is
+   * below the specified minimum pressure.
+   *
+   * The compressor will turn off when \a either:
+   *
+   * - The digital pressure switch is disconnected or indicates that the system
+   * is full OR
+   * - The pressure detected by the analog sensor is greater than the specified
+   * maximum pressure.
+   *
+   * @param minPressure The minimum pressure. The compressor will turn on when
+   * the pressure drops below this value and the pressure switch indicates that
+   * the system is not full.
+   * @param maxPressure The maximum pressure. The compressor will turn off when
+   * the pressure reaches this value or the pressure switch is disconnected or
+   * indicates that the system is full.
+   */
   void EnableCompressorHybrid(
       units::pounds_per_square_inch_t minPressure,
       units::pounds_per_square_inch_t maxPressure) override;
@@ -76,6 +125,11 @@
     uint32_t UniqueId;
   };
 
+  /**
+   * Returns the hardware and firmware versions of this device.
+   *
+   * @return The hardware and firmware versions.
+   */
   Version GetVersion() const;
 
   struct Faults {
@@ -103,6 +157,11 @@
     uint32_t HardwareFault : 1;
   };
 
+  /**
+   * Returns the faults currently active on this device.
+   *
+   * @return The faults.
+   */
   Faults GetFaults() const;
 
   struct StickyFaults {
@@ -115,20 +174,60 @@
     uint32_t HasReset : 1;
   };
 
+  /**
+   * Returns the sticky faults currently active on this device.
+   *
+   * @return The sticky faults.
+   */
   StickyFaults GetStickyFaults() const;
 
+  /** Clears the sticky faults. */
   void ClearStickyFaults();
 
+  /**
+   * Returns the current input voltage for this device.
+   *
+   * @return The input voltage.
+   */
   units::volt_t GetInputVoltage() const;
 
+  /**
+   * Returns the current voltage of the regulated 5v supply.
+   *
+   * @return The current voltage of the 5v supply.
+   */
   units::volt_t Get5VRegulatedVoltage() const;
 
+  /**
+   * Returns the total current drawn by all solenoids.
+   *
+   * @return Total current drawn by all solenoids.
+   */
   units::ampere_t GetSolenoidsTotalCurrent() const;
 
+  /**
+   * Returns the current voltage of the solenoid power supply.
+   *
+   * @return The current voltage of the solenoid power supply.
+   */
   units::volt_t GetSolenoidsVoltage() const;
 
+  /**
+   * Returns the raw voltage of the specified analog input channel.
+   *
+   * @param channel The analog input channel to read voltage from.
+   * @return The voltage of the specified analog input channel.
+   */
   units::volt_t GetAnalogVoltage(int channel) const override;
 
+  /**
+   * Returns the pressure read by an analog pressure sensor on the specified
+   * analog input channel.
+   *
+   * @param channel The analog input channel to read pressure from.
+   * @return The pressure read by an analog pressure sensor on the specified
+   * analog input channel.
+   */
   units::pounds_per_square_inch_t GetPressure(int channel) const override;
 
  private:
diff --git a/wpilibc/src/main/native/include/frc/PneumaticsBase.h b/wpilibc/src/main/native/include/frc/PneumaticsBase.h
index 50ceb87..b455dc6 100644
--- a/wpilibc/src/main/native/include/frc/PneumaticsBase.h
+++ b/wpilibc/src/main/native/include/frc/PneumaticsBase.h
@@ -22,50 +22,194 @@
  public:
   virtual ~PneumaticsBase() = default;
 
+  /**
+   * Returns whether the compressor is active or not.
+   *
+   * @return True if the compressor is on - otherwise false.
+   */
   virtual bool GetCompressor() const = 0;
 
+  /**
+   * Returns the state of the pressure switch.
+   *
+   * @return True if pressure switch indicates that the system is full,
+   * otherwise false.
+   */
   virtual bool GetPressureSwitch() const = 0;
 
+  /**
+   * Returns the current drawn by the compressor.
+   *
+   * @return The current drawn by the compressor.
+   */
   virtual units::ampere_t GetCompressorCurrent() const = 0;
 
+  /** Disables the compressor. */
   virtual void DisableCompressor() = 0;
 
+  /**
+   * Enables the compressor in digital mode using the digital pressure switch.
+   * The compressor will turn on when the pressure switch indicates that the
+   * system is not full, and will turn off when the pressure switch indicates
+   * that the system is full.
+   */
   virtual void EnableCompressorDigital() = 0;
 
+  /**
+   * If supported by the device, enables the compressor in analog mode. This
+   * mode uses an analog pressure sensor connected to analog channel 0 to cycle
+   * the compressor. The compressor will turn on when the pressure drops below
+   * {@code minPressure} and will turn off when the pressure reaches {@code
+   * maxPressure}. This mode is only supported by the REV PH with the REV Analog
+   * Pressure Sensor connected to analog channel 0.
+   *
+   * On CTRE PCM, this will enable digital control.
+   *
+   * @param minPressure The minimum pressure. The compressor will turn on
+   * when the pressure drops below this value.
+   * @param maxPressure The maximum pressure. The compressor will turn
+   * off when the pressure reaches this value.
+   */
   virtual void EnableCompressorAnalog(
       units::pounds_per_square_inch_t minPressure,
       units::pounds_per_square_inch_t maxPressure) = 0;
 
+  /**
+   * If supported by the device, enables the compressor in hybrid mode. This
+   * mode uses both a digital pressure switch and an analog pressure sensor
+   * connected to analog channel 0 to cycle the compressor. This mode is only
+   * supported by the REV PH with the REV Analog Pressure Sensor connected to
+   * analog channel 0.
+   *
+   * The compressor will turn on when \a both:
+   *
+   * - The digital pressure switch indicates the system is not full AND
+   * - The analog pressure sensor indicates that the pressure in the system
+   * is below the specified minimum pressure.
+   *
+   * The compressor will turn off when \a either:
+   *
+   * - The digital pressure switch is disconnected or indicates that the system
+   * is full OR
+   * - The pressure detected by the analog sensor is greater than the specified
+   * maximum pressure.
+   *
+   * On CTRE PCM, this will enable digital control.
+   *
+   * @param minPressure The minimum pressure. The compressor will turn on
+   * when the pressure drops below this value and the pressure switch indicates
+   * that the system is not full.
+   * @param maxPressure The maximum pressure. The compressor will turn
+   * off when the pressure reaches this value or the pressure switch is
+   * disconnected or indicates that the system is full.
+   */
   virtual void EnableCompressorHybrid(
       units::pounds_per_square_inch_t minPressure,
       units::pounds_per_square_inch_t maxPressure) = 0;
 
+  /**
+   * Returns the active compressor configuration.
+   *
+   * @return The active compressor configuration.
+   */
   virtual CompressorConfigType GetCompressorConfigType() const = 0;
 
+  /**
+   * Sets solenoids on a pneumatics module.
+   *
+   * @param mask mask
+   * @param values values
+   */
   virtual void SetSolenoids(int mask, int values) = 0;
 
+  /**
+   * Gets a bitmask of solenoid values.
+   *
+   * @return values
+   */
   virtual int GetSolenoids() const = 0;
 
+  /**
+   * Get module number for this module.
+   *
+   * @return module number
+   */
   virtual int GetModuleNumber() const = 0;
 
+  /**
+   * Get a bitmask of disabled solenoids.
+   *
+   * @return bitmask of disabled solenoids
+   */
   virtual int GetSolenoidDisabledList() const = 0;
 
+  /**
+   * Fire a single solenoid shot.
+   *
+   * @param index solenoid index
+   */
   virtual void FireOneShot(int index) = 0;
 
+  /**
+   * Set the duration for a single solenoid shot.
+   *
+   * @param index solenoid index
+   * @param duration shot duration
+   */
   virtual void SetOneShotDuration(int index, units::second_t duration) = 0;
 
+  /**
+   * Check if a solenoid channel is valid.
+   *
+   * @param channel Channel to check
+   * @return True if channel exists
+   */
   virtual bool CheckSolenoidChannel(int channel) const = 0;
 
+  /**
+   * Check to see if the masked solenoids can be reserved, and if not reserve
+   * them.
+   *
+   * @param mask The bitmask of solenoids to reserve
+   * @return 0 if successful; mask of solenoids that couldn't be allocated
+   * otherwise
+   */
   virtual int CheckAndReserveSolenoids(int mask) = 0;
 
+  /**
+   * Unreserve the masked solenoids.
+   *
+   * @param mask The bitmask of solenoids to unreserve
+   */
   virtual void UnreserveSolenoids(int mask) = 0;
 
   virtual bool ReserveCompressor() = 0;
 
   virtual void UnreserveCompressor() = 0;
 
+  /**
+   * If supported by the device, returns the raw voltage of the specified analog
+   * input channel.
+   *
+   * This function is only supported by the REV PH. On CTRE PCM, this will
+   * return 0.
+   *
+   * @param channel The analog input channel to read voltage from.
+   * @return The voltage of the specified analog input channel.
+   */
   virtual units::volt_t GetAnalogVoltage(int channel) const = 0;
 
+  /**
+   * If supported by the device, returns the pressure read by an analog
+   * pressure sensor on the specified analog input channel.
+   *
+   * This function is only supported by the REV PH. On CTRE PCM, this will
+   * return 0.
+   *
+   * @param channel The analog input channel to read pressure from.
+   * @return The pressure read by an analog pressure sensor on the
+   * specified analog input channel.
+   */
   virtual units::pounds_per_square_inch_t GetPressure(int channel) const = 0;
 
   virtual Solenoid MakeSolenoid(int channel) = 0;
@@ -73,8 +217,22 @@
                                             int reverseChannel) = 0;
   virtual Compressor MakeCompressor() = 0;
 
+  /**
+   * For internal use to get a module for a specific type.
+   *
+   * @param module module number
+   * @param moduleType module type
+   * @return module
+   */
   static std::shared_ptr<PneumaticsBase> GetForType(
       int module, PneumaticsModuleType moduleType);
+
+  /**
+   * For internal use to get the default for a specific type.
+   *
+   * @param moduleType module type
+   * @return module default
+   */
   static int GetDefaultForType(PneumaticsModuleType moduleType);
 };
 }  // namespace frc
diff --git a/wpilibc/src/main/native/include/frc/PneumaticsControlModule.h b/wpilibc/src/main/native/include/frc/PneumaticsControlModule.h
index ea4517b..acad5a1 100644
--- a/wpilibc/src/main/native/include/frc/PneumaticsControlModule.h
+++ b/wpilibc/src/main/native/include/frc/PneumaticsControlModule.h
@@ -13,23 +13,52 @@
 #include "PneumaticsBase.h"
 
 namespace frc {
+/** Module class for controlling a Cross The Road Electronics Pneumatics Control
+ * Module. */
 class PneumaticsControlModule : public PneumaticsBase {
  public:
+  /** Constructs a PneumaticsControlModule with the default ID (0). */
   PneumaticsControlModule();
+
+  /**
+   * Constructs a PneumaticsControlModule.
+   *
+   * @param module module number to construct
+   */
   explicit PneumaticsControlModule(int module);
 
   ~PneumaticsControlModule() override = default;
 
   bool GetCompressor() const override;
 
+  /**
+   * Disables the compressor. The compressor will not turn on until
+   * EnableCompressorDigital() is called.
+   */
   void DisableCompressor() override;
 
   void EnableCompressorDigital() override;
 
+  /**
+   * Enables the compressor in digital mode. Analog mode is unsupported by the
+   * CTRE PCM.
+   *
+   * @param minPressure Unsupported.
+   * @param maxPressure Unsupported.
+   * @see EnableCompressorDigital()
+   */
   void EnableCompressorAnalog(
       units::pounds_per_square_inch_t minPressure,
       units::pounds_per_square_inch_t maxPressure) override;
 
+  /**
+   * Enables the compressor in digital mode. Hybrid mode is unsupported by the
+   * CTRE PCM.
+   *
+   * @param minPressure Unsupported.
+   * @param maxPressure Unsupported.
+   * @see EnableCompressorDigital()
+   */
   void EnableCompressorHybrid(
       units::pounds_per_square_inch_t minPressure,
       units::pounds_per_square_inch_t maxPressure) override;
@@ -40,16 +69,67 @@
 
   units::ampere_t GetCompressorCurrent() const override;
 
+  /**
+   * Return whether the compressor current is currently too high.
+   *
+   * @return True if the compressor current is too high, otherwise false.
+   * @see GetCompressorCurrentTooHighStickyFault()
+   */
   bool GetCompressorCurrentTooHighFault() const;
+
+  /**
+   * Returns whether the compressor current has been too high since sticky
+   * faults were last cleared. This fault is persistent and can be cleared by
+   * ClearAllStickyFaults()
+   *
+   * @return True if the compressor current has been too high since sticky
+   * faults were last cleared.
+   * @see GetCompressorCurrentTooHighFault()
+   */
   bool GetCompressorCurrentTooHighStickyFault() const;
+
+  /**
+   * Returns whether the compressor is currently shorted.
+   *
+   * @return True if the compressor is currently shorted, otherwise false.
+   * @see GetCompressorShortedStickyFault()
+   */
   bool GetCompressorShortedFault() const;
+
+  /**
+   * Returns whether the compressor has been shorted since sticky faults were
+   * last cleared. This fault is persistent and can be cleared by
+   * ClearAllStickyFaults()
+   *
+   * @return True if the compressor has been shorted since sticky faults were
+   * last cleared, otherwise false.
+   * @see GetCompressorShortedFault()
+   */
   bool GetCompressorShortedStickyFault() const;
+
+  /**
+   * Returns whether the compressor is currently disconnected.
+   *
+   * @return True if compressor is currently disconnected, otherwise false.
+   * @see GetCompressorNotConnectedStickyFault()
+   */
   bool GetCompressorNotConnectedFault() const;
+
+  /**
+   * Returns whether the compressor has been disconnected since sticky faults
+   * were last cleared. This fault is persistent and can be cleared by
+   * ClearAllStickyFaults()}
+   *
+   * @return True if the compressor has been disconnected since sticky faults
+   * were last cleared, otherwise false.
+   * @see GetCompressorNotConnectedFault()
+   */
   bool GetCompressorNotConnectedStickyFault() const;
 
   bool GetSolenoidVoltageFault() const;
   bool GetSolenoidVoltageStickyFault() const;
 
+  /** Clears all sticky faults on this device. */
   void ClearAllStickyFaults();
 
   void SetSolenoids(int mask, int values) override;
@@ -74,8 +154,20 @@
 
   void UnreserveCompressor() override;
 
+  /**
+   * Unsupported by the CTRE PCM.
+   *
+   * @param channel Unsupported.
+   * @return 0
+   */
   units::volt_t GetAnalogVoltage(int channel) const override;
 
+  /**
+   * Unsupported by the CTRE PCM.
+   *
+   * @param channel Unsupported.
+   * @return 0
+   */
   units::pounds_per_square_inch_t GetPressure(int channel) const override;
 
   Solenoid MakeSolenoid(int channel) override;
diff --git a/wpilibc/src/main/native/include/frc/Preferences.h b/wpilibc/src/main/native/include/frc/Preferences.h
index b939d9e..0094ad0 100644
--- a/wpilibc/src/main/native/include/frc/Preferences.h
+++ b/wpilibc/src/main/native/include/frc/Preferences.h
@@ -10,8 +10,6 @@
 #include <string_view>
 #include <vector>
 
-#include <wpi/deprecated.h>
-
 namespace frc {
 
 /**
@@ -31,15 +29,6 @@
 class Preferences {
  public:
   /**
-   * Get the one and only {@link Preferences} object.
-   *
-   * @return pointer to the {@link Preferences}
-   * @deprecated Use the static methods
-   */
-  WPI_DEPRECATED("Use static methods")
-  static Preferences* GetInstance();
-
-  /**
    * Returns a vector of all the keys.
    *
    * @return a vector of the keys
@@ -120,18 +109,6 @@
   static void SetString(std::string_view key, std::string_view value);
 
   /**
-   * Puts the given string into the preferences table.
-   *
-   * The value may not have quotation marks, nor may the key have any whitespace
-   * nor an equals sign.
-   *
-   * @param key   the key
-   * @param value the value
-   */
-  WPI_DEPRECATED("Use SetString instead.")
-  static void PutString(std::string_view key, std::string_view value);
-
-  /**
    * Puts the given string into the preferences table if it doesn't
    * already exist.
    */
@@ -148,17 +125,6 @@
   static void SetInt(std::string_view key, int value);
 
   /**
-   * Puts the given int into the preferences table.
-   *
-   * The key may not have any whitespace nor an equals sign.
-   *
-   * @param key   the key
-   * @param value the value
-   */
-  WPI_DEPRECATED("Use SetInt instead.")
-  static void PutInt(std::string_view key, int value);
-
-  /**
    * Puts the given int into the preferences table if it doesn't
    * already exist.
    */
@@ -175,17 +141,6 @@
   static void SetDouble(std::string_view key, double value);
 
   /**
-   * Puts the given double into the preferences table.
-   *
-   * The key may not have any whitespace nor an equals sign.
-   *
-   * @param key   the key
-   * @param value the value
-   */
-  WPI_DEPRECATED("Use SetDouble instead.")
-  static void PutDouble(std::string_view key, double value);
-
-  /**
    * Puts the given double into the preferences table if it doesn't
    * already exist.
    */
@@ -202,17 +157,6 @@
   static void SetFloat(std::string_view key, float value);
 
   /**
-   * Puts the given float into the preferences table.
-   *
-   * The key may not have any whitespace nor an equals sign.
-   *
-   * @param key   the key
-   * @param value the value
-   */
-  WPI_DEPRECATED("Use SetFloat instead.")
-  static void PutFloat(std::string_view key, float value);
-
-  /**
    * Puts the given float into the preferences table if it doesn't
    * already exist.
    */
@@ -229,17 +173,6 @@
   static void SetBoolean(std::string_view key, bool value);
 
   /**
-   * Puts the given boolean into the preferences table.
-   *
-   * The key may not have any whitespace nor an equals sign.
-   *
-   * @param key   the key
-   * @param value the value
-   */
-  WPI_DEPRECATED("Use SetBoolean instead.")
-  static void PutBoolean(std::string_view key, bool value);
-
-  /**
    * Puts the given boolean into the preferences table if it doesn't
    * already exist.
    */
@@ -256,17 +189,6 @@
   static void SetLong(std::string_view key, int64_t value);
 
   /**
-   * Puts the given long (int64_t) into the preferences table.
-   *
-   * The key may not have any whitespace nor an equals sign.
-   *
-   * @param key   the key
-   * @param value the value
-   */
-  WPI_DEPRECATED("Use SetLong instead.")
-  static void PutLong(std::string_view key, int64_t value);
-
-  /**
    * Puts the given long into the preferences table if it doesn't
    * already exist.
    */
diff --git a/wpilibc/src/main/native/include/frc/RobotBase.h b/wpilibc/src/main/native/include/frc/RobotBase.h
index 70c6093..3ff4617 100644
--- a/wpilibc/src/main/native/include/frc/RobotBase.h
+++ b/wpilibc/src/main/native/include/frc/RobotBase.h
@@ -11,7 +11,6 @@
 #include <hal/HALBase.h>
 #include <hal/Main.h>
 #include <wpi/condition_variable.h>
-#include <wpi/deprecated.h>
 #include <wpi/mutex.h>
 
 #include "frc/Errors.h"
@@ -22,6 +21,9 @@
 int RunHALInitialization();
 
 namespace impl {
+#ifndef __FRC_ROBORIO__
+void ResetMotorSafety();
+#endif
 
 template <class Robot>
 void RunRobot(wpi::mutex& m, Robot** robot) {
@@ -35,7 +37,7 @@
   } catch (const frc::RuntimeError& e) {
     e.Report();
     FRC_ReportError(
-        err::Error, "{}",
+        err::Error,
         "The robot program quit unexpectedly."
         " This is usually due to a code error.\n"
         "  The above stacktrace can help determine where the error occurred.\n"
@@ -104,6 +106,9 @@
     impl::RunRobot<Robot>(m, &robot);
   }
 
+#ifndef __FRC_ROBORIO__
+  frc::impl::ResetMotorSafety();
+#endif
   HAL_Shutdown();
 
   return 0;
@@ -156,16 +161,6 @@
    *
    * @return True if the robot is currently operating in Tele-Op mode as
    *         determined by the field controls.
-   * @deprecated Use IsTeleop() instead.
-   */
-  WPI_DEPRECATED("Use IsTeleop() instead")
-  bool IsOperatorControl() const;
-
-  /**
-   * Determine if the robot is currently in Operator Control mode.
-   *
-   * @return True if the robot is currently operating in Tele-Op mode as
-   *         determined by the field controls.
    */
   bool IsTeleop() const;
 
@@ -173,16 +168,6 @@
    * Determine if the robot is current in Operator Control mode and enabled.
    *
    * @return True if the robot is currently operating in Tele-Op mode while
-   *         enabled as determined by the field-controls.
-   * @deprecated Use IsTeleopEnabled() instead.
-   */
-  WPI_DEPRECATED("Use IsTeleopEnabled() instead")
-  bool IsOperatorControlEnabled() const;
-
-  /**
-   * Determine if the robot is current in Operator Control mode and enabled.
-   *
-   * @return True if the robot is currently operating in Tele-Op mode while
    * wnabled as determined by the field-controls.
    */
   bool IsTeleopEnabled() const;
@@ -196,14 +181,6 @@
   bool IsTest() const;
 
   /**
-   * Indicates if new data is available from the driver station.
-   *
-   * @return Has new data arrived over the network since the last time this
-   *         function was called?
-   */
-  bool IsNewDataAvailable() const;
-
-  /**
    * Gets the ID of the main robot thread.
    */
   static std::thread::id GetThreadId();
@@ -237,7 +214,9 @@
    *
    * @return If the robot is running in simulation.
    */
-  static constexpr bool IsSimulation() { return !IsReal(); }
+  static constexpr bool IsSimulation() {
+    return !IsReal();
+  }
 
   /**
    * Constructor for a generic robot program.
diff --git a/wpilibc/src/main/native/include/frc/RobotController.h b/wpilibc/src/main/native/include/frc/RobotController.h
index e9750f0..c020d34 100644
--- a/wpilibc/src/main/native/include/frc/RobotController.h
+++ b/wpilibc/src/main/native/include/frc/RobotController.h
@@ -6,6 +6,8 @@
 
 #include <stdint.h>
 
+#include <string>
+
 #include <units/voltage.h>
 
 namespace frc {
@@ -43,6 +45,24 @@
   static int64_t GetFPGARevision();
 
   /**
+   * Return the serial number of the roboRIO.
+   *
+   * @return The serial number of the roboRIO.
+   */
+  static std::string GetSerialNumber();
+
+  /**
+   * Return the comments from the roboRIO web interface.
+   *
+   * The comments string is cached after the first call to this function on the
+   * RoboRIO - restart the robot code to reload the comments string after
+   * changing it in the web interface.
+   *
+   * @return The comments from the roboRIO web interface.
+   */
+  static std::string GetComments();
+
+  /**
    * Read the microsecond-resolution timer on the FPGA.
    *
    * @return The current time in microseconds according to the FPGA (since FPGA
diff --git a/wpilibc/src/main/native/include/frc/RobotState.h b/wpilibc/src/main/native/include/frc/RobotState.h
index cb97b13..0489b9b 100644
--- a/wpilibc/src/main/native/include/frc/RobotState.h
+++ b/wpilibc/src/main/native/include/frc/RobotState.h
@@ -4,8 +4,6 @@
 
 #pragma once
 
-#include <wpi/deprecated.h>
-
 namespace frc {
 
 class RobotState {
@@ -15,8 +13,6 @@
   static bool IsDisabled();
   static bool IsEnabled();
   static bool IsEStopped();
-  WPI_DEPRECATED("Use IsTeleop() instead")
-  static bool IsOperatorControl();
   static bool IsTeleop();
   static bool IsAutonomous();
   static bool IsTest();
diff --git a/wpilibc/src/main/native/include/frc/SPI.h b/wpilibc/src/main/native/include/frc/SPI.h
index 4f12cbe..0d5b468 100644
--- a/wpilibc/src/main/native/include/frc/SPI.h
+++ b/wpilibc/src/main/native/include/frc/SPI.h
@@ -7,11 +7,11 @@
 #include <stdint.h>
 
 #include <memory>
+#include <span>
 
 #include <hal/SPITypes.h>
 #include <units/time.h>
 #include <wpi/deprecated.h>
-#include <wpi/span.h>
 
 namespace frc {
 
@@ -27,6 +27,12 @@
 class SPI {
  public:
   enum Port { kOnboardCS0 = 0, kOnboardCS1, kOnboardCS2, kOnboardCS3, kMXP };
+  enum Mode {
+    kMode0 = HAL_SPI_kMode0,
+    kMode1 = HAL_SPI_kMode1,
+    kMode2 = HAL_SPI_kMode2,
+    kMode3 = HAL_SPI_kMode3
+  };
 
   /**
    * Constructor
@@ -55,60 +61,73 @@
   /**
    * Configure the order that bits are sent and received on the wire
    * to be most significant bit first.
+   *
+   * @deprecated Does not work, will be removed.
    */
+  WPI_DEPRECATED("Not supported by roboRIO.")
   void SetMSBFirst();
 
   /**
    * Configure the order that bits are sent and received on the wire
    * to be least significant bit first.
+   *
+   * @deprecated Does not work, will be removed.
    */
+  WPI_DEPRECATED("Not supported by roboRIO.")
   void SetLSBFirst();
 
   /**
    * Configure that the data is stable on the leading edge and the data
    * changes on the trailing edge.
+   *
+   * @deprecated Use SetMode() instead.
    */
+  WPI_DEPRECATED("Use SetMode() instead")
   void SetSampleDataOnLeadingEdge();
 
   /**
    * Configure that the data is stable on the trailing edge and the data
    * changes on the leading edge.
+   *
+   * @deprecated Use SetMode() instead.
    */
+  WPI_DEPRECATED("Use SetMode() instead")
   void SetSampleDataOnTrailingEdge();
 
   /**
-   * Configure that the data is stable on the falling edge and the data
-   * changes on the rising edge.
-   *
-   * @deprecated Use SetSampleDataOnTrailingEdge() instead.
-   *
-   */
-  WPI_DEPRECATED("Use SetSampleDataOnTrailingEdge instead.")
-  void SetSampleDataOnFalling();
-
-  /**
-   * Configure that the data is stable on the rising edge and the data
-   * changes on the falling edge.
-   *
-   * @deprecated Use SetSampleDataOnLeadingEdge() instead.
-   *
-   */
-  WPI_DEPRECATED("Use SetSampleDataOnLeadingEdge instead")
-  void SetSampleDataOnRising();
-
-  /**
    * Configure the clock output line to be active low.
    * This is sometimes called clock polarity high or clock idle high.
+   *
+   * @deprecated Use SetMode() instead.
    */
+  WPI_DEPRECATED("Use SetMode() instead")
   void SetClockActiveLow();
 
   /**
    * Configure the clock output line to be active high.
    * This is sometimes called clock polarity low or clock idle low.
+   *
+   * @deprecated Use SetMode() instead.
    */
+  WPI_DEPRECATED("Use SetMode() instead")
   void SetClockActiveHigh();
 
   /**
+   * Sets the mode for the SPI device.
+   *
+   * <p>Mode 0 is Clock idle low, data sampled on rising edge
+   *
+   * <p>Mode 1 is Clock idle low, data sampled on falling edge
+   *
+   * <p>Mode 2 is Clock idle high, data sampled on falling edge
+   *
+   * <p>Mode 3 is Clock idle high, data sampled on rising edge
+   *
+   * @param mode The mode to set.
+   */
+  void SetMode(Mode mode);
+
+  /**
    * Configure the chip select line to be active high.
    */
   void SetChipSelectActiveHigh();
@@ -177,7 +196,7 @@
    * @param dataToSend data to send (maximum 16 bytes)
    * @param zeroSize number of zeros to send after the data
    */
-  void SetAutoTransmitData(wpi::span<const uint8_t> dataToSend, int zeroSize);
+  void SetAutoTransmitData(std::span<const uint8_t> dataToSend, int zeroSize);
 
   /**
    * Start running the automatic SPI transfer engine at a periodic rate.
@@ -190,19 +209,6 @@
   void StartAutoRate(units::second_t period);
 
   /**
-   * Start running the automatic SPI transfer engine at a periodic rate.
-   *
-   * InitAuto() and SetAutoTransmitData() must be called before calling this
-   * function.
-   *
-   * @deprecated use unit-safe StartAutoRate(units::second_t period) instead.
-   *
-   * @param period period between transfers, in seconds (us resolution)
-   */
-  WPI_DEPRECATED("Use StartAutoRate with unit-safety instead")
-  void StartAutoRate(double period);
-
-  /**
    * Start running the automatic SPI transfer engine when a trigger occurs.
    *
    * InitAuto() and SetAutoTransmitData() must be called before calling this
@@ -287,31 +293,6 @@
                        int dataSize, bool isSigned, bool bigEndian);
 
   /**
-   * Initialize the accumulator.
-   *
-   * @deprecated Use unit-safe version instead.
-   *             InitAccumulator(units::second_t period, int cmd, int <!--
-   * -->         xferSize, int validMask, int validValue, int dataShift, <!--
-   * -->         int dataSize, bool isSigned, bool bigEndian)
-   *
-   * @param period     Time between reads
-   * @param cmd        SPI command to send to request data
-   * @param xferSize   SPI transfer size, in bytes
-   * @param validMask  Mask to apply to received data for validity checking
-   * @param validValue After valid_mask is applied, required matching value for
-   *                   validity checking
-   * @param dataShift  Bit shift to apply to received data to get actual data
-   *                   value
-   * @param dataSize   Size (in bits) of data field
-   * @param isSigned   Is data field signed?
-   * @param bigEndian  Is device big endian?
-   */
-  WPI_DEPRECATED("Use InitAccumulator with unit-safety instead")
-  void InitAccumulator(double period, int cmd, int xferSize, int validMask,
-                       int validValue, int dataShift, int dataSize,
-                       bool isSigned, bool bigEndian);
-
-  /**
    * Frees the accumulator.
    */
   void FreeAccumulator();
@@ -404,9 +385,7 @@
 
  protected:
   hal::SPIPort m_port;
-  bool m_msbFirst = false;          // Default little-endian
-  bool m_sampleOnTrailing = false;  // Default data updated on falling edge
-  bool m_clockIdleHigh = false;     // Default clock active high
+  HAL_SPIMode m_mode = HAL_SPIMode::HAL_SPI_kMode0;
 
  private:
   void Init();
diff --git a/wpilibc/src/main/native/include/frc/SpeedController.h b/wpilibc/src/main/native/include/frc/SpeedController.h
deleted file mode 100644
index 8473015..0000000
--- a/wpilibc/src/main/native/include/frc/SpeedController.h
+++ /dev/null
@@ -1,74 +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 <units/voltage.h>
-#include <wpi/deprecated.h>
-
-namespace frc {
-
-/**
- * Interface for speed controlling devices.
- *
- * @deprecated Use MotorController.
- */
-class WPI_DEPRECATED("use MotorController") SpeedController {
- public:
-  virtual ~SpeedController() = default;
-
-  /**
-   * Common interface for setting the speed of a speed controller.
-   *
-   * @param speed The speed to set.  Value should be between -1.0 and 1.0.
-   */
-  virtual void Set(double speed) = 0;
-
-  /**
-   * Sets the voltage output of the SpeedController.  Compensates for
-   * the current bus voltage to ensure that the desired voltage is output even
-   * if the battery voltage is below 12V - highly useful when the voltage
-   * outputs are "meaningful" (e.g. they come from a feedforward calculation).
-   *
-   * <p>NOTE: This function *must* be called regularly in order for voltage
-   * compensation to work properly - unlike the ordinary set function, it is not
-   * "set it and forget it."
-   *
-   * @param output The voltage to output.
-   */
-  virtual void SetVoltage(units::volt_t output);
-
-  /**
-   * Common interface for getting the current set speed of a speed controller.
-   *
-   * @return The current set speed.  Value is between -1.0 and 1.0.
-   */
-  virtual double Get() const = 0;
-
-  /**
-   * Common interface for inverting direction of a speed controller.
-   *
-   * @param isInverted The state of inversion, true is inverted.
-   */
-  virtual void SetInverted(bool isInverted) = 0;
-
-  /**
-   * Common interface for returning the inversion state of a speed controller.
-   *
-   * @return isInverted The state of inversion, true is inverted.
-   */
-  virtual bool GetInverted() const = 0;
-
-  /**
-   * Common interface for disabling a motor.
-   */
-  virtual void Disable() = 0;
-
-  /**
-   * Common interface to stop the motor until Set is called again.
-   */
-  virtual void StopMotor() = 0;
-};
-
-}  // namespace frc
diff --git a/wpilibc/src/main/native/include/frc/SpeedControllerGroup.h b/wpilibc/src/main/native/include/frc/SpeedControllerGroup.h
deleted file mode 100644
index 5a097b5..0000000
--- a/wpilibc/src/main/native/include/frc/SpeedControllerGroup.h
+++ /dev/null
@@ -1,55 +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 <vector>
-
-#include <wpi/deprecated.h>
-#include <wpi/sendable/Sendable.h>
-#include <wpi/sendable/SendableHelper.h>
-
-#include "frc/motorcontrol/MotorController.h"
-
-namespace frc {
-
-/**
- * Allows multiple SpeedController objects to be linked together.
- *
- * @deprecated Use MotorControllerGroup.
- */
-class WPI_DEPRECATED("use MotorControllerGroup") SpeedControllerGroup
-    : public wpi::Sendable,
-      public MotorController,
-      public wpi::SendableHelper<SpeedControllerGroup> {
- public:
-  template <class... SpeedControllers>
-  explicit SpeedControllerGroup(SpeedController& speedController,
-                                SpeedControllers&... speedControllers);
-  explicit SpeedControllerGroup(
-      std::vector<std::reference_wrapper<SpeedController>>&& speedControllers);
-
-  SpeedControllerGroup(SpeedControllerGroup&&) = default;
-  SpeedControllerGroup& operator=(SpeedControllerGroup&&) = default;
-
-  void Set(double speed) override;
-  double Get() const override;
-  void SetInverted(bool isInverted) override;
-  bool GetInverted() const override;
-  void Disable() override;
-  void StopMotor() override;
-
-  void InitSendable(wpi::SendableBuilder& builder) override;
-
- private:
-  bool m_isInverted = false;
-  std::vector<std::reference_wrapper<SpeedController>> m_speedControllers;
-
-  void Initialize();
-};
-
-}  // namespace frc
-
-#include "frc/SpeedControllerGroup.inc"
diff --git a/wpilibc/src/main/native/include/frc/SpeedControllerGroup.inc b/wpilibc/src/main/native/include/frc/SpeedControllerGroup.inc
deleted file mode 100644
index d5f17b4..0000000
--- a/wpilibc/src/main/native/include/frc/SpeedControllerGroup.inc
+++ /dev/null
@@ -1,22 +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 <vector>
-
-#include "frc/SpeedControllerGroup.h"
-
-namespace frc {
-
-template <class... SpeedControllers>
-SpeedControllerGroup::SpeedControllerGroup(
-    SpeedController& speedController, SpeedControllers&... speedControllers)
-    : m_speedControllers(std::vector<std::reference_wrapper<SpeedController>>{
-          speedController, speedControllers...}) {
-  Initialize();
-}
-
-}  // namespace frc
diff --git a/wpilibc/src/main/native/include/frc/SynchronousInterrupt.h b/wpilibc/src/main/native/include/frc/SynchronousInterrupt.h
index 03cc4f8..fbe0fca 100644
--- a/wpilibc/src/main/native/include/frc/SynchronousInterrupt.h
+++ b/wpilibc/src/main/native/include/frc/SynchronousInterrupt.h
@@ -21,6 +21,9 @@
  */
 class SynchronousInterrupt {
  public:
+  /**
+   * Event trigger combinations for a synchronous interrupt.
+   */
   enum WaitResult {
     kTimeout = 0x0,
     kRisingEdge = 0x1,
@@ -62,7 +65,7 @@
    *
    * @param timeout The timeout to wait for. 0s or less will return immediately.
    * @param ignorePrevious True to ignore any previous interrupts, false to
-   * return interrupt value if an interrupt has occured since last call.
+   * return interrupt value if an interrupt has occurred since last call.
    * @return The edge(s) that were triggered, or timeout.
    */
   WaitResult WaitForInterrupt(units::second_t timeout,
diff --git a/wpilibc/src/main/native/include/frc/TimedRobot.h b/wpilibc/src/main/native/include/frc/TimedRobot.h
index cc64c03..f32e748 100644
--- a/wpilibc/src/main/native/include/frc/TimedRobot.h
+++ b/wpilibc/src/main/native/include/frc/TimedRobot.h
@@ -11,7 +11,6 @@
 #include <hal/Types.h>
 #include <units/math.h>
 #include <units/time.h>
-#include <wpi/deprecated.h>
 #include <wpi/priority_queue.h>
 
 #include "frc/IterativeRobotBase.h"
@@ -45,17 +44,6 @@
   /**
    * Constructor for TimedRobot.
    *
-   * @deprecated use unit safe constructor instead.
-   * TimedRobot(units::second_t period = kDefaultPeriod)
-   *
-   * @param period Period in seconds.
-   */
-  WPI_DEPRECATED("Use constructor with unit-safety instead.")
-  explicit TimedRobot(double period);
-
-  /**
-   * Constructor for TimedRobot.
-   *
    * @param period Period.
    */
   explicit TimedRobot(units::second_t period = kDefaultPeriod);
diff --git a/wpilibc/src/main/native/include/frc/Timer.h b/wpilibc/src/main/native/include/frc/Timer.h
index 0aed658..14674ee 100644
--- a/wpilibc/src/main/native/include/frc/Timer.h
+++ b/wpilibc/src/main/native/include/frc/Timer.h
@@ -5,7 +5,6 @@
 #pragma once
 
 #include <units/time.h>
-#include <wpi/deprecated.h>
 
 namespace frc {
 
@@ -101,18 +100,6 @@
    *
    * @param period The period to check for.
    * @return       True if the period has passed.
-   * @deprecated Use AdvanceIfElapsed() instead.
-   */
-  WPI_DEPRECATED("Use AdvanceIfElapsed() instead.")
-  bool HasPeriodPassed(units::second_t period);
-
-  /**
-   * Check if the period specified has passed and if it has, advance the start
-   * time by that period. This is useful to decide if it's time to do periodic
-   * work without drifting later by the time it took to get around to checking.
-   *
-   * @param period The period to check for.
-   * @return       True if the period has passed.
    */
   bool AdvanceIfElapsed(units::second_t period);
 
diff --git a/wpilibc/src/main/native/include/frc/Ultrasonic.h b/wpilibc/src/main/native/include/frc/Ultrasonic.h
index 253192f..137a5b7 100644
--- a/wpilibc/src/main/native/include/frc/Ultrasonic.h
+++ b/wpilibc/src/main/native/include/frc/Ultrasonic.h
@@ -164,11 +164,8 @@
    */
   static void UltrasonicChecker();
 
-  // Time (sec) for the ping trigger pulse.
-  static constexpr double kPingTime = 10 * 1e-6;
-
-  // Priority that the ultrasonic round robin task runs.
-  static constexpr int kPriority = 64;
+  // Time (usec) for the ping trigger pulse.
+  static constexpr auto kPingTime = 10_us;
 
   // Max time (ms) between readings.
   static constexpr auto kMaxUltrasonicTime = 0.1_s;
diff --git a/wpilibc/src/main/native/include/frc/XboxController.h b/wpilibc/src/main/native/include/frc/XboxController.h
index cbdc7d7..370e46e 100644
--- a/wpilibc/src/main/native/include/frc/XboxController.h
+++ b/wpilibc/src/main/native/include/frc/XboxController.h
@@ -97,6 +97,24 @@
   bool GetRightBumperReleased();
 
   /**
+   * Constructs an event instance around the left bumper's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the left bumper's digital signal
+   * attached to the given loop.
+   */
+  BooleanEvent LeftBumper(EventLoop* loop) const;
+
+  /**
+   * Constructs an event instance around the right bumper's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the right bumper's digital signal
+   * attached to the given loop.
+   */
+  BooleanEvent RightBumper(EventLoop* loop) const;
+
+  /**
    * Read the value of the left stick button (LSB) on the controller.
    */
   bool GetLeftStickButton() const;
@@ -127,6 +145,24 @@
   bool GetRightStickButtonReleased();
 
   /**
+   * Constructs an event instance around the left stick's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the left stick's digital signal
+   * attached to the given loop.
+   */
+  BooleanEvent LeftStick(EventLoop* loop) const;
+
+  /**
+   * Constructs an event instance around the right stick's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the right stick's digital signal
+   * attached to the given loop.
+   */
+  BooleanEvent RightStick(EventLoop* loop) const;
+
+  /**
    * Read the value of the A button on the controller.
    *
    * @return The state of the button.
@@ -148,6 +184,15 @@
   bool GetAButtonReleased();
 
   /**
+   * Constructs an event instance around the A button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the A button's digital signal
+   * attached to the given loop.
+   */
+  BooleanEvent A(EventLoop* loop) const;
+
+  /**
    * Read the value of the B button on the controller.
    *
    * @return The state of the button.
@@ -169,6 +214,15 @@
   bool GetBButtonReleased();
 
   /**
+   * Constructs an event instance around the B button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the B button's digital signal
+   * attached to the given loop.
+   */
+  BooleanEvent B(EventLoop* loop) const;
+
+  /**
    * Read the value of the X button on the controller.
    *
    * @return The state of the button.
@@ -190,6 +244,15 @@
   bool GetXButtonReleased();
 
   /**
+   * Constructs an event instance around the X button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the X button's digital signal
+   * attached to the given loop.
+   */
+  BooleanEvent X(EventLoop* loop) const;
+
+  /**
    * Read the value of the Y button on the controller.
    *
    * @return The state of the button.
@@ -211,6 +274,15 @@
   bool GetYButtonReleased();
 
   /**
+   * Constructs an event instance around the Y button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the Y button's digital signal
+   * attached to the given loop.
+   */
+  BooleanEvent Y(EventLoop* loop) const;
+
+  /**
    * Whether the Y button was released since the last check.
    *
    * @return Whether the button was released since the last check.
@@ -232,6 +304,15 @@
   bool GetBackButtonReleased();
 
   /**
+   * Constructs an event instance around the back button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the back button's digital signal
+   * attached to the given loop.
+   */
+  BooleanEvent Back(EventLoop* loop) const;
+
+  /**
    * Read the value of the start button on the controller.
    *
    * @return The state of the button.
@@ -252,6 +333,59 @@
    */
   bool GetStartButtonReleased();
 
+  /**
+   * Constructs an event instance around the start button's digital signal.
+   *
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance representing the start button's digital signal
+   * attached to the given loop.
+   */
+  BooleanEvent Start(EventLoop* loop) const;
+
+  /**
+   * Constructs an event 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 threshold the minimum axis value for the returned event 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 event to.
+   * @return an event instance that is true when the left trigger's axis exceeds
+   * the provided threshold, attached to the given event loop
+   */
+  BooleanEvent LeftTrigger(double threshold, EventLoop* loop) const;
+
+  /**
+   * Constructs an event instance around the axis value of the left trigger.
+   * The returned trigger will be true when the axis value is greater than 0.5.
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance that is true when the right trigger's axis
+   * exceeds 0.5, attached to the given event loop
+   */
+  BooleanEvent LeftTrigger(EventLoop* loop) const;
+
+  /**
+   * Constructs an event instance around the axis value of the right trigger.
+   * The returned trigger will be true when the axis value is greater than
+   * {@code threshold}.
+   * @param threshold the minimum axis value for the returned event 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 event to.
+   * @return an event instance that is true when the right trigger's axis
+   * exceeds the provided threshold, attached to the given event loop
+   */
+  BooleanEvent RightTrigger(double threshold, EventLoop* loop) const;
+
+  /**
+   * Constructs an event instance around the axis value of the right trigger.
+   * The returned trigger will be true when the axis value is greater than 0.5.
+   * @param loop the event loop instance to attach the event to.
+   * @return an event instance that is true when the right trigger's axis
+   * exceeds 0.5, attached to the given event loop
+   */
+  BooleanEvent RightTrigger(EventLoop* loop) const;
+
   struct Button {
     static constexpr int kLeftBumper = 5;
     static constexpr int kRightBumper = 6;
diff --git a/wpilibc/src/main/native/include/frc/drive/DifferentialDrive.h b/wpilibc/src/main/native/include/frc/drive/DifferentialDrive.h
index 7009eb9..2e701a3 100644
--- a/wpilibc/src/main/native/include/frc/drive/DifferentialDrive.h
+++ b/wpilibc/src/main/native/include/frc/drive/DifferentialDrive.h
@@ -13,18 +13,7 @@
 
 namespace frc {
 
-#if defined(_MSC_VER)
-#pragma warning(push)
-#pragma warning(disable : 4996)  // was declared deprecated
-#elif defined(__clang__)
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-#elif defined(__GNUC__)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
-#endif
-
-class SpeedController;
+class MotorController;
 
 /**
  * A class for driving differential drive/skid-steer drive platforms such as
@@ -81,21 +70,22 @@
  * |       |
  * </pre>
  *
- * Each Drive() function provides different inverse kinematic relations for a
- * differential drive robot. Motor outputs for the right side are negated, so
- * motor direction inversion by the user is usually unnecessary.
+ * Each drive function provides different inverse kinematic relations for a
+ * differential drive robot.
  *
- * This library uses the NED axes convention (North-East-Down as external
- * reference in the world frame):
- * http://www.nuclearprojects.com/ins/images/axis_big.png.
- *
- * The positive X axis points ahead, the positive Y axis points to the right,
- * and the positive Z axis points down. Rotations follow the right-hand rule, so
- * clockwise rotation around the Z axis is positive.
+ * This library uses the NWU axes convention (North-West-Up as external
+ * reference in the world frame). The positive X axis points ahead, the positive
+ * Y axis points to the left, and the positive Z axis points up. Rotations
+ * follow the right-hand rule, so counterclockwise rotation around the Z axis is
+ * positive.
  *
  * Inputs smaller then 0.02 will be set to 0, and larger values will be scaled
  * so that the full range is still used. This deadband value can be changed
  * with SetDeadband().
+ *
+ * MotorSafety is enabled by default. The tankDrive, arcadeDrive,
+ * or curvatureDrive methods should be called periodically to avoid Motor
+ * Safety timeouts.
  */
 class DifferentialDrive : public RobotDriveBase,
                           public wpi::Sendable,
@@ -117,7 +107,7 @@
    * To pass multiple motors per side, use a MotorControllerGroup. If a motor
    * needs to be inverted, do so before passing it in.
    */
-  DifferentialDrive(SpeedController& leftMotor, SpeedController& rightMotor);
+  DifferentialDrive(MotorController& leftMotor, MotorController& rightMotor);
 
   ~DifferentialDrive() override = default;
 
@@ -133,7 +123,7 @@
    * @param xSpeed        The speed at which the robot should drive along the X
    *                      axis [-1.0..1.0]. Forward is positive.
    * @param zRotation     The rotation rate of the robot around the Z axis
-   *                      [-1.0..1.0]. Clockwise is positive.
+   *                      [-1.0..1.0]. Counterclockwise is positive.
    * @param squareInputs If set, decreases the input sensitivity at low speeds.
    */
   void ArcadeDrive(double xSpeed, double zRotation, bool squareInputs = true);
@@ -147,8 +137,8 @@
    *
    * @param xSpeed           The robot's speed along the X axis [-1.0..1.0].
    *                         Forward is positive.
-   * @param zRotation        The normalized curvature [-1.0..1.0]. Clockwise is
-   *                         positive.
+   * @param zRotation        The normalized curvature [-1.0..1.0].
+   *                         Counterclockwise is positive.
    * @param allowTurnInPlace If set, overrides constant-curvature turning for
    *                         turn-in-place maneuvers. zRotation will control
    *                         turning rate instead of curvature.
@@ -220,16 +210,8 @@
   void InitSendable(wpi::SendableBuilder& builder) override;
 
  private:
-  SpeedController* m_leftMotor;
-  SpeedController* m_rightMotor;
+  MotorController* m_leftMotor;
+  MotorController* m_rightMotor;
 };
 
-#if defined(_MSC_VER)
-#pragma warning(pop)
-#elif defined(__clang__)
-#pragma clang diagnostic pop
-#elif defined(__GNUC__)
-#pragma GCC diagnostic pop
-#endif
-
 }  // namespace frc
diff --git a/wpilibc/src/main/native/include/frc/drive/KilloughDrive.h b/wpilibc/src/main/native/include/frc/drive/KilloughDrive.h
deleted file mode 100644
index d4c5c5c..0000000
--- a/wpilibc/src/main/native/include/frc/drive/KilloughDrive.h
+++ /dev/null
@@ -1,192 +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 <string>
-
-#include <wpi/sendable/Sendable.h>
-#include <wpi/sendable/SendableHelper.h>
-
-#include "frc/drive/RobotDriveBase.h"
-#include "frc/drive/Vector2d.h"
-
-namespace frc {
-
-#if defined(_MSC_VER)
-#pragma warning(push)
-#pragma warning(disable : 4996)  // was declared deprecated
-#elif defined(__clang__)
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-#elif defined(__GNUC__)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
-#endif
-
-class SpeedController;
-
-/**
- * A class for driving Killough drive platforms.
- *
- * Killough drives are triangular with one omni wheel on each corner.
- *
- * Drive base diagram:
- * <pre>
- *  /_____\
- * / \   / \
- *    \ /
- *    ---
- * </pre>
- *
- * Each Drive() function provides different inverse kinematic relations for a
- * Killough drive. The default wheel vectors are parallel to their respective
- * opposite sides, but can be overridden. See the constructor for more
- * information.
- *
- * This library uses the NED axes convention (North-East-Down as external
- * reference in the world frame):
- * http://www.nuclearprojects.com/ins/images/axis_big.png.
- *
- * The positive X axis points ahead, the positive Y axis points right, and the
- * and the positive Z axis points down. Rotations follow the right-hand rule, so
- * clockwise rotation around the Z axis is positive.
- */
-class KilloughDrive : public RobotDriveBase,
-                      public wpi::Sendable,
-                      public wpi::SendableHelper<KilloughDrive> {
- public:
-  static constexpr double kDefaultLeftMotorAngle = 60.0;
-  static constexpr double kDefaultRightMotorAngle = 120.0;
-  static constexpr double kDefaultBackMotorAngle = 270.0;
-
-  /**
-   * Wheel speeds for a Killough drive.
-   *
-   * Uses normalized voltage [-1.0..1.0].
-   */
-  struct WheelSpeeds {
-    double left = 0.0;
-    double right = 0.0;
-    double back = 0.0;
-  };
-
-  /**
-   * Construct a Killough drive with the given motors and default motor angles.
-   *
-   * The default motor angles make the wheels on each corner parallel to their
-   * respective opposite sides.
-   *
-   * If a motor needs to be inverted, do so before passing it in.
-   *
-   * @param leftMotor  The motor on the left corner.
-   * @param rightMotor The motor on the right corner.
-   * @param backMotor  The motor on the back corner.
-   */
-  KilloughDrive(SpeedController& leftMotor, SpeedController& rightMotor,
-                SpeedController& backMotor);
-
-  /**
-   * Construct a Killough drive with the given motors.
-   *
-   * Angles are measured in degrees clockwise from the positive X axis.
-   *
-   * @param leftMotor       The motor on the left corner.
-   * @param rightMotor      The motor on the right corner.
-   * @param backMotor       The motor on the back corner.
-   * @param leftMotorAngle  The angle of the left wheel's forward direction of
-   *                        travel.
-   * @param rightMotorAngle The angle of the right wheel's forward direction of
-   *                        travel.
-   * @param backMotorAngle  The angle of the back wheel's forward direction of
-   *                        travel.
-   */
-  KilloughDrive(SpeedController& leftMotor, SpeedController& rightMotor,
-                SpeedController& backMotor, double leftMotorAngle,
-                double rightMotorAngle, double backMotorAngle);
-
-  ~KilloughDrive() override = default;
-
-  KilloughDrive(KilloughDrive&&) = default;
-  KilloughDrive& operator=(KilloughDrive&&) = default;
-
-  /**
-   * Drive method for Killough platform.
-   *
-   * Angles are measured clockwise from the positive X axis. The robot's speed
-   * is independent from its angle or rotation rate.
-   *
-   * @param ySpeed    The robot's speed along the Y axis [-1.0..1.0]. Right is
-   *                  positive.
-   * @param xSpeed    The robot's speed along the X axis [-1.0..1.0]. Forward is
-   *                  positive.
-   * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0].
-   *                  Clockwise is positive.
-   * @param gyroAngle The current angle reading from the gyro in degrees around
-   *                  the Z axis. Use this to implement field-oriented controls.
-   */
-  void DriveCartesian(double ySpeed, double xSpeed, double zRotation,
-                      double gyroAngle = 0.0);
-
-  /**
-   * Drive method for Killough platform.
-   *
-   * Angles are measured clockwise from the positive X axis. The robot's speed
-   * is independent from its angle or rotation rate.
-   *
-   * @param magnitude The robot's speed at a given angle [-1.0..1.0]. Forward is
-   *                  positive.
-   * @param angle     The angle around the Z axis at which the robot drives in
-   *                  degrees [-180..180].
-   * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0].
-   *                  Clockwise is positive.
-   */
-  void DrivePolar(double magnitude, double angle, double zRotation);
-
-  /**
-   * Cartesian inverse kinematics for Killough platform.
-   *
-   * Angles are measured clockwise from the positive X axis. The robot's speed
-   * is independent from its angle or rotation rate.
-   *
-   * @param ySpeed    The robot's speed along the Y axis [-1.0..1.0]. Right is
-   *                  positive.
-   * @param xSpeed    The robot's speed along the X axis [-1.0..1.0]. Forward is
-   *                  positive.
-   * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0].
-   *                  Clockwise is positive.
-   * @param gyroAngle The current angle reading from the gyro in degrees around
-   *                  the Z axis. Use this to implement field-oriented controls.
-   * @return Wheel speeds [-1.0..1.0].
-   */
-  WheelSpeeds DriveCartesianIK(double ySpeed, double xSpeed, double zRotation,
-                               double gyroAngle = 0.0);
-
-  void StopMotor() override;
-  std::string GetDescription() const override;
-
-  void InitSendable(wpi::SendableBuilder& builder) override;
-
- private:
-  SpeedController* m_leftMotor;
-  SpeedController* m_rightMotor;
-  SpeedController* m_backMotor;
-
-  Vector2d m_leftVec;
-  Vector2d m_rightVec;
-  Vector2d m_backVec;
-
-  bool reported = false;
-};
-
-#if defined(_MSC_VER)
-#pragma warning(pop)
-#elif defined(__clang__)
-#pragma clang diagnostic pop
-#elif defined(__GNUC__)
-#pragma GCC diagnostic pop
-#endif
-
-}  // namespace frc
diff --git a/wpilibc/src/main/native/include/frc/drive/MecanumDrive.h b/wpilibc/src/main/native/include/frc/drive/MecanumDrive.h
index 757ae67..f4c28f4 100644
--- a/wpilibc/src/main/native/include/frc/drive/MecanumDrive.h
+++ b/wpilibc/src/main/native/include/frc/drive/MecanumDrive.h
@@ -7,25 +7,16 @@
 #include <memory>
 #include <string>
 
+#include <units/angle.h>
 #include <wpi/sendable/Sendable.h>
 #include <wpi/sendable/SendableHelper.h>
 
 #include "frc/drive/RobotDriveBase.h"
+#include "frc/geometry/Rotation2d.h"
 
 namespace frc {
 
-#if defined(_MSC_VER)
-#pragma warning(push)
-#pragma warning(disable : 4996)  // was declared deprecated
-#elif defined(__clang__)
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-#elif defined(__GNUC__)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
-#endif
-
-class SpeedController;
+class MotorController;
 
 /**
  * A class for driving Mecanum drive platforms.
@@ -46,9 +37,11 @@
  * Each Drive() function provides different inverse kinematic relations for a
  * Mecanum drive robot.
  *
- * The positive Y axis points ahead, the positive X axis points to the right,
- * and the positive Z axis points down. Rotations follow the right-hand rule, so
- * clockwise rotation around the Z axis is positive.
+ * This library uses the NWU axes convention (North-West-Up as external
+ * reference in the world frame). The positive X axis points ahead, the positive
+ * Y axis points to the left, and the positive Z axis points up. Rotations
+ * follow the right-hand rule, so counterclockwise rotation around the Z axis is
+ * positive.
  *
  * Note: the axis conventions used in this class differ from DifferentialDrive.
  * This may change in a future year's WPILib release.
@@ -57,15 +50,8 @@
  * so that the full range is still used. This deadband value can be changed
  * with SetDeadband().
  *
- * RobotDrive porting guide:
- * <br>DriveCartesian(double, double, double, double) is equivalent to
- * RobotDrive's MecanumDrive_Cartesian(double, double, double, double)
- * if a deadband of 0 is used, and the ySpeed and gyroAngle values are inverted
- * compared to RobotDrive (eg DriveCartesian(xSpeed, -ySpeed, zRotation,
- * -gyroAngle).
- * <br>DrivePolar(double, double, double) is equivalent to
- * RobotDrive's MecanumDrive_Polar(double, double, double) if a
- * deadband of 0 is used.
+ * MotorSafety is enabled by default. The DriveCartesian or DrivePolar
+ * methods should be called periodically to avoid Motor Safety timeouts.
  */
 class MecanumDrive : public RobotDriveBase,
                      public wpi::Sendable,
@@ -88,9 +74,9 @@
    *
    * If a motor needs to be inverted, do so before passing it in.
    */
-  MecanumDrive(SpeedController& frontLeftMotor, SpeedController& rearLeftMotor,
-               SpeedController& frontRightMotor,
-               SpeedController& rearRightMotor);
+  MecanumDrive(MotorController& frontLeftMotor, MotorController& rearLeftMotor,
+               MotorController& frontRightMotor,
+               MotorController& rearRightMotor);
 
   ~MecanumDrive() override = default;
 
@@ -100,54 +86,54 @@
   /**
    * Drive method for Mecanum platform.
    *
-   * Angles are measured clockwise from the positive X axis. The robot's speed
-   * is independent from its angle or rotation rate.
+   * Angles are measured counterclockwise from the positive X axis. The robot's
+   * speed is independent from its angle or rotation rate.
    *
-   * @param ySpeed    The robot's speed along the Y axis [-1.0..1.0]. Forward is
+   * @param xSpeed    The robot's speed along the X axis [-1.0..1.0]. Forward is
    *                  positive.
-   * @param xSpeed    The robot's speed along the X axis [-1.0..1.0]. Right is
+   * @param ySpeed    The robot's speed along the Y axis [-1.0..1.0]. Left is
    *                  positive.
    * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0].
-   *                  Clockwise is positive.
-   * @param gyroAngle The current angle reading from the gyro in degrees around
-   *                  the Z axis. Use this to implement field-oriented controls.
+   *                  Counterclockwise is positive.
+   * @param gyroAngle The gyro heading around the Z axis. Use this to implement
+   *                  field-oriented controls.
    */
-  void DriveCartesian(double ySpeed, double xSpeed, double zRotation,
-                      double gyroAngle = 0.0);
+  void DriveCartesian(double xSpeed, double ySpeed, double zRotation,
+                      Rotation2d gyroAngle = 0_rad);
 
   /**
    * Drive method for Mecanum platform.
    *
-   * Angles are measured clockwise from the positive X axis. The robot's speed
-   * is independent from its angle or rotation rate.
+   * Angles are measured counterclockwise from the positive X axis. The robot's
+   * speed is independent from its angle or rotation rate.
    *
    * @param magnitude The robot's speed at a given angle [-1.0..1.0]. Forward is
    *                  positive.
-   * @param angle     The angle around the Z axis at which the robot drives in
-   *                  degrees [-180..180].
+   * @param angle     The angle around the Z axis at which the robot drives.
    * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0].
-   *                  Clockwise is positive.
+   *                  Counterclockwise is positive.
    */
-  void DrivePolar(double magnitude, double angle, double zRotation);
+  void DrivePolar(double magnitude, Rotation2d angle, double zRotation);
 
   /**
    * Cartesian inverse kinematics for Mecanum platform.
    *
-   * Angles are measured clockwise from the positive X axis. The robot's speed
-   * is independent from its angle or rotation rate.
+   * Angles are measured counterclockwise from the positive X axis. The robot's
+   * speed is independent from its angle or rotation rate.
    *
-   * @param ySpeed    The robot's speed along the Y axis [-1.0..1.0]. Forward is
+   * @param xSpeed    The robot's speed along the X axis [-1.0..1.0]. Forward is
    *                  positive.
-   * @param xSpeed    The robot's speed along the X axis [-1.0..1.0]. Right is
+   * @param ySpeed    The robot's speed along the Y axis [-1.0..1.0]. Left is
    *                  positive.
    * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0].
-   *                  Clockwise is positive.
-   * @param gyroAngle The current angle reading from the gyro in degrees around
-   *                  the Z axis. Use this to implement field-oriented controls.
+   *                  Counterclockwise is positive.
+   * @param gyroAngle The gyro heading around the Z axis. Use this to implement
+   *                  field-oriented controls.
    * @return Wheel speeds [-1.0..1.0].
    */
-  static WheelSpeeds DriveCartesianIK(double ySpeed, double xSpeed,
-                                      double zRotation, double gyroAngle = 0.0);
+  static WheelSpeeds DriveCartesianIK(double xSpeed, double ySpeed,
+                                      double zRotation,
+                                      Rotation2d gyroAngle = 0_rad);
 
   void StopMotor() override;
   std::string GetDescription() const override;
@@ -155,20 +141,12 @@
   void InitSendable(wpi::SendableBuilder& builder) override;
 
  private:
-  SpeedController* m_frontLeftMotor;
-  SpeedController* m_rearLeftMotor;
-  SpeedController* m_frontRightMotor;
-  SpeedController* m_rearRightMotor;
+  MotorController* m_frontLeftMotor;
+  MotorController* m_rearLeftMotor;
+  MotorController* m_frontRightMotor;
+  MotorController* m_rearRightMotor;
 
   bool reported = false;
 };
 
-#if defined(_MSC_VER)
-#pragma warning(pop)
-#elif defined(__clang__)
-#pragma clang diagnostic pop
-#elif defined(__GNUC__)
-#pragma GCC diagnostic pop
-#endif
-
 }  // namespace frc
diff --git a/wpilibc/src/main/native/include/frc/drive/RobotDriveBase.h b/wpilibc/src/main/native/include/frc/drive/RobotDriveBase.h
index 8ce5636..b3fb56b 100644
--- a/wpilibc/src/main/native/include/frc/drive/RobotDriveBase.h
+++ b/wpilibc/src/main/native/include/frc/drive/RobotDriveBase.h
@@ -5,17 +5,17 @@
 #pragma once
 
 #include <memory>
+#include <span>
 #include <string>
 
-#include <wpi/deprecated.h>
-#include <wpi/span.h>
-
 #include "frc/MotorSafety.h"
 
 namespace frc {
 
 /**
  * Common base class for drive platforms.
+ *
+ * MotorSafety is enabled by default.
  */
 class RobotDriveBase : public MotorSafety {
  public:
@@ -71,21 +71,10 @@
 
  protected:
   /**
-   * Returns 0.0 if the given value is within the specified range around zero.
-   * The remaining range between the deadband and 1.0 is scaled from 0.0 to 1.0.
-   *
-   * @param value    value to clip
-   * @param deadband range around zero
-   * @deprecated Use ApplyDeadband() in frc/MathUtil.h.
-   */
-  WPI_DEPRECATED("Use ApplyDeadband() in frc/MathUtil.h")
-  static double ApplyDeadband(double value, double deadband);
-
-  /**
    * Renormalize all wheel speeds if the magnitude of any wheel is greater than
    * 1.0.
    */
-  static void Desaturate(wpi::span<double> wheelSpeeds);
+  static void Desaturate(std::span<double> wheelSpeeds);
 
   double m_deadband = 0.02;
   double m_maxOutput = 1.0;
diff --git a/wpilibc/src/main/native/include/frc/drive/Vector2d.h b/wpilibc/src/main/native/include/frc/drive/Vector2d.h
deleted file mode 100644
index 92a3de6..0000000
--- a/wpilibc/src/main/native/include/frc/drive/Vector2d.h
+++ /dev/null
@@ -1,46 +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
-
-namespace frc {
-
-/**
- * This is a 2D vector struct that supports basic vector operations.
- */
-struct Vector2d {
-  Vector2d() = default;
-  Vector2d(double x, double y);
-
-  /**
-   * Rotate a vector in Cartesian space.
-   *
-   * @param angle angle in degrees by which to rotate vector counter-clockwise.
-   */
-  void Rotate(double angle);
-
-  /**
-   * Returns dot product of this vector with argument.
-   *
-   * @param vec Vector with which to perform dot product.
-   */
-  double Dot(const Vector2d& vec) const;
-
-  /**
-   * Returns magnitude of vector.
-   */
-  double Magnitude() const;
-
-  /**
-   * Returns scalar projection of this vector onto argument.
-   *
-   * @param vec Vector onto which to project this vector.
-   */
-  double ScalarProject(const Vector2d& vec) const;
-
-  double x = 0.0;
-  double y = 0.0;
-};
-
-}  // namespace frc
diff --git a/wpilibc/src/main/native/include/frc/event/BooleanEvent.h b/wpilibc/src/main/native/include/frc/event/BooleanEvent.h
new file mode 100644
index 0000000..745a53c
--- /dev/null
+++ b/wpilibc/src/main/native/include/frc/event/BooleanEvent.h
@@ -0,0 +1,135 @@
+// 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/filter/Debouncer.h>
+
+#include <functional>
+#include <memory>
+
+#include <units/time.h>
+#include <wpi/FunctionExtras.h>
+
+#include "EventLoop.h"
+
+namespace frc {
+
+/**
+ * This class provides an easy way to link actions to inputs. Each object
+ * represents a boolean condition to which callback actions can be bound using
+ * {@link #IfHigh(std::function<void()>)}.
+ *
+ * <p>These events can easily be composed using factories such as {@link
+ * #operator!},
+ * {@link #operator||}, {@link #operator&&} etc.
+ *
+ * <p>To get an event that activates only when this one changes, see {@link
+ * #Falling()} and {@link #Rising()}.
+ */
+class BooleanEvent {
+ public:
+  /**
+   * Creates a new event with the given condition determining whether it is
+   * active.
+   *
+   * @param loop the loop that polls this event
+   * @param condition returns whether or not the event should be active
+   */
+  BooleanEvent(EventLoop* loop, std::function<bool()> condition);
+
+  /**
+   * Check whether this event is active or not.
+   *
+   * @return true if active.
+   */
+  bool GetAsBoolean() const;
+
+  /**
+   * Bind an action to this event.
+   *
+   * @param action the action to run if this event is active.
+   */
+  void IfHigh(std::function<void()> action);
+
+  operator std::function<bool()>();  // NOLINT
+
+  /**
+   * A method to "downcast" a BooleanEvent instance to a subclass (for example,
+   * to a command-based version of this class).
+   *
+   * @param ctor a method reference to the constructor of the subclass that
+   * accepts the loop as the first parameter and the condition/signal as the
+   * second.
+   * @return an instance of the subclass.
+   */
+  template <class T>
+  T CastTo(std::function<T(EventLoop*, std::function<bool()>)> ctor =
+               [](EventLoop* loop, std::function<bool()> condition) {
+                 return T(loop, condition);
+               }) {
+    return ctor(m_loop, m_condition);
+  }
+
+  /**
+   * Creates a new event that is active when this event is inactive, i.e. that
+   * acts as the negation of this event.
+   *
+   * @return the negated event
+   */
+  BooleanEvent operator!();
+
+  /**
+   * Composes this event with another event, returning a new event that is
+   * active when both events are active.
+   *
+   * <p>The new event will use this event's polling loop.
+   *
+   * @param rhs the event to compose with
+   * @return the event that is active when both events are active
+   */
+  BooleanEvent operator&&(std::function<bool()> rhs);
+
+  /**
+   * Composes this event with another event, returning a new event that is
+   * active when either event is active.
+   *
+   * <p>The new event will use this event's polling loop.
+   *
+   * @param rhs the event to compose with
+   * @return the event that is active when either event is active
+   */
+  BooleanEvent operator||(std::function<bool()> rhs);
+
+  /**
+   * Get a new event that events only when this one newly changes to true.
+   *
+   * @return a new event representing when this one newly changes to true.
+   */
+  BooleanEvent Rising();
+
+  /**
+   * Get a new event that triggers only when this one newly changes to false.
+   *
+   * @return a new event representing when this one newly changes to false.
+   */
+  BooleanEvent Falling();
+
+  /**
+   * Creates a new debounced event from this event - it will become active when
+   * this event has been active for longer than the specified period.
+   *
+   * @param debounceTime The debounce period.
+   * @param type The debounce type.
+   * @return The debounced event.
+   */
+  BooleanEvent Debounce(units::second_t debounceTime,
+                        frc::Debouncer::DebounceType type =
+                            frc::Debouncer::DebounceType::kRising);
+
+ private:
+  EventLoop* m_loop;
+  std::function<bool()> m_condition;
+};
+}  // namespace frc
diff --git a/wpilibc/src/main/native/include/frc/event/EventLoop.h b/wpilibc/src/main/native/include/frc/event/EventLoop.h
new file mode 100644
index 0000000..d18fac3
--- /dev/null
+++ b/wpilibc/src/main/native/include/frc/event/EventLoop.h
@@ -0,0 +1,42 @@
+// 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 <vector>
+
+#include <wpi/FunctionExtras.h>
+
+namespace frc {
+/** The loop polling BooleanEvent objects and executing the actions bound to
+ * them. */
+class EventLoop {
+ public:
+  EventLoop();
+
+  EventLoop(const EventLoop&) = delete;
+  EventLoop& operator=(const EventLoop&) = delete;
+
+  /**
+   * Bind a new action to run.
+   *
+   * @param action the action to run.
+   */
+  void Bind(wpi::unique_function<void()> action);
+
+  /**
+   * Poll all bindings.
+   */
+  void Poll();
+
+  /**
+   * Clear all bindings.
+   */
+  void Clear();
+
+ private:
+  std::vector<wpi::unique_function<void()>> m_bindings;
+};
+}  // namespace frc
diff --git a/wpilibc/src/main/native/include/frc/event/NetworkBooleanEvent.h b/wpilibc/src/main/native/include/frc/event/NetworkBooleanEvent.h
new file mode 100644
index 0000000..85f7039
--- /dev/null
+++ b/wpilibc/src/main/native/include/frc/event/NetworkBooleanEvent.h
@@ -0,0 +1,79 @@
+// 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 <string_view>
+
+#include "BooleanEvent.h"
+
+namespace nt {
+class BooleanSubscriber;
+class BooleanTopic;
+class NetworkTable;
+class NetworkTableInstance;
+}  // namespace nt
+
+namespace frc {
+/**
+ * A Button that uses a NetworkTable boolean field.
+ *
+ * This class is provided by the NewCommands VendorDep
+ */
+class NetworkBooleanEvent : public BooleanEvent {
+ public:
+  /**
+   * Creates a new event with the given boolean topic determining whether it is
+   * active.
+   *
+   * @param loop the loop that polls this event
+   * @param topic The boolean topic that contains the value
+   */
+  NetworkBooleanEvent(EventLoop* loop, nt::BooleanTopic topic);
+
+  /**
+   * Creates a new event with the given boolean subscriber determining whether
+   * it is active.
+   *
+   * @param loop the loop that polls this event
+   * @param sub The boolean subscriber that provides the value
+   */
+  NetworkBooleanEvent(EventLoop* loop, nt::BooleanSubscriber sub);
+
+  /**
+   * Creates a new event with the given boolean topic determining whether it is
+   * active.
+   *
+   * @param loop the loop that polls this event
+   * @param table The NetworkTable that contains the topic
+   * @param topicName The topic name within the table that contains the value
+   */
+  NetworkBooleanEvent(EventLoop* loop, std::shared_ptr<nt::NetworkTable> table,
+                      std::string_view topicName);
+
+  /**
+   * Creates a new event with the given boolean topic determining whether it is
+   * active.
+   *
+   * @param loop the loop that polls this event
+   * @param tableName The NetworkTable name that contains the topic
+   * @param topicName The topic name within the table that contains the value
+   */
+  NetworkBooleanEvent(EventLoop* loop, std::string_view tableName,
+                      std::string_view topicName);
+
+  /**
+   * Creates a new event with the given boolean topic determining whether it is
+   * active.
+   *
+   * @param loop the loop that polls this event
+   * @param inst The NetworkTable instance to use
+   * @param tableName The NetworkTable that contains the topic
+   * @param topicName The topic name within the table that contains the value
+   */
+  NetworkBooleanEvent(EventLoop* loop, nt::NetworkTableInstance inst,
+                      std::string_view tableName, std::string_view topicName);
+};
+}  // namespace frc
diff --git a/wpilibc/src/main/native/include/frc/interfaces/Gyro.h b/wpilibc/src/main/native/include/frc/interfaces/Gyro.h
index 50417ea..b74a3cf 100644
--- a/wpilibc/src/main/native/include/frc/interfaces/Gyro.h
+++ b/wpilibc/src/main/native/include/frc/interfaces/Gyro.h
@@ -77,7 +77,7 @@
    *         based on integration of the returned rate from the gyro.
    */
   virtual Rotation2d GetRotation2d() const {
-    return Rotation2d{units::degree_t{-GetAngle()}};
+    return units::degree_t{-GetAngle()};
   }
 };
 
diff --git a/wpilibc/src/main/native/include/frc/internal/DriverStationModeThread.h b/wpilibc/src/main/native/include/frc/internal/DriverStationModeThread.h
new file mode 100644
index 0000000..f4fd11c
--- /dev/null
+++ b/wpilibc/src/main/native/include/frc/internal/DriverStationModeThread.h
@@ -0,0 +1,36 @@
+// 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 <atomic>
+#include <thread>
+
+namespace frc::internal {
+class DriverStationModeThread {
+ public:
+  DriverStationModeThread();
+  ~DriverStationModeThread();
+
+  DriverStationModeThread(const DriverStationModeThread& other) = delete;
+  DriverStationModeThread(DriverStationModeThread&& other) = delete;
+  DriverStationModeThread& operator=(const DriverStationModeThread& other) =
+      delete;
+  DriverStationModeThread& operator=(DriverStationModeThread&& other) = delete;
+
+  void InAutonomous(bool entering);
+  void InDisabled(bool entering);
+  void InTeleop(bool entering);
+  void InTest(bool entering);
+
+ private:
+  std::atomic_bool m_keepAlive{false};
+  std::thread m_thread;
+  void Run();
+  bool m_userInDisabled{false};
+  bool m_userInAutonomous{false};
+  bool m_userInTeleop{false};
+  bool m_userInTest{false};
+};
+}  // namespace frc::internal
diff --git a/wpilibc/src/main/native/include/frc/livewindow/LiveWindow.h b/wpilibc/src/main/native/include/frc/livewindow/LiveWindow.h
index 6851cde..3714146 100644
--- a/wpilibc/src/main/native/include/frc/livewindow/LiveWindow.h
+++ b/wpilibc/src/main/native/include/frc/livewindow/LiveWindow.h
@@ -6,8 +6,6 @@
 
 #include <functional>
 
-#include <wpi/deprecated.h>
-
 namespace wpi {
 class Sendable;
 }  // namespace wpi
@@ -18,20 +16,9 @@
  * The LiveWindow class is the public interface for putting sensors and
  * actuators on the LiveWindow.
  */
-class LiveWindow {
+class LiveWindow final {
  public:
   /**
-   * Get an instance of the LiveWindow main class.
-   *
-   * This is a singleton to guarantee that there is only a single instance
-   * regardless of how many times GetInstance is called.
-   * @deprecated Use the static methods unless guaranteeing LiveWindow is
-   * instantiated
-   */
-  WPI_DEPRECATED("Use static methods")
-  static LiveWindow* GetInstance();
-
-  /**
    * Set function to be called when LiveWindow is enabled.
    *
    * @param func function (or nullptr for none)
@@ -64,6 +51,11 @@
    */
   static void DisableAllTelemetry();
 
+  /**
+   * Enable ALL telemetry.
+   */
+  static void EnableAllTelemetry();
+
   static bool IsEnabled();
 
   /**
diff --git a/wpilibc/src/main/native/include/frc/motorcontrol/MotorController.h b/wpilibc/src/main/native/include/frc/motorcontrol/MotorController.h
index 8ed19bc..24e1b17 100644
--- a/wpilibc/src/main/native/include/frc/motorcontrol/MotorController.h
+++ b/wpilibc/src/main/native/include/frc/motorcontrol/MotorController.h
@@ -6,32 +6,66 @@
 
 #include <units/voltage.h>
 
-#include "frc/SpeedController.h"
-
 namespace frc {
 
-#if defined(_MSC_VER)
-#pragma warning(push)
-#pragma warning(disable : 4996)  // was declared deprecated
-#elif defined(__clang__)
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-#elif defined(__GNUC__)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
-#endif
-
 /**
  * Interface for motor controlling devices.
  */
-class MotorController : public SpeedController {};
+class MotorController {
+ public:
+  virtual ~MotorController() = default;
 
-#if defined(_MSC_VER)
-#pragma warning(pop)
-#elif defined(__clang__)
-#pragma clang diagnostic pop
-#elif defined(__GNUC__)
-#pragma GCC diagnostic pop
-#endif
+  /**
+   * Common interface for setting the speed of a motor controller.
+   *
+   * @param speed The speed to set.  Value should be between -1.0 and 1.0.
+   */
+  virtual void Set(double speed) = 0;
+
+  /**
+   * Sets the voltage output of the MotorController.  Compensates for
+   * the current bus voltage to ensure that the desired voltage is output even
+   * if the battery voltage is below 12V - highly useful when the voltage
+   * outputs are "meaningful" (e.g. they come from a feedforward calculation).
+   *
+   * <p>NOTE: This function *must* be called regularly in order for voltage
+   * compensation to work properly - unlike the ordinary set function, it is not
+   * "set it and forget it."
+   *
+   * @param output The voltage to output.
+   */
+  virtual void SetVoltage(units::volt_t output);
+
+  /**
+   * Common interface for getting the current set speed of a motor controller.
+   *
+   * @return The current set speed.  Value is between -1.0 and 1.0.
+   */
+  virtual double Get() const = 0;
+
+  /**
+   * Common interface for inverting direction of a motor controller.
+   *
+   * @param isInverted The state of inversion, true is inverted.
+   */
+  virtual void SetInverted(bool isInverted) = 0;
+
+  /**
+   * Common interface for returning the inversion state of a motor controller.
+   *
+   * @return isInverted The state of inversion, true is inverted.
+   */
+  virtual bool GetInverted() const = 0;
+
+  /**
+   * Common interface for disabling a motor.
+   */
+  virtual void Disable() = 0;
+
+  /**
+   * Common interface to stop the motor until Set is called again.
+   */
+  virtual void StopMotor() = 0;
+};
 
 }  // namespace frc
diff --git a/wpilibc/src/main/native/include/frc/motorcontrol/MotorControllerGroup.h b/wpilibc/src/main/native/include/frc/motorcontrol/MotorControllerGroup.h
index 6f47bf9..99a9e4e 100644
--- a/wpilibc/src/main/native/include/frc/motorcontrol/MotorControllerGroup.h
+++ b/wpilibc/src/main/native/include/frc/motorcontrol/MotorControllerGroup.h
@@ -31,6 +31,7 @@
   MotorControllerGroup& operator=(MotorControllerGroup&&) = default;
 
   void Set(double speed) override;
+  void SetVoltage(units::volt_t output) override;
   double Get() const override;
   void SetInverted(bool isInverted) override;
   bool GetInverted() const override;
diff --git a/wpilibc/src/main/native/include/frc/motorcontrol/PWMMotorController.h b/wpilibc/src/main/native/include/frc/motorcontrol/PWMMotorController.h
index 7bb63fb..bca5d7f 100644
--- a/wpilibc/src/main/native/include/frc/motorcontrol/PWMMotorController.h
+++ b/wpilibc/src/main/native/include/frc/motorcontrol/PWMMotorController.h
@@ -64,7 +64,7 @@
   /**
    * Optionally eliminate the deadband from a motor controller.
    *
-   * @param eliminateDeadband If true, set the motor curve on the speed
+   * @param eliminateDeadband If true, set the motor curve on the motor
    *                          controller to eliminate the deadband in the middle
    *                          of the range. Otherwise, keep the full range
    *                          without modifying any values.
diff --git a/wpilibc/src/main/native/include/frc/shuffleboard/ComplexWidget.h b/wpilibc/src/main/native/include/frc/shuffleboard/ComplexWidget.h
index dc49d64..a96f879 100644
--- a/wpilibc/src/main/native/include/frc/shuffleboard/ComplexWidget.h
+++ b/wpilibc/src/main/native/include/frc/shuffleboard/ComplexWidget.h
@@ -21,7 +21,7 @@
 class ShuffleboardContainer;
 
 /**
- * A Shuffleboard widget that handles a Sendable object such as a speed
+ * A Shuffleboard widget that handles a Sendable object such as a motor
  * controller or sensor.
  */
 class ComplexWidget final : public ShuffleboardWidget<ComplexWidget> {
diff --git a/wpilibc/src/main/native/include/frc/shuffleboard/RecordingController.h b/wpilibc/src/main/native/include/frc/shuffleboard/RecordingController.h
index 83b23a4..3ef5120 100644
--- a/wpilibc/src/main/native/include/frc/shuffleboard/RecordingController.h
+++ b/wpilibc/src/main/native/include/frc/shuffleboard/RecordingController.h
@@ -8,8 +8,10 @@
 #include <string>
 #include <string_view>
 
+#include <networktables/BooleanTopic.h>
 #include <networktables/NetworkTable.h>
 #include <networktables/NetworkTableInstance.h>
+#include <networktables/StringTopic.h>
 #include <wpi/SmallVector.h>
 
 #include "frc/shuffleboard/ShuffleboardEventImportance.h"
@@ -30,8 +32,8 @@
                       ShuffleboardEventImportance importance);
 
  private:
-  nt::NetworkTableEntry m_recordingControlEntry;
-  nt::NetworkTableEntry m_recordingFileNameFormatEntry;
+  nt::BooleanPublisher m_recordingControlEntry;
+  nt::StringPublisher m_recordingFileNameFormatEntry;
   std::shared_ptr<nt::NetworkTable> m_eventsTable;
 };
 
diff --git a/wpilibc/src/main/native/include/frc/shuffleboard/SendableCameraWrapper.h b/wpilibc/src/main/native/include/frc/shuffleboard/SendableCameraWrapper.h
index 4792ce3..7951cf9 100644
--- a/wpilibc/src/main/native/include/frc/shuffleboard/SendableCameraWrapper.h
+++ b/wpilibc/src/main/native/include/frc/shuffleboard/SendableCameraWrapper.h
@@ -6,6 +6,7 @@
 
 #include <functional>
 #include <memory>
+#include <span>
 #include <string>
 #include <string_view>
 
@@ -22,9 +23,9 @@
 using CS_Source = CS_Handle;  // NOLINT
 #endif
 
+#include <networktables/StringArrayTopic.h>
 #include <wpi/sendable/Sendable.h>
 #include <wpi/sendable/SendableHelper.h>
-#include <wpi/span.h>
 
 namespace frc {
 
@@ -56,6 +57,17 @@
   SendableCameraWrapper(std::string_view cameraName, const private_init&);
 
   /**
+   * Creates a new sendable wrapper. Private constructor to avoid direct
+   * instantiation with multiple wrappers floating around for the same camera.
+   *
+   * @param cameraName the name of the camera to wrap
+   * @param cameraUrls camera URLs
+   */
+  SendableCameraWrapper(std::string_view cameraName,
+                        std::span<const std::string> cameraUrls,
+                        const private_init&);
+
+  /**
    * Gets a sendable wrapper object for the given video source, creating the
    * wrapper if one does not already exist for the source.
    *
@@ -67,12 +79,13 @@
   static SendableCameraWrapper& Wrap(CS_Source source);
 
   static SendableCameraWrapper& Wrap(std::string_view cameraName,
-                                     wpi::span<const std::string> cameraUrls);
+                                     std::span<const std::string> cameraUrls);
 
   void InitSendable(wpi::SendableBuilder& builder) override;
 
  private:
   std::string m_uri;
+  nt::StringArrayPublisher m_streams;
 };
 
 #ifndef DYNAMIC_CAMERA_SERVER
@@ -83,6 +96,17 @@
   m_uri += name;
 }
 
+inline SendableCameraWrapper::SendableCameraWrapper(
+    std::string_view cameraName, std::span<const std::string> cameraUrls,
+    const private_init&)
+    : SendableCameraWrapper(cameraName, private_init{}) {
+  m_streams = nt::NetworkTableInstance::GetDefault()
+                  .GetStringArrayTopic(
+                      fmt::format("/CameraPublisher/{}/streams", cameraName))
+                  .Publish();
+  m_streams.Set(cameraUrls);
+}
+
 inline SendableCameraWrapper& SendableCameraWrapper::Wrap(
     const cs::VideoSource& source) {
   return Wrap(source.GetHandle());
@@ -99,15 +123,12 @@
 }
 
 inline SendableCameraWrapper& SendableCameraWrapper::Wrap(
-    std::string_view cameraName, wpi::span<const std::string> cameraUrls) {
+    std::string_view cameraName, std::span<const std::string> cameraUrls) {
   auto& wrapper = detail::GetSendableCameraWrapper(cameraName);
   if (!wrapper) {
-    wrapper =
-        std::make_shared<SendableCameraWrapper>(cameraName, private_init{});
+    wrapper = std::make_shared<SendableCameraWrapper>(cameraName, cameraUrls,
+                                                      private_init{});
   }
-  auto streams = fmt::format("/CameraPublisher/{}/streams", cameraName);
-  nt::NetworkTableInstance::GetDefault().GetEntry(streams).SetStringArray(
-      cameraUrls);
   return *wrapper;
 }
 #endif
diff --git a/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardComponent.h b/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardComponent.h
index 24f65a3..fc15948 100644
--- a/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardComponent.h
+++ b/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardComponent.h
@@ -37,8 +37,7 @@
    * @param properties the properties for this component
    * @return this component
    */
-  Derived& WithProperties(
-      const wpi::StringMap<std::shared_ptr<nt::Value>>& properties);
+  Derived& WithProperties(const wpi::StringMap<nt::Value>& properties);
 
   /**
    * Sets the position of this component in the tab. This has no effect if this
diff --git a/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardComponent.inc b/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardComponent.inc
index 9750d4a..63a4933 100644
--- a/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardComponent.inc
+++ b/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardComponent.inc
@@ -21,7 +21,7 @@
 
 template <typename Derived>
 Derived& ShuffleboardComponent<Derived>::WithProperties(
-    const wpi::StringMap<std::shared_ptr<nt::Value>>& properties) {
+    const wpi::StringMap<nt::Value>& properties) {
   m_properties = properties;
   m_metadataDirty = true;
   return *static_cast<Derived*>(this);
diff --git a/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardComponentBase.h b/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardComponentBase.h
index d33a234..de27b35 100644
--- a/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardComponentBase.h
+++ b/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardComponentBase.h
@@ -35,7 +35,7 @@
   const std::string& GetType() const;
 
  protected:
-  wpi::StringMap<std::shared_ptr<nt::Value>> m_properties;
+  wpi::StringMap<nt::Value> m_properties;
   bool m_metadataDirty = true;
   int m_column = -1;
   int m_row = -1;
@@ -49,7 +49,7 @@
   /**
    * Gets the custom properties for this component. May be null.
    */
-  const wpi::StringMap<std::shared_ptr<nt::Value>>& GetProperties() const;
+  const wpi::StringMap<nt::Value>& GetProperties() const;
 };
 
 }  // namespace frc
diff --git a/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardContainer.h b/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardContainer.h
index 13a5cee..5d20783 100644
--- a/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardContainer.h
+++ b/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardContainer.h
@@ -6,6 +6,7 @@
 
 #include <functional>
 #include <memory>
+#include <span>
 #include <string>
 #include <string_view>
 #include <vector>
@@ -14,7 +15,6 @@
 #include <networktables/NetworkTableValue.h>
 #include <wpi/SmallSet.h>
 #include <wpi/StringMap.h>
-#include <wpi/span.h>
 
 #include "frc/shuffleboard/BuiltInLayouts.h"
 #include "frc/shuffleboard/LayoutType.h"
@@ -137,7 +137,7 @@
    * @return a widget to display the camera stream
    */
   ComplexWidget& AddCamera(std::string_view title, std::string_view cameraName,
-                           wpi::span<const std::string> cameraUrls);
+                           std::span<const std::string> cameraUrls);
 
   /**
    * Adds a widget to this container to display the given sendable.
@@ -171,8 +171,7 @@
    * @see AddPersistent(std::string_view, std::shared_ptr<nt::Value>)
    *      Add(std::string_view title, std::shared_ptr<nt::Value> defaultValue)
    */
-  SimpleWidget& Add(std::string_view title,
-                    std::shared_ptr<nt::Value> defaultValue);
+  SimpleWidget& Add(std::string_view title, const nt::Value& defaultValue);
 
   /**
    * Adds a widget to this container to display the given data.
@@ -208,6 +207,19 @@
    * @return a widget to display the sendable data
    * @throws IllegalArgumentException if a widget already exists in this
    *         container with the given title
+   * @see AddPersistent(std::string_view, double)
+   *      Add(std::string_view title, double defaultValue)
+   */
+  SimpleWidget& Add(std::string_view title, float defaultValue);
+
+  /**
+   * Adds a widget to this container to display the given data.
+   *
+   * @param title the title of the widget
+   * @param defaultValue  the default value of the widget
+   * @return a widget to display the sendable data
+   * @throws IllegalArgumentException if a widget already exists in this
+   *         container with the given title
    * @see AddPersistent(std::string_view, int)
    *      Add(std::string_view title, int defaultValue)
    */
@@ -247,10 +259,10 @@
    * @return a widget to display the sendable data
    * @throws IllegalArgumentException if a widget already exists in this
    *         container with the given title
-   * @see AddPersistent(std::string_view, wpi::span<const bool>)
-   *      Add(std::string_view title, wpi::span<const bool> defaultValue)
+   * @see AddPersistent(std::string_view, std::span<const bool>)
+   *      Add(std::string_view title, std::span<const bool> defaultValue)
    */
-  SimpleWidget& Add(std::string_view title, wpi::span<const bool> defaultValue);
+  SimpleWidget& Add(std::string_view title, std::span<const bool> defaultValue);
 
   /**
    * Adds a widget to this container to display the given data.
@@ -260,11 +272,11 @@
    * @return a widget to display the sendable data
    * @throws IllegalArgumentException if a widget already exists in this
    *         container with the given title
-   * @see AddPersistent(std::string_view, wpi::span<const double>)
-   *      Add(std::string_view title, wpi::span<const double> defaultValue)
+   * @see AddPersistent(std::string_view, std::span<const double>)
+   *      Add(std::string_view title, std::span<const double> defaultValue)
    */
   SimpleWidget& Add(std::string_view title,
-                    wpi::span<const double> defaultValue);
+                    std::span<const double> defaultValue);
 
   /**
    * Adds a widget to this container to display the given data.
@@ -274,11 +286,39 @@
    * @return a widget to display the sendable data
    * @throws IllegalArgumentException if a widget already exists in this
    *         container with the given title
-   * @see AddPersistent(std::string_view, wpi::span<const std::string>)
-   *      Add(std::string_view title, wpi::span<const std::string> defaultValue)
+   * @see AddPersistent(std::string_view, std::span<const double>)
+   *      Add(std::string_view title, std::span<const double> defaultValue)
    */
   SimpleWidget& Add(std::string_view title,
-                    wpi::span<const std::string> defaultValue);
+                    std::span<const float> defaultValue);
+
+  /**
+   * Adds a widget to this container to display the given data.
+   *
+   * @param title the title of the widget
+   * @param defaultValue  the default value of the widget
+   * @return a widget to display the sendable data
+   * @throws IllegalArgumentException if a widget already exists in this
+   *         container with the given title
+   * @see AddPersistent(std::string_view, std::span<const double>)
+   *      Add(std::string_view title, std::span<const double> defaultValue)
+   */
+  SimpleWidget& Add(std::string_view title,
+                    std::span<const int64_t> defaultValue);
+
+  /**
+   * Adds a widget to this container to display the given data.
+   *
+   * @param title the title of the widget
+   * @param defaultValue  the default value of the widget
+   * @return a widget to display the sendable data
+   * @throws IllegalArgumentException if a widget already exists in this
+   *         container with the given title
+   * @see AddPersistent(std::string_view, std::span<const std::string>)
+   *      Add(std::string_view title, std::span<const std::string> defaultValue)
+   */
+  SimpleWidget& Add(std::string_view title,
+                    std::span<const std::string> defaultValue);
 
   /**
    * Adds a widget to this container. The widget will display the data provided
@@ -316,6 +356,45 @@
    * @param supplier the supplier for values
    * @return a widget to display data
    */
+  SuppliedValueWidget<double>& AddDouble(std::string_view title,
+                                         std::function<double()> supplier);
+
+  /**
+   * Adds a widget to this container. The widget will display the data provided
+   * by the value supplier. Changes made on the dashboard will not propagate to
+   * the widget object, and will be overridden by values from the value
+   * supplier.
+   *
+   * @param title the title of the widget
+   * @param supplier the supplier for values
+   * @return a widget to display data
+   */
+  SuppliedValueWidget<float>& AddFloat(std::string_view title,
+                                       std::function<float()> supplier);
+
+  /**
+   * Adds a widget to this container. The widget will display the data provided
+   * by the value supplier. Changes made on the dashboard will not propagate to
+   * the widget object, and will be overridden by values from the value
+   * supplier.
+   *
+   * @param title the title of the widget
+   * @param supplier the supplier for values
+   * @return a widget to display data
+   */
+  SuppliedValueWidget<int64_t>& AddInteger(std::string_view title,
+                                           std::function<int64_t()> supplier);
+
+  /**
+   * Adds a widget to this container. The widget will display the data provided
+   * by the value supplier. Changes made on the dashboard will not propagate to
+   * the widget object, and will be overridden by values from the value
+   * supplier.
+   *
+   * @param title the title of the widget
+   * @param supplier the supplier for values
+   * @return a widget to display data
+   */
   SuppliedValueWidget<bool>& AddBoolean(std::string_view title,
                                         std::function<bool()> supplier);
 
@@ -356,6 +435,45 @@
    * @param supplier the supplier for values
    * @return a widget to display data
    */
+  SuppliedValueWidget<std::vector<double>>& AddDoubleArray(
+      std::string_view title, std::function<std::vector<double>()> supplier);
+
+  /**
+   * Adds a widget to this container. The widget will display the data provided
+   * by the value supplier. Changes made on the dashboard will not propagate to
+   * the widget object, and will be overridden by values from the value
+   * supplier.
+   *
+   * @param title the title of the widget
+   * @param supplier the supplier for values
+   * @return a widget to display data
+   */
+  SuppliedValueWidget<std::vector<float>>& AddFloatArray(
+      std::string_view title, std::function<std::vector<float>()> supplier);
+
+  /**
+   * Adds a widget to this container. The widget will display the data provided
+   * by the value supplier. Changes made on the dashboard will not propagate to
+   * the widget object, and will be overridden by values from the value
+   * supplier.
+   *
+   * @param title the title of the widget
+   * @param supplier the supplier for values
+   * @return a widget to display data
+   */
+  SuppliedValueWidget<std::vector<int64_t>>& AddIntegerArray(
+      std::string_view title, std::function<std::vector<int64_t>()> supplier);
+
+  /**
+   * Adds a widget to this container. The widget will display the data provided
+   * by the value supplier. Changes made on the dashboard will not propagate to
+   * the widget object, and will be overridden by values from the value
+   * supplier.
+   *
+   * @param title the title of the widget
+   * @param supplier the supplier for values
+   * @return a widget to display data
+   */
   SuppliedValueWidget<std::vector<int>>& AddBooleanArray(
       std::string_view title, std::function<std::vector<int>()> supplier);
 
@@ -369,8 +487,23 @@
    * @param supplier the supplier for values
    * @return a widget to display data
    */
-  SuppliedValueWidget<std::string_view>& AddRaw(
-      std::string_view title, std::function<std::string_view()> supplier);
+  SuppliedValueWidget<std::vector<uint8_t>>& AddRaw(
+      std::string_view title, std::function<std::vector<uint8_t>()> supplier);
+
+  /**
+   * Adds a widget to this container. The widget will display the data provided
+   * by the value supplier. Changes made on the dashboard will not propagate to
+   * the widget object, and will be overridden by values from the value
+   * supplier.
+   *
+   * @param title the title of the widget
+   * @param typeString the NT type string
+   * @param supplier the supplier for values
+   * @return a widget to display data
+   */
+  SuppliedValueWidget<std::vector<uint8_t>>& AddRaw(
+      std::string_view title, std::string_view typeString,
+      std::function<std::vector<uint8_t>()> supplier);
 
   /**
    * Adds a widget to this container to display a simple piece of data.
@@ -386,7 +519,7 @@
    *      Add(std::string_view title, std::shared_ptr<nt::Value> defaultValue)
    */
   SimpleWidget& AddPersistent(std::string_view title,
-                              std::shared_ptr<nt::Value> defaultValue);
+                              const nt::Value& defaultValue);
 
   /**
    * Adds a widget to this container to display a simple piece of data.
@@ -421,15 +554,30 @@
   /**
    * Adds a widget to this container to display a simple piece of data.
    *
-   * Unlike Add(std::string_view, int), the value in the widget will be saved on
-   * the robot and will be used when the robot program next starts rather than
-   * {@code defaultValue}.
+   * Unlike Add(std::string_view, float), the value in the widget will be saved
+   * on the robot and will be used when the robot program next starts rather
+   * than {@code defaultValue}.
    *
    * @param title the title of the widget
    * @param defaultValue the default value of the widget
    * @return a widget to display the sendable data
-   * @see Add(std:string_view, int)
-   *      Add(std::string_view title, int defaultValue)
+   * @see Add(std::string_view, float)
+   *      Add(std::string_view title, float defaultValue)
+   */
+  SimpleWidget& AddPersistent(std::string_view title, float defaultValue);
+
+  /**
+   * Adds a widget to this container to display a simple piece of data.
+   *
+   * Unlike Add(std::string_view, int64_t), the value in the widget will be
+   * saved on the robot and will be used when the robot program next starts
+   * rather than {@code defaultValue}.
+   *
+   * @param title the title of the widget
+   * @param defaultValue the default value of the widget
+   * @return a widget to display the sendable data
+   * @see Add(std:string_view, int64_t)
+   *      Add(std::string_view title, int64_t defaultValue)
    */
   SimpleWidget& AddPersistent(std::string_view title, int defaultValue);
 
@@ -452,50 +600,82 @@
   /**
    * Adds a widget to this container to display a simple piece of data.
    *
-   * Unlike Add(std::string_view, wpi::span<const bool>), the value in the
+   * Unlike Add(std::string_view, std::span<const bool>), the value in the
    * widget will be saved on the robot and will be used when the robot program
    * next starts rather than {@code defaultValue}.
    *
    * @param title the title of the widget
    * @param defaultValue the default value of the widget
    * @return a widget to display the sendable data
-   * @see Add(std::string_view, wpi::span<const bool>)
-   *      Add(std::string_view title, wpi::span<const bool> defaultValue)
+   * @see Add(std::string_view, std::span<const bool>)
+   *      Add(std::string_view title, std::span<const bool> defaultValue)
    */
   SimpleWidget& AddPersistent(std::string_view title,
-                              wpi::span<const bool> defaultValue);
+                              std::span<const bool> defaultValue);
 
   /**
    * Adds a widget to this container to display a simple piece of data.
    *
-   * Unlike Add(std::string_view, wpi::span<const double>), the value in the
+   * Unlike Add(std::string_view, std::span<const double>), the value in the
    * widget will be saved on the robot and will be used when the robot program
    * next starts rather than {@code defaultValue}.
    *
    * @param title the title of the widget
    * @param defaultValue the default value of the widget
    * @return a widget to display the sendable data
-   * @see Add(std::string_view, wpi::span<const double>)
-   *      Add(std::string_view title, wpi::span<const double> defaultValue)
+   * @see Add(std::string_view, std::span<const double>)
+   *      Add(std::string_view title, std::span<const double> defaultValue)
    */
   SimpleWidget& AddPersistent(std::string_view title,
-                              wpi::span<const double> defaultValue);
+                              std::span<const double> defaultValue);
 
   /**
    * Adds a widget to this container to display a simple piece of data.
    *
-   * Unlike Add(std::string_view, wpi::span<const std::string>), the value in
+   * Unlike Add(std::string_view, std::span<const float>), the value in the
+   * widget will be saved on the robot and will be used when the robot program
+   * next starts rather than {@code defaultValue}.
+   *
+   * @param title the title of the widget
+   * @param defaultValue the default value of the widget
+   * @return a widget to display the sendable data
+   * @see Add(std::string_view, std::span<const float>)
+   *      Add(std::string_view title, std::span<const float> defaultValue)
+   */
+  SimpleWidget& AddPersistent(std::string_view title,
+                              std::span<const float> defaultValue);
+
+  /**
+   * Adds a widget to this container to display a simple piece of data.
+   *
+   * Unlike Add(std::string_view, std::span<const int64_t>), the value in the
+   * widget will be saved on the robot and will be used when the robot program
+   * next starts rather than {@code defaultValue}.
+   *
+   * @param title the title of the widget
+   * @param defaultValue the default value of the widget
+   * @return a widget to display the sendable data
+   * @see Add(std::string_view, std::span<const int64_t>)
+   *      Add(std::string_view title, std::span<const int64_t> defaultValue)
+   */
+  SimpleWidget& AddPersistent(std::string_view title,
+                              std::span<const int64_t> defaultValue);
+
+  /**
+   * Adds a widget to this container to display a simple piece of data.
+   *
+   * Unlike Add(std::string_view, std::span<const std::string>), the value in
    * the widget will be saved on the robot and will be used when the robot
    * program next starts rather than {@code defaultValue}.
    *
    * @param title the title of the widget
    * @param defaultValue the default value of the widget
    * @return a widget to display the sendable data
-   * @see Add(std::string_view, wpi::span<const std::string>)
-   *      Add(std::string_view title, wpi::span<const std::string> defaultValue)
+   * @see Add(std::string_view, std::span<const std::string>)
+   *      Add(std::string_view title, std::span<const std::string> defaultValue)
    */
   SimpleWidget& AddPersistent(std::string_view title,
-                              wpi::span<const std::string> defaultValue);
+                              std::span<const std::string> defaultValue);
 
   void EnableIfActuator() override;
 
@@ -541,7 +721,7 @@
 
 inline frc::ComplexWidget& frc::ShuffleboardContainer::AddCamera(
     std::string_view title, std::string_view cameraName,
-    wpi::span<const std::string> cameraUrls) {
+    std::span<const std::string> cameraUrls) {
   return Add(title, frc::SendableCameraWrapper::Wrap(cameraName, cameraUrls));
 }
 #endif
diff --git a/wpilibc/src/main/native/include/frc/shuffleboard/SimpleWidget.h b/wpilibc/src/main/native/include/frc/shuffleboard/SimpleWidget.h
index 746b7d6..97502be 100644
--- a/wpilibc/src/main/native/include/frc/shuffleboard/SimpleWidget.h
+++ b/wpilibc/src/main/native/include/frc/shuffleboard/SimpleWidget.h
@@ -5,10 +5,11 @@
 #pragma once
 
 #include <memory>
+#include <string>
 #include <string_view>
 
+#include <networktables/GenericEntry.h>
 #include <networktables/NetworkTable.h>
-#include <networktables/NetworkTableEntry.h>
 
 #include "frc/shuffleboard/ShuffleboardWidget.h"
 
@@ -26,14 +27,26 @@
 
   /**
    * Gets the NetworkTable entry that contains the data for this widget.
+   * The widget owns the entry; the returned pointer's lifetime is the same as
+   * that of the widget.
    */
-  nt::NetworkTableEntry GetEntry();
+  nt::GenericEntry* GetEntry();
+
+  /**
+   * Gets the NetworkTable entry that contains the data for this widget.
+   * The widget owns the entry; the returned pointer's lifetime is the same as
+   * that of the widget.
+   *
+   * @param typeString NT type string
+   */
+  nt::GenericEntry* GetEntry(std::string_view typeString);
 
   void BuildInto(std::shared_ptr<nt::NetworkTable> parentTable,
                  std::shared_ptr<nt::NetworkTable> metaTable) override;
 
  private:
-  nt::NetworkTableEntry m_entry;
+  nt::GenericEntry m_entry;
+  std::string m_typeString;
 
   void ForceGenerate();
 };
diff --git a/wpilibc/src/main/native/include/frc/shuffleboard/SuppliedValueWidget.h b/wpilibc/src/main/native/include/frc/shuffleboard/SuppliedValueWidget.h
index a2b042e..4f82dc6 100644
--- a/wpilibc/src/main/native/include/frc/shuffleboard/SuppliedValueWidget.h
+++ b/wpilibc/src/main/native/include/frc/shuffleboard/SuppliedValueWidget.h
@@ -6,10 +6,12 @@
 
 #include <functional>
 #include <memory>
+#include <string>
 #include <string_view>
 
+#include <networktables/BooleanTopic.h>
+#include <networktables/GenericEntry.h>
 #include <networktables/NetworkTable.h>
-#include <networktables/NetworkTableEntry.h>
 
 #include "frc/shuffleboard/ShuffleboardComponent.h"
 #include "frc/shuffleboard/ShuffleboardComponent.inc"
@@ -23,24 +25,35 @@
 class SuppliedValueWidget : public ShuffleboardWidget<SuppliedValueWidget<T>> {
  public:
   SuppliedValueWidget(ShuffleboardContainer& parent, std::string_view title,
-                      std::function<T()> supplier,
-                      std::function<void(nt::NetworkTableEntry, T)> setter)
+                      std::string_view typeString, std::function<T()> supplier,
+                      std::function<void(nt::GenericPublisher&, T)> setter)
       : ShuffleboardValue(title),
         ShuffleboardWidget<SuppliedValueWidget<T>>(parent, title),
+        m_typeString(typeString),
         m_supplier(supplier),
         m_setter(setter) {}
 
   void BuildInto(std::shared_ptr<nt::NetworkTable> parentTable,
                  std::shared_ptr<nt::NetworkTable> metaTable) override {
     this->BuildMetadata(metaTable);
-    metaTable->GetEntry("Controllable").SetBoolean(false);
+    if (!m_controllablePub) {
+      m_controllablePub =
+          nt::BooleanTopic{metaTable->GetTopic("Controllable")}.Publish();
+      m_controllablePub.Set(false);
+    }
 
-    auto entry = parentTable->GetEntry(this->GetTitle());
-    m_setter(entry, m_supplier());
+    if (!m_entry) {
+      m_entry =
+          parentTable->GetTopic(this->GetTitle()).GenericPublish(m_typeString);
+    }
+    m_setter(m_entry, m_supplier());
   }
 
  private:
+  std::string m_typeString;
   std::function<T()> m_supplier;
-  std::function<void(nt::NetworkTableEntry, T)> m_setter;
+  std::function<void(nt::GenericPublisher&, T)> m_setter;
+  nt::BooleanPublisher m_controllablePub;
+  nt::GenericPublisher m_entry;
 };
 }  // namespace frc
diff --git a/wpilibc/src/main/native/include/frc/simulation/BatterySim.h b/wpilibc/src/main/native/include/frc/simulation/BatterySim.h
index 4a350cf..ab79844 100644
--- a/wpilibc/src/main/native/include/frc/simulation/BatterySim.h
+++ b/wpilibc/src/main/native/include/frc/simulation/BatterySim.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <numeric>
+#include <span>
 
 #include <units/current.h>
 #include <units/impedance.h>
@@ -29,6 +30,25 @@
    * @param currents       The currents drawn from the battery.
    * @return The battery's voltage under load.
    */
+  static units::volt_t Calculate(units::volt_t nominalVoltage,
+                                 units::ohm_t resistance,
+                                 std::span<const units::ampere_t> currents) {
+    return nominalVoltage -
+           std::accumulate(currents.begin(), currents.end(), 0_A) * resistance;
+  }
+
+  /**
+   * Calculate the loaded battery voltage. Use this with
+   * RoboRioSim::SetVInVoltage(double) to set the simulated battery voltage,
+   * which can then be retrieved with the RobotController::GetBatteryVoltage()
+   * method.
+   *
+   * @param nominalVoltage The nominal battery voltage. Usually 12v.
+   * @param resistance     The forward resistance of the battery. Most batteries
+   *                       are at or below 20 milliohms.
+   * @param currents       The currents drawn from the battery.
+   * @return The battery's voltage under load.
+   */
   static units::volt_t Calculate(
       units::volt_t nominalVoltage, units::ohm_t resistance,
       std::initializer_list<units::ampere_t> currents) {
@@ -46,6 +66,20 @@
    * @param currents The currents drawn from the battery.
    * @return The battery's voltage under load.
    */
+  static units::volt_t Calculate(std::span<const units::ampere_t> currents) {
+    return Calculate(12_V, 0.02_Ohm, currents);
+  }
+
+  /**
+   * Calculate the loaded battery voltage. Use this with
+   * RoboRioSimSetVInVoltage(double) to set the simulated battery voltage, which
+   * can then be retrieved with the RobotController::GetBatteryVoltage() method.
+   * This function assumes a nominal voltage of 12V and a resistance of 20
+   * milliohms (0.020 ohms).
+   *
+   * @param currents The currents drawn from the battery.
+   * @return The battery's voltage under load.
+   */
   static units::volt_t Calculate(
       std::initializer_list<units::ampere_t> currents) {
     return Calculate(12_V, 0.02_Ohm, currents);
diff --git a/wpilibc/src/main/native/include/frc/simulation/CTREPCMSim.h b/wpilibc/src/main/native/include/frc/simulation/CTREPCMSim.h
index 0929308..96959ed 100644
--- a/wpilibc/src/main/native/include/frc/simulation/CTREPCMSim.h
+++ b/wpilibc/src/main/native/include/frc/simulation/CTREPCMSim.h
@@ -8,17 +8,14 @@
 
 #include "frc/PneumaticsBase.h"
 #include "frc/simulation/CallbackStore.h"
+#include "frc/simulation/PneumaticsBaseSim.h"
 
-namespace frc {
-
-class Compressor;
-
-namespace sim {
+namespace frc::sim {
 
 /**
  * Class to control a simulated Pneumatic Control Module (PCM).
  */
-class CTREPCMSim {
+class CTREPCMSim : public PneumaticsBaseSim {
  public:
   /**
    * Constructs with the default PCM module number (CAN ID).
@@ -34,81 +31,28 @@
 
   explicit CTREPCMSim(const PneumaticsBase& pneumatics);
 
-  /**
-   * Register a callback to be run when a solenoid is initialized on a channel.
-   *
-   * @param callback the callback
-   * @param initialNotify should the callback be run with the initial state
-   * @return the CallbackStore object associated with this callback
-   */
+  ~CTREPCMSim() override = default;
+
   [[nodiscard]] std::unique_ptr<CallbackStore> RegisterInitializedCallback(
-      NotifyCallback callback, bool initialNotify);
+      NotifyCallback callback, bool initialNotify) override;
 
-  /**
-   * Check if a solenoid has been initialized on a specific channel.
-   *
-   * @return true if initialized
-   */
-  bool GetInitialized() const;
+  bool GetInitialized() const override;
 
-  /**
-   * Define whether a solenoid has been initialized on a specific channel.
-   *
-   * @param solenoidInitialized is there a solenoid initialized on that channel
-   */
-  void SetInitialized(bool solenoidInitialized);
+  void SetInitialized(bool initialized) override;
 
-  /**
-   * Register a callback to be run when the solenoid output on a channel
-   * changes.
-   *
-   * @param channel the channel to monitor
-   * @param callback the callback
-   * @param initialNotify should the callback be run with the initial value
-   * @return the CallbackStore object associated with this callback
-   */
   [[nodiscard]] std::unique_ptr<CallbackStore> RegisterSolenoidOutputCallback(
-      int channel, NotifyCallback callback, bool initialNotify);
+      int channel, NotifyCallback callback, bool initialNotify) override;
 
-  /**
-   * Check the solenoid output on a specific channel.
-   *
-   * @param channel the channel to check
-   * @return the solenoid output
-   */
-  bool GetSolenoidOutput(int channel) const;
+  bool GetSolenoidOutput(int channel) const override;
 
-  /**
-   * Change the solenoid output on a specific channel.
-   *
-   * @param channel the channel to check
-   * @param solenoidOutput the new solenoid output
-   */
-  void SetSolenoidOutput(int channel, bool solenoidOutput);
+  void SetSolenoidOutput(int channel, bool solenoidOutput) override;
 
-  /**
-   * Register a callback to be run when the compressor activates.
-   *
-   * @param callback the callback
-   * @param initialNotify whether to run the callback with the initial state
-   * @return the CallbackStore object associated with this callback
-   */
   [[nodiscard]] std::unique_ptr<CallbackStore> RegisterCompressorOnCallback(
-      NotifyCallback callback, bool initialNotify);
+      NotifyCallback callback, bool initialNotify) override;
 
-  /**
-   * Check if the compressor is on.
-   *
-   * @return true if the compressor is active
-   */
-  bool GetCompressorOn() const;
+  bool GetCompressorOn() const override;
 
-  /**
-   * Set whether the compressor is active.
-   *
-   * @param compressorOn the new value
-   */
-  void SetCompressorOn(bool compressorOn);
+  void SetCompressorOn(bool compressorOn) override;
 
   /**
    * Register a callback to be run whenever the closed loop state changes.
@@ -145,21 +89,21 @@
    * @return the CallbackStore object associated with this callback
    */
   [[nodiscard]] std::unique_ptr<CallbackStore> RegisterPressureSwitchCallback(
-      NotifyCallback callback, bool initialNotify);
+      NotifyCallback callback, bool initialNotify) override;
 
   /**
    * Check the value of the pressure switch.
    *
    * @return the pressure switch value
    */
-  bool GetPressureSwitch() const;
+  bool GetPressureSwitch() const override;
 
   /**
    * Set the value of the pressure switch.
    *
    * @param pressureSwitch the new value
    */
-  void SetPressureSwitch(bool pressureSwitch);
+  void SetPressureSwitch(bool pressureSwitch) override;
 
   /**
    * Register a callback to be run whenever the compressor current changes.
@@ -170,43 +114,26 @@
    */
   [[nodiscard]] std::unique_ptr<CallbackStore>
   RegisterCompressorCurrentCallback(NotifyCallback callback,
-                                    bool initialNotify);
+                                    bool initialNotify) override;
 
   /**
    * Read the compressor current.
    *
    * @return the current of the compressor connected to this module
    */
-  double GetCompressorCurrent() const;
+  double GetCompressorCurrent() const override;
 
   /**
    * Set the compressor current.
    *
    * @param compressorCurrent the new compressor current
    */
-  void SetCompressorCurrent(double compressorCurrent);
+  void SetCompressorCurrent(double compressorCurrent) override;
 
-  /**
-   * Get the current value of all solenoid outputs.
-   *
-   * @return the solenoid outputs (1 bit per output)
-   */
-  uint8_t GetAllSolenoidOutputs() const;
+  uint8_t GetAllSolenoidOutputs() const override;
 
-  /**
-   * Change all of the solenoid outputs.
-   *
-   * @param outputs the new solenoid outputs (1 bit per output)
-   */
-  void SetAllSolenoidOutputs(uint8_t outputs);
+  void SetAllSolenoidOutputs(uint8_t outputs) override;
 
-  /**
-   * Reset all simulation data for this object.
-   */
-  void ResetData();
-
- private:
-  int m_index;
+  void ResetData() override;
 };
-}  // namespace sim
-}  // namespace frc
+}  // namespace frc::sim
diff --git a/wpilibc/src/main/native/include/frc/simulation/DifferentialDrivetrainSim.h b/wpilibc/src/main/native/include/frc/simulation/DifferentialDrivetrainSim.h
index 7c53eb7..c1cc1d7 100644
--- a/wpilibc/src/main/native/include/frc/simulation/DifferentialDrivetrainSim.h
+++ b/wpilibc/src/main/native/include/frc/simulation/DifferentialDrivetrainSim.h
@@ -4,11 +4,11 @@
 
 #pragma once
 
+#include <frc/EigenCore.h>
 #include <frc/kinematics/DifferentialDriveKinematics.h>
 #include <frc/system/LinearSystem.h>
 #include <frc/system/plant/DCMotor.h>
 
-#include <Eigen/Core>
 #include <units/length.h>
 #include <units/moment_of_inertia.h>
 #include <units/time.h>
@@ -80,7 +80,7 @@
    * @param u The input vector.
    * @return The normalized input.
    */
-  Eigen::Vector<double, 2> ClampInput(const Eigen::Vector<double, 2>& u);
+  Vectord<2> ClampInput(const Vectord<2>& u);
 
   /**
    * Sets the applied voltage to the drivetrain. Note that positive voltage must
@@ -178,7 +178,7 @@
    *
    * @param state The state.
    */
-  void SetState(const Eigen::Vector<double, 7>& state);
+  void SetState(const Vectord<7>& state);
 
   /**
    * Sets the system pose.
@@ -187,8 +187,7 @@
    */
   void SetPose(const frc::Pose2d& pose);
 
-  Eigen::Vector<double, 7> Dynamics(const Eigen::Vector<double, 7>& x,
-                                    const Eigen::Vector<double, 2>& u);
+  Vectord<7> Dynamics(const Vectord<7>& x, const Vectord<2>& u);
 
   class State {
    public:
@@ -256,7 +255,7 @@
   static DifferentialDrivetrainSim CreateKitbotSim(
       frc::DCMotor motor, double gearing, units::meter_t wheelSize,
       const std::array<double, 7>& measurementStdDevs = {}) {
-    // MOI estimation -- note that I = m r^2 for point masses
+    // MOI estimation -- note that I = mr² for point masses
     units::kilogram_square_meter_t batteryMoi = 12.5_lb * 10_in * 10_in;
     units::kilogram_square_meter_t gearboxMoi = (2.8_lb + 2.0_lb) *
                                                 2  // CIM plus toughbox per side
@@ -301,7 +300,7 @@
   /**
    * Returns the current output vector y.
    */
-  Eigen::Vector<double, 7> GetOutput() const;
+  Vectord<7> GetOutput() const;
 
   /**
    * Returns an element of the state vector. Note that this will not include
@@ -314,7 +313,7 @@
   /**
    * Returns the current state vector x. Note that this will not include noise!
    */
-  Eigen::Vector<double, 7> GetState() const;
+  Vectord<7> GetState() const;
 
   LinearSystem<2, 2, 2> m_plant;
   units::meter_t m_rb;
@@ -325,9 +324,9 @@
   double m_originalGearing;
   double m_currentGearing;
 
-  Eigen::Vector<double, 7> m_x;
-  Eigen::Vector<double, 2> m_u;
-  Eigen::Vector<double, 7> m_y;
+  Vectord<7> m_x;
+  Vectord<2> m_u;
+  Vectord<7> m_y;
   std::array<double, 7> m_measurementStdDevs;
 };
 }  // namespace frc::sim
diff --git a/wpilibc/src/main/native/include/frc/simulation/DoubleSolenoidSim.h b/wpilibc/src/main/native/include/frc/simulation/DoubleSolenoidSim.h
new file mode 100644
index 0000000..eaa697f
--- /dev/null
+++ b/wpilibc/src/main/native/include/frc/simulation/DoubleSolenoidSim.h
@@ -0,0 +1,33 @@
+// 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 "frc/DoubleSolenoid.h"
+#include "frc/PneumaticsModuleType.h"
+#include "frc/simulation/PneumaticsBaseSim.h"
+
+namespace frc::sim {
+
+class DoubleSolenoidSim {
+ public:
+  DoubleSolenoidSim(std::shared_ptr<PneumaticsBaseSim> moduleSim, int fwd,
+                    int rev);
+  DoubleSolenoidSim(int module, PneumaticsModuleType type, int fwd, int rev);
+  DoubleSolenoidSim(PneumaticsModuleType type, int fwd, int rev);
+
+  DoubleSolenoid::Value Get() const;
+  void Set(DoubleSolenoid::Value output);
+
+  std::shared_ptr<PneumaticsBaseSim> GetModuleSim() const;
+
+ private:
+  std::shared_ptr<PneumaticsBaseSim> m_module;
+  int m_fwd;
+  int m_rev;
+};
+
+}  // namespace frc::sim
diff --git a/wpilibc/src/main/native/include/frc/simulation/DriverStationSim.h b/wpilibc/src/main/native/include/frc/simulation/DriverStationSim.h
index 412a921..232c123 100644
--- a/wpilibc/src/main/native/include/frc/simulation/DriverStationSim.h
+++ b/wpilibc/src/main/native/include/frc/simulation/DriverStationSim.h
@@ -337,7 +337,7 @@
    * @param stick The joystick number
    * @param name The value of name
    */
-  static void SetJoystickName(int stick, const char* name);
+  static void SetJoystickName(int stick, std::string_view name);
 
   /**
    * Sets the types of Axes for a joystick.
@@ -353,14 +353,14 @@
    *
    * @param message the game specific message
    */
-  static void SetGameSpecificMessage(const char* message);
+  static void SetGameSpecificMessage(std::string_view message);
 
   /**
    * Sets the event name.
    *
    * @param name the event name
    */
-  static void SetEventName(const char* name);
+  static void SetEventName(std::string_view name);
 
   /**
    * Sets the match type.
diff --git a/wpilibc/src/main/native/include/frc/simulation/ElevatorSim.h b/wpilibc/src/main/native/include/frc/simulation/ElevatorSim.h
index 14b6d2d..cf40810 100644
--- a/wpilibc/src/main/native/include/frc/simulation/ElevatorSim.h
+++ b/wpilibc/src/main/native/include/frc/simulation/ElevatorSim.h
@@ -31,11 +31,13 @@
    *                           wrapped around.
    * @param minHeight          The minimum allowed height of the elevator.
    * @param maxHeight          The maximum allowed height of the elevator.
+   * @param simulateGravity    Whether gravity should be simulated or not.
    * @param measurementStdDevs The standard deviation of the measurements.
    */
   ElevatorSim(const LinearSystem<2, 1, 1>& plant, const DCMotor& gearbox,
               double gearing, units::meter_t drumRadius,
               units::meter_t minHeight, units::meter_t maxHeight,
+              bool simulateGravity,
               const std::array<double, 1>& measurementStdDevs = {0.0});
 
   /**
@@ -50,11 +52,13 @@
    *                           wrapped around.
    * @param minHeight          The minimum allowed height of the elevator.
    * @param maxHeight          The maximum allowed height of the elevator.
+   * @param simulateGravity    Whether gravity should be simulated or not.
    * @param measurementStdDevs The standard deviation of the measurements.
    */
   ElevatorSim(const DCMotor& gearbox, double gearing,
               units::kilogram_t carriageMass, units::meter_t drumRadius,
               units::meter_t minHeight, units::meter_t maxHeight,
+              bool simulateGravity,
               const std::array<double, 1>& measurementStdDevs = {0.0});
 
   /**
@@ -123,9 +127,8 @@
    * @param u           The system inputs (voltage).
    * @param dt          The time difference between controller updates.
    */
-  Eigen::Vector<double, 2> UpdateX(const Eigen::Vector<double, 2>& currentXhat,
-                                   const Eigen::Vector<double, 1>& u,
-                                   units::second_t dt) override;
+  Vectord<2> UpdateX(const Vectord<2>& currentXhat, const Vectord<1>& u,
+                     units::second_t dt) override;
 
  private:
   DCMotor m_gearbox;
@@ -133,5 +136,6 @@
   units::meter_t m_minHeight;
   units::meter_t m_maxHeight;
   double m_gearing;
+  bool m_simulateGravity;
 };
 }  // namespace frc::sim
diff --git a/wpilibc/src/main/native/include/frc/simulation/LinearSystemSim.h b/wpilibc/src/main/native/include/frc/simulation/LinearSystemSim.h
index 731fad3..cfc87b2 100644
--- a/wpilibc/src/main/native/include/frc/simulation/LinearSystemSim.h
+++ b/wpilibc/src/main/native/include/frc/simulation/LinearSystemSim.h
@@ -6,10 +6,10 @@
 
 #include <array>
 
-#include <Eigen/Core>
 #include <units/current.h>
 #include <units/time.h>
 
+#include "frc/EigenCore.h"
 #include "frc/RobotController.h"
 #include "frc/StateSpaceUtil.h"
 #include "frc/system/LinearSystem.h"
@@ -40,9 +40,9 @@
       const LinearSystem<States, Inputs, Outputs>& system,
       const std::array<double, Outputs>& measurementStdDevs = {})
       : m_plant(system), m_measurementStdDevs(measurementStdDevs) {
-    m_x = Eigen::Vector<double, States>::Zero();
-    m_y = Eigen::Vector<double, Outputs>::Zero();
-    m_u = Eigen::Vector<double, Inputs>::Zero();
+    m_x = Vectord<States>::Zero();
+    m_y = Vectord<Outputs>::Zero();
+    m_u = Vectord<Inputs>::Zero();
   }
 
   virtual ~LinearSystemSim() = default;
@@ -71,7 +71,7 @@
    *
    * @return The current output of the plant.
    */
-  const Eigen::Vector<double, Outputs>& GetOutput() const { return m_y; }
+  const Vectord<Outputs>& GetOutput() const { return m_y; }
 
   /**
    * Returns an element of the current output of the plant.
@@ -86,7 +86,7 @@
    *
    * @param u The system inputs.
    */
-  void SetInput(const Eigen::Vector<double, Inputs>& u) { m_u = ClampInput(u); }
+  void SetInput(const Vectord<Inputs>& u) { m_u = ClampInput(u); }
 
   /*
    * Sets the system inputs.
@@ -104,7 +104,7 @@
    *
    * @param state The new state.
    */
-  void SetState(const Eigen::Vector<double, States>& state) { m_x = state; }
+  void SetState(const Vectord<States>& state) { m_x = state; }
 
   /**
    * Returns the current drawn by this simulated system. Override this method to
@@ -112,9 +112,7 @@
    *
    * @return The current drawn by this simulated mechanism.
    */
-  virtual units::ampere_t GetCurrentDraw() const {
-    return units::ampere_t(0.0);
-  }
+  virtual units::ampere_t GetCurrentDraw() const { return 0_A; }
 
  protected:
   /**
@@ -124,9 +122,9 @@
    * @param u           The system inputs (usually voltage).
    * @param dt          The time difference between controller updates.
    */
-  virtual Eigen::Vector<double, States> UpdateX(
-      const Eigen::Vector<double, States>& currentXhat,
-      const Eigen::Vector<double, Inputs>& u, units::second_t dt) {
+  virtual Vectord<States> UpdateX(const Vectord<States>& currentXhat,
+                                  const Vectord<Inputs>& u,
+                                  units::second_t dt) {
     return m_plant.CalculateX(currentXhat, u, dt);
   }
 
@@ -137,16 +135,16 @@
    * @param u          The input vector.
    * @return The normalized input.
    */
-  Eigen::Vector<double, Inputs> ClampInput(Eigen::Vector<double, Inputs> u) {
+  Vectord<Inputs> ClampInput(Vectord<Inputs> u) {
     return frc::DesaturateInputVector<Inputs>(
         u, frc::RobotController::GetInputVoltage());
   }
 
   LinearSystem<States, Inputs, Outputs> m_plant;
 
-  Eigen::Vector<double, States> m_x;
-  Eigen::Vector<double, Outputs> m_y;
-  Eigen::Vector<double, Inputs> m_u;
+  Vectord<States> m_x;
+  Vectord<Outputs> m_y;
+  Vectord<Inputs> m_u;
   std::array<double, Outputs> m_measurementStdDevs;
 };
 }  // namespace frc::sim
diff --git a/wpilibc/src/main/native/include/frc/simulation/PWMSim.h b/wpilibc/src/main/native/include/frc/simulation/PWMSim.h
index 97e9b1f..8b4156e 100644
--- a/wpilibc/src/main/native/include/frc/simulation/PWMSim.h
+++ b/wpilibc/src/main/native/include/frc/simulation/PWMSim.h
@@ -11,6 +11,7 @@
 namespace frc {
 
 class PWM;
+class PWMMotorController;
 
 namespace sim {
 
@@ -27,6 +28,13 @@
   explicit PWMSim(const PWM& pwm);
 
   /**
+   * Constructs from a PWMMotorController object.
+   *
+   * @param motorctrl PWMMotorController to simulate
+   */
+  explicit PWMSim(const PWMMotorController& motorctrl);
+
+  /**
    * Constructs from a PWM channel number.
    *
    * @param channel Channel number
diff --git a/wpilibc/src/main/native/include/frc/simulation/PneumaticsBaseSim.h b/wpilibc/src/main/native/include/frc/simulation/PneumaticsBaseSim.h
new file mode 100644
index 0000000..11110e3
--- /dev/null
+++ b/wpilibc/src/main/native/include/frc/simulation/PneumaticsBaseSim.h
@@ -0,0 +1,183 @@
+// 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 "frc/PneumaticsBase.h"
+#include "frc/PneumaticsModuleType.h"
+#include "frc/simulation/CallbackStore.h"
+
+namespace frc::sim {
+
+class PneumaticsBaseSim {
+ public:
+  virtual ~PneumaticsBaseSim() = default;
+
+  static std::shared_ptr<PneumaticsBaseSim> GetForType(
+      int module, PneumaticsModuleType type);
+
+  /**
+   * Check whether the PCM/PH has been initialized.
+   *
+   * @return true if initialized
+   */
+  virtual bool GetInitialized() const = 0;
+
+  /**
+   * Define whether the PCM/PH has been initialized.
+   *
+   * @param initialized true for initialized
+   */
+  virtual void SetInitialized(bool initialized) = 0;
+
+  /**
+   * Register a callback to be run when the PCM/PH is initialized.
+   *
+   * @param callback the callback
+   * @param initialNotify whether to run the callback with the initial state
+   * @return the {@link CallbackStore} object associated with this callback.
+   * Save a reference to this object; it being deconstructed cancels the
+   * callback.
+   */
+  [[nodiscard]] virtual std::unique_ptr<CallbackStore>
+  RegisterInitializedCallback(NotifyCallback callback, bool initialNotify) = 0;
+
+  /**
+   * Check if the compressor is on.
+   *
+   * @return true if the compressor is active
+   */
+  virtual bool GetCompressorOn() const = 0;
+
+  /**
+   * Set whether the compressor is active.
+   *
+   * @param compressorOn the new value
+   */
+  virtual void SetCompressorOn(bool compressorOn) = 0;
+
+  /**
+   * Register a callback to be run when the compressor activates.
+   *
+   * @param callback the callback
+   * @param initialNotify whether to run the callback with the initial state
+   * @return the {@link CallbackStore} object associated with this callback.
+   * Save a reference to this object; it being deconstructed cancels the
+   * callback.
+   */
+  [[nodiscard]] virtual std::unique_ptr<CallbackStore>
+  RegisterCompressorOnCallback(NotifyCallback callback, bool initialNotify) = 0;
+
+  /**
+   * Check the solenoid output on a specific channel.
+   *
+   * @param channel the channel to check
+   * @return the solenoid output
+   */
+  virtual bool GetSolenoidOutput(int channel) const = 0;
+
+  /**
+   * Change the solenoid output on a specific channel.
+   *
+   * @param channel the channel to check
+   * @param solenoidOutput the new solenoid output
+   */
+  virtual void SetSolenoidOutput(int channel, bool solenoidOutput) = 0;
+
+  /**
+   * Register a callback to be run when the solenoid output on a channel
+   * changes.
+   *
+   * @param channel the channel to monitor
+   * @param callback the callback
+   * @param initialNotify should the callback be run with the initial value
+   * @return the {@link CallbackStore} object associated with this callback.
+   * Save a reference to this object; it being deconstructed cancels the
+   * callback.
+   */
+  [[nodiscard]] virtual std::unique_ptr<CallbackStore>
+  RegisterSolenoidOutputCallback(int channel, NotifyCallback callback,
+                                 bool initialNotify) = 0;
+
+  /**
+   * Check the value of the pressure switch.
+   *
+   * @return the pressure switch value
+   */
+  virtual bool GetPressureSwitch() const = 0;
+
+  /**
+   * Set the value of the pressure switch.
+   *
+   * @param pressureSwitch the new value
+   */
+  virtual void SetPressureSwitch(bool pressureSwitch) = 0;
+
+  /**
+   * Register a callback to be run whenever the pressure switch value changes.
+   *
+   * @param callback the callback
+   * @param initialNotify whether the callback should be called with the initial
+   * value
+   * @return the {@link CallbackStore} object associated with this callback.
+   * Save a reference to this object; it being deconstructed cancels the
+   * callback.
+   */
+  [[nodiscard]] virtual std::unique_ptr<CallbackStore>
+  RegisterPressureSwitchCallback(NotifyCallback callback,
+                                 bool initialNotify) = 0;
+
+  /**
+   * Read the compressor current.
+   *
+   * @return the current of the compressor connected to this module
+   */
+  virtual double GetCompressorCurrent() const = 0;
+
+  /**
+   * Set the compressor current.
+   *
+   * @param compressorCurrent the new compressor current
+   */
+  virtual void SetCompressorCurrent(double compressorCurrent) = 0;
+
+  /**
+   * Register a callback to be run whenever the compressor current changes.
+   *
+   * @param callback the callback
+   * @param initialNotify whether to call the callback with the initial state
+   * @return the {@link CallbackStore} object associated with this callback.
+   * Save a reference to this object; it being deconstructed cancels the
+   * callback.
+   */
+  [[nodiscard]] virtual std::unique_ptr<CallbackStore>
+  RegisterCompressorCurrentCallback(NotifyCallback callback,
+                                    bool initialNotify) = 0;
+
+  /**
+   * Get the current value of all solenoid outputs.
+   *
+   * @return the solenoid outputs (1 bit per output)
+   */
+  virtual uint8_t GetAllSolenoidOutputs() const = 0;
+
+  /**
+   * Change all of the solenoid outputs.
+   *
+   * @param outputs the new solenoid outputs (1 bit per output)
+   */
+  virtual void SetAllSolenoidOutputs(uint8_t outputs) = 0;
+
+  /** Reset all simulation data for this object. */
+  virtual void ResetData() = 0;
+
+ protected:
+  const int m_index;
+  explicit PneumaticsBaseSim(const PneumaticsBase& module);
+  explicit PneumaticsBaseSim(const int index);
+};
+
+}  // namespace frc::sim
diff --git a/wpilibc/src/main/native/include/frc/simulation/REVPHSim.h b/wpilibc/src/main/native/include/frc/simulation/REVPHSim.h
index e5f4efe..fe305ba 100644
--- a/wpilibc/src/main/native/include/frc/simulation/REVPHSim.h
+++ b/wpilibc/src/main/native/include/frc/simulation/REVPHSim.h
@@ -8,6 +8,7 @@
 
 #include "frc/PneumaticsBase.h"
 #include "frc/simulation/CallbackStore.h"
+#include "frc/simulation/PneumaticsBaseSim.h"
 
 namespace frc {
 
@@ -18,7 +19,7 @@
 /**
  * Class to control a simulated Pneumatic Control Module (PCM).
  */
-class REVPHSim {
+class REVPHSim : public PneumaticsBaseSim {
  public:
   /**
    * Constructs with the default PCM module number (CAN ID).
@@ -34,81 +35,38 @@
 
   explicit REVPHSim(const PneumaticsBase& pneumatics);
 
-  /**
-   * Register a callback to be run when a solenoid is initialized on a channel.
-   *
-   * @param callback the callback
-   * @param initialNotify should the callback be run with the initial state
-   * @return the CallbackStore object associated with this callback
-   */
+  ~REVPHSim() override = default;
+
   [[nodiscard]] std::unique_ptr<CallbackStore> RegisterInitializedCallback(
-      NotifyCallback callback, bool initialNotify);
+      NotifyCallback callback, bool initialNotify) override;
 
-  /**
-   * Check if a solenoid has been initialized on a specific channel.
-   *
-   * @return true if initialized
-   */
-  bool GetInitialized() const;
+  bool GetInitialized() const override;
 
-  /**
-   * Define whether a solenoid has been initialized on a specific channel.
-   *
-   * @param solenoidInitialized is there a solenoid initialized on that channel
-   */
-  void SetInitialized(bool solenoidInitialized);
+  void SetInitialized(bool solenoidInitialized) override;
 
-  /**
-   * Register a callback to be run when the solenoid output on a channel
-   * changes.
-   *
-   * @param channel the channel to monitor
-   * @param callback the callback
-   * @param initialNotify should the callback be run with the initial value
-   * @return the CallbackStore object associated with this callback
-   */
   [[nodiscard]] std::unique_ptr<CallbackStore> RegisterSolenoidOutputCallback(
-      int channel, NotifyCallback callback, bool initialNotify);
+      int channel, NotifyCallback callback, bool initialNotify) override;
 
-  /**
-   * Check the solenoid output on a specific channel.
-   *
-   * @param channel the channel to check
-   * @return the solenoid output
-   */
-  bool GetSolenoidOutput(int channel) const;
+  bool GetSolenoidOutput(int channel) const override;
 
-  /**
-   * Change the solenoid output on a specific channel.
-   *
-   * @param channel the channel to check
-   * @param solenoidOutput the new solenoid output
-   */
-  void SetSolenoidOutput(int channel, bool solenoidOutput);
+  void SetSolenoidOutput(int channel, bool solenoidOutput) override;
 
-  /**
-   * Register a callback to be run when the compressor activates.
-   *
-   * @param callback the callback
-   * @param initialNotify whether to run the callback with the initial state
-   * @return the CallbackStore object associated with this callback
-   */
   [[nodiscard]] std::unique_ptr<CallbackStore> RegisterCompressorOnCallback(
-      NotifyCallback callback, bool initialNotify);
+      NotifyCallback callback, bool initialNotify) override;
 
   /**
    * Check if the compressor is on.
    *
    * @return true if the compressor is active
    */
-  bool GetCompressorOn() const;
+  bool GetCompressorOn() const override;
 
   /**
    * Set whether the compressor is active.
    *
    * @param compressorOn the new value
    */
-  void SetCompressorOn(bool compressorOn);
+  void SetCompressorOn(bool compressorOn) override;
 
   /**
    * Register a callback to be run whenever the closed loop state changes.
@@ -136,77 +94,26 @@
    */
   void SetCompressorConfigType(int compressorConfigType);
 
-  /**
-   * Register a callback to be run whenever the pressure switch value changes.
-   *
-   * @param callback the callback
-   * @param initialNotify whether the callback should be called with the
-   *                      initial value
-   * @return the CallbackStore object associated with this callback
-   */
   [[nodiscard]] std::unique_ptr<CallbackStore> RegisterPressureSwitchCallback(
-      NotifyCallback callback, bool initialNotify);
+      NotifyCallback callback, bool initialNotify) override;
 
-  /**
-   * Check the value of the pressure switch.
-   *
-   * @return the pressure switch value
-   */
-  bool GetPressureSwitch() const;
+  bool GetPressureSwitch() const override;
 
-  /**
-   * Set the value of the pressure switch.
-   *
-   * @param pressureSwitch the new value
-   */
-  void SetPressureSwitch(bool pressureSwitch);
+  void SetPressureSwitch(bool pressureSwitch) override;
 
-  /**
-   * Register a callback to be run whenever the compressor current changes.
-   *
-   * @param callback the callback
-   * @param initialNotify whether to call the callback with the initial state
-   * @return the CallbackStore object associated with this callback
-   */
   [[nodiscard]] std::unique_ptr<CallbackStore>
   RegisterCompressorCurrentCallback(NotifyCallback callback,
-                                    bool initialNotify);
+                                    bool initialNotify) override;
 
-  /**
-   * Read the compressor current.
-   *
-   * @return the current of the compressor connected to this module
-   */
-  double GetCompressorCurrent() const;
+  double GetCompressorCurrent() const override;
 
-  /**
-   * Set the compressor current.
-   *
-   * @param compressorCurrent the new compressor current
-   */
-  void SetCompressorCurrent(double compressorCurrent);
+  void SetCompressorCurrent(double compressorCurrent) override;
 
-  /**
-   * Get the current value of all solenoid outputs.
-   *
-   * @return the solenoid outputs (1 bit per output)
-   */
-  uint8_t GetAllSolenoidOutputs() const;
+  uint8_t GetAllSolenoidOutputs() const override;
 
-  /**
-   * Change all of the solenoid outputs.
-   *
-   * @param outputs the new solenoid outputs (1 bit per output)
-   */
-  void SetAllSolenoidOutputs(uint8_t outputs);
+  void SetAllSolenoidOutputs(uint8_t outputs) override;
 
-  /**
-   * Reset all simulation data for this object.
-   */
-  void ResetData();
-
- private:
-  int m_index;
+  void ResetData() override;
 };
 }  // namespace sim
 }  // namespace frc
diff --git a/wpilibc/src/main/native/include/frc/simulation/RoboRioSim.h b/wpilibc/src/main/native/include/frc/simulation/RoboRioSim.h
index 1095106..ee959b66 100644
--- a/wpilibc/src/main/native/include/frc/simulation/RoboRioSim.h
+++ b/wpilibc/src/main/native/include/frc/simulation/RoboRioSim.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <memory>
+#include <string>
 
 #include <units/current.h>
 #include <units/voltage.h>
@@ -419,6 +420,34 @@
   static void SetBrownoutVoltage(units::volt_t brownoutVoltage);
 
   /**
+   * Get the serial number.
+   *
+   * @return The serial number.
+   */
+  static std::string GetSerialNumber();
+
+  /**
+   * Set the serial number.
+   *
+   * @param serialNumber The serial number.
+   */
+  static void SetSerialNumber(std::string_view serialNumber);
+
+  /**
+   * Get the comments.
+   *
+   * @return The comments.
+   */
+  static std::string GetComments();
+
+  /**
+   * Set the comments.
+   *
+   * @param comments The comments.
+   */
+  static void SetComments(std::string_view comments);
+
+  /**
    * Reset all simulation data.
    */
   static void ResetData();
diff --git a/wpilibc/src/main/native/include/frc/simulation/SingleJointedArmSim.h b/wpilibc/src/main/native/include/frc/simulation/SingleJointedArmSim.h
index 4ff29cc..57ad6b1 100644
--- a/wpilibc/src/main/native/include/frc/simulation/SingleJointedArmSim.h
+++ b/wpilibc/src/main/native/include/frc/simulation/SingleJointedArmSim.h
@@ -30,14 +30,14 @@
    * @param armLength          The length of the arm.
    * @param minAngle           The minimum angle that the arm is capable of.
    * @param maxAngle           The maximum angle that the arm is capable of.
-   * @param mass               The mass of the arm.
-   * @param measurementStdDevs The standard deviations of the measurements.
+   * @param armMass            The mass of the arm.
    * @param simulateGravity    Whether gravity should be simulated or not.
+   * @param measurementStdDevs The standard deviations of the measurements.
    */
   SingleJointedArmSim(const LinearSystem<2, 1, 1>& system,
                       const DCMotor& gearbox, double gearing,
                       units::meter_t armLength, units::radian_t minAngle,
-                      units::radian_t maxAngle, units::kilogram_t mass,
+                      units::radian_t maxAngle, units::kilogram_t armMass,
                       bool simulateGravity,
                       const std::array<double, 1>& measurementStdDevs = {0.0});
   /**
@@ -52,8 +52,8 @@
    * @param minAngle           The minimum angle that the arm is capable of.
    * @param maxAngle           The maximum angle that the arm is capable of.
    * @param mass               The mass of the arm.
-   * @param measurementStdDevs The standard deviation of the measurement noise.
    * @param simulateGravity    Whether gravity should be simulated or not.
+   * @param measurementStdDevs The standard deviation of the measurement noise.
    */
   SingleJointedArmSim(const DCMotor& gearbox, double gearing,
                       units::kilogram_square_meter_t moi,
@@ -142,15 +142,14 @@
    * @param u           The system inputs (voltage).
    * @param dt          The time difference between controller updates.
    */
-  Eigen::Vector<double, 2> UpdateX(const Eigen::Vector<double, 2>& currentXhat,
-                                   const Eigen::Vector<double, 1>& u,
-                                   units::second_t dt) override;
+  Vectord<2> UpdateX(const Vectord<2>& currentXhat, const Vectord<1>& u,
+                     units::second_t dt) override;
 
  private:
   units::meter_t m_r;
   units::radian_t m_minAngle;
   units::radian_t m_maxAngle;
-  units::kilogram_t m_mass;
+  units::kilogram_t m_armMass;
   const DCMotor m_gearbox;
   double m_gearing;
   bool m_simulateGravity;
diff --git a/wpilibc/src/main/native/include/frc/simulation/SolenoidSim.h b/wpilibc/src/main/native/include/frc/simulation/SolenoidSim.h
new file mode 100644
index 0000000..66e3589
--- /dev/null
+++ b/wpilibc/src/main/native/include/frc/simulation/SolenoidSim.h
@@ -0,0 +1,42 @@
+// 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 "frc/PneumaticsModuleType.h"
+#include "frc/simulation/PneumaticsBaseSim.h"
+
+namespace frc::sim {
+
+class SolenoidSim {
+ public:
+  SolenoidSim(std::shared_ptr<PneumaticsBaseSim> moduleSim, int channel);
+  SolenoidSim(int module, PneumaticsModuleType type, int channel);
+  SolenoidSim(PneumaticsModuleType type, int channel);
+
+  bool GetOutput() const;
+  void SetOutput(bool output);
+
+  /**
+   * Register a callback to be run when the output of this solenoid has changed.
+   *
+   * @param callback the callback
+   * @param initialNotify whether to run the callback with the initial state
+   * @return the {@link CallbackStore} object associated with this callback.
+   * Save a reference to this object; it being deconstructed cancels the
+   * callback.
+   */
+  [[nodiscard]] virtual std::unique_ptr<CallbackStore> RegisterOutputCallback(
+      NotifyCallback callback, bool initialNotify);
+
+  std::shared_ptr<PneumaticsBaseSim> GetModuleSim() const;
+
+ private:
+  std::shared_ptr<PneumaticsBaseSim> m_module;
+  int m_channel;
+};
+
+}  // namespace frc::sim
diff --git a/wpilibc/src/main/native/include/frc/smartdashboard/FieldObject2d.h b/wpilibc/src/main/native/include/frc/smartdashboard/FieldObject2d.h
index 2e41d84..f55938e 100644
--- a/wpilibc/src/main/native/include/frc/smartdashboard/FieldObject2d.h
+++ b/wpilibc/src/main/native/include/frc/smartdashboard/FieldObject2d.h
@@ -5,16 +5,16 @@
 #pragma once
 
 #include <initializer_list>
+#include <span>
 #include <string>
 #include <string_view>
 #include <utility>
 #include <vector>
 
-#include <networktables/NetworkTableEntry.h>
+#include <networktables/DoubleArrayTopic.h>
 #include <units/length.h>
 #include <wpi/SmallVector.h>
 #include <wpi/mutex.h>
-#include <wpi/span.h>
 
 #include "frc/geometry/Pose2d.h"
 #include "frc/geometry/Rotation2d.h"
@@ -66,7 +66,7 @@
    *
    * @param poses array of 2D poses
    */
-  void SetPoses(wpi::span<const Pose2d> poses);
+  void SetPoses(std::span<const Pose2d> poses);
 
   /**
    * Set multiple poses from an array of Pose objects.
@@ -97,7 +97,7 @@
    * @param out output SmallVector to hold 2D poses
    * @return span referring to output SmallVector
    */
-  wpi::span<const Pose2d> GetPoses(wpi::SmallVectorImpl<Pose2d>& out) const;
+  std::span<const Pose2d> GetPoses(wpi::SmallVectorImpl<Pose2d>& out) const;
 
  private:
   void UpdateEntry(bool setDefault = false);
@@ -105,7 +105,7 @@
 
   mutable wpi::mutex m_mutex;
   std::string m_name;
-  nt::NetworkTableEntry m_entry;
+  nt::DoubleArrayEntry m_entry;
   mutable wpi::SmallVector<Pose2d, 1> m_poses;
 };
 
diff --git a/wpilibc/src/main/native/include/frc/smartdashboard/Mechanism2d.h b/wpilibc/src/main/native/include/frc/smartdashboard/Mechanism2d.h
index cbe25be..c71bcaf 100644
--- a/wpilibc/src/main/native/include/frc/smartdashboard/Mechanism2d.h
+++ b/wpilibc/src/main/native/include/frc/smartdashboard/Mechanism2d.h
@@ -7,8 +7,10 @@
 #include <memory>
 #include <string>
 
+#include <networktables/DoubleArrayTopic.h>
 #include <networktables/NTSendable.h>
-#include <networktables/NetworkTableEntry.h>
+#include <networktables/NetworkTable.h>
+#include <networktables/StringTopic.h>
 #include <wpi/StringMap.h>
 #include <wpi/mutex.h>
 #include <wpi/sendable/SendableHelper.h>
@@ -79,9 +81,11 @@
  private:
   double m_width;
   double m_height;
-  char m_color[10];
+  std::string m_color;
   mutable wpi::mutex m_mutex;
   std::shared_ptr<nt::NetworkTable> m_table;
   wpi::StringMap<std::unique_ptr<MechanismRoot2d>> m_roots;
+  nt::DoubleArrayPublisher m_dimsPub;
+  nt::StringPublisher m_colorPub;
 };
 }  // namespace frc
diff --git a/wpilibc/src/main/native/include/frc/smartdashboard/MechanismLigament2d.h b/wpilibc/src/main/native/include/frc/smartdashboard/MechanismLigament2d.h
index f1bebb9..f9c86fc 100644
--- a/wpilibc/src/main/native/include/frc/smartdashboard/MechanismLigament2d.h
+++ b/wpilibc/src/main/native/include/frc/smartdashboard/MechanismLigament2d.h
@@ -7,7 +7,8 @@
 #include <memory>
 #include <string_view>
 
-#include <networktables/NetworkTableEntry.h>
+#include <networktables/DoubleTopic.h>
+#include <networktables/StringTopic.h>
 #include <units/angle.h>
 
 #include "frc/smartdashboard/MechanismObject2d.h"
@@ -89,14 +90,14 @@
   void UpdateEntries(std::shared_ptr<nt::NetworkTable> table) override;
 
  private:
-  void Flush();
+  nt::StringPublisher m_typePub;
   double m_length;
-  nt::NetworkTableEntry m_lengthEntry;
+  nt::DoubleEntry m_lengthEntry;
   double m_angle;
-  nt::NetworkTableEntry m_angleEntry;
+  nt::DoubleEntry m_angleEntry;
   double m_weight;
-  nt::NetworkTableEntry m_weightEntry;
+  nt::DoubleEntry m_weightEntry;
   char m_color[10];
-  nt::NetworkTableEntry m_colorEntry;
+  nt::StringEntry m_colorEntry;
 };
 }  // namespace frc
diff --git a/wpilibc/src/main/native/include/frc/smartdashboard/MechanismRoot2d.h b/wpilibc/src/main/native/include/frc/smartdashboard/MechanismRoot2d.h
index 5072547..37e0f7b 100644
--- a/wpilibc/src/main/native/include/frc/smartdashboard/MechanismRoot2d.h
+++ b/wpilibc/src/main/native/include/frc/smartdashboard/MechanismRoot2d.h
@@ -7,7 +7,7 @@
 #include <memory>
 #include <string_view>
 
-#include <networktables/NetworkTableEntry.h>
+#include <networktables/DoubleTopic.h>
 
 #include "MechanismObject2d.h"
 
@@ -48,7 +48,7 @@
   inline void Flush();
   double m_x;
   double m_y;
-  nt::NetworkTableEntry m_xEntry;
-  nt::NetworkTableEntry m_yEntry;
+  nt::DoublePublisher m_xPub;
+  nt::DoublePublisher m_yPub;
 };
 }  // namespace frc
diff --git a/wpilibc/src/main/native/include/frc/smartdashboard/SendableBuilderImpl.h b/wpilibc/src/main/native/include/frc/smartdashboard/SendableBuilderImpl.h
index 36830f6..4bd4289 100644
--- a/wpilibc/src/main/native/include/frc/smartdashboard/SendableBuilderImpl.h
+++ b/wpilibc/src/main/native/include/frc/smartdashboard/SendableBuilderImpl.h
@@ -6,17 +6,18 @@
 
 #include <functional>
 #include <memory>
+#include <span>
 #include <string>
 #include <string_view>
 #include <utility>
 #include <vector>
 
+#include <networktables/BooleanTopic.h>
 #include <networktables/NTSendableBuilder.h>
 #include <networktables/NetworkTable.h>
-#include <networktables/NetworkTableEntry.h>
-#include <networktables/NetworkTableValue.h>
+#include <networktables/StringTopic.h>
+#include <wpi/FunctionExtras.h>
 #include <wpi/SmallVector.h>
-#include <wpi/span.h>
 
 namespace frc {
 
@@ -54,7 +55,8 @@
   bool IsActuator() const;
 
   /**
-   * Update the network table values by calling the getters for all properties.
+   * Synchronize with network table values by calling the getters for all
+   * properties and setters when the network table value has changed.
    */
   void Update() override;
 
@@ -88,12 +90,18 @@
   void SetSmartDashboardType(std::string_view type) override;
   void SetActuator(bool value) override;
   void SetSafeState(std::function<void()> func) override;
-  void SetUpdateTable(std::function<void()> func) override;
-  nt::NetworkTableEntry GetEntry(std::string_view key) override;
+  void SetUpdateTable(wpi::unique_function<void()> func) override;
+  nt::Topic GetTopic(std::string_view key) override;
 
   void AddBooleanProperty(std::string_view key, std::function<bool()> getter,
                           std::function<void(bool)> setter) override;
 
+  void AddIntegerProperty(std::string_view key, std::function<int64_t()> getter,
+                          std::function<void(int64_t)> setter) override;
+
+  void AddFloatProperty(std::string_view key, std::function<float()> getter,
+                        std::function<void(float)> setter) override;
+
   void AddDoubleProperty(std::string_view key, std::function<double()> getter,
                          std::function<void(double)> setter) override;
 
@@ -103,22 +111,28 @@
 
   void AddBooleanArrayProperty(
       std::string_view key, std::function<std::vector<int>()> getter,
-      std::function<void(wpi::span<const int>)> setter) override;
+      std::function<void(std::span<const int>)> setter) override;
+
+  void AddIntegerArrayProperty(
+      std::string_view key, std::function<std::vector<int64_t>()> getter,
+      std::function<void(std::span<const int64_t>)> setter) override;
+
+  void AddFloatArrayProperty(
+      std::string_view key, std::function<std::vector<float>()> getter,
+      std::function<void(std::span<const float>)> setter) override;
 
   void AddDoubleArrayProperty(
       std::string_view key, std::function<std::vector<double>()> getter,
-      std::function<void(wpi::span<const double>)> setter) override;
+      std::function<void(std::span<const double>)> setter) override;
 
   void AddStringArrayProperty(
       std::string_view key, std::function<std::vector<std::string>()> getter,
-      std::function<void(wpi::span<const std::string>)> setter) override;
+      std::function<void(std::span<const std::string>)> setter) override;
 
-  void AddRawProperty(std::string_view key, std::function<std::string()> getter,
-                      std::function<void(std::string_view)> setter) override;
-
-  void AddValueProperty(
-      std::string_view key, std::function<std::shared_ptr<nt::Value>()> getter,
-      std::function<void(std::shared_ptr<nt::Value>)> setter) override;
+  void AddRawProperty(
+      std::string_view key, std::string_view typeString,
+      std::function<std::vector<uint8_t>()> getter,
+      std::function<void(std::span<const uint8_t>)> setter) override;
 
   void AddSmallStringProperty(
       std::string_view key,
@@ -127,82 +141,77 @@
 
   void AddSmallBooleanArrayProperty(
       std::string_view key,
-      std::function<wpi::span<const int>(wpi::SmallVectorImpl<int>& buf)>
+      std::function<std::span<const int>(wpi::SmallVectorImpl<int>& buf)>
           getter,
-      std::function<void(wpi::span<const int>)> setter) override;
+      std::function<void(std::span<const int>)> setter) override;
+
+  void AddSmallIntegerArrayProperty(
+      std::string_view key,
+      std::function<
+          std::span<const int64_t>(wpi::SmallVectorImpl<int64_t>& buf)>
+          getter,
+      std::function<void(std::span<const int64_t>)> setter) override;
+
+  void AddSmallFloatArrayProperty(
+      std::string_view key,
+      std::function<std::span<const float>(wpi::SmallVectorImpl<float>& buf)>
+          getter,
+      std::function<void(std::span<const float>)> setter) override;
 
   void AddSmallDoubleArrayProperty(
       std::string_view key,
-      std::function<wpi::span<const double>(wpi::SmallVectorImpl<double>& buf)>
+      std::function<std::span<const double>(wpi::SmallVectorImpl<double>& buf)>
           getter,
-      std::function<void(wpi::span<const double>)> setter) override;
+      std::function<void(std::span<const double>)> setter) override;
 
   void AddSmallStringArrayProperty(
       std::string_view key,
       std::function<
-          wpi::span<const std::string>(wpi::SmallVectorImpl<std::string>& buf)>
+          std::span<const std::string>(wpi::SmallVectorImpl<std::string>& buf)>
           getter,
-      std::function<void(wpi::span<const std::string>)> setter) override;
+      std::function<void(std::span<const std::string>)> setter) override;
 
   void AddSmallRawProperty(
-      std::string_view key,
-      std::function<std::string_view(wpi::SmallVectorImpl<char>& buf)> getter,
-      std::function<void(std::string_view)> setter) override;
+      std::string_view key, std::string_view typeString,
+      std::function<std::span<uint8_t>(wpi::SmallVectorImpl<uint8_t>& buf)>
+          getter,
+      std::function<void(std::span<const uint8_t>)> setter) override;
 
  private:
   struct Property {
-    Property(nt::NetworkTable& table, std::string_view key)
-        : entry(table.GetEntry(key)) {}
-
-    Property(const Property&) = delete;
-    Property& operator=(const Property&) = delete;
-
-    Property(Property&& other) noexcept
-        : entry(other.entry),
-          listener(other.listener),
-          update(std::move(other.update)),
-          createListener(std::move(other.createListener)) {
-      other.entry = nt::NetworkTableEntry();
-      other.listener = 0;
-    }
-
-    Property& operator=(Property&& other) noexcept {
-      entry = other.entry;
-      listener = other.listener;
-      other.entry = nt::NetworkTableEntry();
-      other.listener = 0;
-      update = std::move(other.update);
-      createListener = std::move(other.createListener);
-      return *this;
-    }
-
-    ~Property() { StopListener(); }
-
-    void StartListener() {
-      if (entry && listener == 0 && createListener) {
-        listener = createListener(entry);
-      }
-    }
-
-    void StopListener() {
-      if (entry && listener != 0) {
-        entry.RemoveListener(listener);
-        listener = 0;
-      }
-    }
-
-    nt::NetworkTableEntry entry;
-    NT_EntryListener listener = 0;
-    std::function<void(nt::NetworkTableEntry entry, uint64_t time)> update;
-    std::function<NT_EntryListener(nt::NetworkTableEntry entry)> createListener;
+    virtual ~Property() = default;
+    virtual void Update(bool controllable, int64_t time) = 0;
   };
 
-  std::vector<Property> m_properties;
+  template <typename Topic>
+  struct PropertyImpl : public Property {
+    void Update(bool controllable, int64_t time) override;
+
+    using Publisher = typename Topic::PublisherType;
+    using Subscriber = typename Topic::SubscriberType;
+    Publisher pub;
+    Subscriber sub;
+    std::function<void(Publisher& pub, int64_t time)> updateNetwork;
+    std::function<void(Subscriber& sub)> updateLocal;
+  };
+
+  template <typename Topic, typename Getter, typename Setter>
+  void AddPropertyImpl(Topic topic, Getter getter, Setter setter);
+
+  template <typename T, size_t Size, typename Topic, typename Getter,
+            typename Setter>
+  void AddSmallPropertyImpl(Topic topic, Getter getter, Setter setter);
+
+  std::vector<std::unique_ptr<Property>> m_properties;
   std::function<void()> m_safeState;
-  std::vector<std::function<void()>> m_updateTables;
+  std::vector<wpi::unique_function<void()>> m_updateTables;
   std::shared_ptr<nt::NetworkTable> m_table;
-  nt::NetworkTableEntry m_controllableEntry;
+  bool m_controllable = false;
   bool m_actuator = false;
+
+  nt::BooleanPublisher m_controllablePublisher;
+  nt::StringPublisher m_typePublisher;
+  nt::BooleanPublisher m_actuatorPublisher;
 };
 
 }  // namespace frc
diff --git a/wpilibc/src/main/native/include/frc/smartdashboard/SendableChooser.h b/wpilibc/src/main/native/include/frc/smartdashboard/SendableChooser.h
index b693762..7fe0b59 100644
--- a/wpilibc/src/main/native/include/frc/smartdashboard/SendableChooser.h
+++ b/wpilibc/src/main/native/include/frc/smartdashboard/SendableChooser.h
@@ -8,7 +8,6 @@
 #include <string_view>
 
 #include <wpi/StringMap.h>
-#include <wpi/deprecated.h>
 
 #include "frc/smartdashboard/SendableChooserBase.h"
 
@@ -69,36 +68,6 @@
   void SetDefaultOption(std::string_view name, T object);
 
   /**
-   * Adds the given object to the list of options.
-   *
-   * On the SmartDashboard on the desktop, the object will appear as the given
-   * name.
-   *
-   * @deprecated use AddOption(std::string_view name, T object) instead.
-   *
-   * @param name   the name of the option
-   * @param object the option
-   */
-  WPI_DEPRECATED("use AddOption() instead")
-  void AddObject(std::string_view name, T object) { AddOption(name, object); }
-
-  /**
-   * Add the given object to the list of options and marks it as the default.
-   *
-   * Functionally, this is very close to AddOption() except that it will use
-   * this as the default option if none other is explicitly selected.
-   *
-   * @deprecated use SetDefaultOption(std::string_view name, T object) instead.
-   *
-   * @param name   the name of the option
-   * @param object the option
-   */
-  WPI_DEPRECATED("use SetDefaultOption() instead")
-  void AddDefault(std::string_view name, T object) {
-    SetDefaultOption(name, object);
-  }
-
-  /**
    * Returns a copy of the selected option (a raw pointer U* if T =
    * std::unique_ptr<U> or a std::weak_ptr<U> if T = std::shared_ptr<U>).
    *
diff --git a/wpilibc/src/main/native/include/frc/smartdashboard/SendableChooser.inc b/wpilibc/src/main/native/include/frc/smartdashboard/SendableChooser.inc
index 25e2551..e90542b 100644
--- a/wpilibc/src/main/native/include/frc/smartdashboard/SendableChooser.inc
+++ b/wpilibc/src/main/native/include/frc/smartdashboard/SendableChooser.inc
@@ -48,10 +48,17 @@
 template <class T>
 void SendableChooser<T>::InitSendable(nt::NTSendableBuilder& builder) {
   builder.SetSmartDashboardType("String Chooser");
-  builder.GetEntry(kInstance).SetDouble(m_instance);
+  {
+    std::scoped_lock lock(m_mutex);
+    m_instancePubs.emplace_back(
+        nt::IntegerTopic{builder.GetTopic(kInstance)}.Publish());
+    m_instancePubs.back().Set(m_instance);
+    m_activePubs.emplace_back(
+        nt::StringTopic{builder.GetTopic(kActive)}.Publish());
+  }
   builder.AddStringArrayProperty(
       kOptions,
-      [=] {
+      [=, this] {
         std::vector<std::string> keys;
         for (const auto& choice : m_choices) {
           keys.emplace_back(choice.first());
@@ -66,13 +73,13 @@
       nullptr);
   builder.AddSmallStringProperty(
       kDefault,
-      [=](wpi::SmallVectorImpl<char>&) -> std::string_view {
+      [=, this](wpi::SmallVectorImpl<char>&) -> std::string_view {
         return m_defaultChoice;
       },
       nullptr);
   builder.AddSmallStringProperty(
       kActive,
-      [=](wpi::SmallVectorImpl<char>& buf) -> std::string_view {
+      [=, this](wpi::SmallVectorImpl<char>& buf) -> std::string_view {
         std::scoped_lock lock(m_mutex);
         if (m_haveSelected) {
           buf.assign(m_selected.begin(), m_selected.end());
@@ -82,18 +89,15 @@
         }
       },
       nullptr);
-  {
-    std::scoped_lock lock(m_mutex);
-    m_activeEntries.emplace_back(builder.GetEntry(kActive));
-  }
-  builder.AddStringProperty(kSelected, nullptr, [=](std::string_view val) {
-    std::scoped_lock lock(m_mutex);
-    m_haveSelected = true;
-    m_selected = val;
-    for (auto& entry : m_activeEntries) {
-      entry.SetString(val);
-    }
-  });
+  builder.AddStringProperty(kSelected, nullptr,
+                            [=, this](std::string_view val) {
+                              std::scoped_lock lock(m_mutex);
+                              m_haveSelected = true;
+                              m_selected = val;
+                              for (auto& pub : m_activePubs) {
+                                pub.Set(val);
+                              }
+                            });
 }
 
 template <class T>
diff --git a/wpilibc/src/main/native/include/frc/smartdashboard/SendableChooserBase.h b/wpilibc/src/main/native/include/frc/smartdashboard/SendableChooserBase.h
index 78f891a..b5df73c 100644
--- a/wpilibc/src/main/native/include/frc/smartdashboard/SendableChooserBase.h
+++ b/wpilibc/src/main/native/include/frc/smartdashboard/SendableChooserBase.h
@@ -7,8 +7,9 @@
 #include <atomic>
 #include <string>
 
+#include <networktables/IntegerTopic.h>
 #include <networktables/NTSendable.h>
-#include <networktables/NetworkTableEntry.h>
+#include <networktables/StringTopic.h>
 #include <wpi/SmallVector.h>
 #include <wpi/mutex.h>
 #include <wpi/sendable/SendableHelper.h>
@@ -40,7 +41,8 @@
   std::string m_defaultChoice;
   std::string m_selected;
   bool m_haveSelected = false;
-  wpi::SmallVector<nt::NetworkTableEntry, 2> m_activeEntries;
+  wpi::SmallVector<nt::IntegerPublisher, 2> m_instancePubs;
+  wpi::SmallVector<nt::StringPublisher, 2> m_activePubs;
   wpi::mutex m_mutex;
   int m_instance;
   static std::atomic_int s_instances;
diff --git a/wpilibc/src/main/native/include/frc/smartdashboard/SmartDashboard.h b/wpilibc/src/main/native/include/frc/smartdashboard/SmartDashboard.h
index 47c4a28..aeb4947 100644
--- a/wpilibc/src/main/native/include/frc/smartdashboard/SmartDashboard.h
+++ b/wpilibc/src/main/native/include/frc/smartdashboard/SmartDashboard.h
@@ -5,13 +5,13 @@
 #pragma once
 
 #include <memory>
+#include <span>
 #include <string>
 #include <string_view>
 #include <vector>
 
 #include <networktables/NetworkTableEntry.h>
 #include <networktables/NetworkTableValue.h>
-#include <wpi/span.h>
 
 namespace wpi {
 class Sendable;
@@ -63,39 +63,6 @@
   static bool IsPersistent(std::string_view key);
 
   /**
-   * Sets flags on the specified key in this table. The key can
-   * not be null.
-   *
-   * @param key the key name
-   * @param flags the flags to set (bitmask)
-   */
-  static void SetFlags(std::string_view key, unsigned int flags);
-
-  /**
-   * Clears flags on the specified key in this table. The key can
-   * not be null.
-   *
-   * @param key the key name
-   * @param flags the flags to clear (bitmask)
-   */
-  static void ClearFlags(std::string_view key, unsigned int flags);
-
-  /**
-   * Returns the flags for the specified key.
-   *
-   * @param key the key name
-   * @return the flags, or 0 if the key is not defined
-   */
-  static unsigned int GetFlags(std::string_view key);
-
-  /**
-   * Deletes the specified key in this table.
-   *
-   * @param key the key name
-   */
-  static void Delete(std::string_view key);
-
-  /**
    * Returns an NT Entry mapping to the specified key
    *
    * This is useful if an entry is used often, or is read and then modified.
@@ -249,7 +216,7 @@
    *       std::vector<bool> is special-cased in C++. 0 is false, any
    *       non-zero value is true.
    */
-  static bool PutBooleanArray(std::string_view key, wpi::span<const int> value);
+  static bool PutBooleanArray(std::string_view key, std::span<const int> value);
 
   /**
    * Gets the current value in the table, setting it if it does not exist.
@@ -259,7 +226,7 @@
    * @returns False if the table key exists with a different type
    */
   static bool SetDefaultBooleanArray(std::string_view key,
-                                     wpi::span<const int> defaultValue);
+                                     std::span<const int> defaultValue);
 
   /**
    * Returns the boolean array the key maps to.
@@ -280,7 +247,7 @@
    *       non-zero value is true.
    */
   static std::vector<int> GetBooleanArray(std::string_view key,
-                                          wpi::span<const int> defaultValue);
+                                          std::span<const int> defaultValue);
 
   /**
    * Put a number array in the table.
@@ -290,7 +257,7 @@
    * @return False if the table key already exists with a different type
    */
   static bool PutNumberArray(std::string_view key,
-                             wpi::span<const double> value);
+                             std::span<const double> value);
 
   /**
    * Gets the current value in the table, setting it if it does not exist.
@@ -300,7 +267,7 @@
    * @returns False if the table key exists with a different type
    */
   static bool SetDefaultNumberArray(std::string_view key,
-                                    wpi::span<const double> defaultValue);
+                                    std::span<const double> defaultValue);
 
   /**
    * Returns the number array the key maps to.
@@ -317,7 +284,7 @@
    *       use GetValue() instead.
    */
   static std::vector<double> GetNumberArray(
-      std::string_view key, wpi::span<const double> defaultValue);
+      std::string_view key, std::span<const double> defaultValue);
 
   /**
    * Put a string array in the table.
@@ -327,7 +294,7 @@
    * @return False if the table key already exists with a different type
    */
   static bool PutStringArray(std::string_view key,
-                             wpi::span<const std::string> value);
+                             std::span<const std::string> value);
 
   /**
    * Gets the current value in the table, setting it if it does not exist.
@@ -337,7 +304,7 @@
    * @returns False if the table key exists with a different type
    */
   static bool SetDefaultStringArray(std::string_view key,
-                                    wpi::span<const std::string> defaultValue);
+                                    std::span<const std::string> defaultValue);
 
   /**
    * Returns the string array the key maps to.
@@ -354,7 +321,7 @@
    *       use GetValue() instead.
    */
   static std::vector<std::string> GetStringArray(
-      std::string_view key, wpi::span<const std::string> defaultValue);
+      std::string_view key, std::span<const std::string> defaultValue);
 
   /**
    * Put a raw value (byte array) in the table.
@@ -363,7 +330,7 @@
    * @param value The value that will be assigned.
    * @return False if the table key already exists with a different type
    */
-  static bool PutRaw(std::string_view key, std::string_view value);
+  static bool PutRaw(std::string_view key, std::span<const uint8_t> value);
 
   /**
    * Gets the current value in the table, setting it if it does not exist.
@@ -373,7 +340,7 @@
    * @returns False if the table key exists with a different type
    */
   static bool SetDefaultRaw(std::string_view key,
-                            std::string_view defaultValue);
+                            std::span<const uint8_t> defaultValue);
 
   /**
    * Returns the raw value (byte array) the key maps to.
@@ -389,8 +356,8 @@
    * @note This makes a copy of the raw contents. If the overhead of this is a
    *       concern, use GetValue() instead.
    */
-  static std::string GetRaw(std::string_view key,
-                            std::string_view defaultValue);
+  static std::vector<uint8_t> GetRaw(std::string_view key,
+                                     std::span<const uint8_t> defaultValue);
 
   /**
    * Maps the specified key to the specified complex value (such as an array) in
@@ -403,8 +370,7 @@
    * @param value   the value
    * @return        False if the table key already exists with a different type
    */
-  static bool PutValue(std::string_view keyName,
-                       std::shared_ptr<nt::Value> value);
+  static bool PutValue(std::string_view keyName, const nt::Value& value);
 
   /**
    * Gets the current value in the table, setting it if it does not exist.
@@ -414,7 +380,7 @@
    * @returns False if the table key exists with a different type
    */
   static bool SetDefaultValue(std::string_view key,
-                              std::shared_ptr<nt::Value> defaultValue);
+                              const nt::Value& defaultValue);
 
   /**
    * Retrieves the complex value (such as an array) in this table into the
@@ -422,7 +388,7 @@
    *
    * @param keyName the key
    */
-  static std::shared_ptr<nt::Value> GetValue(std::string_view keyName);
+  static nt::Value GetValue(std::string_view keyName);
 
   /**
    * Posts a task from a listener to the ListenerExecutor, so that it can be run
diff --git a/wpilibc/src/main/native/include/frc/util/Color.h b/wpilibc/src/main/native/include/frc/util/Color.h
index 00bc4af..87d8c12 100644
--- a/wpilibc/src/main/native/include/frc/util/Color.h
+++ b/wpilibc/src/main/native/include/frc/util/Color.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <algorithm>
+#include <string>
 
 namespace frc {
 
@@ -741,7 +742,7 @@
   constexpr Color() = default;
 
   /**
-   * Constructs a Color.
+   * Constructs a Color from doubles (0-1).
    *
    * @param r Red value (0-1)
    * @param g Green value (0-1)
@@ -753,41 +754,70 @@
         blue(roundAndClamp(b)) {}
 
   /**
+   * Constructs a Color from ints (0-255).
+   *
+   * @param r Red value (0-255)
+   * @param g Green value (0-255)
+   * @param b Blue value (0-255)
+   */
+  constexpr Color(int r, int g, int b)
+      : Color(r / 255.0, g / 255.0, b / 255.0) {}
+
+  constexpr bool operator==(const Color&) const = default;
+
+  /**
    * Creates a Color from HSV values.
    *
-   * @param h The h value [0-180]
+   * @param h The h value [0-180)
    * @param s The s value [0-255]
    * @param v The v value [0-255]
    * @return The color
    */
   static constexpr Color FromHSV(int h, int s, int v) {
-    if (s == 0) {
-      return {v / 255.0, v / 255.0, v / 255.0};
-    }
+    // Loosely based on
+    // https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB
+    // The hue range is split into 60 degree regions where in each region there
+    // is one rgb component at a low value (m), one at a high value (v) and one
+    // that changes (X) from low to high (X+m) or high to low (v-X)
 
-    int region = h / 30;
-    int remainder = (h - (region * 30)) * 6;
+    // Difference between highest and lowest value of any rgb component
+    int chroma = (s * v) >> 8;
 
-    int p = (v * (255 - s)) >> 8;
-    int q = (v * (255 - ((s * remainder) >> 8))) >> 8;
-    int t = (v * (255 - ((s * (255 - remainder)) >> 8))) >> 8;
+    // Because hue is 0-180 rather than 0-360 use 30 not 60
+    int region = (h / 30) % 6;
+
+    // Remainder converted from 0-30 to 0-255
+    int remainder = static_cast<int>((h % 30) * (255 / 30.0));
+
+    // Value of the lowest rgb component
+    int m = v - chroma;
+
+    // Goes from 0 to chroma as hue increases
+    int X = (chroma * remainder) >> 8;
 
     switch (region) {
       case 0:
-        return Color(v / 255.0, t / 255.0, p / 255.0);
+        return Color(v, X + m, m);
       case 1:
-        return Color(q / 255.0, v / 255.0, p / 255.0);
+        return Color(v - X, v, m);
       case 2:
-        return Color(p / 255.0, v / 255.0, t / 255.0);
+        return Color(m, v, X + m);
       case 3:
-        return Color(p / 255.0, q / 255.0, v / 255.0);
+        return Color(m, v - X, v);
       case 4:
-        return Color(t / 255.0, p / 255.0, v / 255.0);
+        return Color(X + m, m, v);
       default:
-        return Color(v / 255.0, p / 255.0, q / 255.0);
+        return Color(v, m, v - X);
     }
   }
 
+  /**
+   * Return this color represented as a hex string.
+   *
+   * @return a string of the format <tt>\#RRGGBB</tt>
+   */
+  std::string HexString() const;
+
   double red = 0.0;
   double green = 0.0;
   double blue = 0.0;
@@ -802,14 +832,6 @@
   }
 };
 
-inline bool operator==(const Color& c1, const Color& c2) {
-  return c1.red == c2.red && c1.green == c2.green && c1.blue == c2.blue;
-}
-
-inline bool operator!=(const Color& c1, const Color& c2) {
-  return !(c1 == c2);
-}
-
 /*
  * FIRST Colors
  */
diff --git a/wpilibc/src/main/native/include/frc/util/Color8Bit.h b/wpilibc/src/main/native/include/frc/util/Color8Bit.h
index 62cbde9..10afead 100644
--- a/wpilibc/src/main/native/include/frc/util/Color8Bit.h
+++ b/wpilibc/src/main/native/include/frc/util/Color8Bit.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <algorithm>
+#include <string>
 
 #include "Color.h"
 
@@ -43,13 +44,18 @@
     return Color(red / 255.0, green / 255.0, blue / 255.0);
   }
 
+  constexpr bool operator==(const Color8Bit&) const = default;
+
+  /**
+   * Return this color represented as a hex string.
+   *
+   * @return a string of the format <tt>\#RRGGBB</tt>
+   */
+  std::string HexString() const;
+
   int red = 0;
   int green = 0;
   int blue = 0;
 };
 
-inline bool operator==(const Color8Bit& c1, const Color8Bit& c2) {
-  return c1.red == c2.red && c1.green == c2.green && c1.blue == c2.blue;
-}
-
 }  // namespace frc
diff --git a/wpilibc/src/test/native/cpp/SpeedControllerGroupTest.cpp b/wpilibc/src/test/native/cpp/SpeedControllerGroupTest.cpp
deleted file mode 100644
index 38e1592..0000000
--- a/wpilibc/src/test/native/cpp/SpeedControllerGroupTest.cpp
+++ /dev/null
@@ -1,125 +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 "frc/motorcontrol/MotorControllerGroup.h"  // NOLINT(build/include_order)
-
-#include <memory>
-#include <vector>
-
-#include "MockMotorController.h"
-#include "gtest/gtest.h"
-
-using namespace frc;
-
-enum MotorControllerGroupTestType { TEST_ONE, TEST_TWO, TEST_THREE };
-
-std::ostream& operator<<(std::ostream& os,
-                         const MotorControllerGroupTestType& type) {
-  switch (type) {
-    case TEST_ONE:
-      os << "MotorControllerGroup with one speed controller";
-      break;
-    case TEST_TWO:
-      os << "MotorControllerGroup with two speed controllers";
-      break;
-    case TEST_THREE:
-      os << "MotorControllerGroup with three speed controllers";
-      break;
-  }
-
-  return os;
-}
-
-/**
- * A fixture used for MotorControllerGroup testing.
- */
-class MotorControllerGroupTest
-    : public testing::TestWithParam<MotorControllerGroupTestType> {
- protected:
-  std::vector<MockMotorController> m_speedControllers;
-  std::unique_ptr<MotorControllerGroup> m_group;
-
-  void SetUp() override {
-    switch (GetParam()) {
-      case TEST_ONE: {
-        m_speedControllers.emplace_back();
-        m_group = std::make_unique<MotorControllerGroup>(m_speedControllers[0]);
-        break;
-      }
-
-      case TEST_TWO: {
-        m_speedControllers.emplace_back();
-        m_speedControllers.emplace_back();
-        m_group = std::make_unique<MotorControllerGroup>(m_speedControllers[0],
-                                                         m_speedControllers[1]);
-        break;
-      }
-
-      case TEST_THREE: {
-        m_speedControllers.emplace_back();
-        m_speedControllers.emplace_back();
-        m_speedControllers.emplace_back();
-        m_group = std::make_unique<MotorControllerGroup>(m_speedControllers[0],
-                                                         m_speedControllers[1],
-                                                         m_speedControllers[2]);
-        break;
-      }
-    }
-  }
-};
-
-TEST_P(MotorControllerGroupTest, Set) {
-  m_group->Set(1.0);
-
-  for (auto& speedController : m_speedControllers) {
-    EXPECT_FLOAT_EQ(speedController.Get(), 1.0);
-  }
-}
-
-TEST_P(MotorControllerGroupTest, GetInverted) {
-  m_group->SetInverted(true);
-
-  EXPECT_TRUE(m_group->GetInverted());
-}
-
-TEST_P(MotorControllerGroupTest, SetInvertedDoesNotModifyMotorControllers) {
-  for (auto& speedController : m_speedControllers) {
-    speedController.SetInverted(false);
-  }
-  m_group->SetInverted(true);
-
-  for (auto& speedController : m_speedControllers) {
-    EXPECT_EQ(speedController.GetInverted(), false);
-  }
-}
-
-TEST_P(MotorControllerGroupTest, SetInvertedDoesInvert) {
-  m_group->SetInverted(true);
-  m_group->Set(1.0);
-
-  for (auto& speedController : m_speedControllers) {
-    EXPECT_FLOAT_EQ(speedController.Get(), -1.0);
-  }
-}
-
-TEST_P(MotorControllerGroupTest, Disable) {
-  m_group->Set(1.0);
-  m_group->Disable();
-
-  for (auto& speedController : m_speedControllers) {
-    EXPECT_FLOAT_EQ(speedController.Get(), 0.0);
-  }
-}
-
-TEST_P(MotorControllerGroupTest, StopMotor) {
-  m_group->Set(1.0);
-  m_group->StopMotor();
-
-  for (auto& speedController : m_speedControllers) {
-    EXPECT_FLOAT_EQ(speedController.Get(), 0.0);
-  }
-}
-
-INSTANTIATE_TEST_SUITE_P(Tests, MotorControllerGroupTest,
-                         testing::Values(TEST_ONE, TEST_TWO, TEST_THREE));
diff --git a/wpilibc/src/test/native/cpp/TestCallbackHelpers.cpp b/wpilibc/src/test/native/cpp/TestCallbackHelpers.cpp
index 12ae5d2..99bd0fd 100644
--- a/wpilibc/src/test/native/cpp/TestCallbackHelpers.cpp
+++ b/wpilibc/src/test/native/cpp/TestCallbackHelpers.cpp
@@ -14,8 +14,9 @@
     throw std::invalid_argument("Null value");
   }
   if (value->type != HAL_BOOLEAN) {
-    throw std::invalid_argument(
-        fmt::format("Wrong type '{}' for boolean", value->type).c_str());
+    throw std::invalid_argument(fmt::format("Wrong type '{}' for boolean",
+                                            static_cast<int>(value->type))
+                                    .c_str());
   }
   m_wasTriggered = true;
   m_lastValue = value->data.v_boolean;
@@ -28,7 +29,8 @@
   }
   if (value->type != HAL_ENUM) {
     throw std::invalid_argument(
-        fmt::format("Wrong type '{}' for enum", value->type).c_str());
+        fmt::format("Wrong type '{}' for enum", static_cast<int>(value->type))
+            .c_str());
   }
 
   m_wasTriggered = true;
@@ -41,8 +43,9 @@
     throw std::invalid_argument("Null value");
   }
   if (value->type != HAL_INT) {
-    throw std::invalid_argument(
-        fmt::format("Wrong type '{}' for integer", value->type).c_str());
+    throw std::invalid_argument(fmt::format("Wrong type '{}' for integer",
+                                            static_cast<int>(value->type))
+                                    .c_str());
   }
 
   m_wasTriggered = true;
@@ -56,7 +59,8 @@
   }
   if (value->type != HAL_LONG) {
     throw std::invalid_argument(
-        fmt::format("Wrong type '{}' for long", value->type).c_str());
+        fmt::format("Wrong type '{}' for long", static_cast<int>(value->type))
+            .c_str());
   }
 
   m_wasTriggered = true;
@@ -70,7 +74,8 @@
   }
   if (value->type != HAL_DOUBLE) {
     throw std::invalid_argument(
-        fmt::format("Wrong type '{}' for double", value->type).c_str());
+        fmt::format("Wrong type '{}' for double", static_cast<int>(value->type))
+            .c_str());
   }
 
   m_wasTriggered = true;
diff --git a/wpilibc/src/test/native/cpp/TimedRobotTest.cpp b/wpilibc/src/test/native/cpp/TimedRobotTest.cpp
index ea9656b..08c262b 100644
--- a/wpilibc/src/test/native/cpp/TimedRobotTest.cpp
+++ b/wpilibc/src/test/native/cpp/TimedRobotTest.cpp
@@ -9,6 +9,7 @@
 #include <atomic>
 #include <thread>
 
+#include "frc/livewindow/LiveWindow.h"
 #include "frc/simulation/DriverStationSim.h"
 #include "frc/simulation/SimHooks.h"
 #include "gtest/gtest.h"
@@ -16,7 +17,7 @@
 using namespace frc;
 
 namespace {
-class TimedRobotTest : public ::testing::Test {
+class TimedRobotTest : public ::testing::TestWithParam<bool> {
  protected:
   void SetUp() override { frc::sim::PauseTiming(); }
 
@@ -304,8 +305,11 @@
   robotThread.join();
 }
 
-TEST_F(TimedRobotTest, TestMode) {
+TEST_P(TimedRobotTest, TestMode) {
+  bool isTestLW = GetParam();
+
   MockRobot robot;
+  robot.EnableLiveWindowInTest(isTestLW);
 
   std::thread robotThread{[&] { robot.StartCompetition(); }};
 
@@ -321,6 +325,7 @@
   EXPECT_EQ(0u, robot.m_autonomousInitCount);
   EXPECT_EQ(0u, robot.m_teleopInitCount);
   EXPECT_EQ(0u, robot.m_testInitCount);
+  EXPECT_FALSE(frc::LiveWindow::IsEnabled());
 
   EXPECT_EQ(0u, robot.m_robotPeriodicCount);
   EXPECT_EQ(0u, robot.m_simulationPeriodicCount);
@@ -342,6 +347,9 @@
   EXPECT_EQ(0u, robot.m_autonomousInitCount);
   EXPECT_EQ(0u, robot.m_teleopInitCount);
   EXPECT_EQ(1u, robot.m_testInitCount);
+  EXPECT_EQ(isTestLW, frc::LiveWindow::IsEnabled());
+
+  EXPECT_THROW(robot.EnableLiveWindowInTest(isTestLW), std::runtime_error);
 
   EXPECT_EQ(1u, robot.m_robotPeriodicCount);
   EXPECT_EQ(1u, robot.m_simulationPeriodicCount);
@@ -376,6 +384,32 @@
   EXPECT_EQ(0u, robot.m_teleopExitCount);
   EXPECT_EQ(0u, robot.m_testExitCount);
 
+  frc::sim::DriverStationSim::SetEnabled(false);
+  frc::sim::DriverStationSim::SetAutonomous(false);
+  frc::sim::DriverStationSim::SetTest(false);
+  frc::sim::DriverStationSim::NotifyNewData();
+  frc::sim::StepTiming(20_ms);  // Wait for Notifiers
+
+  EXPECT_EQ(1u, robot.m_robotInitCount);
+  EXPECT_EQ(1u, robot.m_simulationInitCount);
+  EXPECT_EQ(1u, robot.m_disabledInitCount);
+  EXPECT_EQ(0u, robot.m_autonomousInitCount);
+  EXPECT_EQ(0u, robot.m_teleopInitCount);
+  EXPECT_EQ(1u, robot.m_testInitCount);
+  EXPECT_FALSE(frc::LiveWindow::IsEnabled());
+
+  EXPECT_EQ(3u, robot.m_robotPeriodicCount);
+  EXPECT_EQ(3u, robot.m_simulationPeriodicCount);
+  EXPECT_EQ(1u, robot.m_disabledPeriodicCount);
+  EXPECT_EQ(0u, robot.m_autonomousPeriodicCount);
+  EXPECT_EQ(0u, robot.m_teleopPeriodicCount);
+  EXPECT_EQ(2u, robot.m_testPeriodicCount);
+
+  EXPECT_EQ(0u, robot.m_disabledExitCount);
+  EXPECT_EQ(0u, robot.m_autonomousExitCount);
+  EXPECT_EQ(0u, robot.m_teleopExitCount);
+  EXPECT_EQ(1u, robot.m_testExitCount);
+
   robot.EndCompetition();
   robotThread.join();
 }
@@ -418,6 +452,7 @@
   frc::sim::DriverStationSim::SetEnabled(true);
   frc::sim::DriverStationSim::SetAutonomous(true);
   frc::sim::DriverStationSim::SetTest(false);
+  frc::sim::DriverStationSim::NotifyNewData();
 
   frc::sim::StepTiming(20_ms);
 
@@ -435,6 +470,7 @@
   frc::sim::DriverStationSim::SetEnabled(true);
   frc::sim::DriverStationSim::SetAutonomous(false);
   frc::sim::DriverStationSim::SetTest(false);
+  frc::sim::DriverStationSim::NotifyNewData();
 
   frc::sim::StepTiming(20_ms);
 
@@ -452,6 +488,7 @@
   frc::sim::DriverStationSim::SetEnabled(true);
   frc::sim::DriverStationSim::SetAutonomous(false);
   frc::sim::DriverStationSim::SetTest(true);
+  frc::sim::DriverStationSim::NotifyNewData();
 
   frc::sim::StepTiming(20_ms);
 
@@ -469,6 +506,7 @@
   frc::sim::DriverStationSim::SetEnabled(false);
   frc::sim::DriverStationSim::SetAutonomous(false);
   frc::sim::DriverStationSim::SetTest(false);
+  frc::sim::DriverStationSim::NotifyNewData();
 
   frc::sim::StepTiming(20_ms);
 
@@ -568,3 +606,5 @@
   robot.EndCompetition();
   robotThread.join();
 }
+
+INSTANTIATE_TEST_SUITE_P(TimedRobotTests, TimedRobotTest, testing::Bool());
diff --git a/wpilibc/src/test/native/cpp/UnitNetworkTablesTest.cpp b/wpilibc/src/test/native/cpp/UnitNetworkTablesTest.cpp
new file mode 100644
index 0000000..505f906
--- /dev/null
+++ b/wpilibc/src/test/native/cpp/UnitNetworkTablesTest.cpp
@@ -0,0 +1,45 @@
+// 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 <networktables/DoubleTopic.h>
+#include <networktables/NetworkTableInstance.h>
+#include <networktables/UnitTopic.h>
+#include <units/length.h>
+
+#include "gtest/gtest.h"
+
+class UnitNetworkTablesTest : public ::testing::Test {
+ public:
+  UnitNetworkTablesTest() : inst{nt::NetworkTableInstance::Create()} {}
+  ~UnitNetworkTablesTest() override { nt::NetworkTableInstance::Destroy(inst); }
+  nt::NetworkTableInstance inst;
+};
+
+TEST_F(UnitNetworkTablesTest, Publish) {
+  auto topic = nt::UnitTopic<units::meter_t>{inst.GetTopic("meterTest")};
+  auto pub = topic.Publish();
+  pub.Set(2_m);
+  ASSERT_EQ(topic.GetProperty("unit"), "meter");
+  ASSERT_TRUE(topic.IsMatchingUnit());
+}
+
+TEST_F(UnitNetworkTablesTest, SubscribeDouble) {
+  auto topic = nt::UnitTopic<units::meter_t>{inst.GetTopic("meterTest")};
+  auto pub = topic.Publish();
+  auto sub = inst.GetDoubleTopic("meterTest").Subscribe(0);
+  ASSERT_EQ(sub.Get(), 0);
+  ASSERT_EQ(sub.Get(3), 3);
+  pub.Set(2_m);
+  ASSERT_EQ(sub.Get(), 2);
+}
+
+TEST_F(UnitNetworkTablesTest, SubscribeUnit) {
+  auto topic = nt::UnitTopic<units::meter_t>{inst.GetTopic("meterTest")};
+  auto pub = topic.Publish();
+  auto sub = topic.Subscribe(0_m);
+  ASSERT_EQ(sub.Get(), 0_m);
+  ASSERT_EQ(sub.Get(3_m), 3_m);
+  pub.Set(2_m);
+  ASSERT_EQ(sub.Get(), 2_m);
+}
diff --git a/wpilibc/src/test/native/cpp/drive/DifferentialDriveTest.cpp b/wpilibc/src/test/native/cpp/drive/DifferentialDriveTest.cpp
index b930346..1f542be 100644
--- a/wpilibc/src/test/native/cpp/drive/DifferentialDriveTest.cpp
+++ b/wpilibc/src/test/native/cpp/drive/DifferentialDriveTest.cpp
@@ -2,9 +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 "MockMotorController.h"
 #include "frc/drive/DifferentialDrive.h"
 #include "gtest/gtest.h"
+#include "motorcontrol/MockMotorController.h"
 
 TEST(DifferentialDriveTest, ArcadeDriveIK) {
   // Forward
@@ -13,12 +13,12 @@
   EXPECT_DOUBLE_EQ(1.0, speeds.right);
 
   // Forward left turn
-  speeds = frc::DifferentialDrive::ArcadeDriveIK(0.5, -0.5, false);
+  speeds = frc::DifferentialDrive::ArcadeDriveIK(0.5, 0.5, false);
   EXPECT_DOUBLE_EQ(0.0, speeds.left);
   EXPECT_DOUBLE_EQ(0.5, speeds.right);
 
   // Forward right turn
-  speeds = frc::DifferentialDrive::ArcadeDriveIK(0.5, 0.5, false);
+  speeds = frc::DifferentialDrive::ArcadeDriveIK(0.5, -0.5, false);
   EXPECT_DOUBLE_EQ(0.5, speeds.left);
   EXPECT_DOUBLE_EQ(0.0, speeds.right);
 
@@ -28,32 +28,32 @@
   EXPECT_DOUBLE_EQ(-1.0, speeds.right);
 
   // Backward left turn
-  speeds = frc::DifferentialDrive::ArcadeDriveIK(-0.5, -0.5, false);
+  speeds = frc::DifferentialDrive::ArcadeDriveIK(-0.5, 0.5, false);
   EXPECT_DOUBLE_EQ(-0.5, speeds.left);
   EXPECT_DOUBLE_EQ(0.0, speeds.right);
 
   // Backward right turn
-  speeds = frc::DifferentialDrive::ArcadeDriveIK(-0.5, 0.5, false);
+  speeds = frc::DifferentialDrive::ArcadeDriveIK(-0.5, -0.5, false);
   EXPECT_DOUBLE_EQ(0.0, speeds.left);
   EXPECT_DOUBLE_EQ(-0.5, speeds.right);
 
   // Left turn (xSpeed with negative sign)
-  speeds = frc::DifferentialDrive::ArcadeDriveIK(-0.0, -1.0, false);
+  speeds = frc::DifferentialDrive::ArcadeDriveIK(-0.0, 1.0, false);
   EXPECT_DOUBLE_EQ(-1.0, speeds.left);
   EXPECT_DOUBLE_EQ(1.0, speeds.right);
 
   // Left turn (xSpeed with positive sign)
-  speeds = frc::DifferentialDrive::ArcadeDriveIK(0.0, -1.0, false);
+  speeds = frc::DifferentialDrive::ArcadeDriveIK(0.0, 1.0, false);
   EXPECT_DOUBLE_EQ(-1.0, speeds.left);
   EXPECT_DOUBLE_EQ(1.0, speeds.right);
 
   // Right turn (xSpeed with negative sign)
-  speeds = frc::DifferentialDrive::ArcadeDriveIK(-0.0, 1.0, false);
+  speeds = frc::DifferentialDrive::ArcadeDriveIK(-0.0, -1.0, false);
   EXPECT_DOUBLE_EQ(1.0, speeds.left);
   EXPECT_DOUBLE_EQ(-1.0, speeds.right);
 
   // Right turn (xSpeed with positive sign)
-  speeds = frc::DifferentialDrive::ArcadeDriveIK(0.0, 1.0, false);
+  speeds = frc::DifferentialDrive::ArcadeDriveIK(0.0, -1.0, false);
   EXPECT_DOUBLE_EQ(1.0, speeds.left);
   EXPECT_DOUBLE_EQ(-1.0, speeds.right);
 }
@@ -65,12 +65,12 @@
   EXPECT_DOUBLE_EQ(1.0, speeds.right);
 
   // Forward left turn
-  speeds = frc::DifferentialDrive::ArcadeDriveIK(0.5, -0.5, true);
+  speeds = frc::DifferentialDrive::ArcadeDriveIK(0.5, 0.5, true);
   EXPECT_DOUBLE_EQ(0.0, speeds.left);
   EXPECT_DOUBLE_EQ(0.25, speeds.right);
 
   // Forward right turn
-  speeds = frc::DifferentialDrive::ArcadeDriveIK(0.5, 0.5, true);
+  speeds = frc::DifferentialDrive::ArcadeDriveIK(0.5, -0.5, true);
   EXPECT_DOUBLE_EQ(0.25, speeds.left);
   EXPECT_DOUBLE_EQ(0.0, speeds.right);
 
@@ -80,32 +80,32 @@
   EXPECT_DOUBLE_EQ(-1.0, speeds.right);
 
   // Backward left turn
-  speeds = frc::DifferentialDrive::ArcadeDriveIK(-0.5, -0.5, true);
+  speeds = frc::DifferentialDrive::ArcadeDriveIK(-0.5, 0.5, true);
   EXPECT_DOUBLE_EQ(-0.25, speeds.left);
   EXPECT_DOUBLE_EQ(0.0, speeds.right);
 
   // Backward right turn
-  speeds = frc::DifferentialDrive::ArcadeDriveIK(-0.5, 0.5, true);
+  speeds = frc::DifferentialDrive::ArcadeDriveIK(-0.5, -0.5, true);
   EXPECT_DOUBLE_EQ(0.0, speeds.left);
   EXPECT_DOUBLE_EQ(-0.25, speeds.right);
 
   // Left turn (xSpeed with negative sign)
-  speeds = frc::DifferentialDrive::ArcadeDriveIK(-0.0, -1.0, false);
+  speeds = frc::DifferentialDrive::ArcadeDriveIK(-0.0, 1.0, false);
   EXPECT_DOUBLE_EQ(-1.0, speeds.left);
   EXPECT_DOUBLE_EQ(1.0, speeds.right);
 
   // Left turn (xSpeed with positive sign)
-  speeds = frc::DifferentialDrive::ArcadeDriveIK(0.0, -1.0, false);
+  speeds = frc::DifferentialDrive::ArcadeDriveIK(0.0, 1.0, false);
   EXPECT_DOUBLE_EQ(-1.0, speeds.left);
   EXPECT_DOUBLE_EQ(1.0, speeds.right);
 
   // Right turn (xSpeed with negative sign)
-  speeds = frc::DifferentialDrive::ArcadeDriveIK(-0.0, 1.0, false);
+  speeds = frc::DifferentialDrive::ArcadeDriveIK(-0.0, -1.0, false);
   EXPECT_DOUBLE_EQ(1.0, speeds.left);
   EXPECT_DOUBLE_EQ(-1.0, speeds.right);
 
   // Right turn (xSpeed with positive sign)
-  speeds = frc::DifferentialDrive::ArcadeDriveIK(0.0, 1.0, false);
+  speeds = frc::DifferentialDrive::ArcadeDriveIK(0.0, -1.0, false);
   EXPECT_DOUBLE_EQ(1.0, speeds.left);
   EXPECT_DOUBLE_EQ(-1.0, speeds.right);
 }
@@ -117,12 +117,12 @@
   EXPECT_DOUBLE_EQ(1.0, speeds.right);
 
   // Forward left turn
-  speeds = frc::DifferentialDrive::CurvatureDriveIK(0.5, -0.5, false);
+  speeds = frc::DifferentialDrive::CurvatureDriveIK(0.5, 0.5, false);
   EXPECT_DOUBLE_EQ(0.25, speeds.left);
   EXPECT_DOUBLE_EQ(0.75, speeds.right);
 
   // Forward right turn
-  speeds = frc::DifferentialDrive::CurvatureDriveIK(0.5, 0.5, false);
+  speeds = frc::DifferentialDrive::CurvatureDriveIK(0.5, -0.5, false);
   EXPECT_DOUBLE_EQ(0.75, speeds.left);
   EXPECT_DOUBLE_EQ(0.25, speeds.right);
 
@@ -132,12 +132,12 @@
   EXPECT_DOUBLE_EQ(-1.0, speeds.right);
 
   // Backward left turn
-  speeds = frc::DifferentialDrive::CurvatureDriveIK(-0.5, -0.5, false);
+  speeds = frc::DifferentialDrive::CurvatureDriveIK(-0.5, 0.5, false);
   EXPECT_DOUBLE_EQ(-0.75, speeds.left);
   EXPECT_DOUBLE_EQ(-0.25, speeds.right);
 
   // Backward right turn
-  speeds = frc::DifferentialDrive::CurvatureDriveIK(-0.5, 0.5, false);
+  speeds = frc::DifferentialDrive::CurvatureDriveIK(-0.5, -0.5, false);
   EXPECT_DOUBLE_EQ(-0.25, speeds.left);
   EXPECT_DOUBLE_EQ(-0.75, speeds.right);
 }
@@ -149,12 +149,12 @@
   EXPECT_DOUBLE_EQ(1.0, speeds.right);
 
   // Forward left turn
-  speeds = frc::DifferentialDrive::CurvatureDriveIK(0.5, -0.5, true);
+  speeds = frc::DifferentialDrive::CurvatureDriveIK(0.5, 0.5, true);
   EXPECT_DOUBLE_EQ(0.0, speeds.left);
   EXPECT_DOUBLE_EQ(1.0, speeds.right);
 
   // Forward right turn
-  speeds = frc::DifferentialDrive::CurvatureDriveIK(0.5, 0.5, true);
+  speeds = frc::DifferentialDrive::CurvatureDriveIK(0.5, -0.5, true);
   EXPECT_DOUBLE_EQ(1.0, speeds.left);
   EXPECT_DOUBLE_EQ(0.0, speeds.right);
 
@@ -164,12 +164,12 @@
   EXPECT_DOUBLE_EQ(-1.0, speeds.right);
 
   // Backward left turn
-  speeds = frc::DifferentialDrive::CurvatureDriveIK(-0.5, -0.5, true);
+  speeds = frc::DifferentialDrive::CurvatureDriveIK(-0.5, 0.5, true);
   EXPECT_DOUBLE_EQ(-1.0, speeds.left);
   EXPECT_DOUBLE_EQ(0.0, speeds.right);
 
   // Backward right turn
-  speeds = frc::DifferentialDrive::CurvatureDriveIK(-0.5, 0.5, true);
+  speeds = frc::DifferentialDrive::CurvatureDriveIK(-0.5, -0.5, true);
   EXPECT_DOUBLE_EQ(0.0, speeds.left);
   EXPECT_DOUBLE_EQ(-1.0, speeds.right);
 }
@@ -250,12 +250,12 @@
   EXPECT_DOUBLE_EQ(1.0, right.Get());
 
   // Forward left turn
-  drive.ArcadeDrive(0.5, -0.5, false);
+  drive.ArcadeDrive(0.5, 0.5, false);
   EXPECT_DOUBLE_EQ(0.0, left.Get());
   EXPECT_DOUBLE_EQ(0.5, right.Get());
 
   // Forward right turn
-  drive.ArcadeDrive(0.5, 0.5, false);
+  drive.ArcadeDrive(0.5, -0.5, false);
   EXPECT_DOUBLE_EQ(0.5, left.Get());
   EXPECT_DOUBLE_EQ(0.0, right.Get());
 
@@ -265,12 +265,12 @@
   EXPECT_DOUBLE_EQ(-1.0, right.Get());
 
   // Backward left turn
-  drive.ArcadeDrive(-0.5, -0.5, false);
+  drive.ArcadeDrive(-0.5, 0.5, false);
   EXPECT_DOUBLE_EQ(-0.5, left.Get());
   EXPECT_DOUBLE_EQ(0.0, right.Get());
 
   // Backward right turn
-  drive.ArcadeDrive(-0.5, 0.5, false);
+  drive.ArcadeDrive(-0.5, -0.5, false);
   EXPECT_DOUBLE_EQ(0.0, left.Get());
   EXPECT_DOUBLE_EQ(-0.5, right.Get());
 }
@@ -287,12 +287,12 @@
   EXPECT_DOUBLE_EQ(1.0, right.Get());
 
   // Forward left turn
-  drive.ArcadeDrive(0.5, -0.5, true);
+  drive.ArcadeDrive(0.5, 0.5, true);
   EXPECT_DOUBLE_EQ(0.0, left.Get());
   EXPECT_DOUBLE_EQ(0.25, right.Get());
 
   // Forward right turn
-  drive.ArcadeDrive(0.5, 0.5, true);
+  drive.ArcadeDrive(0.5, -0.5, true);
   EXPECT_DOUBLE_EQ(0.25, left.Get());
   EXPECT_DOUBLE_EQ(0.0, right.Get());
 
@@ -302,12 +302,12 @@
   EXPECT_DOUBLE_EQ(-1.0, right.Get());
 
   // Backward left turn
-  drive.ArcadeDrive(-0.5, -0.5, true);
+  drive.ArcadeDrive(-0.5, 0.5, true);
   EXPECT_DOUBLE_EQ(-0.25, left.Get());
   EXPECT_DOUBLE_EQ(0.0, right.Get());
 
   // Backward right turn
-  drive.ArcadeDrive(-0.5, 0.5, true);
+  drive.ArcadeDrive(-0.5, -0.5, true);
   EXPECT_DOUBLE_EQ(0.0, left.Get());
   EXPECT_DOUBLE_EQ(-0.25, right.Get());
 }
@@ -324,12 +324,12 @@
   EXPECT_DOUBLE_EQ(1.0, right.Get());
 
   // Forward left turn
-  drive.CurvatureDrive(0.5, -0.5, false);
+  drive.CurvatureDrive(0.5, 0.5, false);
   EXPECT_DOUBLE_EQ(0.25, left.Get());
   EXPECT_DOUBLE_EQ(0.75, right.Get());
 
   // Forward right turn
-  drive.CurvatureDrive(0.5, 0.5, false);
+  drive.CurvatureDrive(0.5, -0.5, false);
   EXPECT_DOUBLE_EQ(0.75, left.Get());
   EXPECT_DOUBLE_EQ(0.25, right.Get());
 
@@ -339,12 +339,12 @@
   EXPECT_DOUBLE_EQ(-1.0, right.Get());
 
   // Backward left turn
-  drive.CurvatureDrive(-0.5, -0.5, false);
+  drive.CurvatureDrive(-0.5, 0.5, false);
   EXPECT_DOUBLE_EQ(-0.75, left.Get());
   EXPECT_DOUBLE_EQ(-0.25, right.Get());
 
   // Backward right turn
-  drive.CurvatureDrive(-0.5, 0.5, false);
+  drive.CurvatureDrive(-0.5, -0.5, false);
   EXPECT_DOUBLE_EQ(-0.25, left.Get());
   EXPECT_DOUBLE_EQ(-0.75, right.Get());
 }
@@ -361,12 +361,12 @@
   EXPECT_DOUBLE_EQ(1.0, right.Get());
 
   // Forward left turn
-  drive.CurvatureDrive(0.5, -0.5, true);
+  drive.CurvatureDrive(0.5, 0.5, true);
   EXPECT_DOUBLE_EQ(0.0, left.Get());
   EXPECT_DOUBLE_EQ(1.0, right.Get());
 
   // Forward right turn
-  drive.CurvatureDrive(0.5, 0.5, true);
+  drive.CurvatureDrive(0.5, -0.5, true);
   EXPECT_DOUBLE_EQ(1.0, left.Get());
   EXPECT_DOUBLE_EQ(0.0, right.Get());
 
@@ -376,12 +376,12 @@
   EXPECT_DOUBLE_EQ(-1.0, right.Get());
 
   // Backward left turn
-  drive.CurvatureDrive(-0.5, -0.5, true);
+  drive.CurvatureDrive(-0.5, 0.5, true);
   EXPECT_DOUBLE_EQ(-1.0, left.Get());
   EXPECT_DOUBLE_EQ(0.0, right.Get());
 
   // Backward right turn
-  drive.CurvatureDrive(-0.5, 0.5, true);
+  drive.CurvatureDrive(-0.5, -0.5, true);
   EXPECT_DOUBLE_EQ(0.0, left.Get());
   EXPECT_DOUBLE_EQ(-1.0, right.Get());
 }
diff --git a/wpilibc/src/test/native/cpp/drive/KilloughDriveTest.cpp b/wpilibc/src/test/native/cpp/drive/KilloughDriveTest.cpp
deleted file mode 100644
index c23c7b0..0000000
--- a/wpilibc/src/test/native/cpp/drive/KilloughDriveTest.cpp
+++ /dev/null
@@ -1,197 +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 <cmath>
-
-#include "MockMotorController.h"
-#include "frc/drive/KilloughDrive.h"
-#include "gtest/gtest.h"
-
-TEST(KilloughDriveTest, CartesianIK) {
-  frc::MockMotorController left;
-  frc::MockMotorController right;
-  frc::MockMotorController back;
-  frc::KilloughDrive drive{left, right, back};
-
-  // Forward
-  auto speeds = drive.DriveCartesianIK(1.0, 0.0, 0.0);
-  EXPECT_DOUBLE_EQ(0.5, speeds.left);
-  EXPECT_DOUBLE_EQ(-0.5, speeds.right);
-  EXPECT_NEAR(0.0, speeds.back, 1e-9);
-
-  // Left
-  speeds = drive.DriveCartesianIK(0.0, -1.0, 0.0);
-  EXPECT_DOUBLE_EQ(-std::sqrt(3) / 2, speeds.left);
-  EXPECT_DOUBLE_EQ(-std::sqrt(3) / 2, speeds.right);
-  EXPECT_DOUBLE_EQ(1.0, speeds.back);
-
-  // Right
-  speeds = drive.DriveCartesianIK(0.0, 1.0, 0.0);
-  EXPECT_DOUBLE_EQ(std::sqrt(3) / 2, speeds.left);
-  EXPECT_DOUBLE_EQ(std::sqrt(3) / 2, speeds.right);
-  EXPECT_DOUBLE_EQ(-1.0, speeds.back);
-
-  // Rotate CCW
-  speeds = drive.DriveCartesianIK(0.0, 0.0, -1.0);
-  EXPECT_DOUBLE_EQ(-1.0, speeds.left);
-  EXPECT_DOUBLE_EQ(-1.0, speeds.right);
-  EXPECT_DOUBLE_EQ(-1.0, speeds.back);
-
-  // Rotate CW
-  speeds = drive.DriveCartesianIK(0.0, 0.0, 1.0);
-  EXPECT_DOUBLE_EQ(1.0, speeds.left);
-  EXPECT_DOUBLE_EQ(1.0, speeds.right);
-  EXPECT_DOUBLE_EQ(1.0, speeds.back);
-}
-
-TEST(KilloughDriveTest, CartesianIKGyro90CW) {
-  frc::MockMotorController left;
-  frc::MockMotorController right;
-  frc::MockMotorController back;
-  frc::KilloughDrive drive{left, right, back};
-
-  // Forward in global frame; left in robot frame
-  auto speeds = drive.DriveCartesianIK(1.0, 0.0, 0.0, 90.0);
-  EXPECT_DOUBLE_EQ(-std::sqrt(3) / 2, speeds.left);
-  EXPECT_DOUBLE_EQ(-std::sqrt(3) / 2, speeds.right);
-  EXPECT_DOUBLE_EQ(1.0, speeds.back);
-
-  // Left in global frame; backward in robot frame
-  speeds = drive.DriveCartesianIK(0.0, -1.0, 0.0, 90.0);
-  EXPECT_DOUBLE_EQ(-0.5, speeds.left);
-  EXPECT_NEAR(0.5, speeds.right, 1e-9);
-  EXPECT_NEAR(0.0, speeds.back, 1e-9);
-
-  // Right in global frame; forward in robot frame
-  speeds = drive.DriveCartesianIK(0.0, 1.0, 0.0, 90.0);
-  EXPECT_DOUBLE_EQ(0.5, speeds.left);
-  EXPECT_NEAR(-0.5, speeds.right, 1e-9);
-  EXPECT_NEAR(0.0, speeds.back, 1e-9);
-
-  // Rotate CCW
-  speeds = drive.DriveCartesianIK(0.0, 0.0, -1.0, 90.0);
-  EXPECT_DOUBLE_EQ(-1.0, speeds.left);
-  EXPECT_DOUBLE_EQ(-1.0, speeds.right);
-  EXPECT_DOUBLE_EQ(-1.0, speeds.back);
-
-  // Rotate CW
-  speeds = drive.DriveCartesianIK(0.0, 0.0, 1.0, 90.0);
-  EXPECT_DOUBLE_EQ(1.0, speeds.left);
-  EXPECT_DOUBLE_EQ(1.0, speeds.right);
-  EXPECT_DOUBLE_EQ(1.0, speeds.back);
-}
-
-TEST(KilloughDriveTest, Cartesian) {
-  frc::MockMotorController left;
-  frc::MockMotorController right;
-  frc::MockMotorController back;
-  frc::KilloughDrive drive{left, right, back};
-  drive.SetDeadband(0.0);
-
-  // Forward
-  drive.DriveCartesian(1.0, 0.0, 0.0);
-  EXPECT_DOUBLE_EQ(0.5, left.Get());
-  EXPECT_DOUBLE_EQ(-0.5, right.Get());
-  EXPECT_NEAR(0.0, back.Get(), 1e-9);
-
-  // Left
-  drive.DriveCartesian(0.0, -1.0, 0.0);
-  EXPECT_DOUBLE_EQ(-std::sqrt(3) / 2, left.Get());
-  EXPECT_DOUBLE_EQ(-std::sqrt(3) / 2, right.Get());
-  EXPECT_DOUBLE_EQ(1.0, back.Get());
-
-  // Right
-  drive.DriveCartesian(0.0, 1.0, 0.0);
-  EXPECT_DOUBLE_EQ(std::sqrt(3) / 2, left.Get());
-  EXPECT_DOUBLE_EQ(std::sqrt(3) / 2, right.Get());
-  EXPECT_DOUBLE_EQ(-1.0, back.Get());
-
-  // Rotate CCW
-  drive.DriveCartesian(0.0, 0.0, -1.0);
-  EXPECT_DOUBLE_EQ(-1.0, left.Get());
-  EXPECT_DOUBLE_EQ(-1.0, right.Get());
-  EXPECT_DOUBLE_EQ(-1.0, back.Get());
-
-  // Rotate CW
-  drive.DriveCartesian(0.0, 0.0, 1.0);
-  EXPECT_DOUBLE_EQ(1.0, left.Get());
-  EXPECT_DOUBLE_EQ(1.0, right.Get());
-  EXPECT_DOUBLE_EQ(1.0, back.Get());
-}
-
-TEST(KilloughDriveTest, CartesianGyro90CW) {
-  frc::MockMotorController left;
-  frc::MockMotorController right;
-  frc::MockMotorController back;
-  frc::KilloughDrive drive{left, right, back};
-  drive.SetDeadband(0.0);
-
-  // Forward in global frame; left in robot frame
-  drive.DriveCartesian(1.0, 0.0, 0.0, 90.0);
-  EXPECT_DOUBLE_EQ(-std::sqrt(3) / 2, left.Get());
-  EXPECT_DOUBLE_EQ(-std::sqrt(3) / 2, right.Get());
-  EXPECT_DOUBLE_EQ(1.0, back.Get());
-
-  // Left in global frame; backward in robot frame
-  drive.DriveCartesian(0.0, -1.0, 0.0, 90.0);
-  EXPECT_DOUBLE_EQ(-0.5, left.Get());
-  EXPECT_NEAR(0.5, right.Get(), 1e-9);
-  EXPECT_NEAR(0.0, back.Get(), 1e-9);
-
-  // Right in global frame; forward in robot frame
-  drive.DriveCartesian(0.0, 1.0, 0.0, 90.0);
-  EXPECT_DOUBLE_EQ(0.5, left.Get());
-  EXPECT_NEAR(-0.5, right.Get(), 1e-9);
-  EXPECT_NEAR(0.0, back.Get(), 1e-9);
-
-  // Rotate CCW
-  drive.DriveCartesian(0.0, 0.0, -1.0, 90.0);
-  EXPECT_DOUBLE_EQ(-1.0, left.Get());
-  EXPECT_DOUBLE_EQ(-1.0, right.Get());
-  EXPECT_DOUBLE_EQ(-1.0, back.Get());
-
-  // Rotate CW
-  drive.DriveCartesian(0.0, 0.0, 1.0, 90.0);
-  EXPECT_DOUBLE_EQ(1.0, left.Get());
-  EXPECT_DOUBLE_EQ(1.0, right.Get());
-  EXPECT_DOUBLE_EQ(1.0, back.Get());
-}
-
-TEST(KilloughDriveTest, Polar) {
-  frc::MockMotorController left;
-  frc::MockMotorController right;
-  frc::MockMotorController back;
-  frc::KilloughDrive drive{left, right, back};
-  drive.SetDeadband(0.0);
-
-  // Forward
-  drive.DrivePolar(1.0, 0.0, 0.0);
-  EXPECT_DOUBLE_EQ(std::sqrt(3) / 2, left.Get());
-  EXPECT_DOUBLE_EQ(std::sqrt(3) / 2, right.Get());
-  EXPECT_DOUBLE_EQ(-1.0, back.Get());
-
-  // Left
-  drive.DrivePolar(1.0, -90.0, 0.0);
-  EXPECT_DOUBLE_EQ(-0.5, left.Get());
-  EXPECT_DOUBLE_EQ(0.5, right.Get());
-  EXPECT_NEAR(0.0, back.Get(), 1e-9);
-
-  // Right
-  drive.DrivePolar(1.0, 90.0, 0.0);
-  EXPECT_DOUBLE_EQ(0.5, left.Get());
-  EXPECT_NEAR(-0.5, right.Get(), 1e-9);
-  EXPECT_NEAR(0.0, back.Get(), 1e-9);
-
-  // Rotate CCW
-  drive.DrivePolar(0.0, 0.0, -1.0);
-  EXPECT_DOUBLE_EQ(-1.0, left.Get());
-  EXPECT_DOUBLE_EQ(-1.0, right.Get());
-  EXPECT_DOUBLE_EQ(-1.0, back.Get());
-
-  // Rotate CW
-  drive.DrivePolar(0.0, 0.0, 1.0);
-  EXPECT_DOUBLE_EQ(1.0, left.Get());
-  EXPECT_DOUBLE_EQ(1.0, right.Get());
-  EXPECT_DOUBLE_EQ(1.0, back.Get());
-}
diff --git a/wpilibc/src/test/native/cpp/drive/MecanumDriveTest.cpp b/wpilibc/src/test/native/cpp/drive/MecanumDriveTest.cpp
index 2990848..de55462 100644
--- a/wpilibc/src/test/native/cpp/drive/MecanumDriveTest.cpp
+++ b/wpilibc/src/test/native/cpp/drive/MecanumDriveTest.cpp
@@ -2,9 +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 "MockMotorController.h"
 #include "frc/drive/MecanumDrive.h"
 #include "gtest/gtest.h"
+#include "motorcontrol/MockMotorController.h"
 
 TEST(MecanumDriveTest, CartesianIK) {
   // Forward
@@ -45,35 +45,35 @@
 
 TEST(MecanumDriveTest, CartesianIKGyro90CW) {
   // Forward in global frame; left in robot frame
-  auto speeds = frc::MecanumDrive::DriveCartesianIK(1.0, 0.0, 0.0, 90.0);
+  auto speeds = frc::MecanumDrive::DriveCartesianIK(1.0, 0.0, 0.0, 90_deg);
   EXPECT_DOUBLE_EQ(-1.0, speeds.frontLeft);
   EXPECT_DOUBLE_EQ(1.0, speeds.frontRight);
   EXPECT_DOUBLE_EQ(1.0, speeds.rearLeft);
   EXPECT_DOUBLE_EQ(-1.0, speeds.rearRight);
 
   // Left in global frame; backward in robot frame
-  speeds = frc::MecanumDrive::DriveCartesianIK(0.0, -1.0, 0.0, 90.0);
+  speeds = frc::MecanumDrive::DriveCartesianIK(0.0, -1.0, 0.0, 90_deg);
   EXPECT_DOUBLE_EQ(-1.0, speeds.frontLeft);
   EXPECT_DOUBLE_EQ(-1.0, speeds.frontRight);
   EXPECT_DOUBLE_EQ(-1.0, speeds.rearLeft);
   EXPECT_DOUBLE_EQ(-1.0, speeds.rearRight);
 
   // Right in global frame; forward in robot frame
-  speeds = frc::MecanumDrive::DriveCartesianIK(0.0, 1.0, 0.0, 90.0);
+  speeds = frc::MecanumDrive::DriveCartesianIK(0.0, 1.0, 0.0, 90_deg);
   EXPECT_DOUBLE_EQ(1.0, speeds.frontLeft);
   EXPECT_DOUBLE_EQ(1.0, speeds.frontRight);
   EXPECT_DOUBLE_EQ(1.0, speeds.rearLeft);
   EXPECT_DOUBLE_EQ(1.0, speeds.rearRight);
 
   // Rotate CCW
-  speeds = frc::MecanumDrive::DriveCartesianIK(0.0, 0.0, -1.0, 90.0);
+  speeds = frc::MecanumDrive::DriveCartesianIK(0.0, 0.0, -1.0, 90_deg);
   EXPECT_DOUBLE_EQ(-1.0, speeds.frontLeft);
   EXPECT_DOUBLE_EQ(1.0, speeds.frontRight);
   EXPECT_DOUBLE_EQ(-1.0, speeds.rearLeft);
   EXPECT_DOUBLE_EQ(1.0, speeds.rearRight);
 
   // Rotate CW
-  speeds = frc::MecanumDrive::DriveCartesianIK(0.0, 0.0, 1.0, 90.0);
+  speeds = frc::MecanumDrive::DriveCartesianIK(0.0, 0.0, 1.0, 90_deg);
   EXPECT_DOUBLE_EQ(1.0, speeds.frontLeft);
   EXPECT_DOUBLE_EQ(-1.0, speeds.frontRight);
   EXPECT_DOUBLE_EQ(1.0, speeds.rearLeft);
@@ -133,35 +133,35 @@
   drive.SetDeadband(0.0);
 
   // Forward in global frame; left in robot frame
-  drive.DriveCartesian(1.0, 0.0, 0.0, 90.0);
+  drive.DriveCartesian(1.0, 0.0, 0.0, 90_deg);
   EXPECT_DOUBLE_EQ(-1.0, fl.Get());
   EXPECT_DOUBLE_EQ(1.0, fr.Get());
   EXPECT_DOUBLE_EQ(1.0, rl.Get());
   EXPECT_DOUBLE_EQ(-1.0, rr.Get());
 
   // Left in global frame; backward in robot frame
-  drive.DriveCartesian(0.0, -1.0, 0.0, 90.0);
+  drive.DriveCartesian(0.0, -1.0, 0.0, 90_deg);
   EXPECT_DOUBLE_EQ(-1.0, fl.Get());
   EXPECT_DOUBLE_EQ(-1.0, fr.Get());
   EXPECT_DOUBLE_EQ(-1.0, rl.Get());
   EXPECT_DOUBLE_EQ(-1.0, rr.Get());
 
   // Right in global frame; forward in robot frame
-  drive.DriveCartesian(0.0, 1.0, 0.0, 90.0);
+  drive.DriveCartesian(0.0, 1.0, 0.0, 90_deg);
   EXPECT_DOUBLE_EQ(1.0, fl.Get());
   EXPECT_DOUBLE_EQ(1.0, fr.Get());
   EXPECT_DOUBLE_EQ(1.0, rl.Get());
   EXPECT_DOUBLE_EQ(1.0, rr.Get());
 
   // Rotate CCW
-  drive.DriveCartesian(0.0, 0.0, -1.0, 90.0);
+  drive.DriveCartesian(0.0, 0.0, -1.0, 90_deg);
   EXPECT_DOUBLE_EQ(-1.0, fl.Get());
   EXPECT_DOUBLE_EQ(1.0, fr.Get());
   EXPECT_DOUBLE_EQ(-1.0, rl.Get());
   EXPECT_DOUBLE_EQ(1.0, rr.Get());
 
   // Rotate CW
-  drive.DriveCartesian(0.0, 0.0, 1.0, 90.0);
+  drive.DriveCartesian(0.0, 0.0, 1.0, 90_deg);
   EXPECT_DOUBLE_EQ(1.0, fl.Get());
   EXPECT_DOUBLE_EQ(-1.0, fr.Get());
   EXPECT_DOUBLE_EQ(1.0, rl.Get());
@@ -177,35 +177,35 @@
   drive.SetDeadband(0.0);
 
   // Forward
-  drive.DrivePolar(1.0, 0.0, 0.0);
+  drive.DrivePolar(1.0, 0_deg, 0.0);
   EXPECT_DOUBLE_EQ(1.0, fl.Get());
   EXPECT_DOUBLE_EQ(1.0, fr.Get());
   EXPECT_DOUBLE_EQ(1.0, rl.Get());
   EXPECT_DOUBLE_EQ(1.0, rr.Get());
 
   // Left
-  drive.DrivePolar(1.0, -90.0, 0.0);
+  drive.DrivePolar(1.0, -90_deg, 0.0);
   EXPECT_DOUBLE_EQ(-1.0, fl.Get());
   EXPECT_DOUBLE_EQ(1.0, fr.Get());
   EXPECT_DOUBLE_EQ(1.0, rl.Get());
   EXPECT_DOUBLE_EQ(-1.0, rr.Get());
 
   // Right
-  drive.DrivePolar(1.0, 90.0, 0.0);
+  drive.DrivePolar(1.0, 90_deg, 0.0);
   EXPECT_DOUBLE_EQ(1.0, fl.Get());
   EXPECT_DOUBLE_EQ(-1.0, fr.Get());
   EXPECT_DOUBLE_EQ(-1.0, rl.Get());
   EXPECT_DOUBLE_EQ(1.0, rr.Get());
 
   // Rotate CCW
-  drive.DrivePolar(0.0, 0.0, -1.0);
+  drive.DrivePolar(0.0, 0_deg, -1.0);
   EXPECT_DOUBLE_EQ(-1.0, fl.Get());
   EXPECT_DOUBLE_EQ(1.0, fr.Get());
   EXPECT_DOUBLE_EQ(-1.0, rl.Get());
   EXPECT_DOUBLE_EQ(1.0, rr.Get());
 
   // Rotate CW
-  drive.DrivePolar(0.0, 0.0, 1.0);
+  drive.DrivePolar(0.0, 0_deg, 1.0);
   EXPECT_DOUBLE_EQ(1.0, fl.Get());
   EXPECT_DOUBLE_EQ(-1.0, fr.Get());
   EXPECT_DOUBLE_EQ(1.0, rl.Get());
diff --git a/wpilibc/src/test/native/cpp/event/NetworkBooleanEventTest.cpp b/wpilibc/src/test/native/cpp/event/NetworkBooleanEventTest.cpp
new file mode 100644
index 0000000..d2ac7c4
--- /dev/null
+++ b/wpilibc/src/test/native/cpp/event/NetworkBooleanEventTest.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 <networktables/BooleanTopic.h>
+#include <networktables/NetworkTableInstance.h>
+
+#include "frc/event/EventLoop.h"
+#include "frc/event/NetworkBooleanEvent.h"
+#include "gtest/gtest.h"
+
+using namespace frc;
+
+class NetworkBooleanEventTest : public ::testing::Test {
+ public:
+  NetworkBooleanEventTest() {
+    m_inst = nt::NetworkTableInstance::Create();
+    m_inst.StartLocal();
+  }
+
+  ~NetworkBooleanEventTest() override {
+    nt::NetworkTableInstance::Destroy(m_inst);
+  }
+
+  nt::NetworkTableInstance m_inst;
+};
+
+TEST_F(NetworkBooleanEventTest, Set) {
+  EventLoop loop;
+  int counter = 0;
+
+  auto pub = m_inst.GetTable("TestTable")->GetBooleanTopic("Test").Publish();
+
+  NetworkBooleanEvent(&loop, m_inst, "TestTable", "Test").IfHigh([&] {
+    ++counter;
+  });
+  pub.Set(false);
+  loop.Poll();
+  EXPECT_EQ(0, counter);
+  pub.Set(true);
+  loop.Poll();
+  EXPECT_EQ(1, counter);
+  pub.Set(false);
+  loop.Poll();
+  EXPECT_EQ(1, counter);
+}
diff --git a/wpilibc/src/test/native/cpp/main.cpp b/wpilibc/src/test/native/cpp/main.cpp
index 6aea19a..ba29975 100644
--- a/wpilibc/src/test/native/cpp/main.cpp
+++ b/wpilibc/src/test/native/cpp/main.cpp
@@ -6,9 +6,18 @@
 
 #include "gtest/gtest.h"
 
+#ifndef __FRC_ROBORIO__
+namespace frc::impl {
+void ResetMotorSafety();
+}
+#endif
+
 int main(int argc, char** argv) {
   HAL_Initialize(500, 0);
   ::testing::InitGoogleTest(&argc, argv);
   int ret = RUN_ALL_TESTS();
+#ifndef __FRC_ROBORIO__
+  frc::impl::ResetMotorSafety();
+#endif
   return ret;
 }
diff --git a/wpilibc/src/test/native/cpp/MockMotorController.cpp b/wpilibc/src/test/native/cpp/motorcontrol/MockMotorController.cpp
similarity index 93%
rename from wpilibc/src/test/native/cpp/MockMotorController.cpp
rename to wpilibc/src/test/native/cpp/motorcontrol/MockMotorController.cpp
index 62fd54a..e0c9ee9 100644
--- a/wpilibc/src/test/native/cpp/MockMotorController.cpp
+++ b/wpilibc/src/test/native/cpp/motorcontrol/MockMotorController.cpp
@@ -2,7 +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 "MockMotorController.h"
+#include "motorcontrol/MockMotorController.h"
 
 using namespace frc;
 
diff --git a/wpilibc/src/test/native/cpp/motorcontrol/MotorControllerGroupTest.cpp b/wpilibc/src/test/native/cpp/motorcontrol/MotorControllerGroupTest.cpp
new file mode 100644
index 0000000..48cf700
--- /dev/null
+++ b/wpilibc/src/test/native/cpp/motorcontrol/MotorControllerGroupTest.cpp
@@ -0,0 +1,125 @@
+// 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 "frc/motorcontrol/MotorControllerGroup.h"  // NOLINT(build/include_order)
+
+#include <memory>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "motorcontrol/MockMotorController.h"
+
+using namespace frc;
+
+enum MotorControllerGroupTestType { TEST_ONE, TEST_TWO, TEST_THREE };
+
+std::ostream& operator<<(std::ostream& os,
+                         const MotorControllerGroupTestType& type) {
+  switch (type) {
+    case TEST_ONE:
+      os << "MotorControllerGroup with one motor controller";
+      break;
+    case TEST_TWO:
+      os << "MotorControllerGroup with two motor controllers";
+      break;
+    case TEST_THREE:
+      os << "MotorControllerGroup with three motor controllers";
+      break;
+  }
+
+  return os;
+}
+
+/**
+ * A fixture used for MotorControllerGroup testing.
+ */
+class MotorControllerGroupTest
+    : public testing::TestWithParam<MotorControllerGroupTestType> {
+ protected:
+  std::vector<MockMotorController> m_motorControllers;
+  std::unique_ptr<MotorControllerGroup> m_group;
+
+  void SetUp() override {
+    switch (GetParam()) {
+      case TEST_ONE: {
+        m_motorControllers.emplace_back();
+        m_group = std::make_unique<MotorControllerGroup>(m_motorControllers[0]);
+        break;
+      }
+
+      case TEST_TWO: {
+        m_motorControllers.emplace_back();
+        m_motorControllers.emplace_back();
+        m_group = std::make_unique<MotorControllerGroup>(m_motorControllers[0],
+                                                         m_motorControllers[1]);
+        break;
+      }
+
+      case TEST_THREE: {
+        m_motorControllers.emplace_back();
+        m_motorControllers.emplace_back();
+        m_motorControllers.emplace_back();
+        m_group = std::make_unique<MotorControllerGroup>(m_motorControllers[0],
+                                                         m_motorControllers[1],
+                                                         m_motorControllers[2]);
+        break;
+      }
+    }
+  }
+};
+
+TEST_P(MotorControllerGroupTest, Set) {
+  m_group->Set(1.0);
+
+  for (auto& motorController : m_motorControllers) {
+    EXPECT_FLOAT_EQ(motorController.Get(), 1.0);
+  }
+}
+
+TEST_P(MotorControllerGroupTest, GetInverted) {
+  m_group->SetInverted(true);
+
+  EXPECT_TRUE(m_group->GetInverted());
+}
+
+TEST_P(MotorControllerGroupTest, SetInvertedDoesNotModifyMotorControllers) {
+  for (auto& motorController : m_motorControllers) {
+    motorController.SetInverted(false);
+  }
+  m_group->SetInverted(true);
+
+  for (auto& motorController : m_motorControllers) {
+    EXPECT_EQ(motorController.GetInverted(), false);
+  }
+}
+
+TEST_P(MotorControllerGroupTest, SetInvertedDoesInvert) {
+  m_group->SetInverted(true);
+  m_group->Set(1.0);
+
+  for (auto& motorController : m_motorControllers) {
+    EXPECT_FLOAT_EQ(motorController.Get(), -1.0);
+  }
+}
+
+TEST_P(MotorControllerGroupTest, Disable) {
+  m_group->Set(1.0);
+  m_group->Disable();
+
+  for (auto& motorController : m_motorControllers) {
+    EXPECT_FLOAT_EQ(motorController.Get(), 0.0);
+  }
+}
+
+TEST_P(MotorControllerGroupTest, StopMotor) {
+  m_group->Set(1.0);
+  m_group->StopMotor();
+
+  for (auto& motorController : m_motorControllers) {
+    EXPECT_FLOAT_EQ(motorController.Get(), 0.0);
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(Tests, MotorControllerGroupTest,
+                         testing::Values(TEST_ONE, TEST_TWO, TEST_THREE));
diff --git a/wpilibc/src/test/native/cpp/shuffleboard/ShuffleboardInstanceTest.cpp b/wpilibc/src/test/native/cpp/shuffleboard/ShuffleboardInstanceTest.cpp
index d370a2d..0b0c8df 100644
--- a/wpilibc/src/test/native/cpp/shuffleboard/ShuffleboardInstanceTest.cpp
+++ b/wpilibc/src/test/native/cpp/shuffleboard/ShuffleboardInstanceTest.cpp
@@ -31,8 +31,8 @@
                    .WithWidget("Text View")
                    .GetEntry();
 
-  EXPECT_EQ("string", entry.GetString("")) << "Wrong entry value";
-  EXPECT_EQ("/Shuffleboard/Tab Title/List/Data", entry.GetName())
+  EXPECT_EQ("string", entry->GetString("")) << "Wrong entry value";
+  EXPECT_EQ("/Shuffleboard/Tab Title/List/Data", entry->GetTopic().GetName())
       << "Entry path generated incorrectly";
 }
 
@@ -48,9 +48,9 @@
                    .Add("Value", "string")
                    .GetEntry();
 
-  EXPECT_EQ("string", entry.GetString("")) << "Wrong entry value";
+  EXPECT_EQ("string", entry->GetString("")) << "Wrong entry value";
   EXPECT_EQ("/Shuffleboard/Tab/First/Second/Third/Fourth/Value",
-            entry.GetName())
+            entry->GetTopic().GetName())
       << "Entry path generated incorrectly";
 }
 
@@ -66,9 +66,9 @@
   frc::SimpleWidget& widget = fourth.Add("Value", "string");
   auto entry = widget.GetEntry();
 
-  EXPECT_EQ("string", entry.GetString("")) << "Wrong entry value";
+  EXPECT_EQ("string", entry->GetString("")) << "Wrong entry value";
   EXPECT_EQ("/Shuffleboard/Tab/First/Second/Third/Fourth/Value",
-            entry.GetName())
+            entry->GetTopic().GetName())
       << "Entry path generated incorrectly";
 }
 
@@ -97,12 +97,12 @@
   // Note: we use the unsafe `GetBoolean()` method because if the value is NOT
   // a boolean, or if it is not present, then something has clearly gone very,
   // very wrong
-  bool controllable = controllableEntry.GetValue()->GetBoolean();
+  bool controllable = controllableEntry.GetValue().GetBoolean();
   // Sanity check
   EXPECT_TRUE(controllable)
       << "The nested actuator widget should be enabled by default";
   shuffleboardInst.DisableActuatorWidgets();
-  controllable = controllableEntry.GetValue()->GetBoolean();
+  controllable = controllableEntry.GetValue().GetBoolean();
   EXPECT_FALSE(controllable)
       << "The nested actuator widget should have been disabled";
 }
diff --git a/wpilibc/src/test/native/cpp/shuffleboard/SuppliedValueWidgetTest.cpp b/wpilibc/src/test/native/cpp/shuffleboard/SuppliedValueWidgetTest.cpp
index c8449e5..d964639 100644
--- a/wpilibc/src/test/native/cpp/shuffleboard/SuppliedValueWidgetTest.cpp
+++ b/wpilibc/src/test/native/cpp/shuffleboard/SuppliedValueWidgetTest.cpp
@@ -33,7 +33,7 @@
   auto entry = m_ntInst.inst.GetEntry("/Shuffleboard/Tab/String");
 
   m_shuffleboardInst.Update();
-  EXPECT_EQ("foo", entry.GetValue()->GetString());
+  EXPECT_EQ("foo", entry.GetValue().GetString());
 }
 
 TEST_F(SuppliedValueWidgetTest, AddNumber) {
@@ -42,7 +42,7 @@
   auto entry = m_ntInst.inst.GetEntry("/Shuffleboard/Tab/Num");
 
   m_shuffleboardInst.Update();
-  EXPECT_FLOAT_EQ(1.0, entry.GetValue()->GetDouble());
+  EXPECT_FLOAT_EQ(1.0, entry.GetValue().GetDouble());
 }
 
 TEST_F(SuppliedValueWidgetTest, AddBoolean) {
@@ -51,7 +51,7 @@
   auto entry = m_ntInst.inst.GetEntry("/Shuffleboard/Tab/Bool");
 
   m_shuffleboardInst.Update();
-  EXPECT_EQ(true, entry.GetValue()->GetBoolean());
+  EXPECT_EQ(true, entry.GetValue().GetBoolean());
 }
 
 TEST_F(SuppliedValueWidgetTest, AddStringArray) {
@@ -60,7 +60,7 @@
   auto entry = m_ntInst.inst.GetEntry("/Shuffleboard/Tab/Strings");
 
   m_shuffleboardInst.Update();
-  auto actual = entry.GetValue()->GetStringArray();
+  auto actual = entry.GetValue().GetStringArray();
 
   EXPECT_EQ(strings.size(), actual.size());
   for (size_t i = 0; i < strings.size(); i++) {
@@ -74,7 +74,7 @@
   auto entry = m_ntInst.inst.GetEntry("/Shuffleboard/Tab/Numbers");
 
   m_shuffleboardInst.Update();
-  auto actual = entry.GetValue()->GetDoubleArray();
+  auto actual = entry.GetValue().GetDoubleArray();
 
   EXPECT_EQ(nums.size(), actual.size());
   for (size_t i = 0; i < nums.size(); i++) {
@@ -88,7 +88,7 @@
   auto entry = m_ntInst.inst.GetEntry("/Shuffleboard/Tab/Booleans");
 
   m_shuffleboardInst.Update();
-  auto actual = entry.GetValue()->GetBooleanArray();
+  auto actual = entry.GetValue().GetBooleanArray();
 
   EXPECT_EQ(bools.size(), actual.size());
   for (size_t i = 0; i < bools.size(); i++) {
@@ -97,11 +97,11 @@
 }
 
 TEST_F(SuppliedValueWidgetTest, AddRaw) {
-  std::string_view bytes = "\1\2\3";
+  std::vector<uint8_t> bytes = {1, 2, 3};
   m_tab->AddRaw("Raw", [&bytes]() { return bytes; });
   auto entry = m_ntInst.inst.GetEntry("/Shuffleboard/Tab/Raw");
 
   m_shuffleboardInst.Update();
-  auto actual = entry.GetValue()->GetRaw();
-  EXPECT_EQ(bytes, actual);
+  auto actual = entry.GetValue().GetRaw();
+  EXPECT_EQ(bytes, std::vector<uint8_t>(actual.begin(), actual.end()));
 }
diff --git a/wpilibc/src/test/native/cpp/simulation/AddressableLEDSimTest.cpp b/wpilibc/src/test/native/cpp/simulation/AddressableLEDSimTest.cpp
index ad4452e..e450477 100644
--- a/wpilibc/src/test/native/cpp/simulation/AddressableLEDSimTest.cpp
+++ b/wpilibc/src/test/native/cpp/simulation/AddressableLEDSimTest.cpp
@@ -4,6 +4,8 @@
 
 #include "frc/simulation/AddressableLEDSim.h"  // NOLINT(build/include_order)
 
+#include <array>
+
 #include <hal/HAL.h>
 
 #include "callback_helpers/TestCallbackHelpers.h"
diff --git a/wpilibc/src/test/native/cpp/simulation/AnalogEncoderSimTest.cpp b/wpilibc/src/test/native/cpp/simulation/AnalogEncoderSimTest.cpp
index 375e21a..0a1b687 100644
--- a/wpilibc/src/test/native/cpp/simulation/AnalogEncoderSimTest.cpp
+++ b/wpilibc/src/test/native/cpp/simulation/AnalogEncoderSimTest.cpp
@@ -2,9 +2,10 @@
 // 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 <numbers>
+
 #include <hal/HAL.h>
 #include <units/math.h>
-#include <wpi/numbers>
 
 #include "frc/AnalogEncoder.h"
 #include "frc/AnalogInput.h"
@@ -22,6 +23,6 @@
   encoderSim.SetPosition(180_deg);
   EXPECT_NEAR(encoder.Get().value(), 0.5, 1E-8);
   EXPECT_NEAR(encoderSim.GetTurns().value(), 0.5, 1E-8);
-  EXPECT_NEAR(encoderSim.GetPosition().Radians().value(), wpi::numbers::pi,
+  EXPECT_NEAR(encoderSim.GetPosition().Radians().value(), std::numbers::pi,
               1E-8);
 }
diff --git a/wpilibc/src/test/native/cpp/simulation/DifferentialDrivetrainSimTest.cpp b/wpilibc/src/test/native/cpp/simulation/DifferentialDrivetrainSimTest.cpp
index 8ccfcd6..6edfde0 100644
--- a/wpilibc/src/test/native/cpp/simulation/DifferentialDrivetrainSimTest.cpp
+++ b/wpilibc/src/test/native/cpp/simulation/DifferentialDrivetrainSimTest.cpp
@@ -30,17 +30,17 @@
   frc::LinearPlantInversionFeedforward feedforward{plant, 20_ms};
   frc::RamseteController ramsete;
 
-  feedforward.Reset(Eigen::Vector<double, 2>{0.0, 0.0});
+  feedforward.Reset(frc::Vectord<2>{0.0, 0.0});
 
   // Ground truth.
-  Eigen::Vector<double, 7> groundTruthX = Eigen::Vector<double, 7>::Zero();
+  frc::Vectord<7> groundTruthX = frc::Vectord<7>::Zero();
 
   frc::TrajectoryConfig config{1_mps, 1_mps_sq};
   config.AddConstraint(
       frc::DifferentialDriveKinematicsConstraint(kinematics, 1_mps));
 
   auto trajectory = frc::TrajectoryGenerator::GenerateTrajectory(
-      frc::Pose2d(), {}, frc::Pose2d(2_m, 2_m, 0_rad), config);
+      frc::Pose2d{}, {}, frc::Pose2d{2_m, 2_m, 0_rad}, config);
 
   for (auto t = 0_s; t < trajectory.TotalTime(); t += 20_ms) {
     auto state = trajectory.Sample(t);
@@ -48,15 +48,15 @@
 
     auto [l, r] = kinematics.ToWheelSpeeds(ramseteOut);
     auto voltages =
-        feedforward.Calculate(Eigen::Vector<double, 2>{l.value(), r.value()});
+        feedforward.Calculate(frc::Vectord<2>{l.value(), r.value()});
 
     // Sim periodic code.
-    sim.SetInputs(units::volt_t(voltages(0, 0)), units::volt_t(voltages(1, 0)));
+    sim.SetInputs(units::volt_t{voltages(0, 0)}, units::volt_t{voltages(1, 0)});
     sim.Update(20_ms);
 
     // Update ground truth.
     groundTruthX = frc::RK4(
-        [&sim](const auto& x, const auto& u) -> Eigen::Vector<double, 7> {
+        [&sim](const auto& x, const auto& u) -> frc::Vectord<7> {
           return sim.Dynamics(x, u);
         },
         groundTruthX, voltages, 20_ms);
diff --git a/wpilibc/src/test/native/cpp/simulation/DriverStationSimTest.cpp b/wpilibc/src/test/native/cpp/simulation/DriverStationSimTest.cpp
index d01ca27..6aa31c6 100644
--- a/wpilibc/src/test/native/cpp/simulation/DriverStationSimTest.cpp
+++ b/wpilibc/src/test/native/cpp/simulation/DriverStationSimTest.cpp
@@ -132,6 +132,7 @@
   // B1
   allianceStation = HAL_AllianceStationID_kBlue1;
   DriverStationSim::SetAllianceStationId(allianceStation);
+  frc::sim::DriverStationSim::NotifyNewData();
   EXPECT_EQ(allianceStation, DriverStationSim::GetAllianceStationId());
   EXPECT_EQ(DriverStation::kBlue, DriverStation::GetAlliance());
   EXPECT_EQ(1, DriverStation::GetLocation());
@@ -141,6 +142,7 @@
   // B2
   allianceStation = HAL_AllianceStationID_kBlue2;
   DriverStationSim::SetAllianceStationId(allianceStation);
+  frc::sim::DriverStationSim::NotifyNewData();
   EXPECT_EQ(allianceStation, DriverStationSim::GetAllianceStationId());
   EXPECT_EQ(DriverStation::kBlue, DriverStation::GetAlliance());
   EXPECT_EQ(2, DriverStation::GetLocation());
@@ -150,6 +152,7 @@
   // B3
   allianceStation = HAL_AllianceStationID_kBlue3;
   DriverStationSim::SetAllianceStationId(allianceStation);
+  frc::sim::DriverStationSim::NotifyNewData();
   EXPECT_EQ(allianceStation, DriverStationSim::GetAllianceStationId());
   EXPECT_EQ(DriverStation::kBlue, DriverStation::GetAlliance());
   EXPECT_EQ(3, DriverStation::GetLocation());
@@ -159,6 +162,7 @@
   // R1
   allianceStation = HAL_AllianceStationID_kRed1;
   DriverStationSim::SetAllianceStationId(allianceStation);
+  frc::sim::DriverStationSim::NotifyNewData();
   EXPECT_EQ(allianceStation, DriverStationSim::GetAllianceStationId());
   EXPECT_EQ(DriverStation::kRed, DriverStation::GetAlliance());
   EXPECT_EQ(1, DriverStation::GetLocation());
@@ -168,6 +172,7 @@
   // R2
   allianceStation = HAL_AllianceStationID_kRed2;
   DriverStationSim::SetAllianceStationId(allianceStation);
+  frc::sim::DriverStationSim::NotifyNewData();
   EXPECT_EQ(allianceStation, DriverStationSim::GetAllianceStationId());
   EXPECT_EQ(DriverStation::kRed, DriverStation::GetAlliance());
   EXPECT_EQ(2, DriverStation::GetLocation());
@@ -177,6 +182,7 @@
   // R3
   allianceStation = HAL_AllianceStationID_kRed3;
   DriverStationSim::SetAllianceStationId(allianceStation);
+  frc::sim::DriverStationSim::NotifyNewData();
   EXPECT_EQ(allianceStation, DriverStationSim::GetAllianceStationId());
   EXPECT_EQ(DriverStation::kRed, DriverStation::GetAlliance());
   EXPECT_EQ(3, DriverStation::GetLocation());
@@ -211,6 +217,7 @@
                                                         false);
   constexpr double kTestTime = 19.174;
   DriverStationSim::SetMatchTime(kTestTime);
+  frc::sim::DriverStationSim::NotifyNewData();
   EXPECT_EQ(kTestTime, DriverStationSim::GetMatchTime());
   EXPECT_EQ(kTestTime, DriverStation::GetMatchTime());
   EXPECT_TRUE(callback.WasTriggered());
diff --git a/wpilibc/src/test/native/cpp/simulation/ElevatorSimTest.cpp b/wpilibc/src/test/native/cpp/simulation/ElevatorSimTest.cpp
index 03022ab..3c647e6 100644
--- a/wpilibc/src/test/native/cpp/simulation/ElevatorSimTest.cpp
+++ b/wpilibc/src/test/native/cpp/simulation/ElevatorSimTest.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 <units/math.h>
 #include <units/time.h>
 
 #include "frc/Encoder.h"
@@ -15,10 +16,12 @@
 #include "frc/system/plant/LinearSystemId.h"
 #include "gtest/gtest.h"
 
+#define EXPECT_NEAR_UNITS(val1, val2, eps) \
+  EXPECT_LE(units::math::abs(val1 - val2), eps)
+
 TEST(ElevatorSimTest, StateSpaceSim) {
-  frc::sim::ElevatorSim sim(frc::DCMotor::Vex775Pro(4), 14.67, 8_kg,
-                            units::meter_t(0.75 * 25.4 / 1000.0), 0_m, 3_m,
-                            {0.01});
+  frc::sim::ElevatorSim sim(frc::DCMotor::Vex775Pro(4), 14.67, 8_kg, 0.75_in,
+                            0_m, 3_m, true, {0.01});
   frc2::PIDController controller(10, 0.0, 0.0);
 
   frc::PWMVictorSPX motor(0);
@@ -30,8 +33,7 @@
     auto nextVoltage = controller.Calculate(encoderSim.GetDistance());
     motor.Set(nextVoltage / frc::RobotController::GetInputVoltage());
 
-    Eigen::Vector<double, 1> u{motor.Get() *
-                               frc::RobotController::GetInputVoltage()};
+    frc::Vectord<1> u{motor.Get() * frc::RobotController::GetInputVoltage()};
     sim.SetInput(u);
     sim.Update(20_ms);
 
@@ -43,11 +45,10 @@
 }
 
 TEST(ElevatorSimTest, MinMax) {
-  frc::sim::ElevatorSim sim(frc::DCMotor::Vex775Pro(4), 14.67, 8_kg,
-                            units::meter_t(0.75 * 25.4 / 1000.0), 0_m, 1_m,
-                            {0.01});
+  frc::sim::ElevatorSim sim(frc::DCMotor::Vex775Pro(4), 14.67, 8_kg, 0.75_in,
+                            0_m, 1_m, true, {0.01});
   for (size_t i = 0; i < 100; ++i) {
-    sim.SetInput(Eigen::Vector<double, 1>{0.0});
+    sim.SetInput(frc::Vectord<1>{0.0});
     sim.Update(20_ms);
 
     auto height = sim.GetPosition();
@@ -55,7 +56,7 @@
   }
 
   for (size_t i = 0; i < 100; ++i) {
-    sim.SetInput(Eigen::Vector<double, 1>{12.0});
+    sim.SetInput(frc::Vectord<1>{12.0});
     sim.Update(20_ms);
 
     auto height = sim.GetPosition();
@@ -64,26 +65,19 @@
 }
 
 TEST(ElevatorSimTest, Stability) {
-  static constexpr double kElevatorGearing = 100.0;
-  static constexpr units::meter_t kElevatorDrumRadius = 0.5_in;
-  static constexpr units::kilogram_t kCarriageMass = 4.0_kg;
-  frc::DCMotor m_elevatorGearbox = frc::DCMotor::Vex775Pro(4);
+  frc::sim::ElevatorSim sim{
+      frc::DCMotor::Vex775Pro(4), 100, 4_kg, 0.5_in, 0_m, 10_m, true};
 
-  frc::LinearSystem<2, 1, 1> system = frc::LinearSystemId::ElevatorSystem(
-      m_elevatorGearbox, kCarriageMass, kElevatorDrumRadius, kElevatorGearing);
-
-  Eigen::Vector<double, 2> x0{0.0, 0.0};
-  Eigen::Vector<double, 1> u0{12.0};
-
-  Eigen::Vector<double, 2> x1{0.0, 0.0};
-  for (size_t i = 0; i < 50; i++) {
-    x1 = frc::RKDP(
-        [&](const Eigen::Vector<double, 2>& x,
-            const Eigen::Vector<double, 1>& u) -> Eigen::Vector<double, 2> {
-          return system.A() * x + system.B() * u;
-        },
-        x1, u0, 0.020_s);
+  sim.SetState(frc::Vectord<2>{0.0, 0.0});
+  sim.SetInput(frc::Vectord<1>{12.0});
+  for (int i = 0; i < 50; ++i) {
+    sim.Update(20_ms);
   }
 
-  EXPECT_NEAR(x1(0), system.CalculateX(x0, u0, 1_s)(0), 0.1);
+  frc::LinearSystem<2, 1, 1> system = frc::LinearSystemId::ElevatorSystem(
+      frc::DCMotor::Vex775Pro(4), 4_kg, 0.5_in, 100);
+  EXPECT_NEAR_UNITS(
+      units::meter_t{system.CalculateX(frc::Vectord<2>{0.0, 0.0},
+                                       frc::Vectord<1>{12.0}, 20_ms * 50)(0)},
+      sim.GetPosition(), 1_cm);
 }
diff --git a/wpilibc/src/test/native/cpp/simulation/EncoderSimTest.cpp b/wpilibc/src/test/native/cpp/simulation/EncoderSimTest.cpp
index be13ca5..7722ccc 100644
--- a/wpilibc/src/test/native/cpp/simulation/EncoderSimTest.cpp
+++ b/wpilibc/src/test/native/cpp/simulation/EncoderSimTest.cpp
@@ -91,7 +91,9 @@
   auto cb = sim.RegisterPeriodCallback(callback.GetCallback(), false);
   sim.SetPeriod(123.456);
   EXPECT_EQ(123.456, sim.GetPeriod());
+  WPI_IGNORE_DEPRECATED
   EXPECT_EQ(123.456, encoder.GetPeriod().value());
+  WPI_UNIGNORE_DEPRECATED
   EXPECT_EQ(kDefaultDistancePerPulse / 123.456, encoder.GetRate());
 
   EXPECT_TRUE(callback.WasTriggered());
@@ -110,7 +112,9 @@
   DoubleCallback callback;
   auto cb = sim.RegisterMaxPeriodCallback(callback.GetCallback(), false);
 
+  WPI_IGNORE_DEPRECATED
   encoder.SetMaxPeriod(units::second_t{123.456});
+  WPI_UNIGNORE_DEPRECATED
   EXPECT_EQ(123.456, sim.GetMaxPeriod());
 
   EXPECT_TRUE(callback.WasTriggered());
diff --git a/wpilibc/src/test/native/cpp/simulation/RoboRioSimTest.cpp b/wpilibc/src/test/native/cpp/simulation/RoboRioSimTest.cpp
index c6e93d4..98d4620 100644
--- a/wpilibc/src/test/native/cpp/simulation/RoboRioSimTest.cpp
+++ b/wpilibc/src/test/native/cpp/simulation/RoboRioSimTest.cpp
@@ -207,4 +207,41 @@
   EXPECT_EQ(kTestFaults, RobotController::GetFaultCount3V3());
 }
 
+TEST(RoboRioSimTest, SetSerialNumber) {
+  const std::string kSerialNum = "Hello";
+
+  RoboRioSim::ResetData();
+
+  RoboRioSim::SetSerialNumber(kSerialNum);
+  EXPECT_EQ(kSerialNum, RoboRioSim::GetSerialNumber());
+  EXPECT_EQ(kSerialNum, RobotController::GetSerialNumber());
+
+  const std::string kSerialNumberOverflow = "SerialNumber";
+  const std::string kSerialNumberTruncated = kSerialNumberOverflow.substr(0, 8);
+
+  RoboRioSim::SetSerialNumber(kSerialNumberOverflow);
+  EXPECT_EQ(kSerialNumberTruncated, RoboRioSim::GetSerialNumber());
+  EXPECT_EQ(kSerialNumberTruncated, RobotController::GetSerialNumber());
+}
+
+TEST(RoboRioSimTest, SetComments) {
+  const std::string kComments =
+      "Hello! These are comments in the roboRIO web interface!";
+
+  RoboRioSim::ResetData();
+
+  RoboRioSim::SetComments(kComments);
+  EXPECT_EQ(kComments, RoboRioSim::GetComments());
+  EXPECT_EQ(kComments, RobotController::GetComments());
+
+  const std::string kCommentsOverflow =
+      "Hello! These are comments in the roboRIO web interface! This comment "
+      "exceeds 64 characters!";
+  const std::string kCommentsTruncated = kCommentsOverflow.substr(0, 64);
+
+  RoboRioSim::SetComments(kCommentsOverflow);
+  EXPECT_EQ(kCommentsTruncated, RoboRioSim::GetComments());
+  EXPECT_EQ(kCommentsTruncated, RobotController::GetComments());
+}
+
 }  // namespace frc::sim
diff --git a/wpilibc/src/test/native/cpp/simulation/SingleJointedArmSimTest.cpp b/wpilibc/src/test/native/cpp/simulation/SingleJointedArmSimTest.cpp
index 03df12b..2e4c793 100644
--- a/wpilibc/src/test/native/cpp/simulation/SingleJointedArmSimTest.cpp
+++ b/wpilibc/src/test/native/cpp/simulation/SingleJointedArmSimTest.cpp
@@ -2,7 +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 <wpi/numbers>
+#include <numbers>
 
 #include "frc/simulation/SingleJointedArmSim.h"
 #include "gtest/gtest.h"
@@ -10,13 +10,13 @@
 TEST(SingleJointedArmTest, Disabled) {
   frc::sim::SingleJointedArmSim sim(frc::DCMotor::Vex775Pro(2), 100, 3_kg_sq_m,
                                     9.5_in, -180_deg, 0_deg, 10_lb, true);
-  sim.SetState(Eigen::Vector<double, 2>{0.0, 0.0});
+  sim.SetState(frc::Vectord<2>{0.0, 0.0});
 
   for (size_t i = 0; i < 12 / 0.02; ++i) {
-    sim.SetInput(Eigen::Vector<double, 1>{0.0});
+    sim.SetInput(frc::Vectord<1>{0.0});
     sim.Update(20_ms);
   }
 
   // The arm should swing down.
-  EXPECT_NEAR(sim.GetAngle().value(), -wpi::numbers::pi / 2, 0.01);
+  EXPECT_NEAR(sim.GetAngle().value(), -std::numbers::pi / 2, 0.01);
 }
diff --git a/wpilibc/src/test/native/cpp/simulation/StateSpaceSimTest.cpp b/wpilibc/src/test/native/cpp/simulation/StateSpaceSimTest.cpp
index 23af83d..7456adb 100644
--- a/wpilibc/src/test/native/cpp/simulation/StateSpaceSimTest.cpp
+++ b/wpilibc/src/test/native/cpp/simulation/StateSpaceSimTest.cpp
@@ -40,14 +40,14 @@
   for (int i = 0; i < 100; i++) {
     // RobotPeriodic runs first
     auto voltageOut = controller.Calculate(encoder.GetRate(), 200.0);
-    motor.SetVoltage(units::volt_t(voltageOut) +
+    motor.SetVoltage(units::volt_t{voltageOut} +
                      feedforward.Calculate(200_rad_per_s));
 
     // Then, SimulationPeriodic runs
     frc::sim::RoboRioSim::SetVInVoltage(
         frc::sim::BatterySim::Calculate({sim.GetCurrentDraw()}));
-    sim.SetInput(Eigen::Vector<double, 1>{
-        motor.Get() * frc::RobotController::GetInputVoltage()});
+    sim.SetInput(
+        frc::Vectord<1>{motor.Get() * frc::RobotController::GetInputVoltage()});
     sim.Update(20_ms);
     encoderSim.SetRate(sim.GetAngularVelocity().value());
   }
diff --git a/wpilibc/src/test/native/include/MockMotorController.h b/wpilibc/src/test/native/include/motorcontrol/MockMotorController.h
similarity index 100%
rename from wpilibc/src/test/native/include/MockMotorController.h
rename to wpilibc/src/test/native/include/motorcontrol/MockMotorController.h