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/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);
+}