001 /*----------------------------------------------------------------------------*/
002 /* Copyright (c) FIRST 2008-2012. All Rights Reserved. */
003 /* Open Source Software - may be modified and shared by FRC teams. The code */
004 /* must be accompanied by the FIRST BSD license file in the root directory of */
005 /* the project. */
006 /*----------------------------------------------------------------------------*/
007
008 package edu.wpi.first.wpilibj.command;
009
010 import edu.wpi.first.wpilibj.DriverStation;
011 import edu.wpi.first.wpilibj.NamedSendable;
012 import edu.wpi.first.wpilibj.Timer;
013 import edu.wpi.first.wpilibj.tables.ITable;
014 import edu.wpi.first.wpilibj.tables.ITableListener;
015 import java.util.Enumeration;
016 import java.util.NoSuchElementException;
017
018 /**
019 * The Command class is at the very core of the entire command framework.
020 * Every command can be started with a call to {@link Command#start() start()}.
021 * Once a command is started it will call {@link Command#initialize() initialize()}, and then
022 * will repeatedly call {@link Command#execute() execute()} until the {@link Command#isFinished() isFinished()}
023 * returns true. Once it does, {@link Command#end() end()} will be called.
024 *
025 * <p>However, if at any point while it is running {@link Command#cancel() cancel()} is called, then
026 * the command will be stopped and {@link Command#interrupted() interrupted()} will be called.</p>
027 *
028 * <p>If a command uses a {@link Subsystem}, then it should specify that it does so by
029 * calling the {@link Command#requires(Subsystem) requires(...)} method
030 * in its constructor. Note that a Command may have multiple requirements, and
031 * {@link Command#requires(Subsystem) requires(...)} should be
032 * called for each one.</p>
033 *
034 * <p>If a command is running and a new command with shared requirements is started,
035 * then one of two things will happen. If the active command is interruptible,
036 * then {@link Command#cancel() cancel()} will be called and the command will be removed
037 * to make way for the new one. If the active command is not interruptible, the
038 * other one will not even be started, and the active one will continue functioning.</p>
039 *
040 * @author Brad Miller
041 * @author Joe Grinstead
042 * @see Subsystem
043 * @see CommandGroup
044 * @see IllegalUseOfCommandException
045 */
046 public abstract class Command implements NamedSendable{
047
048 /** The name of this command */
049 private String m_name;
050 /** The time since this command was initialized */
051 private double m_startTime = -1;
052 /** The time (in seconds) before this command "times out" (or -1 if no timeout) */
053 private double m_timeout = -1;
054 /** Whether or not this command has been initialized */
055 private boolean m_initialized = false;
056 /** The requirements (or null if no requirements) */
057 private Set m_requirements;
058 /** Whether or not it is running */
059 private boolean m_running = false;
060 /** Whether or not it is interruptible*/
061 private boolean m_interruptible = true;
062 /** Whether or not it has been canceled */
063 private boolean m_canceled = false;
064 /** Whether or not it has been locked */
065 private boolean m_locked = false;
066 /** Whether this command should run when the robot is disabled */
067 private boolean m_runWhenDisabled = false;
068 /** The {@link CommandGroup} this is in */
069 private CommandGroup m_parent;
070
071 /**
072 * Creates a new command.
073 * The name of this command will be set to its class name.
074 */
075 public Command() {
076 m_name = getClass().getName();
077 m_name = m_name.substring(m_name.lastIndexOf('.') + 1);
078 }
079
080 /**
081 * Creates a new command with the given name.
082 * @param name the name for this command
083 * @throws IllegalArgumentException if name is null
084 */
085 public Command(String name) {
086 if (name == null) {
087 throw new IllegalArgumentException("Name must not be null.");
088 }
089 m_name = name;
090 }
091
092 /**
093 * Creates a new command with the given timeout and a default name.
094 * The default name is the name of the class.
095 * @param timeout the time (in seconds) before this command "times out"
096 * @throws IllegalArgumentException if given a negative timeout
097 * @see Command#isTimedOut() isTimedOut()
098 */
099 public Command(double timeout) {
100 this();
101 if (timeout < 0) {
102 throw new IllegalArgumentException("Timeout must not be negative. Given:" + timeout);
103 }
104 m_timeout = timeout;
105 }
106
107 /**
108 * Creates a new command with the given name and timeout.
109 * @param name the name of the command
110 * @param timeout the time (in seconds) before this command "times out"
111 * @throws IllegalArgumentException if given a negative timeout or name was null.
112 * @see Command#isTimedOut() isTimedOut()
113 */
114 public Command(String name, double timeout) {
115 this(name);
116 if (timeout < 0) {
117 throw new IllegalArgumentException("Timeout must not be negative. Given:" + timeout);
118 }
119 m_timeout = timeout;
120 }
121
122 /**
123 * Returns the name of this command.
124 * If no name was specified in the constructor,
125 * then the default is the name of the class.
126 * @return the name of this command
127 */
128 public String getName() {
129 return m_name;
130 }
131
132 /**
133 * Sets the timeout of this command.
134 * @param seconds the timeout (in seconds)
135 * @throws IllegalArgumentException if seconds is negative
136 * @see Command#isTimedOut() isTimedOut()
137 */
138 protected synchronized final void setTimeout(double seconds) {
139 if (seconds < 0) {
140 throw new IllegalArgumentException("Seconds must be positive. Given:" + seconds);
141 }
142 m_timeout = seconds;
143 }
144
145 /**
146 * Returns the time since this command was initialized (in seconds).
147 * This function will work even if there is no specified timeout.
148 * @return the time since this command was initialized (in seconds).
149 */
150 public synchronized final double timeSinceInitialized() {
151 return m_startTime < 0 ? 0 : Timer.getFPGATimestamp() - m_startTime;
152 }
153
154 /**
155 * This method specifies that the given {@link Subsystem} is used by this command.
156 * This method is crucial to the functioning of the Command System in general.
157 *
158 * <p>Note that the recommended way to call this method is in the constructor.</p>
159 *
160 * @param subsystem the {@link Subsystem} required
161 * @throws IllegalArgumentException if subsystem is null
162 * @throws IllegalUseOfCommandException if this command has started before or if it has been given to a {@link CommandGroup}
163 * @see Subsystem
164 */
165 protected synchronized void requires(Subsystem subsystem) {
166 validate("Can not add new requirement to command");
167 if (subsystem != null) {
168 if (m_requirements == null) {
169 m_requirements = new Set();
170 }
171 m_requirements.add(subsystem);
172 } else {
173 throw new IllegalArgumentException("Subsystem must not be null.");
174 }
175 }
176
177 /**
178 * Called when the command has been removed.
179 * This will call {@link Command#interrupted() interrupted()} or {@link Command#end() end()}.
180 */
181 synchronized void removed() {
182 if (m_initialized) {
183 if (isCanceled()) {
184 interrupted();
185 _interrupted();
186 } else {
187 end();
188 _end();
189 }
190 }
191 m_initialized = false;
192 m_canceled = false;
193 m_running = false;
194 if (table != null) {
195 table.putBoolean("running", false);
196 }
197 }
198
199 /**
200 * The run method is used internally to actually run the commands.
201 * @return whether or not the command should stay within the {@link Scheduler}.
202 */
203 synchronized boolean run() {
204 if (!m_runWhenDisabled && m_parent == null && DriverStation.getInstance().isDisabled()) {
205 cancel();
206 }
207 if (isCanceled()) {
208 return false;
209 }
210 if (!m_initialized) {
211 m_initialized = true;
212 startTiming();
213 _initialize();
214 initialize();
215 }
216 _execute();
217 execute();
218 return !isFinished();
219 }
220
221 /**
222 * The initialize method is called the first time this Command is run after
223 * being started.
224 */
225 protected abstract void initialize();
226
227 /** A shadow method called before {@link Command#initialize() initialize()} */
228 void _initialize() {
229 }
230
231 /**
232 * The execute method is called repeatedly until this Command either finishes
233 * or is canceled.
234 */
235 protected abstract void execute();
236
237 /** A shadow method called before {@link Command#execute() execute()} */
238 void _execute() {
239 }
240
241 /**
242 * Returns whether this command is finished.
243 * If it is, then the command will be removed
244 * and {@link Command#end() end()} will be called.
245 *
246 * <p>It may be useful for a team to reference the {@link Command#isTimedOut() isTimedOut()} method
247 * for time-sensitive commands.</p>
248 * @return whether this command is finished.
249 * @see Command#isTimedOut() isTimedOut()
250 */
251 protected abstract boolean isFinished();
252
253 /**
254 * Called when the command ended peacefully. This is where you may want
255 * to wrap up loose ends, like shutting off a motor that was being used
256 * in the command.
257 */
258 protected abstract void end();
259
260 /** A shadow method called after {@link Command#end() end()}. */
261 void _end() {
262 }
263
264 /**
265 * Called when the command ends because somebody called {@link Command#cancel() cancel()}
266 * or another command shared the same requirements as this one, and booted
267 * it out.
268 *
269 * <p>This is where you may want
270 * to wrap up loose ends, like shutting off a motor that was being used
271 * in the command.</p>
272 *
273 * <p>Generally, it is useful to simply call the {@link Command#end() end()} method
274 * within this method</p>
275 */
276 protected abstract void interrupted();
277
278 /** A shadow method called after {@link Command#interrupted() interrupted()}. */
279 void _interrupted() {
280 }
281
282 /**
283 * Called to indicate that the timer should start.
284 * This is called right before {@link Command#initialize() initialize()} is, inside the
285 * {@link Command#run() run()} method.
286 */
287 private void startTiming() {
288 m_startTime = Timer.getFPGATimestamp();
289 }
290
291 /**
292 * Returns whether or not the {@link Command#timeSinceInitialized() timeSinceInitialized()}
293 * method returns a number which is greater than or equal to the timeout for the command.
294 * If there is no timeout, this will always return false.
295 * @return whether the time has expired
296 */
297 protected synchronized boolean isTimedOut() {
298 return m_timeout != -1 && timeSinceInitialized() >= m_timeout;
299 }
300
301 /**
302 * Returns the requirements (as an {@link Enumeration Enumeration} of {@link Subsystem Subsystems}) of this command
303 * @return the requirements (as an {@link Enumeration Enumeration} of {@link Subsystem Subsystems}) of this command
304 */
305 synchronized Enumeration getRequirements() {
306 return m_requirements == null ? emptyEnumeration : m_requirements.getElements();
307 }
308
309 /**
310 * Prevents further changes from being made
311 */
312 synchronized void lockChanges() {
313 m_locked = true;
314 }
315
316 /**
317 * If changes are locked, then this will throw an {@link IllegalUseOfCommandException}.
318 * @param message the message to say (it is appended by a default message)
319 */
320 synchronized void validate(String message) {
321 if (m_locked) {
322 throw new IllegalUseOfCommandException(message + " after being started or being added to a command group");
323 }
324 }
325
326 /**
327 * Sets the parent of this command. No actual change is made to the group.
328 * @param parent the parent
329 * @throws IllegalUseOfCommandException if this {@link Command} already is already in a group
330 */
331 synchronized void setParent(CommandGroup parent) {
332 if (this.m_parent != null) {
333 throw new IllegalUseOfCommandException("Can not give command to a command group after already being put in a command group");
334 }
335 lockChanges();
336 this.m_parent = parent;
337 if (table != null) {
338 table.putBoolean("isParented", true);
339 }
340 }
341
342 /**
343 * Starts up the command. Gets the command ready to start.
344 * <p>Note that the command will eventually start, however it will not necessarily
345 * do so immediately, and may in fact be canceled before initialize is even called.</p>
346 * @throws IllegalUseOfCommandException if the command is a part of a CommandGroup
347 */
348 public synchronized void start() {
349 lockChanges();
350 if (m_parent != null) {
351 throw new IllegalUseOfCommandException("Can not start a command that is a part of a command group");
352 }
353 Scheduler.getInstance().add(this);
354 }
355
356 /**
357 * This is used internally to mark that the command has been started.
358 * The lifecycle of a command is:
359 *
360 * startRunning() is called.
361 * run() is called (multiple times potentially)
362 * removed() is called
363 *
364 * It is very important that startRunning and removed be called in order or some assumptions
365 * of the code will be broken.
366 */
367 synchronized void startRunning() {
368 m_running = true;
369 m_startTime = -1;
370 if (table != null) {
371 table.putBoolean("running", true);
372 }
373 }
374
375 /**
376 * Returns whether or not the command is running.
377 * This may return true even if the command has just been canceled, as it may
378 * not have yet called {@link Command#interrupted()}.
379 * @return whether or not the command is running
380 */
381 public synchronized boolean isRunning() {
382 return m_running;
383 }
384
385 /**
386 * This will cancel the current command.
387 * <p>This will cancel the current command eventually. It can be called multiple times.
388 * And it can be called when the command is not running. If the command is running though,
389 * then the command will be marked as canceled and eventually removed.</p>
390 * <p>A command can not be canceled
391 * if it is a part of a command group, you must cancel the command group instead.</p>
392 * @throws IllegalUseOfCommandException if this command is a part of a command group
393 */
394 public synchronized void cancel() {
395 if (m_parent != null) {
396 throw new IllegalUseOfCommandException("Can not manually cancel a command in a command group");
397 }
398 _cancel();
399 }
400
401 /**
402 * This works like cancel(), except that it doesn't throw an exception if it is a part
403 * of a command group. Should only be called by the parent command group.
404 */
405 synchronized void _cancel() {
406 if (isRunning()) {
407 m_canceled = true;
408 }
409 }
410
411 /**
412 * Returns whether or not this has been canceled.
413 * @return whether or not this has been canceled
414 */
415 public synchronized boolean isCanceled() {
416 return m_canceled;
417 }
418
419 /**
420 * Returns whether or not this command can be interrupted.
421 * @return whether or not this command can be interrupted
422 */
423 public synchronized boolean isInterruptible() {
424 return m_interruptible;
425 }
426
427 /**
428 * Sets whether or not this command can be interrupted.
429 * @param interruptible whether or not this command can be interrupted
430 */
431 protected synchronized void setInterruptible(boolean interruptible) {
432 this.m_interruptible = interruptible;
433 }
434
435 /**
436 * Checks if the command requires the given {@link Subsystem}.
437 * @param system the system
438 * @return whether or not the subsystem is required, or false if given null
439 */
440 public synchronized boolean doesRequire(Subsystem system) {
441 return m_requirements != null && m_requirements.contains(system);
442 }
443
444 /**
445 * Returns the {@link CommandGroup} that this command is a part of.
446 * Will return null if this {@link Command} is not in a group.
447 * @return the {@link CommandGroup} that this command is a part of (or null if not in group)
448 */
449 public synchronized CommandGroup getGroup() {
450 return m_parent;
451 }
452
453 /**
454 * Sets whether or not this {@link Command} should run when the robot is disabled.
455 *
456 * <p>By default a command will not run when the robot is disabled, and will in fact be canceled.</p>
457 * @param run whether or not this command should run when the robot is disabled
458 */
459 public void setRunWhenDisabled(boolean run) {
460 m_runWhenDisabled = run;
461 }
462
463 /**
464 * Returns whether or not this {@link Command} will run when the robot is disabled, or if it will cancel itself.
465 * @return whether or not this {@link Command} will run when the robot is disabled, or if it will cancel itself
466 */
467 public boolean willRunWhenDisabled() {
468 return m_runWhenDisabled;
469 }
470 /** An empty enumeration given whenever there are no requirements */
471 private static Enumeration emptyEnumeration = new Enumeration() {
472
473 public boolean hasMoreElements() {
474 return false;
475 }
476
477 public Object nextElement() {
478 throw new NoSuchElementException();
479 }
480 };
481
482 /**
483 * The string representation for a {@link Command} is by default its name.
484 * @return the string representation of this object
485 */
486 public String toString() {
487 return getName();
488 }
489
490
491 public String getSmartDashboardType(){
492 return "Command";
493 }
494 private ITableListener listener = new ITableListener() {
495 public void valueChanged(ITable table, String key, Object value, boolean isNew) {
496 if (((Boolean) value).booleanValue()) {
497 start();
498 } else {
499 cancel();
500 }
501 }
502 };
503 private ITable table;
504 public void initTable(ITable table){
505 if(this.table!=null)
506 this.table.removeTableListener(listener);
507 this.table = table;
508 if(table!=null){
509 table.putString("name", getName());
510 table.putBoolean("running", isRunning());
511 table.putBoolean("isParented", m_parent != null);
512 table.addTableListener("running", listener, false);
513 }
514 }
515
516 /**
517 * {@inheritDoc}
518 */
519 public ITable getTable(){
520 return table;
521 }
522
523 }