00001
00002 import rospy
00003 from PyQt4.QtGui import *
00004 from PyQt4.QtCore import *
00005 import rcommander.tool_utils as tu
00006 import numpy as np
00007 import actionlib_msgs.msg as am
00008 import smach
00009 import functools as ft
00010 import pr2_utils as pu
00011 from rcommander_pr2_gui.srv import MinTime
00012 import trajectory_msgs.msg as tm
00013 import os.path as pt
00014 import roslib
00015
00016 def create_color(a, r,g,b):
00017 palette = QPalette(QColor(a, r, g, b))
00018 palette.setColor(QPalette.Text, QColor(a, r, g, b))
00019 return palette
00020
00021 class JointSequenceTool(tu.ToolBase):
00022
00023 def __init__(self, rcommander):
00024 tu.ToolBase.__init__(self, rcommander, 'joint_sequence', 'Joint Sequence', JointSequenceState)
00025 self.joint_name_fields = ["shoulder_pan_joint", "shoulder_lift_joint", "upper_arm_roll_joint",
00026 "elbow_flex_joint", "forearm_roll_joint", "wrist_flex_joint", "wrist_roll_joint"]
00027 self.reverse_idx = {}
00028 for idx, n in enumerate(self.joint_name_fields):
00029 self.reverse_idx[n] = idx
00030
00031 self.joint_angs_list = None
00032
00033 self.status_bar_timer = QTimer()
00034 self.rcommander.connect(self.status_bar_timer, SIGNAL('timeout()'), self.get_current_joint_angles)
00035 self.limits = [self.rcommander.robot.left.get_limits(), self.rcommander.robot.right.get_limits()]
00036 self.min_time_service = rospy.ServiceProxy('min_time_to_move', MinTime)
00037
00038 self.current_update_color = create_color(0,0,0,255)
00039
00040 def _value_changed_validate(self, value, joint):
00041 arm = tu.selected_radio_button(self.arm_radio_buttons).lower()
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052 self._check_limit(arm, value, joint)
00053 self._check_time_validity(self.time_box.value())
00054
00055 def _check_limit(self, arm, value, joint):
00056 if arm == 'left':
00057 idx = 0
00058 pref = 'l_'
00059 else:
00060 idx = 1
00061 pref = 'r_'
00062
00063 limits = self.limits[idx]
00064 jname = pref + joint
00065 if limits.has_key(jname):
00066 exec('box = self.%s' % joint)
00067 mina, maxa = limits[jname]
00068 v = np.radians(value)
00069 if v < mina or v > maxa:
00070 self.set_invalid_color(joint, True)
00071 else:
00072 self.set_invalid_color(joint, False)
00073
00074 def _check_time_validity(self, value):
00075
00076 r,g,b = 0,0,0
00077 palette = QPalette(QColor(r, g, b, 255))
00078 palette.setColor(QPalette.Text, QColor(r, g, b, 255))
00079 self.time_box.setPalette(palette)
00080
00081 arm = tu.selected_radio_button(self.arm_radio_buttons).lower()
00082 if arm == 'left':
00083
00084 pref = 'l_'
00085 left_arm = True
00086 else:
00087
00088 pref = 'r_'
00089 left_arm = False
00090
00091
00092 if len(self.joint_angs_list) == 0:
00093 return
00094
00095 if self.curr_selected == None:
00096 ref = self.joint_angs_list[-1]
00097 else:
00098 pidx = self.curr_selected - 1
00099 if pidx < 0:
00100 return
00101 else:
00102 ref = self.joint_angs_list[pidx]
00103
00104 start_point = tm.JointTrajectoryPoint()
00105 start_point.velocities = [0.] * len(self.joint_name_fields)
00106 for name in self.joint_name_fields:
00107 exec('box = self.%s' % name)
00108 start_point.positions.append(np.radians(box.value()))
00109
00110 end_point = tm.JointTrajectoryPoint()
00111 end_point.velocities = [0.] * len(self.joint_name_fields)
00112 end_point.positions = ref['angs']
00113
00114 min_time = self.min_time_service(start_point, end_point, left_arm).time
00115 curr_time = self.time_box.value()
00116
00117 for multiplier, color in [[1., [255,0,0]], [2., [255,153,0]]]:
00118 if curr_time <= (min_time * multiplier):
00119 r,g,b = color
00120 palette = QPalette(QColor(r, g, b, 255))
00121 palette.setColor(QPalette.Text, QColor(r, g, b, 255))
00122 self.time_box.setPalette(palette)
00123
00124 return
00125
00126 def _time_changed_validate(self, value):
00127 self._check_time_validity(value)
00128
00129 def set_invalid_color(self, joint_name, invalid, color = [255,0,0]):
00130 r,g,b = color
00131
00132 if invalid:
00133 palette = QPalette(QColor(r, g, b, 255))
00134 palette.setColor(QPalette.Text, QColor(r, g, b, 255))
00135 else:
00136
00137 palette = self.current_update_color
00138
00139
00140
00141 exec('self.%s.setPalette(palette)' % joint_name)
00142
00143 def fill_property_box(self, pbox):
00144 self.curr_selected = None
00145 formlayout = pbox.layout()
00146
00147 self.arm_radio_boxes, self.arm_radio_buttons = tu.make_radio_box(pbox, ['Left', 'Right'], 'arm')
00148 formlayout.addRow('&Arm', self.arm_radio_boxes)
00149
00150
00151 for name in self.joint_name_fields:
00152
00153 exec("self.%s = QDoubleSpinBox(pbox)" % name)
00154 exec('box = self.%s' % name)
00155 box.setSingleStep(.5)
00156 box.setMinimum(-9999999)
00157 box.setMaximum(9999999)
00158 formlayout.addRow("&%s" % name, box)
00159 vchanged_func = ft.partial(self._value_changed_validate, joint=name)
00160 self.rcommander.connect(box, SIGNAL('valueChanged(double)'), vchanged_func)
00161
00162 self.time_box = QDoubleSpinBox(pbox)
00163 self.time_box.setMinimum(0)
00164 self.time_box.setMaximum(1000.)
00165 self.time_box.setSingleStep(.2)
00166 self.time_box.setValue(1.)
00167 formlayout.addRow('&Time', self.time_box)
00168 self.rcommander.connect(self.time_box, SIGNAL('valueChanged(double)'), self._time_changed_validate)
00169
00170 self.live_update_button = QPushButton(pbox)
00171 self.live_update_button.setText('Live Update')
00172 self.rcommander.connect(self.live_update_button, SIGNAL('clicked()'), self.update_selected_cb)
00173 formlayout.addRow(self.live_update_button)
00174
00175 self.pose_button = QPushButton(pbox)
00176 self.pose_button.setText('Current Pose')
00177 self.rcommander.connect(self.pose_button, SIGNAL('clicked()'), self.get_current_joint_angles)
00178 formlayout.addRow(self.pose_button)
00179
00180
00181 self.joint_angs_list = []
00182
00183 self.list_box = QWidget(pbox)
00184 self.list_box_layout = QHBoxLayout(self.list_box)
00185 self.list_box_layout.setMargin(0)
00186
00187 self.list_widget = QListWidget(self.list_box)
00188 self.rcommander.connect(self.list_widget, SIGNAL('itemSelectionChanged()'), self.item_selection_changed_cb)
00189 self.list_box_layout.addWidget(self.list_widget)
00190
00191 self.list_widget_buttons = QWidget(pbox)
00192 self.lbb_hlayout = QHBoxLayout(self.list_widget_buttons)
00193
00194 self.move_up_button = QPushButton(self.list_widget_buttons)
00195 self.move_up_button.setText("")
00196 icon = QIcon()
00197 base_path = roslib.packages.get_pkg_dir('rcommander_pr2_gui')
00198 icon.addPixmap(QPixmap(pt.join(base_path, "icons/UpButton.png")), QIcon.Normal, QIcon.Off)
00199 self.move_up_button.setIcon(icon)
00200 self.move_up_button.setObjectName("up_button")
00201 self.rcommander.connect(self.move_up_button, SIGNAL('clicked()'), self.move_up_cb)
00202 self.move_up_button.setToolTip('Move Up')
00203
00204 self.move_down_button = QPushButton(self.list_widget_buttons)
00205 self.move_down_button.setText("")
00206 icon = QIcon()
00207 icon.addPixmap(QPixmap(pt.join(base_path, "icons/DownButton.png")), QIcon.Normal, QIcon.Off)
00208 self.move_down_button.setIcon(icon)
00209 self.move_down_button.setObjectName("down_button")
00210 self.rcommander.connect(self.move_down_button, SIGNAL('clicked()'), self.move_down_cb)
00211 self.move_down_button.setToolTip('Move Down')
00212
00213 self.add_joint_set_button = QPushButton(self.list_widget_buttons)
00214 self.add_joint_set_button.setText("")
00215 icon = QIcon()
00216 icon.addPixmap(QPixmap(pt.join(base_path, "icons/AddButton.png")), QIcon.Normal, QIcon.Off)
00217 self.add_joint_set_button.setIcon(icon)
00218 self.add_joint_set_button.setObjectName("add_button")
00219 self.rcommander.connect(self.add_joint_set_button, SIGNAL('clicked()'), self.add_joint_set_cb)
00220
00221 self.add_joint_set_button.setToolTip('Add')
00222
00223 self.remove_joint_set_button = QPushButton(self.list_widget_buttons)
00224 self.remove_joint_set_button.setText("")
00225 icon = QIcon()
00226 icon.addPixmap(QPixmap(pt.join(base_path, "icons/RemoveButton.png")), QIcon.Normal, QIcon.Off)
00227 self.remove_joint_set_button.setIcon(icon)
00228 self.remove_joint_set_button.setObjectName("remove_button")
00229 self.rcommander.connect(self.remove_joint_set_button, SIGNAL('clicked()'), self.remove_pose_cb)
00230 self.remove_joint_set_button.setToolTip('Remove')
00231
00232 self.save_button = QPushButton(self.list_widget_buttons)
00233 self.save_button.setText("")
00234 icon = QIcon()
00235 icon.addPixmap(QPixmap(pt.join(base_path, "icons/SaveButton.png")), QIcon.Normal, QIcon.Off)
00236 self.save_button.setIcon(icon)
00237 self.save_button.setObjectName("save_button")
00238 self.rcommander.connect(self.save_button, SIGNAL('clicked()'), self.save_button_cb)
00239 self.save_button.setToolTip('Save')
00240
00241 spacer = QSpacerItem(40, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)
00242
00243 self.lbb_hlayout.addWidget(self.add_joint_set_button)
00244 self.lbb_hlayout.addWidget(self.remove_joint_set_button)
00245 self.lbb_hlayout.addWidget(self.save_button)
00246 self.lbb_hlayout.addItem(spacer)
00247 self.lbb_hlayout.addWidget(self.move_up_button)
00248 self.lbb_hlayout.addWidget(self.move_down_button)
00249 self.lbb_hlayout.setContentsMargins(2, 2, 2, 2)
00250
00251 formlayout.addRow('\n', self.list_box)
00252 formlayout.addRow('&Sequence:', self.list_box)
00253 formlayout.addRow(self.list_box)
00254 formlayout.addRow(self.list_widget_buttons)
00255 self.reset()
00256
00257 def set_update_mode(self, on):
00258 if on:
00259 self.live_update_button.setText('End Live Update')
00260 self.live_update_button.setEnabled(True)
00261 self.pose_button.setEnabled(False)
00262 self.status_bar_timer.start(30)
00263 self.current_update_color = create_color(0,180,75,255)
00264 else:
00265 self.live_update_button.setText('Live Update')
00266 self.pose_button.setEnabled(True)
00267 self.status_bar_timer.stop()
00268 self.current_update_color = create_color(0,0,0,255)
00269
00270 for name in self.joint_name_fields:
00271 palette = self.current_update_color
00272 exec('self.%s.setPalette(palette)' % name)
00273
00274 arm = tu.selected_radio_button(self.arm_radio_buttons).lower()
00275 for idx, name in enumerate(self.joint_name_fields):
00276 exec('line_edit = self.%s' % name)
00277 self._check_limit(arm, line_edit.value(), name)
00278
00279
00280 def update_selected_cb(self):
00281 if self.live_update_button.text() == 'Live Update':
00282 self.set_update_mode(True)
00283 else:
00284 self.set_update_mode(False)
00285
00286 def _refill_list_widget(self, joints_list):
00287 self.list_widget.clear()
00288 for d in joints_list:
00289 self.list_widget.addItem(d['name'])
00290
00291 def get_current_joint_angles(self):
00292 arm = tu.selected_radio_button(self.arm_radio_buttons).lower()
00293 if ('left' == arm):
00294 arm_obj = self.rcommander.robot.left
00295 else:
00296 arm_obj = self.rcommander.robot.right
00297
00298 pose_mat = arm_obj.pose()
00299
00300 for idx, name in enumerate(self.joint_name_fields):
00301 deg = np.degrees(pose_mat[idx, 0])
00302 exec('line_edit = self.%s' % name)
00303 line_edit.setValue(deg)
00304
00305
00306 def _has_name(self, test_name):
00307 for rec in self.joint_angs_list:
00308 if rec['name'] == test_name:
00309 return True
00310 else:
00311 return False
00312
00313 def _create_name(self):
00314 idx = len(self.joint_angs_list)
00315 tentative_name = 'point%d' % idx
00316
00317 while self._has_name(tentative_name):
00318 idx = idx + 1
00319 tentative_name = 'point%d' % idx
00320
00321 return tentative_name
00322
00323 def item_selection_changed_cb(self):
00324 self.set_update_mode(False)
00325 selected = self.list_widget.selectedItems()
00326 if len(selected) == 0:
00327 return
00328 idx = self._find_index_of(str(selected[0].text()))
00329 self.curr_selected = idx
00330
00331 joint_angs = self.joint_angs_list[idx]['angs']
00332 self._set_joints_to_fields(joint_angs)
00333 self.time_box.setValue(self.joint_angs_list[idx]['time'])
00334 self._time_changed_validate(self.joint_angs_list[idx]['time'])
00335
00336
00337
00338
00339 def add_joint_set_cb(self):
00340
00341 name = self._create_name()
00342
00343 self.joint_angs_list.append({'name':name,
00344 'time': self.time_box.value(),
00345 'angs': self._read_joints_from_fields(True)})
00346 self._refill_list_widget(self.joint_angs_list)
00347
00348 def _find_index_of(self, name):
00349 for idx, tup in enumerate(self.joint_angs_list):
00350 if tup['name'] == name:
00351 return idx
00352 return None
00353
00354 def move_up_cb(self):
00355
00356 idx = self._selected_idx()
00357 if idx == None:
00358 return
00359
00360
00361 item = self.joint_angs_list.pop(idx)
00362 self.joint_angs_list.insert(idx-1, item)
00363
00364
00365 self._refill_list_widget(self.joint_angs_list)
00366 self.list_widget.setCurrentItem(self.list_widget.item(idx-1))
00367
00368 def move_down_cb(self):
00369
00370 idx = self._selected_idx()
00371 if idx == None:
00372 return
00373
00374
00375 item = self.joint_angs_list.pop(idx)
00376 self.joint_angs_list.insert(idx+1, item)
00377
00378
00379 self._refill_list_widget(self.joint_angs_list)
00380 self.list_widget.setCurrentItem(self.list_widget.item(idx+1))
00381
00382 def _selected_idx(self):
00383
00384 selected = self.list_widget.selectedItems()
00385 if len(selected) == 0:
00386 return None
00387 sname = str(selected[0].text())
00388
00389
00390 idx = self._find_index_of(sname)
00391 return idx
00392
00393 def save_button_cb(self):
00394 idx = self._selected_idx()
00395 if idx == None:
00396 return
00397 el = self.joint_angs_list[idx]
00398 self.joint_angs_list[idx] = {'name': el['name'],
00399 'time': self.time_box.value(),
00400 'angs': self._read_joints_from_fields(True)}
00401
00402 def remove_pose_cb(self):
00403 idx = self._selected_idx()
00404 if idx == None:
00405 return
00406
00407
00408 if idx == None:
00409 raise RuntimeError('Inconsistency detected in list')
00410 else:
00411 self.joint_angs_list.pop(idx)
00412 self._refill_list_widget(self.joint_angs_list)
00413
00414 def _read_joints_from_fields(self, limit_ranges=False):
00415 arm = tu.selected_radio_button(self.arm_radio_buttons).lower()
00416 if arm == 'left':
00417 idx = 0
00418 pref = 'l_'
00419 else:
00420 idx = 1
00421 pref = 'r_'
00422
00423 limits = self.limits[idx]
00424 joints = []
00425 for name in self.joint_name_fields:
00426
00427 exec('rad = np.radians(self.%s.value())' % name)
00428 if limit_ranges and limits.has_key(pref+name):
00429 mn, mx = limits[pref+name]
00430 nrad = max(min(rad, mx-.005), mn+.005)
00431
00432
00433 rad = nrad
00434 joints.append(rad)
00435 return joints
00436
00437 def _set_joints_to_fields(self, joints):
00438 for idx, name in enumerate(self.joint_name_fields):
00439 deg = np.degrees(joints[idx])
00440 exec('line_edit = self.%s' % name)
00441 line_edit.setValue(deg)
00442
00443 def new_node(self, name=None):
00444 if name == None:
00445 nname = self.name + str(self.counter)
00446 else:
00447 nname = name
00448
00449 if (self.joint_angs_list == None or len(self.joint_angs_list) == 0) and (name != None):
00450 return None
00451
00452
00453
00454 arm = tu.selected_radio_button(self.arm_radio_buttons).lower()
00455 sstate = JointSequenceState(nname, arm, self.joint_angs_list)
00456
00457 return sstate
00458
00459 def set_node_properties(self, my_node):
00460 self.joint_angs_list = my_node.joint_waypoints
00461 self._refill_list_widget(self.joint_angs_list)
00462
00463 if 'left' == my_node.arm:
00464 self.arm_radio_buttons[0].setChecked(True)
00465 if my_node.arm == 'right':
00466 self.arm_radio_buttons[1].setChecked(True)
00467 self.list_widget.setCurrentItem(self.list_widget.item(0))
00468
00469 def reset(self):
00470 self.arm_radio_buttons[0].setChecked(True)
00471
00472 for name in self.joint_name_fields:
00473 exec('self.%s.setValue(0)' % name)
00474
00475
00476 self.status_bar_timer.stop()
00477 self.pose_button.setEnabled(True)
00478
00479 class JointSequenceState(tu.StateBase):
00480
00481 def __init__(self, name, arm, joint_waypoints):
00482 tu.StateBase.__init__(self, name)
00483 self.arm = arm
00484 self.joint_waypoints = joint_waypoints
00485
00486 def get_smach_state(self):
00487 return JointSequenceStateSmach(self.arm, self.joint_waypoints)
00488
00489 class JointSequenceStateSmach(smach.State):
00490
00491 TIME_OUT_FACTOR = 3.
00492
00493 def __init__(self, arm, joint_waypoints):
00494 smach.State.__init__(self, outcomes = ['succeeded', 'preempted', 'failed', 'aborted'],
00495 input_keys = [], output_keys = [])
00496 self.arm = arm
00497 self.joint_waypoints = joint_waypoints
00498 self.arm_obj = None
00499
00500 def set_robot(self, pr2):
00501 if pr2 == None:
00502 return
00503
00504 if self.arm == 'left':
00505 self.arm_obj = pr2.left
00506
00507 if self.arm == 'right':
00508 self.arm_obj = pr2.right
00509 self.controller_manager = pr2.controller_manager
00510
00511 def execute(self, userdata):
00512 status, started, stopped = self.controller_manager.joint_mode(self.arm)
00513
00514
00515 times = []
00516 wps = []
00517 for d in self.joint_waypoints:
00518 wps.append(np.matrix(d['angs']).T)
00519 times.append(d['time'])
00520
00521
00522
00523
00524
00525 self.arm_obj.set_poses(np.column_stack(wps), np.cumsum(np.array(times)), block=False)
00526 client = self.arm_obj.client
00527 state = client.get_state()
00528
00529
00530 trajectory_time_out = JointSequenceStateSmach.TIME_OUT_FACTOR * np.sum(times)
00531 succeeded = False
00532 preempted = False
00533 r = rospy.Rate(30)
00534 start_time = rospy.get_time()
00535 while True:
00536
00537 if self.preempt_requested():
00538 rospy.loginfo('JointSequenceState: preempt requested')
00539 client.cancel_goal()
00540 self.service_preempt()
00541 preempted = True
00542 break
00543
00544 if (rospy.get_time() - start_time) > trajectory_time_out:
00545 client.cancel_goal()
00546 rospy.loginfo('JointSequenceState: timed out!')
00547 succeeded = False
00548 break
00549
00550
00551 if (state not in [am.GoalStatus.ACTIVE, am.GoalStatus.PENDING]):
00552 if state == am.GoalStatus.SUCCEEDED:
00553 rospy.loginfo('JointSequenceState: Succeeded!')
00554 succeeded = True
00555 break
00556
00557 state = client.get_state()
00558
00559 r.sleep()
00560
00561
00562
00563 self.controller_manager.switch(stopped, started)
00564
00565 if preempted:
00566 return 'preempted'
00567
00568 if succeeded:
00569 return 'succeeded'
00570
00571 if state == am.GoalStatus.ABORTED:
00572 return 'aborted'
00573
00574 return 'failed'
00575
00576
00577
00578
00579
00580
00581
00582
00583
00584
00585
00586
00587
00588
00589
00590
00591
00592
00593
00594
00595
00596
00597
00598
00599
00600
00601
00602
00603
00604
00605
00606
00607
00608
00609
00610
00611
00612
00613
00614
00615
00616
00617
00618
00619
00620
00621
00622
00623
00624
00625
00626
00627
00628
00629
00630
00631
00632
00633
00634
00635
00636
00637
00638
00639
00640
00641
00642
00643
00644
00645
00646
00647
00648
00649
00650
00651
00652
00653
00654
00655
00656
00657
00658
00659
00660
00661
00662
00663
00664
00665