Merge changes I97756b30,I0529fc0c

* changes:
  Merge commit 'fe830c78bac85aea842c4d329b9a90087ddc6537' into master
  Squashed 'third_party/autocxx/' changes from c35090b75..fdfb26e26
diff --git a/WORKSPACE b/WORKSPACE
index 51f9c8f..1be9988 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -724,29 +724,29 @@
 )
 
 http_archive(
-    name = "ctre_phoenixpro_api_cpp_headers",
+    name = "ctre_phoenix6_api_cpp_headers",
     build_file_content = """
 cc_library(
     name = 'api-cpp',
     visibility = ['//visibility:public'],
-    hdrs = glob(['ctre/phoenixpro/**/*.hpp', 'units/*.h']),
+    hdrs = glob(['ctre/phoenix6/**/*.hpp', 'units/*.h']),
     includes = ["."],
     deps = ["@//third_party/allwpilib/wpimath"],
 )
 """,
-    sha256 = "340a9c8e726e2eb365b7a40a722df05fe7c7072c5c4a617fa0218eb6d074ad9f",
+    sha256 = "3c4da9f46c751d4981697da26d3c8680f40c87090782f5adf63412e34508f372",
     urls = [
-        "https://maven.ctr-electronics.com/release/com/ctre/phoenixpro/api-cpp/23.0.11/api-cpp-23.0.11-headers.zip",
+        "https://maven.ctr-electronics.com/release/com/ctre/phoenix6/api-cpp/23.2.2/api-cpp-23.2.2-headers.zip",
     ],
 )
 
 http_archive(
-    name = "ctre_phoenixpro_api_cpp_athena",
+    name = "ctre_phoenix6_api_cpp_athena",
     build_file_content = """
 filegroup(
     name = 'shared_libraries',
     srcs = [
-        'linux/athena/shared/libCTRE_PhoenixPro.so',
+        'linux/athena/shared/libCTRE_Phoenix6.so',
     ],
     visibility = ['//visibility:public'],
 )
@@ -754,18 +754,18 @@
 cc_library(
     name = 'api-cpp',
     visibility = ['//visibility:public'],
-    srcs = ['linux/athena/shared/libCTRE_PhoenixPro.so'],
+    srcs = ['linux/athena/shared/libCTRE_Phoenix6.so'],
     target_compatible_with = ['@//tools/platforms/hardware:roborio'],
 )
 """,
-    sha256 = "11f392bebfe54f512be9ef59809e1a10c4497e0ce92970645f054e7e04fe7ef6",
+    sha256 = "8391cbd24582c951a8fdbcff533243be718fc54e091c068d5441bb0c18ff822c",
     urls = [
-        "https://maven.ctr-electronics.com/release/com/ctre/phoenixpro/api-cpp/23.0.11/api-cpp-23.0.11-linuxathena.zip",
+        "https://maven.ctr-electronics.com/release/com/ctre/phoenix6/api-cpp/23.2.2/api-cpp-23.2.2-linuxathena.zip",
     ],
 )
 
 http_archive(
-    name = "ctre_phoenixpro_tools_headers",
+    name = "ctre_phoenix6_tools_headers",
     build_file_content = """
 cc_library(
     name = 'tools',
@@ -773,14 +773,14 @@
     hdrs = glob(['ctre/**/*.h', 'ctre/**/*.hpp']),
 )
 """,
-    sha256 = "7585e1bd9e581dd745e7f040ab521b966b40a04d05bc7fa82d6dafe2fb65764e",
+    sha256 = "33781c9db0a204e257928351c700295aec2bf6e2abb6a49ef237a95e98442a18",
     urls = [
-        "https://maven.ctr-electronics.com/release/com/ctre/phoenixpro/tools/23.0.11/tools-23.0.11-headers.zip",
+        "https://maven.ctr-electronics.com/release/com/ctre/phoenix6/tools/23.2.2/tools-23.2.2-headers.zip",
     ],
 )
 
 http_archive(
-    name = "ctre_phoenixpro_tools_athena",
+    name = "ctre_phoenix6_tools_athena",
     build_file_content = """
 filegroup(
     name = 'shared_libraries',
@@ -797,9 +797,9 @@
     target_compatible_with = ['@//tools/platforms/hardware:roborio'],
 )
 """,
-    sha256 = "b1daadfe782c43ed32c2e1a3956998f9604a3fc9282ef866fd8dc1482f3b8cc9",
+    sha256 = "75ec607f81ab470bc7c01fda2b8ca7b71b7dc3378b370f806f8646db27600504",
     urls = [
-        "https://maven.ctr-electronics.com/release/com/ctre/phoenixpro/tools/23.0.11/tools-23.0.11-linuxathena.zip",
+        "https://maven.ctr-electronics.com/release/com/ctre/phoenix6/tools/23.2.2/tools-23.2.2-linuxathena.zip",
     ],
 )
 
diff --git a/aos/events/BUILD b/aos/events/BUILD
index 07c1f3a..eb77036 100644
--- a/aos/events/BUILD
+++ b/aos/events/BUILD
@@ -3,7 +3,7 @@
 load("//aos:flatbuffers.bzl", "cc_static_flatbuffer")
 load("//aos:config.bzl", "aos_config")
 load("//tools/build_rules:autocxx.bzl", "autocxx_library")
-load("@rules_rust//rust:defs.bzl", "rust_doc_test", "rust_test")
+load("@rules_rust//rust:defs.bzl", "rust_doc", "rust_doc_test", "rust_test")
 
 package(default_visibility = ["//visibility:public"])
 
@@ -181,6 +181,12 @@
     ],
 )
 
+rust_doc(
+    name = "event_loop_runtime_doc",
+    crate = ":event_loop_runtime",
+    target_compatible_with = ["@platforms//cpu:x86_64"],
+)
+
 rust_doc_test(
     name = "event_loop_runtime_doc_test",
     crate = ":event_loop_runtime",
@@ -520,6 +526,15 @@
     ],
 )
 
+cc_library(
+    name = "shm_event_loop_for_rust",
+    hdrs = ["shm_event_loop_for_rust.h"],
+    deps = [
+        ":event_loop",
+        ":simulated_event_loop",
+    ],
+)
+
 autocxx_library(
     name = "simulated_event_loop_rs",
     srcs = ["simulated_event_loop.rs"],
@@ -560,6 +575,66 @@
     ],
 )
 
+autocxx_library(
+    name = "shm_event_loop_rs",
+    srcs = ["shm_event_loop.rs"],
+    crate_name = "aos_events_shm_event_loop",
+    libs = [
+        ":shm_event_loop",
+        ":shm_event_loop_for_rust",
+    ],
+    rs_deps = [
+        "@com_github_google_flatbuffers//rust",
+        "@crate_index//:futures",
+        "//aos:configuration_rust_fbs",
+        "//aos:flatbuffers_rs",
+    ],
+    target_compatible_with = select({
+        "//conditions:default": ["//tools/platforms/rust:has_support"],
+        "//tools:has_msan": ["@platforms//:incompatible"],
+    }),
+    visibility = ["//visibility:public"],
+    deps = [
+        ":event_loop_runtime",
+        "//aos:configuration_rs",
+    ],
+)
+
+rust_doc(
+    name = "shm_event_loop_rs_doc",
+    crate = ":shm_event_loop_rs",
+    target_compatible_with = ["@platforms//cpu:x86_64"],
+)
+
+rust_test(
+    name = "shm_event_loop_rs_test",
+    crate = ":shm_event_loop_rs",
+    data = [":pingpong_config"],
+    # TODO: Make Rust play happy with pic vs nopic. Details at:
+    # https://github.com/bazelbuild/rules_rust/issues/118
+    rustc_flags = ["-Crelocation-model=static"],
+    target_compatible_with = select({
+        "//conditions:default": ["//tools/platforms/rust:has_support"],
+        "//tools:has_msan": ["@platforms//:incompatible"],
+    }),
+    deps = [
+        ":ping_rust_fbs",
+        "//aos:init_rs",
+        "@crate_index//:futures",
+        "@rules_rust//tools/runfiles",
+    ],
+)
+
+rust_doc_test(
+    name = "shm_event_loop_rs_doc_test",
+    crate = ":shm_event_loop_rs",
+    target_compatible_with = ["@platforms//cpu:x86_64"],
+    deps = [
+        ":ping_rust_fbs",
+        ":pong_rust_fbs",
+    ],
+)
+
 cc_test(
     name = "event_scheduler_test",
     srcs = ["event_scheduler_test.cc"],
diff --git a/aos/events/shm_event_loop.rs b/aos/events/shm_event_loop.rs
new file mode 100644
index 0000000..3e387ef
--- /dev/null
+++ b/aos/events/shm_event_loop.rs
@@ -0,0 +1,302 @@
+pub use aos_configuration::{Configuration, ConfigurationExt};
+pub use aos_events_event_loop_runtime::EventLoop;
+pub use aos_events_event_loop_runtime::{CppExitHandle, EventLoopRuntime, ExitHandle};
+
+use aos_configuration_fbs::aos::Configuration as RustConfiguration;
+use aos_flatbuffers::{transmute_table_to, Flatbuffer};
+use autocxx::WithinBox;
+use core::marker::PhantomData;
+use core::pin::Pin;
+use std::boxed::Box;
+use std::ops::{Deref, DerefMut};
+
+autocxx::include_cpp! (
+#include "aos/events/shm_event_loop.h"
+#include "aos/events/shm_event_loop_for_rust.h"
+
+safety!(unsafe)
+
+generate!("aos::ShmEventLoopForRust")
+
+extern_cpp_type!("aos::ExitHandle", crate::CppExitHandle)
+extern_cpp_type!("aos::Configuration", crate::Configuration)
+extern_cpp_type!("aos::EventLoop", crate::EventLoop)
+);
+
+/// A Rust-owned C++ `ShmEventLoop` object.
+pub struct ShmEventLoop<'config> {
+    inner: Pin<Box<ffi::aos::ShmEventLoopForRust>>,
+    _config: PhantomData<&'config Configuration>,
+}
+
+impl<'config> ShmEventLoop<'config> {
+    /// Creates a Rust-owned ShmEventLoop.
+    pub fn new(config: &'config impl Flatbuffer<RustConfiguration<'static>>) -> Self {
+        // SAFETY: The `_config` represents the lifetime of this pointer we're handing off to c++ to
+        // store.
+        let event_loop = unsafe {
+            ffi::aos::ShmEventLoopForRust::new(transmute_table_to::<Configuration>(
+                &config.message()._tab,
+            ))
+        }
+        .within_box();
+
+        Self {
+            inner: event_loop,
+            _config: PhantomData,
+        }
+    }
+
+    /// Provides a runtime to construct the application and runs the event loop.
+    ///
+    /// The runtime is the only way to interact with the event loop. It provides the functionality
+    /// to spawn a task, construct timers, watchers, fetchers, and so on.
+    ///
+    /// Making an [`EventLoopRuntime`] is tricky since the lifetime of the runtime is invariant
+    /// w.r.t the event loop. In other words, the runtime and the event loop must have the same
+    /// lifetime. By providing access to the runtime through an [`FnOnce`], we can guarantee
+    /// that the runtime and the event loop both have the same lifetime.
+    ///
+    /// # Examples
+    ///
+    /// A ping application might do something like the following
+    ///
+    /// ```no_run
+    /// # use aos_events_shm_event_loop::*;
+    /// use ping_rust_fbs::aos::examples as ping;
+    /// use pong_rust_fbs::aos::examples as pong;
+    /// use std::cell::Cell;
+    /// use std::path::Path;
+    /// use aos_configuration::read_config_from;
+    /// use aos_events_event_loop_runtime::{Sender, Watcher};
+    ///
+    /// let config = read_config_from(Path::new("path/to/aos_config.json")).unwrap();
+    /// let event_loop = ShmEventLoop::new(&config);
+    /// event_loop.run_with(|runtime| {
+    ///   // One task will send a ping, the other will listen to pong messages.
+    ///   let mut sender: Sender<ping::Ping> = runtime
+    ///       .make_sender("/test")
+    ///       .expect("Can't create `Ping` sender");
+    ///
+    ///   let on_run = runtime.on_run();
+    ///   // Sends a single ping message.
+    ///   let send_task = async move {
+    ///     on_run.await;
+    ///     let mut builder = sender.make_builder();
+    ///     let mut ping = ping::PingBuilder::new(builder.fbb());
+    ///     ping.add_value(10);
+    ///     let ping = ping.finish();
+    ///     builder.send(ping).expect("Can't send ping");
+    ///   };
+    ///
+    ///   let mut watcher: Watcher<pong::Pong> = runtime
+    ///       .make_watcher("/test")
+    ///       .expect("Can't create `Ping` watcher");
+    ///
+    ///   // Listens to pong messages and prints them.
+    ///   let receive_task = async move {
+    ///     loop {
+    ///       let pong = dbg!(watcher.next().await);
+    ///     }
+    ///   };
+    ///
+    ///   runtime.spawn(async move {
+    ///      futures::join!(send_task, receive_task);
+    ///      std::future::pending().await
+    ///   });
+    /// }); // Event loop starts runnning...
+    /// unreachable!("This can't be reached since no ExitHandle was made");
+    /// ```
+    ///
+    /// `run_with` can also borrow data from the outer scope that can be used in the async task.
+    ///
+    /// ```no_run
+    /// # use aos_events_shm_event_loop::*;
+    /// # use std::cell::Cell;
+    /// # use std::path::Path;
+    /// # use aos_configuration::read_config_from;
+    /// let config = read_config_from(Path::new("path/to/aos_config.json")).unwrap();
+    /// let shared_data = Cell::new(971);
+    /// let shared_data = &shared_data;
+    /// let event_loop = ShmEventLoop::new(&config);
+    /// event_loop.run_with(|runtime| {
+    ///   // Note how `Cell` is enough since the event loop is single threaded.
+    ///   let t1 = async move {
+    ///     shared_data.set(shared_data.get() + 1);
+    ///   };
+    ///   let t2 = async move {
+    ///     shared_data.set(shared_data.get() + 1);
+    ///   };
+    ///
+    ///   runtime.spawn(async move {
+    ///      futures::join!(t1, t2);
+    ///      std::future::pending().await
+    ///   });
+    /// });
+    /// unreachable!("This can't be reached since no ExitHandle was made");
+    /// ```
+    ///
+    /// However, the spawned future must outlive `run_with`.
+    ///
+    /// ```compile_fail
+    /// # use aos_events_shm_event_loop::*;
+    /// # use std::cell::Cell;
+    /// # use std::path::Path;
+    /// # use aos_configuration::read_config_from;
+    /// let config = read_config_from(Path::new("path/to/aos_config.json")).unwrap();
+    /// let event_loop = ShmEventLoop::new(&config);
+    /// event_loop.run_with(|runtime| {
+    ///   // ERROR: `shared_data` doesn't live long enough.
+    ///   let shared_data = Cell::new(971);
+    ///   let t1 = async {
+    ///     shared_data.set(shared_data.get() + 1);
+    ///   };
+    ///   let t2 = async {
+    ///     shared_data.set(shared_data.get() + 1);
+    ///   };
+    ///
+    ///   runtime.spawn(async move {
+    ///      futures::join!(t1, t2);
+    ///      std::future::pending().await
+    ///   });
+    /// });
+    /// ```
+    pub fn run_with<'env, F>(mut self, fun: F)
+    where
+        F: for<'event_loop> FnOnce(&mut Scoped<'event_loop, 'env, EventLoopRuntime<'event_loop>>),
+    {
+        // SAFETY: The runtime and the event loop (i.e. self) both get destroyed at the end of this
+        // scope: first the runtime followed by the event loop. The runtime gets exclusive access
+        // during initialization in `fun` while the event loop remains unused.
+        let runtime = unsafe { EventLoopRuntime::new(self.inner.as_mut().event_loop_mut()) };
+        let mut runtime = Scoped::new(runtime);
+        fun(&mut runtime);
+        self.run();
+    }
+
+    /// Makes an exit handle.
+    ///
+    /// Awaiting on the exit handle is the only way to actually exit the event loop
+    /// task, other than panicking.
+    pub fn make_exit_handle(&mut self) -> ExitHandle {
+        self.inner.as_mut().MakeExitHandle().into()
+    }
+
+    /// Runs the spawned task to completion.
+    fn run(&mut self) {
+        self.inner.as_mut().Run();
+    }
+}
+
+/// A wrapper over some data that lives for the duration of a scope.
+///
+/// This struct ensures the existence of some `'env` which outlives `'scope`. In
+/// the presence of higher-ranked trait bounds which require types that work for
+/// any `'scope`, this allows the compiler to propagate lifetime bounds which
+/// outlive any of the possible `'scope`. This is the simplest way to express
+/// this concept to the compiler right now.
+pub struct Scoped<'scope, 'env: 'scope, T: 'scope> {
+    data: T,
+    _env: PhantomData<fn(&'env ()) -> &'env ()>,
+    _scope: PhantomData<fn(&'scope ()) -> &'scope ()>,
+}
+
+impl<'scope, 'env: 'scope, T: 'scope> Scoped<'scope, 'env, T> {
+    /// Makes the [`Scoped`].
+    pub fn new(data: T) -> Self {
+        Self {
+            data,
+            _env: PhantomData,
+            _scope: PhantomData,
+        }
+    }
+}
+
+impl<'scope, 'env: 'scope, T: 'scope> Deref for Scoped<'scope, 'env, T> {
+    type Target = T;
+    fn deref(&self) -> &Self::Target {
+        &self.data
+    }
+}
+
+impl<'scope, 'env: 'scope, T: 'scope> DerefMut for Scoped<'scope, 'env, T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.data
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use runfiles::Runfiles;
+
+    use aos_configuration::read_config_from;
+    use aos_events_event_loop_runtime::{Sender, Watcher};
+    use aos_init::test_init;
+    use ping_rust_fbs::aos::examples as ping;
+    use std::sync::atomic::{AtomicUsize, Ordering};
+    use std::sync::Barrier;
+
+    /// Tests basic functionality with 2 threads operating their own event loops.
+    #[test]
+    fn smoke_test() {
+        test_init();
+
+        let r = Runfiles::create().unwrap();
+        let config =
+            read_config_from(&r.rlocation("org_frc971/aos/events/pingpong_config.json")).unwrap();
+
+        const VALUE: i32 = 971;
+        let barrier = Barrier::new(2);
+        let count = AtomicUsize::new(0);
+
+        std::thread::scope(|s| {
+            let config = &config;
+            let barrier = &barrier;
+            let count = &count;
+            s.spawn(move || {
+                let mut event_loop = ShmEventLoop::new(config);
+                let exit_handle = event_loop.make_exit_handle();
+                event_loop.run_with(|runtime| {
+                    let mut watcher: Watcher<ping::Ping> = runtime
+                        .make_watcher("/test")
+                        .expect("Can't create `Ping` watcher");
+                    let on_run = runtime.on_run();
+                    runtime.spawn(async move {
+                        on_run.await;
+                        barrier.wait();
+                        let ping = watcher.next().await;
+                        assert_eq!(ping.message().unwrap().value(), VALUE);
+                        count.fetch_add(1, Ordering::Relaxed);
+                        exit_handle.exit().await
+                    });
+                });
+            });
+            s.spawn(move || {
+                let mut event_loop = ShmEventLoop::new(config);
+                let exit_handle = event_loop.make_exit_handle();
+                event_loop.run_with(|runtime| {
+                    let mut sender: Sender<ping::Ping> = runtime
+                        .make_sender("/test")
+                        .expect("Can't create `Ping` sender");
+                    let on_run = runtime.on_run();
+                    runtime.spawn(async move {
+                        on_run.await;
+                        // Give the waiting thread a chance to start.
+                        barrier.wait();
+                        let mut sender = sender.make_builder();
+                        let mut ping = ping::PingBuilder::new(sender.fbb());
+                        ping.add_value(VALUE);
+                        let ping = ping.finish();
+                        sender.send(ping).expect("send should succeed");
+                        count.fetch_add(1, Ordering::Relaxed);
+                        exit_handle.exit().await
+                    });
+                });
+            });
+        });
+
+        assert_eq!(count.into_inner(), 2, "Not all event loops ran.");
+    }
+}
diff --git a/aos/events/shm_event_loop_for_rust.h b/aos/events/shm_event_loop_for_rust.h
new file mode 100644
index 0000000..3a815e1
--- /dev/null
+++ b/aos/events/shm_event_loop_for_rust.h
@@ -0,0 +1,31 @@
+#ifndef AOS_EVENTS_SHM_EVENT_LOOP_FOR_RUST_H_
+#define AOS_EVENTS_SHM_EVENT_LOOP_FOR_RUST_H_
+
+#include <memory>
+
+#include "aos/events/event_loop.h"
+#include "aos/events/shm_event_loop.h"
+
+namespace aos {
+
+class ShmEventLoopForRust {
+ public:
+  ShmEventLoopForRust(const Configuration *configuration)
+      : event_loop_(configuration) {}
+
+  const EventLoop *event_loop() const { return &event_loop_; }
+  EventLoop *event_loop_mut() { return &event_loop_; }
+
+  std::unique_ptr<ExitHandle> MakeExitHandle() {
+    return event_loop_.MakeExitHandle();
+  }
+
+  void Run() { event_loop_.Run(); }
+
+ private:
+  ShmEventLoop event_loop_;
+};
+
+}  // namespace aos
+
+#endif  // AOS_EVENTS_SHM_EVENT_LOOP_FOR_RUST_H_
diff --git a/frc971/BUILD b/frc971/BUILD
index d6a3af8..5613737 100644
--- a/frc971/BUILD
+++ b/frc971/BUILD
@@ -1,3 +1,5 @@
+load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library")
+
 package(default_visibility = ["//visibility:public"])
 
 cc_library(
@@ -21,3 +23,12 @@
     target_compatible_with = ["@platforms//os:linux"],
     visibility = ["//visibility:public"],
 )
+
+flatbuffer_cc_library(
+    name = "can_configuration_fbs",
+    srcs = [
+        ":can_configuration.fbs",
+    ],
+    gen_reflections = 1,
+    visibility = ["//visibility:public"],
+)
diff --git a/y2023/can_configuration.fbs b/frc971/can_configuration.fbs
similarity index 93%
rename from y2023/can_configuration.fbs
rename to frc971/can_configuration.fbs
index 75e2691..4ce70ab 100644
--- a/y2023/can_configuration.fbs
+++ b/frc971/can_configuration.fbs
@@ -1,4 +1,4 @@
-namespace y2023;
+namespace frc971;
 
 // Message which triggers wpilib_interface to print out the current
 // configuration, and optionally re-apply it.
diff --git a/frc971/wpilib/BUILD b/frc971/wpilib/BUILD
index a9d4ebf..33a3cd1 100644
--- a/frc971/wpilib/BUILD
+++ b/frc971/wpilib/BUILD
@@ -506,11 +506,12 @@
     target_compatible_with = ["//tools/platforms/hardware:roborio"],
     deps = [
         "//aos:init",
+        "//aos:math",
         "//aos/events:shm_event_loop",
         "//aos/logging",
         "//frc971/control_loops/drivetrain:drivetrain_can_position_fbs",
         "//third_party:phoenix",
-        "//third_party:phoenixpro",
+        "//third_party:phoenix6",
         "//third_party:wpilib",
         "@com_github_google_glog//:glog",
     ],
diff --git a/frc971/wpilib/can_sensor_reader.cc b/frc971/wpilib/can_sensor_reader.cc
index 5a19e8f..ad244fb 100644
--- a/frc971/wpilib/can_sensor_reader.cc
+++ b/frc971/wpilib/can_sensor_reader.cc
@@ -5,18 +5,19 @@
 
 CANSensorReader::CANSensorReader(
     aos::EventLoop *event_loop,
-    std::vector<ctre::phoenixpro::BaseStatusSignalValue *> signals_registry,
-    std::vector<std::shared_ptr<Falcon>> falcons)
+    std::vector<ctre::phoenix6::BaseStatusSignal *> signals_registry,
+    std::vector<std::shared_ptr<Falcon>> falcons,
+    std::function<void(ctre::phoenix::StatusCode status)> flatbuffer_callback)
     : event_loop_(event_loop),
       signals_(signals_registry.begin(), signals_registry.end()),
-      can_position_sender_(
-          event_loop->MakeSender<control_loops::drivetrain::CANPosition>(
-              "/drivetrain")),
-      falcons_(falcons) {
+      falcons_(falcons),
+      flatbuffer_callback_(flatbuffer_callback) {
   event_loop->SetRuntimeRealtimePriority(40);
 
   // TODO(max): Decide if we want to keep this on this core.
   event_loop->SetRuntimeAffinity(aos::MakeCpusetFromCpus({1}));
+
+  CHECK(flatbuffer_callback_);
   timer_handler_ = event_loop->AddTimer([this]() { Loop(); });
   timer_handler_->set_name("CANSensorReader Loop");
 
@@ -28,37 +29,12 @@
 
 void CANSensorReader::Loop() {
   ctre::phoenix::StatusCode status =
-      ctre::phoenixpro::BaseStatusSignalValue::WaitForAll(20_ms, signals_);
+      ctre::phoenix6::BaseStatusSignal::WaitForAll(20_ms, signals_);
 
   if (!status.IsOK()) {
     AOS_LOG(ERROR, "Failed to read signals from falcons: %s: %s",
             status.GetName(), status.GetDescription());
   }
 
-  auto builder = can_position_sender_.MakeBuilder();
-
-  for (auto falcon : falcons_) {
-    falcon->RefreshNontimesyncedSignals();
-    falcon->SerializePosition(builder.fbb());
-  }
-
-  auto falcon_offsets =
-      builder.fbb()
-          ->CreateVector<flatbuffers::Offset<control_loops::CANFalcon>>(
-              falcons_.size(), [this](size_t index) {
-                auto offset = falcons_.at(index)->TakeOffset();
-                CHECK(offset.has_value());
-                return offset.value();
-              });
-
-  control_loops::drivetrain::CANPosition::Builder can_position_builder =
-      builder.MakeBuilder<control_loops::drivetrain::CANPosition>();
-
-  can_position_builder.add_falcons(falcon_offsets);
-  if (!falcons_.empty()) {
-    can_position_builder.add_timestamp(falcons_.at(0)->GetTimestamp());
-  }
-  can_position_builder.add_status(static_cast<int>(status));
-
-  builder.CheckOk(builder.Send(can_position_builder.Finish()));
+  flatbuffer_callback_(status);
 }
diff --git a/frc971/wpilib/can_sensor_reader.h b/frc971/wpilib/can_sensor_reader.h
index 2e1a406..764a041 100644
--- a/frc971/wpilib/can_sensor_reader.h
+++ b/frc971/wpilib/can_sensor_reader.h
@@ -15,16 +15,17 @@
  public:
   CANSensorReader(
       aos::EventLoop *event_loop,
-      std::vector<ctre::phoenixpro::BaseStatusSignalValue *> signals_registry,
-      std::vector<std::shared_ptr<Falcon>> falcons);
+      std::vector<ctre::phoenix6::BaseStatusSignal *> signals_registry,
+      std::vector<std::shared_ptr<Falcon>> falcons,
+      std::function<void(ctre::phoenix::StatusCode status)>
+          flatbuffer_callback);
 
  private:
   void Loop();
 
   aos::EventLoop *event_loop_;
 
-  const std::vector<ctre::phoenixpro::BaseStatusSignalValue *> signals_;
-  aos::Sender<control_loops::drivetrain::CANPosition> can_position_sender_;
+  const std::vector<ctre::phoenix6::BaseStatusSignal *> signals_;
 
   // This is a vector of falcons becuase we don't need to care
   // about falcons individually.
@@ -32,6 +33,9 @@
 
   // Pointer to the timer handler used to modify the wakeup.
   ::aos::TimerHandler *timer_handler_;
+
+  // Callback used to send the CANPosition flatbuffer
+  std::function<void(ctre::phoenix::StatusCode status)> flatbuffer_callback_;
 };
 }  // namespace wpilib
 }  // namespace frc971
diff --git a/frc971/wpilib/falcon.cc b/frc971/wpilib/falcon.cc
index 2dee390..6be83aa 100644
--- a/frc971/wpilib/falcon.cc
+++ b/frc971/wpilib/falcon.cc
@@ -1,9 +1,10 @@
 #include "frc971/wpilib/falcon.h"
 
 using frc971::wpilib::Falcon;
+using frc971::wpilib::kMaxBringupPower;
 
 Falcon::Falcon(int device_id, std::string canbus,
-               std::vector<ctre::phoenixpro::BaseStatusSignalValue *> *signals,
+               std::vector<ctre::phoenix6::BaseStatusSignal *> *signals,
                double stator_current_limit, double supply_current_limit)
     : talon_(device_id, canbus),
       device_id_(device_id),
@@ -37,7 +38,7 @@
 }
 
 void Falcon::PrintConfigs() {
-  ctre::phoenixpro::configs::TalonFXConfiguration configuration;
+  ctre::phoenix6::configs::TalonFXConfiguration configuration;
   ctre::phoenix::StatusCode status =
       talon_.GetConfigurator().Refresh(configuration);
   if (!status.IsOK()) {
@@ -47,23 +48,22 @@
   AOS_LOG(INFO, "configuration: %s", configuration.ToString().c_str());
 }
 
-void Falcon::WriteConfigs(ctre::phoenixpro::signals::InvertedValue invert) {
+void Falcon::WriteConfigs(ctre::phoenix6::signals::InvertedValue invert) {
   inverted_ = invert;
 
-  ctre::phoenixpro::configs::CurrentLimitsConfigs current_limits;
+  ctre::phoenix6::configs::CurrentLimitsConfigs current_limits;
   current_limits.StatorCurrentLimit = stator_current_limit_;
   current_limits.StatorCurrentLimitEnable = true;
   current_limits.SupplyCurrentLimit = supply_current_limit_;
   current_limits.SupplyCurrentLimitEnable = true;
 
-  ctre::phoenixpro::configs::MotorOutputConfigs output_configs;
-  output_configs.NeutralMode =
-      ctre::phoenixpro::signals::NeutralModeValue::Brake;
+  ctre::phoenix6::configs::MotorOutputConfigs output_configs;
+  output_configs.NeutralMode = ctre::phoenix6::signals::NeutralModeValue::Brake;
   output_configs.DutyCycleNeutralDeadband = 0;
 
   output_configs.Inverted = inverted_;
 
-  ctre::phoenixpro::configs::TalonFXConfiguration configuration;
+  ctre::phoenix6::configs::TalonFXConfiguration configuration;
   configuration.CurrentLimits = current_limits;
   configuration.MotorOutput = output_configs;
 
@@ -77,6 +77,23 @@
   PrintConfigs();
 }
 
+ctre::phoenix::StatusCode Falcon::WriteCurrent(double current,
+                                               double max_voltage) {
+  ctre::phoenix6::controls::TorqueCurrentFOC control(
+      static_cast<units::current::ampere_t>(current));
+  // Using 0_Hz here makes it a one-shot update.
+  control.UpdateFreqHz = 0_Hz;
+  control.MaxAbsDutyCycle =
+      ::aos::Clip(max_voltage, -kMaxBringupPower, kMaxBringupPower) / 12.0;
+  ctre::phoenix::StatusCode status = talon()->SetControl(control);
+  if (!status.IsOK()) {
+    AOS_LOG(ERROR, "Failed to write control to falcon %d: %s: %s", device_id(),
+            status.GetName(), status.GetDescription());
+  }
+
+  return status;
+}
+
 void Falcon::SerializePosition(flatbuffers::FlatBufferBuilder *fbb) {
   control_loops::CANFalcon::Builder builder(*fbb);
   builder.add_id(device_id_);
diff --git a/frc971/wpilib/falcon.h b/frc971/wpilib/falcon.h
index 7137d82..8f0f1f0 100644
--- a/frc971/wpilib/falcon.h
+++ b/frc971/wpilib/falcon.h
@@ -5,9 +5,10 @@
 #include <cinttypes>
 #include <vector>
 
-#include "ctre/phoenixpro/TalonFX.hpp"
+#include "ctre/phoenix6/TalonFX.hpp"
 #include "glog/logging.h"
 
+#include "aos/commonmath.h"
 #include "aos/init.h"
 #include "aos/logging/logging.h"
 #include "frc971/control_loops/drivetrain/drivetrain_can_position_generated.h"
@@ -18,19 +19,21 @@
 namespace wpilib {
 
 static constexpr units::frequency::hertz_t kCANUpdateFreqHz = 200_Hz;
+static constexpr double kMaxBringupPower = 12.0;
 
 // Gets info from and writes to falcon motors using the TalonFX controller.
 class Falcon {
  public:
   Falcon(int device_id, std::string canbus,
-         std::vector<ctre::phoenixpro::BaseStatusSignalValue *> *signals,
+         std::vector<ctre::phoenix6::BaseStatusSignal *> *signals,
          double stator_current_limit, double supply_current_limit);
 
   void PrintConfigs();
 
-  void WriteConfigs(ctre::phoenixpro::signals::InvertedValue invert);
+  void WriteConfigs(ctre::phoenix6::signals::InvertedValue invert);
+  ctre::phoenix::StatusCode WriteCurrent(double current, double max_voltage);
 
-  ctre::phoenixpro::hardware::TalonFX *talon() { return &talon_; }
+  ctre::phoenix6::hardware::TalonFX *talon() { return &talon_; }
 
   void SerializePosition(flatbuffers::FlatBufferBuilder *fbb);
 
@@ -66,19 +69,17 @@
   }
 
  private:
-  ctre::phoenixpro::hardware::TalonFX talon_;
+  ctre::phoenix6::hardware::TalonFX talon_;
   int device_id_;
 
-  ctre::phoenixpro::signals::InvertedValue inverted_;
+  ctre::phoenix6::signals::InvertedValue inverted_;
 
-  ctre::phoenixpro::StatusSignalValue<units::temperature::celsius_t>
-      device_temp_;
-  ctre::phoenixpro::StatusSignalValue<units::voltage::volt_t> supply_voltage_;
-  ctre::phoenixpro::StatusSignalValue<units::current::ampere_t> supply_current_,
+  ctre::phoenix6::StatusSignal<units::temperature::celsius_t> device_temp_;
+  ctre::phoenix6::StatusSignal<units::voltage::volt_t> supply_voltage_;
+  ctre::phoenix6::StatusSignal<units::current::ampere_t> supply_current_,
       torque_current_;
-  ctre::phoenixpro::StatusSignalValue<units::angle::turn_t> position_;
-  ctre::phoenixpro::StatusSignalValue<units::dimensionless::scalar_t>
-      duty_cycle_;
+  ctre::phoenix6::StatusSignal<units::angle::turn_t> position_;
+  ctre::phoenix6::StatusSignal<units::dimensionless::scalar_t> duty_cycle_;
 
   double stator_current_limit_;
   double supply_current_limit_;
diff --git a/frc971/wpilib/swerve/BUILD b/frc971/wpilib/swerve/BUILD
new file mode 100644
index 0000000..9f559f6
--- /dev/null
+++ b/frc971/wpilib/swerve/BUILD
@@ -0,0 +1,30 @@
+package(default_visibility = ["//visibility:public"])
+
+cc_library(
+    name = "swerve_drivetrain_writer",
+    srcs = [
+        "swerve_drivetrain_writer.cc",
+    ],
+    hdrs = [
+        "swerve_drivetrain_writer.h",
+    ],
+    deps = [
+        ":swerve_module",
+        "//aos/logging",
+        "//frc971:can_configuration_fbs",
+        "//frc971/control_loops/drivetrain/swerve:swerve_drivetrain_output_fbs",
+        "//frc971/wpilib:falcon",
+        "//frc971/wpilib:loop_output_handler",
+        "//third_party:phoenix6",
+    ],
+)
+
+cc_library(
+    name = "swerve_module",
+    hdrs = [
+        "swerve_module.h",
+    ],
+    deps = [
+        "//frc971/wpilib:falcon",
+    ],
+)
diff --git a/frc971/wpilib/swerve/swerve_drivetrain_writer.cc b/frc971/wpilib/swerve/swerve_drivetrain_writer.cc
new file mode 100644
index 0000000..2b6ef9e
--- /dev/null
+++ b/frc971/wpilib/swerve/swerve_drivetrain_writer.cc
@@ -0,0 +1,60 @@
+#include "frc971/wpilib/swerve/swerve_drivetrain_writer.h"
+
+using frc971::wpilib::swerve::DrivetrainWriter;
+
+DrivetrainWriter::DrivetrainWriter(::aos::EventLoop *event_loop,
+                                   int drivetrain_writer_priority,
+                                   double max_voltage)
+    : ::frc971::wpilib::LoopOutputHandler<
+          ::frc971::control_loops::drivetrain::swerve::Output>(event_loop,
+                                                               "/drivetrain"),
+      max_voltage_(max_voltage) {
+  event_loop->SetRuntimeRealtimePriority(drivetrain_writer_priority);
+
+  event_loop->OnRun([this]() { WriteConfigs(); });
+}
+
+void DrivetrainWriter::set_falcons(std::shared_ptr<SwerveModule> front_left,
+                                   std::shared_ptr<SwerveModule> front_right,
+                                   std::shared_ptr<SwerveModule> back_left,
+                                   std::shared_ptr<SwerveModule> back_right) {
+  front_left_ = std::move(front_left);
+  front_right_ = std::move(front_right);
+  back_left_ = std::move(back_left);
+  back_right_ = std::move(back_right);
+}
+
+void DrivetrainWriter::HandleCANConfiguration(
+    const CANConfiguration &configuration) {
+  for (auto module : {front_left_, front_right_, back_left_, back_right_}) {
+    module->rotation->PrintConfigs();
+    module->translation->PrintConfigs();
+  }
+  if (configuration.reapply()) {
+    WriteConfigs();
+  }
+}
+
+void DrivetrainWriter::WriteConfigs() {
+  for (auto module : {front_left_, front_right_, back_left_, back_right_}) {
+    module->rotation->WriteConfigs(false);
+    module->translation->WriteConfigs(false);
+  }
+}
+
+void DrivetrainWriter::Write(
+    const ::frc971::control_loops::drivetrain::swerve::Output &output) {
+  front_left_->WriteModule(output.front_left_output(), max_voltage_);
+  front_right_->WriteModule(output.front_right_output(), max_voltage_);
+  back_left_->WriteModule(output.back_left_output(), max_voltage_);
+  back_right_->WriteModule(output.back_right_output(), max_voltage_);
+}
+
+void DrivetrainWriter::Stop() {
+  AOS_LOG(WARNING, "drivetrain output too old\n");
+
+  for (auto module : {front_left_, front_right_, back_left_, back_right_}) {
+    module->rotation->WriteCurrent(0, 0);
+    module->translation->WriteCurrent(0, 0);
+  }
+}
diff --git a/frc971/wpilib/swerve/swerve_drivetrain_writer.h b/frc971/wpilib/swerve/swerve_drivetrain_writer.h
new file mode 100644
index 0000000..4bd6639
--- /dev/null
+++ b/frc971/wpilib/swerve/swerve_drivetrain_writer.h
@@ -0,0 +1,52 @@
+#ifndef FRC971_WPILIB_SWERVE_DRIVETRAIN_WRITER_H_
+#define FRC971_WPILIB_SWERVE_DRIVETRAIN_WRITER_H_
+
+#include "ctre/phoenix6/TalonFX.hpp"
+
+#include "frc971/can_configuration_generated.h"
+#include "frc971/control_loops/drivetrain/swerve/swerve_drivetrain_output_generated.h"
+#include "frc971/wpilib/falcon.h"
+#include "frc971/wpilib/loop_output_handler.h"
+#include "frc971/wpilib/swerve/swerve_module.h"
+
+namespace frc971 {
+namespace wpilib {
+namespace swerve {
+
+// Reads from the swerve output flatbuffer and uses wpilib to set the current
+// for each motor.
+class DrivetrainWriter
+    : public ::frc971::wpilib::LoopOutputHandler<
+          ::frc971::control_loops::drivetrain::swerve::Output> {
+ public:
+  DrivetrainWriter(::aos::EventLoop *event_loop, int drivetrain_writer_priority,
+                   double max_voltage);
+
+  void set_falcons(std::shared_ptr<SwerveModule> front_left,
+                   std::shared_ptr<SwerveModule> front_right,
+                   std::shared_ptr<SwerveModule> back_left,
+                   std::shared_ptr<SwerveModule> back_right);
+
+  void HandleCANConfiguration(const CANConfiguration &configuration);
+
+ private:
+  void WriteConfigs();
+
+  void Write(const ::frc971::control_loops::drivetrain::swerve::Output &output)
+      override;
+
+  void Stop() override;
+
+  double SafeSpeed(double voltage);
+
+  std::shared_ptr<SwerveModule> front_left_, front_right_, back_left_,
+      back_right_;
+
+  double max_voltage_;
+};
+
+}  // namespace swerve
+}  // namespace wpilib
+}  // namespace frc971
+
+#endif  // FRC971_WPILIB_SWERVE_DRIVETRAIN_WRITER_H_
diff --git a/frc971/wpilib/swerve/swerve_module.h b/frc971/wpilib/swerve/swerve_module.h
new file mode 100644
index 0000000..534f0ce
--- /dev/null
+++ b/frc971/wpilib/swerve/swerve_module.h
@@ -0,0 +1,44 @@
+#ifndef FRC971_WPILIB_SWERVE_SWERVE_MODULE_H_
+#define FRC971_WPILIB_SWERVE_SWERVE_MODULE_H_
+
+#include "frc971/wpilib/falcon.h"
+
+namespace frc971 {
+namespace wpilib {
+namespace swerve {
+
+struct SwerveModule {
+  SwerveModule(int rotation_id, int translation_id, std::string canbus,
+               std::vector<ctre::phoenix6::BaseStatusSignal *> *signals,
+               double stator_current_limit, double supply_current_limit)
+      : rotation(std::make_shared<Falcon>(rotation_id, canbus, signals,
+                                          stator_current_limit,
+                                          supply_current_limit)),
+        translation(std::make_shared<Falcon>(translation_id, canbus, signals,
+                                             stator_current_limit,
+                                             supply_current_limit)) {}
+
+  void WriteModule(
+      const frc971::control_loops::drivetrain::swerve::SwerveModuleOutput
+          *module_output,
+      double max_voltage) {
+    double rotation_current = 0.0;
+    double translation_current = 0.0;
+
+    if (module_output != nullptr) {
+      rotation_current = module_output->rotation_current();
+      translation_current = module_output->translation_current();
+    }
+
+    rotation->WriteCurrent(rotation_current, max_voltage);
+    translation->WriteCurrent(translation_current, max_voltage);
+  }
+
+  std::shared_ptr<Falcon> rotation;
+  std::shared_ptr<Falcon> translation;
+};
+
+}  // namespace swerve
+}  // namespace wpilib
+}  // namespace frc971
+#endif  // FRC971_WPILIB_SWERVE_SWERVE_MODULE_H_
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/rustfmt.toml
diff --git a/scouting/BUILD b/scouting/BUILD
index d6c09b2..b7e50fa 100644
--- a/scouting/BUILD
+++ b/scouting/BUILD
@@ -19,6 +19,11 @@
     ],
 )
 
+# TODO(philipp): Sandbox the following:
+# - libnss3
+# - libdrm2
+# - libgbm1
+# - x11-xkb-utils (See TODO in scouting/scouting_test_runner.js)
 cypress_test(
     name = "scouting_test",
     data = [
diff --git a/scouting/scouting_test_runner.js b/scouting/scouting_test_runner.js
index 3106ee7..c2ef027 100644
--- a/scouting/scouting_test_runner.js
+++ b/scouting/scouting_test_runner.js
@@ -4,9 +4,13 @@
 const cypress = require('cypress');
 
 // Set up the xvfb binary.
-process.env[
-  'PATH'
-] = `${process.env.RUNFILES_DIR}/xvfb_amd64/wrapped_bin:${process.env.PATH}`;
+// TODO(philipp): Figure out how to point Xvfb at the sandboxed usr/bin
+// directory. Currently impossible as it's hardcoded to use /usr/bin.
+process.env['PATH'] = [
+  `${process.env.RUNFILES_DIR}/xvfb_amd64/wrapped_bin`,
+  `${process.env.RUNFILES_DIR}/xvfb_amd64/usr/bin`,
+  process.env.PATH,
+].join(':');
 
 // Start the web server, database, and fake TBA server.
 // We use file descriptor 3 ('pipe') for the test server to let us know when
diff --git a/third_party/BUILD b/third_party/BUILD
index 53b5d24..7195123 100644
--- a/third_party/BUILD
+++ b/third_party/BUILD
@@ -31,17 +31,17 @@
     target_compatible_with = ["//tools/platforms/hardware:roborio"],
     visibility = ["//visibility:public"],
     deps = [
+        "@ctre_phoenix6_tools_athena//:tools",
+        "@ctre_phoenix6_tools_headers//:tools",
         "@ctre_phoenix_api_cpp_athena//:api-cpp",
         "@ctre_phoenix_api_cpp_headers//:api-cpp",
         "@ctre_phoenix_cci_athena//:cci",
         "@ctre_phoenix_cci_headers//:cci",
-        "@ctre_phoenixpro_tools_athena//:tools",
-        "@ctre_phoenixpro_tools_headers//:tools",
     ],
 )
 
 cc_library(
-    name = "phoenixpro",
+    name = "phoenix6",
     linkopts = [
         "-Wl,-rpath",
         "-Wl,.",
@@ -49,10 +49,10 @@
     target_compatible_with = ["//tools/platforms/hardware:roborio"],
     visibility = ["//visibility:public"],
     deps = [
-        "@ctre_phoenixpro_api_cpp_athena//:api-cpp",
-        "@ctre_phoenixpro_api_cpp_headers//:api-cpp",
-        "@ctre_phoenixpro_tools_athena//:tools",
-        "@ctre_phoenixpro_tools_headers//:tools",
+        "@ctre_phoenix6_api_cpp_athena//:api-cpp",
+        "@ctre_phoenix6_api_cpp_headers//:api-cpp",
+        "@ctre_phoenix6_tools_athena//:tools",
+        "@ctre_phoenix6_tools_headers//:tools",
     ],
 )
 
diff --git a/y2022/BUILD b/y2022/BUILD
index f8bf59f..f25925e 100644
--- a/y2022/BUILD
+++ b/y2022/BUILD
@@ -18,10 +18,10 @@
     data = [
         ":aos_config",
         ":message_bridge_client.sh",
+        "@ctre_phoenix6_api_cpp_athena//:shared_libraries",
+        "@ctre_phoenix6_tools_athena//:shared_libraries",
         "@ctre_phoenix_api_cpp_athena//:shared_libraries",
         "@ctre_phoenix_cci_athena//:shared_libraries",
-        "@ctre_phoenixpro_api_cpp_athena//:shared_libraries",
-        "@ctre_phoenixpro_tools_athena//:shared_libraries",
     ],
     dirs = [
         "//y2022/actors:splines",
@@ -270,7 +270,7 @@
         "//frc971/wpilib:wpilib_interface",
         "//frc971/wpilib:wpilib_robot_base",
         "//third_party:phoenix",
-        "//third_party:phoenixpro",
+        "//third_party:phoenix6",
         "//third_party:wpilib",
         "//y2022/control_loops/superstructure:led_indicator_lib",
         "//y2022/control_loops/superstructure:superstructure_can_position_fbs",
diff --git a/y2022_bot3/BUILD b/y2022_bot3/BUILD
index e7146ce..29debe7 100644
--- a/y2022_bot3/BUILD
+++ b/y2022_bot3/BUILD
@@ -8,8 +8,8 @@
     ],
     data = [
         ":aos_config",
-        "@ctre_phoenixpro_api_cpp_athena//:shared_libraries",
-        "@ctre_phoenixpro_tools_athena//:shared_libraries",
+        "@ctre_phoenix6_api_cpp_athena//:shared_libraries",
+        "@ctre_phoenix6_tools_athena//:shared_libraries",
     ],
     dirs = [
         "//y2022_bot3/actors:splines",
diff --git a/y2023/BUILD b/y2023/BUILD
index ed8f347..e200f87 100644
--- a/y2023/BUILD
+++ b/y2023/BUILD
@@ -1,7 +1,6 @@
 load("//frc971:downloader.bzl", "robot_downloader")
 load("//aos:config.bzl", "aos_config")
 load("//tools/build_rules:template.bzl", "jinja2_template")
-load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library")
 load("//aos/util:config_validator_macro.bzl", "config_validator_test")
 
 config_validator_test(
@@ -21,10 +20,10 @@
         "//aos/starter:roborio_irq_config.json",
         "//y2023/constants:constants.json",
         "//y2023/control_loops/superstructure/arm:arm_trajectories_generated.bfbs",
+        "@ctre_phoenix6_api_cpp_athena//:shared_libraries",
+        "@ctre_phoenix6_tools_athena//:shared_libraries",
         "@ctre_phoenix_api_cpp_athena//:shared_libraries",
         "@ctre_phoenix_cci_athena//:shared_libraries",
-        "@ctre_phoenixpro_api_cpp_athena//:shared_libraries",
-        "@ctre_phoenixpro_tools_athena//:shared_libraries",
     ],
     dirs = [
         "//y2023/www:www_files",
@@ -203,7 +202,7 @@
     name = "config_roborio",
     src = "y2023_roborio.json",
     flatbuffers = [
-        ":can_configuration_fbs",
+        "//frc971:can_configuration_fbs",
         "//aos/network:remote_message_fbs",
         "//aos/network:message_bridge_client_fbs",
         "//aos/network:message_bridge_server_fbs",
@@ -271,7 +270,6 @@
     ],
     target_compatible_with = ["//tools/platforms/hardware:roborio"],
     deps = [
-        ":can_configuration_fbs",
         ":constants",
         "//aos:init",
         "//aos:math",
@@ -283,6 +281,7 @@
         "//aos/util:log_interval",
         "//aos/util:phased_loop",
         "//aos/util:wrapping_counter",
+        "//frc971:can_configuration_fbs",
         "//frc971/autonomous:auto_mode_fbs",
         "//frc971/control_loops:control_loop",
         "//frc971/control_loops:control_loops_fbs",
@@ -303,7 +302,7 @@
         "//frc971/wpilib:wpilib_interface",
         "//frc971/wpilib:wpilib_robot_base",
         "//third_party:phoenix",
-        "//third_party:phoenixpro",
+        "//third_party:phoenix6",
         "//third_party:wpilib",
         "//y2023/control_loops/superstructure:led_indicator_lib",
         "//y2023/control_loops/superstructure:superstructure_output_fbs",
@@ -384,12 +383,3 @@
         "@com_github_google_glog//:glog",
     ],
 )
-
-flatbuffer_cc_library(
-    name = "can_configuration_fbs",
-    srcs = [
-        ":can_configuration.fbs",
-    ],
-    gen_reflections = 1,
-    visibility = ["//visibility:public"],
-)
diff --git a/y2023/wpilib_interface.cc b/y2023/wpilib_interface.cc
index d043e6f..abf11da 100644
--- a/y2023/wpilib_interface.cc
+++ b/y2023/wpilib_interface.cc
@@ -26,7 +26,7 @@
 #include "ctre/phoenix/cci/Diagnostics_CCI.h"
 #include "ctre/phoenix/motorcontrol/can/TalonFX.h"
 #include "ctre/phoenix/motorcontrol/can/TalonSRX.h"
-#include "ctre/phoenixpro/TalonFX.hpp"
+#include "ctre/phoenix6/TalonFX.hpp"
 
 #include "aos/commonmath.h"
 #include "aos/containers/sized_array.h"
@@ -40,6 +40,7 @@
 #include "aos/util/phased_loop.h"
 #include "aos/util/wrapping_counter.h"
 #include "frc971/autonomous/auto_mode_generated.h"
+#include "frc971/can_configuration_generated.h"
 #include "frc971/control_loops/drivetrain/drivetrain_can_position_generated.h"
 #include "frc971/control_loops/drivetrain/drivetrain_position_generated.h"
 #include "frc971/input/robot_state_generated.h"
@@ -56,7 +57,6 @@
 #include "frc971/wpilib/pdp_fetcher.h"
 #include "frc971/wpilib/sensor_reader.h"
 #include "frc971/wpilib/wpilib_robot_base.h"
-#include "y2023/can_configuration_generated.h"
 #include "y2023/constants.h"
 #include "y2023/control_loops/superstructure/led_indicator.h"
 #include "y2023/control_loops/superstructure/superstructure_output_generated.h"
@@ -67,6 +67,7 @@
             "devices on the CAN bus using Phoenix Tuner");
 
 using ::aos::monotonic_clock;
+using ::frc971::CANConfiguration;
 using ::y2023::constants::Values;
 namespace superstructure = ::y2023::control_loops::superstructure;
 namespace drivetrain = ::y2023::control_loops::drivetrain;
@@ -121,7 +122,7 @@
 class Falcon {
  public:
   Falcon(int device_id, std::string canbus,
-         std::vector<ctre::phoenixpro::BaseStatusSignalValue *> *signals)
+         std::vector<ctre::phoenix6::BaseStatusSignal *> *signals)
       : talon_(device_id, canbus),
         device_id_(device_id),
         device_temp_(talon_.GetDeviceTemp()),
@@ -152,7 +153,7 @@
   }
 
   void PrintConfigs() {
-    ctre::phoenixpro::configs::TalonFXConfiguration configuration;
+    ctre::phoenix6::configs::TalonFXConfiguration configuration;
     ctre::phoenix::StatusCode status =
         talon_.GetConfigurator().Refresh(configuration);
     if (!status.IsOK()) {
@@ -162,10 +163,10 @@
     AOS_LOG(INFO, "configuration: %s", configuration.ToString().c_str());
   }
 
-  void WriteConfigs(ctre::phoenixpro::signals::InvertedValue invert) {
+  void WriteConfigs(ctre::phoenix6::signals::InvertedValue invert) {
     inverted_ = invert;
 
-    ctre::phoenixpro::configs::CurrentLimitsConfigs current_limits;
+    ctre::phoenix6::configs::CurrentLimitsConfigs current_limits;
     current_limits.StatorCurrentLimit =
         constants::Values::kDrivetrainStatorCurrentLimit();
     current_limits.StatorCurrentLimitEnable = true;
@@ -173,14 +174,14 @@
         constants::Values::kDrivetrainSupplyCurrentLimit();
     current_limits.SupplyCurrentLimitEnable = true;
 
-    ctre::phoenixpro::configs::MotorOutputConfigs output_configs;
+    ctre::phoenix6::configs::MotorOutputConfigs output_configs;
     output_configs.NeutralMode =
-        ctre::phoenixpro::signals::NeutralModeValue::Brake;
+        ctre::phoenix6::signals::NeutralModeValue::Brake;
     output_configs.DutyCycleNeutralDeadband = 0;
 
     output_configs.Inverted = inverted_;
 
-    ctre::phoenixpro::configs::TalonFXConfiguration configuration;
+    ctre::phoenix6::configs::TalonFXConfiguration configuration;
     configuration.CurrentLimits = current_limits;
     configuration.MotorOutput = output_configs;
 
@@ -195,7 +196,7 @@
   }
 
   void WriteRollerConfigs() {
-    ctre::phoenixpro::configs::CurrentLimitsConfigs current_limits;
+    ctre::phoenix6::configs::CurrentLimitsConfigs current_limits;
     current_limits.StatorCurrentLimit =
         constants::Values::kRollerStatorCurrentLimit();
     current_limits.StatorCurrentLimitEnable = true;
@@ -203,12 +204,12 @@
         constants::Values::kRollerSupplyCurrentLimit();
     current_limits.SupplyCurrentLimitEnable = true;
 
-    ctre::phoenixpro::configs::MotorOutputConfigs output_configs;
+    ctre::phoenix6::configs::MotorOutputConfigs output_configs;
     output_configs.NeutralMode =
-        ctre::phoenixpro::signals::NeutralModeValue::Brake;
+        ctre::phoenix6::signals::NeutralModeValue::Brake;
     output_configs.DutyCycleNeutralDeadband = 0;
 
-    ctre::phoenixpro::configs::TalonFXConfiguration configuration;
+    ctre::phoenix6::configs::TalonFXConfiguration configuration;
     configuration.CurrentLimits = current_limits;
     configuration.MotorOutput = output_configs;
 
@@ -222,7 +223,7 @@
     PrintConfigs();
   }
 
-  ctre::phoenixpro::hardware::TalonFX *talon() { return &talon_; }
+  ctre::phoenix6::hardware::TalonFX *talon() { return &talon_; }
 
   flatbuffers::Offset<frc971::control_loops::CANFalcon> WritePosition(
       flatbuffers::FlatBufferBuilder *fbb) {
@@ -235,8 +236,7 @@
     builder.add_duty_cycle(duty_cycle());
 
     double invert =
-        (inverted_ ==
-                 ctre::phoenixpro::signals::InvertedValue::Clockwise_Positive
+        (inverted_ == ctre::phoenix6::signals::InvertedValue::Clockwise_Positive
              ? 1
              : -1);
 
@@ -266,26 +266,24 @@
   void RefreshNontimesyncedSignals() { device_temp_.Refresh(); };
 
  private:
-  ctre::phoenixpro::hardware::TalonFX talon_;
+  ctre::phoenix6::hardware::TalonFX talon_;
   int device_id_;
 
-  ctre::phoenixpro::signals::InvertedValue inverted_;
+  ctre::phoenix6::signals::InvertedValue inverted_;
 
-  ctre::phoenixpro::StatusSignalValue<units::temperature::celsius_t>
-      device_temp_;
-  ctre::phoenixpro::StatusSignalValue<units::voltage::volt_t> supply_voltage_;
-  ctre::phoenixpro::StatusSignalValue<units::current::ampere_t> supply_current_,
+  ctre::phoenix6::StatusSignal<units::temperature::celsius_t> device_temp_;
+  ctre::phoenix6::StatusSignal<units::voltage::volt_t> supply_voltage_;
+  ctre::phoenix6::StatusSignal<units::current::ampere_t> supply_current_,
       torque_current_;
-  ctre::phoenixpro::StatusSignalValue<units::angle::turn_t> position_;
-  ctre::phoenixpro::StatusSignalValue<units::dimensionless::scalar_t>
-      duty_cycle_;
+  ctre::phoenix6::StatusSignal<units::angle::turn_t> position_;
+  ctre::phoenix6::StatusSignal<units::dimensionless::scalar_t> duty_cycle_;
 };
 
 class CANSensorReader {
  public:
   CANSensorReader(
       aos::EventLoop *event_loop,
-      std::vector<ctre::phoenixpro::BaseStatusSignalValue *> signals_registry)
+      std::vector<ctre::phoenix6::BaseStatusSignal *> signals_registry)
       : event_loop_(event_loop),
         signals_(signals_registry.begin(), signals_registry.end()),
         can_position_sender_(
@@ -328,7 +326,7 @@
  private:
   void Loop() {
     ctre::phoenix::StatusCode status =
-        ctre::phoenixpro::BaseStatusSignalValue::WaitForAll(2000_ms, signals_);
+        ctre::phoenix6::BaseStatusSignal::WaitForAll(2000_ms, signals_);
 
     if (!status.IsOK()) {
       AOS_LOG(ERROR, "Failed to read signals from falcons: %s: %s",
@@ -384,7 +382,7 @@
 
   aos::EventLoop *event_loop_;
 
-  const std::vector<ctre::phoenixpro::BaseStatusSignalValue *> signals_;
+  const std::vector<ctre::phoenix6::BaseStatusSignal *> signals_;
   aos::Sender<frc971::control_loops::drivetrain::CANPosition>
       can_position_sender_;
 
@@ -761,7 +759,7 @@
   void WriteConfigs() { roller_falcon_->WriteRollerConfigs(); }
 
   void Write(const superstructure::Output &output) override {
-    ctre::phoenixpro::controls::DutyCycleOut roller_control(
+    ctre::phoenix6::controls::DutyCycleOut roller_control(
         SafeSpeed(-output.roller_voltage()));
     roller_control.UpdateFreqHz = 0_Hz;
     roller_control.EnableFOC = true;
@@ -777,7 +775,7 @@
 
   void Stop() override {
     AOS_LOG(WARNING, "Superstructure CAN output too old.\n");
-    ctre::phoenixpro::controls::DutyCycleOut stop_command(0.0);
+    ctre::phoenix6::controls::DutyCycleOut stop_command(0.0);
     stop_command.UpdateFreqHz = 0_Hz;
     stop_command.EnableFOC = true;
 
@@ -818,11 +816,11 @@
     left_under_ = std::move(left_under);
   }
 
-  void set_right_inverted(ctre::phoenixpro::signals::InvertedValue invert) {
+  void set_right_inverted(ctre::phoenix6::signals::InvertedValue invert) {
     right_inverted_ = invert;
   }
 
-  void set_left_inverted(ctre::phoenixpro::signals::InvertedValue invert) {
+  void set_left_inverted(ctre::phoenix6::signals::InvertedValue invert) {
     left_inverted_ = invert;
   }
 
@@ -851,12 +849,12 @@
 
   void Write(
       const ::frc971::control_loops::drivetrain::Output &output) override {
-    ctre::phoenixpro::controls::DutyCycleOut left_control(
+    ctre::phoenix6::controls::DutyCycleOut left_control(
         SafeSpeed(output.left_voltage()));
     left_control.UpdateFreqHz = 0_Hz;
     left_control.EnableFOC = true;
 
-    ctre::phoenixpro::controls::DutyCycleOut right_control(
+    ctre::phoenix6::controls::DutyCycleOut right_control(
         SafeSpeed(output.right_voltage()));
     right_control.UpdateFreqHz = 0_Hz;
     right_control.EnableFOC = true;
@@ -886,7 +884,7 @@
 
   void Stop() override {
     AOS_LOG(WARNING, "drivetrain output too old\n");
-    ctre::phoenixpro::controls::DutyCycleOut stop_command(0.0);
+    ctre::phoenix6::controls::DutyCycleOut stop_command(0.0);
     stop_command.UpdateFreqHz = 0_Hz;
     stop_command.EnableFOC = true;
 
@@ -901,7 +899,7 @@
     return (::aos::Clip(voltage, -kMaxBringupPower, kMaxBringupPower) / 12.0);
   }
 
-  ctre::phoenixpro::signals::InvertedValue left_inverted_, right_inverted_;
+  ctre::phoenix6::signals::InvertedValue left_inverted_, right_inverted_;
   std::shared_ptr<Falcon> right_front_, right_back_, right_under_, left_front_,
       left_back_, left_under_;
 };
@@ -934,7 +932,7 @@
     std::shared_ptr<frc::DigitalOutput> superstructure_reading =
         make_unique<frc::DigitalOutput>(25);
 
-    std::vector<ctre::phoenixpro::BaseStatusSignalValue *> signals_registry;
+    std::vector<ctre::phoenix6::BaseStatusSignal *> signals_registry;
     std::shared_ptr<Falcon> right_front =
         std::make_shared<Falcon>(1, "Drivetrain Bus", &signals_registry);
     std::shared_ptr<Falcon> right_back =
@@ -1013,9 +1011,9 @@
     drivetrain_writer.set_falcons(right_front, right_back, right_under,
                                   left_front, left_back, left_under);
     drivetrain_writer.set_right_inverted(
-        ctre::phoenixpro::signals::InvertedValue::Clockwise_Positive);
+        ctre::phoenix6::signals::InvertedValue::Clockwise_Positive);
     drivetrain_writer.set_left_inverted(
-        ctre::phoenixpro::signals::InvertedValue::CounterClockwise_Positive);
+        ctre::phoenix6::signals::InvertedValue::CounterClockwise_Positive);
 
     SuperstructureCANWriter superstructure_can_writer(&can_output_event_loop);
     superstructure_can_writer.set_roller_falcon(roller);
diff --git a/y2023/y2023_roborio.json b/y2023/y2023_roborio.json
index 33d9904..c1e42e6 100644
--- a/y2023/y2023_roborio.json
+++ b/y2023/y2023_roborio.json
@@ -300,7 +300,7 @@
     },
     {
       "name": "/roborio",
-      "type": "y2023.CANConfiguration",
+      "type": "frc971.CANConfiguration",
       "source_node": "roborio",
       "frequency": 2
     },