Package node_manager_fkie :: Module node_tree_model
[frames] | no frames]

Source Code for Module node_manager_fkie.node_tree_model

   1  # Software License Agreement (BSD License) 
   2  # 
   3  # Copyright (c) 2012, Fraunhofer FKIE/US, Alexander Tiderko 
   4  # All rights reserved. 
   5  # 
   6  # Redistribution and use in source and binary forms, with or without 
   7  # modification, are permitted provided that the following conditions 
   8  # are met: 
   9  # 
  10  #  * Redistributions of source code must retain the above copyright 
  11  #    notice, this list of conditions and the following disclaimer. 
  12  #  * Redistributions in binary form must reproduce the above 
  13  #    copyright notice, this list of conditions and the following 
  14  #    disclaimer in the documentation and/or other materials provided 
  15  #    with the distribution. 
  16  #  * Neither the name of Fraunhofer nor the names of its 
  17  #    contributors may be used to endorse or promote products derived 
  18  #    from this software without specific prior written permission. 
  19  # 
  20  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
  21  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
  22  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
  23  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
  24  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
  25  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
  26  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
  27  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
  28  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
  29  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
  30  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
  31  # POSSIBILITY OF SUCH DAMAGE. 
  32   
  33  from python_qt_binding import QtCore 
  34  from python_qt_binding import QtGui 
  35   
  36  import re 
  37  import roslib 
  38  import rospy 
  39  import node_manager_fkie as nm 
  40  from master_discovery_fkie.master_info import NodeInfo 
  41  from parameter_handler import ParameterHandler 
42 43 44 ################################################################################ 45 ############## GrouptItem ############## 46 ################################################################################ 47 48 -class GroupItem(QtGui.QStandardItem):
49 ''' 50 The GroupItem stores the information about a group of nodes. 51 ''' 52 ITEM_TYPE = QtCore.Qt.UserRole + 25 53
54 - def __init__(self, name, parent=None):
55 ''' 56 Initialize the GroupItem object with given values. 57 @param name: the name of the group 58 @type name: C{str} 59 @param parent: the parent item. In most cases this is the HostItem. The 60 variable is used to determine the different columns of the NodeItem. 61 @type parent: L{PySide.QtGui.QStandardItem} 62 ''' 63 QtGui.QStandardItem.__init__(self, name if name.rfind('@') > 0 else '{' + name + '}') 64 self.parent_item = parent 65 self._name = name 66 self.setIcon(QtGui.QIcon(':/icons/state_off.png')) 67 self.descr_type = self.descr_name = self.descr = '' 68 self.descr_images = [] 69 self._capcabilities = dict() 70 ''' 71 @ivar: dict(config : dict(namespace: dict(group:dict('type' : str, 'images' : [str], 'description' : str, 'nodes' : [str])))) 72 ''' 73 self._re_cap_nodes = dict()
74 75 @property
76 - def name(self):
77 ''' 78 The name of this group. 79 @rtype: C{str} 80 ''' 81 return self._name
82 83 @name.setter
84 - def name(self, new_name):
85 ''' 86 Set the new name of this group and updates the displayed name of the item. 87 @param new_name: The new name of the group. Used also to identify the group. 88 @type new_name: C{str} 89 ''' 90 self._name = new_name 91 self.setText('{' + self._name + '}')
92
93 - def is_in_cap_group(self, nodename, config, ns, groupname):
94 ''' 95 Returns `True` if the group contains the node. 96 @param nodename: the name of the node to test 97 @type nodename: str 98 @param config: the configuration name 99 @type config: str 100 @param ns: namespace 101 @type ns: str 102 @param groupname: the group name 103 @type groupname: str 104 @return: `True`, if the nodename is in the group 105 @rtype: bool 106 ''' 107 try: 108 if self._re_cap_nodes[(config, ns, groupname)].match(nodename): 109 return True 110 except: 111 pass 112 return False
113
114 - def _create_cap_nodes_pattern(self, config, cap):
115 for ns, groups in cap.items(): 116 for groupname, descr in groups.items(): 117 try: 118 nodes = descr['nodes'] 119 def_list = ['\A' + n.strip().replace('*','.*') + '\Z' for n in nodes] 120 if def_list: 121 self._re_cap_nodes[(config, ns, groupname)] = re.compile('|'.join(def_list), re.I) 122 else: 123 self._re_cap_nodes[(config, ns, groupname)] = re.compile('\b', re.I) 124 except: 125 import traceback 126 print traceback.format_exc()
127 128
129 - def addCapabilities(self, config, capabilities, masteruri):
130 ''' 131 Add new capabilities. Based on this capabilities the node are grouped. The 132 view will be updated. 133 @param config: The name of the configuration containing this new capabilities. 134 @type config: C{str} 135 @param masteruri: The masteruri is used only used, if new nodes are created. 136 @type masteruri: C{str} 137 @param capabilities: The capabilities, which defines groups and containing nodes. 138 @type capabilities: C{dict(namespace: dict(group:dict('type' : str, 'images' : [str], 'description' : str, 'nodes' : [str])))} 139 ''' 140 self._capcabilities[config] = capabilities 141 self._create_cap_nodes_pattern(config, capabilities) 142 # update the view 143 for ns, groups in capabilities.items(): 144 for group, descr in groups.items(): 145 group_changed = False 146 # create nodes for each group 147 nodes = descr['nodes'] 148 if nodes: 149 groupItem = self.getGroupItem(roslib.names.ns_join(ns, group)) 150 groupItem.descr_name = group 151 if descr['type']: 152 groupItem.descr_type = descr['type'] 153 if descr['description']: 154 groupItem.descr = descr['description'] 155 if descr['images']: 156 groupItem.descr_images = list(descr['images']) 157 # move the nodes from host to the group 158 for i in reversed(range(self.rowCount())): 159 item = self.child(i) 160 if isinstance(item, NodeItem) and self.is_in_cap_group(item.name, config, ns, group): 161 row = self.takeRow(i) 162 groupItem._addRow_sorted(row) 163 group_changed = True 164 # row[0].parent_item = groupItem 165 166 # create new or update existing items in the group 167 for node_name in nodes: 168 # do not add nodes with * in the name 169 if not re.search(r"\*", node_name): 170 items = groupItem.getNodeItemsByName(node_name) 171 if items: 172 for item in items: 173 item.addConfig(config) 174 group_changed = True 175 else: 176 items = self.getNodeItemsByName(node_name) 177 if items: 178 # copy the state of the existing node 179 groupItem.addNode(items[0].node_info, config) 180 elif config: 181 groupItem.addNode(NodeInfo(node_name, masteruri), config) 182 group_changed = True 183 if group_changed: 184 groupItem.updateDisplayedConfig() 185 groupItem.updateIcon()
186 # groupItem.updateTooltip() 187
188 - def remCapablities(self, config):
189 ''' 190 Removes internal entry of the capability, so the new nodes are not grouped. 191 To update view L{NodeTreeModel.removeConfigNodes()} and L{GroupItem.clearUp()} 192 must be called. 193 @param config: The name of the configuration containing this new capabilities. 194 @type config: C{str} 195 ''' 196 try: 197 if self._capcabilities.has_key(config): 198 del self._capcabilities[config] 199 except: 200 pass 201 else: 202 #todo update view? 203 pass
204
205 - def getCapabilityGroups(self, node_name):
206 ''' 207 Returns the names of groups, which contains the given node. 208 @param node_name: The name of the node 209 @type node_name: C{str} 210 @param config: The name of configuration, which describes the node. 211 @type config: C{str} 212 @return: The name of the configuration containing this new capabilities. 213 @rtype: C{dict(config : [str])} 214 ''' 215 result = dict() # dict(config : [group names]) 216 try: 217 for cfg, cap in self._capcabilities.items(): 218 for ns, groups in cap.items(): 219 for group, descr in groups.items(): 220 if self.is_in_cap_group(node_name, cfg, ns, group): 221 if not result.has_key(cfg): 222 result[cfg] = [] 223 result[cfg].append(roslib.names.ns_join(ns, group)) 224 except: 225 pass 226 # import traceback 227 # print traceback.format_exc() 228 return result
229
230 - def getNodeItemsByName(self, node_name, recursive=True):
231 ''' 232 Since the same node can be included by different groups, this method searches 233 for all nodes with given name and returns these items. 234 @param node_name: The name of the node 235 @type node_name: C{str} 236 @param recursive: Searches in (sub) groups 237 @type recursive: C{bool} 238 @return: The list with node items. 239 @rtype: C{[L{PySide.QtGui.QStandardItem}]} 240 ''' 241 result = [] 242 for i in range(self.rowCount()): 243 item = self.child(i) 244 if isinstance(item, GroupItem): 245 if recursive: 246 result[len(result):] = item.getNodeItemsByName(node_name) 247 elif isinstance(item, NodeItem) and item == node_name: 248 return [item] 249 return result
250
251 - def getNodeItems(self, recursive=True):
252 ''' 253 Returns all nodes in this group and subgroups. 254 @param recursive: returns the nodes of the subgroups 255 @type recursive: bool 256 @return: The list with node items. 257 @rtype: C{[L{PySide.QtGui.QStandardItem}]} 258 ''' 259 result = [] 260 for i in range(self.rowCount()): 261 item = self.child(i) 262 if isinstance(item, GroupItem): 263 if recursive: 264 result[len(result):] = item.getNodeItems() 265 elif isinstance(item, NodeItem): 266 result.append(item) 267 return result
268
269 - def getGroupItems(self):
270 ''' 271 Returns all group items this group 272 @return: The list with group items. 273 @rtype: C{[L{GroupItem}]} 274 ''' 275 result = [] 276 for i in range(self.rowCount()): 277 item = self.child(i) 278 if isinstance(item, GroupItem): 279 result.append(item) 280 result[len(result):] = item.getGroupItems() 281 return result
282 283
284 - def getGroupItem(self, group_name):
285 ''' 286 Returns a GroupItem with given name. If no group with this name exists, a 287 new one will be created. 288 Assumption: No groups in group!! 289 @param group_name: the name of the group 290 @type group_name: C{str} 291 @return: The group with given name 292 @rtype: L{GroupItem} 293 ''' 294 for i in range(self.rowCount()): 295 item = self.child(i) 296 if isinstance(item, GroupItem): 297 if item == group_name: 298 return item 299 elif item > group_name: 300 items = [] 301 newItem = GroupItem(group_name, self) 302 items.append(newItem) 303 cfgitem = QtGui.QStandardItem() 304 items.append(cfgitem) 305 self.insertRow(i, items) 306 return newItem 307 items = [] 308 newItem = GroupItem(group_name, self) 309 items.append(newItem) 310 cfgitem = QtGui.QStandardItem() 311 items.append(cfgitem) 312 self.appendRow(items) 313 return newItem
314
315 - def addNode(self, node, cfg=''):
316 ''' 317 Adds a new node with given name. 318 @param node: the NodeInfo of the node to create 319 @type node: L{NodeInfo} 320 @param cfg: The configuration, which describes the node 321 @type cfg: C{str} 322 ''' 323 groups = self.getCapabilityGroups(node.name) 324 if groups: 325 for c, group_list in groups.items(): 326 for group_name in group_list: 327 # insert in the group 328 groupItem = self.getGroupItem(group_name) 329 groupItem.addNode(node, cfg) 330 else: 331 # insert in order 332 new_item_row = NodeItem.newNodeRow(node.name, node.masteruri) 333 self._addRow_sorted(new_item_row) 334 new_item_row[0].node_info = node 335 if cfg or cfg == '': 336 new_item_row[0].addConfig(cfg)
337
338 - def _addRow_sorted(self, row):
339 for i in range(self.rowCount()): 340 item = self.child(i) 341 if item > row[0].name: 342 self.insertRow(i, row) 343 row[0].parent_item = self 344 return 345 self.appendRow(row) 346 row[0].parent_item = self
347
348 - def clearUp(self, fixed_node_names = None):
349 ''' 350 Removes not running and not configured nodes. 351 @param fixed_node_names: If the list is not None, the node not in the list are 352 set to not running! 353 @type fixed_node_names: C{[str]} 354 ''' 355 # first clear sub groups 356 groups = self.getGroupItems() 357 for group in groups: 358 group.clearUp(fixed_node_names) 359 removed = False 360 # move running nodes without configuration to the upper layer, remove not running and duplicate nodes 361 for i in reversed(range(self.rowCount())): 362 item = self.child(i) 363 if isinstance(item, NodeItem): 364 # set the running state of the node to None 365 if not fixed_node_names is None and not item.name in fixed_node_names: 366 item.node_info = NodeInfo(item.name, item.node_info.masteruri) 367 if not (item.has_configs() or item.is_running() or item.published or item.subscribed or item.services): 368 removed = True 369 self.removeRow(i) 370 elif not isinstance(self, HostItem): 371 has_launches = NodeItem.has_launch_cfgs(item.cfgs) 372 has_defaults = NodeItem.has_default_cfgs(item.cfgs) 373 has_std_cfg = item.has_std_cfg() 374 if item.state == NodeItem.STATE_RUN and not (has_launches or has_defaults or has_std_cfg): 375 # if it is in a group, is running, but has no configuration, move it to the host 376 if not self.parent_item is None and isinstance(self.parent_item, HostItem): 377 items_in_host = self.parent_item.getNodeItemsByName(item.name, True) 378 if len(items_in_host) == 1: 379 row = self.takeRow(i) 380 self.parent_item._addRow_sorted(row) 381 else: 382 #remove item 383 removed = True 384 self.removeRow(i) 385 if removed: 386 self.updateIcon() 387 388 # remove empty groups 389 for i in reversed(range(self.rowCount())): 390 item = self.child(i) 391 if isinstance(item, GroupItem): 392 if item.rowCount() == 0: 393 self.removeRow(i)
394
395 - def updateRunningNodeState(self, nodes):
396 ''' 397 Updates the running state of the nodes given in a dictionary. 398 @param nodes: A dictionary with node names and their running state described by L{NodeInfo}. 399 @type nodes: C{dict(str: L{master_discovery_fkie.NodeInfo})} 400 ''' 401 for (name, node) in nodes.items(): 402 # get the node items 403 items = self.getNodeItemsByName(name) 404 if items: 405 for item in items: 406 # update the node item 407 item.node_info = node 408 else: 409 # create the new node 410 self.addNode(node) 411 self.clearUp(nodes.keys())
412
413 - def getRunningNodes(self):
414 ''' 415 Returns the names of all running nodes. A running node is defined by his 416 PID. 417 @see: L{master_dicovery_fkie.NodeInfo} 418 @return: A list with node names 419 @rtype: C{[str]} 420 ''' 421 result = [] 422 for i in range(self.rowCount()): 423 item = self.child(i) 424 if isinstance(item, GroupItem): 425 result[len(result):] = item.getRunningNodes() 426 elif isinstance(item, NodeItem) and not item.node_info.pid is None: 427 result.append(item.name) 428 return result
429
430 - def markNodesAsDuplicateOf(self, running_nodes, is_sync_running=False):
431 ''' 432 While a synchronization same node on different hosts have the same name, the 433 nodes with the same on other host are marked. 434 @param running_nodes: The dictionary with names of running nodes and their masteruri 435 @type running_nodes: C{dict(str:str)} 436 @param is_sync_running: If the master_sync is running, the nodes are marked 437 as ghost nodes. So they are handled as running nodes, but has not run 438 informations. This nodes are running on remote host, but are not 439 syncronized because of filter or errrors. 440 @type is_sync_running: bool 441 ''' 442 ignore = ['/master_sync', '/master_discovery', '/node_manager'] 443 for i in range(self.rowCount()): 444 item = self.child(i) 445 if isinstance(item, GroupItem): 446 item.markNodesAsDuplicateOf(running_nodes, is_sync_running) 447 elif isinstance(item, NodeItem): 448 if is_sync_running: 449 item.is_ghost = (item.node_info.uri is None and (item.name in running_nodes and running_nodes[item.name] == item.node_info.masteruri)) 450 item.has_running = (item.node_info.uri is None and not item.name in ignore and (item.name in running_nodes and running_nodes[item.name] != item.node_info.masteruri)) 451 else: 452 if item.is_ghost: 453 item.is_ghost = False 454 item.has_running = (item.node_info.uri is None and not item.name in ignore and (item.name in running_nodes))
455
456 - def updateIcon(self):
457 if isinstance(self, HostItem): 458 # skip the icon update on a host item 459 return 460 has_running = False 461 has_off = False 462 has_duplicate = False 463 has_ghosts = False 464 for i in range(self.rowCount()): 465 item = self.child(i) 466 if isinstance(item, NodeItem): 467 if item.state == NodeItem.STATE_WARNING: 468 self.setIcon(QtGui.QIcon(':/icons/crystal_clear_warning.png')) 469 return 470 elif item.state == NodeItem.STATE_OFF: 471 has_off = True 472 elif item.state == NodeItem.STATE_RUN: 473 has_running = True 474 elif item.state == NodeItem.STATE_GHOST: 475 has_ghosts = True 476 elif item.state == NodeItem.STATE_DUPLICATE: 477 has_duplicate = True 478 if has_duplicate: 479 self.setIcon(QtGui.QIcon(':/icons/imacadam_stop.png')) 480 elif has_ghosts: 481 self.setIcon(QtGui.QIcon(':/icons/state_ghost.png')) 482 elif has_running and has_off: 483 self.setIcon(QtGui.QIcon(':/icons/state_part.png')) 484 elif not has_running: 485 self.setIcon(QtGui.QIcon(':/icons/state_off.png')) 486 elif not has_off and has_running: 487 self.setIcon(QtGui.QIcon(':/icons/state_run.png'))
488
489 - def _create_html_list(self, title, items):
490 result = '' 491 if items: 492 result += '<b><u>%s</u></b>'%title 493 if len(items) > 1: 494 result += ' <span style="color:gray;">[%d]</span>'%len(items) 495 result += '<ul><span></span><br>' 496 for i in items: 497 result += '<a href="node://%s%s">%s</a><br>'%(self.name, i, i) 498 result += '</ul>' 499 return result
500
501 - def updateTooltip(self):
502 ''' 503 Creates a tooltip description based on text set by L{updateDescription()} 504 and all childs of this host with valid sensor description. The result is 505 returned as a HTML part. 506 @return: the tooltip description coded as a HTML part 507 @rtype: C{str} 508 ''' 509 tooltip = self.generateDescription(False) 510 self.setToolTip(tooltip if tooltip else self.name) 511 return tooltip
512
513 - def generateDescription(self, extended=True):
514 tooltip = '' 515 if self.descr_type or self.descr_name or self.descr: 516 tooltip += '<h4>%s</h4><dl>'%self.descr_name 517 if self.descr_type: 518 tooltip += '<dt>Type: %s</dt></dl>'%self.descr_type 519 if extended: 520 try: 521 from docutils import examples 522 if self.descr: 523 tooltip += '<b><u>Detailed description:</u></b>' 524 tooltip += examples.html_body(unicode(self.descr)) 525 except: 526 import traceback 527 rospy.logwarn("Error while generate description for a tooltip: %s", traceback.format_exc()) 528 tooltip += '<br>' 529 # get nodes 530 nodes = [] 531 for j in range(self.rowCount()): 532 nodes.append(self.child(j).name) 533 if nodes: 534 tooltip += self._create_html_list('Nodes:', nodes) 535 return '<div>%s</div>'%tooltip
536
537 - def updateDescription(self, descr_type, descr_name, descr):
538 ''' 539 Sets the description of the robot. To update the tooltip of the host item use L{updateTooltip()}. 540 @param descr_type: the type of the robot 541 @type descr_type: C{str} 542 @param descr_name: the name of the robot 543 @type descr_name: C{str} 544 @param descr: the description of the robot as a U{http://docutils.sourceforge.net/rst.html|reStructuredText} 545 @type descr: C{str} 546 ''' 547 self.descr_type = descr_type 548 self.descr_name = descr_name 549 self.descr = descr
550
551 - def updateDisplayedConfig(self):
552 ''' 553 Updates the configuration representation in other column. 554 ''' 555 if not self.parent_item is None: 556 # get nodes 557 cfgs = [] 558 for j in range(self.rowCount()): 559 if self.child(j).cfgs: 560 cfgs[len(cfgs):] = self.child(j).cfgs 561 if cfgs: 562 cfgs = list(set(cfgs)) 563 cfg_col = self.parent_item.child(self.row(), NodeItem.COL_CFG) 564 if not cfg_col is None and isinstance(cfg_col, QtGui.QStandardItem): 565 cfg_col.setText('[%d]'%len(cfgs) if len(cfgs) > 1 else "") 566 # set tooltip 567 # removed for clarity !!! 568 # tooltip = '' 569 # if len(cfgs) > 0: 570 # tooltip = '' 571 # if len(cfgs) > 0: 572 # tooltip = ''.join([tooltip, '<h4>', 'Configurations:', '</h4><dl>']) 573 # for c in cfgs: 574 # if NodeItem.is_default_cfg(c): 575 # tooltip = ''.join([tooltip, '<dt>[default]', c[0], '</dt>']) 576 # else: 577 # tooltip = ''.join([tooltip, '<dt>', c, '</dt>']) 578 # tooltip = ''.join([tooltip, '</dl>']) 579 # cfg_col.setToolTip(''.join(['<div>', tooltip, '</div>'])) 580 # set icons 581 has_launches = NodeItem.has_launch_cfgs(cfgs) 582 has_defaults = NodeItem.has_default_cfgs(cfgs) 583 if has_launches and has_defaults: 584 cfg_col.setIcon(QtGui.QIcon(':/icons/crystal_clear_launch_file_def_cfg.png')) 585 elif has_launches: 586 cfg_col.setIcon(QtGui.QIcon(':/icons/crystal_clear_launch_file.png')) 587 elif has_defaults: 588 cfg_col.setIcon(QtGui.QIcon(':/icons/default_cfg.png')) 589 else: 590 cfg_col.setIcon(QtGui.QIcon())
591
592 - def type(self):
593 return GroupItem.ITEM_TYPE
594
595 - def __eq__(self, item):
596 ''' 597 Compares the name of the group. 598 ''' 599 if isinstance(item, str) or isinstance(item, unicode): 600 return self.name.lower() == item.lower() 601 elif not (item is None): 602 return self.name.lower() == item.name.lower() 603 return False
604
605 - def __gt__(self, item):
606 ''' 607 Compares the name of the group. 608 ''' 609 if isinstance(item, str) or isinstance(item, unicode): 610 return self.name.lower() > item.lower() 611 elif not (item is None): 612 return self.name.lower() > item.name.lower() 613 return False
614
615 616 617 ################################################################################ 618 ############## HostItem ############## 619 ################################################################################ 620 621 -class HostItem(GroupItem):
622 ''' 623 The HostItem stores the information about a host. 624 ''' 625 ITEM_TYPE = QtCore.Qt.UserRole + 26 626
627 - def __init__(self, masteruri, address, local, parent=None):
628 ''' 629 Initialize the HostItem object with given values. 630 @param masteruri: URI of the ROS master assigned to the host 631 @type masteruri: C{str} 632 @param address: the address of the host 633 @type address: C{str} 634 @param local: is this host the localhost where the node_manager is running. 635 @type local: C{bool} 636 ''' 637 name = self.hostNameFrom(masteruri, address) 638 self._hostname = nm.nameres().mastername(masteruri, address) 639 self._masteruri = masteruri 640 if self._hostname is None: 641 self._hostname = str(address) 642 GroupItem.__init__(self, name, parent) 643 self.id = (unicode(masteruri), unicode(address)) 644 image_file = nm.settings().robot_image_file(name) 645 if QtCore.QFile.exists(image_file): 646 self.setIcon(QtGui.QIcon(image_file)) 647 else: 648 if local: 649 self.setIcon(QtGui.QIcon(':/icons/crystal_clear_miscellaneous.png')) 650 else: 651 self.setIcon(QtGui.QIcon(':/icons/remote.png')) 652 self.descr_type = self.descr_name = self.descr = ''
653 654 @property
655 - def hostname(self):
656 return self._hostname
657 658 @property
659 - def masteruri(self):
660 return self._masteruri
661 662 @classmethod
663 - def hostNameFrom(cls, masteruri, address):
664 ''' 665 Returns the name generated from masteruri and address 666 @param masteruri: URI of the ROS master assigned to the host 667 @type masteruri: C{str} 668 @param address: the address of the host 669 @type address: C{str} 670 ''' 671 #'print "hostNameFrom - mastername" 672 name = nm.nameres().mastername(masteruri, address) 673 if not name: 674 name = address 675 #'print "hostNameFrom - hostname" 676 hostname = nm.nameres().hostname(address) 677 if hostname is None: 678 hostname = str(address) 679 result = '%s@%s'%(name, hostname) 680 if nm.nameres().getHostname(masteruri) != hostname: 681 result += '[%s]'%masteruri 682 #'print "- hostNameFrom" 683 return result
684 685
686 - def updateTooltip(self):
687 ''' 688 Creates a tooltip description based on text set by L{updateDescription()} 689 and all childs of this host with valid sensor description. The result is 690 returned as a HTML part. 691 @return: the tooltip description coded as a HTML part 692 @rtype: C{str} 693 ''' 694 tooltip = self.generateDescription(False) 695 self.setToolTip(tooltip if tooltip else self.name) 696 return tooltip
697
698 - def generateDescription(self, extended=True):
699 tooltip = '' 700 if self.descr_type or self.descr_name or self.descr: 701 tooltip += '<h4>%s</h4><dl>'%self.descr_name 702 if self.descr_type: 703 tooltip += '<dt>Type: %s</dt></dl>'%self.descr_type 704 if extended: 705 try: 706 from docutils import examples 707 if self.descr: 708 tooltip += '<b><u>Detailed description:</u></b>' 709 tooltip += examples.html_body(self.descr, input_encoding='utf8') 710 except: 711 import traceback 712 rospy.logwarn("Error while generate description for a tooltip: %s", traceback.format_exc()) 713 tooltip += '<br>' 714 tooltip += '<h3>%s</h3>'%self._hostname 715 tooltip += '<font size="+1"><i>%s</i></font><br>'%self.id[0] 716 tooltip += '<font size="+1">Host: <b>%s</b></font><br>'%self.id[1] 717 tooltip += '<a href="open_sync_dialog://%s">open sync dialog</a>'%(str(self.id[0]).replace('http://', '')) 718 tooltip += '<p>' 719 tooltip += '<a href="show_all_screens://%s">show all screens</a>'%(str(self.id[0]).replace('http://', '')) 720 tooltip += '<p>' 721 tooltip += '<a href="remove_all_launch_server://%s">kill all launch server</a>'%str(self.id[0]).replace('http://', '') 722 tooltip += '<p>' 723 # get sensors 724 capabilities = [] 725 for j in range(self.rowCount()): 726 item = self.child(j) 727 if isinstance(item, GroupItem): 728 capabilities.append(item.name) 729 if capabilities: 730 tooltip += '<b><u>Capabilities:</u></b>' 731 try: 732 from docutils import examples 733 tooltip += examples.html_body('- %s'%('\n- '.join(capabilities)), input_encoding='utf8') 734 except: 735 import traceback 736 rospy.logwarn("Error while generate description for a tooltip: %s", traceback.format_exc()) 737 return '<div>%s</div>'%tooltip if tooltip else ''
738
739 - def type(self):
740 return HostItem.ITEM_TYPE
741
742 - def __eq__(self, item):
743 ''' 744 Compares the address of the host. 745 ''' 746 if isinstance(item, str) or isinstance(item, unicode): 747 return str(self.id).lower() == item.lower() 748 elif isinstance(item, tuple): 749 return str(self.id).lower() == str(item).lower() 750 elif isinstance(item, HostItem): 751 return str(self.id).lower() == str(item.id).lower() 752 return False
753
754 - def __gt__(self, item):
755 ''' 756 Compares the address of the host. 757 ''' 758 if isinstance(item, str) or isinstance(item, unicode): 759 return str(self.id).lower() > item.lower() 760 elif isinstance(item, tuple): 761 return str(self.id).lower() > str(item).lower() 762 elif isinstance(item, HostItem): 763 return str(self.id).lower() > str(item.id).lower() 764 return False
765
766 767 ################################################################################ 768 ############## NodeItem ############## 769 ################################################################################ 770 771 -class NodeItem(QtGui.QStandardItem):
772 ''' 773 The NodeItem stores the information about the node using the ExtendedNodeInfo 774 class and represents it in a L{PySide.QtGui.QTreeModel} using the 775 L{PySide.QtGui.QStandardItemModel} 776 ''' 777 778 ITEM_TYPE = QtGui.QStandardItem.UserType + 35 779 NAME_ROLE = QtCore.Qt.UserRole + 1 780 COL_CFG = 1 781 # COL_URI = 2 782 783 STATE_OFF = 0 784 STATE_RUN = 1 785 STATE_WARNING = 2 786 STATE_GHOST = 3 787 STATE_DUPLICATE = 4 788
789 - def __init__(self, node_info):
790 ''' 791 Initialize the NodeItem instance. 792 @param node_info: the node information 793 @type node_info: L{master_discovery_fkie.NodeInfo} 794 ''' 795 QtGui.QStandardItem.__init__(self, node_info.name) 796 self.parent_item = None 797 self._node_info = node_info.copy() 798 # self.ICONS = {'empty' : QtGui.QIcon(), 799 # 'run' : QtGui.QIcon(':/icons/state_run.png'), 800 # 'off' :QtGui.QIcon(':/icons/state_off.png'), 801 # 'warning' : QtGui.QIcon(':/icons/crystal_clear_warning.png'), 802 # 'stop' : QtGui.QIcon('icons/imacadam_stop.png'), 803 # 'cfg+def' : QtGui.QIcon(':/icons/crystal_clear_launch_file_def_cfg.png'), 804 # 'cfg' : QtGui.QIcon(':/icons/crystal_clear_launch_file.png'), 805 # 'default_cfg' : QtGui.QIcon(':/icons/default_cfg.png') 806 # } 807 self._cfgs = [] 808 self._std_config = None # it's config with empty name. for default proposals 809 self._is_ghost = False 810 self._has_running = False 811 self.setIcon(QtGui.QIcon(':/icons/state_off.png')) 812 self._state = NodeItem.STATE_OFF
813 814 @property
815 - def state(self):
816 return self._state
817 818 @property
819 - def name(self):
820 return self._node_info.name
821 822 @property
823 - def masteruri(self):
824 return self._node_info.masteruri
825 826 @property
827 - def published(self):
828 return self._node_info.publishedTopics
829 830 @property
831 - def subscribed(self):
832 return self._node_info.subscribedTopics
833 834 @property
835 - def services(self):
836 return self._node_info.services
837 838 @property
839 - def node_info(self):
840 ''' 841 Returns the NodeInfo instance of this node. 842 @rtype: L{master_discovery_fkie.NodeInfo} 843 ''' 844 return self._node_info
845 846 @node_info.setter
847 - def node_info(self, node_info):
848 ''' 849 Sets the NodeInfo and updates the view, if needed. 850 ''' 851 abbos_changed = False 852 run_changed = False 853 # print "!!!", self.name 854 # print " subs: ", self._node_info.subscribedTopics, node_info.subscribedTopics 855 # print " pubs: ", self._node_info.publishedTopics, node_info.publishedTopics 856 # print " srvs: ", self._node_info.services, node_info.services 857 if self._node_info.publishedTopics != node_info.publishedTopics: 858 abbos_changed = True 859 self._node_info._publishedTopics = list(node_info.publishedTopics) 860 if self._node_info.subscribedTopics != node_info.subscribedTopics: 861 abbos_changed = True 862 self._node_info._subscribedTopics = list(node_info.subscribedTopics) 863 if self._node_info.services != node_info.services: 864 abbos_changed = True 865 self._node_info._services = list(node_info.services) 866 if self._node_info.pid != node_info.pid: 867 self._node_info.pid = node_info.pid 868 run_changed = True 869 if self._node_info.uri != node_info.uri: 870 self._node_info.uri = node_info.uri 871 run_changed = True 872 # update the tooltip and icon 873 if run_changed and (self.is_running() or self.has_configs) or abbos_changed: 874 self.updateDispayedName() 875 # self.updateDisplayedURI() 876 if not self.parent_item is None and not isinstance(self.parent_item, HostItem): 877 self.parent_item.updateIcon()
878 879 @property
880 - def uri(self):
881 return self._node_info.uri
882 883 @property
884 - def pid(self):
885 return self._node_info.pid
886 887 @property
888 - def has_running(self):
889 ''' 890 Returns C{True}, if there are exists other nodes with the same name. This 891 variable must be set manually! 892 @rtype: C{bool} 893 ''' 894 return self._has_running
895 896 @has_running.setter
897 - def has_running(self, state):
898 ''' 899 Sets however other node with the same name are running or not (on other hosts) 900 and updates the view of this item. 901 ''' 902 if self._has_running != state: 903 self._has_running = state 904 if self.has_configs() or self.is_running(): 905 self.updateDispayedName() 906 if not self.parent_item is None and not isinstance(self.parent_item, HostItem): 907 self.parent_item.updateIcon()
908 909 @property
910 - def is_ghost(self):
911 ''' 912 Returns C{True}, if there are exists other runnig nodes with the same name. This 913 variable must be set manually! 914 @rtype: C{bool} 915 ''' 916 return self._is_ghost
917 918 @is_ghost.setter
919 - def is_ghost(self, state):
920 ''' 921 Sets however other node with the same name is running (on other hosts) and 922 and the host showing this node the master_sync is running, but the node is 923 not synchronized. 924 ''' 925 if self._is_ghost != state: 926 self._is_ghost = state 927 if self.has_configs() or self.is_running(): 928 self.updateDispayedName() 929 if not self.parent_item is None and not isinstance(self.parent_item, HostItem): 930 self.parent_item.updateIcon()
931
932 - def data(self, role):
933 if role == self.NAME_ROLE: 934 return self.name 935 else: 936 return QtGui.QStandardItem.data(self, role)
937
938 - def updateDispayedName(self):
939 ''' 940 Updates the name representation of the Item 941 ''' 942 tooltip = '<h4>%s</h4><dl>'%self.node_info.name 943 tooltip += '<dt><b>URI:</b> %s</dt>'%self.node_info.uri 944 tooltip += '<dt><b>PID:</b> %s</dt>'%self.node_info.pid 945 tooltip += '<dt><b>ORG.MASTERURI:</b> %s</dt></dl>'%self.node_info.masteruri 946 #'print "updateDispayedName - hasMaster" 947 master_discovered = nm.nameres().hasMaster(self.node_info.masteruri) 948 local = False 949 # if not self.node_info.uri is None and not self.node_info.masteruri is None: 950 # local = (nm.nameres().getHostname(self.node_info.uri) == nm.nameres().getHostname(self.node_info.masteruri)) 951 if not self.node_info.pid is None: 952 self._state = NodeItem.STATE_RUN 953 self.setIcon(QtGui.QIcon(':/icons/state_run.png')) 954 # self.setIcon(ICONS['run']) 955 self.setToolTip('') 956 elif not self.node_info.uri is None and not self.node_info.isLocal: 957 self._state = NodeItem.STATE_RUN 958 self.setIcon(QtGui.QIcon(':/icons/state_unknown.png')) 959 tooltip += '<dl><dt>(Remote nodes will not be ping, so they are always marked running)</dt></dl>' 960 tooltip += '</dl>' 961 self.setToolTip('<div>%s</div>'%tooltip) 962 # elif not self.node_info.isLocal and not master_discovered and not self.node_info.uri is None: 963 ## elif not local and not master_discovered and not self.node_info.uri is None: 964 # self._state = NodeItem.STATE_RUN 965 # self.setIcon(QtGui.QIcon(':/icons/state_run.png')) 966 # tooltip = ''.join([tooltip, '<dl><dt>(Remote nodes will not be ping, so they are always marked running)</dt></dl>']) 967 # tooltip = ''.join([tooltip, '</dl>']) 968 # self.setToolTip(''.join(['<div>', tooltip, '</div>'])) 969 elif self.node_info.pid is None and self.node_info.uri is None and (self.node_info.subscribedTopics or self.node_info.publishedTopics or self.node_info.services): 970 self.setIcon(QtGui.QIcon(':/icons/crystal_clear_warning.png')) 971 self._state = NodeItem.STATE_WARNING 972 tooltip += '<dl><dt>Can\'t get node contact information, but there exists publisher, subscriber or services of this node.</dt></dl>' 973 tooltip += '</dl>' 974 self.setToolTip('<div>%s</div>'%tooltip) 975 elif not self.node_info.uri is None: 976 self._state = NodeItem.STATE_WARNING 977 self.setIcon(QtGui.QIcon(':/icons/crystal_clear_warning.png')) 978 if not self.node_info.isLocal and master_discovered: 979 tooltip = '<h4>%s is not local, however the ROS master on this host is discovered, but no information about this node received!</h4>'%self.node_info.name 980 self.setToolTip('<div>%s</div>'%tooltip) 981 elif self.is_ghost: 982 self._state = NodeItem.STATE_GHOST 983 self.setIcon(QtGui.QIcon(':/icons/state_ghost.png')) 984 tooltip = '<h4>The node is running, but not synchronized because of filter or errors, see master_sync log.</h4>' 985 self.setToolTip('<div>%s</div>'%tooltip) 986 elif self.has_running: 987 self._state = NodeItem.STATE_DUPLICATE 988 self.setIcon(QtGui.QIcon(':/icons/imacadam_stop.png')) 989 tooltip = '<h4>Where are nodes with the same name on remote hosts running. These will be terminated, if you run this node! (Only if master_sync is running or will be started somewhere!)</h4>' 990 self.setToolTip('<div>%s</div>'%tooltip) 991 else: 992 self._state = NodeItem.STATE_OFF 993 self.setIcon(QtGui.QIcon(':/icons/state_off.png')) 994 self.setToolTip('')
995 #'print "- updateDispayedName" 996 997 # removed common tooltip for clarity !!! 998 # self.setToolTip(''.join(['<div>', tooltip, '</div>'])) 999
1000 - def updateDisplayedURI(self):
1001 ''' 1002 Updates the URI representation in other column. 1003 ''' 1004 if not self.parent_item is None: 1005 uri_col = self.parent_item.child(self.row(), NodeItem.COL_URI) 1006 if not uri_col is None and isinstance(uri_col, QtGui.QStandardItem): 1007 uri_col.setText(str(self.node_info.uri) if not self.node_info.uri is None else "")
1008 1009 @property
1010 - def cfgs(self):
1011 ''' 1012 Returns the list with all launch configurations assigned to this item. 1013 @rtype: C{[str]} 1014 ''' 1015 return self._cfgs
1016
1017 - def addConfig(self, cfg):
1018 ''' 1019 Add the given configurations to the node. 1020 @param cfg: the loaded configuration, which contains this node. 1021 @type cfg: C{str} 1022 ''' 1023 if cfg == '': 1024 self._std_config = cfg 1025 elif cfg and not cfg in self._cfgs: 1026 self._cfgs.append(cfg) 1027 self.updateDisplayedConfig()
1028
1029 - def remConfig(self, cfg):
1030 ''' 1031 Remove the given configurations from the node. 1032 @param cfg: the loaded configuration, which contains this node. 1033 @type cfg: C{str} 1034 ''' 1035 result = False 1036 if cfg == '': 1037 self._std_config = None 1038 result = True 1039 if cfg in self._cfgs: 1040 self._cfgs.remove(cfg) 1041 result = True 1042 if result and (self.has_configs() or self.is_running()): 1043 self.updateDisplayedConfig() 1044 return result
1045
1046 - def updateDisplayedConfig(self):
1047 ''' 1048 Updates the configuration representation in other column. 1049 ''' 1050 if not self.parent_item is None: 1051 cfg_col = self.parent_item.child(self.row(), NodeItem.COL_CFG) 1052 if not cfg_col is None and isinstance(cfg_col, QtGui.QStandardItem): 1053 cfg_count = len(self._cfgs) 1054 cfg_col.setText(str(''.join(['[',str(cfg_count),']'])) if cfg_count > 1 else "") 1055 # set tooltip 1056 # removed tooltip for clarity !!! 1057 # tooltip = '' 1058 # if len(self._cfgs) > 0: 1059 # tooltip = '' 1060 # if len(self._cfgs) > 0: 1061 # tooltip = ''.join([tooltip, '<h4>', 'Configurations:', '</h4><dl>']) 1062 # for c in self._cfgs: 1063 # if NodeItem.is_default_cfg(c): 1064 # tooltip = ''.join([tooltip, '<dt>[default]', c[0], '</dt>']) 1065 # else: 1066 # tooltip = ''.join([tooltip, '<dt>', c, '</dt>']) 1067 # tooltip = ''.join([tooltip, '</dl>']) 1068 # cfg_col.setToolTip(''.join(['<div>', tooltip, '</div>'])) 1069 # set icons 1070 has_launches = NodeItem.has_launch_cfgs(self._cfgs) 1071 has_defaults = NodeItem.has_default_cfgs(self._cfgs) 1072 if has_launches and has_defaults: 1073 cfg_col.setIcon(QtGui.QIcon(':/icons/crystal_clear_launch_file_def_cfg.png')) 1074 elif has_launches: 1075 cfg_col.setIcon(QtGui.QIcon(':/icons/crystal_clear_launch_file.png')) 1076 elif has_defaults: 1077 cfg_col.setIcon(QtGui.QIcon(':/icons/default_cfg.png')) 1078 else: 1079 cfg_col.setIcon(QtGui.QIcon())
1080 # the update of the group will be perform in node_tree_model to reduce calls 1081 # if isinstance(self.parent_item, GroupItem): 1082 # self.parent_item.updateDisplayedConfig() 1083
1084 - def type(self):
1085 return NodeItem.ITEM_TYPE
1086 1087 @classmethod
1088 - def newNodeRow(self, name, masteruri):
1089 ''' 1090 Creates a new node row and returns it as a list with items. This list is 1091 used for the visualization of node data as a table row. 1092 @param name: the node name 1093 @type name: C{str} 1094 @param masteruri: the URI or the ROS master assigned to this node. 1095 @type masteruri: C{str} 1096 @return: the list for the representation as a row 1097 @rtype: C{[L{NodeItem}, L{PySide.QtGui.QStandardItem}(Cofigurations), L{PySide.QtGui.QStandardItem}(Node URI)]} 1098 ''' 1099 items = [] 1100 item = NodeItem(NodeInfo(name, masteruri)) 1101 items.append(item) 1102 cfgitem = QtGui.QStandardItem() 1103 items.append(cfgitem) 1104 # uriitem = QtGui.QStandardItem() 1105 # items.append(uriitem) 1106 return items
1107
1108 - def has_configs(self):
1109 return not (len(self._cfgs) == 0)# and self._std_config is None)
1110
1111 - def is_running(self):
1112 return not (self._node_info.pid is None and self._node_info.uri is None)
1113
1114 - def has_std_cfg(self):
1115 return self._std_config == ''
1116 1117 @classmethod
1118 - def has_launch_cfgs(cls, cfgs):
1119 for c in cfgs: 1120 if not cls.is_default_cfg(c): 1121 return True 1122 return False
1123 1124 @classmethod
1125 - def has_default_cfgs(cls, cfgs):
1126 for c in cfgs: 1127 if cls.is_default_cfg(c): 1128 return True 1129 return False
1130 1131 @classmethod
1132 - def is_default_cfg(cls, cfg):
1133 return isinstance(cfg, tuple)
1134
1135 - def __eq__(self, item):
1136 ''' 1137 Compares the name of the node. 1138 ''' 1139 if isinstance(item, str) or isinstance(item, unicode): 1140 return self.name == item 1141 elif not (item is None): 1142 return self.name == item.name 1143 return False
1144
1145 - def __gt__(self, item):
1146 ''' 1147 Compares the name of the node. 1148 ''' 1149 if isinstance(item, str) or isinstance(item, unicode): 1150 return self.name > item 1151 elif not (item is None): 1152 return self.name > item.name 1153 return False
1154
1155 1156 ################################################################################ 1157 ############## NodeTreeModel ############## 1158 ################################################################################ 1159 1160 -class NodeTreeModel(QtGui.QStandardItemModel):
1161 ''' 1162 The model to show the nodes running in a ROS system or loaded by a launch 1163 configuration. 1164 ''' 1165 # ICONS = {'default' : QtGui.QIcon(), 1166 # 'run' : QtGui.QIcon(":/icons/state_run.png"), 1167 # 'warning' : QtGui.QIcon(":/icons/crystal_clear_warning.png"), 1168 # 'def_launch_cfg' : QtGui.QIcon(":/icons/crystal_clear_launch_file_def_cfg.png"), 1169 # 'launch_cfg' : QtGui.QIcon(":/icons/crystal_clear_launch_file.png"), 1170 # 'def_cfg' : QtGui.QIcon(":/icons/default_cfg.png") } 1171 1172 header = [('Name', 450), 1173 ('Cfgs', -1)] 1174 # ('URI', -1)] 1175 1176 hostInserted = QtCore.Signal(HostItem) 1177 '''@ivar: the Qt signal, which is emitted, if a new host was inserted. 1178 Parameter: L{QtCore.QModelIndex} of the inserted host item''' 1179
1180 - def __init__(self, host_address, masteruri, parent=None):
1181 ''' 1182 Initialize the model. 1183 ''' 1184 super(NodeTreeModel, self).__init__(parent) 1185 self.setColumnCount(len(NodeTreeModel.header)) 1186 self.setHorizontalHeaderLabels([label for label, width in NodeTreeModel.header]) 1187 self._local_host_address = host_address 1188 self._std_capabilities = {'': {'SYSTEM': {'images': [], 1189 'nodes': [ '/rosout', 1190 '/master_discovery', 1191 '/zeroconf', 1192 '/master_sync', 1193 '/node_manager', 1194 '/dynamic_reconfigure/*'], 1195 'type': '', 1196 'description': 'This group contains the system management nodes.'} } } 1197 1198 #create a handler to request the parameter 1199 self.parameterHandler = ParameterHandler() 1200 # self.parameterHandler.parameter_list_signal.connect(self._on_param_list) 1201 self.parameterHandler.parameter_values_signal.connect(self._on_param_values)
1202 # self.parameterHandler.delivery_result_signal.connect(self._on_delivered_values) 1203 1204 1205 @property
1206 - def local_addr(self):
1207 return self._local_host_address
1208
1209 - def flags(self, index):
1210 if not index.isValid(): 1211 return QtCore.Qt.NoItemFlags 1212 return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
1213
1214 - def _set_std_capabilities(self, host_item):
1215 if not host_item is None: 1216 cap = self._std_capabilities 1217 s = roslib.names.SEP 1218 hostname = roslib.names.SEP.join(['', host_item.hostname, '*', 'default_cfg']) 1219 if not hostname in cap['']['SYSTEM']['nodes']: 1220 cap['']['SYSTEM']['nodes'].append(hostname) 1221 host_item.addCapabilities('', cap, host_item.masteruri) 1222 return cap 1223 return dict(self._std_capabilities)
1224
1225 - def getHostItem(self, masteruri, address):
1226 ''' 1227 Searches for the host item in the model. If no item is found a new one will 1228 created and inserted in sorted order. 1229 @param masteruri: ROS master URI 1230 @type masteruri: C{str} 1231 @param address: the address of the host 1232 @type address: C{str} 1233 @return: the item associated with the given master 1234 @rtype: L{HostItem} 1235 ''' 1236 if masteruri is None: 1237 return None 1238 host = (unicode(masteruri), unicode(address)) 1239 local = (self.local_addr == host) 1240 # find the host item by address 1241 root = self.invisibleRootItem() 1242 for i in range(root.rowCount()): 1243 if root.child(i) == host: 1244 return root.child(i) 1245 elif root.child(i) > host: 1246 hostItem = HostItem(masteruri, address, local) 1247 self.insertRow(i, hostItem) 1248 self.hostInserted.emit(hostItem) 1249 self._set_std_capabilities(hostItem) 1250 return hostItem 1251 hostItem = HostItem(masteruri, address, local) 1252 self.appendRow(hostItem) 1253 self.hostInserted.emit(hostItem) 1254 self._set_std_capabilities(hostItem) 1255 return hostItem
1256
1257 - def updateModelData(self, nodes):
1258 ''' 1259 Updates the model data. 1260 @param nodes: a dictionary with name and info objects of the nodes. 1261 @type nodes: C{dict(str:L{NodeInfo}, ...)} 1262 ''' 1263 # separate into different hosts 1264 hosts = dict() 1265 for (name, node) in nodes.items(): 1266 addr = nm.nameres().getHostname(node.uri if not node.uri is None else node.masteruri) 1267 host = (node.masteruri, addr) 1268 if not hosts.has_key(host): 1269 hosts[host] = dict() 1270 hosts[host][name] = node 1271 # update nodes for each host 1272 for ((masteruri, host), nodes_filtered) in hosts.items(): 1273 hostItem = self.getHostItem(masteruri, host) 1274 # rename the host item if needed 1275 if not hostItem is None: 1276 hostItem.updateRunningNodeState(nodes_filtered) 1277 # update nodes of the hosts, which are not more exists 1278 for i in reversed(range(self.invisibleRootItem().rowCount())): 1279 host = self.invisibleRootItem().child(i) 1280 if not hosts.has_key(host.id): 1281 host.updateRunningNodeState({}) 1282 self.removeEmptyHosts() 1283 # request for all nodes in host the parameter capability_group 1284 for ((masteruri, host), nodes_filtered) in hosts.items(): 1285 hostItem = self.getHostItem(masteruri, host) 1286 self._requestCapabilityGroupParameter(hostItem)
1287 # update the duplicate state 1288 # self.markNodesAsDuplicateOf(self.getRunningNodes()) 1289
1290 - def _requestCapabilityGroupParameter(self, host_item):
1291 if not host_item is None: 1292 items = host_item.getNodeItems() 1293 params = [roslib.names.ns_join(item.name, 'capability_group') for item in items if not item.has_configs() and item.is_running() and not host_item.is_in_cap_group(item.name, '', '', 'SYSTEM')] 1294 if params: 1295 self.parameterHandler.requestParameterValues(host_item.masteruri, params)
1296
1297 - def _on_param_values(self, masteruri, code, msg, params):
1298 ''' 1299 Updates the capability groups of nodes from ROS parameter server. 1300 @param masteruri: The URI of the ROS parameter server 1301 @type masteruri: C{str} 1302 @param code: The return code of the request. If not 1, the message is set and the list can be ignored. 1303 @type code: C{int} 1304 @param msg: The message of the result. 1305 @type msg: C{str} 1306 @param params: The dictionary the parameter names and request result. 1307 @type param: C{dict(paramName : (code, statusMessage, parameterValue))} 1308 ''' 1309 host = nm.nameres().address(masteruri) 1310 hostItem = self.getHostItem(masteruri, host) 1311 changed = False 1312 if not hostItem is None and code == 1: 1313 capabilities = self._set_std_capabilities(hostItem) 1314 available_ns = set(['']) 1315 available_groups = set(['SYSTEM']) 1316 # assumption: all parameter are 'capability_group' parameter 1317 for p, (code_n, msg_n, val) in params.items(): 1318 nodename = roslib.names.namespace(p).rstrip(roslib.names.SEP) 1319 ns = roslib.names.namespace(nodename).rstrip(roslib.names.SEP) 1320 available_ns.add(ns) 1321 if code_n == 1: 1322 # add group 1323 if val: 1324 available_groups.add(val) 1325 if not capabilities.has_key(ns): 1326 capabilities[ns] = dict() 1327 if not capabilities[ns].has_key(val): 1328 capabilities[ns][val] = {'images': [], 'nodes': [], 'type': '', 'description': 'This group is created from `capability_group` parameter of the node defined in ROS parameter server.' } 1329 if not nodename in capabilities[ns][val]['nodes']: 1330 capabilities[ns][val]['nodes'].append(nodename) 1331 changed = True 1332 else: 1333 try: 1334 for group, caps in capabilities[ns].items(): 1335 try: 1336 #remove the config from item, if parameter was not foun on the ROS parameter server 1337 groupItem = hostItem.getGroupItem(roslib.names.ns_join(ns,group)) 1338 if not groupItem is None: 1339 nodeItems = groupItem.getNodeItemsByName(nodename, True) 1340 for item in nodeItems: 1341 item.remConfig('') 1342 capabilities[ns][group]['nodes'].remove(nodename) 1343 # remove the group, if empty 1344 if not capabilities[ns][group]['nodes']: 1345 del capabilities[ns][group] 1346 if not capabilities[ns]: 1347 del capabilities[ns] 1348 groupItem.updateDisplayedConfig() 1349 changed = True 1350 except: 1351 pass 1352 except: 1353 pass 1354 # clearup namespaces to remove empty groups 1355 for ns in capabilities.keys(): 1356 if ns and not ns in available_ns: 1357 del capabilities[ns] 1358 changed = True 1359 else: 1360 for group in capabilities[ns].keys(): 1361 if group and not group in available_groups: 1362 del capabilities[ns][group] 1363 changed = True 1364 # update the capabilities and the view 1365 if changed: 1366 if capabilities: 1367 hostItem.addCapabilities('', capabilities, hostItem.masteruri) 1368 hostItem.clearUp() 1369 else: 1370 rospy.logwarn("Error on retrieve \'capability group\' parameter from %s: %s", str(masteruri), msg)
1371 1372
1373 - def set_std_capablilities(self, capabilities):
1374 ''' 1375 Sets the default capabilities description, which is assigned to each new 1376 host. 1377 @param capabilities: the structure for capabilities 1378 @type capabilities: C{dict(namespace: dict(group:dict('type' : str, 'description' : str, 'nodes' : [str])))} 1379 ''' 1380 self._std_capabilities = capabilities
1381
1382 - def addCapabilities(self, masteruri, host_address, cfg, capabilities):
1383 ''' 1384 Adds groups to the model 1385 @param masteruri: ROS master URI 1386 @type masteruri: C{str} 1387 @param host_address: the address the host 1388 @type host_address: C{str} 1389 @param cfg: the configuration name (launch file name or tupel for default configuration) 1390 @type cfg: C{str or (str, str))} 1391 @param capabilities: the structure for capabilities 1392 @type capabilities: C{dict(namespace: dict(group:dict('type' : str, 'description' : str, 'nodes' : [str])))} 1393 ''' 1394 hostItem = self.getHostItem(masteruri, host_address) 1395 if not hostItem is None: 1396 # add new capabilities 1397 hostItem.addCapabilities(cfg, capabilities, hostItem.masteruri) 1398 self.removeEmptyHosts()
1399
1400 - def appendConfigNodes(self, masteruri, host_address, nodes):
1401 ''' 1402 Adds nodes to the model. If the node is already in the model, only his 1403 configuration list will be extended. 1404 @param masteruri: ROS master URI 1405 @type masteruri: C{str} 1406 @param host_address: the address the host 1407 @type host_address: C{str} 1408 @param nodes: a dictionary with node names and their configurations 1409 @type nodes: C{dict(str : str)} 1410 ''' 1411 hostItem = self.getHostItem(masteruri, host_address) 1412 if not hostItem is None: 1413 groups = set() 1414 for (name, cfg) in nodes.items(): 1415 items = hostItem.getNodeItemsByName(name) 1416 for item in items: 1417 if not item.parent_item is None: 1418 groups.add(item.parent_item) 1419 # only added the config to the node, if the node is in the same group 1420 if isinstance(item.parent_item, HostItem): 1421 item.addConfig(cfg) 1422 elif hostItem.is_in_cap_group(item.name, cfg, rospy.names.namespace(item.name).rstrip(rospy.names.SEP), item.parent_item.name): 1423 item.addConfig(cfg) 1424 # test for default group 1425 elif hostItem.is_in_cap_group(item.name, '', '', item.parent_item.name): 1426 item.addConfig(cfg) 1427 else: 1428 item.addConfig(cfg) 1429 if not items: 1430 # create the new node 1431 node_info = NodeInfo(name, masteruri) 1432 hostItem.addNode(node_info, cfg) 1433 # get the group of the added node to be able to update the group view, if needed 1434 items = hostItem.getNodeItemsByName(name) 1435 for item in items: 1436 if not item.parent_item is None: 1437 groups.add(item.parent_item) 1438 # update the changed groups 1439 for g in groups: 1440 g.updateDisplayedConfig() 1441 self.removeEmptyHosts()
1442 # update the duplicate state 1443 # self.markNodesAsDuplicateOf(self.getRunningNodes()) 1444
1445 - def removeConfigNodes(self, cfg):
1446 ''' 1447 Removes nodes from the model. If node is running or containing in other 1448 launch or default configurations , only his configuration list will be 1449 reduced. 1450 @param cfg: the name of the confugration to close 1451 @type cfg: C{str} 1452 ''' 1453 for i in reversed(range(self.invisibleRootItem().rowCount())): 1454 host = self.invisibleRootItem().child(i) 1455 items = host.getNodeItems() 1456 groups = set() 1457 for item in items: 1458 removed = item.remConfig(cfg) 1459 if removed and not item.parent_item is None: 1460 groups.add(item.parent_item) 1461 for g in groups: 1462 g.updateDisplayedConfig() 1463 host.remCapablities(cfg) 1464 host.clearUp() 1465 if host.rowCount() == 0: 1466 self.invisibleRootItem().removeRow(i) 1467 elif groups: 1468 # request for all nodes in host the parameter capability_group 1469 self._requestCapabilityGroupParameter(host)
1470
1471 - def removeEmptyHosts(self):
1472 # remove empty hosts 1473 for i in reversed(range(self.invisibleRootItem().rowCount())): 1474 host = self.invisibleRootItem().child(i) 1475 if host.rowCount() == 0: 1476 self.invisibleRootItem().removeRow(i)
1477
1478 - def isDuplicateNode(self, node_name):
1479 for i in reversed(range(self.invisibleRootItem().rowCount())): 1480 host = self.invisibleRootItem().child(i) 1481 if not host is None: # should not occur 1482 nodes = host.getNodeItemsByName(node_name) 1483 for n in nodes: 1484 if n.has_running: 1485 return True 1486 return False
1487
1488 - def getNode(self, node_name):
1489 ''' 1490 Since the same node can be included by different groups, this method searches 1491 for all nodes with given name and returns these items. 1492 @param node_name: The name of the node 1493 @type node_name: C{str} 1494 @return: The list with node items. 1495 @rtype: C{[L{PySide.QtGui.QStandardItem}]} 1496 ''' 1497 for i in reversed(range(self.invisibleRootItem().rowCount())): 1498 host = self.invisibleRootItem().child(i) 1499 if not host is None: # should not occur 1500 return host.getNodeItemsByName(node_name) 1501 return []
1502
1503 - def getRunningNodes(self):
1504 ''' 1505 Returns a list with all known running nodes. 1506 @rtype: C{[str]} 1507 ''' 1508 running_nodes = list() 1509 ## determine all running nodes 1510 for i in reversed(range(self.invisibleRootItem().rowCount())): 1511 host = self.invisibleRootItem().child(i) 1512 if not host is None: # should not occur 1513 running_nodes[len(running_nodes):] = host.getRunningNodes() 1514 return running_nodes
1515
1516 - def markNodesAsDuplicateOf(self, running_nodes, is_sync_running=False):
1517 ''' 1518 If there are a synchronization running, you have to avoid to running the 1519 node with the same name on different hosts. This method helps to find the 1520 nodes with same name running on other hosts and loaded by a configuration. 1521 The nodes loaded by a configuration will be inform about a currently running 1522 nodes, so a warning can be displayed! 1523 @param running_nodes: The dictionary with names of running nodes and their masteruri 1524 @type running_nodes: C{dict(str:str)} 1525 @param is_sync_running: If the master_sync is running, the nodes are marked 1526 as ghost nodes. So they are handled as running nodes, but has not run 1527 informations. This nodes are running on remote host, but are not 1528 syncronized because of filter or errrors. 1529 @type is_sync_running: bool 1530 ''' 1531 for i in reversed(range(self.invisibleRootItem().rowCount())): 1532 host = self.invisibleRootItem().child(i) 1533 if not host is None: # should not occur 1534 host.markNodesAsDuplicateOf(running_nodes, is_sync_running)
1535
1536 - def updateHostDescription(self, masteruri, host, descr_type, descr_name, descr):
1537 ''' 1538 Updates the description of a host. 1539 @param masteruri: ROS master URI of the host to update 1540 @type masteruri: C{str} 1541 @param host: host to update 1542 @type host: C{str} 1543 @param descr_type: the type of the robot 1544 @type descr_type: C{str} 1545 @param descr_name: the name of the robot 1546 @type descr_name: C{str} 1547 @param descr: the description of the robot as a U{http://docutils.sourceforge.net/rst.html|reStructuredText} 1548 @type descr: C{str} 1549 ''' 1550 root = self.invisibleRootItem() 1551 for i in range(root.rowCount()): 1552 if root.child(i) == (unicode(masteruri), unicode(host)): 1553 h = root.child(i) 1554 h.updateDescription(descr_type, descr_name, descr) 1555 return h.updateTooltip()
1556