blob: 36474ae8853ec6b2d0352fbdcf7063c1276b8043 [file] [log] [blame]
Austin Schuhdc1c84a2013-02-23 16:33:10 -08001#include <unistd.h>
2
3#include <memory>
4
5#include "gtest/gtest.h"
6#include "aos/common/queue.h"
7#include "aos/common/queue_testutils.h"
8#include "frc971/control_loops/wrist_motor.q.h"
9#include "frc971/control_loops/wrist.h"
Austin Schuhfa033692013-02-24 01:00:55 -080010#include "frc971/constants.h"
Austin Schuhdc1c84a2013-02-23 16:33:10 -080011
12
13using ::aos::time::Time;
14
15namespace frc971 {
16namespace control_loops {
17namespace testing {
18
Austin Schuhfa033692013-02-24 01:00:55 -080019
20// Class which simulates the wrist and sends out queue messages containing the
21// position.
22class WristMotorSimulation {
23 public:
24 // Constructs a motor simulation. initial_position is the inital angle of the
25 // wrist, which will be treated as 0 by the encoder.
26 WristMotorSimulation(double initial_position)
27 : wrist_plant_(new StateFeedbackPlant<2, 1, 1>(MakeWristPlant())),
28 my_wrist_loop_(".frc971.control_loops.wrist",
29 0x1a7b7094, ".frc971.control_loops.wrist.goal",
30 ".frc971.control_loops.wrist.position",
31 ".frc971.control_loops.wrist.output",
32 ".frc971.control_loops.wrist.status") {
33 Reinitialize(initial_position);
34 }
35
36 // Resets the plant so that it starts at initial_position.
37 void Reinitialize(double initial_position) {
38 initial_position_ = initial_position;
39 wrist_plant_->X(0, 0) = initial_position_;
40 wrist_plant_->X(1, 0) = 0.0;
41 wrist_plant_->Y = wrist_plant_->C * wrist_plant_->X;
42 last_position_ = wrist_plant_->Y(0, 0);
43 calibration_value_ = 0.0;
44 }
45
46 // Returns the absolute angle of the wrist.
47 double GetAbsolutePosition() const {
48 return wrist_plant_->Y(0, 0);
49 }
50
51 // Returns the adjusted angle of the wrist.
52 double GetPosition() const {
53 return GetAbsolutePosition() - initial_position_;
54 }
55
56 // Sends out the position queue messages.
57 void SendPositionMessage() {
58 const double angle = GetPosition();
59
60 double horizontal_hall_effect_start_angle;
61 ASSERT_TRUE(constants::horizontal_hall_effect_start_angle(
62 &horizontal_hall_effect_start_angle));
63 double horizontal_hall_effect_stop_angle;
64 ASSERT_TRUE(constants::horizontal_hall_effect_stop_angle(
65 &horizontal_hall_effect_stop_angle));
66
67 ::aos::ScopedMessagePtr<control_loops::WristLoop::Position> position =
68 my_wrist_loop_.position.MakeMessage();
69 position->pos = angle;
70
71 // Signal that the hall effect sensor has been triggered if it is within
72 // the correct range.
73 double abs_position = GetAbsolutePosition();
74 if (abs_position >= horizontal_hall_effect_start_angle &&
75 abs_position <= horizontal_hall_effect_stop_angle) {
76 position->hall_effect = true;
77 } else {
78 position->hall_effect = false;
79 }
80
81 // Only set calibration if it changed last cycle. Calibration starts out
82 // with a value of 0.
83 if ((last_position_ < horizontal_hall_effect_start_angle ||
84 last_position_ > horizontal_hall_effect_stop_angle) &&
85 position->hall_effect) {
86 calibration_value_ =
87 horizontal_hall_effect_start_angle - initial_position_;
88 }
89
90 position->calibration = calibration_value_;
91 position.Send();
92 }
93
94 // Simulates the wrist moving for one timestep.
95 void Simulate() {
96 last_position_ = wrist_plant_->Y(0, 0);
97 EXPECT_TRUE(my_wrist_loop_.output.FetchLatest());
98 wrist_plant_->U << my_wrist_loop_.output->voltage;
99 wrist_plant_->Update();
100
101 // Assert that we are in the right physical range.
102 double horizontal_upper_physical_limit;
103 ASSERT_TRUE(constants::horizontal_upper_physical_limit(
104 &horizontal_upper_physical_limit));
105 double horizontal_lower_physical_limit;
106 ASSERT_TRUE(constants::horizontal_lower_physical_limit(
107 &horizontal_lower_physical_limit));
108
109 EXPECT_GE(horizontal_upper_physical_limit, wrist_plant_->Y(0, 0));
110 EXPECT_LE(horizontal_lower_physical_limit, wrist_plant_->Y(0, 0));
111 }
112
113 ::std::unique_ptr<StateFeedbackPlant<2, 1, 1>> wrist_plant_;
114 private:
115 WristLoop my_wrist_loop_;
116 double initial_position_;
117 double last_position_;
118 double calibration_value_;
119};
120
Austin Schuhdc1c84a2013-02-23 16:33:10 -0800121class WristTest : public ::testing::Test {
122 protected:
123 ::aos::common::testing::GlobalCoreInstance my_core;
124
125 // Create a new instance of the test queue so that it invalidates the queue
126 // that it points to. Otherwise, we will have a pointer to shared memory that
127 // is no longer valid.
128 WristLoop my_wrist_loop_;
129
Austin Schuhfa033692013-02-24 01:00:55 -0800130 // Create a loop and simulation plant.
Austin Schuhdc1c84a2013-02-23 16:33:10 -0800131 WristMotor wrist_motor_;
Austin Schuhfa033692013-02-24 01:00:55 -0800132 WristMotorSimulation wrist_motor_plant_;
Austin Schuhdc1c84a2013-02-23 16:33:10 -0800133
134 WristTest() : my_wrist_loop_(".frc971.control_loops.wrist",
135 0x1a7b7094, ".frc971.control_loops.wrist.goal",
136 ".frc971.control_loops.wrist.position",
137 ".frc971.control_loops.wrist.output",
138 ".frc971.control_loops.wrist.status"),
139 wrist_motor_(&my_wrist_loop_),
Austin Schuhfa033692013-02-24 01:00:55 -0800140 wrist_motor_plant_(0.5) {
141 // Flush the robot state queue so we can use clean shared memory for this
Austin Schuhdc1c84a2013-02-23 16:33:10 -0800142 // test.
143 ::aos::robot_state.Clear();
Austin Schuhfa033692013-02-24 01:00:55 -0800144 SendDSPacket(true);
145 }
146
147 void SendDSPacket(bool enabled) {
148 ::aos::robot_state.MakeWithBuilder().enabled(enabled)
Austin Schuhdc1c84a2013-02-23 16:33:10 -0800149 .autonomous(false)
150 .team_id(971).Send();
Austin Schuhfa033692013-02-24 01:00:55 -0800151 ::aos::robot_state.FetchLatest();
152 }
153
154 void VerifyNearGoal() {
155 my_wrist_loop_.goal.FetchLatest();
156 my_wrist_loop_.position.FetchLatest();
157 EXPECT_NEAR(my_wrist_loop_.goal->goal,
158 wrist_motor_plant_.GetAbsolutePosition(),
159 1e-4);
160 }
161
162 virtual ~WristTest() {
163 ::aos::robot_state.Clear();
Austin Schuhdc1c84a2013-02-23 16:33:10 -0800164 }
165};
166
Austin Schuhfa033692013-02-24 01:00:55 -0800167// Tests that the wrist zeros correctly and goes to a position.
168TEST_F(WristTest, ZerosCorrectly) {
169 my_wrist_loop_.goal.MakeWithBuilder().goal(0.1).Send();
170 for (int i = 0; i < 400; ++i) {
171 wrist_motor_plant_.SendPositionMessage();
172 wrist_motor_.Iterate();
173 wrist_motor_plant_.Simulate();
174 SendDSPacket(true);
175 }
176 VerifyNearGoal();
177}
Austin Schuhdc1c84a2013-02-23 16:33:10 -0800178
Austin Schuhfa033692013-02-24 01:00:55 -0800179// Tests that the wrist zeros correctly starting on the hall effect sensor.
180TEST_F(WristTest, ZerosStartingOn) {
181 wrist_motor_plant_.Reinitialize(90 * M_PI / 180.0);
182
183 my_wrist_loop_.goal.MakeWithBuilder().goal(0.1).Send();
184 for (int i = 0; i < 500; ++i) {
185 wrist_motor_plant_.SendPositionMessage();
186 wrist_motor_.Iterate();
187 wrist_motor_plant_.Simulate();
188 SendDSPacket(true);
189 }
190 VerifyNearGoal();
191}
192
193// Tests that missing positions are correctly handled.
194TEST_F(WristTest, HandleMissingPosition) {
195 my_wrist_loop_.goal.MakeWithBuilder().goal(0.1).Send();
196 for (int i = 0; i < 400; ++i) {
197 if (i % 23) {
198 wrist_motor_plant_.SendPositionMessage();
199 }
200 wrist_motor_.Iterate();
201 wrist_motor_plant_.Simulate();
202 SendDSPacket(true);
203 }
204 VerifyNearGoal();
205}
206
207// Tests that loosing the encoder for a second triggers a re-zero.
208TEST_F(WristTest, RezeroWithMissingPos) {
209 my_wrist_loop_.goal.MakeWithBuilder().goal(0.1).Send();
210 for (int i = 0; i < 800; ++i) {
211 // After 3 seconds, simulate the encoder going missing.
212 // This should trigger a re-zero. To make sure it works, change the goal as
213 // well.
214 if (i < 300 || i > 400) {
215 wrist_motor_plant_.SendPositionMessage();
216 } else {
217 if (i > 310) {
218 // Should be re-zeroing now.
219 EXPECT_EQ(WristMotor::UNINITIALIZED, wrist_motor_.state_);
220 }
221 my_wrist_loop_.goal.MakeWithBuilder().goal(0.2).Send();
222 }
223 if (i == 430) {
224 EXPECT_EQ(WristMotor::ZEROING, wrist_motor_.state_);
225 }
Austin Schuhdc1c84a2013-02-23 16:33:10 -0800226
227 wrist_motor_.Iterate();
Austin Schuhfa033692013-02-24 01:00:55 -0800228 wrist_motor_plant_.Simulate();
229 SendDSPacket(true);
Austin Schuhdc1c84a2013-02-23 16:33:10 -0800230 }
Austin Schuhfa033692013-02-24 01:00:55 -0800231 VerifyNearGoal();
232}
233
234// Tests that disabling while zeroing sends the state machine into the
235// uninitialized state.
236TEST_F(WristTest, DisableGoesUninitialized) {
237 my_wrist_loop_.goal.MakeWithBuilder().goal(0.1).Send();
238 for (int i = 0; i < 800; ++i) {
239 wrist_motor_plant_.SendPositionMessage();
240 // After 0.5 seconds, disable the robot.
241 if (i > 50 && i < 200) {
242 SendDSPacket(false);
243 if (i > 100) {
244 // Give the loop a couple cycled to get the message and then verify that
245 // it is in the correct state.
246 EXPECT_EQ(WristMotor::UNINITIALIZED, wrist_motor_.state_);
Austin Schuh06ee48e2013-03-02 01:47:54 -0800247 // When disabled, we should be applying 0 power.
248 EXPECT_TRUE(my_wrist_loop_.output.FetchLatest());
249 EXPECT_EQ(0, my_wrist_loop_.output->voltage);
Austin Schuhfa033692013-02-24 01:00:55 -0800250 }
251 } else {
252 SendDSPacket(true);
253 }
254 if (i == 202) {
255 // Verify that we are zeroing after the bot gets enabled again.
256 EXPECT_EQ(WristMotor::ZEROING, wrist_motor_.state_);
257 }
258
259 wrist_motor_.Iterate();
260 wrist_motor_plant_.Simulate();
261 }
262 VerifyNearGoal();
Austin Schuhdc1c84a2013-02-23 16:33:10 -0800263}
264
Austin Schuh06ee48e2013-03-02 01:47:54 -0800265// Tests that the wrist can't get too far away from the zeroing position if the
266// zeroing position is saturating the goal.
267TEST_F(WristTest, NoWindupNegative) {
268 double saved_zeroing_position = 0;
269 my_wrist_loop_.goal.MakeWithBuilder().goal(0.1).Send();
270 for (int i = 0; i < 500; ++i) {
271 wrist_motor_plant_.SendPositionMessage();
272 if (i == 50) {
273 EXPECT_EQ(WristMotor::ZEROING, wrist_motor_.state_);
274 // Move the zeroing position far away and verify that it gets moved back.
275 saved_zeroing_position = wrist_motor_.zeroing_position_;
276 wrist_motor_.zeroing_position_ = -100.0;
277 } else if (i == 51) {
278 EXPECT_EQ(WristMotor::ZEROING, wrist_motor_.state_);
279 EXPECT_NEAR(saved_zeroing_position, wrist_motor_.zeroing_position_, 0.4);
280 }
281 if (wrist_motor_.state_ != WristMotor::READY) {
282 if (i == 51) {
283 // The cycle after we kick the zero position is the only cycle during
284 // which we should expect to see a high uncapped power during zeroing.
285 EXPECT_LT(5, ::std::abs(wrist_motor_.loop_->U_uncapped(0, 0)));
286 } else {
287 EXPECT_GT(5, ::std::abs(wrist_motor_.loop_->U_uncapped(0, 0)));
288 }
289 }
290
291
292 wrist_motor_.Iterate();
293 wrist_motor_plant_.Simulate();
294 SendDSPacket(true);
295 }
296 VerifyNearGoal();
297}
298
299// Tests that the wrist can't get too far away from the zeroing position if the
300// zeroing position is saturating the goal.
301TEST_F(WristTest, NoWindupPositive) {
302 double saved_zeroing_position = 0;
303 my_wrist_loop_.goal.MakeWithBuilder().goal(0.1).Send();
304 for (int i = 0; i < 500; ++i) {
305 wrist_motor_plant_.SendPositionMessage();
306 if (i == 50) {
307 EXPECT_EQ(WristMotor::ZEROING, wrist_motor_.state_);
308 // Move the zeroing position far away and verify that it gets moved back.
309 saved_zeroing_position = wrist_motor_.zeroing_position_;
310 wrist_motor_.zeroing_position_ = 100.0;
311 } else {
312 if (i == 51) {
313 EXPECT_EQ(WristMotor::ZEROING, wrist_motor_.state_);
314 EXPECT_NEAR(saved_zeroing_position, wrist_motor_.zeroing_position_, 0.4);
315 }
316 }
317 if (wrist_motor_.state_ != WristMotor::READY) {
318 if (i == 51) {
319 // The cycle after we kick the zero position is the only cycle during
320 // which we should expect to see a high uncapped power during zeroing.
321 EXPECT_LT(5, ::std::abs(wrist_motor_.loop_->U_uncapped(0, 0)));
322 } else {
323 EXPECT_GT(5, ::std::abs(wrist_motor_.loop_->U_uncapped(0, 0)));
324 }
325 }
326
327 wrist_motor_.Iterate();
328 wrist_motor_plant_.Simulate();
329 SendDSPacket(true);
330 }
331 VerifyNearGoal();
332}
333
Austin Schuhdc1c84a2013-02-23 16:33:10 -0800334} // namespace testing
335} // namespace control_loops
336} // namespace frc971