blob: 68ef38fd844c252472e17eee95ea55a99e8418f8 [file] [log] [blame]
Adam Snaiderc4b3c192015-02-01 01:30:39 +00001#ifndef FRC971_ZEROING_ZEROING_H_
2#define FRC971_ZEROING_ZEROING_H_
3
Austin Schuh66c59ba2019-01-26 20:34:35 -08004#include <algorithm>
5#include <cmath>
Philipp Schrader41d82912015-02-15 03:44:23 +00006#include <cstdint>
Adam Snaiderc4b3c192015-02-01 01:30:39 +00007#include <vector>
Philipp Schrader41d82912015-02-15 03:44:23 +00008
James Kuszmauldf6b1682024-07-06 13:53:10 -07009#include "absl/log/check.h"
Alex Perrycb7da4b2019-08-28 19:35:56 -070010#include "flatbuffers/flatbuffers.h"
Philipp Schrader790cb542023-07-05 21:06:52 -070011
Tyler Chatow3c47f3c2020-01-29 20:45:23 -080012#include "frc971/constants.h"
13#include "frc971/control_loops/control_loops_generated.h"
Alex Perrycb7da4b2019-08-28 19:35:56 -070014
Adam Snaiderb4119252015-02-15 01:30:57 +000015// TODO(pschrader): Flag an error if encoder index pulse is not n revolutions
16// away from the last one (i.e. got extra counts from noise, etc..)
17//
18// TODO(pschrader): Flag error if the pot disagrees too much with the encoder
19// after being zeroed.
20//
21// TODO(pschrader): Watch the offset over long periods of time and flag if it
22// gets too far away from the initial value.
23
Stephan Pleinesd99b1ee2024-02-02 20:56:44 -080024namespace frc971::zeroing {
Adam Snaiderc4b3c192015-02-01 01:30:39 +000025
Lee Mracekcdd19ee2019-01-07 10:19:54 -050026template <typename TPosition, typename TZeroingConstants, typename TState>
Neil Balch1049be92017-02-15 23:20:49 -080027class ZeroingEstimator {
28 public:
Lee Mracekcdd19ee2019-01-07 10:19:54 -050029 using Position = TPosition;
30 using ZeroingConstants = TZeroingConstants;
31 using State = TState;
Tyler Chatow3c47f3c2020-01-29 20:45:23 -080032 virtual ~ZeroingEstimator() {}
Neil Balch1049be92017-02-15 23:20:49 -080033
34 // Returns true if the logic considers the corresponding mechanism to be
Isaac Wilcove0851ffd2017-02-16 04:13:14 +000035 // zeroed.
Neil Balch1049be92017-02-15 23:20:49 -080036 virtual bool zeroed() const = 0;
37
Isaac Wilcove0851ffd2017-02-16 04:13:14 +000038 // Returns the estimated position of the corresponding mechanism.
Neil Balch1049be92017-02-15 23:20:49 -080039 virtual double offset() const = 0;
40
Isaac Wilcove0851ffd2017-02-16 04:13:14 +000041 // Returns true if there has been an error.
Neil Balch1049be92017-02-15 23:20:49 -080042 virtual bool error() const = 0;
Lee Mracekcdd19ee2019-01-07 10:19:54 -050043
44 // Returns true if an offset is ready.
45 virtual bool offset_ready() const = 0;
46
47 // Triggers an internal error. This is used for testing the error
48 // logic.
49 virtual void TriggerError() = 0;
50
51 // Resets the estimator, clearing error and zeroing states.
52 virtual void Reset() = 0;
53
54 // Updates the internal logic with new sensor values
55 virtual void UpdateEstimate(const Position &) = 0;
56
57 // Returns the state of the estimator
Alex Perrycb7da4b2019-08-28 19:35:56 -070058 virtual flatbuffers::Offset<State> GetEstimatorState(
59 flatbuffers::FlatBufferBuilder *fbb) const = 0;
Neil Balch1049be92017-02-15 23:20:49 -080060};
61
Austin Schuh66c59ba2019-01-26 20:34:35 -080062// Class to encapsulate the logic to decide when we are moving and which samples
63// are safe to use.
Alex Perrycb7da4b2019-08-28 19:35:56 -070064template <typename Position, typename PositionBuffer>
Austin Schuh66c59ba2019-01-26 20:34:35 -080065class MoveDetector {
66 public:
67 MoveDetector(size_t filter_size) {
68 buffered_samples_.reserve(filter_size);
69 Reset();
70 }
71
72 // Clears all the state.
73 void Reset() {
74 buffered_samples_.clear();
75 buffered_samples_idx_ = 0;
76 }
77
78 // Updates the detector with a new sample. Returns true if we are moving.
79 // buffer_size is the number of samples in the moving buffer, and
80 // zeroing_threshold is the max amount we can move within the period specified
81 // by buffer_size.
Alex Perrycb7da4b2019-08-28 19:35:56 -070082 bool Update(const PositionBuffer &position_buffer, size_t buffer_size,
Austin Schuh66c59ba2019-01-26 20:34:35 -080083 double zeroing_threshold) {
James Kuszmauldf6b1682024-07-06 13:53:10 -070084 CHECK_LT(0u, buffer_size);
Austin Schuh66c59ba2019-01-26 20:34:35 -080085 bool moving = true;
Alex Perrycb7da4b2019-08-28 19:35:56 -070086 Position position(position_buffer);
Austin Schuh66c59ba2019-01-26 20:34:35 -080087 if (buffered_samples_.size() < buffer_size) {
88 // Not enough samples to start determining if the robot is moving or not,
89 // don't use the samples yet.
90 buffered_samples_.push_back(position);
91 } else {
92 // Have enough samples to start determining if the robot is moving or not.
93 buffered_samples_[buffered_samples_idx_] = position;
94 const auto minmax_value = ::std::minmax_element(
95 buffered_samples_.begin(), buffered_samples_.end(),
96 [](const Position &left, const Position &right) {
97 return left.encoder < right.encoder;
98 });
99 const double min_value = minmax_value.first->encoder;
100 const double max_value = minmax_value.second->encoder;
101
102 if (::std::abs(max_value - min_value) < zeroing_threshold) {
103 // Robot isn't moving, use middle sample to determine offsets.
104 moving = false;
105 }
106 }
107 buffered_samples_idx_ = (buffered_samples_idx_ + 1) % buffer_size;
James Kuszmauldf6b1682024-07-06 13:53:10 -0700108
Austin Schuh66c59ba2019-01-26 20:34:35 -0800109 return moving;
110 }
111
112 // Returns a safe sample if we aren't moving as reported by Update.
113 const Position &GetSample() const {
114 // The robot is not moving, use the middle sample to determine offsets.
115 // The middle sample makes it so that we don't use the samples from the
116 // beginning or end of periods when the robot is moving.
117 const int middle_index =
118 (buffered_samples_idx_ + (buffered_samples_.size() / 2)) %
119 buffered_samples_.size();
120 return buffered_samples_[middle_index];
121 }
122
123 private:
124 // The last buffer_size samples.
125 ::std::vector<Position> buffered_samples_;
126 // The index to place the next sample in.
127 size_t buffered_samples_idx_;
128};
129
Brian Silvermana57b7012020-03-11 20:19:23 -0700130// A trivial ZeroingEstimator which just passes the position straight through.
Tyler Chatow3c47f3c2020-01-29 20:45:23 -0800131class RelativeEncoderZeroingEstimator
Siddhant Kanwar0e37f592022-02-21 19:26:50 -0800132 : public ZeroingEstimator<RelativePosition,
133 constants::RelativeEncoderZeroingConstants,
Tyler Chatow3c47f3c2020-01-29 20:45:23 -0800134 RelativeEncoderEstimatorState> {
135 public:
Siddhant Kanwar0e37f592022-02-21 19:26:50 -0800136 explicit RelativeEncoderZeroingEstimator(
137 const constants::RelativeEncoderZeroingConstants &) {}
Tyler Chatow3c47f3c2020-01-29 20:45:23 -0800138
139 // Update position with new position from encoder
140 void UpdateEstimate(const RelativePosition &position) override {
141 position_ = position.encoder();
142 }
143
144 // We alre always zeroed
145 bool zeroed() const override { return true; }
146
147 // Starting position of the joint
148 double offset() const override { return 0; }
149
150 // Has an error occured? Note: Only triggered by TriggerError()
151 bool error() const override { return error_; }
152
153 // Offset is always ready, since we are always zeroed.
154 bool offset_ready() const override { return true; }
155
156 void TriggerError() override { error_ = true; }
157
158 void Reset() override { error_ = false; }
159
160 flatbuffers::Offset<State> GetEstimatorState(
161 flatbuffers::FlatBufferBuilder *fbb) const override {
162 State::Builder builder(*fbb);
163 builder.add_error(error_);
164 builder.add_position(position_);
165 return builder.Finish();
166 }
167
168 private:
169 // Position from encoder relative to start
170 double position_ = 0;
171 bool error_ = false;
172};
173
Stephan Pleinesd99b1ee2024-02-02 20:56:44 -0800174} // namespace frc971::zeroing
Adam Snaiderc4b3c192015-02-01 01:30:39 +0000175
Adam Snaiderc4b3c192015-02-01 01:30:39 +0000176#endif // FRC971_ZEROING_ZEROING_H_