bridge_subscriber.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 
00003 """
00004    Copyright 2013 Southwest Research Institute
00005  
00006    Licensed under the Apache License, Version 2.0 (the "License");
00007    you may not use this file except in compliance with the License.
00008    You may obtain a copy of the License at
00009  
00010      http://www.apache.org/licenses/LICENSE-2.0
00011  
00012    Unless required by applicable law or agreed to in writing, software
00013    distributed under the License is distributed on an "AS IS" BASIS,
00014    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00015    See the License for the specific language governing permissions and
00016    limitations under the License.
00017    """
00018 
00019 # Import standard Python modules
00020 import sys
00021 import os
00022 import optparse
00023 import yaml
00024 import operator
00025 import thread
00026 import re
00027 import time
00028 from importlib import import_module
00029 
00030 # Import custom ROS-MTConnect function library
00031 import bridge_library
00032 
00033 # Import custom Python modules for MTConnect Adapter interface
00034 path, file = os.path.split(__file__)
00035 sys.path.append(os.path.realpath(path) + '/src')
00036 from data_item import Event, SimpleCondition, Sample, ThreeDSample
00037 from mtconnect_adapter import Adapter
00038 
00039 # Import ROS Python modules
00040 import roslib
00041 import rospy
00042 
00043 
00044 ## @class BridgeSubscriber
00045 ## @brief The BridgeSubscriber
00046 ## will subscribe to ROS topics specified in the configuration file and then port the data
00047 ## to the machine tool adapter.  Topics and topic parameters are specified by a configuration
00048 ## file that must be included with the main program during execution. If this file is not
00049 ## provided, the node will terminate with an error message indicating the need for this file.
00050 ##
00051 ## Command line example:
00052 ##
00053 ##     bridge_subscriber.py -i bridge_subscriber_config.yaml
00054 ##     bridge_subscriber.py -input bridge_subscriber_config.yaml
00055 ##
00056 ## The class contains the following methods:
00057 ##
00058 ## setup_topic_data -- utilizes introspection to set up class instance variables.
00059 ## topic_callback -- callback function that captures the attribute values for a ROS topic.
00060 ## data_item_conversion -- sets MTConnect adapter value via ROS message CONSTANT value.
00061 ## topic_listener -- ROS subscriber function that launches subscribers.
00062 class BridgeSubscriber():
00063     ## @brief Constructor for a BridgeSubscriber
00064     def __init__(self):
00065         # Initialize the ROS Bridge subscriber node
00066         rospy.init_node('bridge_subscriber')
00067         
00068         # Read configuration file and extract topics and types
00069         self.config = bridge_library.obtain_dataMap()
00070         self.msg_parameters = ['url', 'adapter_port']
00071         self.url = self.config[self.msg_parameters[0]]
00072         self.adapter_port = self.config[self.msg_parameters[1]]
00073         
00074         # Setup MTConnect Adapter for robot status data items
00075         self.adapter = Adapter((self.url, self.adapter_port))
00076         self.di_dict = {} # XML data item dictionary to store MTConnect Adapter Events
00077         
00078         # Setup ROS topics as specified in .yaml file
00079         self.topic_name_list = [] # List of topic names
00080         self.subscribed_list = [] # List of subscribed topics
00081         self.lib_manifests = [] # Stores loaded ROS library manifests
00082 
00083         # Topic type and MTConnect message members from the module import
00084         self.topic_type_list = {}
00085         self.member_types = {}
00086         self.member_names = {}
00087         
00088         # The ROS message text retainer, used to map ROS values to MTConnect XML tag.text 
00089         self.msg_text = {}
00090         
00091         # Create the data sets for the topic types, member names, and member types
00092         self.setup_topic_data()
00093         
00094         # Start the adapter
00095         rospy.loginfo('Start the Robot Link adapter')
00096         self.adapter.start()
00097         
00098         # Create class lock
00099         self.lock = thread.allocate_lock()
00100         
00101         # Loop through requested topics and spawn ROS subscribers when topics are available
00102         topic_list = self.topic_name_list
00103         dwell = 10
00104         while topic_list:
00105             published_topics = dict(rospy.get_published_topics())
00106             for topic_name in topic_list:
00107                 if topic_name in published_topics.keys():
00108                     # Create ROS Subscriber
00109                     idx = topic_list.index(topic_name)
00110                     del topic_list[idx]
00111                     self.topic_listener((topic_name, self.topic_type_list[topic_name], self.member_names[topic_name], self.msg_text[topic_name]))
00112                 else:
00113                     rospy.loginfo('ROS Topic %s not available, will try to subscribe in %d seconds' % (topic_name, dwell))
00114                     time.sleep(dwell)
00115     
00116     ## @brief This function captures the topic name, type, and member attributes that are required for 
00117     ## the ROS subscriber.  This task is completed for each topic specified in the configuration file.
00118     ## 
00119     ## This function then performs a relative import of the topic via the getattr(import_module) function.
00120     ## Data is stored in the following class instance attributes:
00121     ## 
00122     ##     self.topic_type_list --> used for module import and msg parameters.
00123     ##     self.member_types    --> member type, not used in msg parameters, future use.
00124     ##     self.member_names    --> used for ROS subscriber msg parameters.
00125     def setup_topic_data(self):
00126         for topic, topic_type in self.config.items():
00127             if topic not in self.msg_parameters:
00128                 self.topic_name_list.append(topic)
00129                 
00130                 # Only one type per topic
00131                 type_key = topic_type.keys()[0]
00132                 self.subscribed_list.append(type_key)
00133                 
00134                 # One set of data items per topic type
00135                 self.data_items = [data_item for data_item in topic_type[type_key].keys()]
00136         
00137                 # Add data items to the MTConnect Adapter - must be unique data items
00138                 bridge_library.add_event((self.adapter, self.data_items, self.di_dict, False))
00139                 
00140                 # Extract package name and topic type name
00141                 tokens = topic_type.keys()[0].split('/')
00142                 package = tokens[0]
00143                 type_name = tokens[1]
00144                 
00145                 # Load package manifest if unique
00146                 if package not in self.lib_manifests:
00147                     roslib.load_manifest(package)
00148                     self.lib_manifests.append(package)
00149                 
00150                 # Import module and create topic type class,
00151                 #    i.e. append <class 'mtconnect_msgs.msg._RobotStates.RobotStates'>
00152                 rospy.loginfo('Class Instance --> ' + package + '.msg.' + type_name)
00153                 type_handle = getattr(import_module(package + '.msg'), type_name)
00154 
00155                 self.topic_type_list[topic] = type_handle
00156                 self.msg_text[topic] = type_handle._full_text
00157                 self.member_types[topic] = type_handle._slot_types
00158                 self.member_names[topic] = type_handle.__slots__
00159         return
00160     
00161     ## @brief Callback function that captures the attribute values for a ROS topic.
00162     ## The topic data is stored in a list of tuples, where each tuple is a
00163     ## (attrib_name, attrib_value) pair.  To access the integer value of the attribute
00164     ## value, use attrib_value.val.
00165     ## 
00166     ## All data conversions between ROS and MTConnect are stored in the ROS
00167     ## subscriber YAML file. A separate function handles the ROS to MTConnect
00168     ## conversions for generic robot messages.
00169     ## @param data: callback ROS message data from the ROS subscriber
00170     ## @param cb_data: tuple containing the following parameters:
00171     ## @param topic_name: string defining the name of the ROS topic
00172     ## @param type_handle: class instance of the ROS topic message i.e. <class 'mtconnect_msgs.msg._RobotStates.RobotStates'>
00173     ## @param member_set: list of strings defining the attribute members for the message class
00174     ## @param msg_text: string of the entire message text (may be eliminated in future revisions)
00175     def topic_callback(self, data, cb_data):
00176         self.lock.acquire()
00177         try:
00178             (topic_name, type_handle, member_set, msg_text) = cb_data
00179             type_name = type_handle._type
00180             
00181             # Repackage members into a dictionary
00182             dout = {val:val for val in member_set}
00183 
00184             # Iterate through data items and capture message data
00185             msg_data = []
00186             msg_constants = {}
00187             for dataitem in member_set:
00188                 # Capture attribute for package/message type --> industrial_msgs/TriState.avail
00189                 attrib_handle = getattr(type_handle(), dataitem)
00190                 
00191                 if 'header' not in dataitem:
00192                     # Extract string representing message attribute --> 'val'
00193                     val_key = attrib_handle.__slots__[0]
00194                     
00195                     # Capture message data.attrib --> <class message data>.dataitem: TriState.avail
00196                     token = getattr(data, dataitem)
00197                     
00198                     # Capture the integer value for the attribute: avail.val
00199                     item_value = getattr(token, val_key)
00200                     
00201                     # Store published message data for the topic
00202                     msg_data.append((dataitem, item_value))
00203                     
00204                     # Create a list of strings containing message CONSTANTS
00205                     msg_constants[dataitem] = []
00206                     for attrib in dir(attrib_handle):
00207                         if attrib.isupper():
00208                             if getattr(attrib_handle, attrib) == item_value:
00209                                 msg_constants[dataitem].append(attrib)
00210             
00211             # Execute the ROS to MTConnet conversion function
00212             self.data_item_conversion(topic_name, type_name, msg_data, msg_text, msg_constants)
00213             
00214         except Exception as e:
00215             rospy.logerr('Topic callback failed: %s, releasing lock' % e)
00216         finally:
00217             self.lock.release()
00218         return
00219     
00220     ## @brief A conversion function that will convert the ROS message value to the 
00221     ## machine tool value.  The conversion dictionary is provided in the configuration file.
00222     ## @param topic_name: string defining the name of the ROS topic
00223     ## @param type_name: string defining the name of the ROS topic type
00224     ## @param msg_data: list of strings defining message attributes
00225     ## @param msg_text: string of the entire message text (may be eliminated in future revisions)
00226     ## @param constants: list of ROS topic message type CONSTANTS stored as strings
00227     def data_item_conversion(self, topic_name, type_name, msg_data, msg_text, constants):
00228         # Set the Robot XML data item via the MTConnect Adapter
00229         for member_data in msg_data:
00230             
00231             # Iterate through list of ROS message CONSTANTS and set the MTConnect Adapter value
00232             for const_val in constants[member_data[0]]:
00233                 if const_val in self.config[topic_name][type_name][member_data[0]].keys():
00234                     # If ROS to MTConnect mapping dictionary contains the constant, set the adapter value 
00235                     adapter_val = self.config[topic_name][type_name][member_data[0]][const_val]
00236                     break
00237                 else:
00238                     adapter_val = None
00239             
00240             if adapter_val is None:
00241                 rospy.logerr('ROS to MTConnect Mapping failed')
00242 
00243             # Set the Robot XML data item
00244             bridge_library.action_cb((self.adapter, self.di_dict, member_data[0], adapter_val))
00245         return   
00246     
00247     ## @brief Main ROS subscriber function.  A new thread is created for each callback.
00248     ## @param data: tuple containing the following parameters:
00249     ## @param topic_name: string defining the name of the ROS topic
00250     ## @param type_handle: class instance of the ROS topic message i.e. <class 'mtconnect_msgs.msg._RobotStates.RobotStates'>
00251     ## @param member_set: list of strings defining the message attributes
00252     ## @param msg_text: string of the entire message text (may be eliminated in future revisions) 
00253     def topic_listener(self, data):
00254         # Unpack arguments
00255         topic_name, type_handle, member_set, msg_text = data
00256         
00257         # Launch the ROS subscriber
00258         rospy.Subscriber(topic_name, type_handle, self.topic_callback, data)
00259         return
00260 
00261 #--------------------------------------------------
00262 # Main program
00263 if __name__ == '__main__':
00264     try:
00265         rospy.loginfo('Executing robot topic subscriber')
00266         robot_topic = BridgeSubscriber()
00267         rospy.spin()
00268     except rospy.ROSInterruptException:
00269         pass


mtconnect_ros_bridge
Author(s): Stephen L. Wiedmann
autogenerated on Mon Jan 6 2014 11:30:45