blob: 6f897ea03b47a713ee6bcdf9659b50eb255d046a [file] [log] [blame]
Brian Silverman26e4e522015-12-17 01:56:40 -05001/*----------------------------------------------------------------------------*/
Brian Silverman1a675112016-02-20 20:42:49 -05002/* Copyright (c) FIRST 2014-2016. All Rights Reserved. */
Brian Silverman26e4e522015-12-17 01:56:40 -05003/* Open Source Software - may be modified and shared by FRC teams. The code */
4/* must be accompanied by the FIRST BSD license file in the root directory of */
5/* the project. */
6/*----------------------------------------------------------------------------*/
7
8#include <AnalogOutput.h>
9#include <DigitalOutput.h>
10#include <CANJaguar.h>
11#include <Relay.h>
12#include <Timer.h>
13#include <WPIErrors.h>
14#include "gtest/gtest.h"
15#include "TestBench.h"
16
17static constexpr double kSpikeTime = 0.5;
18
19static constexpr double kExpectedBusVoltage = 14.0;
20static constexpr double kExpectedTemperature = 25.0;
21
22static constexpr double kMotorTime = 0.5;
23
24static constexpr double kEncoderSettlingTime = 1.0;
25static constexpr double kEncoderPositionTolerance = 0.1;
26static constexpr double kEncoderSpeedTolerance = 30.0;
27
28static constexpr double kPotentiometerSettlingTime = 1.0;
29static constexpr double kPotentiometerPositionTolerance = 0.1;
30
31static constexpr double kCurrentTolerance = 0.1;
32
33static constexpr double kVoltageTolerance = 0.1;
34
35static constexpr double kMotorVoltage = 5.0;
36
37static constexpr double kMotorPercent = 0.5;
38
39static constexpr double kMotorSpeed = 100;
40class CANJaguarTest : public testing::Test {
41 protected:
42 CANJaguar *m_jaguar;
43 DigitalOutput *m_fakeForwardLimit, *m_fakeReverseLimit;
44 AnalogOutput *m_fakePotentiometer;
45 Relay *m_spike;
46
47 virtual void SetUp() override {
48 m_spike = new Relay(TestBench::kCANJaguarRelayChannel, Relay::kForwardOnly);
49 m_spike->Set(Relay::kOn);
50 Wait(kSpikeTime);
51
52 m_jaguar = new CANJaguar(TestBench::kCANJaguarID);
53
54 m_fakeForwardLimit = new DigitalOutput(TestBench::kFakeJaguarForwardLimit);
55 m_fakeForwardLimit->Set(0);
56
57 m_fakeReverseLimit = new DigitalOutput(TestBench::kFakeJaguarReverseLimit);
58 m_fakeReverseLimit->Set(0);
59
60 m_fakePotentiometer = new AnalogOutput(TestBench::kFakeJaguarPotentiometer);
61 m_fakePotentiometer->SetVoltage(0.0f);
62
63 /* The motor might still have momentum from the previous test. */
64 Wait(kEncoderSettlingTime);
65 }
66
67 virtual void TearDown() override {
68 delete m_jaguar;
69 delete m_fakeForwardLimit;
70 delete m_fakeReverseLimit;
71 delete m_fakePotentiometer;
72 delete m_spike;
73 }
74
75 /**
76 * Calls CANJaguar::Set periodically 50 times to make sure everything is
77 * verified. This mimics a real robot program, where Set is presumably
78 * called in each iteration of the main loop.
79 */
80 void SetJaguar(float totalTime, float value = 0.0f) {
81 for (int i = 0; i < 50; i++) {
82 m_jaguar->Set(value);
83 Wait(totalTime / 50.0);
84 }
85 }
86 /**
87 * returns the sign of the given number
88 */
89 int SignNum(double value) { return -(value < 0) + (value > 0); }
90 void InversionTest(float motorValue, float delayTime = kMotorTime) {
91 m_jaguar->EnableControl();
92 m_jaguar->SetInverted(false);
93 SetJaguar(delayTime, motorValue);
94 double initialSpeed = m_jaguar->GetSpeed();
95 m_jaguar->Set(0.0);
96 m_jaguar->SetInverted(true);
97 SetJaguar(delayTime, motorValue);
98 double finalSpeed = m_jaguar->GetSpeed();
99 // checks that the motor has changed direction
100 EXPECT_FALSE(SignNum(initialSpeed) == SignNum(finalSpeed))
101 << "CAN Jaguar did not invert direction positive. Initial speed was: "
102 << initialSpeed << " Final displacement was: " << finalSpeed
103 << " Sign of initial displacement was: " << SignNum(initialSpeed)
104 << " Sign of final displacement was: " << SignNum(finalSpeed);
105 SetJaguar(delayTime, -motorValue);
106 initialSpeed = m_jaguar->GetSpeed();
107 m_jaguar->Set(0.0);
108 m_jaguar->SetInverted(false);
109 SetJaguar(delayTime, -motorValue);
110 finalSpeed = m_jaguar->GetSpeed();
111 EXPECT_FALSE(SignNum(initialSpeed) == SignNum(finalSpeed))
112 << "CAN Jaguar did not invert direction negative. Initial displacement "
113 "was: " << initialSpeed << " Final displacement was: " << finalSpeed
114 << " Sign of initial displacement was: " << SignNum(initialSpeed)
115 << " Sign of final displacement was: " << SignNum(finalSpeed);
116 }
117};
118
119/**
120 * Tests that allocating the same CANJaguar port as an already allocated port
121 * causes a ResourceAlreadyAllocated error.
122 */
123TEST_F(CANJaguarTest, AlreadyAllocatedError) {
124 std::cout << "The following errors are expected." << std::endl
125 << std::endl;
126
127 CANJaguar jaguar(TestBench::kCANJaguarID);
128 EXPECT_EQ(wpi_error_value_ResourceAlreadyAllocated,
129 jaguar.GetError().GetCode())
130 << "An error should have been returned";
131}
132
133/**
134 * Test that allocating a CANJaguar with device number 64 causes an
135 * out-of-range error.
136 */
137TEST_F(CANJaguarTest, 64OutOfRangeError) {
138 std::cout << "The following errors are expected." << std::endl
139 << std::endl;
140
141 CANJaguar jaguar(64);
142 EXPECT_EQ(wpi_error_value_ChannelIndexOutOfRange, jaguar.GetError().GetCode())
143 << "An error should have been returned";
144}
145
146/**
147 * Test that allocating a CANJaguar with device number 0 causes an out-of-range
148 * error.
149 */
150TEST_F(CANJaguarTest, 0OutOfRangeError) {
151 std::cout << "The following errors are expected." << std::endl
152 << std::endl;
153
154 CANJaguar jaguar(0);
155 EXPECT_EQ(wpi_error_value_ChannelIndexOutOfRange, jaguar.GetError().GetCode())
156 << "An error should have been returned";
157}
158
159/**
160 * Checks the default status data for reasonable values to confirm that we're
161 * really getting status data from the Jaguar.
162 */
163TEST_F(CANJaguarTest, InitialStatus) {
164 m_jaguar->SetPercentMode();
165
166 EXPECT_NEAR(m_jaguar->GetBusVoltage(), kExpectedBusVoltage, 3.0)
167 << "Bus voltage is not a plausible value.";
168
169 EXPECT_FLOAT_EQ(m_jaguar->GetOutputVoltage(), 0.0)
170 << "Output voltage is non-zero.";
171
172 EXPECT_FLOAT_EQ(m_jaguar->GetOutputCurrent(), 0.0)
173 << "Output current is non-zero.";
174
175 EXPECT_NEAR(m_jaguar->GetTemperature(), kExpectedTemperature, 5.0)
176 << "Temperature is not a plausible value.";
177
178 EXPECT_EQ(m_jaguar->GetFaults(), 0) << "Jaguar has one or more fault set.";
179}
180
181/**
182 * Ensure that the jaguar doesn't move when it's disabled
183 */
184TEST_F(CANJaguarTest, Disable) {
185 m_jaguar->SetPercentMode(CANJaguar::QuadEncoder, 360);
186 m_jaguar->EnableControl();
187 m_jaguar->DisableControl();
188
189 Wait(kEncoderSettlingTime);
190
191 double initialPosition = m_jaguar->GetPosition();
192
193 SetJaguar(kMotorTime, 1.0f);
194 m_jaguar->Set(0.0f);
195
196 double finalPosition = m_jaguar->GetPosition();
197
198 EXPECT_NEAR(initialPosition, finalPosition, kEncoderPositionTolerance)
199 << "Jaguar moved while disabled";
200}
201
202/**
203 * Make sure the Jaguar keeps its state after a power cycle by setting a
204 * control mode, turning the spike on and off, then checking if the Jaguar
205 * behaves like it should in that control mode.
206 */
207TEST_F(CANJaguarTest, BrownOut) {
208 /* Set the jaguar to quad encoder position mode */
209 m_jaguar->SetPositionMode(CANJaguar::QuadEncoder, 360, 20.0f, 0.01f, 0.0f);
210 m_jaguar->EnableControl();
211 SetJaguar(kMotorTime, 0.0);
212 double setpoint = m_jaguar->GetPosition() + 1.0f;
213
214 /* Turn the spike off and on again */
215 m_spike->Set(Relay::kOff);
216 Wait(kSpikeTime);
217 m_spike->Set(Relay::kOn);
218 Wait(kSpikeTime);
219
220 /* The jaguar should automatically get set to quad encoder position mode,
221 so it should be able to reach a setpoint in a couple seconds. */
222 for (int i = 0; i < 10; i++) {
223 SetJaguar(1.0f, setpoint);
224
225 if (std::abs(m_jaguar->GetPosition() - setpoint) <=
226 kEncoderPositionTolerance) {
227 return;
228 }
229 }
230
231 EXPECT_NEAR(setpoint, m_jaguar->GetPosition(), kEncoderPositionTolerance)
232 << "CAN Jaguar should have resumed PID control after power cycle";
233}
234
235/**
236 * Test if we can set arbitrary setpoints and PID values each each applicable
237 * mode and get the same values back.
238 */
239TEST_F(CANJaguarTest, SetGet) {
240 m_jaguar->DisableControl();
241
242 m_jaguar->SetSpeedMode(CANJaguar::QuadEncoder, 360, 1, 2, 3);
243 m_jaguar->Set(4);
244
245 EXPECT_FLOAT_EQ(1, m_jaguar->GetP());
246 EXPECT_FLOAT_EQ(2, m_jaguar->GetI());
247 EXPECT_FLOAT_EQ(3, m_jaguar->GetD());
248 EXPECT_FLOAT_EQ(4, m_jaguar->Get());
249}
250
251/**
252 * Test if we can drive the motor in percentage mode and get a position back
253 */
254TEST_F(CANJaguarTest, PercentModeForwardWorks) {
255 m_jaguar->SetPercentMode(CANJaguar::QuadEncoder, 360);
256 m_jaguar->EnableControl();
257
258 /* The motor might still have momentum from the previous test. */
259 SetJaguar(kEncoderSettlingTime, 0.0f);
260
261 double initialPosition = m_jaguar->GetPosition();
262
263 /* Drive the speed controller briefly to move the encoder */
264 SetJaguar(kMotorTime, 1.0f);
265 m_jaguar->Set(0.0f);
266
267 /* The position should have increased */
268 EXPECT_GT(m_jaguar->GetPosition(), initialPosition)
269 << "CAN Jaguar position should have increased after the motor moved";
270}
271
272/**
273 * Test if we can drive the motor backwards in percentage mode and get a
274 * position back
275 */
276TEST_F(CANJaguarTest, PercentModeReverseWorks) {
277 m_jaguar->SetPercentMode(CANJaguar::QuadEncoder, 360);
278 m_jaguar->EnableControl();
279
280 /* The motor might still have momentum from the previous test. */
281 SetJaguar(kEncoderSettlingTime, 0.0f);
282
283 double initialPosition = m_jaguar->GetPosition();
284
285 /* Drive the speed controller briefly to move the encoder */
286 SetJaguar(kMotorTime, -1.0f);
287 m_jaguar->Set(0.0f);
288
289 float p = m_jaguar->GetPosition();
290 /* The position should have decreased */
291 EXPECT_LT(p, initialPosition)
292 << "CAN Jaguar position should have decreased after the motor moved";
293}
294
295/**
296 * Test if we can set an absolute voltage and receive a matching output voltage
297 * status.
298 */
299TEST_F(CANJaguarTest, VoltageModeWorks) {
300 m_jaguar->SetVoltageMode();
301 m_jaguar->EnableControl();
302
303 float setpoints[] = {M_PI, 8.0f, -10.0f};
304
305 for (auto setpoint : setpoints) {
306 SetJaguar(kMotorTime, setpoint);
307 EXPECT_NEAR(setpoint, m_jaguar->GetOutputVoltage(), kVoltageTolerance);
308 }
309}
310
311/**
312 * Test if we can set a speed in speed control mode and receive a matching
313 * speed status.
314 */
315TEST_F(CANJaguarTest, SpeedModeWorks) {
316 m_jaguar->SetSpeedMode(CANJaguar::QuadEncoder, 360, 0.1f, 0.003f, 0.01f);
317 m_jaguar->EnableControl();
318
319 constexpr float speed = 50.0f;
320
321 SetJaguar(kMotorTime, speed);
322 EXPECT_NEAR(speed, m_jaguar->GetSpeed(), kEncoderSpeedTolerance);
323}
324
325/**
326 * Test if we can set a position and reach that position with PID control on
327 * the Jaguar.
328 */
329TEST_F(CANJaguarTest, PositionModeWorks) {
330 m_jaguar->SetPositionMode(CANJaguar::QuadEncoder, 360, 15.0f, 0.02f, 0.0f);
331
332 double setpoint = m_jaguar->GetPosition() + 1.0f;
333
334 m_jaguar->EnableControl();
335
336 /* It should get to the setpoint within 10 seconds */
337 for (int i = 0; i < 10; i++) {
338 SetJaguar(1.0f, setpoint);
339
340 if (std::abs(m_jaguar->GetPosition() - setpoint) <=
341 kEncoderPositionTolerance) {
342 return;
343 }
344 }
345
346 EXPECT_NEAR(setpoint, m_jaguar->GetPosition(), kEncoderPositionTolerance)
347 << "CAN Jaguar should have reached setpoint with PID control";
348}
349
350/**
351 * Test if we can set a current setpoint with PID control on the Jaguar and get
352 * a corresponding output current
353 */
354TEST_F(CANJaguarTest, DISABLED_CurrentModeWorks) {
355 m_jaguar->SetCurrentMode(10.0, 4.0, 1.0);
356 m_jaguar->EnableControl();
357
358 float setpoints[] = {1.6f, 2.0f, -1.6f};
359
360 for (auto& setpoints_i : setpoints) {
361 float setpoint = setpoints_i;
362 float expectedCurrent = std::abs(setpoints_i);
363
364 /* It should get to each setpoint within 10 seconds */
365 for (int j = 0; j < 10; j++) {
366 SetJaguar(1.0, setpoint);
367
368 if (std::abs(m_jaguar->GetOutputCurrent() - expectedCurrent) <=
369 kCurrentTolerance) {
370 break;
371 }
372 }
373
374 EXPECT_NEAR(expectedCurrent, m_jaguar->GetOutputCurrent(),
375 kCurrentTolerance);
376 }
377}
378
379/**
380 * Test if we can get a position in potentiometer mode, using an analog output
381 * as a fake potentiometer.
382 */
383TEST_F(CANJaguarTest, FakePotentiometerPosition) {
384 m_jaguar->SetPercentMode(CANJaguar::Potentiometer);
385 m_jaguar->EnableControl();
386
387 // Set the analog output to 4 different voltages and check if the Jaguar
388 // returns corresponding positions.
389 for (int i = 0; i <= 3; i++) {
390 m_fakePotentiometer->SetVoltage(static_cast<float>(i));
391
392 SetJaguar(kPotentiometerSettlingTime);
393
394 EXPECT_NEAR(m_fakePotentiometer->GetVoltage() / 3.0f,
395 m_jaguar->GetPosition(), kPotentiometerPositionTolerance)
396 << "CAN Jaguar should have returned the potentiometer position set by "
397 "the analog output";
398 }
399}
400
401/**
402 * Test if we can limit the Jaguar to only moving in reverse with a fake
403 * limit switch.
404 */
405TEST_F(CANJaguarTest, FakeLimitSwitchForwards) {
406 m_jaguar->SetPercentMode(CANJaguar::QuadEncoder, 360);
407 m_jaguar->ConfigLimitMode(CANJaguar::kLimitMode_SwitchInputsOnly);
408 m_fakeForwardLimit->Set(1);
409 m_fakeReverseLimit->Set(0);
410 m_jaguar->EnableControl();
411
412 SetJaguar(kEncoderSettlingTime);
413
414 /* Make sure the limits are recognized by the Jaguar. */
415 ASSERT_FALSE(m_jaguar->GetForwardLimitOK());
416 ASSERT_TRUE(m_jaguar->GetReverseLimitOK());
417
418 double initialPosition = m_jaguar->GetPosition();
419
420 /* Drive the speed controller briefly to move the encoder. If the limit
421 switch is recognized, it shouldn't actually move. */
422 SetJaguar(kMotorTime, 1.0f);
423
424 /* The position should be the same, since the limit switch was on. */
425 EXPECT_NEAR(initialPosition, m_jaguar->GetPosition(),
426 kEncoderPositionTolerance)
427 << "CAN Jaguar should not have moved with the limit switch pressed";
428
429 /* Drive the speed controller in the other direction. It should actually
430 move, since only the forward switch is activated.*/
431 SetJaguar(kMotorTime, -1.0f);
432
433 /* The position should have decreased */
434 EXPECT_LT(m_jaguar->GetPosition(), initialPosition)
435 << "CAN Jaguar should have moved in reverse while the forward limit was "
436 "on";
437}
438
439/**
440 * Test if we can limit the Jaguar to only moving forwards with a fake limit
441 * switch.
442 */
443TEST_F(CANJaguarTest, FakeLimitSwitchReverse) {
444 m_jaguar->SetPercentMode(CANJaguar::QuadEncoder, 360);
445 m_jaguar->ConfigLimitMode(CANJaguar::kLimitMode_SwitchInputsOnly);
446 m_fakeForwardLimit->Set(0);
447 m_fakeReverseLimit->Set(1);
448 m_jaguar->EnableControl();
449
450 SetJaguar(kEncoderSettlingTime);
451
452 /* Make sure the limits are recognized by the Jaguar. */
453 ASSERT_TRUE(m_jaguar->GetForwardLimitOK());
454 ASSERT_FALSE(m_jaguar->GetReverseLimitOK());
455
456 double initialPosition = m_jaguar->GetPosition();
457
458 /* Drive the speed controller backwards briefly to move the encoder. If
459 the limit switch is recognized, it shouldn't actually move. */
460 SetJaguar(kMotorTime, -1.0f);
461
462 /* The position should be the same, since the limit switch was on. */
463 EXPECT_NEAR(initialPosition, m_jaguar->GetPosition(),
464 kEncoderPositionTolerance)
465 << "CAN Jaguar should not have moved with the limit switch pressed";
466
467 /* Drive the speed controller in the other direction. It should actually
468 move, since only the reverse switch is activated.*/
469 SetJaguar(kMotorTime, 1.0f);
470
471 /* The position should have increased */
472 EXPECT_GT(m_jaguar->GetPosition(), initialPosition)
473 << "CAN Jaguar should have moved forwards while the reverse limit was on";
474}
475/**
476* Tests that inversion works in voltage mode
477*/
478TEST_F(CANJaguarTest, InvertingVoltageMode) {
479 m_jaguar->SetVoltageMode(CANJaguar::QuadEncoder, 360);
480 m_jaguar->EnableControl();
481 InversionTest(kMotorVoltage);
482}
483
484/**
485* Tests that inversion works in percentMode
486*/
487TEST_F(CANJaguarTest, InvertingPercentMode) {
488 m_jaguar->SetPercentMode(CANJaguar::QuadEncoder, 360);
489 m_jaguar->EnableControl();
490 InversionTest(kMotorPercent);
491}
492/**
493* Tests that inversion works in SpeedMode
494*/
495TEST_F(CANJaguarTest, InvertingSpeedMode) {
496 m_jaguar->SetSpeedMode(CANJaguar::QuadEncoder, 360, 0.1f, 0.005f, 0.00f);
497 m_jaguar->EnableControl();
498 InversionTest(kMotorSpeed, kMotorTime);
499}