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(1)
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
189 - def remCapablities(self, config):
190 ''' 191 Removes internal entry of the capability, so the new nodes are not grouped. 192 To update view L{NodeTreeModel.removeConfigNodes()} and L{GroupItem.clearUp()} 193 must be called. 194 @param config: The name of the configuration containing this new capabilities. 195 @type config: C{str} 196 ''' 197 try: 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, _ in groups.items():#_:=decription 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(1) 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 _, 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(1)) 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 from docutils import examples 700 tooltip = '' 701 if self.descr_type or self.descr_name or self.descr: 702 tooltip += '<h4>%s</h4><dl>'%self.descr_name 703 if self.descr_type: 704 tooltip += '<dt>Type: %s</dt></dl>'%self.descr_type 705 if extended: 706 try: 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(1)) 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 tooltip += examples.html_body('- %s'%('\n- '.join(capabilities)), input_encoding='utf8') 733 except: 734 import traceback 735 rospy.logwarn("Error while generate description for a tooltip: %s", traceback.format_exc(1)) 736 return '<div>%s</div>'%tooltip if tooltip else ''
737
738 - def type(self):
739 return HostItem.ITEM_TYPE
740
741 - def __eq__(self, item):
742 ''' 743 Compares the address of the host. 744 ''' 745 if isinstance(item, str) or isinstance(item, unicode): 746 return str(self.id).lower() == item.lower() 747 elif isinstance(item, tuple): 748 return str(self.id).lower() == str(item).lower() 749 elif isinstance(item, HostItem): 750 return str(self.id).lower() == str(item.id).lower() 751 return False
752
753 - def __gt__(self, item):
754 ''' 755 Compares the address of the host. 756 ''' 757 if isinstance(item, str) or isinstance(item, unicode): 758 return str(self.id).lower() > item.lower() 759 elif isinstance(item, tuple): 760 return str(self.id).lower() > str(item).lower() 761 elif isinstance(item, HostItem): 762 return str(self.id).lower() > str(item.id).lower() 763 return False
764
765 766 ################################################################################ 767 ############## NodeItem ############## 768 ################################################################################ 769 770 -class NodeItem(QtGui.QStandardItem):
771 ''' 772 The NodeItem stores the information about the node using the ExtendedNodeInfo 773 class and represents it in a L{PySide.QtGui.QTreeModel} using the 774 L{PySide.QtGui.QStandardItemModel} 775 ''' 776 777 ITEM_TYPE = QtGui.QStandardItem.UserType + 35 778 NAME_ROLE = QtCore.Qt.UserRole + 1 779 COL_CFG = 1 780 # COL_URI = 2 781 782 STATE_OFF = 0 783 STATE_RUN = 1 784 STATE_WARNING = 2 785 STATE_GHOST = 3 786 STATE_DUPLICATE = 4 787
788 - def __init__(self, node_info):
789 ''' 790 Initialize the NodeItem instance. 791 @param node_info: the node information 792 @type node_info: L{master_discovery_fkie.NodeInfo} 793 ''' 794 QtGui.QStandardItem.__init__(self, node_info.name) 795 self.parent_item = None 796 self._node_info = node_info.copy() 797 # self.ICONS = {'empty' : QtGui.QIcon(), 798 # 'run' : QtGui.QIcon(':/icons/state_run.png'), 799 # 'off' :QtGui.QIcon(':/icons/state_off.png'), 800 # 'warning' : QtGui.QIcon(':/icons/crystal_clear_warning.png'), 801 # 'stop' : QtGui.QIcon('icons/imacadam_stop.png'), 802 # 'cfg+def' : QtGui.QIcon(':/icons/crystal_clear_launch_file_def_cfg.png'), 803 # 'cfg' : QtGui.QIcon(':/icons/crystal_clear_launch_file.png'), 804 # 'default_cfg' : QtGui.QIcon(':/icons/default_cfg.png') 805 # } 806 self._cfgs = [] 807 self._std_config = None # it's config with empty name. for default proposals 808 self._is_ghost = False 809 self._has_running = False 810 self.setIcon(QtGui.QIcon(':/icons/state_off.png')) 811 self._state = NodeItem.STATE_OFF
812 813 @property
814 - def state(self):
815 return self._state
816 817 @property
818 - def name(self):
819 return self._node_info.name
820 821 @property
822 - def masteruri(self):
823 return self._node_info.masteruri
824 825 @property
826 - def published(self):
827 return self._node_info.publishedTopics
828 829 @property
830 - def subscribed(self):
831 return self._node_info.subscribedTopics
832 833 @property
834 - def services(self):
835 return self._node_info.services
836 837 @property
838 - def node_info(self):
839 ''' 840 Returns the NodeInfo instance of this node. 841 @rtype: L{master_discovery_fkie.NodeInfo} 842 ''' 843 return self._node_info
844 845 @node_info.setter
846 - def node_info(self, node_info):
847 ''' 848 Sets the NodeInfo and updates the view, if needed. 849 ''' 850 abbos_changed = False 851 run_changed = False 852 # print "!!!", self.name 853 # print " subs: ", self._node_info.subscribedTopics, node_info.subscribedTopics 854 # print " pubs: ", self._node_info.publishedTopics, node_info.publishedTopics 855 # print " srvs: ", self._node_info.services, node_info.services 856 if self._node_info.publishedTopics != node_info.publishedTopics: 857 abbos_changed = True 858 self._node_info._publishedTopics = list(node_info.publishedTopics) 859 if self._node_info.subscribedTopics != node_info.subscribedTopics: 860 abbos_changed = True 861 self._node_info._subscribedTopics = list(node_info.subscribedTopics) 862 if self._node_info.services != node_info.services: 863 abbos_changed = True 864 self._node_info._services = list(node_info.services) 865 if self._node_info.pid != node_info.pid: 866 self._node_info.pid = node_info.pid 867 run_changed = True 868 if self._node_info.uri != node_info.uri: 869 self._node_info.uri = node_info.uri 870 run_changed = True 871 # update the tooltip and icon 872 if run_changed and (self.is_running() or self.has_configs) or abbos_changed: 873 self.updateDispayedName() 874 # self.updateDisplayedURI() 875 if not self.parent_item is None and not isinstance(self.parent_item, HostItem): 876 self.parent_item.updateIcon()
877 878 @property
879 - def uri(self):
880 return self._node_info.uri
881 882 @property
883 - def pid(self):
884 return self._node_info.pid
885 886 @property
887 - def has_running(self):
888 ''' 889 Returns C{True}, if there are exists other nodes with the same name. This 890 variable must be set manually! 891 @rtype: C{bool} 892 ''' 893 return self._has_running
894 895 @has_running.setter
896 - def has_running(self, state):
897 ''' 898 Sets however other node with the same name are running or not (on other hosts) 899 and updates the view of this item. 900 ''' 901 if self._has_running != state: 902 self._has_running = state 903 if self.has_configs() or self.is_running(): 904 self.updateDispayedName() 905 if not self.parent_item is None and not isinstance(self.parent_item, HostItem): 906 self.parent_item.updateIcon()
907 908 @property
909 - def is_ghost(self):
910 ''' 911 Returns C{True}, if there are exists other runnig nodes with the same name. This 912 variable must be set manually! 913 @rtype: C{bool} 914 ''' 915 return self._is_ghost
916 917 @is_ghost.setter
918 - def is_ghost(self, state):
919 ''' 920 Sets however other node with the same name is running (on other hosts) and 921 and the host showing this node the master_sync is running, but the node is 922 not synchronized. 923 ''' 924 if self._is_ghost != state: 925 self._is_ghost = state 926 if self.has_configs() or self.is_running(): 927 self.updateDispayedName() 928 if not self.parent_item is None and not isinstance(self.parent_item, HostItem): 929 self.parent_item.updateIcon()
930
931 - def data(self, role):
932 if role == self.NAME_ROLE: 933 return self.name 934 else: 935 return QtGui.QStandardItem.data(self, role)
936
937 - def updateDispayedName(self):
938 ''' 939 Updates the name representation of the Item 940 ''' 941 tooltip = '<h4>%s</h4><dl>'%self.node_info.name 942 tooltip += '<dt><b>URI:</b> %s</dt>'%self.node_info.uri 943 tooltip += '<dt><b>PID:</b> %s</dt>'%self.node_info.pid 944 tooltip += '<dt><b>ORG.MASTERURI:</b> %s</dt></dl>'%self.node_info.masteruri 945 #'print "updateDispayedName - hasMaster" 946 master_discovered = nm.nameres().hasMaster(self.node_info.masteruri) 947 # local = False 948 # if not self.node_info.uri is None and not self.node_info.masteruri is None: 949 # local = (nm.nameres().getHostname(self.node_info.uri) == nm.nameres().getHostname(self.node_info.masteruri)) 950 if not self.node_info.pid is None: 951 self._state = NodeItem.STATE_RUN 952 self.setIcon(QtGui.QIcon(':/icons/state_run.png')) 953 # self.setIcon(ICONS['run']) 954 self.setToolTip('') 955 elif not self.node_info.uri is None and not self.node_info.isLocal: 956 self._state = NodeItem.STATE_RUN 957 self.setIcon(QtGui.QIcon(':/icons/state_unknown.png')) 958 tooltip += '<dl><dt>(Remote nodes will not be ping, so they are always marked running)</dt></dl>' 959 tooltip += '</dl>' 960 self.setToolTip('<div>%s</div>'%tooltip) 961 # elif not self.node_info.isLocal and not master_discovered and not self.node_info.uri is None: 962 ## elif not local and not master_discovered and not self.node_info.uri is None: 963 # self._state = NodeItem.STATE_RUN 964 # self.setIcon(QtGui.QIcon(':/icons/state_run.png')) 965 # tooltip = ''.join([tooltip, '<dl><dt>(Remote nodes will not be ping, so they are always marked running)</dt></dl>']) 966 # tooltip = ''.join([tooltip, '</dl>']) 967 # self.setToolTip(''.join(['<div>', tooltip, '</div>'])) 968 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): 969 self.setIcon(QtGui.QIcon(':/icons/crystal_clear_warning.png')) 970 self._state = NodeItem.STATE_WARNING 971 tooltip += '<dl><dt>Can\'t get node contact information, but there exists publisher, subscriber or services of this node.</dt></dl>' 972 tooltip += '</dl>' 973 self.setToolTip('<div>%s</div>'%tooltip) 974 elif not self.node_info.uri is None: 975 self._state = NodeItem.STATE_WARNING 976 self.setIcon(QtGui.QIcon(':/icons/crystal_clear_warning.png')) 977 if not self.node_info.isLocal and master_discovered: 978 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 979 self.setToolTip('<div>%s</div>'%tooltip) 980 elif self.is_ghost: 981 self._state = NodeItem.STATE_GHOST 982 self.setIcon(QtGui.QIcon(':/icons/state_ghost.png')) 983 tooltip = '<h4>The node is running, but not synchronized because of filter or errors, see master_sync log.</h4>' 984 self.setToolTip('<div>%s</div>'%tooltip) 985 elif self.has_running: 986 self._state = NodeItem.STATE_DUPLICATE 987 self.setIcon(QtGui.QIcon(':/icons/imacadam_stop.png')) 988 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>' 989 self.setToolTip('<div>%s</div>'%tooltip) 990 else: 991 self._state = NodeItem.STATE_OFF 992 self.setIcon(QtGui.QIcon(':/icons/state_off.png')) 993 self.setToolTip('')
994 #'print "- updateDispayedName" 995 996 # removed common tooltip for clarity !!! 997 # self.setToolTip(''.join(['<div>', tooltip, '</div>'])) 998
999 - def updateDisplayedURI(self):
1000 ''' 1001 Updates the URI representation in other column. 1002 ''' 1003 if not self.parent_item is None: 1004 uri_col = self.parent_item.child(self.row(), NodeItem.COL_URI) 1005 if not uri_col is None and isinstance(uri_col, QtGui.QStandardItem): 1006 uri_col.setText(str(self.node_info.uri) if not self.node_info.uri is None else "")
1007 1008 @property
1009 - def cfgs(self):
1010 ''' 1011 Returns the list with all launch configurations assigned to this item. 1012 @rtype: C{[str]} 1013 ''' 1014 return self._cfgs
1015
1016 - def addConfig(self, cfg):
1017 ''' 1018 Add the given configurations to the node. 1019 @param cfg: the loaded configuration, which contains this node. 1020 @type cfg: C{str} 1021 ''' 1022 if cfg == '': 1023 self._std_config = cfg 1024 elif cfg and not cfg in self._cfgs: 1025 self._cfgs.append(cfg) 1026 self.updateDisplayedConfig()
1027
1028 - def remConfig(self, cfg):
1029 ''' 1030 Remove the given configurations from the node. 1031 @param cfg: the loaded configuration, which contains this node. 1032 @type cfg: C{str} 1033 ''' 1034 result = False 1035 if cfg == '': 1036 self._std_config = None 1037 result = True 1038 if cfg in self._cfgs: 1039 self._cfgs.remove(cfg) 1040 result = True 1041 if result and (self.has_configs() or self.is_running()): 1042 self.updateDisplayedConfig() 1043 return result
1044
1045 - def updateDisplayedConfig(self):
1046 ''' 1047 Updates the configuration representation in other column. 1048 ''' 1049 if not self.parent_item is None: 1050 cfg_col = self.parent_item.child(self.row(), NodeItem.COL_CFG) 1051 if not cfg_col is None and isinstance(cfg_col, QtGui.QStandardItem): 1052 cfg_count = len(self._cfgs) 1053 cfg_col.setText(str(''.join(['[',str(cfg_count),']'])) if cfg_count > 1 else "") 1054 # set tooltip 1055 # removed tooltip for clarity !!! 1056 # tooltip = '' 1057 # if len(self._cfgs) > 0: 1058 # tooltip = '' 1059 # if len(self._cfgs) > 0: 1060 # tooltip = ''.join([tooltip, '<h4>', 'Configurations:', '</h4><dl>']) 1061 # for c in self._cfgs: 1062 # if NodeItem.is_default_cfg(c): 1063 # tooltip = ''.join([tooltip, '<dt>[default]', c[0], '</dt>']) 1064 # else: 1065 # tooltip = ''.join([tooltip, '<dt>', c, '</dt>']) 1066 # tooltip = ''.join([tooltip, '</dl>']) 1067 # cfg_col.setToolTip(''.join(['<div>', tooltip, '</div>'])) 1068 # set icons 1069 has_launches = NodeItem.has_launch_cfgs(self._cfgs) 1070 has_defaults = NodeItem.has_default_cfgs(self._cfgs) 1071 if has_launches and has_defaults: 1072 cfg_col.setIcon(QtGui.QIcon(':/icons/crystal_clear_launch_file_def_cfg.png')) 1073 elif has_launches: 1074 cfg_col.setIcon(QtGui.QIcon(':/icons/crystal_clear_launch_file.png')) 1075 elif has_defaults: 1076 cfg_col.setIcon(QtGui.QIcon(':/icons/default_cfg.png')) 1077 else: 1078 cfg_col.setIcon(QtGui.QIcon())
1079 # the update of the group will be perform in node_tree_model to reduce calls 1080 # if isinstance(self.parent_item, GroupItem): 1081 # self.parent_item.updateDisplayedConfig() 1082
1083 - def type(self):
1084 return NodeItem.ITEM_TYPE
1085 1086 @classmethod
1087 - def newNodeRow(self, name, masteruri):
1088 ''' 1089 Creates a new node row and returns it as a list with items. This list is 1090 used for the visualization of node data as a table row. 1091 @param name: the node name 1092 @type name: C{str} 1093 @param masteruri: the URI or the ROS master assigned to this node. 1094 @type masteruri: C{str} 1095 @return: the list for the representation as a row 1096 @rtype: C{[L{NodeItem}, L{PySide.QtGui.QStandardItem}(Cofigurations), L{PySide.QtGui.QStandardItem}(Node URI)]} 1097 ''' 1098 items = [] 1099 item = NodeItem(NodeInfo(name, masteruri)) 1100 items.append(item) 1101 cfgitem = QtGui.QStandardItem() 1102 items.append(cfgitem) 1103 # uriitem = QtGui.QStandardItem() 1104 # items.append(uriitem) 1105 return items
1106
1107 - def has_configs(self):
1108 return not (len(self._cfgs) == 0)# and self._std_config is None)
1109
1110 - def is_running(self):
1111 return not (self._node_info.pid is None and self._node_info.uri is None)
1112
1113 - def has_std_cfg(self):
1114 return self._std_config == ''
1115 1116 @classmethod
1117 - def has_launch_cfgs(cls, cfgs):
1118 for c in cfgs: 1119 if not cls.is_default_cfg(c): 1120 return True 1121 return False
1122 1123 @classmethod
1124 - def has_default_cfgs(cls, cfgs):
1125 for c in cfgs: 1126 if cls.is_default_cfg(c): 1127 return True 1128 return False
1129 1130 @classmethod
1131 - def is_default_cfg(cls, cfg):
1132 return isinstance(cfg, tuple)
1133
1134 - def __eq__(self, item):
1135 ''' 1136 Compares the name of the node. 1137 ''' 1138 if isinstance(item, str) or isinstance(item, unicode): 1139 return self.name == item 1140 elif not (item is None): 1141 return self.name == item.name 1142 return False
1143
1144 - def __gt__(self, item):
1145 ''' 1146 Compares the name of the node. 1147 ''' 1148 if isinstance(item, str) or isinstance(item, unicode): 1149 return self.name > item 1150 elif not (item is None): 1151 return self.name > item.name 1152 return False
1153
1154 1155 ################################################################################ 1156 ############## NodeTreeModel ############## 1157 ################################################################################ 1158 1159 -class NodeTreeModel(QtGui.QStandardItemModel):
1160 ''' 1161 The model to show the nodes running in a ROS system or loaded by a launch 1162 configuration. 1163 ''' 1164 # ICONS = {'default' : QtGui.QIcon(), 1165 # 'run' : QtGui.QIcon(":/icons/state_run.png"), 1166 # 'warning' : QtGui.QIcon(":/icons/crystal_clear_warning.png"), 1167 # 'def_launch_cfg' : QtGui.QIcon(":/icons/crystal_clear_launch_file_def_cfg.png"), 1168 # 'launch_cfg' : QtGui.QIcon(":/icons/crystal_clear_launch_file.png"), 1169 # 'def_cfg' : QtGui.QIcon(":/icons/default_cfg.png") } 1170 1171 header = [('Name', 450), 1172 ('Cfgs', -1)] 1173 # ('URI', -1)] 1174 1175 hostInserted = QtCore.Signal(HostItem) 1176 '''@ivar: the Qt signal, which is emitted, if a new host was inserted. 1177 Parameter: L{QtCore.QModelIndex} of the inserted host item''' 1178
1179 - def __init__(self, host_address, masteruri, parent=None):
1180 ''' 1181 Initialize the model. 1182 ''' 1183 super(NodeTreeModel, self).__init__(parent) 1184 self.setColumnCount(len(NodeTreeModel.header)) 1185 self.setHorizontalHeaderLabels([label for label, _ in NodeTreeModel.header]) 1186 self._local_host_address = host_address 1187 self._std_capabilities = {'': {'SYSTEM': {'images': [], 1188 'nodes': [ '/rosout', 1189 '/master_discovery', 1190 '/zeroconf', 1191 '/master_sync', 1192 '/node_manager', 1193 '/dynamic_reconfigure/*'], 1194 'type': '', 1195 'description': 'This group contains the system management nodes.'} } } 1196 1197 #create a handler to request the parameter 1198 self.parameterHandler = ParameterHandler() 1199 # self.parameterHandler.parameter_list_signal.connect(self._on_param_list) 1200 self.parameterHandler.parameter_values_signal.connect(self._on_param_values)
1201 # self.parameterHandler.delivery_result_signal.connect(self._on_delivered_values) 1202 1203 1204 @property
1205 - def local_addr(self):
1206 return self._local_host_address
1207
1208 - def flags(self, index):
1209 if not index.isValid(): 1210 return QtCore.Qt.NoItemFlags 1211 return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
1212
1213 - def _set_std_capabilities(self, host_item):
1214 if not host_item is None: 1215 cap = self._std_capabilities 1216 hostname = roslib.names.SEP.join(['', host_item.hostname, '*', 'default_cfg']) 1217 if not hostname in cap['']['SYSTEM']['nodes']: 1218 cap['']['SYSTEM']['nodes'].append(hostname) 1219 host_item.addCapabilities('', cap, host_item.masteruri) 1220 return cap 1221 return dict(self._std_capabilities)
1222
1223 - def getHostItem(self, masteruri, address):
1224 ''' 1225 Searches for the host item in the model. If no item is found a new one will 1226 created and inserted in sorted order. 1227 @param masteruri: ROS master URI 1228 @type masteruri: C{str} 1229 @param address: the address of the host 1230 @type address: C{str} 1231 @return: the item associated with the given master 1232 @rtype: L{HostItem} 1233 ''' 1234 if masteruri is None: 1235 return None 1236 host = (unicode(masteruri), unicode(address)) 1237 local = (self.local_addr == host) 1238 # find the host item by address 1239 root = self.invisibleRootItem() 1240 for i in range(root.rowCount()): 1241 if root.child(i) == host: 1242 return root.child(i) 1243 elif root.child(i) > host: 1244 hostItem = HostItem(masteruri, address, local) 1245 self.insertRow(i, hostItem) 1246 self.hostInserted.emit(hostItem) 1247 self._set_std_capabilities(hostItem) 1248 return hostItem 1249 hostItem = HostItem(masteruri, address, local) 1250 self.appendRow(hostItem) 1251 self.hostInserted.emit(hostItem) 1252 self._set_std_capabilities(hostItem) 1253 return hostItem
1254
1255 - def updateModelData(self, nodes):
1256 ''' 1257 Updates the model data. 1258 @param nodes: a dictionary with name and info objects of the nodes. 1259 @type nodes: C{dict(str:L{NodeInfo}, ...)} 1260 ''' 1261 # separate into different hosts 1262 hosts = dict() 1263 for (name, node) in nodes.items(): 1264 addr = nm.nameres().getHostname(node.uri if not node.uri is None else node.masteruri) 1265 host = (node.masteruri, addr) 1266 if not hosts.has_key(host): 1267 hosts[host] = dict() 1268 hosts[host][name] = node 1269 # update nodes for each host 1270 for ((masteruri, host), nodes_filtered) in hosts.items(): 1271 hostItem = self.getHostItem(masteruri, host) 1272 # rename the host item if needed 1273 if not hostItem is None: 1274 hostItem.updateRunningNodeState(nodes_filtered) 1275 # update nodes of the hosts, which are not more exists 1276 for i in reversed(range(self.invisibleRootItem().rowCount())): 1277 host = self.invisibleRootItem().child(i) 1278 if not hosts.has_key(host.id): 1279 host.updateRunningNodeState({}) 1280 self.removeEmptyHosts() 1281 # request for all nodes in host the parameter capability_group 1282 for ((masteruri, host), nodes_filtered) in hosts.items(): 1283 hostItem = self.getHostItem(masteruri, host) 1284 self._requestCapabilityGroupParameter(hostItem)
1285 # update the duplicate state 1286 # self.markNodesAsDuplicateOf(self.getRunningNodes()) 1287
1288 - def _requestCapabilityGroupParameter(self, host_item):
1289 if not host_item is None: 1290 items = host_item.getNodeItems() 1291 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')] 1292 if params: 1293 self.parameterHandler.requestParameterValues(host_item.masteruri, params)
1294
1295 - def _on_param_values(self, masteruri, code, msg, params):
1296 ''' 1297 Updates the capability groups of nodes from ROS parameter server. 1298 @param masteruri: The URI of the ROS parameter server 1299 @type masteruri: C{str} 1300 @param code: The return code of the request. If not 1, the message is set and the list can be ignored. 1301 @type code: C{int} 1302 @param msg: The message of the result. 1303 @type msg: C{str} 1304 @param params: The dictionary the parameter names and request result. 1305 @type param: C{dict(paramName : (code, statusMessage, parameterValue))} 1306 ''' 1307 host = nm.nameres().address(masteruri) 1308 hostItem = self.getHostItem(masteruri, host) 1309 changed = False 1310 if not hostItem is None and code == 1: 1311 capabilities = self._set_std_capabilities(hostItem) 1312 available_ns = set(['']) 1313 available_groups = set(['SYSTEM']) 1314 # assumption: all parameter are 'capability_group' parameter 1315 for p, (code_n, _, val) in params.items():#_:=msg_n 1316 nodename = roslib.names.namespace(p).rstrip(roslib.names.SEP) 1317 ns = roslib.names.namespace(nodename).rstrip(roslib.names.SEP) 1318 available_ns.add(ns) 1319 if code_n == 1: 1320 # add group 1321 if val: 1322 available_groups.add(val) 1323 if not capabilities.has_key(ns): 1324 capabilities[ns] = dict() 1325 if not capabilities[ns].has_key(val): 1326 capabilities[ns][val] = {'images': [], 'nodes': [], 'type': '', 'description': 'This group is created from `capability_group` parameter of the node defined in ROS parameter server.' } 1327 if not nodename in capabilities[ns][val]['nodes']: 1328 capabilities[ns][val]['nodes'].append(nodename) 1329 changed = True 1330 else: 1331 try: 1332 for group, _ in capabilities[ns].items(): 1333 try: 1334 #remove the config from item, if parameter was not foun on the ROS parameter server 1335 groupItem = hostItem.getGroupItem(roslib.names.ns_join(ns,group)) 1336 if not groupItem is None: 1337 nodeItems = groupItem.getNodeItemsByName(nodename, True) 1338 for item in nodeItems: 1339 item.remConfig('') 1340 capabilities[ns][group]['nodes'].remove(nodename) 1341 # remove the group, if empty 1342 if not capabilities[ns][group]['nodes']: 1343 del capabilities[ns][group] 1344 if not capabilities[ns]: 1345 del capabilities[ns] 1346 groupItem.updateDisplayedConfig() 1347 changed = True 1348 except: 1349 pass 1350 except: 1351 pass 1352 # clearup namespaces to remove empty groups 1353 for ns in capabilities.keys(): 1354 if ns and not ns in available_ns: 1355 del capabilities[ns] 1356 changed = True 1357 else: 1358 for group in capabilities[ns].keys(): 1359 if group and not group in available_groups: 1360 del capabilities[ns][group] 1361 changed = True 1362 # update the capabilities and the view 1363 if changed: 1364 if capabilities: 1365 hostItem.addCapabilities('', capabilities, hostItem.masteruri) 1366 hostItem.clearUp() 1367 else: 1368 rospy.logwarn("Error on retrieve \'capability group\' parameter from %s: %s", str(masteruri), msg)
1369 1370
1371 - def set_std_capablilities(self, capabilities):
1372 ''' 1373 Sets the default capabilities description, which is assigned to each new 1374 host. 1375 @param capabilities: the structure for capabilities 1376 @type capabilities: C{dict(namespace: dict(group:dict('type' : str, 'description' : str, 'nodes' : [str])))} 1377 ''' 1378 self._std_capabilities = capabilities
1379
1380 - def addCapabilities(self, masteruri, host_address, cfg, capabilities):
1381 ''' 1382 Adds groups to the model 1383 @param masteruri: ROS master URI 1384 @type masteruri: C{str} 1385 @param host_address: the address the host 1386 @type host_address: C{str} 1387 @param cfg: the configuration name (launch file name or tupel for default configuration) 1388 @type cfg: C{str or (str, str))} 1389 @param capabilities: the structure for capabilities 1390 @type capabilities: C{dict(namespace: dict(group:dict('type' : str, 'description' : str, 'nodes' : [str])))} 1391 ''' 1392 hostItem = self.getHostItem(masteruri, host_address) 1393 if not hostItem is None: 1394 # add new capabilities 1395 hostItem.addCapabilities(cfg, capabilities, hostItem.masteruri) 1396 self.removeEmptyHosts()
1397
1398 - def appendConfigNodes(self, masteruri, host_address, nodes):
1399 ''' 1400 Adds nodes to the model. If the node is already in the model, only his 1401 configuration list will be extended. 1402 @param masteruri: ROS master URI 1403 @type masteruri: C{str} 1404 @param host_address: the address the host 1405 @type host_address: C{str} 1406 @param nodes: a dictionary with node names and their configurations 1407 @type nodes: C{dict(str : str)} 1408 ''' 1409 hostItem = self.getHostItem(masteruri, host_address) 1410 if not hostItem is None: 1411 groups = set() 1412 for (name, cfg) in nodes.items(): 1413 items = hostItem.getNodeItemsByName(name) 1414 for item in items: 1415 if not item.parent_item is None: 1416 groups.add(item.parent_item) 1417 # only added the config to the node, if the node is in the same group 1418 if isinstance(item.parent_item, HostItem): 1419 item.addConfig(cfg) 1420 elif hostItem.is_in_cap_group(item.name, cfg, rospy.names.namespace(item.name).rstrip(rospy.names.SEP), item.parent_item.name): 1421 item.addConfig(cfg) 1422 # test for default group 1423 elif hostItem.is_in_cap_group(item.name, '', '', item.parent_item.name): 1424 item.addConfig(cfg) 1425 else: 1426 item.addConfig(cfg) 1427 if not items: 1428 # create the new node 1429 node_info = NodeInfo(name, masteruri) 1430 hostItem.addNode(node_info, cfg) 1431 # get the group of the added node to be able to update the group view, if needed 1432 items = hostItem.getNodeItemsByName(name) 1433 for item in items: 1434 if not item.parent_item is None: 1435 groups.add(item.parent_item) 1436 # update the changed groups 1437 for g in groups: 1438 g.updateDisplayedConfig() 1439 self.removeEmptyHosts()
1440 # update the duplicate state 1441 # self.markNodesAsDuplicateOf(self.getRunningNodes()) 1442
1443 - def removeConfigNodes(self, cfg):
1444 ''' 1445 Removes nodes from the model. If node is running or containing in other 1446 launch or default configurations , only his configuration list will be 1447 reduced. 1448 @param cfg: the name of the confugration to close 1449 @type cfg: C{str} 1450 ''' 1451 for i in reversed(range(self.invisibleRootItem().rowCount())): 1452 host = self.invisibleRootItem().child(i) 1453 items = host.getNodeItems() 1454 groups = set() 1455 for item in items: 1456 removed = item.remConfig(cfg) 1457 if removed and not item.parent_item is None: 1458 groups.add(item.parent_item) 1459 for g in groups: 1460 g.updateDisplayedConfig() 1461 host.remCapablities(cfg) 1462 host.clearUp() 1463 if host.rowCount() == 0: 1464 self.invisibleRootItem().removeRow(i) 1465 elif groups: 1466 # request for all nodes in host the parameter capability_group 1467 self._requestCapabilityGroupParameter(host)
1468
1469 - def removeEmptyHosts(self):
1470 # remove empty hosts 1471 for i in reversed(range(self.invisibleRootItem().rowCount())): 1472 host = self.invisibleRootItem().child(i) 1473 if host.rowCount() == 0: 1474 self.invisibleRootItem().removeRow(i)
1475
1476 - def isDuplicateNode(self, node_name):
1477 for i in reversed(range(self.invisibleRootItem().rowCount())): 1478 host = self.invisibleRootItem().child(i) 1479 if not host is None: # should not occur 1480 nodes = host.getNodeItemsByName(node_name) 1481 for n in nodes: 1482 if n.has_running: 1483 return True 1484 return False
1485
1486 - def getNode(self, node_name, masteruri):
1487 ''' 1488 Since the same node can be included by different groups, this method searches 1489 for all nodes with given name and returns these items. 1490 @param node_name: The name of the node 1491 @type node_name: C{str} 1492 @return: The list with node items. 1493 @rtype: C{[L{PySide.QtGui.QStandardItem}]} 1494 ''' 1495 for i in reversed(range(self.invisibleRootItem().rowCount())): 1496 host = self.invisibleRootItem().child(i) 1497 if not host is None and (masteruri is None or host.masteruri == masteruri): 1498 res = host.getNodeItemsByName(node_name) 1499 if res: 1500 return res 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