def _process_listeners(self, name, exit_code): ''' This is usually run as a post-executing function to the interaction and so will do the cleanup when the user's side of the interaction has terminated. Note that there are other means of stopping & cleanup for interactions: - via the remocon stop buttons (self.stop_interaction, self.stop_all_interactions) - via a rapp manager status callback when it is a pairing interaction There is some common code (namely del launched_interactions element, check pairing, publish remocon) so if changing that flow, be sure to check the code in self.stop_interaction() @param str name : name of the launched process stored in the interactions launch_list dict. @param int exit_code : could be utilised from roslaunched processes but not currently used. ''' terminated = False for interaction in self.interactions_table.interactions: if self.launched_interactions.remove(interaction.hash, name): # toggle the pairing indicator if it was a pairing interaction if interaction.is_paired_type() and interaction.hash in self.active_paired_interaction_hashes: self.active_paired_interaction_hashes = [interaction_hash for interaction_hash in self.active_paired_interaction_hashes if interaction_hash != interaction.hash] if not self.launched_interactions.get_launch_details(interaction.hash): # inform the gui to update self.signal_updated.emit() # update the rocon interactions handler self._publish_remocon_status() terminated = True break if not terminated: console.logwarn("A process listener detected a terminating interaction, but nothing to do.") console.logwarn("Probably mopped up by gui or independently terminating pairing requirement. [%s]" % name) else: console.logdebug("A process listener detected a terminating interaction & mopped up appropriately [%s]" % name)
def start_interaction(self, interaction_hash): interaction = self.interactions_table.find(interaction_hash) if interaction is None: console.logerror("Couldn't find interaction with hash '%s'" % interaction_hash) return (False, "interaction key %s not found in interactions table" % interaction_hash) print("\nInteraction: %s" % interaction) console.logdebug(" - requesting permission to start interaction") response = self.service_proxies.request_interaction(remocon=self.name, hash=interaction_hash) if response.result == interaction_msgs.ErrorCodes.SUCCESS: console.logdebug(" - request granted") try: (app_executable, start_app_handler) = self._determine_interaction_type(interaction) except rocon_interactions.InvalidInteraction as e: return (False, ("invalid interaction specified [%s]" % str(e))) result = start_app_handler(interaction, app_executable) if result: if interaction.is_paired_type(): console.logdebug(" - setting an active pairing with %s" % interaction_hash) self.active_paired_interaction_hashes.append(interaction_hash) self.signal_updated.emit() self._publish_remocon_status() return (result, "success") else: console.logerror("Result unknown") return (result, "unknown") else: console.logwarn("Request rejected [%s]" % response.message) return False, ("interaction request rejected [%s]" % response.message)
def __init__(self, context): self._context = context super(RqtRemocon, self).__init__(context) self.rocon_master_uri = None self.host_name = None ############################## # Environment Variables ############################## try: self.rocon_master_uri = os.environ["ROS_MASTER_URI"] except KeyError as unused_e: console.logerror("ROS_MASTER_URI not set, aborting") sys.exit(1) try: self.host_name = os.environ["ROS_HOSTNAME"] except KeyError as unused_e: console.logwarn("ROS_HOSTNAME not set, you may drop comms across a network connection.") self.host_name = 'localhost' ############################## # Launch User Interface ############################## self.setObjectName('Rqt Remocon') self.rqt_remocon = InteractionsChooserUI(self.rocon_master_uri, self.host_name, True) # self._rqt_remocon = InteractiveClientUI(None, "Rqt remocon", None, self.rocon_master_uri, self.host_name, True) context.add_widget(self.rqt_remocon.widget)
def load_rapp_specs_from_file(specification): ''' Specification consists of resource which is file pointer. This function loads those files in memeory :param specification: Rapp Specification :type Specification: rocon_app_utilities.Rapp :returns Fully loaded rapp data dictionary :rtype: dict ''' base_path = os.path.dirname(specification.filename) rapp_data = specification.raw_data data = {} data['name'] = specification.resource_name data['ancestor_name'] = specification.ancestor_name data['display_name'] = rapp_data.get('display', data['name']) data['description'] = rapp_data.get('description', '') data['compatibility'] = rapp_data['compatibility'] data['launch'] = rapp_data['launch'] data['launch_args'] = _get_standard_args(data['launch']) data['public_interface'] = rapp_data.get('public_interface', _default_public_interface()) data['public_parameters'] = rapp_data.get('public_parameters', {}) data['icon'] = rapp_data.get('icon', None) if 'pairing_clients' in rapp_data: console.logwarn('Rapp Indexer : [%s] includes "pairing_clients". It is deprecated attribute. Please drop it'%specification.resource_name) required_capabilities = 'required_capabilities' if required_capabilities in rapp_data: data[required_capabilities] = [c for c in rapp_data[required_capabilities]] return data
def _prepare_roslaunch_args(self, interaction_parameters): """ Convert the interaction specified yaml string into roslaunch args to be passed to the roslaunchable. Note that we only use a constrained subset of yaml to be compatible with roslaunch args here. The root type has to be a dict and values themselves may not be dicts or lists. :param str interaction_parameters: parameters specified as a yaml string :returns: the parameters as roslaunch args key-value pairs :rtype: list of (name, value) pairs """ args = [] parameters = yaml.load( interaction_parameters ) # convert from yaml string into python dictionary if parameters is not None: if type(parameters) is types.DictType: for name, value in parameters.items(): if type(value) is types.DictType or type( value) is types.ListType: console.logwarn( "Ignoring invalid parameter for roslaunch arg (simple key-value pairs only) [%s][%s]" % (name, value)) else: args.append((name, value)) else: console.logwarn( "Ignoring invalid parameters for roslaunch args (must be a simple key-value dict) [%s]" % parameters) return args
def _process_listeners(self, name, exit_code): ''' Callback function used to catch terminating applications and cleanup appropriately. @param name : name of the launched process stored in the interactions index. @type str @param exit_code : could be utilised from roslaunched processes but not currently used. @type int ''' console.logdebug( "Interactive Client : process_listener detected terminating interaction [%s]" % name) for interaction in self._interactions_table.interactions: if name in interaction.launch_list: del interaction.launch_list[name] # toggle the pairing indicator if it was a pairing interaction if interaction.is_paired_type(): self.pairing = None if not interaction.launch_list: # inform the gui to update self._stop_interaction_postexec_fn() # update the rocon interactions handler self._publish_remocon_status() else: console.logwarn( "Interactive Client : process_listener detected unknown terminating interaction [%s]" % name)
def start_pairing(self, pairing): required_interaction = None if pairing.requires_interaction: required_interaction = self.interactions_table.find_by_name( pairing.requires_interaction) if required_interaction is None: response = interaction_srvs.StartPairingResponse() response.result = interaction_msgs.ErrorCodes.REQUIRED_INTERACTION_IS_NOT_AVAILABLE response.message = interaction_msgs.ErrorCodes.MSG_REQUIRED_INTERACTION_IS_NOT_AVAILABLE + " [{0}]".format( pairing.requires_interaction) console.logwarn( "%s [%s]" % (response.message, pairing.requires_interaction)) return response request = interaction_srvs.StartPairingRequest(pairing.name) response = self.service_proxies.start_pairing(request) if response.result == interaction_msgs.ErrorCodes.SUCCESS: if required_interaction is not None: (result, unused_message) = self.start_interaction( required_interaction.hash) if not result: self.stop_pairing(pairing) response.result = interaction_msgs.ErrorCodes.REQUIRED_INTERACTION_FAILED response.message = interaction_msgs.ErrorCodes.MSG_REQUIRED_INTERACTION_FAILED return response
def _stop_interaction(self): console.logdebug("Interactions Chooser : stopping interaction %s " % str(self.cur_selected_interaction.name)) (result, message) = self.interactive_client.stop_interaction(self.cur_selected_interaction.hash) if result: if self.cur_selected_interaction.is_paired_type(): self._refresh_interactions_list() # make sure the highlight is disabled self._set_stop_interactions_button() #self.interactions_widget.stop_interactions_button.setDisabled(True) else: QMessageBox.warning(self, 'Stop Interaction Failed', "%s." % message.capitalize(), QMessageBox.Ok) console.logwarn("Interactions Chooser : stop interaction failed [%s]" % message)
def get_rocon_master_info(self): console.logdebug("RemoconInfo : retrieving rocon master information") time_out_cnt = 0 while not rospy.is_shutdown(): if len(self.rocon_master_info) != 0: break else: rospy.sleep(rospy.Duration(0.2)) time_out_cnt += 1 if time_out_cnt > 5: console.logwarn("RemoconInfo : timed out waiting for rocon master information") break return self.rocon_master_info
def append(self, interaction): """ Append an interaction to the table. :param :class:`.Interaction` interaction: """ matches = [i for i in self.interactions if i.hash == interaction.hash] if not matches: self.interactions.append(interaction) else: console.logwarn( "Interactions Table : tried to append an already existing interaction [%s]" % interaction.hash)
def _set_stop_interactions_button(self): ''' Disable or enable the stop button depending on whether the selected interaction has any currently launched processes, ''' if not self.interactions: console.logwarn("No interactions") return if self.cur_selected_interaction.launch_list: console.logdebug("Interactions Chooser : enabling stop interactions button [%s]" % self.cur_selected_interaction.display_name) self.interactions_widget.stop_interactions_button.setEnabled(True) else: console.logdebug("Interactions Chooser : disabling stop interactions button [%s]" % self.cur_selected_interaction.display_name) self.interactions_widget.stop_interactions_button.setEnabled(False)
def stop_interaction(self, interaction_hash): """ This stops all launches for an interaction of a particular type. """ interaction = self._interactions_table.find(interaction_hash) if interaction is None: error_message = "interaction key %s not found in interactions table" % interaction_hash console.logwarn( "Interactive Client : could not stop interactions of this kind [%s]" % error_message) return (False, "%s" % error_message) else: console.logdebug( "Interactive Client : stopping all '%s' interactions" % interaction.display_name) try: for launch_info in interaction.launch_list.values(): if launch_info.running: launch_info.shutdown() console.loginfo( "Interactive Client : interaction stopped [%s]" % (launch_info.name)) del interaction.launch_list[launch_info.name] elif launch_info.process is None: launch_info.running = False console.loginfo( "Interactive Client : no attached interaction process to stop [%s]" % (launch_info.name)) del interaction.launch_list.launch_list[launch_info.name] else: console.loginfo( "Interactive Client : interaction is already stopped [%s]" % (launch_info.name)) del interaction.launch_list.launch_list[launch_info.name] except Exception as e: console.logerror( "Interactive Client : error trying to stop an interaction [%s][%s]" % (type(e), str(e))) # this is bad...should not create bottomless exception buckets. return (False, "unknown failure - (%s)(%s)" % (type(e), str(e))) # console.logdebug("Interactive Client : interaction's updated launch list- %s" % str(interaction.launch_list)) if interaction.is_paired_type(): self.currently_pairing_interaction_hashes = [ h for h in self.currently_pairing_interaction_hashes if h != interaction_hash ] self._publish_remocon_status() return (True, "success")
def _start_command_line_interaction(self, interaction, command): ''' Start a command line executable inside a shell window. ''' console.logwarn("Starting command line executable [%s]" % interaction.command) cmd = command.split(' ') name = os.path.basename(cmd[0]).replace('.', '_') anonymous_name = name + "_" + uuid.uuid4().hex process_listener = functools.partial(self._process_listeners, anonymous_name, 1) console.logdebug("Command line executable: '%s'" % cmd) process = self.roslaunch_terminal.spawn_executable_window(interaction.name, cmd, postexec_fn=process_listener) self.launched_interactions.add( interaction.hash, anonymous_name, launch.LaunchInfo(anonymous_name, True, process) ) return True
def _process_listeners(self, name, exit_code): ''' This is usually run as a post-executing function to the interaction and so will do the cleanup when the user's side of the interaction has terminated. Note that there are other means of stopping & cleanup for interactions: - via the remocon stop buttons (self.stop_interaction, self.stop_all_interactions) - via a rapp manager status callback when it is a pairing interaction There is some common code (namely del launch_list element, check pairing, publish remocon) so if changing that flow, be sure to check the code in self.stop_interaction() @param str name : name of the launched process stored in the interactions launch_list dict. @param int exit_code : could be utilised from roslaunched processes but not currently used. ''' terminated = False for interaction in self._interactions_table.interactions: if name in interaction.launch_list: del interaction.launch_list[name] # toggle the pairing indicator if it was a pairing interaction if interaction.is_paired_type( ) and interaction.hash in self.currently_pairing_interaction_hashes: self.currently_pairing_interaction_hashes = [ interaction_hash for interaction_hash in self.currently_pairing_interaction_hashes if interaction_hash != interaction.hash ] if not interaction.launch_list: # inform the gui to update self._stop_interaction_postexec_fn() # update the rocon interactions handler self._publish_remocon_status() terminated = True break if not terminated: console.logwarn( "Interactive Client : detected a terminating interaction, but nothing to do [either mopped up via the gui or via rapp manager callback][%s]" % name) else: console.logdebug( "Interactive Client : detected terminating interaction and moppedddd up appropriately [%s]" % name)
def _start_global_executable_interaction(self, interaction, filename): console.logwarn( "Interactive Client : starting global executable [%s]" % interaction.name) name = os.path.basename(filename).replace('.', '_') anonymous_name = name + "_" + uuid.uuid4().hex process_listener = partial(self._process_listeners, anonymous_name, 1) cmd = [filename] remapping_args = [] for remap in interaction.remappings: remapping_args.append(remap.remap_from + ":=" + remap.remap_to) cmd.extend(remapping_args) cmd.extend( self._prepare_command_line_parameters(interaction.parameters)) console.logdebug("Interactive Client : global executable command %s" % cmd) process = rocon_python_utils.system.Popen(cmd, postexec_fn=process_listener) interaction.launch_list[anonymous_name] = LaunchInfo( anonymous_name, True, process) return True
def _shutdown(self): if(self.is_connect != True): console.logwarn("RemoconInfo : tried to shutdown already disconnected remocon") else: console.logdebug("RemoconInfo : shutting down all apps") for app_hash in self.app_list.keys(): self._stop_app(app_hash) self.app_list = {} self.is_connect = False rospy.signal_shutdown("shut down remocon_info") while not rospy.is_shutdown(): rospy.rostime.wallsleep(0.1) self.is_connect = False self.info_sub.unregister() self.remocon_status_pub.unregister() self.info_sub = None self.remocon_status_pub = None console.logdebug("RemoconInfo : has shutdown.")
def _stop_interaction(self): """ Stop running interactions when user hits `stop` or 'all stop interactions button` button. If no interactions is running, buttons are disabled. """ console.logdebug("Interactions Chooser : stopping interaction %s " % str(self.cur_selected_interaction.name)) (result, message) = self.interactive_client_interface.stop_interaction( self.cur_selected_interaction.hash) if result: if self.cur_selected_interaction.is_paired_type(): self.refresh_interactions_list( ) # make sure the highlight is disabled self._set_stop_interactions_button() # self.interactions_widget.stop_interactions_button.setDisabled(True) else: QMessageBox.warning(self.interactions_widget, 'Stop Interaction Failed', "%s." % message.capitalize(), QMessageBox.Ok) console.logwarn( "Interactions Chooser : stop interaction failed [%s]" % message)
def load_rapp_specs_from_file(specification): ''' Specification consists of resource which is file pointer. This function loads those files in memeory :param specification: Rapp Specification :type Specification: rocon_app_utilities.Rapp :returns Fully loaded rapp data dictionary :rtype: dict ''' base_path = os.path.dirname(specification.filename) rapp_data = specification.raw_data data = {} data['name'] = specification.resource_name data['ancestor_name'] = specification.ancestor_name data['display_name'] = rapp_data.get('display', data['name']) data['description'] = rapp_data.get('description', '') data['compatibility'] = rapp_data['compatibility'] data['launch'] = rapp_data['launch'] data['launch_args'] = _get_standard_args(data['launch']) data['public_interface'] = rapp_data.get('public_interface', _default_public_interface()) data['public_parameters'] = rapp_data.get('public_parameters', {}) data['icon'] = rapp_data.get('icon', None) if 'pairing_clients' in rapp_data: console.logwarn( 'Rapp Indexer : [%s] includes "pairing_clients". It is deprecated attribute. Please drop it' % specification.resource_name) required_capabilities = 'required_capabilities' if required_capabilities in rapp_data: data[required_capabilities] = [ c for c in rapp_data[required_capabilities] ] return data
def _start_interaction(self): """ Start selected interactions when user hits start button and does doubleclicking interaction item. The interactions can be launched in duplicate. """ console.logdebug("Interactions Chooser : starting interaction [%s]" % str(self.cur_selected_interaction.name)) (result, message) = self.interactive_client_interface.start_interaction( self.cur_selected_role, self.cur_selected_interaction.hash) if result: if self.cur_selected_interaction.is_paired_type(): self.refresh_interactions_list( ) # make sure the highlight is working self.interactions_widget.stop_interactions_button.setDisabled( False) else: QMessageBox.warning(self.interactions_widget, 'Start Interaction Failed', "%s." % message.capitalize(), QMessageBox.Ok) console.logwarn( "Interactions Chooser : start interaction failed [%s]" % message)
def check_if_executable_available(name): ''' Ensure a particular executable is available on the system. Could use package names and python-apt here to find if the package is available, but more reliable and general - just check if program binary is available. Deprecated - aborts program execution with fatal error if not found. ''' if which(name + 'z') is None: console.logwarn("Hub : " + name + " not found, either") console.logwarn("Hub : 1) it is there and you can't look up the admin PATH (ok - ignore this)") console.logwarn("Hub : 2) OR it is not installed - hint 'rosdep install rocon_hub'")