blob: 9f3afb81ed6b8d1092f03dccbf69f82e2515819c [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_);
247 }
248 } else {
249 SendDSPacket(true);
250 }
251 if (i == 202) {
252 // Verify that we are zeroing after the bot gets enabled again.
253 EXPECT_EQ(WristMotor::ZEROING, wrist_motor_.state_);
254 }
255
256 wrist_motor_.Iterate();
257 wrist_motor_plant_.Simulate();
258 }
259 VerifyNearGoal();
Austin Schuhdc1c84a2013-02-23 16:33:10 -0800260}
261
262} // namespace testing
263} // namespace control_loops
264} // namespace frc971