jerrym | f157933 | 2013-02-07 01:56:28 +0000 | [diff] [blame] | 1 | /*----------------------------------------------------------------------------*/
|
| 2 | /* Copyright (c) FIRST 2011. All Rights Reserved. */
|
| 3 | /* Open Source Software - may be modified and shared by FRC teams. The code */
|
| 4 | /* must be accompanied by the FIRST BSD license file in $(WIND_BASE)/WPILib. */
|
| 5 | /*----------------------------------------------------------------------------*/
|
| 6 |
|
| 7 | #include "Commands/Command.h"
|
| 8 | #include "Commands/CommandGroup.h"
|
| 9 | #include "Commands/Scheduler.h"
|
| 10 | #include "DriverStation.h"
|
| 11 | #include "Timer.h"
|
| 12 | #include "WPIErrors.h"
|
| 13 |
|
| 14 | static const char *kName = "name";
|
| 15 | static const char *kRunning = "running";
|
| 16 | static const char *kIsParented = "isParented";
|
| 17 |
|
jerrym | 42dedc0 | 2013-02-25 01:59:14 +0000 | [diff] [blame] | 18 | int Command::m_commandCounter = 0;
|
| 19 |
|
jerrym | f157933 | 2013-02-07 01:56:28 +0000 | [diff] [blame] | 20 | void Command::InitCommand(const char *name, double timeout)
|
| 21 | {
|
jerrym | 42dedc0 | 2013-02-25 01:59:14 +0000 | [diff] [blame] | 22 | m_commandID = m_commandCounter++;
|
jerrym | f157933 | 2013-02-07 01:56:28 +0000 | [diff] [blame] | 23 | m_timeout = timeout;
|
| 24 | m_locked = false;
|
| 25 | m_startTime = -1;
|
| 26 | m_initialized = false;
|
| 27 | m_running = false;
|
| 28 | m_interruptible = true;
|
| 29 | m_canceled = false;
|
| 30 | m_runWhenDisabled = false;
|
| 31 | m_parent = NULL;
|
| 32 | if (name == NULL)
|
| 33 | {
|
| 34 | // Don't have a way to find the subclass name like java, so use the address
|
| 35 | char buf[32];
|
| 36 | snprintf(buf, 32, "Command_%p", this);
|
| 37 | m_name = buf;
|
| 38 | }
|
| 39 | else
|
| 40 | {
|
| 41 | m_name = name;
|
| 42 | }
|
| 43 | m_table = NULL;
|
| 44 | }
|
| 45 |
|
| 46 | /**
|
| 47 | * Creates a new command.
|
| 48 | * The name of this command will be default.
|
| 49 | */
|
| 50 | Command::Command()
|
| 51 | {
|
| 52 | InitCommand(NULL, -1.0);
|
| 53 | }
|
| 54 |
|
| 55 | /**
|
| 56 | * Creates a new command with the given name and no timeout.
|
| 57 | * @param name the name for this command
|
| 58 | */
|
| 59 | Command::Command(const char *name)
|
| 60 | {
|
| 61 | if (name == NULL)
|
| 62 | wpi_setWPIErrorWithContext(NullParameter, "name");
|
| 63 | InitCommand(name, -1.0);
|
| 64 | }
|
| 65 |
|
| 66 | /**
|
| 67 | * Creates a new command with the given timeout and a default name.
|
| 68 | * @param timeout the time (in seconds) before this command "times out"
|
| 69 | * @see Command#isTimedOut() isTimedOut()
|
| 70 | */
|
| 71 | Command::Command(double timeout)
|
| 72 | {
|
| 73 | if (timeout < 0.0)
|
| 74 | wpi_setWPIErrorWithContext(ParameterOutOfRange, "timeout < 0.0");
|
| 75 | InitCommand(NULL, timeout);
|
| 76 | }
|
| 77 |
|
| 78 | /**
|
| 79 | * Creates a new command with the given name and timeout.
|
| 80 | * @param name the name of the command
|
| 81 | * @param timeout the time (in seconds) before this command "times out"
|
| 82 | * @see Command#isTimedOut() isTimedOut()
|
| 83 | */
|
| 84 | Command::Command(const char *name, double timeout)
|
| 85 | {
|
| 86 | if (name == NULL)
|
| 87 | wpi_setWPIErrorWithContext(NullParameter, "name");
|
| 88 | if (timeout < 0.0)
|
| 89 | wpi_setWPIErrorWithContext(ParameterOutOfRange, "timeout < 0.0");
|
| 90 | InitCommand(name, timeout);
|
| 91 | }
|
| 92 |
|
| 93 | Command::~Command()
|
| 94 | {//TODO deal with cleaning up all listeners
|
| 95 | /*if (m_table != NULL){
|
| 96 | m_table->RemoveChangeListener(kRunning, this);
|
| 97 | }*/
|
| 98 | }
|
| 99 |
|
| 100 | /**
|
jerrym | 42dedc0 | 2013-02-25 01:59:14 +0000 | [diff] [blame] | 101 | * Get the ID (sequence number) for this command
|
| 102 | * The ID is a unique sequence number that is incremented for each command.
|
| 103 | * @return the ID of this command
|
| 104 | */
|
| 105 | int Command::GetID() {
|
| 106 | return m_commandID;
|
| 107 | }
|
| 108 |
|
| 109 | /**
|
jerrym | f157933 | 2013-02-07 01:56:28 +0000 | [diff] [blame] | 110 | * Sets the timeout of this command.
|
| 111 | * @param timeout the timeout (in seconds)
|
| 112 | * @see Command#isTimedOut() isTimedOut()
|
| 113 | */
|
| 114 | void Command::SetTimeout(double timeout)
|
| 115 | {
|
| 116 | if (timeout < 0.0)
|
| 117 | wpi_setWPIErrorWithContext(ParameterOutOfRange, "timeout < 0.0");
|
| 118 | else
|
| 119 | m_timeout = timeout;
|
| 120 | }
|
| 121 |
|
| 122 | /**
|
| 123 | * Returns the time since this command was initialized (in seconds).
|
| 124 | * This function will work even if there is no specified timeout.
|
| 125 | * @return the time since this command was initialized (in seconds).
|
| 126 | */
|
| 127 | double Command::TimeSinceInitialized()
|
| 128 | {
|
| 129 | if (m_startTime < 0.0)
|
| 130 | return 0.0;
|
| 131 | else
|
| 132 | return Timer::GetFPGATimestamp() - m_startTime;
|
| 133 | }
|
| 134 |
|
| 135 | /**
|
| 136 | * This method specifies that the given {@link Subsystem} is used by this command.
|
| 137 | * This method is crucial to the functioning of the Command System in general.
|
| 138 | *
|
| 139 | * <p>Note that the recommended way to call this method is in the constructor.</p>
|
| 140 | *
|
| 141 | * @param subsystem the {@link Subsystem} required
|
| 142 | * @see Subsystem
|
| 143 | */
|
| 144 | void Command::Requires(Subsystem *subsystem)
|
| 145 | {
|
| 146 | if (!AssertUnlocked("Can not add new requirement to command"))
|
| 147 | return;
|
| 148 |
|
| 149 | if (subsystem != NULL)
|
| 150 | m_requirements.insert(subsystem);
|
| 151 | else
|
| 152 | wpi_setWPIErrorWithContext(NullParameter, "subsystem");
|
| 153 | }
|
| 154 |
|
| 155 | /**
|
| 156 | * Called when the command has been removed.
|
| 157 | * This will call {@link Command#interrupted() interrupted()} or {@link Command#end() end()}.
|
| 158 | */
|
| 159 | void Command::Removed()
|
| 160 | {
|
| 161 | if (m_initialized)
|
| 162 | {
|
| 163 | if (IsCanceled())
|
| 164 | {
|
| 165 | Interrupted();
|
| 166 | _Interrupted();
|
| 167 | }
|
| 168 | else
|
| 169 | {
|
| 170 | End();
|
| 171 | _End();
|
| 172 | }
|
| 173 | }
|
| 174 | m_initialized = false;
|
| 175 | m_canceled = false;
|
| 176 | m_running = false;
|
| 177 | if (m_table != NULL)
|
| 178 | m_table->PutBoolean(kRunning, false);
|
| 179 | }
|
| 180 |
|
| 181 | /**
|
| 182 | * Starts up the command. Gets the command ready to start.
|
| 183 | * <p>Note that the command will eventually start, however it will not necessarily
|
| 184 | * do so immediately, and may in fact be canceled before initialize is even called.</p>
|
| 185 | */
|
| 186 | void Command::Start()
|
| 187 | {
|
| 188 | LockChanges();
|
| 189 | if (m_parent != NULL)
|
| 190 | wpi_setWPIErrorWithContext(CommandIllegalUse, "Can not start a command that is part of a command group");
|
| 191 |
|
| 192 | Scheduler::GetInstance()->AddCommand(this);
|
| 193 | }
|
| 194 |
|
| 195 | /**
|
| 196 | * The run method is used internally to actually run the commands.
|
| 197 | * @return whether or not the command should stay within the {@link Scheduler}.
|
| 198 | */
|
| 199 | bool Command::Run()
|
| 200 | {
|
| 201 | if (!m_runWhenDisabled && m_parent == NULL && DriverStation::GetInstance()->IsDisabled())
|
| 202 | Cancel();
|
| 203 |
|
| 204 | if (IsCanceled())
|
| 205 | return false;
|
| 206 |
|
| 207 | if (!m_initialized)
|
| 208 | {
|
| 209 | m_initialized = true;
|
| 210 | StartTiming();
|
| 211 | _Initialize();
|
| 212 | Initialize();
|
| 213 | }
|
| 214 | _Execute();
|
| 215 | Execute();
|
| 216 | return !IsFinished();
|
| 217 | }
|
| 218 |
|
| 219 | void Command::_Initialize()
|
| 220 | {
|
| 221 | }
|
| 222 |
|
| 223 | void Command::_Interrupted()
|
| 224 | {
|
| 225 | }
|
| 226 |
|
| 227 | void Command::_Execute()
|
| 228 | {
|
| 229 | }
|
| 230 |
|
| 231 | void Command::_End()
|
| 232 | {
|
| 233 | }
|
| 234 |
|
| 235 | /**
|
| 236 | * Called to indicate that the timer should start.
|
| 237 | * This is called right before {@link Command#initialize() initialize()} is, inside the
|
| 238 | * {@link Command#run() run()} method.
|
| 239 | */
|
| 240 | void Command::StartTiming()
|
| 241 | {
|
| 242 | m_startTime = Timer::GetFPGATimestamp();
|
| 243 | }
|
| 244 |
|
| 245 | /**
|
| 246 | * Returns whether or not the {@link Command#timeSinceInitialized() timeSinceInitialized()}
|
| 247 | * method returns a number which is greater than or equal to the timeout for the command.
|
| 248 | * If there is no timeout, this will always return false.
|
| 249 | * @return whether the time has expired
|
| 250 | */
|
| 251 | bool Command::IsTimedOut()
|
| 252 | {
|
| 253 | return m_timeout != -1 && TimeSinceInitialized() >= m_timeout;
|
| 254 | }
|
| 255 |
|
| 256 | /**
|
| 257 | * Returns the requirements (as an std::set of {@link Subsystem Subsystems} pointers) of this command
|
| 258 | * @return the requirements (as an std::set of {@link Subsystem Subsystems} pointers) of this command
|
| 259 | */
|
| 260 | Command::SubsystemSet Command::GetRequirements()
|
| 261 | {
|
| 262 | return m_requirements;
|
| 263 | }
|
| 264 |
|
| 265 | /**
|
| 266 | * Prevents further changes from being made
|
| 267 | */
|
| 268 | void Command::LockChanges()
|
| 269 | {
|
| 270 | m_locked = true;
|
| 271 | }
|
| 272 |
|
| 273 | /**
|
| 274 | * If changes are locked, then this will generate a CommandIllegalUse error.
|
| 275 | * @param message the message to report on error (it is appended by a default message)
|
| 276 | * @return true if assert passed, false if assert failed
|
| 277 | */
|
| 278 | bool Command::AssertUnlocked(const char *message)
|
| 279 | {
|
| 280 | if (m_locked)
|
| 281 | {
|
| 282 | char buf[128];
|
| 283 | snprintf(buf, 128, "%s after being started or being added to a command group", message);
|
| 284 | wpi_setWPIErrorWithContext(CommandIllegalUse, buf);
|
| 285 | return false;
|
| 286 | }
|
| 287 | return true;
|
| 288 | }
|
| 289 |
|
| 290 | /**
|
| 291 | * Sets the parent of this command. No actual change is made to the group.
|
| 292 | * @param parent the parent
|
| 293 | */
|
| 294 | void Command::SetParent(CommandGroup *parent)
|
| 295 | {
|
| 296 | if (parent == NULL)
|
| 297 | {
|
| 298 | wpi_setWPIErrorWithContext(NullParameter, "parent");
|
| 299 | }
|
| 300 | else if (m_parent != NULL)
|
| 301 | {
|
| 302 | wpi_setWPIErrorWithContext(CommandIllegalUse, "Can not give command to a command group after already being put in a command group");
|
| 303 | }
|
| 304 | else
|
| 305 | {
|
| 306 | LockChanges();
|
| 307 | m_parent = parent;
|
| 308 | if (m_table != NULL)
|
| 309 | {
|
| 310 | m_table->PutBoolean(kIsParented, true);
|
| 311 | }
|
| 312 | }
|
| 313 | }
|
| 314 |
|
| 315 | /**
|
| 316 | * This is used internally to mark that the command has been started.
|
| 317 | * The lifecycle of a command is:
|
| 318 | *
|
| 319 | * startRunning() is called.
|
| 320 | * run() is called (multiple times potentially)
|
| 321 | * removed() is called
|
| 322 | *
|
| 323 | * It is very important that startRunning and removed be called in order or some assumptions
|
| 324 | * of the code will be broken.
|
| 325 | */
|
| 326 | void Command::StartRunning()
|
| 327 | {
|
| 328 | m_running = true;
|
| 329 | m_startTime = -1;
|
| 330 | if (m_table != NULL)
|
| 331 | m_table->PutBoolean(kRunning, true);
|
| 332 | }
|
| 333 |
|
| 334 | /**
|
| 335 | * Returns whether or not the command is running.
|
| 336 | * This may return true even if the command has just been canceled, as it may
|
| 337 | * not have yet called {@link Command#interrupted()}.
|
| 338 | * @return whether or not the command is running
|
| 339 | */
|
| 340 | bool Command::IsRunning()
|
| 341 | {
|
| 342 | return m_running;
|
| 343 | }
|
| 344 |
|
| 345 | /**
|
| 346 | * This will cancel the current command.
|
| 347 | * <p>This will cancel the current command eventually. It can be called multiple times.
|
| 348 | * And it can be called when the command is not running. If the command is running though,
|
| 349 | * then the command will be marked as canceled and eventually removed.</p>
|
| 350 | * <p>A command can not be canceled
|
| 351 | * if it is a part of a command group, you must cancel the command group instead.</p>
|
| 352 | */
|
| 353 | void Command::Cancel()
|
| 354 | {
|
| 355 | if (m_parent != NULL)
|
| 356 | wpi_setWPIErrorWithContext(CommandIllegalUse, "Can not cancel a command that is part of a command group");
|
| 357 |
|
| 358 | _Cancel();
|
| 359 | }
|
| 360 |
|
| 361 | /**
|
| 362 | * This works like cancel(), except that it doesn't throw an exception if it is a part
|
| 363 | * of a command group. Should only be called by the parent command group.
|
| 364 | */
|
| 365 | void Command::_Cancel()
|
| 366 | {
|
| 367 | if (IsRunning())
|
| 368 | m_canceled = true;
|
| 369 | }
|
| 370 |
|
| 371 | /**
|
| 372 | * Returns whether or not this has been canceled.
|
| 373 | * @return whether or not this has been canceled
|
| 374 | */
|
| 375 | bool Command::IsCanceled()
|
| 376 | {
|
| 377 | return m_canceled;
|
| 378 | }
|
| 379 |
|
| 380 | /**
|
| 381 | * Returns whether or not this command can be interrupted.
|
| 382 | * @return whether or not this command can be interrupted
|
| 383 | */
|
| 384 | bool Command::IsInterruptible()
|
| 385 | {
|
| 386 | return m_interruptible;
|
| 387 | }
|
| 388 |
|
| 389 | /**
|
| 390 | * Sets whether or not this command can be interrupted.
|
| 391 | * @param interruptible whether or not this command can be interrupted
|
| 392 | */
|
| 393 | void Command::SetInterruptible(bool interruptible)
|
| 394 | {
|
| 395 | m_interruptible = interruptible;
|
| 396 | }
|
| 397 |
|
| 398 | /**
|
| 399 | * Checks if the command requires the given {@link Subsystem}.
|
| 400 | * @param system the system
|
| 401 | * @return whether or not the subsystem is required (false if given NULL)
|
| 402 | */
|
| 403 | bool Command::DoesRequire(Subsystem *system)
|
| 404 | {
|
| 405 | return m_requirements.count(system) > 0;
|
| 406 | }
|
| 407 |
|
| 408 | /**
|
| 409 | * Returns the {@link CommandGroup} that this command is a part of.
|
| 410 | * Will return null if this {@link Command} is not in a group.
|
| 411 | * @return the {@link CommandGroup} that this command is a part of (or null if not in group)
|
| 412 | */
|
| 413 | CommandGroup *Command::GetGroup()
|
| 414 | {
|
| 415 | return m_parent;
|
| 416 | }
|
| 417 |
|
| 418 | /**
|
| 419 | * Sets whether or not this {@link Command} should run when the robot is disabled.
|
| 420 | *
|
| 421 | * <p>By default a command will not run when the robot is disabled, and will in fact be canceled.</p>
|
| 422 | * @param run whether or not this command should run when the robot is disabled
|
| 423 | */
|
| 424 | void Command::SetRunWhenDisabled(bool run)
|
| 425 | {
|
| 426 | m_runWhenDisabled = run;
|
| 427 | }
|
| 428 |
|
| 429 | /**
|
| 430 | * Returns whether or not this {@link Command} will run when the robot is disabled, or if it will cancel itself.
|
| 431 | * @return whether or not this {@link Command} will run when the robot is disabled, or if it will cancel itself
|
| 432 | */
|
| 433 | bool Command::WillRunWhenDisabled()
|
| 434 | {
|
| 435 | return m_runWhenDisabled;
|
| 436 | }
|
| 437 |
|
| 438 | std::string Command::GetName()
|
| 439 | {
|
| 440 | return m_name;
|
| 441 | }
|
| 442 |
|
| 443 | std::string Command::GetSmartDashboardType()
|
| 444 | {
|
| 445 | return "Command";
|
| 446 | }
|
| 447 |
|
| 448 | void Command::InitTable(ITable* table)
|
| 449 | {
|
| 450 | if(m_table!=NULL)
|
| 451 | m_table->RemoveTableListener(this);
|
| 452 | m_table = table;
|
| 453 | if(m_table!=NULL){
|
| 454 | m_table->PutString(kName, GetName());
|
| 455 | m_table->PutBoolean(kRunning, IsRunning());
|
| 456 | m_table->PutBoolean(kIsParented, m_parent != NULL);
|
| 457 | m_table->AddTableListener(kRunning, this, false);
|
| 458 | }
|
| 459 | }
|
| 460 |
|
| 461 | ITable* Command::GetTable(){
|
| 462 | return m_table;
|
| 463 | }
|
| 464 |
|
| 465 | void Command::ValueChanged(ITable* source, const std::string& key, EntryValue value, bool isNew)
|
| 466 | {
|
| 467 | if (value.b){
|
| 468 | if(!IsRunning())
|
| 469 | Start();
|
| 470 | }
|
| 471 | else{
|
| 472 | if(IsRunning())
|
| 473 | Cancel();
|
| 474 | }
|
| 475 | }
|