blob: ebaf101ad6515ddbb3355dae133e62a215afa956 [file] [log] [blame]
James Kuszmaulbdc6a792023-08-12 16:29:38 -07001#include "frc971/zeroing/continuous_absolute_encoder.h"
2
3#include <cmath>
4#include <numeric>
5
6#include "glog/logging.h"
7
8#include "aos/containers/error_list.h"
9#include "frc971/zeroing/wrap.h"
10
Stephan Pleinesf63bde82024-01-13 15:59:33 -080011namespace frc971::zeroing {
James Kuszmaulbdc6a792023-08-12 16:29:38 -070012
13ContinuousAbsoluteEncoderZeroingEstimator::
14 ContinuousAbsoluteEncoderZeroingEstimator(
15 const constants::ContinuousAbsoluteEncoderZeroingConstants &constants)
16 : constants_(constants), move_detector_(constants_.moving_buffer_size) {
17 relative_to_absolute_offset_samples_.reserve(constants_.average_filter_size);
18 Reset();
19}
20
21void ContinuousAbsoluteEncoderZeroingEstimator::Reset() {
22 zeroed_ = false;
23 error_ = false;
24 first_offset_ = 0.0;
25 offset_ = 0.0;
26 samples_idx_ = 0;
27 position_ = 0.0;
28 nan_samples_ = 0;
29 relative_to_absolute_offset_samples_.clear();
30 move_detector_.Reset();
31}
32
33// The math here is a bit backwards, but I think it'll be less error prone that
34// way and more similar to the version with a pot as well.
35//
36// We start by unwrapping the absolute encoder using the relative encoder. This
37// puts us in a non-wrapping space and lets us average a bit easier. From
38// there, we can compute an offset and wrap ourselves back such that we stay
39// close to the middle value.
40//
41// To guard against the robot moving while updating estimates, buffer a number
42// of samples and check that the buffered samples are not different than the
43// zeroing threshold. At any point that the samples differ too much, do not
44// update estimates based on those samples.
45void ContinuousAbsoluteEncoderZeroingEstimator::UpdateEstimate(
46 const AbsolutePosition &info) {
47 // Check for Abs Encoder NaN value that would mess up the rest of the zeroing
48 // code below. NaN values are given when the Absolute Encoder is disconnected.
49 if (::std::isnan(info.absolute_encoder())) {
50 if (zeroed_) {
51 VLOG(1) << "NAN on absolute encoder.";
52 errors_.Set(ZeroingError::LOST_ABSOLUTE_ENCODER);
53 error_ = true;
54 } else {
55 ++nan_samples_;
56 VLOG(1) << "NAN on absolute encoder while zeroing " << nan_samples_;
57 if (nan_samples_ >= constants_.average_filter_size) {
58 errors_.Set(ZeroingError::LOST_ABSOLUTE_ENCODER);
59 error_ = true;
60 zeroed_ = true;
61 }
62 }
63 // Throw some dummy values in for now.
64 filtered_absolute_encoder_ = info.absolute_encoder();
65 position_ = offset_ + info.encoder();
66 return;
67 }
68
69 const bool moving = move_detector_.Update(info, constants_.moving_buffer_size,
70 constants_.zeroing_threshold);
71
72 if (!moving) {
73 const PositionStruct &sample = move_detector_.GetSample();
74
75 // adjusted_* numbers are nominally in the desired output frame.
76 const double adjusted_absolute_encoder =
77 sample.absolute_encoder - constants_.measured_absolute_position;
78
79 // Note: If are are near the breakpoint of the absolute encoder, this number
80 // will be jitter between numbers that are ~one_revolution_distance apart.
81 // For that reason, we rewrap it so that we are not near that boundary.
82 const double relative_to_absolute_offset =
83 adjusted_absolute_encoder - sample.encoder;
84
85 // To avoid the aforementioned jitter, choose a base value to use for
86 // wrapping. When we have no prior samples, just use the current offset.
87 // Otherwise, we use an arbitrary prior offset (the stored offsets will all
88 // already be wrapped).
89 const double relative_to_absolute_offset_wrap_base =
90 relative_to_absolute_offset_samples_.size() == 0
91 ? relative_to_absolute_offset
92 : relative_to_absolute_offset_samples_[0];
93
94 const double relative_to_absolute_offset_wrapped =
95 UnWrap(relative_to_absolute_offset_wrap_base,
96 relative_to_absolute_offset, constants_.one_revolution_distance);
97
98 const size_t relative_to_absolute_offset_samples_size =
99 relative_to_absolute_offset_samples_.size();
100 if (relative_to_absolute_offset_samples_size <
101 constants_.average_filter_size) {
102 relative_to_absolute_offset_samples_.push_back(
103 relative_to_absolute_offset_wrapped);
104 } else {
105 relative_to_absolute_offset_samples_[samples_idx_] =
106 relative_to_absolute_offset_wrapped;
107 }
108 samples_idx_ = (samples_idx_ + 1) % constants_.average_filter_size;
109
110 // Compute the average offset between the absolute encoder and relative
111 // encoder. Because we just pushed a value, the size() will never be zero.
112 offset_ =
113 ::std::accumulate(relative_to_absolute_offset_samples_.begin(),
114 relative_to_absolute_offset_samples_.end(), 0.0) /
115 relative_to_absolute_offset_samples_.size();
116
117 // To provide a value that can be used to estimate the
118 // measured_absolute_position when zeroing, we just need to output the
119 // current absolute encoder value. We could make use of the averaging
120 // implicit in offset_ to reduce the noise on this slightly.
121 filtered_absolute_encoder_ = sample.absolute_encoder;
122
123 if (offset_ready()) {
124 if (!zeroed_) {
125 first_offset_ = offset_;
126 }
127
128 if (::std::abs(first_offset_ - offset_) >
129 constants_.allowable_encoder_error *
130 constants_.one_revolution_distance) {
131 VLOG(1) << "Offset moved too far. Initial: " << first_offset_
132 << ", current " << offset_ << ", allowable change: "
133 << constants_.allowable_encoder_error *
134 constants_.one_revolution_distance;
135 errors_.Set(ZeroingError::OFFSET_MOVED_TOO_FAR);
136 error_ = true;
137 }
138
139 zeroed_ = true;
140 }
141 }
142
143 // Update the position. Wrap it to reflect the fact that we do not have
144 // sufficient information to disambiguate which revolution we are on (also,
145 // since this value is primarily meant for debugging, this makes it easier to
146 // see that the device is actually at zero without having to divide by 2 *
147 // pi).
148 position_ =
149 Wrap(0.0, offset_ + info.encoder(), constants_.one_revolution_distance);
150}
151
152flatbuffers::Offset<ContinuousAbsoluteEncoderZeroingEstimator::State>
153ContinuousAbsoluteEncoderZeroingEstimator::GetEstimatorState(
154 flatbuffers::FlatBufferBuilder *fbb) const {
155 flatbuffers::Offset<flatbuffers::Vector<ZeroingError>> errors_offset =
156 errors_.ToFlatbuffer(fbb);
157
158 State::Builder builder(*fbb);
159 builder.add_error(error_);
160 builder.add_zeroed(zeroed_);
161 builder.add_position(position_);
162 builder.add_absolute_position(filtered_absolute_encoder_);
163 builder.add_errors(errors_offset);
164 return builder.Finish();
165}
166
Stephan Pleinesf63bde82024-01-13 15:59:33 -0800167} // namespace frc971::zeroing