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