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 }