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