00001 import PyQt4.QtGui as qtg
00002 import PyQt4.QtCore as qtc
00003
00004
00005 import smach_ros
00006 import functools as ft
00007 import actionlib_msgs.msg as am
00008 import os.path as pt
00009 from tf_broadcast_server.srv import GetTransforms
00010 import rospy
00011 import actionlib
00012 import smach
00013
00014
00015 status_dict = {am.GoalStatus.PENDING : 'PENDING',
00016 am.GoalStatus.ACTIVE : 'ACTIVE',
00017 am.GoalStatus.PREEMPTED : 'PREEMPTED',
00018 am.GoalStatus.SUCCEEDED : 'SUCCEEDED',
00019 am.GoalStatus.ABORTED : 'ABORTED',
00020 am.GoalStatus.REJECTED : 'REJECTED',
00021 am.GoalStatus.PREEMPTING: 'PREEMPTING',
00022 am.GoalStatus.RECALLING : 'RECALLING',
00023 am.GoalStatus.RECALLED : 'RECALLED',
00024 am.GoalStatus.LOST : 'LOST'}
00025
00026 class ComboBox:
00027
00028 def __init__(self):
00029 pass
00030
00031 def create_box(self, pbox):
00032 self.combobox = qtg.QComboBox(pbox)
00033
00034
00035
00036
00037
00038
00039 def set_text(self, item, create=True):
00040 idx = combobox_idx(self.combobox, item, create=create)
00041 if idx != -1:
00042 self.combobox.setCurrentIndex(idx)
00043
00044 def text(self):
00045 return str(self.combobox.currentText())
00046
00047 class FrameBox(ComboBox):
00048
00049 def __init__(self, frames_service=None):
00050 ComboBox.__init__(self)
00051 if frames_service == None:
00052 frames_service = rospy.ServiceProxy('get_transforms', GetTransforms)
00053 self.frames_service = frames_service
00054
00055 def create_box(self, pbox):
00056 ComboBox.create_box(self, pbox)
00057 for f in self.frames_service().frames:
00058 self.combobox.addItem(f)
00059 self.setEnabled = self.combobox.setEnabled
00060 return self.combobox
00061
00062 def goal_status_to_string(status):
00063 return status_dict[status]
00064
00065 def create_tool_button(name, container):
00066 button = qtg.QToolButton(container)
00067 sizePolicy = qtg.QSizePolicy(qtg.QSizePolicy.Expanding, qtg.QSizePolicy.Fixed)
00068 sizePolicy.setHorizontalStretch(0)
00069 sizePolicy.setVerticalStretch(0)
00070 sizePolicy.setHeightForWidth(button.sizePolicy().hasHeightForWidth())
00071 button.setSizePolicy(sizePolicy)
00072 button.setAutoRaise(True)
00073 button.setObjectName("button")
00074 button.setCheckable(True)
00075 container.layout().addWidget(button)
00076 button.setText(qtg.QApplication.translate("RCommanderWindow", name, None, qtg.QApplication.UnicodeUTF8))
00077 return button
00078
00079 def make_radio_box(parent, options, name_preffix):
00080 container_name = name_preffix + '_radio_box'
00081
00082 container = qtg.QWidget(parent)
00083 container.setObjectName(container_name)
00084 hlayout = qtg.QHBoxLayout(container)
00085 radio_buttons = []
00086
00087 for option in options:
00088 r = qtg.QRadioButton(container)
00089 r.setObjectName(name_preffix + '_' + option)
00090 r.setText(option)
00091 hlayout.addWidget(r)
00092 radio_buttons.append(r)
00093 radio_buttons[0].setChecked(True)
00094
00095 return container, radio_buttons
00096
00097
00098
00099
00100
00101
00102
00103 def combobox_idx(combobox, name, create=True):
00104 if name == None:
00105 name = ' '
00106 idx = combobox.findText(name)
00107 if idx == -1:
00108 if create == True:
00109 combobox.addItem(name)
00110 idx = combobox.findText(name)
00111 return idx
00112 else:
00113 return -1
00114 return idx
00115
00116
00117 def double_spin_box(parent, minv, maxv, step):
00118 box = qtg.QDoubleSpinBox(parent)
00119 box.setMinimum(minv)
00120 box.setMaximum(maxv)
00121 box.setSingleStep(step)
00122 return box
00123
00124 def selected_radio_button(buttons_list):
00125 selected = None
00126 for r in buttons_list:
00127 if r.isChecked():
00128 selected = str(r.text())
00129 return selected
00130
00131 class SliderBox:
00132
00133 def __init__(self, parent, initial_value, max_value, min_value, ticks, name_preffix, units=''):
00134 self.min_value = min_value
00135 self.max_value = max_value
00136
00137 container_name = name_preffix + '_box'
00138 disp_name = name_preffix + '_disp'
00139 slider_name = name_preffix + '_slider'
00140
00141 nticks = float(max_value - min_value) / float(ticks)
00142 tick_size = int(round(100. / nticks))
00143 tick_pos = self._units_to_slider(initial_value)
00144
00145 container = qtg.QWidget(parent)
00146 container.setObjectName(container_name)
00147
00148 disp = qtg.QLabel(container)
00149 disp.setObjectName(disp_name)
00150 disp.setText('%.2f %s' % (initial_value, units))
00151
00152 slider = qtg.QSlider(container)
00153 slider.setSingleStep(1)
00154 slider.setOrientation(qtc.Qt.Horizontal)
00155 slider.setTickPosition(qtg.QSlider.TicksBelow)
00156 slider.setTickInterval(tick_size)
00157 slider.setObjectName(slider_name)
00158 slider.setSliderPosition(tick_size)
00159 slider.setValue(tick_pos)
00160
00161 hlayout = qtg.QVBoxLayout(container)
00162 hlayout.addWidget(slider)
00163 hlayout.addWidget(disp)
00164
00165 def slider_moved_cb(disp, value):
00166 cv = self._slider_to_units(value)
00167 disp.setText('%3.2f %s' % (cv, units))
00168 parent.connect(slider, qtc.SIGNAL('sliderMoved(int)'), ft.partial(slider_moved_cb, disp))
00169
00170 self.container = container
00171 self.slider = slider
00172 self.disp = disp
00173 self.units = units
00174
00175 def _slider_to_units(self, value):
00176 return ((value / 100.) * (self.max_value - self.min_value)) + self.min_value
00177
00178 def _units_to_slider(self, value):
00179 return int(round(((value - self.min_value) / (self.max_value - self.min_value)) * 100.0))
00180
00181 def value(self):
00182 return self._slider_to_units(self.slider.value())
00183
00184 def set_value(self, value):
00185 self.slider.setValue(self._units_to_slider(value))
00186 self.disp.setText('%3.2f %s' % (value, self.units))
00187
00188
00189 class ToolBase:
00190
00191 def __init__(self, rcommander, name, button_name, smach_class):
00192 self.rcommander = rcommander
00193
00194 self.name = name
00195 self.name_input = None
00196 self.loaded_node_name = None
00197
00198 self.button = None
00199 self.button_name = button_name
00200
00201 self.smach_class = smach_class
00202 self.outcome_inputs = {}
00203 self.counter = 0
00204
00205 self.node_exists = False
00206 self.saved_state = None
00207
00208 def create_button(self, container):
00209 self.button = create_tool_button(self.button_name, container)
00210 self.rcommander.connect(self.button, qtc.SIGNAL('clicked()'), self.activate_cb)
00211 return self.button
00212
00213 def activate_cb(self, loaded_node_name=None):
00214 self.rcommander.notify_activated()
00215
00216 self.node_exists = False
00217 self.rcommander.enable_buttons()
00218 self.set_loaded_node_name(loaded_node_name)
00219 self.rcommander.add_mode()
00220 self.rcommander.empty_properties_box()
00221
00222 if self.button.isChecked():
00223
00224 self.rcommander.set_selected_tool(self.get_smach_class())
00225
00226 self.fill_property_box(self.rcommander.ui.properties_tab)
00227 self.fill_connections_box(self.rcommander.ui.connections_tab)
00228 self.rcommander.ui.node_settings_tabs.setCurrentIndex(0)
00229 else:
00230 self.rcommander.set_selected_tool(None)
00231
00232 if self.saved_state != None and loaded_node_name == None:
00233 self.set_node_properties(self.saved_state)
00234
00235 def clear_saved_state(self):
00236 self.saved_state = None
00237
00238
00239 def deselect_tool(self):
00240
00241 if self.node_exists:
00242 return
00243 else:
00244 try:
00245 self.saved_state = self.new_node()
00246 except Exception, e:
00247 rospy.loginfo(str(e))
00248
00249 def _get_outcome_choices(self):
00250 outcome_choices = {}
00251 for outcome_name in self.outcome_inputs.keys():
00252 outcome_choices[outcome_name] = str(self.outcome_inputs[outcome_name].currentText())
00253 return outcome_choices
00254
00255 def refresh_connections_box(self):
00256 if not self.button.isChecked():
00257 return
00258
00259
00260 outcome_choices = self._get_outcome_choices()
00261 node_name = str(self.name_input.text())
00262
00263
00264 self.rcommander.empty_container(self.rcommander.ui.connections_tab)
00265
00266
00267 self.fill_connections_box(self.rcommander.ui.connections_tab)
00268
00269
00270 self.name_input.setText(node_name)
00271 for k in outcome_choices:
00272 selected = outcome_choices[k]
00273 widget = self.outcome_inputs[k]
00274 widget.setCurrentIndex(widget.findText(selected))
00275
00276 def fill_connections_box(self, pbox):
00277 formlayout = pbox.layout()
00278
00279 if hasattr(self, 'set_child_node'):
00280 if self.rcommander.selected_node == None:
00281 return
00282 else:
00283 state = self.rcommander.graph_model.get_state(self.rcommander.selected_node)
00284 self.set_child_node(state)
00285
00286 self.name_input = qtg.QLineEdit()
00287 formlayout.addRow('Name', self.name_input)
00288
00289 if self.get_current_node_name() == None:
00290 current_node = self.new_node()
00291 else:
00292 current_node = self.rcommander.graph_model.get_state(self.get_current_node_name())
00293
00294 current_node_smach = current_node.get_smach_state()
00295 self.rcommander.connect_node(current_node_smach)
00296
00297 current_node_name = current_node.get_name()
00298 self.name_input.setText(current_node_name)
00299
00300 if issubclass(current_node.__class__, EmptyState):
00301 return
00302
00303 registered_outcomes = list(current_node_smach.get_registered_outcomes())
00304 registered_outcomes.sort()
00305 for outcome in registered_outcomes:
00306
00307 input_box = qtg.QComboBox(pbox)
00308 nodes = self.rcommander.connectable_nodes(self.get_current_node_name(), outcome)
00309
00310 nodes.sort()
00311 for n in nodes:
00312 input_box.addItem(n)
00313
00314
00315 formlayout.addRow(outcome, input_box)
00316
00317
00318 input_box.setCurrentIndex(input_box.findText(outcome))
00319
00320 self.outcome_inputs[outcome] = input_box
00321
00322
00323 def cb(outcome, new_index):
00324 new_outcome = str(self.outcome_inputs[outcome].currentText())
00325 self.rcommander.connection_changed(self.get_current_node_name(), outcome, new_outcome)
00326
00327 outcome_cb = ft.partial(cb, outcome)
00328 self.rcommander.connect(input_box, qtc.SIGNAL('currentIndexChanged(int)'), outcome_cb)
00329
00330
00331 def set_loaded_node_name(self, name):
00332 self.loaded_node_name = name
00333
00334 def get_current_node_name(self):
00335 return self.loaded_node_name
00336
00337 def create_node(self, unique=True):
00338 if unique:
00339 self.counter = self.counter + 1
00340 n = self.new_node(str(self.name_input.text()))
00341 if unique:
00342 self.node_exists = True
00343 self.saved_state = None
00344 return n
00345
00346 def node_selected(self, node):
00347 self.node_exists = True
00348 outcome_list = self.rcommander.current_children_of(node.get_name())
00349 for outcome_name, connect_node in outcome_list:
00350 if not self.outcome_inputs.has_key(outcome_name):
00351 continue
00352 widget = self.outcome_inputs[outcome_name]
00353 widget.setCurrentIndex(widget.findText(connect_node))
00354
00355 self.name_input.setText(node.get_name())
00356 self.set_loaded_node_name(node.get_name())
00357 self.loaded_node_name = node.get_name()
00358 self.set_node_properties(node)
00359
00360 def get_smach_class(self):
00361 return self.smach_class
00362
00363
00364
00365
00366
00367
00368
00369
00370
00371
00372 def fill_property_box(self, pbox):
00373 pass
00374
00375
00376
00377
00378
00379
00380
00381
00382
00383
00384 def new_node(self, name=None):
00385 pass
00386
00387
00388
00389
00390
00391
00392 def set_node_properties(self, node):
00393 pass
00394
00395
00396
00397
00398
00399 def reset(self):
00400 pass
00401
00402
00403 class StateBase:
00404
00405
00406
00407
00408
00409
00410 def __init__(self, name, outputs={}):
00411 self.name = name
00412 self.remapping = {}
00413 self.runnable = True
00414 self.outputs = outputs
00415
00416
00417
00418
00419
00420 def output_names(self):
00421 return self.outputs.keys()
00422
00423
00424
00425
00426
00427
00428 def output_type(self, name):
00429 return self.outputs[name]
00430
00431
00432
00433
00434
00435
00436 def set_name(self, name):
00437 self.name = name
00438
00439
00440
00441
00442 def get_name(self):
00443 return self.name
00444
00445
00446
00447
00448
00449
00450 def remapping_for(self, var_name):
00451 return self.remapping[var_name]
00452
00453
00454
00455
00456
00457
00458
00459 def set_remapping_for(self, var, remapped):
00460 self.remapping[var] = remapped
00461
00462
00463
00464
00465 def is_runnable(self):
00466 return self.runnable
00467
00468
00469
00470
00471
00472
00473 def set_runnable(self, v):
00474 self.runnable = v
00475
00476
00477
00478
00479
00480 def get_smach_state(self):
00481 raise RuntimeError('Unimplemented method: get_smach_state')
00482
00483
00484 class EmptyState(StateBase):
00485
00486 TOOL_NAME = 'outcome'
00487
00488 def __init__(self, name, temporary, tool_name=TOOL_NAME):
00489 StateBase.__init__(self, name)
00490 self.set_runnable(False)
00491 self.temporary = temporary
00492 self.tool_name = tool_name
00493
00494 def get_smach_state(self):
00495 return self
00496
00497
00498
00499
00500 class EmbeddableState(StateBase):
00501
00502 def __init__(self, name, child_gm):
00503 StateBase.__init__(self, name)
00504 self.child_gm = child_gm
00505 self.child_document = None
00506
00507
00508 self._init_child(child_gm)
00509
00510 def get_child_document(self):
00511 return self.child_document
00512
00513 def abort_child(self):
00514 fetus = self.child_gm
00515 self.child_gm = None
00516 return fetus
00517
00518 def get_child(self):
00519 return self.child_gm
00520
00521 def set_child(self, child):
00522 self.child_gm = child
00523
00524
00525
00526
00527
00528 def _init_child(self, child_gm):
00529 if child_gm != None:
00530 self.child_document = child_gm.get_document()
00531
00532 for node_name in child_gm.states_dict.keys():
00533 mapping = child_gm.get_state(node_name).remapping
00534 for input_key in mapping.keys():
00535 source = mapping[input_key]
00536 self.set_remapping_for(source, source)
00537
00538
00539
00540
00541 def save_child(self, base_path=""):
00542 child_gm = self.get_child()
00543 if child_gm.document.has_real_filename():
00544
00545 child_gm.save(child_gm.get_document().get_filename())
00546 else:
00547 fname = pt.join(base_path, child_gm.get_document().get_name())
00548
00549 if not pt.exists(fname):
00550 child_gm.save(fname)
00551
00552 else:
00553 raise RuntimeError('FILE EXISTS. CAN\'T OVERWRITE')
00554
00555 self.child_document = child_gm.document
00556
00557
00558
00559
00560 def load_and_recreate(self, base_path):
00561 import graph_model as gm
00562
00563 fname = pt.join(base_path, pt.split(self.child_document.get_filename())[1])
00564
00565 child_gm = gm.GraphModel.load(fname)
00566 return self.recreate(child_gm)
00567
00568
00569
00570
00571 def get_child_name(self):
00572 return self.child_gm.get_start_state()
00573
00574
00575
00576
00577 def recreate(self, graph_model):
00578 raise RuntimeError('Unimplemented!!')
00579
00580
00581 class SimpleStateCB:
00582
00583 def __init__(self, cb_func, input_keys, output_keys):
00584 self.cb_func = cb_func
00585 self.input_keys = input_keys
00586 self.output_keys = output_keys
00587
00588 def __call__(self, userdata, default_goal):
00589 return self.cb_func(userdata, default_goal)
00590
00591 def get_registered_input_keys(self):
00592 return self.input_keys
00593
00594 def get_registered_output_keys(self):
00595 return self.output_keys
00596
00597 def get_registered_outcomes(self):
00598 return []
00599
00600
00601
00602
00603 class SimpleStateBase(StateBase):
00604
00605
00606
00607
00608
00609
00610
00611
00612
00613 def __init__(self, name, action_name, action_type, goal_cb_str, input_keys=[], output_keys=[]):
00614 StateBase.__init__(self, name)
00615 self.action_name = action_name
00616 self.action_type = action_type
00617 self.goal_cb_str = goal_cb_str
00618 self.input_keys = input_keys
00619 self.output_keys = output_keys
00620
00621 def get_smach_state(self):
00622 return SimpleStateBaseSmach(self.action_name, self.action_type, self, self.goal_cb_str,
00623 self.input_keys, self.output_keys)
00624
00625
00626 class SimpleStateBaseSmach(smach_ros.SimpleActionState):
00627
00628 def __init__(self, action_name, action_type, goal_obj, goal_cb_str, input_keys, output_keys):
00629 smach_ros.SimpleActionState.__init__(self, action_name, action_type,
00630 goal_cb = SimpleStateCB(eval('goal_obj.%s' % goal_cb_str), input_keys, output_keys))
00631 self.goal_obj = goal_obj
00632
00633 def __call__(self, userdata, default_goal):
00634 f = eval('self.goal_obj.%s' % self.goal_cb_str)
00635 return f(userdata, default_goal)
00636
00637
00638 class ActionWithTimeoutSmach(smach.State):
00639
00640 def __init__(self, time_out, action_name, action_class):
00641 smach.State.__init__(self,
00642 outcomes = ['succeeded', 'preempted', 'timed_out'],
00643 input_keys = [], output_keys = [])
00644 self.action_client = actionlib.SimpleActionClient(action_name, action_class)
00645 self.action_name = action_name
00646 self.time_out = time_out
00647
00648 def get_goal(self):
00649 pass
00650
00651 def process_result(self):
00652 pass
00653
00654 def execute(self, userdata):
00655 self.action_client.send_goal(self.get_goal())
00656 state = self.action_client.get_state()
00657
00658 succeeded = False
00659 preempted = False
00660 r = rospy.Rate(30)
00661 start_time = rospy.get_time()
00662 while True:
00663
00664 if self.preempt_requested():
00665 rospy.loginfo('%s: preempt requested' % self.action_name)
00666 self.action_client.cancel_goal()
00667 self.service_preempt()
00668 preempted = True
00669 break
00670
00671 if (rospy.get_time() - start_time) > self.time_out:
00672 self.action_client.cancel_goal()
00673 rospy.loginfo('%s: timed out!' % self.action_name)
00674 succeeded = False
00675 break
00676
00677
00678 if (state not in [am.GoalStatus.ACTIVE, am.GoalStatus.PENDING]):
00679 if state == am.GoalStatus.SUCCEEDED:
00680 rospy.loginfo('%s: Succeeded!' % self.action_name)
00681 succeeded = True
00682 break
00683
00684 state = self.action_client.get_state()
00685 r.sleep()
00686
00687 if preempted:
00688 return 'preempted'
00689
00690 if succeeded:
00691 self.process_result(self.action_client.get_result())
00692 return 'succeeded'
00693
00694 return 'timed_out'
00695
00696
00697