-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathMotionController.java
More file actions
443 lines (406 loc) · 10.6 KB
/
MotionController.java
File metadata and controls
443 lines (406 loc) · 10.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
package org.usfirst.frc4904.standard.custom.motioncontrollers;
import java.util.Timer;
import java.util.TimerTask;
import org.usfirst.frc4904.standard.custom.sensors.InvalidSensorException;
import org.usfirst.frc4904.standard.custom.sensors.PIDSensor;
import edu.wpi.first.wpilibj.PIDOutput;
import edu.wpi.first.wpilibj.PIDSource;
import edu.wpi.first.hal.util.BoundaryException;
/**
* A MotionController modifies an output using a sensor
* to precisely maintain a certain input.
*
*/
public abstract class MotionController {
protected PIDOutput output;
protected Timer timer;
protected MotionControllerTask task;
protected final PIDSensor sensor;
protected double setpoint;
protected double absoluteTolerance;
protected boolean continuous;
protected double inputMax;
protected double inputMin;
protected boolean capOutput;
protected double outputMax;
protected double outputMin;
protected boolean enable;
protected boolean overridden;
protected Exception sensorException;
private volatile boolean justReset;
private final Object lock = new Object();
protected static final String DEFAULT_SMARTDASHBOARD_PREFIX = "MC";
/**
* A MotionController modifies an output using a sensor
* to precisely maintain a certain input.
*
* @param sensor
* The sensor associated with the output you are
* trying to control
*/
public MotionController(PIDSensor sensor) {
this.sensor = sensor;
output = null;
timer = new Timer();
task = new MotionControllerTask();
enable = false;
overridden = false;
absoluteTolerance = Double.MIN_VALUE; // Nonzero to avoid floating point errors
capOutput = false;
continuous = false;
inputMin = 0.0;
inputMax = 0.0;
outputMin = 0.0;
outputMax = 0.0;
reset();
justReset = true;
sensorException = null;
}
/**
* Sets the output for this MotionController.
* Once every MotionController tick, the output will
* be set to the results from the motion control
* calculation via the pidWrite function.
*
* @param output
* The output to control
*/
public void setOutput(PIDOutput output) {
this.output = output;
}
/**
* A MotionController modifies an output using a sensor
* to precisely maintain a certain input.
*
* @param sensor
* The sensor associated with the output you are
* trying to control
*/
public MotionController(PIDSource source) {
this(new PIDSensor.PIDSourceWrapper(source));
}
/**
* This should return the motion controller
* to a state such that it returns 0.
*
* @warning this does not indicate sensor errors
*/
public final void reset() {
resetErrorToZero();
setpoint = sensor.pidGet();
justReset = true;
}
/**
* This should return the motion controller
* to a state such that it returns 0.
*/
public final void resetSafely() throws InvalidSensorException {
resetErrorToZero();
setpoint = sensor.pidGetSafely();
justReset = true;
}
/**
* Method-specific method for resetting the
* motion controller without indicating sensor
* errors.
*/
protected abstract void resetErrorToZero();
/**
* The calculated output value to achieve the
* current setpoint.
*
* @return
* Output value. If output range is set,
* this will be restricted to within
* that range.
*/
public abstract double getSafely() throws InvalidSensorException;
/**
* The calculated output value to achieve the
* current setpoint.
*
* @return
* Output value. If output range is set,
* this will be restricted to within
* that range.
* @warning does not indicate sensor errors
*/
public abstract double get();
/**
* A very recent error.
*
* @return
* The most recent error calculated by
* the get function.
*/
public abstract double getError();
/**
* Get the current input to the PID loop.
*
* @return the current value of the sensor
*
* @warning this does not indicate sensor errors
*/
public double getSensorValue() {
return sensor.pidGet();
}
/**
* Get the current input to the PID loop.
*
* @return the current value of the sensor
*/
public double getInputSafely() throws InvalidSensorException {
return sensor.pidGetSafely();
}
/**
* The most recent setpoint.
*
* @return
* The most recent setpoint.
*/
public double getSetpoint() {
return setpoint;
}
/**
* Sets the setpoint of the motion controller.
* This is the value that the motion controller seeks.
*
* @param setpoint
*/
public void setSetpoint(double setpoint) {
this.setpoint = setpoint;
}
public boolean didJustReset() {
return justReset;
}
/**
* Sets the tolerance of the motion controller.
* When the error is less than the tolerance,
* onTarget returns true.
*
* @param absoluteTolerance
*/
public void setAbsoluteTolerance(double absoluteTolerance) {
if (absoluteTolerance >= 0) {
this.absoluteTolerance = absoluteTolerance;
return;
}
throw new BoundaryException("Absolute tolerance negative");
}
/**
* Returns the absolute tolerance set on the
* motion controller. Will return {@link org.usfirst.frc4904.standard.Util#EPSILON Util.EPSILON}
*
* @return absolute tolerance
*/
public double getAbsoluteTolerance() {
return absoluteTolerance;
}
/**
* Sets the input range of the motion controller.
* This is only used to work with continuous inputs.
* If minimum is greater than maximum, this will throw
* an exception.
*
* @param minimum
* @param maximum
*/
public void setInputRange(double minimum, double maximum) {
if (minimum > maximum) {
throw new BoundaryException("Minimum is greater than maximum");
}
inputMin = minimum;
inputMax = maximum;
}
/**
* Sets the output range of the motion controller.
* Results from the motion control calculation will be
* capped at these values. The cap is automatically
* enabled by calling this function.
*
* @param minimum
* @param maximum
*/
public void setOutputRange(double minimum, double maximum) {
outputMin = minimum;
outputMax = maximum;
capOutput = true;
}
/**
* Stops capping the output range.
*/
public void disableOutputRange() {
capOutput = false;
}
/**
* Sets the input range to continuous.
* This means that it will treat the
* maximum and minimum sensor and input values as
* being at the same point, e.g. the controller
* will try to pass through the maximum
* to get to a point beyond it.
*
* @param continuous
*/
public void setContinuous(boolean continuous) {
this.continuous = continuous;
}
/**
* Turns on the motion controller.
*/
public void enable() {
if (isOverridden()) {
return;
}
enable = true;
try {
timer.scheduleAtFixedRate(task, 10, 20);
justReset = true;
// justReset is written to by both the main thread and the Task,
// so there is a 10 millisecond delay in the initial execution of
// the task, which should reduce blocking
}
catch (IllegalStateException e) {} // Do not die if the timer is already running
}
/**
* Bypasses the motion controller.
* In some cases, this will still scale by
* a feed forward term of the motion controller.
*/
public void disable() {
if (isOverridden()) {
return;
}
enable = false;
task.cancel();
timer.purge();
task = new MotionControllerTask();
setpoint = sensor.pidGet();
}
/**
* Is motion control enabled?
*/
public boolean isEnabled() {
return enable;
}
/**
* Set whether or not the motion controller
* is overridden.
*
* @see #startOverriding()
* @see #stopOverriding()
*/
private void setOverride(boolean overridden) {
this.overridden = overridden;
}
/**
* Starts overriding the controller.
* The controller will disable and not be allowed
* to enable until the override is turned off.
*
* @see #stopOverriding()
*/
public void startOverriding() {
disable();
setOverride(true);
}
/**
* Stops overriding the motion controller.
* Enabling the controller will now be allowed.
*
* @see #startOverriding()
*/
public void stopOverriding() {
setOverride(false);
}
/**
* Has the controller been overridden?
*
* @see #setOverride(boolean)
*/
public boolean isOverridden() {
return overridden;
}
/**
* True if the error in the motion controller is
* less than the tolerance of the motion controller.
*
* @return
*/
public boolean onTarget() {
return Math.abs(getError()) <= absoluteTolerance;
}
/**
* Check if the motion controller has generated
* an exception within the TimerTask. If there is
* not an exception, the function returns null.
*
* @return the exception (probably null)
*/
public Exception checkException() {
return sensorException;
}
/**
* The thread in which the output is updated with the
* results of the motion controller calculation.
*
*/
protected class MotionControllerTask extends TimerTask {
@Override
public void run() {
try {
double value = getSafely(); // Always calculate MC output
synchronized (lock) {
if (justReset) {
justReset = false;
return;
}
}
if (output != null && isEnabled()) {
output.pidWrite(value);
}
}
catch (Exception e) {
sensorException = e;
}
}
}
/**
* Put the controller constants to SmartDashboard for tuning.
* as well as the error, setpoint, sensor value, and output.
* This method adds a very small random value to everything
* so that graphs show properly on SmartDashboard.
*
* @param prefix
* The prefix to use when putting things on SmartDashboard.
*/
public abstract void putToSmartDashboard(String prefix);
/**
* Put the controller constants to SmartDashboard for tuning.
* as well as the error, setpoint, sensor value, and output.
* This method adds a very small random value to everything
* so that graphs show properly on SmartDashboard.
*
* Uses the default prefix {@value #DEFAULT_SMARTDASHBOARD_PREFIX}
*/
public void putToSmartDashboard() {
putToSmartDashboard(MotionController.DEFAULT_SMARTDASHBOARD_PREFIX);
}
/**
* Update the controller constants from SmartDashboard for tuning.
* Gets new constants from SmartDashboard, defaulting to the current ones.
*
* @param prefix
* The prefix to use when putting things on SmartDashboard.
*/
public abstract void updateFromSmartDashboard(String prefix);
/**
* Update the controller constants from SmartDashboard for tuning.
* Gets new constants from SmartDashboard, defaulting to the current ones.
*
* Uses the default prefix {@value #DEFAULT_SMARTDASHBOARD_PREFIX}
*/
public void updateFromSmartDashboard() {
updateFromSmartDashboard(MotionController.DEFAULT_SMARTDASHBOARD_PREFIX);
}
}