blob: ae69996ca94508e8e976f311de5c6e65b44e2178 [file] [log] [blame]
James Kuszmaul9f2f53c2023-02-19 14:08:18 -08001#include "frc971/imu_reader/imu_watcher.h"
2
3#include "frc971/wpilib/imu_batch_generated.h"
4
5namespace frc971::controls {
6namespace {
7// Return the amount of distance that the drivetrain can travel before the
8// encoders will wrap. Necessary because the pico only sends over the encoders
9// in 16-bit counters, which will wrap relatively readily.
10double EncoderWrapDistance(double drivetrain_distance_per_encoder_tick) {
11 return drivetrain_distance_per_encoder_tick * (1 << 16);
12}
13} // namespace
14ImuWatcher::ImuWatcher(
15 aos::EventLoop *event_loop,
16 const control_loops::drivetrain::DrivetrainConfig<double> &dt_config,
17 const double drivetrain_distance_per_encoder_tick,
18 std::function<
19 void(aos::monotonic_clock::time_point, aos::monotonic_clock::time_point,
20 std::optional<Eigen::Vector2d>, Eigen::Vector3d, Eigen::Vector3d)>
James Kuszmaul54fe9d02023-03-23 20:32:40 -070021 callback,
22 TimestampSource timestamp_source)
James Kuszmaul9f2f53c2023-02-19 14:08:18 -080023 : dt_config_(dt_config),
24 callback_(std::move(callback)),
25 zeroer_(zeroing::ImuZeroer::FaultBehavior::kTemporary),
26 left_encoder_(
27 -EncoderWrapDistance(drivetrain_distance_per_encoder_tick) / 2.0,
28 EncoderWrapDistance(drivetrain_distance_per_encoder_tick)),
29 right_encoder_(
30 -EncoderWrapDistance(drivetrain_distance_per_encoder_tick) / 2.0,
31 EncoderWrapDistance(drivetrain_distance_per_encoder_tick)) {
James Kuszmaul723e66e2023-04-05 21:22:57 -070032 event_loop->MakeWatcher("/localizer", [this, timestamp_source, event_loop](
James Kuszmaul54fe9d02023-03-23 20:32:40 -070033 const IMUValuesBatch &values) {
James Kuszmaul9f2f53c2023-02-19 14:08:18 -080034 CHECK(values.has_readings());
35 for (const IMUValues *value : *values.readings()) {
36 zeroer_.InsertAndProcessMeasurement(*value);
37 if (zeroer_.Faulted()) {
38 if (value->checksum_failed()) {
39 imu_fault_tracker_.pico_to_pi_checksum_mismatch++;
James Kuszmaul5e5dc532024-02-14 19:54:09 -080040 } else if (value->has_previous_reading_diag_stat() &&
41 value->previous_reading_diag_stat()->checksum_mismatch()) {
James Kuszmaul9f2f53c2023-02-19 14:08:18 -080042 imu_fault_tracker_.imu_to_pico_checksum_mismatch++;
43 } else {
44 imu_fault_tracker_.other_zeroing_faults++;
45 }
46 } else {
James Kuszmaul5e5dc532024-02-14 19:54:09 -080047 if (!first_valid_data_counter_.has_value() &&
48 value->has_data_counter()) {
James Kuszmaul9f2f53c2023-02-19 14:08:18 -080049 first_valid_data_counter_ = value->data_counter();
50 }
51 }
James Kuszmaul723e66e2023-04-05 21:22:57 -070052 int messages_dropped_this_cycle = 0;
James Kuszmaul9f2f53c2023-02-19 14:08:18 -080053 if (first_valid_data_counter_.has_value()) {
54 total_imu_messages_received_++;
55 // Only update when we have good checksums, since the data counter
56 // could get corrupted.
57 if (!zeroer_.Faulted()) {
58 if (value->data_counter() < last_data_counter_) {
59 data_counter_offset_ += 1 << 16;
60 }
James Kuszmaul723e66e2023-04-05 21:22:57 -070061 const int total_dropped =
James Kuszmaul9f2f53c2023-02-19 14:08:18 -080062 (1 + value->data_counter() + data_counter_offset_ -
63 first_valid_data_counter_.value()) -
64 total_imu_messages_received_;
James Kuszmaul723e66e2023-04-05 21:22:57 -070065 messages_dropped_this_cycle =
66 total_dropped - imu_fault_tracker_.missed_messages;
67 imu_fault_tracker_.missed_messages = total_dropped;
James Kuszmaul9f2f53c2023-02-19 14:08:18 -080068 last_data_counter_ = value->data_counter();
69 }
70 }
71 // Set encoders to nullopt if we are faulted at all (faults may include
72 // checksum mismatches).
James Kuszmaul5e5dc532024-02-14 19:54:09 -080073 const bool have_encoders = !zeroer_.Faulted() &&
74 value->has_left_encoder() &&
75 value->has_right_encoder();
James Kuszmaul9f2f53c2023-02-19 14:08:18 -080076 const std::optional<Eigen::Vector2d> encoders =
James Kuszmaul5e5dc532024-02-14 19:54:09 -080077 have_encoders ? std::make_optional(Eigen::Vector2d{
78 left_encoder_.Unwrap(value->left_encoder()),
79 right_encoder_.Unwrap(value->right_encoder())})
80 : std::nullopt;
James Kuszmaul9f2f53c2023-02-19 14:08:18 -080081 {
James Kuszmaul04a343c2023-02-20 16:38:22 -080082 const aos::monotonic_clock::time_point pi_read_timestamp =
83 aos::monotonic_clock::time_point(
84 std::chrono::nanoseconds(value->monotonic_timestamp_ns()));
James Kuszmaul54fe9d02023-03-23 20:32:40 -070085 // If we can't trust the imu reading, just naively increment the
86 // pico timestamp.
87 const aos::monotonic_clock::time_point pico_timestamp =
88 timestamp_source == TimestampSource::kPi
89 ? pi_read_timestamp
90 : (zeroer_.Faulted()
91 ? (last_pico_timestamp_.has_value()
92 ? last_pico_timestamp_.value() + kNominalDt
93 : aos::monotonic_clock::epoch())
94 : aos::monotonic_clock::time_point(
95 std::chrono::microseconds(
96 value->pico_timestamp_us())));
James Kuszmaul9f2f53c2023-02-19 14:08:18 -080097 // TODO(james): If we get large enough drift off of the pico,
98 // actually do something about it.
99 if (!pico_offset_.has_value()) {
James Kuszmaul04a343c2023-02-20 16:38:22 -0800100 pico_offset_ = pi_read_timestamp - pico_timestamp;
James Kuszmaul9f2f53c2023-02-19 14:08:18 -0800101 last_pico_timestamp_ = pico_timestamp;
102 }
James Kuszmaul723e66e2023-04-05 21:22:57 -0700103 // The pico will sleep for a minimum of 3 seconds when resetting the
104 // IMU. Any absence of messages for less than that time is probably not
105 // an actual reset.
James Kuszmaul9f2f53c2023-02-19 14:08:18 -0800106 if (pico_timestamp < last_pico_timestamp_) {
107 pico_offset_.value() += std::chrono::microseconds(1ULL << 32);
108 }
109 const aos::monotonic_clock::time_point sample_timestamp =
110 pico_offset_.value() + pico_timestamp;
James Kuszmaul04a343c2023-02-20 16:38:22 -0800111 pico_offset_error_ = pi_read_timestamp - sample_timestamp;
James Kuszmaul723e66e2023-04-05 21:22:57 -0700112
113 if (last_imu_message_.has_value()) {
114 constexpr std::chrono::seconds kMinimumImuResetTime(3);
115 const std::chrono::nanoseconds message_time_gap =
116 last_imu_message_.value() -
117 event_loop->context().monotonic_event_time;
118 if (message_time_gap > kMinimumImuResetTime) {
119 bool assigned_fault = false;
120 // The IMU has probably reset; attempt to determine whether just the
121 // IMU was reset or if the IMU and pico reset.
122 if (std::chrono::abs(pico_offset_error_) >
123 std::chrono::seconds(1)) {
124 // If we have this big of a gap, then everything downstream of
125 // this is going to complain anyways, so reset the offset.
126 pico_offset_.reset();
127
128 imu_fault_tracker_.probable_pico_reset_count++;
129 assigned_fault = true;
130 }
131 // See if we dropped the "right" number of messages for the gap in
132 // question.
133 if (std::chrono::abs(messages_dropped_this_cycle * kNominalDt -
134 message_time_gap) >
135 std::chrono::milliseconds(100)) {
136 imu_fault_tracker_.probable_imu_reset_count++;
137 assigned_fault = true;
138 }
139
140 if (!assigned_fault) {
141 imu_fault_tracker_.unassignable_reset_count++;
142 }
143 }
144 }
James Kuszmaul9f2f53c2023-02-19 14:08:18 -0800145 const bool zeroed = zeroer_.Zeroed();
146
147 // When not zeroed, we aim to approximate zero acceleration by doing a
148 // zero-order hold on the gyro and setting the accelerometer readings to
149 // gravity.
James Kuszmaul04a343c2023-02-20 16:38:22 -0800150 callback_(sample_timestamp, pi_read_timestamp, encoders,
151 zeroed ? zeroer_.ZeroedGyro().value() : last_gyro_,
James Kuszmaul9f2f53c2023-02-19 14:08:18 -0800152 zeroed ? zeroer_.ZeroedAccel().value()
153 : dt_config_.imu_transform.transpose() *
154 Eigen::Vector3d::UnitZ());
155
156 if (zeroed) {
157 last_gyro_ = zeroer_.ZeroedGyro().value();
158 }
159 last_pico_timestamp_ = pico_timestamp;
160 }
James Kuszmaul723e66e2023-04-05 21:22:57 -0700161 last_imu_message_ = event_loop->context().monotonic_event_time;
James Kuszmaul9f2f53c2023-02-19 14:08:18 -0800162 }
163 });
164}
165} // namespace frc971::controls