El planificador de comandos
The CommandScheduler
(Java, C++) is the class responsible for actually running commands. Each iteration (ordinarily once per 20ms), the scheduler polls all registered buttons, schedules commands for execution accordingly, runs the command bodies of all scheduled commands, and ends those commands that have finished or are interrupted.
El CommandScheduler
también ejecuta el método periodic()
de cada `` Subsistema`` registrado.
Uso del programador de comandos
El CommandScheduler
es un singleton, lo que significa que es una clase accesible globalmente con una sola instancia. En consecuencia, para acceder al programador, los usuarios deben llamar al comando CommandScheduler.getInstance()
.
For the most part, users do not have to call scheduler methods directly - almost all important scheduler methods have convenience wrappers elsewhere (e.g. in the Command
and Subsystem
classes).
Sin embargo, hay una excepción: los usuarios deben llamar a CommandScheduler.getInstance().run()
del método robotPeriodic()
de su clase Robot
. Si no se hace esto, el programador nunca se ejecutará y el marco de comandos no funcionará. La plantilla de proyecto a base de comandos proporcionada ya tiene esta llamada incluida.
El método schedule()
To schedule a command, users call the schedule()
method (Java, C++). This method takes a command, and attempts to add it to list of currently-running commands, pending whether it is already running or whether its requirements are available. If it is added, its initialize()
method is called.
This method walks through the following steps:
Verifies that the command isn’t in a composition.
No-op if scheduler is disabled, command is already scheduled, or robot is disabled and command doesn’t runsWhenDisabled.
If requirements are in use:
If all conflicting commands are interruptible, cancel them.
If not, don’t schedule the new command.
Call
initialize()
.
202 private void schedule(Command command) {
203 if (command == null) {
204 DriverStation.reportWarning("Tried to schedule a null command", true);
205 return;
206 }
207 if (m_inRunLoop) {
208 m_toSchedule.add(command);
209 return;
210 }
211
212 requireNotComposed(command);
213
214 // Do nothing if the scheduler is disabled, the robot is disabled and the command doesn't
215 // run when disabled, or the command is already scheduled.
216 if (m_disabled
217 || isScheduled(command)
218 || RobotState.isDisabled() && !command.runsWhenDisabled()) {
219 return;
220 }
221
222 Set<Subsystem> requirements = command.getRequirements();
223
224 // Schedule the command if the requirements are not currently in-use.
225 if (Collections.disjoint(m_requirements.keySet(), requirements)) {
226 initCommand(command, requirements);
227 } else {
228 // Else check if the requirements that are in use have all have interruptible commands,
229 // and if so, interrupt those commands and schedule the new command.
230 for (Subsystem requirement : requirements) {
231 Command requiring = requiring(requirement);
232 if (requiring != null
233 && requiring.getInterruptionBehavior() == InterruptionBehavior.kCancelIncoming) {
234 return;
235 }
236 }
237 for (Subsystem requirement : requirements) {
238 Command requiring = requiring(requirement);
239 if (requiring != null) {
240 cancel(requiring);
241 }
242 }
243 initCommand(command, requirements);
244 }
245 }
181 private void initCommand(Command command, Set<Subsystem> requirements) {
182 m_scheduledCommands.add(command);
183 for (Subsystem requirement : requirements) {
184 m_requirements.put(requirement, command);
185 }
186 command.initialize();
187 for (Consumer<Command> action : m_initActions) {
188 action.accept(command);
189 }
190
191 m_watchdog.addEpoch(command.getName() + ".initialize()");
114void CommandScheduler::Schedule(Command* command) {
115 if (m_impl->inRunLoop) {
116 m_impl->toSchedule.emplace_back(command);
117 return;
118 }
119
120 RequireUngrouped(command);
121
122 if (m_impl->disabled || m_impl->scheduledCommands.contains(command) ||
123 (frc::RobotState::IsDisabled() && !command->RunsWhenDisabled())) {
124 return;
125 }
126
127 const auto& requirements = command->GetRequirements();
128
129 wpi::SmallVector<Command*, 8> intersection;
130
131 bool isDisjoint = true;
132 bool allInterruptible = true;
133 for (auto&& i1 : m_impl->requirements) {
134 if (requirements.find(i1.first) != requirements.end()) {
135 isDisjoint = false;
136 allInterruptible &= (i1.second->GetInterruptionBehavior() ==
137 Command::InterruptionBehavior::kCancelSelf);
138 intersection.emplace_back(i1.second);
139 }
140 }
141
142 if (isDisjoint || allInterruptible) {
143 if (allInterruptible) {
144 for (auto&& cmdToCancel : intersection) {
145 Cancel(cmdToCancel);
146 }
147 }
148 m_impl->scheduledCommands.insert(command);
149 for (auto&& requirement : requirements) {
150 m_impl->requirements[requirement] = command;
151 }
152 command->Initialize();
153 for (auto&& action : m_impl->initActions) {
154 action(*command);
155 }
156 m_watchdog.AddEpoch(command->GetName() + ".Initialize()");
157 }
158}
La secuencia de ejecución del programador
Nota
El método initialize()
de cada Comando
es llamado cuando el comando es programado, lo cual no es necesariamente cuando un programador este ejecutándose (a menos que ese comando este atado a un botón).
What does a single iteration of the scheduler’s run()
method (Java, C++) actually do? The following section walks through the logic of a scheduler iteration. For the full implementation, see the source code (Java, C++).
Paso 1: ejecutar los métodos periódicos del subsistema
First, the scheduler runs the periodic()
method of each registered Subsystem
. In simulation, each subsystem’s simulationPeriodic()
method is called as well.
278 // Run the periodic method of all registered subsystems.
279 for (Subsystem subsystem : m_subsystems.keySet()) {
280 subsystem.periodic();
281 if (RobotBase.isSimulation()) {
282 subsystem.simulationPeriodic();
283 }
284 m_watchdog.addEpoch(subsystem.getClass().getSimpleName() + ".periodic()");
285 }
183 // Run the periodic method of all registered subsystems.
184 for (auto&& subsystem : m_impl->subsystems) {
185 subsystem.getFirst()->Periodic();
186 if constexpr (frc::RobotBase::IsSimulation()) {
187 subsystem.getFirst()->SimulationPeriodic();
188 }
189 m_watchdog.AddEpoch("Subsystem Periodic()");
190 }
Paso 2: Activadores de programación de comandos
Nota
Para obtener más información sobre cómo funcionan los enlaces de activadores, consulte Enlazando comandos a Triggers
En segundo lugar, el programador sondea el estado de todos los desencadenantes registrados para ver si se debe programar algún comando nuevo que se haya vinculado a esos desencadenantes. Si se cumplen las condiciones para programar un comando vinculado, se programa el comando y se ejecuta su método Initialize()
.
290 // Poll buttons for new commands to add.
291 loopCache.poll();
292 m_watchdog.addEpoch("buttons.run()");
195 // Poll buttons for new commands to add.
196 loopCache->Poll();
197 m_watchdog.AddEpoch("buttons.Run()");
Paso 3: ejecutar/finalizar comandos programados
En tercer lugar, el planificador llama al método execute()
de cada comando programado actualmente, y luego verifica si el comando ha terminado llamando al método isFinished()
. Si el comando ha finalizado, también se llama al método end()
, y el comando se desprograma y se liberan los subsistemas necesarios.
Tenga en cuenta que esta secuencia de llamadas se realiza en orden para cada comando; por lo tanto, un comando puede tener su método end()
llamado antes de que otro tenga su método execute()
llamado. Los comandos se manejan en el orden en que fueron programados.
295 // Run scheduled commands, remove finished commands.
296 for (Iterator<Command> iterator = m_scheduledCommands.iterator(); iterator.hasNext(); ) {
297 Command command = iterator.next();
298
299 if (!command.runsWhenDisabled() && RobotState.isDisabled()) {
300 command.end(true);
301 for (Consumer<Command> action : m_interruptActions) {
302 action.accept(command);
303 }
304 m_requirements.keySet().removeAll(command.getRequirements());
305 iterator.remove();
306 m_watchdog.addEpoch(command.getName() + ".end(true)");
307 continue;
308 }
309
310 command.execute();
311 for (Consumer<Command> action : m_executeActions) {
312 action.accept(command);
313 }
314 m_watchdog.addEpoch(command.getName() + ".execute()");
315 if (command.isFinished()) {
316 command.end(false);
317 for (Consumer<Command> action : m_finishActions) {
318 action.accept(command);
319 }
320 iterator.remove();
321
322 m_requirements.keySet().removeAll(command.getRequirements());
323 m_watchdog.addEpoch(command.getName() + ".end(false)");
324 }
325 }
201 for (Command* command : m_impl->scheduledCommands) {
202 if (!command->RunsWhenDisabled() && frc::RobotState::IsDisabled()) {
203 Cancel(command);
204 continue;
205 }
206
207 command->Execute();
208 for (auto&& action : m_impl->executeActions) {
209 action(*command);
210 }
211 m_watchdog.AddEpoch(command->GetName() + ".Execute()");
212
213 if (command->IsFinished()) {
214 command->End(false);
215 for (auto&& action : m_impl->finishActions) {
216 action(*command);
217 }
218
219 for (auto&& requirement : command->GetRequirements()) {
220 m_impl->requirements.erase(requirement);
221 }
222
223 m_impl->scheduledCommands.erase(command);
224 m_watchdog.AddEpoch(command->GetName() + ".End(false)");
225 }
226 }
Paso 4: Programar comandos predeterminados
Finalmente, cualquier Subsistema
registrado tiene su comando predeterminado programado (si lo tiene). Tenga en cuenta que en este momento se llamará al método initialize()
del comando predeterminado.
340 // Add default commands for un-required registered subsystems.
341 for (Map.Entry<Subsystem, Command> subsystemCommand : m_subsystems.entrySet()) {
342 if (!m_requirements.containsKey(subsystemCommand.getKey())
343 && subsystemCommand.getValue() != null) {
344 schedule(subsystemCommand.getValue());
345 }
346 }
240 // Add default commands for un-required registered subsystems.
241 for (auto&& subsystem : m_impl->subsystems) {
242 auto s = m_impl->requirements.find(subsystem.getFirst());
243 if (s == m_impl->requirements.end() && subsystem.getSecond()) {
244 Schedule({subsystem.getSecond().get()});
245 }
246 }
Deshabilitar el programador
El planificador se puede desactivar llamando a CommandScheduler.getInstance().disable()
. Cuando está deshabilitado, los comandos schedule()
y run()
del programador no harán nada.
El programador se puede volver a habilitar al llamar a CommandScheduler.getInstance().enable()
.
Métodos de eventos de comando
Occasionally, it is desirable to have the scheduler execute a custom action whenever a certain command event (initialization, execution, or ending) occurs. This can be done with the following methods:
onCommandInitialize
(Java, C++) runs a specified action whenever a command is initialized.onCommandExecute
(Java, C++) runs a specified action whenever a command is executed.onCommandFinish
(Java, C++) runs a specified action whenever a command finishes normally (i.e. theisFinished()
method returned true).onCommandInterrupt
(Java, C++) runs a specified action whenever a command is interrupted (i.e. by being explicitly canceled or by another command that shares one of its requirements).
A typical use-case for these methods is adding markers in an event log whenever a command scheduling event takes place, as demonstrated in the following code from the HatchbotInlined example project (Java, C++):
73 // Set the scheduler to log Shuffleboard events for command initialize, interrupt, finish
74 CommandScheduler.getInstance()
75 .onCommandInitialize(
76 command ->
77 Shuffleboard.addEventMarker(
78 "Command initialized", command.getName(), EventImportance.kNormal));
79 CommandScheduler.getInstance()
80 .onCommandInterrupt(
81 command ->
82 Shuffleboard.addEventMarker(
83 "Command interrupted", command.getName(), EventImportance.kNormal));
84 CommandScheduler.getInstance()
85 .onCommandFinish(
86 command ->
87 Shuffleboard.addEventMarker(
88 "Command finished", command.getName(), EventImportance.kNormal));
23 // Log Shuffleboard events for command initialize, execute, finish, interrupt
24 frc2::CommandScheduler::GetInstance().OnCommandInitialize(
25 [](const frc2::Command& command) {
26 frc::Shuffleboard::AddEventMarker(
27 "Command initialized", command.GetName(),
28 frc::ShuffleboardEventImportance::kNormal);
29 });
30 frc2::CommandScheduler::GetInstance().OnCommandExecute(
31 [](const frc2::Command& command) {
32 frc::Shuffleboard::AddEventMarker(
33 "Command executed", command.GetName(),
34 frc::ShuffleboardEventImportance::kNormal);
35 });
36 frc2::CommandScheduler::GetInstance().OnCommandFinish(
37 [](const frc2::Command& command) {
38 frc::Shuffleboard::AddEventMarker(
39 "Command finished", command.GetName(),
40 frc::ShuffleboardEventImportance::kNormal);
41 });
42 frc2::CommandScheduler::GetInstance().OnCommandInterrupt(
43 [](const frc2::Command& command) {
44 frc::Shuffleboard::AddEventMarker(
45 "Command interrupted", command.GetName(),
46 frc::ShuffleboardEventImportance::kNormal);
47 });