Skip to content

Commit 09da331

Browse files
shriishwaryaanevalsar
authored andcommitted
Modified the content in /wiki/tools/ros-gui.md
1 parent 482b2de commit 09da331

File tree

9 files changed

+367
-0
lines changed

9 files changed

+367
-0
lines changed

_data/navigation.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,8 @@ wiki:
275275
url: /wiki/tools/tmux/
276276
- title: udev Rules
277277
url: /wiki/tools/udev-rules/
278+
- title: ROS GUI
279+
url: /wiki/tools/ros-gui/
278280
- title: Rosbags in Matlab
279281
url: /wiki/tools/rosbags-matlab/
280282
- title: Making Field Testing Easier through Visualization and Simulation

wiki/tools/assets/PyQt-final.png

74 KB
Loading

wiki/tools/assets/PyQt-green.png

41.7 KB
Loading

wiki/tools/assets/PyQt-init.png

40 KB
Loading

wiki/tools/assets/PyQt-logs.png

54.8 KB
Loading

wiki/tools/assets/PyQt-orange.png

43 KB
Loading

wiki/tools/assets/PyQt-red.png

42.4 KB
Loading

wiki/tools/assets/PyQt-yellow.png

42.3 KB
Loading

wiki/tools/ros-gui.md

Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
---
2+
# Jekyll 'Front Matter' goes here. Most are set by default, and should NOT be
3+
# overwritten except in special circumstances.
4+
# You should set the date the article was last updated like this:
5+
date: 2023-05-02 # YYYY-MM-DD
6+
# This will be displayed at the bottom of the article
7+
# You should set the article's title:
8+
title: Framework GUI for robotic system using ROS
9+
# The 'title' is automatically displayed at the top of the page
10+
# and used in other parts of the site.
11+
---
12+
PyQt is an easy to implement user interface that can be integrated with ROS. It is customizable and can be deployed on multiple platforms. This documentation provides a basic GUI framework that all robotic systems using ROS can use and build upon.
13+
14+
## Requirements
15+
16+
- This application assumes your workspace has ROS installed. If not, use [this link](http://wiki.ros.org/ROS/Installation) for installing ROS.
17+
18+
- This application can be used on any Operating System including Linux, Windows and Mac.
19+
20+
- This application can be used with ROS1 and ROS2.
21+
22+
23+
### Installation
24+
25+
To use PyQt5, open a terminal and type the following command
26+
27+
```bash
28+
$ pip install PyQt5
29+
```
30+
31+
## Overview of Final Application
32+
33+
This is the final application you can get by following this tutorial. Blocks that require customization will be explained in the documentation.
34+
35+
![Final QT](assets/PyQt-final.png)
36+
37+
## Application Walkthrough
38+
39+
### Timers
40+
41+
The application has two timers
42+
43+
1. System level timer: allows you to keep track of your entire system's operation time.
44+
45+
2. Task level timer: allows you to keep track of a specific task's operation time.
46+
47+
### System Level Timer
48+
49+
The system level timer captures the time taken between the start and the end of your system. This can be done by pressing the 'Start System Timer' button. Clicking it once begins the timer and clicking it again stops the timer. The color of the timer will change based on the ` color_change_times ` and ` color_change_colors` defined in the full code at the bottom of this tutorial. This can be modified to suit your system's requirements.
50+
51+
The color change for the timers are as follows:
52+
53+
Green Button
54+
![Green Button](assets/PyQt-green.png)
55+
Yellow Button
56+
![Yellow Button](assets/PyQt-yellow.png)
57+
Orange Button
58+
![Orange Button](assets/PyQt-orange.png)
59+
Red Button
60+
![Red Button](assets/PyQt-red.png)
61+
62+
The code block for this is given here:
63+
```
64+
def SystemTimerBlock(self):
65+
self.system_count = 0
66+
self.system_start = False
67+
self.system_end = False
68+
69+
self.system_timer_button = QPushButton("Start System Timer", self)
70+
self.system_timer_button.setFixedSize(self.win.frameGeometry().width(),self.win.frameGeometry().height()//4)
71+
self.grid.addWidget(self.system_timer_button, 0, 2)
72+
self.system_timer_button.clicked.connect(self.system_timer_controller)
73+
74+
system_timer = QTimer(self)
75+
system_timer.timeout.connect(self.system_timer)
76+
system_timer.start(1000) # modify to match system needs
77+
```
78+
79+
### Task Level Timer
80+
81+
The task level timer captures the time taken between the start and the end of one task. This can be done by pressing the 'Start Task Timer' button. This button also changes color when the time taken to perform a task is reaching the total allowed time.
82+
83+
One additional feature of the task level timer is it saves the task logs. These are outputted in the `Task Times` block that is directly below the button.
84+
85+
![Red Button](assets/PyQt-logs.png)
86+
87+
```
88+
def TaskTimerBlock(self):
89+
self.task_times = {}
90+
self.task_count = 0
91+
self.task_start = False
92+
self.task_end = False
93+
94+
self.task_timer_button = QPushButton("Start Task Timer", self)
95+
self.task_timer_button.setFixedSize(self.win.frameGeometry().width(),self.win.frameGeometry().height()//4)
96+
self.grid.addWidget(self.task_timer_button, 1, 2)
97+
self.task_timer_button.clicked.connect(self.task_timer_controller)
98+
99+
task_timer = QTimer(self)
100+
task_timer.timeout.connect(self.task_timer)
101+
task_timer.start(1000) # modify to match system needs
102+
103+
self.task_times_label = QLabel("Task Times", self)
104+
self.grid.addWidget(self.task_times_label, 2, 2, 2, 1)
105+
self.task_times_label.setStyleSheet("border : 3px solid black")
106+
self.task_times_label.setFont(QFont('Times', 10))
107+
self.task_times_label.setAlignment(Qt.AlignCenter)
108+
```
109+
110+
### E-Stop Button
111+
112+
The E-Stop button is a ROS publisher that will publish to a topic which can then be used by your system's code to stop the robot.
113+
114+
You need to change the following code
115+
```
116+
self.estop_pub = rospy.Publisher('/mrsd/estop', Bool, queue_size=10)
117+
```
118+
and customize it for your system. Your ROS system's main code should have an e-stop subscriber that will shut down the entire system once the button is pressed.
119+
120+
121+
### System Status
122+
123+
The system status block subscribes to the following topic.
124+
```
125+
self.status_sub = rospy.Subscriber('/mrsd/status', String, self.status_callback)
126+
```
127+
Thus your main system should publish to a `/mrsd/status` topic. Ideally, your state machine will have a topic publisher that this application can subscribe to.
128+
129+
### System Log
130+
131+
This block is left for you to customize and display any other useful information. It subscribes to
132+
133+
```
134+
self.system_log_sub = rospy.Subscriber('/mrsd/system_log', String, self.system_log_callback)
135+
```
136+
137+
and you need to add some code to format the output. For example, this section could be used to display some of these information
138+
139+
1. How many peppers you have harvested
140+
2. How many people you have saved
141+
3. What process are up next
142+
143+
144+
### Visualization Block
145+
146+
The visualization block can be used to display any results visually. Use cases can be
147+
148+
1. Robot localization within the map
149+
2. 3D point clouds
150+
3. Object detection results
151+
152+
### Entire code
153+
<details>
154+
<summary>pyqt-ros.py</summary>
155+
156+
```
157+
# importing libraries
158+
from PyQt5.QtWidgets import *
159+
from PyQt5.QtGui import *
160+
from PyQt5.QtCore import *
161+
import sys, emoji, rospy
162+
from PyQt5.QtGui import QPixmap
163+
164+
# system level requirements
165+
total_demo_time = 60*20 # assuming SVD is 20 minutes
166+
one_task_max = 60 # assuming each task is 60 seconds
167+
color_change_times = [0.25, 0.5, 0.75, 1.0]
168+
color_change_colors = ['green', 'yellow', 'orange', 'red']
169+
170+
gui_x, gui_y = 700, 600
171+
172+
class Window(QMainWindow):
173+
def __init__(self):
174+
super().__init__()
175+
self.setWindowTitle("Python ")
176+
self.win = QWidget()
177+
self.grid = QGridLayout()
178+
179+
self.UiComponents()
180+
self.win.setLayout(self.grid)
181+
self.win.setGeometry(0, 0, gui_x, gui_y)
182+
self.win.show()
183+
184+
185+
# self.status_sub = rospy.Subscriber('/mrsd/status', String, self.status_callback)
186+
# self.estop_pub = rospy.Publisher('/mrsd/estop', Bool, queue_size=10)
187+
# self.system_log_sub = rospy.Subscriber('/mrsd/system_log', String, self.system_log_callback)
188+
189+
def SystemTimerBlock(self):
190+
self.system_count = 0
191+
self.system_start = False
192+
self.system_end = False
193+
194+
self.system_timer_button = QPushButton("Start System Timer", self)
195+
self.system_timer_button.setFixedSize(self.win.frameGeometry().width(),self.win.frameGeometry().height()//4)
196+
self.grid.addWidget(self.system_timer_button, 0, 2)
197+
self.system_timer_button.clicked.connect(self.system_timer_controller)
198+
199+
system_timer = QTimer(self)
200+
system_timer.timeout.connect(self.system_timer)
201+
system_timer.start(1000) # modify to match system needs
202+
203+
def TaskTimerBlock(self):
204+
self.task_times = {}
205+
self.task_count = 0
206+
self.task_start = False
207+
self.task_end = False
208+
209+
self.task_timer_button = QPushButton("Start Task Timer", self)
210+
self.task_timer_button.setFixedSize(self.win.frameGeometry().width(),self.win.frameGeometry().height()//4)
211+
self.grid.addWidget(self.task_timer_button, 1, 2)
212+
self.task_timer_button.clicked.connect(self.task_timer_controller)
213+
214+
task_timer = QTimer(self)
215+
task_timer.timeout.connect(self.task_timer)
216+
task_timer.start(1000) # modify to match system needs
217+
218+
self.task_times_label = QLabel("Task Times", self)
219+
self.grid.addWidget(self.task_times_label, 2, 2, 2, 1)
220+
self.task_times_label.setStyleSheet("border : 3px solid black")
221+
self.task_times_label.setFont(QFont('Times', 10))
222+
self.task_times_label.setAlignment(Qt.AlignCenter)
223+
224+
def EStopBlock(self):
225+
self.estop_button = QPushButton("E-Stop", self)
226+
self.estop_button.setStyleSheet("background-color: red; border-radius: 15px")
227+
self.estop_button.setFixedWidth(self.win.frameGeometry().width())
228+
self.estop_button.setFixedHeight(self.win.frameGeometry().height()//4)
229+
self.grid.addWidget(self.estop_button, 3, 0, 1, 1)
230+
self.estop_button.clicked.connect(self.estop_button_clicked)
231+
232+
def SystemLogsBlock(self):
233+
self.system_logs = QLabel("System Logs", self)
234+
self.grid.addWidget(self.system_logs, 1, 0, 2, 1)
235+
self.system_logs.setStyleSheet("border : 3px solid black")
236+
self.system_logs.setFont(QFont('Times', 8))
237+
self.system_logs.setAlignment(Qt.AlignCenter)
238+
239+
def VisualizationBlock(self):
240+
self.pixmap = QPixmap('turtlesim.png')
241+
self.image_label = QLabel(self)
242+
self.image_label.setPixmap(self.pixmap)
243+
self.image_label.setStyleSheet("border : 3px solid black")
244+
self.grid.addWidget(self.image_label, 1, 1, 3, 1)
245+
246+
def SystemStatusBlock(self):
247+
self.system_status = QLabel("System Status", self)
248+
self.system_status.setStyleSheet("border : 3px solid black")
249+
self.system_status.setFont(QFont('Times', 10))
250+
self.system_status.setAlignment(Qt.AlignCenter)
251+
self.grid.addWidget(self.system_status, 0, 0, 1, 2)
252+
253+
def UiComponents(self):
254+
self.SystemTimerBlock()
255+
self.TaskTimerBlock()
256+
self.EStopBlock()
257+
self.SystemLogsBlock()
258+
self.VisualizationBlock()
259+
self.SystemStatusBlock()
260+
261+
262+
def format_time(self, seconds):
263+
return f'{seconds // 60} Minutes {seconds % 60} Seconds'
264+
def change_system_color(self):
265+
if self.system_count/total_demo_time < color_change_times[0]:
266+
color = color_change_colors[0]
267+
elif self.system_count/total_demo_time < color_change_times[1]:
268+
color = color_change_colors[1]
269+
elif self.system_count/total_demo_time < color_change_times[2]:
270+
color = color_change_colors[2]
271+
else:
272+
color = color_change_colors[3]
273+
self.system_timer_button.setStyleSheet(f"background-color: {color}")
274+
def system_timer(self):
275+
if self.system_start == True:
276+
self.system_count += 1
277+
text = self.format_time(self.system_count)
278+
self.system_timer_button.setText(text)
279+
self.change_system_color()
280+
281+
if self.system_end == True:
282+
self.system_start = False
283+
self.system_end = False
284+
def system_timer_controller(self):
285+
if self.system_start == False:
286+
self.system_start = True
287+
self.system_end = False
288+
else:
289+
self.system_start = False
290+
self.system_end = True
291+
self.system_timer_button.setText("Start System Timer")
292+
def change_task_color(self):
293+
if self.task_count/one_task_max < color_change_times[0]:
294+
color = color_change_colors[0]
295+
emoji = '😀'
296+
elif self.task_count/one_task_max < color_change_times[1]:
297+
color = color_change_colors[1]
298+
emoji = '😐'
299+
elif self.task_count/one_task_max < color_change_times[2]:
300+
color = color_change_colors[2]
301+
emoji = '😕'
302+
else:
303+
color = color_change_colors[3]
304+
emoji = '😡'
305+
self.task_timer_button.setStyleSheet(f"background-color: {color}")
306+
return emoji
307+
308+
def task_timer(self):
309+
if self.task_start == True:
310+
self.task_count += 1
311+
text = self.format_time(self.task_count)
312+
self.task_timer_button.setText(text)
313+
self.change_task_color()
314+
315+
if self.task_end == True:
316+
self.task_start = False
317+
self.task_end = False
318+
self.task_times[len(self.task_times)] = (self.task_count, self.change_task_color())
319+
self.task_count = 0
320+
self.task_times_label.setText(self.timer_label_format())
321+
def task_timer_controller(self):
322+
if self.task_start == False:
323+
self.task_start = True
324+
self.task_end = False
325+
else:
326+
self.task_start = False
327+
self.task_end = True
328+
self.task_timer_button.setText("Start Task Timer")
329+
def timer_label_format(self):
330+
text = ""
331+
for i in range(len(self.task_times)):
332+
text += f"[{i+1}]:{self.task_times[i][1]}: {self.format_time(self.task_times[i][0])}\n"
333+
return text
334+
def estop_button_clicked(self):
335+
print("estop clicked")
336+
# self.estop_pub.publish(True)
337+
338+
def status_callback(self, msg):
339+
# updates the system status when status information is received
340+
if msg.data != '':
341+
self.system_status_label.setText(msg.data)
342+
343+
def system_output_callback(self, msg):
344+
# updates the system output when system information is received
345+
# should modify to display the system output in a more readable format for each team
346+
if msg.data != '':
347+
self.system_logs.setText(msg.data)
348+
349+
App = QApplication(sys.argv)
350+
window = Window()
351+
sys.exit(App.exec())
352+
```
353+
354+
</details>
355+
356+
357+
## Summary
358+
This article provides a walkthrough of basic code that uses PyQt5 for development of a GUI that is integrated with ROS. It highlights parts that need to be modified in your system level ROS code as well as suggests possible modifications.
359+
360+
## See Also:
361+
- [PyQt5 Official Documentation](https://doc.qt.io/qtforpython-5/)
362+
363+
## Further Reading
364+
- [PyQt5 Official Documentation](https://doc.qt.io/qtforpython-5/)
365+

0 commit comments

Comments
 (0)