Merge "Import driver ranking script for scouting"
diff --git a/scouting/testing/scouting_test_servers.py b/scouting/testing/scouting_test_servers.py
index 40412c0..95c146e 100644
--- a/scouting/testing/scouting_test_servers.py
+++ b/scouting/testing/scouting_test_servers.py
@@ -101,22 +101,23 @@
         ])
         wait_for_server(5432)
 
+        # Create a fake TBA server to serve the static match list.
+        # Make sure it starts up first so that the webserver successfully
+        # scrapes the TBA data on startup.
+        set_up_tba_api_dir(self.tmpdir, year, event_code)
+        self.fake_tba_api = subprocess.Popen(
+            ["python3", "-m", "http.server", "7000"],
+            cwd=self.tmpdir,
+        )
+        wait_for_server(7000)
+
+        # Wait for the scouting webserver to start up.
         self.webserver = subprocess.Popen([
             RUNFILES.Rlocation("org_frc971/scouting/scouting"),
             f"--port={port}",
             f"--db_config={db_config}",
             f"--tba_config={tba_config}",
         ])
-
-        # Create a fake TBA server to serve the static match list.
-        set_up_tba_api_dir(self.tmpdir, year, event_code)
-        self.fake_tba_api = subprocess.Popen(
-            ["python3", "-m", "http.server", "7000"],
-            cwd=self.tmpdir,
-        )
-
-        # Wait for the TBA server and the scouting webserver to start up.
-        wait_for_server(7000)
         wait_for_server(port)
 
         if notify_fd:
diff --git a/scouting/webserver/requests/debug/cli/BUILD b/scouting/webserver/requests/debug/cli/BUILD
index 371f66e..ebc23b3 100644
--- a/scouting/webserver/requests/debug/cli/BUILD
+++ b/scouting/webserver/requests/debug/cli/BUILD
@@ -25,9 +25,13 @@
 
 py_test(
     name = "cli_test",
+    size = "small",
     srcs = [
         "cli_test.py",
     ],
+    args = [
+        "-v",
+    ],
     data = [
         ":cli",
     ],
diff --git a/scouting/webserver/requests/debug/cli/cli_test.py b/scouting/webserver/requests/debug/cli/cli_test.py
index 4e47b2d..979ef5a 100644
--- a/scouting/webserver/requests/debug/cli/cli_test.py
+++ b/scouting/webserver/requests/debug/cli/cli_test.py
@@ -89,6 +89,10 @@
                     sys.stderr.write(
                         f"Waiting until {expected_num_matches} are imported. "
                         f"Currently at {num_matches_imported}.\n")
+            else:
+                sys.stderr.write(
+                    "Failed to parse requestAllMatches for number of "
+                    f"matches: {stdout}\n")
 
             time.sleep(0.25)
 
diff --git a/y2023/vision/BUILD b/y2023/vision/BUILD
index 9bd29fa..94803e1 100644
--- a/y2023/vision/BUILD
+++ b/y2023/vision/BUILD
@@ -75,6 +75,7 @@
         "//aos:init",
         "//aos/events:simulated_event_loop",
         "//aos/events/logging:log_reader",
+        "//aos/util:mcap_logger",
         "//frc971/constants:constants_sender_lib",
         "//frc971/control_loops:pose",
         "//frc971/vision:calibration_fbs",
diff --git a/y2023/vision/target_mapping.cc b/y2023/vision/target_mapping.cc
index a941923..c59414b 100644
--- a/y2023/vision/target_mapping.cc
+++ b/y2023/vision/target_mapping.cc
@@ -1,6 +1,8 @@
+#include "aos/configuration.h"
 #include "aos/events/logging/log_reader.h"
 #include "aos/events/simulated_event_loop.h"
 #include "aos/init.h"
+#include "aos/util/mcap_logger.h"
 #include "frc971/control_loops/pose.h"
 #include "frc971/vision/calibration_generated.h"
 #include "frc971/vision/charuco_lib.h"
@@ -18,6 +20,8 @@
 
 DEFINE_string(json_path, "y2023/vision/maps/target_map.json",
               "Specify path for json with initial pose guesses.");
+DEFINE_string(config, "y2023/aos_config.json",
+              "Path to the config file to use.");
 DEFINE_string(constants_path, "y2023/constants/constants.json",
               "Path to the constant file");
 DEFINE_string(output_dir, "y2023/vision/maps",
@@ -26,6 +30,8 @@
               "Field name, for the output json filename and flatbuffer field");
 DEFINE_int32(team_number, 7971,
              "Use the calibration for a node with this team number");
+DEFINE_string(mcap_output_path, "/tmp/log.mcap", "Log to output.");
+DEFINE_string(pi, "pi1", "Pi name to generate mcap log for; defaults to pi1.");
 
 namespace y2023 {
 namespace vision {
@@ -112,8 +118,12 @@
 
   std::vector<DataAdapter::TimestampedDetection> timestamped_target_detections;
 
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig(FLAGS_config);
+
   // open logfiles
-  aos::logger::LogReader reader(aos::logger::SortParts(unsorted_logfiles));
+  aos::logger::LogReader reader(aos::logger::SortParts(unsorted_logfiles),
+                                &config.message());
   // Send new april tag poses. This allows us to take a log of images, then play
   // with the april detection code and see those changes take effect in mapping
   constexpr size_t kNumPis = 4;
@@ -171,6 +181,25 @@
   HandlePiCaptures(pi4_detection_event_loop.get(), pi4_mapping_event_loop.get(),
                    &reader, &timestamped_target_detections, &detectors);
 
+  std::unique_ptr<aos::EventLoop> mcap_event_loop;
+  std::unique_ptr<aos::McapLogger> relogger;
+  if (!FLAGS_mcap_output_path.empty()) {
+    LOG(INFO) << "Writing out mcap file to " << FLAGS_mcap_output_path;
+    // TODO: Should make this work for any pi
+    const aos::Node *node =
+        aos::configuration::GetNode(reader.configuration(), FLAGS_pi);
+    reader.event_loop_factory()->GetNodeEventLoopFactory(node)->OnStartup(
+        [&relogger, &mcap_event_loop, &reader, node]() {
+          mcap_event_loop =
+              reader.event_loop_factory()->MakeEventLoop("mcap", node);
+          relogger = std::make_unique<aos::McapLogger>(
+              mcap_event_loop.get(), FLAGS_mcap_output_path,
+              aos::McapLogger::Serialization::kFlatbuffer,
+              aos::McapLogger::CanonicalChannelNames::kShortened,
+              aos::McapLogger::Compression::kLz4);
+        });
+  }
+
   reader.event_loop_factory()->Run();
 
   auto target_constraints =