blob: aea6fdf7a009cf4d1a5812acc82cc943644c2c69 [file] [log] [blame]
jerrymf1579332013-02-07 01:56:28 +00001/*----------------------------------------------------------------------------*/
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
14static const char *kName = "name";
15static const char *kRunning = "running";
16static const char *kIsParented = "isParented";
17
18void 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 */
47Command::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 */
56Command::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 */
68Command::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 */
81Command::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
90Command::~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 */
102void 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 */
115double 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 */
132void 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 */
147void 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 */
174void 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 */
187bool 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
207void Command::_Initialize()
208{
209}
210
211void Command::_Interrupted()
212{
213}
214
215void Command::_Execute()
216{
217}
218
219void 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 */
228void 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 */
239bool 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 */
248Command::SubsystemSet Command::GetRequirements()
249{
250 return m_requirements;
251}
252
253/**
254 * Prevents further changes from being made
255 */
256void 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 */
266bool 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 */
282void 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 */
314void 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 */
328bool 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 */
341void 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 */
353void 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 */
363bool 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 */
372bool 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 */
381void 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 */
391bool 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 */
401CommandGroup *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 */
412void 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 */
421bool Command::WillRunWhenDisabled()
422{
423 return m_runWhenDisabled;
424}
425
426std::string Command::GetName()
427{
428 return m_name;
429}
430
431std::string Command::GetSmartDashboardType()
432{
433 return "Command";
434}
435
436void 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
449ITable* Command::GetTable(){
450 return m_table;
451}
452
453void 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}