Skip to content

Commit 6c9b8ae

Browse files
authored
Merge pull request #22 from FRC-7525/Manager
Manager
2 parents 616968b + d23624f commit 6c9b8ae

39 files changed

+1148
-872
lines changed

.github/scripts/visualizeStates.py

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import sys
2+
import re
3+
from pathlib import Path
4+
from graphviz import Digraph
5+
6+
# ---------- Parsing ManagerStates ----------
7+
def get_manager_states(manager_states_path):
8+
text = Path(manager_states_path).read_text(encoding="utf8")
9+
10+
pattern = re.compile(
11+
r"""
12+
(\w+)\s*
13+
\(\s*
14+
"(.*?)"\s*,\s*
15+
([^,]+)\s*,\s*
16+
([^,]+)\s*,\s*
17+
([^,]+)\s*,\s*
18+
([^)]+)
19+
\)
20+
""",
21+
re.VERBOSE | re.DOTALL,
22+
)
23+
24+
states = {}
25+
26+
for m in pattern.finditer(text):
27+
name = m.group(1)
28+
29+
def clean(x: str) -> str:
30+
x = x.strip()
31+
x = re.sub(r".*?\.", "", x)
32+
x = re.sub(r"\(.*\)", "", x)
33+
return x
34+
35+
states[name] = {
36+
"Intake": clean(m.group(3)),
37+
"Hopper": clean(m.group(4)),
38+
"Shooter": clean(m.group(5)),
39+
"Climber": clean(m.group(6)),
40+
"connectionsTo": [],
41+
}
42+
43+
return states
44+
45+
# ---------- Parsing Triggers ----------
46+
def get_triggers(manager_path):
47+
file = Path(manager_path).read_text(encoding="utf8")
48+
return re.findall(r"addTrigger\([\s\S]*?\);", file)
49+
50+
def parse_triggers(triggers):
51+
parsed = []
52+
53+
for t in triggers:
54+
m = re.search(
55+
r"""
56+
addTrigger\(
57+
\s*(?:\w+\.)?(\w+)\s*, # from
58+
\s*(?:\w+\.)?(\w+)\s*, # to
59+
\s*(.*?) # condition (FULL)
60+
\)\s*;
61+
""",
62+
t,
63+
re.VERBOSE | re.DOTALL,
64+
)
65+
if not m:
66+
continue
67+
68+
condition = m.group(3).strip()
69+
70+
# Remove lambda wrapper
71+
condition = re.sub(r"^\(\)\s*->\s*", "", condition)
72+
73+
# Remove controller prefixes
74+
condition = re.sub(r"(?:DRIVER|OPERATOR)_CONTROLLER::get", "", condition)
75+
condition = re.sub(r".*?::", "", condition)
76+
77+
parsed.append({
78+
"from": m.group(1),
79+
"to": m.group(2),
80+
"condition": condition,
81+
})
82+
83+
return parsed
84+
85+
def attach_triggers(state_map, triggers):
86+
for t in triggers:
87+
if t["from"] in state_map:
88+
state_map[t["from"]]["connectionsTo"].append(t)
89+
90+
# ---------- Graphviz helpers ----------
91+
def node_id(name: str) -> str:
92+
return re.sub(r"\W+", "_", name)
93+
94+
EDGE_COLORS = [
95+
"#f6e05e", "#68d391", "#63b3ed", "#fc8181",
96+
"#90cdf4", "#faf089", "#fbb6ce", "#9f7aea"
97+
]
98+
99+
# ---------- Visualization ----------
100+
def generate_graph(state_map):
101+
dot = Digraph("StateMachine", format="png", engine="dot")
102+
103+
dot.attr(
104+
rankdir="TB",
105+
bgcolor="#1e1e2f",
106+
fontname="Helvetica",
107+
nodesep="0.6",
108+
ranksep="0.9",
109+
)
110+
111+
dot.attr(
112+
"node",
113+
shape="box",
114+
style="rounded,filled",
115+
fillcolor="#2d2d3c",
116+
color="#68d391",
117+
fontcolor="white",
118+
fontname="Helvetica-Bold",
119+
fontsize="11",
120+
penwidth="1.4",
121+
)
122+
123+
dot.attr(
124+
"edge",
125+
fontname="Helvetica",
126+
fontsize="9",
127+
arrowsize="0.8",
128+
)
129+
130+
# Nodes
131+
for state, info in state_map.items():
132+
lines = [
133+
f"{state}",
134+
"────────────",
135+
f"Intake → {info['Intake']}",
136+
f"Hopper → {info['Hopper']}",
137+
f"Shooter → {info['Shooter']}",
138+
f"Climber → {info['Climber']}",
139+
]
140+
label = "\n".join(lines)
141+
142+
if state == "IDLE":
143+
dot.node(
144+
node_id(state),
145+
label=label,
146+
fillcolor="#3e2c1c",
147+
color="#f6ad55",
148+
penwidth="2.5",
149+
)
150+
else:
151+
dot.node(node_id(state), label=label)
152+
153+
# Edges
154+
color_index = 0
155+
for state, info in state_map.items():
156+
if not info["connectionsTo"]:
157+
continue
158+
159+
color = EDGE_COLORS[color_index % len(EDGE_COLORS)]
160+
color_index += 1
161+
162+
for c in info["connectionsTo"]:
163+
dot.edge(
164+
node_id(state),
165+
node_id(c["to"]),
166+
label=c["condition"],
167+
color=color,
168+
fontcolor=color,
169+
penwidth="1.8",
170+
)
171+
172+
dot.render("state_machine", cleanup=True)
173+
print("Rendered state_machine.png")
174+
175+
# ---------- Main ----------
176+
def main(managerStatesPath, managerPath):
177+
state_map = get_manager_states(managerStatesPath)
178+
triggers = parse_triggers(get_triggers(managerPath))
179+
attach_triggers(state_map, triggers)
180+
generate_graph(state_map)
181+
print("Success!")
182+
183+
if __name__ == "__main__":
184+
main(sys.argv[1], sys.argv[2])
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: Visualize States
2+
3+
on:
4+
pull_request:
5+
types:
6+
- reopened
7+
- opened
8+
- synchronize
9+
10+
jobs:
11+
generate-graph:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout repo
15+
uses: actions/checkout@v3
16+
17+
- name: Install system dependencies
18+
run: |
19+
sudo apt-get update
20+
sudo apt-get install -y graphviz
21+
22+
- name: Set up Python
23+
uses: actions/setup-python@v4
24+
with:
25+
python-version: '3.x'
26+
27+
- name: Install dependencies
28+
run: |
29+
pip install graphviz
30+
31+
- name: Run graph generation
32+
id: run-script
33+
run: |
34+
python3 .github/scripts/visualizeStates.py ./src/main/java/frc/robot/Subsystems/Manager/ManagerStates.java ./src/main/java/frc/robot/Subsystems/Manager/Manager.java
35+
echo "exit_code=$?" >> $GITHUB_OUTPUT
36+
continue-on-error: true
37+
38+
- name: Upload graph as artifact
39+
uses: actions/upload-artifact@v4
40+
with:
41+
name: state-machine-graph
42+
path: state_machine.png
43+
44+
- name: Comment on PR
45+
uses: actions/github-script@v6
46+
with:
47+
script: |
48+
const prNumber = context.payload.pull_request.number;
49+
const exitCode = Number(process.env.EXIT_CODE);
50+
const commentBody = exitCode === 0
51+
? "✅ State machine graph generated and uploaded."
52+
: "⚠️ Something went wrong while generating the state machine graph.";
53+
54+
github.rest.issues.createComment({
55+
issue_number: prNumber,
56+
owner: context.repo.owner,
57+
repo: context.repo.repo,
58+
body: commentBody
59+
})
60+
env:
61+
EXIT_CODE: ${{ steps.run-script.outputs.exit_code }}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
hmm
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
hmmmmmm

Other/Sim/GoatedSimConfig.json

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
{
2+
"Docking": {
3+
"Data": [
4+
"DockNode ID=0x00000002 Pos=-2,2 Size=1926,1098 Split=X",
5+
"DockNode ID=0x00000001 Parent=0x00000002 SizeRef=1153,1095 Split=Y",
6+
"DockNode ID=0x0000000F Parent=0x00000001 SizeRef=991,210 Split=X",
7+
"DockNode ID=0x0000000D Parent=0x0000000F SizeRef=519,140 Split=X",
8+
"DockNode ID=0x0000000B Parent=0x0000000D SizeRef=257,140 Selected=0x39AD1970",
9+
"DockNode ID=0x0000000C Parent=0x0000000D SizeRef=280,140 Selected=0xD58031C5",
10+
"DockNode ID=0x0000000E Parent=0x0000000F SizeRef=632,140 Selected=0x513EDE86",
11+
"DockNode ID=0x00000010 Parent=0x00000001 SizeRef=991,883 Split=Y Selected=0xB5CBE9F0",
12+
"DockNode ID=0x00000012 Parent=0x00000010 SizeRef=1153,233 Split=Y Selected=0xF9AD29AD",
13+
"DockNode ID=0x00000014 Parent=0x00000012 SizeRef=1155,199 Selected=0x5446CBB7",
14+
"DockNode ID=0x00000015 Parent=0x00000012 SizeRef=1155,240 Selected=0xF9AD29AD",
15+
"DockNode ID=0x00000013 Parent=0x00000010 SizeRef=1153,650 Selected=0xB5CBE9F0",
16+
"DockNode ID=0x00000011 Parent=0x00000002 SizeRef=767,1095 Split=Y",
17+
"DockNode ID=0x00000007 Parent=0x00000011 SizeRef=313,611 Split=X Selected=0x3E3C37F1",
18+
"DockNode ID=0x00000003 Parent=0x00000007 SizeRef=309,746 Selected=0x3E3C37F1",
19+
"DockNode ID=0x00000004 Parent=0x00000007 SizeRef=458,746 Selected=0x2D9B9756",
20+
"DockNode ID=0x00000008 Parent=0x00000011 SizeRef=313,287 Split=Y Selected=0x6AE6D891",
21+
"DockNode ID=0x00000005 Parent=0x00000008 SizeRef=313,32 Split=X Selected=0x6AE6D891",
22+
"DockNode ID=0x00000009 Parent=0x00000005 SizeRef=295,176 Selected=0x6AE6D891",
23+
"DockNode ID=0x0000000A Parent=0x00000005 SizeRef=472,176 Selected=0xC8493FD8",
24+
"DockNode ID=0x00000006 Parent=0x00000008 SizeRef=313,316 Selected=0x138219D4"
25+
]
26+
},
27+
"MainWindow": {
28+
"GLOBAL": {
29+
"font": "Roboto Bold",
30+
"fps": "120",
31+
"height": "1051",
32+
"maximized": "1",
33+
"style": "3",
34+
"userScale": "2",
35+
"width": "1920",
36+
"xpos": "0",
37+
"ypos": "29"
38+
}
39+
},
40+
"Window": {
41+
"###/AdvantageKit/RealOutputs/Alerts": {
42+
"Collapsed": "0",
43+
"Pos": "137,171",
44+
"Size": "300,148"
45+
},
46+
"###/FMSInfo": {
47+
"Collapsed": "0",
48+
"Pos": "60,60",
49+
"Size": "168,206"
50+
},
51+
"###/SmartDashboard/Auto Chooser": {
52+
"Collapsed": "0",
53+
"DockId": "0x0000000E,0",
54+
"Pos": "521,20",
55+
"Size": "633,211"
56+
},
57+
"###/SmartDashboard/Field": {
58+
"Collapsed": "0",
59+
"DockId": "0x00000013,0",
60+
"Pos": "-2,450",
61+
"Size": "1155,650"
62+
},
63+
"###/SmartDashboard/VisionSystemSim-Vision/Sim Field": {
64+
"Collapsed": "0",
65+
"DockId": "0x00000015,0",
66+
"Pos": "-1,233",
67+
"Size": "1155,441"
68+
},
69+
"###/SmartDashboard/VisionSystemSim-main/Sim Field": {
70+
"Collapsed": "0",
71+
"DockId": "0x00000014,0",
72+
"Pos": "-2,215",
73+
"Size": "1155,233"
74+
},
75+
"###FMS": {
76+
"Collapsed": "0",
77+
"DockId": "0x0000000A,0",
78+
"Pos": "1452,750",
79+
"Size": "472,32"
80+
},
81+
"###Joysticks": {
82+
"Collapsed": "0",
83+
"DockId": "0x00000006,0",
84+
"Pos": "1155,784",
85+
"Size": "769,316"
86+
},
87+
"###Keyboard 0 Settings": {
88+
"Collapsed": "0",
89+
"DockId": "0x00000003,0",
90+
"Pos": "1156,20",
91+
"Size": "309,746"
92+
},
93+
"###NetworkTables": {
94+
"Collapsed": "0",
95+
"DockId": "0x00000004,0",
96+
"Pos": "1155,2",
97+
"Size": "769,746"
98+
},
99+
"###NetworkTables Info": {
100+
"Collapsed": "0",
101+
"Pos": "939,136",
102+
"Size": "681,492"
103+
},
104+
"###Other Devices": {
105+
"Collapsed": "0",
106+
"Pos": "558,386",
107+
"Size": "312,1173"
108+
},
109+
"###Plot <0>": {
110+
"Collapsed": "0",
111+
"Pos": "65,61",
112+
"Size": "700,400"
113+
},
114+
"###PowerDistributions": {
115+
"Collapsed": "0",
116+
"Pos": "245,155",
117+
"Size": "200,456"
118+
},
119+
"###System Joysticks": {
120+
"Collapsed": "0",
121+
"DockId": "0x00000009,0",
122+
"Pos": "1155,750",
123+
"Size": "295,32"
124+
},
125+
"###Timing": {
126+
"Collapsed": "0",
127+
"DockId": "0x0000000C,0",
128+
"Pos": "552,2",
129+
"Size": "601,211"
130+
},
131+
"Debug##Default": {
132+
"Collapsed": "0",
133+
"Pos": "60,60",
134+
"Size": "400,400"
135+
},
136+
"Robot State": {
137+
"Collapsed": "0",
138+
"DockId": "0x0000000B,0",
139+
"Pos": "-2,2",
140+
"Size": "552,211"
141+
}
142+
}
143+
}

0 commit comments

Comments
 (0)