blob: 342bc0b9f1e89722989845a8109e817651fabe9a [file] [log] [blame]
Adam Snaiderc4b3c192015-02-01 01:30:39 +00001#include <unistd.h>
2
3#include <memory>
4
5#include <random>
6
7#include "gtest/gtest.h"
8#include "frc971/zeroing/zeroing_queue.q.h"
9#include "frc971/zeroing/zeroing.h"
10#include "aos/common/queue_testutils.h"
11#include "aos/common/util/thread.h"
12#include "aos/common/die.h"
13
14namespace frc971 {
15namespace zeroing {
16
17const int kSeed1 = 0;
18const int kSeed2 = 3;
19
20class NoiseGenerator {
21 public:
22 virtual double AddNoiseToSample(double sample) = 0;
23};
24
25class NoNoise : public NoiseGenerator {
26 public:
27 double AddNoiseToSample(double sample) { return sample; }
28};
29
30class FloorNoise : public NoiseGenerator {
31 public:
32 FloorNoise(double accuracy) : accuracy_(accuracy) {}
33
34 double AddNoiseToSample(double sample) {
35 return accuracy_ * ((int)(sample / accuracy_));
36 }
37
38 private:
39 double accuracy_;
40};
41
42class GaussianNoise : public NoiseGenerator {
43 public:
44 GaussianNoise(unsigned int seed, double stddev)
45 : generator_(seed), distribution_(0.0, stddev) {}
46
47 double AddNoiseToSample(double sample) {
48 return sample + distribution_(generator_);
49 }
50
51 private:
52 std::default_random_engine generator_;
53 std::normal_distribution<double> distribution_;
54};
55
56class ZeroingEstimatorSimulator {
57 public:
58 ZeroingEstimatorSimulator(double start_pos, double index_diff,
59 NoiseGenerator& noise, int filter_size = 30)
60 : estimator_(index_diff, filter_size), noise_generator_(noise) {
61 cur_index_segment_ = (int)(start_pos / index_diff);
62 index_diff_ = index_diff;
63 start_pos_ = start_pos;
64 cur_pos_ = start_pos;
65 index_count_ = 0;
66 encoder_slip_ = 0;
67
68 // Initialize the ZeroingEstimator instance with the first sensor readings.
69 estimator_.UpdateEstimate(getInfo());
70 }
71
72 void MoveTo(double new_pos) {
73 int new_index = (int)(new_pos - encoder_slip_) / index_diff_;
74 if (new_index < cur_index_segment_) {
75 cur_index_ = new_index + 1;
76 index_count_++;
77 }
78 if (new_index > cur_index_segment_) {
79 cur_index_ = new_index;
80 index_count_++;
81 }
82 cur_index_segment_ = new_index;
83 cur_pos_ = new_pos;
84
85 estimator_.UpdateEstimate(getInfo());
86 }
87
88 // Simulate the encoder slipping by `slip'.
89 void MoveWithEncoderSlip(double slip) {
90 encoder_slip_ += slip;
91
92 MoveTo(cur_pos_ + slip);
93
94 estimator_.UpdateEstimate(getInfo());
95 }
96
97 ZeroingInfo getInfo() {
98 ZeroingInfo estimate;
99 estimate.pot = noise_generator_.AddNoiseToSample(cur_pos_);
100 if (index_count_ == 0) {
101 estimate.index_encoder = 0.0;
102 } else {
103 estimate.index_encoder = cur_index_ * index_diff_ - start_pos_;
104 }
105 estimate.index_count = index_count_;
106 estimate.encoder = cur_pos_ - start_pos_ - encoder_slip_;
107 return estimate;
108 }
109
110 double getEstimate(void) { return estimator_.getPosition(); }
111
112 private:
113 int index_count_;
114 int cur_index_;
115 int cur_index_segment_;
116 double index_diff_;
117 double start_pos_;
118 double cur_pos_;
119 double encoder_slip_;
120 ZeroingEstimator estimator_;
121 NoiseGenerator& noise_generator_;
122};
123
124class QueueTest : public ::testing::Test {
125 protected:
126 void SetUp() override { aos::SetDieTestMode(true); }
127
128 aos::common::testing::GlobalCoreInstance my_core;
129 // Create a new instance of the test queue so that it invalidates the queue
130 // that it points to. Otherwise, we will have a pointer to shared memory that
131 // is no longer valid.
132 ::aos::Queue<TestMessage> my_test_queue;
133
134 QueueTest() : my_test_queue(".frc971.zeroing.test_queue") {}
135};
136
137TEST_F(QueueTest, FetchBlocking) {
138 // Make sure that the queue works.
139 my_test_queue.MakeWithBuilder().test_int(0x971).Send();
140 EXPECT_TRUE(my_test_queue.FetchNext());
141}
142
143TEST_F(QueueTest, SimpleStep) {
144 FloorNoise floored_pot(0.25);
145 ZeroingEstimatorSimulator sim(3.6, 1.0, floored_pot, 1);
146
147 // The first estimate should be 3.5 since that's the only reliable number we
148 // have. (i.e. 3.6 rounded down to the nearest 0.25 multiple.
149 ASSERT_NEAR(3.5, sim.getEstimate(), 0.001);
150
151 // Next we'll move to 3.65 which should still give us a reading of 3.50. This
152 // is because we're just using one sample to "filter" the noise. In this case
153 // the filter would take 3.5 from the pot value and subract the encoder
154 // reading of 0.05. In order to come up with an accurate estimate, we add the
155 // encoder value back in.
156 sim.MoveTo(3.65);
157 ASSERT_NEAR(3.50, sim.getEstimate(), 0.001);
158
159 // Now we move to 3.80 which should give us the a reading of 3.70. Similar to
160 // the above scenario we've now moved 0.20 in total which is the reading of
161 // the encoder. Unfortunately, we can't use the encoder value yet since we
162 // don't know where it is relative to the index pulse.
163 sim.MoveTo(3.80);
164 ASSERT_NEAR(3.75, sim.getEstimate(), 0.001);
165
166 // We move past the 4.00 mark right to 4.10. The pot value will read 4.00,
167 // the encoder reads 0.5 and the index pulse sample will read 0.4. Now we
168 // know that we are 0.1 past the 4.00 mark.
169 sim.MoveTo(4.10);
170 ASSERT_NEAR(4.10, sim.getEstimate(), 0.001);
171
172 // We move back to 3.80 and now we should have an accurate reading. The pot
173 // value reads 3.75, the encoder reads 0.2 and the index pulse is again set
174 // at 0.4. Thus we can deduce that we're 0.2 below the 4.00 mark (i.e. at
175 // 3.80)
176 sim.MoveTo(3.80);
177 ASSERT_NEAR(3.80, sim.getEstimate(), 0.001);
178
179 // Just for kicks we'll move back to a value of 2.56 which the estimator
180 // should be able to calculate.
181 sim.MoveTo(2.56);
182 ASSERT_NEAR(2.56, sim.getEstimate(), 0.001);
183}
184
185TEST_F(QueueTest, TestMovingAverageFilter) {
186 GaussianNoise pot_noise(kSeed1, 0.5 / 3.0);
187 ZeroingEstimatorSimulator sim(3.6, 1.0, pot_noise);
188
189 // The zeroing code is supposed to perform some filtering on the difference
190 // between the potentiometer value and the encoder value. We assume that 300
191 // samples are sufficient to have updated the filter.
192 for (int i = 0; i < 300; i++) {
193 sim.MoveTo(3.3);
194 }
195 ASSERT_NEAR(3.3, sim.getEstimate(), 0.1);
196
197 for (int i = 0; i < 300; i++) {
198 sim.MoveTo(3.9);
199 }
200 ASSERT_NEAR(3.9, sim.getEstimate(), 0.1);
201}
202
203TEST_F(QueueTest, TestLotsOfMovement) {
204 double index_diff = 1.00;
205 GaussianNoise pot_noise(kSeed2, index_diff / 3.0);
206 ZeroingEstimatorSimulator sim(3.6, index_diff, pot_noise);
207
208 // The zeroing code is supposed to perform some filtering on the difference
209 // between the potentiometer value and the encoder value. We assume that 300
210 // samples are sufficient to have updated the filter.
211 for (int i = 0; i < 300; i++) {
212 sim.MoveTo(3.6);
213 }
214 ASSERT_NEAR(3.6, sim.getEstimate(), 0.1);
215
216 // With a single index pulse the zeroing estimator should be able to lock
217 // onto the true value of the position.
218 sim.MoveTo(4.01);
219 ASSERT_NEAR(4.01, sim.getEstimate(), 0.001);
220
221 sim.MoveTo(4.99);
222 ASSERT_NEAR(4.99, sim.getEstimate(), 0.001);
223
224 sim.MoveTo(3.99);
225 ASSERT_NEAR(3.99, sim.getEstimate(), 0.001);
226
227 sim.MoveTo(3.01);
228 ASSERT_NEAR(3.01, sim.getEstimate(), 0.001);
229
230 sim.MoveTo(13.55);
231 ASSERT_NEAR(13.55, sim.getEstimate(), 0.001);
232}
233
234TEST_F(QueueTest, TestDifferentIndexDiffs) {
235 double index_diff = 0.89;
236 GaussianNoise pot_noise(kSeed2, index_diff / 3.0);
237 ZeroingEstimatorSimulator sim(3.5 * index_diff, index_diff, pot_noise);
238
239 // The zeroing code is supposed to perform some filtering on the difference
240 // between the potentiometer value and the encoder value. We assume that 300
241 // samples are sufficient to have updated the filter.
242 for (int i = 0; i < 300; i++) {
243 sim.MoveTo(3.5 * index_diff);
244 }
245 ASSERT_NEAR(3.5 * index_diff, sim.getEstimate(), 0.1);
246
247 // With a single index pulse the zeroing estimator should be able to lock
248 // onto the true value of the position.
249 sim.MoveTo(4.01);
250 ASSERT_NEAR(4.01, sim.getEstimate(), 0.001);
251
252 sim.MoveTo(4.99);
253 ASSERT_NEAR(4.99, sim.getEstimate(), 0.001);
254
255 sim.MoveTo(3.99);
256 ASSERT_NEAR(3.99, sim.getEstimate(), 0.001);
257
258 sim.MoveTo(3.01);
259 ASSERT_NEAR(3.01, sim.getEstimate(), 0.001);
260
261 sim.MoveTo(13.55);
262 ASSERT_NEAR(13.55, sim.getEstimate(), 0.001);
263}
264
265} // namespace zeroing
266} // namespace frc971