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