-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCommandLogger.java
More file actions
128 lines (106 loc) · 4.5 KB
/
Copy pathCommandLogger.java
File metadata and controls
128 lines (106 loc) · 4.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package frc.robot.util;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.CommandScheduler;
import edu.wpi.first.wpilibj2.command.ParallelCommandGroup;
import edu.wpi.first.wpilibj2.command.SequentialCommandGroup;
import edu.wpi.first.wpilibj2.command.Subsystem;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;
import org.littletonrobotics.junction.Logger;
/**
* Utility to track active commands and log their "Leaf" (deepest active child) state to the
* Dashboard. Uses Java Reflection to peek inside Sequential and Parallel CommandGroups.
*
* <p>Notably this does not work with Pathplanner autos, as they utilize a different structure. <br>
* <br>
* This is hopefully unnecessary in 2027 with coming WPILib updates.
*/
public class CommandLogger {
private static final CommandLogger instance = new CommandLogger();
private final Set<Command> m_activeCommands = Collections.newSetFromMap(new IdentityHashMap<>());
private CommandLogger() {}
public static CommandLogger getInstance() {
return instance;
}
/** Call this in robotInit() to hook into the CommandScheduler. */
public void init() {
CommandScheduler.getInstance().onCommandInitialize(m_activeCommands::add);
CommandScheduler.getInstance().onCommandInterrupt(this::handleCommandEnding);
CommandScheduler.getInstance().onCommandFinish(this::handleCommandEnding);
}
public void handleCommandEnding(Command endingCommand) {
this.m_activeCommands.remove(endingCommand);
for (Subsystem sub : endingCommand.getRequirements()) {
Logger.recordOutput("Subsystems/" + sub.getName() + "/Command", "Nothing");
}
}
/**
* Updates SmartDashboard with the active command for every subsystem. Call this in
* robotPeriodic().
*/
public void updateTelemetry() {
LoggedTracer.reset(); // reset start time
Map<String, String> subsystemStatus = new HashMap<>();
for (Command topLevelCmd : m_activeCommands) {
String activeLeafName = resolveActiveLeafName(topLevelCmd); // find actual command running
Set<Subsystem> requirements = topLevelCmd.getRequirements();
if (requirements.isEmpty()) {
String status = (activeLeafName == null) ? "Nothing" : activeLeafName;
Logger.recordOutput("ActiveCommands/NoSubsystem/" + topLevelCmd.getName(), status);
} else {
for (Subsystem sub : requirements) {
if (activeLeafName == null) {
subsystemStatus.put(sub.getName(), "Nothing");
continue;
}
subsystemStatus.put(sub.getName(), activeLeafName);
}
}
}
// Publish active commands
for (Map.Entry<String, String> entry : subsystemStatus.entrySet()) {
Logger.recordOutput("Subsystems/" + entry.getKey() + "/Command", entry.getValue());
}
LoggedTracer.record("CommandLogger"); // log time taken
}
/**
* Recursively uses reflection to peel back CommandGroup layers to find the currently running
* child.
*/
private String resolveActiveLeafName(Command command) {
if (command instanceof SequentialCommandGroup) {
try {
Field indexField = SequentialCommandGroup.class.getDeclaredField("m_currentCommandIndex");
indexField.setAccessible(true);
int index = indexField.getInt(command);
Field commandsField = SequentialCommandGroup.class.getDeclaredField("m_commands");
commandsField.setAccessible(true);
@SuppressWarnings("unchecked")
List<Command> commands = (List<Command>) commandsField.get(command);
if (index >= 0 && index < commands.size()) {
return resolveActiveLeafName(commands.get(index));
}
return null;
} catch (Exception e) {
return command.getName() + " (Reflection Error)";
}
} else if (command instanceof ParallelCommandGroup) {
try {
Field commandsField = ParallelCommandGroup.class.getDeclaredField("m_commands");
commandsField.setAccessible(true);
@SuppressWarnings("unchecked")
Map<Command, Boolean> commands = (Map<Command, Boolean>) commandsField.get(command);
// Filter to only show commands where Value is TRUE (Running)
return commands.entrySet().stream()
.filter(Map.Entry::getValue)
.map(entry -> resolveActiveLeafName(entry.getKey())) // Recurse
.collect(Collectors.joining(" + "));
} catch (Exception e) {
return command.getName();
}
}
// Leaf command
return command.getName();
}
}