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 import os
29 import rospy
30 import rospkg
31
32 from qt_gui.plugin import Plugin
33 from python_qt_binding import loadUi
34 from python_qt_binding.QtCore import QTimer, Signal
35 from python_qt_binding.QtGui import QWidget, QFormLayout
36
37 from control_msgs.msg import JointTrajectoryControllerState
38 from controller_manager_msgs.utils\
39 import ControllerLister, ControllerManagerLister
40 from trajectory_msgs.msg import JointTrajectory, JointTrajectoryPoint
41
42 from double_editor import DoubleEditor
43 from joint_limits_urdf import get_joint_limits
44 from update_combo import update_combo
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
71 """
72 Graphical frontend for a C{JointTrajectoryController}.
73
74 There are two modes for interacting with a controller:
75 1. B{Monitor mode} Joint displays are updated with the state reported
76 by the controller. This is a read-only mode and does I{not} send
77 control commands. Every time a new controller is selected, it starts
78 in monitor mode for safety reasons.
79
80 2. B{Control mode} Joint displays update the control command that is
81 sent to the controller. Commands are sent periodically evan if the
82 displays are not being updated by the user.
83
84 To control the aggressiveness of the motions, the maximum speed of the
85 sent commands can be scaled down using the C{Speed scaling control}
86
87 This plugin is able to detect and keep track of all active controller
88 managers, as well as the JointTrajectoryControllers that are I{running}
89 in each controller manager.
90 For a controller to be compatible with this plugin, it must comply with
91 the following requisites:
92 - The controller type contains the C{JointTrajectoryController}
93 substring, e.g., C{position_controllers/JointTrajectoryController}
94 - The controller exposes the C{command} and C{state} topics in its
95 ROS interface.
96
97 Additionally, there must be a URDF loaded with a valid joint limit
98 specification, namely position (when applicable) and velocity limits.
99
100 A reference implementation of the C{JointTrajectoryController} is
101 available in the C{joint_trajectory_controller} ROS package.
102 """
103 _cmd_pub_freq = 10.0
104 _widget_update_freq = 30.0
105 _ctrlrs_update_freq = 1
106 _min_traj_dur = 5.0 / _cmd_pub_freq
107
108 jointStateChanged = Signal([dict])
109
111 super(JointTrajectoryController, self).__init__(context)
112 self.setObjectName('JointTrajectoryController')
113
114
115
116 self._widget = QWidget()
117 rp = rospkg.RosPack()
118 ui_file = os.path.join(rp.get_path('rqt_joint_trajectory_controller'),
119 'resource',
120 'joint_trajectory_controller.ui')
121 loadUi(ui_file, self._widget)
122 self._widget.setObjectName('JointTrajectoryControllerUi')
123
124
125 speed_scaling = DoubleEditor(1.0, 100.0)
126 speed_scaling.spin_box.setSuffix('%')
127 speed_scaling.spin_box.setValue(50.0)
128 speed_scaling.spin_box.setDecimals(0)
129 speed_scaling.setEnabled(False)
130 self._widget.speed_scaling_layout.addWidget(speed_scaling)
131 self._speed_scaling_widget = speed_scaling
132 speed_scaling.valueChanged.connect(self._on_speed_scaling_change)
133 self._on_speed_scaling_change(speed_scaling.value())
134
135
136
137
138
139
140 if context.serial_number() > 1:
141 self._widget.setWindowTitle(self._widget.windowTitle() +
142 (' (%d)' % context.serial_number()))
143
144 context.add_widget(self._widget)
145
146
147 self._jtc_name = []
148 self._cm_ns = []
149 self._joint_pos = {}
150 self._joint_names = []
151 self._robot_joint_limits = {}
152
153
154 self._update_cmd_timer = QTimer(self)
155 self._update_cmd_timer.setInterval(1000.0 / self._cmd_pub_freq)
156 self._update_cmd_timer.timeout.connect(self._update_cmd_cb)
157
158
159 self._update_act_pos_timer = QTimer(self)
160 self._update_act_pos_timer.setInterval(1000.0 /
161 self._widget_update_freq)
162 self._update_act_pos_timer.timeout.connect(self._update_joint_widgets)
163
164
165 self._list_cm = ControllerManagerLister()
166 self._update_cm_list_timer = QTimer(self)
167 self._update_cm_list_timer.setInterval(1000.0 /
168 self._ctrlrs_update_freq)
169 self._update_cm_list_timer.timeout.connect(self._update_cm_list)
170 self._update_cm_list_timer.start()
171
172
173 self._update_jtc_list_timer = QTimer(self)
174 self._update_jtc_list_timer.setInterval(1000.0 /
175 self._ctrlrs_update_freq)
176 self._update_jtc_list_timer.timeout.connect(self._update_jtc_list)
177 self._update_jtc_list_timer.start()
178
179
180 w = self._widget
181 w.enable_button.toggled.connect(self._on_jtc_enabled)
182 w.jtc_combo.currentIndexChanged[str].connect(self._on_jtc_change)
183 w.cm_combo.currentIndexChanged[str].connect(self._on_cm_change)
184
185 self._cmd_pub = None
186 self._state_sub = None
187
188 self._list_controllers = None
189
191 self._update_cmd_timer.stop()
192 self._update_act_pos_timer.stop()
193 self._update_cm_list_timer.stop()
194 self._update_jtc_list_timer.stop()
195 self._unregister_state_sub()
196 self._unregister_cmd_pub()
197
199 instance_settings.set_value('cm_ns', self._cm_ns)
200 instance_settings.set_value('jtc_name', self._jtc_name)
201
203
204 self._update_cm_list()
205 cm_ns = instance_settings.value('cm_ns')
206 cm_combo = self._widget.cm_combo
207 cm_list = [cm_combo.itemText(i) for i in range(cm_combo.count())]
208 try:
209 idx = cm_list.index(cm_ns)
210 cm_combo.setCurrentIndex(idx)
211
212 self._update_jtc_list()
213 jtc_name = instance_settings.value('jtc_name')
214 jtc_combo = self._widget.jtc_combo
215 jtc_list = [jtc_combo.itemText(i) for i in range(jtc_combo.count())]
216 try:
217 idx = jtc_list.index(jtc_name)
218 jtc_combo.setCurrentIndex(idx)
219 except (ValueError):
220 pass
221 except (ValueError):
222 pass
223
224
225
226
227
228
229
232
234
235 if not self._list_controllers:
236 self._widget.jtc_combo.clear()
237 return
238
239
240
241 running_jtc = self._running_jtc_info()
242 if running_jtc and not self._robot_joint_limits:
243 self._robot_joint_limits = get_joint_limits()
244 valid_jtc = []
245 for jtc_info in running_jtc:
246 has_limits = all(name in self._robot_joint_limits
247 for name in _jtc_joint_names(jtc_info))
248 if has_limits:
249 valid_jtc.append(jtc_info);
250 valid_jtc_names = [data.name for data in valid_jtc]
251
252
253 update_combo(self._widget.jtc_combo, sorted(valid_jtc_names))
254
256 self._speed_scale = val / self._speed_scaling_widget.slider.maximum()
257
259 assert(len(actual_pos) == len(self._joint_pos))
260 for name in actual_pos.keys():
261 self._joint_pos[name]['position'] = actual_pos[name]
262
264 self._cm_ns = cm_ns
265 if cm_ns:
266 self._list_controllers = ControllerLister(cm_ns)
267
268
269
270 self._widget.jtc_combo.clear()
271 self._update_jtc_list()
272 else:
273 self._list_controllers = None
274
276 self._unload_jtc()
277 self._jtc_name = jtc_name
278 if self._jtc_name:
279 self._load_jtc()
280
282
283 if not self._jtc_name:
284 self._widget.enable_button.setChecked(False)
285 return
286
287
288 for joint_widget in self._joint_widgets():
289 joint_widget.setEnabled(val)
290
291
292 self._speed_scaling_widget.setEnabled(val)
293
294 if val:
295
296 self._update_act_pos_timer.stop()
297 self._update_cmd_timer.start()
298 else:
299
300 self._update_cmd_timer.stop()
301 self._update_act_pos_timer.start()
302
304
305 running_jtc = self._running_jtc_info()
306 self._joint_names = next(_jtc_joint_names(x) for x in running_jtc
307 if x.name == self._jtc_name)
308 for name in self._joint_names:
309 self._joint_pos[name] = {}
310
311
312 try:
313 layout = self._widget.joint_group.layout()
314 for name in self._joint_names:
315 limits = self._robot_joint_limits[name]
316 joint_widget = DoubleEditor(limits['min_position'],
317 limits['max_position'])
318 layout.addRow(name, joint_widget)
319
320
321
322
323 from functools import partial
324 par = partial(self._update_single_cmd_cb, name=name)
325 joint_widget.valueChanged.connect(par)
326 except:
327
328 from sys import exc_info
329 print 'Unexpected error:', exc_info()[0]
330
331
332 self._on_jtc_enabled(False)
333
334
335 jtc_ns = _resolve_controller_ns(self._cm_ns, self._jtc_name)
336 state_topic = jtc_ns + '/state'
337 cmd_topic = jtc_ns + '/command'
338 self._state_sub = rospy.Subscriber(state_topic,
339 JointTrajectoryControllerState,
340 self._state_cb,
341 queue_size=1)
342 self._cmd_pub = rospy.Publisher(cmd_topic,
343 JointTrajectory,
344 queue_size=1)
345
346
347 self.jointStateChanged.connect(self._on_joint_state_change)
348
350
351 try:
352 self.jointStateChanged.disconnect(self._on_joint_state_change)
353 except:
354 pass
355
356
357 self._unregister_state_sub()
358 self._unregister_cmd_pub()
359
360
361
362
363 layout = self._widget.joint_group.layout()
364 if layout is not None:
365 while layout.count():
366 layout.takeAt(0).widget().deleteLater()
367
368 QWidget().setLayout(layout)
369 self._widget.joint_group.setLayout(QFormLayout())
370
371
372 self._joint_names = []
373 self._joint_pos = {}
374
375
376 self._widget.enable_button.setChecked(False)
377
379 from controller_manager_msgs.utils\
380 import filter_by_type, filter_by_state
381
382 controller_list = self._list_controllers()
383 jtc_list = filter_by_type(controller_list,
384 'JointTrajectoryController',
385 match_substring=True)
386 running_jtc_list = filter_by_state(jtc_list, 'running')
387 return running_jtc_list
388
390 if self._cmd_pub is not None:
391 self._cmd_pub.unregister()
392 self._cmd_pub = None
393
395 if self._state_sub is not None:
396 self._state_sub.unregister()
397 self._state_sub = None
398
400 actual_pos = {}
401 for i in range(len(msg.joint_names)):
402 joint_name = msg.joint_names[i]
403 joint_pos = msg.actual.positions[i]
404 actual_pos[joint_name] = joint_pos
405 self.jointStateChanged.emit(actual_pos)
406
408 self._joint_pos[name]['command'] = val
409
411 dur = []
412 traj = JointTrajectory()
413 traj.joint_names = self._joint_names
414 point = JointTrajectoryPoint()
415 for name in traj.joint_names:
416 pos = self._joint_pos[name]['position']
417 cmd = pos
418 try:
419 cmd = self._joint_pos[name]['command']
420 except (KeyError):
421 pass
422 max_vel = self._robot_joint_limits[name]['max_velocity']
423 dur.append(max(abs(cmd - pos) / max_vel, self._min_traj_dur))
424 point.positions.append(cmd)
425 point.time_from_start = rospy.Duration(max(dur) / self._speed_scale)
426 traj.points.append(point)
427
428 self._cmd_pub.publish(traj)
429
439
441 widgets = []
442 layout = self._widget.joint_group.layout()
443 for row_id in range(layout.rowCount()):
444 widgets.append(layout.itemAt(row_id,
445 QFormLayout.FieldRole).widget())
446 return widgets
447
449
450
451
452 return jtc_info.claimed_resources[0].resources
453
455 """
456 Resolve a controller's namespace from that of the controller manager.
457 Controllers are assumed to live one level above the controller
458 manager, e.g.
459
460 >>> _resolve_controller_ns('/path/to/controller_manager', 'foo')
461 '/path/to/foo'
462
463 In the particular case in which the controller manager is not
464 namespaced, the controller is assumed to live in the root namespace
465
466 >>> _resolve_controller_ns('/', 'foo')
467 '/foo'
468 >>> _resolve_controller_ns('', 'foo')
469 '/foo'
470 @param cm_ns Controller manager namespace (can be an empty string)
471 @type cm_ns str
472 @param controller_name Controller name (non-empty string)
473 @type controller_name str
474 @return Controller namespace
475 @rtype str
476 """
477 assert(controller_name)
478 ns = cm_ns.rsplit('/', 1)[0]
479 if ns != '/':
480 ns += '/'
481 ns += controller_name
482 return ns
483