Merge changes I87f6584a,I0d8e22bb

* changes:
  Switch to generating pip packages in a Debian container
  Add --local_test mode for mirror_pip_packages.py
diff --git a/aos/aos_cli_utils.cc b/aos/aos_cli_utils.cc
index 2016a71..a4ea95e 100644
--- a/aos/aos_cli_utils.cc
+++ b/aos/aos_cli_utils.cc
@@ -228,7 +228,7 @@
   aos::FlatbufferToJson(
       builder, channel->schema(), static_cast<const uint8_t *>(context.data),
       {options.pretty, static_cast<size_t>(options.max_vector_size),
-       options.pretty_max, options.use_hex});
+       options.pretty_max, options.hex});
 
   if (options.json) {
     std::cout << "{";
@@ -249,7 +249,7 @@
 
     std::cout << "\"channel\": "
               << aos::configuration::StrippedChannelToString(channel)
-              << ", \"data\": " << *builder << "}\n";
+              << ", \"data\": " << *builder << "}";
   } else {
     if (!node_name.empty()) {
       std::cout << node_name << " ";
@@ -262,15 +262,15 @@
                   << context.realtime_remote_time << " ("
                   << context.monotonic_remote_time << ") "
                   << channel->name()->c_str() << ' ' << channel->type()->c_str()
-                  << ": " << *builder << "\n";
+                  << ": " << *builder;
       } else {
         std::cout << context.realtime_event_time << " ("
                   << context.monotonic_event_time << ") "
                   << channel->name()->c_str() << ' ' << channel->type()->c_str()
-                  << ": " << *builder << "\n";
+                  << ": " << *builder;
       }
     } else {
-      std::cout << *builder << '\n';
+      std::cout << *builder;
     }
   }
 }
@@ -291,4 +291,68 @@
   PrintMessage(node_name, channel, context, builder, options);
 }
 
+Printer::Printer(PrintOptions options, bool flush)
+    : options_(options), flush_(flush) {
+  if (options_.json) {
+    std::cout << "[";
+  }
+}
+
+Printer::~Printer() {
+  if (options_.json) {
+    if (message_count_ > 0) {
+      std::cout << "\n]\n";
+    } else {
+      std::cout << "]\n";
+    }
+  }
+}
+
+void Printer::PrintMessage(const std::string_view node_name,
+                           aos::NodeEventLoopFactory *node_factory,
+                           const aos::Channel *channel,
+                           const aos::Context &context) {
+  if (options_.json) {
+    if (message_count_ != 0) {
+      std::cout << ",\n  ";
+    } else {
+      std::cout << "\n  ";
+    }
+  }
+
+  aos::PrintMessage(node_name, node_factory, channel, context, &str_builder_,
+                    options_);
+
+  if (!options_.json) {
+    if (flush_) {
+      std::cout << std::endl;
+    } else {
+      std::cout << "\n";
+    }
+  }
+  ++message_count_;
+}
+
+void Printer::PrintMessage(const aos::Channel *channel,
+                           const aos::Context &context) {
+  if (options_.json) {
+    if (message_count_ != 0) {
+      std::cout << ",\n  ";
+    } else {
+      std::cout << "\n  ";
+    }
+  }
+
+  aos::PrintMessage(channel, context, &str_builder_, options_);
+
+  if (!options_.json) {
+    if (flush_) {
+      std::cout << std::endl;
+    } else {
+      std::cout << "\n";
+    }
+  }
+  ++message_count_;
+}
+
 }  // namespace aos
diff --git a/aos/aos_cli_utils.h b/aos/aos_cli_utils.h
index 7e143dd..35fb61f 100644
--- a/aos/aos_cli_utils.h
+++ b/aos/aos_cli_utils.h
@@ -23,7 +23,7 @@
   // Print the distributed clock.
   bool distributed_clock;
   // Print numbers out in hex.
-  bool use_hex;
+  bool hex;
 };
 
 // Print the flatbuffer out to stdout, both to remove the unnecessary cruft from
@@ -37,6 +37,36 @@
 void PrintMessage(const aos::Channel *channel, const aos::Context &context,
                   aos::FastStringBuilder *builder, PrintOptions options);
 
+// RAII class to manage printing out sequences of messages, and to print them
+// all in a JSON array properly.
+class Printer {
+ public:
+  Printer(PrintOptions options, bool flush);
+  ~Printer();
+
+  // Number of messages that have been printed.
+  uint64_t message_count() const { return message_count_; }
+
+  // Prints a message.
+  void PrintMessage(const std::string_view node_name,
+                    aos::NodeEventLoopFactory *node_factory,
+                    const aos::Channel *channel, const aos::Context &context);
+  void PrintMessage(const aos::Channel *channel, const aos::Context &context);
+
+ private:
+  // Builder to make printing fast
+  aos::FastStringBuilder str_builder_;
+
+  // Number of messages
+  uint64_t message_count_ = 0;
+
+  // Options for printing.
+  const PrintOptions options_;
+
+  // If true, use std::endl to flush stdout, otherwise write a "\n"
+  const bool flush_;
+};
+
 // The information needed by the main function of a CLI tool.
 struct CliUtilInfo {
   // If this returns true, main should return immediately with 0.
diff --git a/aos/aos_dump.cc b/aos/aos_dump.cc
index 9f3d01f..b76b1a3 100644
--- a/aos/aos_dump.cc
+++ b/aos/aos_dump.cc
@@ -27,7 +27,8 @@
 DEFINE_int32(timeout, -1,
              "The max time in milliseconds to wait for messages before "
              "exiting.  -1 means forever, 0 means don't wait.");
-DEFINE_bool(use_hex, false, "Are integers in the messages printed in hex notation.");
+DEFINE_bool(hex, false,
+            "Are integers in the messages printed in hex notation.");
 
 int main(int argc, char **argv) {
   gflags::SetUsageMessage(
@@ -51,32 +52,32 @@
 
   uint64_t message_count = 0;
 
-  aos::FastStringBuilder str_builder;
-
   aos::monotonic_clock::time_point next_send_time =
       aos::monotonic_clock::min_time;
 
+  aos::Printer printer(
+      {
+          .pretty = FLAGS_pretty,
+          .max_vector_size = static_cast<size_t>(FLAGS_max_vector_size),
+          .pretty_max = FLAGS_pretty_max,
+          .print_timestamps = FLAGS_print_timestamps,
+          .json = FLAGS_json,
+          .distributed_clock = false,
+          .hex = FLAGS_hex,
+      },
+      /*flush*/ true);
+
   for (const aos::Channel *channel : cli_info.found_channels) {
     if (FLAGS_fetch) {
       const std::unique_ptr<aos::RawFetcher> fetcher =
           cli_info.event_loop->MakeRawFetcher(channel);
       if (fetcher->Fetch()) {
-        PrintMessage(
-            channel, fetcher->context(), &str_builder,
-            {
-                .pretty = FLAGS_pretty,
-                .max_vector_size = static_cast<size_t>(FLAGS_max_vector_size),
-                .pretty_max = FLAGS_pretty_max,
-                .print_timestamps = FLAGS_print_timestamps,
-                .json = FLAGS_json,
-                .distributed_clock = false,
-                .use_hex = FLAGS_use_hex,
-            });
+        printer.PrintMessage(channel, fetcher->context());
         ++message_count;
       }
     }
 
-    if (FLAGS_count > 0 && message_count >= FLAGS_count) {
+    if (FLAGS_count > 0 && printer.message_count() >= FLAGS_count) {
       return 0;
     }
 
@@ -85,28 +86,17 @@
     }
 
     cli_info.event_loop->MakeRawWatcher(
-        channel,
-        [channel, &str_builder, &cli_info, &message_count, &next_send_time](
-            const aos::Context &context, const void * /*message*/) {
+        channel, [channel, &printer, &cli_info, &next_send_time](
+                     const aos::Context &context, const void * /*message*/) {
           if (context.monotonic_event_time > next_send_time) {
-            if (FLAGS_count > 0 && message_count >= FLAGS_count) {
+            if (FLAGS_count > 0 && printer.message_count() >= FLAGS_count) {
               return;
             }
-            PrintMessage(channel, context, &str_builder,
-                         {
-                             .pretty = FLAGS_pretty,
-                             .max_vector_size =
-                                 static_cast<size_t>(FLAGS_max_vector_size),
-                             .pretty_max = FLAGS_pretty_max,
-                             .print_timestamps = FLAGS_print_timestamps,
-                             .json = FLAGS_json,
-                             .distributed_clock = false,
-                             .use_hex = FLAGS_use_hex,
-                         });
-            ++message_count;
+
+            printer.PrintMessage(channel, context);
             next_send_time = context.monotonic_event_time +
                              std::chrono::milliseconds(FLAGS_rate_limit);
-            if (FLAGS_count > 0 && message_count >= FLAGS_count) {
+            if (FLAGS_count > 0 && printer.message_count() >= FLAGS_count) {
               cli_info.event_loop->Exit();
             }
           }
diff --git a/aos/events/logging/log_cat.cc b/aos/events/logging/log_cat.cc
index f925651..a2441c8 100644
--- a/aos/events/logging/log_cat.cc
+++ b/aos/events/logging/log_cat.cc
@@ -59,7 +59,8 @@
 DEFINE_double(monotonic_end_time, 0.0,
               "If set, only print messages sent at or before this many seconds "
               "after epoch.");
-DEFINE_bool(use_hex, false, "Are integers in the messages printed in hex notation.");
+DEFINE_bool(hex, false,
+            "Are integers in the messages printed in hex notation.");
 
 using aos::monotonic_clock;
 namespace chrono = std::chrono;
@@ -200,18 +201,16 @@
 // This class prints out all data from a node on a boot.
 class NodePrinter {
  public:
-  NodePrinter(aos::EventLoop *event_loop, uint64_t *message_print_counter,
-              aos::SimulatedEventLoopFactory *factory,
-              aos::FastStringBuilder *builder)
+  NodePrinter(aos::EventLoop *event_loop,
+              aos::SimulatedEventLoopFactory *factory, aos::Printer *printer)
       : factory_(factory),
         node_factory_(factory->GetNodeEventLoopFactory(event_loop->node())),
         event_loop_(event_loop),
-        message_print_counter_(message_print_counter),
         node_name_(
             event_loop_->node() == nullptr
                 ? ""
                 : std::string(event_loop->node()->name()->string_view())),
-        builder_(builder) {
+        printer_(printer) {
     event_loop_->SkipTimingReport();
     event_loop_->SkipAosLog();
 
@@ -253,6 +252,9 @@
           if (!FLAGS_print) {
             return;
           }
+          if (FLAGS_count > 0 && printer_->message_count() >= FLAGS_count) {
+            return;
+          }
 
           if (!FLAGS_fetch && !started_) {
             return;
@@ -263,19 +265,8 @@
             return;
           }
 
-          PrintMessage(
-              node_name_, node_factory_, channel, context, builder_,
-              {
-                  .pretty = FLAGS_pretty,
-                  .max_vector_size = static_cast<size_t>(FLAGS_max_vector_size),
-                  .pretty_max = FLAGS_pretty_max,
-                  .print_timestamps = FLAGS_print_timestamps,
-                  .json = FLAGS_json,
-                  .distributed_clock = FLAGS_distributed_clock,
-                  .use_hex = FLAGS_use_hex,
-              });
-          ++(*message_print_counter_);
-          if (FLAGS_count > 0 && *message_print_counter_ >= FLAGS_count) {
+          printer_->PrintMessage(node_name_, node_factory_, channel, context);
+          if (FLAGS_count > 0 && printer_->message_count() >= FLAGS_count) {
             factory_->Exit();
           }
         });
@@ -318,13 +309,11 @@
   aos::NodeEventLoopFactory *node_factory_;
   aos::EventLoop *event_loop_;
 
-  uint64_t *message_print_counter_ = nullptr;
-
   std::string node_name_;
 
   bool started_ = false;
 
-  aos::FastStringBuilder *builder_;
+  aos::Printer *printer_ = nullptr;
 };
 
 int main(int argc, char **argv) {
@@ -393,9 +382,17 @@
     }
   }
 
-  aos::FastStringBuilder builder;
-
-  uint64_t message_print_counter = 0;
+  aos::Printer printer(
+      {
+          .pretty = FLAGS_pretty,
+          .max_vector_size = static_cast<size_t>(FLAGS_max_vector_size),
+          .pretty_max = FLAGS_pretty_max,
+          .print_timestamps = FLAGS_print_timestamps,
+          .json = FLAGS_json,
+          .distributed_clock = FLAGS_distributed_clock,
+          .hex = FLAGS_hex,
+      },
+      false);
 
   std::vector<NodePrinter *> printers;
   printers.resize(aos::configuration::NodesCount(reader.configuration()),
@@ -413,12 +410,11 @@
     // notified when the log starts and stops.
     aos::NodeEventLoopFactory *node_factory =
         event_loop_factory.GetNodeEventLoopFactory(node);
-    node_factory->OnStartup([&event_loop_factory, node_factory,
-                             &message_print_counter, &builder, &printers,
-                             node_index]() {
-      printers[node_index] = node_factory->AlwaysStart<NodePrinter>(
-          "printer", &message_print_counter, &event_loop_factory, &builder);
-    });
+    node_factory->OnStartup(
+        [&event_loop_factory, node_factory, &printer, &printers, node_index]() {
+          printers[node_index] = node_factory->AlwaysStart<NodePrinter>(
+              "printer", &event_loop_factory, &printer);
+        });
     node_factory->OnShutdown(
         [&printers, node_index]() { printers[node_index] = nullptr; });
 
diff --git a/aos/network/multinode_timestamp_filter.cc b/aos/network/multinode_timestamp_filter.cc
index 8c48fa1..32d345a 100644
--- a/aos/network/multinode_timestamp_filter.cc
+++ b/aos/network/multinode_timestamp_filter.cc
@@ -1306,16 +1306,29 @@
         // hitting this anymore.  I'm also likely the one who will be debugging
         // it next and would rather spend the time debugging it when I get a bug
         // report.
-        gradients[i].emplace_back(
-            std::string("- ") +
-            filter.filter->DebugOffsetError(
-                filter.b_filter, NoncausalTimestampFilter::Pointer(),
-                base_clock_[i], 0.0, base_clock_[filter.b_index], 0.0, i,
-                filter.b_index));
-        gradients[filter.b_index].emplace_back(filter.filter->DebugOffsetError(
-            filter.b_filter, NoncausalTimestampFilter::Pointer(),
-            base_clock_[i], 0.0, base_clock_[filter.b_index], 0.0, i,
-            filter.b_index));
+        if (FLAGS_bounds_offset_error) {
+          gradients[i].emplace_back(
+              std::string("- ") +
+              filter.filter->DebugOffsetError(
+                  nullptr, NoncausalTimestampFilter::Pointer(), base_clock_[i],
+                  0.0, base_clock_[filter.b_index], 0.0, i, filter.b_index));
+          gradients[filter.b_index].emplace_back(
+              filter.filter->DebugOffsetError(
+                  nullptr, NoncausalTimestampFilter::Pointer(), base_clock_[i],
+                  0.0, base_clock_[filter.b_index], 0.0, i, filter.b_index));
+        } else {
+          gradients[i].emplace_back(
+              std::string("- ") +
+              filter.filter->DebugOffsetError(
+                  filter.b_filter, NoncausalTimestampFilter::Pointer(),
+                  base_clock_[i], 0.0, base_clock_[filter.b_index], 0.0, i,
+                  filter.b_index));
+          gradients[filter.b_index].emplace_back(
+              filter.filter->DebugOffsetError(
+                  filter.b_filter, NoncausalTimestampFilter::Pointer(),
+                  base_clock_[i], 0.0, base_clock_[filter.b_index], 0.0, i,
+                  filter.b_index));
+        }
       }
     }
   }
diff --git a/aos/util/top.cc b/aos/util/top.cc
index 9a6bbd0..79ee2db 100644
--- a/aos/util/top.cc
+++ b/aos/util/top.cc
@@ -172,6 +172,7 @@
         pids.insert(pid);
       }
     }
+    closedir(dir);
   }
 
   for (const pid_t pid : pids) {
diff --git a/frc971/zeroing/imu_zeroer.cc b/frc971/zeroing/imu_zeroer.cc
index 6bc2d0e..0872b1c 100644
--- a/frc971/zeroing/imu_zeroer.cc
+++ b/frc971/zeroing/imu_zeroer.cc
@@ -28,7 +28,6 @@
 ImuZeroer::ImuZeroer(FaultBehavior fault_behavior)
     : fault_behavior_(fault_behavior) {
   gyro_average_.setZero();
-  accel_average_.setZero();
   last_gyro_sample_.setZero();
   last_accel_sample_.setZero();
 }
@@ -47,8 +46,7 @@
 
 std::optional<Eigen::Vector3d> ImuZeroer::ZeroedAccel() const {
   return Faulted() ? std::nullopt
-                   : std::make_optional<Eigen::Vector3d>(last_accel_sample_ -
-                                                         accel_average_);
+                   : std::make_optional<Eigen::Vector3d>(last_accel_sample_);
 }
 
 Eigen::Vector3d ImuZeroer::GyroOffset() const { return gyro_average_; }
@@ -129,9 +127,9 @@
   builder.add_gyro_y_average(GyroOffset().y());
   builder.add_gyro_z_average(GyroOffset().z());
 
-  builder.add_accel_x_average(accel_average_.x());
-  builder.add_accel_y_average(accel_average_.y());
-  builder.add_accel_z_average(accel_average_.z());
+  builder.add_accel_x_average(accel_averager_.GetAverage()[0]);
+  builder.add_accel_y_average(accel_averager_.GetAverage()[1]);
+  builder.add_accel_z_average(accel_averager_.GetAverage()[2]);
 
   return builder.Finish();
 }
diff --git a/frc971/zeroing/imu_zeroer.h b/frc971/zeroing/imu_zeroer.h
index f9ae4d2..7fad58c 100644
--- a/frc971/zeroing/imu_zeroer.h
+++ b/frc971/zeroing/imu_zeroer.h
@@ -70,7 +70,6 @@
   Averager<double, kSamplesToAverage, 3> accel_averager_;
   // The average zero position of the gyro.
   Eigen::Vector3d gyro_average_;
-  Eigen::Vector3d accel_average_;
   Eigen::Vector3d last_gyro_sample_;
   Eigen::Vector3d last_accel_sample_;
 
diff --git a/scouting/www/counter_button/counter_button.component.css b/scouting/www/counter_button/counter_button.component.css
index 35c1929..df95f65 100644
--- a/scouting/www/counter_button/counter_button.component.css
+++ b/scouting/www/counter_button/counter_button.component.css
@@ -8,3 +8,7 @@
 * {
   padding: 10px;
 }
+
+.no-touch-action {
+  touch-action: manipulation;
+}
diff --git a/scouting/www/counter_button/counter_button.ng.html b/scouting/www/counter_button/counter_button.ng.html
index 4abf415..3300ff2 100644
--- a/scouting/www/counter_button/counter_button.ng.html
+++ b/scouting/www/counter_button/counter_button.ng.html
@@ -1,4 +1,11 @@
 <h4><ng-content></ng-content></h4>
-<button (click)="update(1)" class="btn btn-secondary btn-block">+</button>
+<button (click)="update(1)" class="btn btn-secondary btn-block no-touch-action">
+  +
+</button>
 <h3>{{value}}</h3>
-<button (click)="update(-1)" class="btn btn-secondary btn-block">-</button>
+<button
+  (click)="update(-1)"
+  class="btn btn-secondary btn-block no-touch-action"
+>
+  -
+</button>
diff --git a/scouting/www/entry/entry.component.css b/scouting/www/entry/entry.component.css
index 24e4127..6d13657 100644
--- a/scouting/www/entry/entry.component.css
+++ b/scouting/www/entry/entry.component.css
@@ -15,3 +15,12 @@
 button {
   touch-action: manipulation;
 }
+
+#switchFldbtn {
+  width: 15%;
+  display: block;
+  margin: 0 auto;
+  box-shadow: 2px 2px 1px #ccc;
+  max-width: 105px;
+  text-align: center;
+}
diff --git a/scouting/www/entry/entry.component.ts b/scouting/www/entry/entry.component.ts
index d6829c5..08f4dd4 100644
--- a/scouting/www/entry/entry.component.ts
+++ b/scouting/www/entry/entry.component.ts
@@ -38,6 +38,23 @@
   f: 'Finals',
 };
 
+const IMAGES_ARRAY = [
+  {
+    id: 'field_quadrants_image',
+    original_image:
+      '/sha256/cbb99a057a2504e80af526dae7a0a04121aed84c56a6f4889e9576fe1c20c61e/pictures/field/quadrants.jpeg',
+    reversed_image:
+      '/sha256/2c67fffbb722e9a7d0e1d270f1aad7f39a2dc8493c2e7ad1ae50bd6fa52d5bb7/pictures/field/reversed_quadrants.jpeg',
+  },
+  {
+    id: 'field_balls_image',
+    original_image:
+      '/sha256/e095cc8a75d804b0e2070e0a941fab37154176756d4c1a775e53cc48c3a732b9/pictures/field/balls.jpeg',
+    reversed_image:
+      '/sha256/fe4a4605c03598611c583d4dcdf28e06a056a17302ae91f5c527568966d95f3a/pictures/field/reversed_balls.jpeg',
+  },
+];
+
 @Component({
   selector: 'app-entry',
   templateUrl: './entry.ng.html',
@@ -122,6 +139,14 @@
     this.scrollToTop();
   }
 
+  flipImages() {
+    for (let obj of IMAGES_ARRAY) {
+      let img = document.getElementById(obj.id) as HTMLImageElement;
+      img.src = img.src.endsWith(obj.original_image)
+        ? obj.reversed_image
+        : obj.original_image;
+    }
+  }
   private scrollToTop() {
     this.header.nativeElement.scrollIntoView();
   }
diff --git a/scouting/www/entry/entry.ng.html b/scouting/www/entry/entry.ng.html
index 6dab39b..9be8db7 100644
--- a/scouting/www/entry/entry.ng.html
+++ b/scouting/www/entry/entry.ng.html
@@ -54,8 +54,12 @@
   </div>
 
   <div *ngSwitchCase="'Auto'" id="auto" class="container-fluid">
+    <button class="buttons" id="switch_field_button" (click)="flipImages()">
+      Flip
+    </button>
     <div class="row">
       <img
+        id="field_quadrants_image"
         src="/sha256/cbb99a057a2504e80af526dae7a0a04121aed84c56a6f4889e9576fe1c20c61e/pictures/field/quadrants.jpeg"
         alt="Quadrants Image"
       />
@@ -97,8 +101,9 @@
     </div>
     <div class="row">
       <img
+        id="field_balls_image"
         src="/sha256/e095cc8a75d804b0e2070e0a941fab37154176756d4c1a775e53cc48c3a732b9/pictures/field/balls.jpeg"
-        alt="Image"
+        alt="Balls Image"
       />
       <form>
         <!--Choice for each ball location-->
diff --git a/third_party/y2022/field/reversed_balls.jpeg b/third_party/y2022/field/reversed_balls.jpeg
new file mode 100644
index 0000000..5a2dc93
--- /dev/null
+++ b/third_party/y2022/field/reversed_balls.jpeg
Binary files differ
diff --git a/third_party/y2022/field/reversed_quadrants.jpeg b/third_party/y2022/field/reversed_quadrants.jpeg
new file mode 100644
index 0000000..adacd8d
--- /dev/null
+++ b/third_party/y2022/field/reversed_quadrants.jpeg
Binary files differ
diff --git a/y2022/control_loops/superstructure/catapult/catapult.cc b/y2022/control_loops/superstructure/catapult/catapult.cc
index 612a17a..cdffecd 100644
--- a/y2022/control_loops/superstructure/catapult/catapult.cc
+++ b/y2022/control_loops/superstructure/catapult/catapult.cc
@@ -67,7 +67,8 @@
                           Eigen::Matrix<double, 2, 1> X_final) {
   X_initial_ = X_initial;
   X_final_ = X_final;
-  objective_vector_ =
+  // If we mark this noalias(), it won't re-allocate the vector each time.
+  objective_vector_.noalias() =
       X_initial(1, 0) * accel_q_ + final_q_ * (Af_ * X_initial - X_final);
 
   auto status = solver_.SetObjectiveVector(objective_vector_);
diff --git a/y2022/vision/README.md b/y2022/vision/README.md
new file mode 100644
index 0000000..2073fa7
--- /dev/null
+++ b/y2022/vision/README.md
@@ -0,0 +1 @@
+# [Spartan Series Vision Presentation](https://docs.google.com/presentation/d/1QJ-c5YYI_HC-rvaBSfPz1umMqL3TcKnja0nijyAlbVE/edit?usp=sharing)