19
19
20
20
import edu .wpi .first .math .MathUtil ;
21
21
import edu .wpi .first .math .system .plant .DCMotor ;
22
+ import edu .wpi .first .wpilibj .Timer ;
22
23
23
24
@ Timeout (value = 30 , unit = TimeUnit .MINUTES )
24
25
@ ResourceLock ("WebotsSimulator" )
@@ -91,6 +92,12 @@ public class MotorControllerTest {
91
92
// Rearranging gives:
92
93
// theta(t) = theta_0 + w_f*t + (w_0 - w_f) * t_c * (1-exp(-t/t_c))
93
94
95
+
96
+ // The robot autonomous code runs the motor at 0.5 for 2 seconds and then stops it (i.e.
97
+ // sets the speed to 0 and lets it stop on it's own).
98
+ private static double tMotorStartsSecs = 0.0 ;
99
+ private static double tMotorStopsSecs = timeOfNextStepSecs (2.0 );
100
+
94
101
private static double computeSpeedRadPerSec (DCMotor gearMotor ,
95
102
double moiKgM2 , double throttle , double initialSpeedRadPerSec ,
96
103
double tSecs ) {
@@ -104,7 +111,7 @@ private static double computeSpeedRadPerSec(DCMotor gearMotor,
104
111
}
105
112
106
113
private static double computeAngleRadians (DCMotor gearMotor , double moiKgM2 ,
107
- double throttle , double initialSpeedRadPerSec ,
114
+ double throttle , double initialSpeedRadPerSec ,
108
115
double initialAngleRad , double tSecs ) {
109
116
double timeConstantSecs = computeTimeConstantSecs (gearMotor , moiKgM2 );
110
117
double targetSpeedRadPerSec = throttle * gearMotor .nominalVoltageVolts
@@ -124,7 +131,7 @@ private static double computeTimeConstantSecs(DCMotor gearMotor,
124
131
}
125
132
126
133
private static double computeCylinderMoiKgM2 (double radiusMeters ,
127
- double heightMeters , double densityKgPerM3 ) {
134
+ double heightMeters , double densityKgPerM3 ) {
128
135
double massKg = densityKgPerM3 * Math .PI * radiusMeters * radiusMeters
129
136
* heightMeters ;
130
137
return massKg * radiusMeters * radiusMeters / 2.0 ;
@@ -136,12 +143,13 @@ private static double timeOfNextStepSecs(double tSecs) {
136
143
137
144
private static double expectedSpeedRadPerSec (DCMotor gearMotor ,
138
145
double moiKgM2 , double tSecs ) {
139
- // The robot autonomous code runs the motor at 0.5 for 2 seconds and then stops it (i.e.
140
- // sets the speed to 0 and lets it stop on it's own).
141
- double tMotorStopsSecs = timeOfNextStepSecs (2.0 );
142
146
147
+ if (tSecs <= tMotorStartsSecs ) {
148
+ return 0.0 ;
149
+ }
143
150
if (tSecs <= tMotorStopsSecs ) {
144
- return computeSpeedRadPerSec (gearMotor , moiKgM2 , 0.5 , 0 , tSecs );
151
+ return computeSpeedRadPerSec (gearMotor , moiKgM2 , 0.5 , 0 ,
152
+ tSecs - tMotorStartsSecs );
145
153
}
146
154
return computeSpeedRadPerSec (gearMotor , moiKgM2 , 0 ,
147
155
expectedSpeedRadPerSec (gearMotor , moiKgM2 , tMotorStopsSecs ),
@@ -150,11 +158,11 @@ private static double expectedSpeedRadPerSec(DCMotor gearMotor,
150
158
151
159
private static double expectedAngleRadians (DCMotor gearMotor ,
152
160
double moiKgM2 , double tSecs ) {
153
- // The robot autonomous code runs the motor at 0.5 for 2 seconds and then stops it (i.e.
154
- // sets the speed to 0 and lets it stop on it's own).
155
- double tMotorStopsSecs = timeOfNextStepSecs ( 2.0 );
156
- if ( tSecs <= tMotorStopsSecs ) {
157
- return computeAngleRadians ( gearMotor , moiKgM2 , 0.5 , 0 , 0 , tSecs );
161
+ if ( tSecs <= tMotorStartsSecs ) {
162
+ return 0.0 ;
163
+ } else if ( tSecs <= tMotorStopsSecs ) {
164
+ return computeAngleRadians ( gearMotor , moiKgM2 , 0.5 , 0 , 0 ,
165
+ tSecs - tMotorStartsSecs );
158
166
}
159
167
return computeAngleRadians (gearMotor , moiKgM2 , 0 ,
160
168
expectedSpeedRadPerSec (gearMotor , moiKgM2 , tMotorStopsSecs ),
@@ -168,7 +176,7 @@ private static double computeGearing(DCMotor motor,
168
176
}
169
177
170
178
private static double computeFlywheelThickness (DCMotor gearMotor ,
171
- double flywheelRadiusMeters , double flywheelDensityKgPerM3 ,
179
+ double flywheelRadiusMeters , double flywheelDensityKgPerM3 ,
172
180
double desiredTimeConstantSecs ) {
173
181
// for a time constant of t_c seconds:
174
182
// t_c = R*J*k_v/k_T
@@ -193,7 +201,8 @@ private static class Model {
193
201
double moiKgM2 ;
194
202
Measurement m1 , m2 ;
195
203
196
- Model (String motorModelName , String shaftDefPath ) throws Exception {
204
+ Model (String motorModelName , String shaftDefPath , double gearing ,
205
+ double flywheelThicknessMeters ) throws Exception {
197
206
this .motorModelName = motorModelName ;
198
207
this .shaftDefPath = shaftDefPath ;
199
208
// To ensure we the flywheel doesn't spin or accelerate too fast...
@@ -207,20 +216,20 @@ private static class Model {
207
216
var motor = (DCMotor ) (DCMotor .class
208
217
.getDeclaredMethod ("get" + motorModelName , int .class )
209
218
.invoke (null , 1 ));
210
- var gearing = computeGearing (motor , desiredFlywheelFreeSpeedRPS );
219
+ assertEquals (computeGearing (motor , desiredFlywheelFreeSpeedRPS ),
220
+ gearing , 0.01 * gearing ,
221
+ "Incorrect gearing for a free speed of %g RPS when using a %s"
222
+ .formatted (desiredFlywheelFreeSpeedRPS ,
223
+ motorModelName ));
211
224
gearMotor = motor .withReduction (gearing );
212
- LOG .log (Level .INFO ,
213
- "For a free speed of {0} RPS when using a {1}, assuming the gearing is {2}.\n " ,
214
- desiredFlywheelFreeSpeedRPS , motorModelName , gearing );
215
- var flywheelThicknessMeters =
225
+ assertEquals (
216
226
computeFlywheelThickness (gearMotor , flywheelRadiusMeters ,
217
- flywheelDensityKgPerM3 , desiredTimeConstantSecs );
218
- LOG .log (Level .INFO ,
219
- "For a time constant of {0} second when using a {1} with a gearing of {2} and a flywheel with radius {3} meters and density {4} kg/m^3, assuming the flywheel thickness is {5} meters.\n " ,
220
- desiredTimeConstantSecs , motorModelName , gearing ,
221
- flywheelRadiusMeters , flywheelDensityKgPerM3 ,
222
- flywheelThicknessMeters );
223
-
227
+ flywheelDensityKgPerM3 , desiredTimeConstantSecs ),
228
+ flywheelThicknessMeters , 0.01 * flywheelThicknessMeters ,
229
+ "Incorrect flywheel thickness for a time constant of %g second when using a %s with a gearing of %g and a flywheel with radius %g meters and density %g kg/m^3"
230
+ .formatted (desiredTimeConstantSecs , motorModelName ,
231
+ gearing , flywheelRadiusMeters ,
232
+ flywheelDensityKgPerM3 ));
224
233
// With those settings, the flywheel MOI will be:
225
234
moiKgM2 = computeCylinderMoiKgM2 (flywheelRadiusMeters ,
226
235
flywheelThicknessMeters , flywheelDensityKgPerM3 );
@@ -233,9 +242,7 @@ public String toString() {
233
242
234
243
private void assertShaftCorrectAtSecs (double actualSpeedRadPerSec ,
235
244
double actualAngleRadians , double tSecs ) {
236
- // TODO: Make sim accurate enough that this can be less than 1*simStepSizeSecs
237
- // (https://github.com/DeepBlueRobotics/DeepBlueSim/issues/101)
238
- double jitterSecs = 2.5 * simStepSizeSecs ;
245
+ double jitterSecs = 0.5 * simStepSizeSecs ;
239
246
double expectedEarlierSpeedRadPerSec = expectedSpeedRadPerSec (
240
247
gearMotor , moiKgM2 , tSecs - jitterSecs );
241
248
double expectedLaterSpeedRadPerSec = expectedSpeedRadPerSec (
@@ -300,15 +307,20 @@ private Consumer<SimulationState> stateChecker(
300
307
};
301
308
}
302
309
310
+ // @RepeatedTest(value = 20, failureThreshold = 1)
303
311
@ RepeatedTest (1 )
304
312
void testCorrectRotationInAutonomous () throws Exception {
305
- models .add (new Model ("NEO" , "CAN_SHAFT" ));
306
- models .add (new Model ("MiniCIM" , "PWM_SHAFT" ));
313
+ models .add (new Model ("NEO" , "CAN_SHAFT" , 94.6 , 0.392 ));
314
+ models .add (new Model ("MiniCIM" , "PWM_SHAFT" , 97.3333 , 0.215 ));
307
315
308
316
try (var manager = new WebotsSimulator (
309
317
"../plugin/controller/src/webotsFolder/dist/worlds/MotorController.wbt" ,
310
318
MotorControllerRobot ::new )) {
311
319
manager .atSec (0.0 , s -> {
320
+ // Ensure that the timer didn't step beyond the "microstep" needed to ensure that
321
+ // the onInited callbacks get run.
322
+ assertEquals (2e-6 , Timer .getFPGATimestamp (),
323
+ "Timer.getFPGATimestamp() should be 2e-6" );
312
324
s .enableAutonomous ();
313
325
}).setMaxJitterSecs (0 ).everyStep (stateChecker ((s , m ) -> {
314
326
LOG .log (Level .DEBUG ,
0 commit comments