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

Source Code for Module node_manager_fkie.profile_widget

  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  from python_qt_binding import loadUi 
 33  from python_qt_binding.QtCore import QTimer 
 34  try: 
 35      from python_qt_binding.QtGui import QDockWidget, QFileDialog 
 36  except: 
 37      from python_qt_binding.QtWidgets import QDockWidget, QFileDialog 
 38  import os 
 39  import roslib 
 40  import rospy 
 41  import uuid 
 42  from master_discovery_fkie.common import get_hostname, resolve_url 
 43   
 44  import node_manager_fkie as nm 
 45  from .common import get_rosparam, delete_rosparam, package_name, to_url, utf8 
 46  from .detailed_msg_box import MessageBox 
 47   
 48   
49 -class ProfileWidget(QDockWidget):
50 ''' 51 Profile widget to show the current load state of the profile 52 ''' 53
54 - def __init__(self, main_window, parent=None):
55 ''' 56 Creates the window, connects the signals and init the class. 57 ''' 58 QDockWidget.__init__(self, parent) 59 # load the UI file 60 profile_dock_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ProfileWidget.ui') 61 loadUi(profile_dock_file, self) 62 self._main_window = main_window 63 self.setVisible(False) 64 self._current_profile = dict()
65
66 - def get_profile_file(self):
67 # save the profile 68 (path, _) = QFileDialog.getSaveFileName(self, 69 "New profile file", 70 nm.settings().current_dialog_path, 71 "node manager profile files (*.nmprofile);;All files (*)") # _:=filter 72 if path: 73 if not path.endswith('.nmprofile'): 74 path = "%s.nmprofile" % path 75 nm.settings().current_dialog_path = os.path.dirname(path) 76 try: 77 (pkg, _) = package_name(os.path.dirname(path)) # _:=pkg_path 78 if pkg is None: 79 ret = MessageBox.warning(self, "New File Error", 80 'The new file is not in a ROS package', buttons=MessageBox.Ok | MessageBox.Cancel) 81 if ret == MessageBox.Cancel: 82 return None 83 return path 84 except EnvironmentError as e: 85 MessageBox.warning(self, "New File Error", 86 'Error while create a new file', 87 utf8(e)) 88 return None
89
90 - def on_save_profile(self, masteruri='', path=None):
91 ''' 92 Saves the current environment to a node manager profile. 93 :param path: the pach the file to save 94 :type path: str 95 :param masteruri: If not empty, save the profile only for given master 96 :type masteruri: str 97 ''' 98 try: 99 if path is None: 100 path = self.get_profile_file() 101 if path is None: 102 return 103 rospy.loginfo("Save profile %s" % path) 104 import yaml 105 content = {} 106 for muri, master in self._main_window.masters.items(): 107 if not masteruri or masteruri == muri: 108 running_nodes = master.getRunningNodesIfLocal() 109 configs = {} 110 md_param = {} 111 ms_param = {} 112 zc_param = {} 113 smuri = muri 114 addr = nm.nameres().address(smuri) 115 hostname = get_hostname(smuri) 116 mastername = '' 117 if nm.is_local(addr): 118 smuri = smuri.replace(hostname, '$LOCAL$') 119 addr = '$LOCAL$' 120 else: 121 mastername = nm.nameres().mastername(smuri, nm.nameres().address(smuri)) 122 for node_name in running_nodes.keys(): 123 node_items = master.getNode(node_name) 124 for node in node_items: 125 if node.is_running() and node.launched_cfg is not None: # node.has_launch_cfgs(node.cfgs): 126 cfg = node.launched_cfg 127 if isinstance(node.launched_cfg, (str, unicode)): 128 # it is default config 129 cfg = node.launched_cfg.replace("/%s" % hostname, '/$LOCAL$').rstrip('/run') 130 else: 131 # it is a loaded launch file, get the filename 132 cfg = to_url(node.launched_cfg.Filename) 133 if cfg not in configs: 134 configs[cfg] = {'nodes': []} 135 configs[cfg]['nodes'].append(node_name) 136 elif node_name.endswith('master_discovery'): 137 md_param = get_rosparam('master_discovery', muri) 138 elif node_name.endswith('master_sync'): 139 ms_param = get_rosparam('master_sync', muri) 140 elif node_name.endswith('zeroconf'): 141 zc_param = get_rosparam('zeroconf', muri) 142 elif node_name.endswith('default_cfg'): 143 # store parameter for default configuration 144 nn = node_name.replace("/%s" % hostname, '/$LOCAL$') 145 if nn not in configs: 146 configs[nn] = {'nodes': []} 147 configs[nn]['params'] = get_rosparam(node_name, muri) 148 configs[nn]['default'] = True 149 # store arguments for launchfiles 150 for a, b in master.launchfiles.items(): 151 resolved_a = to_url(a) 152 if resolved_a not in configs: 153 if resolved_a.endswith('default_cfg/run'): 154 pass 155 else: 156 configs[resolved_a] = {} 157 configs[resolved_a]['default'] = False 158 configs[resolved_a]['argv'] = b.argv 159 # fill the configuration content for yaml as dictionary 160 content[smuri] = {'mastername': mastername, 161 'address': addr, 162 'configs': configs} 163 if md_param: 164 content[smuri]['master_discovery'] = md_param 165 if ms_param: 166 content[smuri]['master_sync'] = ms_param 167 if zc_param: 168 content[smuri]['zeroconf'] = zc_param 169 text = yaml.dump(content, default_flow_style=False) 170 with open(path, 'w+') as f: 171 f.write(text) 172 except Exception as e: 173 import traceback 174 print utf8(traceback.format_exc(3)) 175 MessageBox.warning(self, "Save profile Error", 176 'Error while save profile', 177 utf8(e))
178
179 - def on_load_profile_file(self, path):
180 ''' 181 Load the profile file. 182 :param path: the path of the profile file. 183 :type path: str 184 ''' 185 rospy.loginfo("Load profile %s" % path) 186 self.progressBar.setValue(0) 187 self.setVisible(True) 188 self.setWindowTitle("%s profile started" % os.path.basename(path).rstrip('.nmprofile')) 189 hasstart = False 190 if path: 191 try: 192 import yaml 193 with open(path, 'r') as f: 194 # print yaml.load(f.read()) 195 content = yaml.load(f.read()) 196 if not isinstance(content, dict): 197 raise Exception("Mailformed profile: %s" % os.path.basename(path)) 198 for muri, master_dict in content.items(): 199 local_hostname = get_hostname(self._main_window.getMasteruri()) 200 rmuri = muri.replace('$LOCAL$', local_hostname) 201 master = self._main_window.getMaster(rmuri) 202 running_nodes = master.getRunningNodesIfLocal() 203 usr = None 204 self._current_profile[rmuri] = set() 205 if 'user' in master_dict: 206 usr = master_dict['user'] 207 if master_dict['mastername'] and master_dict['mastername']: 208 nm.nameres().add_master_entry(master.masteruri, master_dict['mastername'], master_dict['address']) 209 hostname = master_dict['address'].replace('$LOCAL$', local_hostname) 210 if 'master_discovery' in master_dict: 211 self._start_node_from_profile(master, hostname, 'master_discovery_fkie', 'master_discovery', usr, cfg=master_dict['master_discovery']) 212 self._current_profile[rmuri].add('/master_discovery') 213 if 'master_sync' in master_dict: 214 self._start_node_from_profile(master, hostname, 'master_sync_fkie', 'master_sync', usr, cfg=master_dict['master_sync']) 215 self._current_profile[rmuri].add('/master_sync') 216 if 'zeroconf' in master_dict: 217 self._start_node_from_profile(master, hostname, 'master_discovery_fkie', 'zeroconf', usr, cfg=master_dict['zeroconf']) 218 self._current_profile[rmuri].add('/zeroconf') 219 try: 220 do_start = [] 221 do_not_stop = {'/rosout', rospy.get_name(), '/node_manager', '/master_discovery', '/master_sync', '*default_cfg', '/zeroconf'} 222 configs = master_dict['configs'] 223 conf_set = set() 224 for cfg_name, cmdict in configs.items(): 225 cfg_name = cfg_name.replace('$LOCAL$', local_hostname) 226 if cfg_name.startswith("pkg://"): 227 cfg_name = os.path.abspath(resolve_url(cfg_name)) 228 conf_set.add(cfg_name) 229 reload_launch = True 230 argv = [] 231 is_default = False 232 if 'default' in cmdict: 233 if cmdict['default']: 234 # it is a default configuration, test for load or not 235 is_default = True 236 if 'argv_used' in cmdict['params']: 237 argv = cmdict['params']['argv_used'] 238 if cfg_name in master.default_cfgs: 239 reload_launch = False 240 if argv: 241 params = get_rosparam(cfg_name, rmuri) 242 if 'argv_used' in params: 243 reload_launch = set(params['argv_used']) != set(argv) 244 if reload_launch: 245 self._main_window.on_load_launch_as_default_bypkg(cmdict['params']['package'], cmdict['params']['launch_file'], master, argv, local_hostname) 246 else: 247 do_not_stop.add(cfg_name) 248 elif 'argv' in cmdict: 249 if 'argv' in cmdict: 250 argv = cmdict['argv'] 251 # do we need to load to load/reload launch file 252 if cfg_name in master.launchfiles: 253 reload_launch = set(master.launchfiles[cfg_name].argv) != set(argv) 254 if reload_launch: 255 self._main_window.launch_dock.load_file(cfg_name, argv, master.masteruri) 256 if 'nodes' in cmdict: 257 self._current_profile[rmuri].update(cmdict['nodes']) 258 force_start = True 259 cfg = cfg_name if not is_default else roslib.names.ns_join(cfg_name, 'run') 260 if not reload_launch: 261 force_start = False 262 do_not_stop.update(set(cmdict['nodes'])) 263 do_start.append((reload_launch, cfg, cmdict['nodes'], force_start)) 264 else: 265 do_start.append((reload_launch, cfg, cmdict['nodes'], force_start)) 266 # close unused configurations 267 for lfile in set(master.launchfiles.keys()) - conf_set: 268 master._close_cfg(lfile) 269 master.stop_nodes_by_name(running_nodes.keys(), True, do_not_stop) 270 for reload_launch, cfg, nodes, force_start in do_start: 271 if nodes: 272 hasstart = True 273 if reload_launch: 274 master.start_nodes_after_load_cfg(cfg.rstrip('/run'), list(nodes), force_start) 275 else: 276 master.start_nodes_by_name(list(nodes), cfg, force_start) 277 except Exception as ml: 278 import traceback 279 print utf8(traceback.format_exc(1)) 280 rospy.logwarn("Can not load launch file for %s: %s" % (muri, utf8(ml))) 281 except Exception as e: 282 import traceback 283 print traceback.format_exc(1) 284 MessageBox.warning(self, "Load profile error", 285 'Error while load profile', 286 utf8(e)) 287 if not hasstart: 288 self.update_progress() 289 else: 290 QTimer.singleShot(1000, self.update_progress)
291
292 - def update_progress(self):
293 if self.isVisible(): 294 count = 0 295 count_run = 0 296 for muri, nodes in self._current_profile.items(): 297 count += len(nodes) 298 master = self._main_window.getMaster(muri, False) 299 if master is not None: 300 running_nodes = master.getRunningNodesIfLocal() 301 profile_nodes = nodes & set(running_nodes.keys()) 302 count_run += len(profile_nodes) 303 if count > 0: 304 progress = float(count_run) / float(count) * 100 305 if progress >= 100: 306 self.setVisible(False) 307 else: 308 self.progressBar.setValue(progress)
309
310 - def closeEvent(self, event):
311 rospy.loginfo("Cancel profile loading...") 312 QDockWidget.closeEvent(self, event) 313 ret = MessageBox.warning(self, "Cancel Start?", 314 'This stops all starting queues!', buttons=MessageBox.Ok | MessageBox.Cancel) 315 if ret == MessageBox.Cancel: 316 return None 317 self._main_window._progress_queue.stop() 318 self._main_window.launch_dock.progress_queue.stop() 319 for muri, _ in self._current_profile.items(): 320 master = self._main_window.getMaster(muri, False) 321 if master is not None: 322 master.start_nodes_after_load_cfg_clear() 323 master._progress_queue.stop() 324 rospy.loginfo("Profile loading canceled!")
325
326 - def _start_node_from_profile(self, master, hostname, pkg, binary, usr, cfg={}):
327 try: 328 args = [] 329 restart = False 330 # test for start or not 331 node = master.getNode("/%s" % binary) 332 if node: 333 param = get_rosparam(binary, master.masteruri) 334 if set(param.keys()) == set(cfg.keys()): 335 for k, v in param.items(): 336 if v != cfg[k]: 337 restart = True 338 master.stop_node(node[0], True) 339 break 340 else: 341 restart = True 342 if restart: 343 delete_rosparam(binary, master.masteruri) 344 for pname, pval in cfg.items(): 345 args.append('_%s:=%s' % (pname, pval)) 346 self._main_window._progress_queue.add2queue(utf8(uuid.uuid4()), 347 'start %s on %s' % (binary, hostname), 348 nm.starter().runNodeWithoutConfig, 349 (utf8(hostname), pkg, utf8(binary), utf8(binary), args, master.masteruri, False, usr)) 350 self._main_window._progress_queue.start() 351 except Exception as me: 352 rospy.logwarn("Can not start %s for %s: %s" % (binary, master.masteruri, utf8(me)))
353