bridge_library.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 ## @package bridge_library.py
00020 ## This module contains functions utilized by the MTConnect to ROS bridge nodes.
00021 ## Currently, this module contains methods for HTTP connectivity verification,
00022 ## XML parsing, importing configuration files, and several MTConnect Adapter
00023 ## related functions.
00024 
00025 # Import standard Python modules
00026 import sys
00027 import os
00028 import optparse
00029 import yaml
00030 import re
00031 import time
00032 import urllib2
00033 from xml.etree import ElementTree
00034 
00035 # Import custom Python modules for MTConnect Adapter interface
00036 path, file = os.path.split(__file__)
00037 sys.path.append(os.path.realpath(path) + '/src')
00038 from data_item import Event, SimpleCondition, Sample, ThreeDSample
00039 
00040 # Import ROS Python modules
00041 import rospy
00042 
00043 ## @brief obtain_dataMap Function documentation.
00044 ##
00045 ## This function utilizes python option parser to determine the option filename.
00046 ## Once the file name is obtained, the .yaml file contents are stored in a dictionary.
00047 ## Program terminates if the option file is not available, or if it is in an
00048 ## incorrect YAML format.
00049 ## 
00050 ## This function does not take any arguments.
00051 ##
00052 ## @return: dataMap, dictionary of node parameters
00053 def obtain_dataMap():
00054     ## @brief determine_config_file_name Function documentation
00055     ##
00056     ## In order to execute a ROS bridge node a configuration file in YAML
00057     ## format is required.  This function allows the following input
00058     ## options for the configuration file:
00059     ##     -i, --input
00060     def determine_config_file_name():
00061         parser = optparse.OptionParser()
00062         parser.add_option('-i', '--input',
00063                       dest="input_filename",
00064                       default=None,
00065                       )
00066         options, remainder = parser.parse_args()    
00067     
00068         if not options.input_filename:
00069             print('ERROR: Must provide .yaml configuration file')
00070             sys.exit(0)
00071         return options.input_filename if options.input_filename else None
00072     
00073     fn = determine_config_file_name()
00074     
00075     # Read file contents and store into dataMap dictionary
00076     try:
00077         with open(fn) as f:
00078             dataMap = yaml.load(f)
00079         if dataMap == {}:
00080             sys.exit(0)
00081     except IOError as e:
00082         print('({})'.format(e))
00083         sys.exit(0)
00084     return dataMap
00085 
00086 ## @brief check_connectivity Function documentation
00087 ##
00088 ## The purpose of this function is to determine if an HTTP connection is available.
00089 ## It will continue to try to make a connection up to a user specified time.
00090 ##
00091 ## This function takes the following arguments:
00092 ## @param data: data is a tuple containing the following parameters:
00093 ##
00094 ## @param tout: int, allowable time in seconds before the open request times out
00095 ## @param url: string, url that will be opened
00096 ## @param url_port: int, url port that will be concatenated to the url string
00097 def check_connectivity(data):
00098     # Unpack function arguments
00099     tout, url, url_port = data
00100     
00101     # Verify url availability, dwell until time out
00102     current = time.time()
00103     time_out = current + 20.0
00104     rospy.loginfo('Checking for URL availability')
00105     while time_out > current:
00106         try:
00107             response = urllib2.urlopen('http://' + url + ':' + str(url_port) + '/current', timeout = tout)
00108             rospy.loginfo('Connection available')
00109             break
00110         except urllib2.URLError as err:
00111             current = time.time()
00112             pass
00113     else:
00114         rospy.loginfo('System Time Out: URL Unavailable, check if the MTConnect Agent is running')
00115         sys.exit()
00116     return
00117 
00118 ## @brief xml_get_response Function documentation
00119 ##
00120 ## This function determines if an HTTP connection can be made.  If so, it returns a response
00121 ## to a user specified "GET" request.
00122 ##
00123 ## This function takes the following arguments:
00124 ## @param data: data is a tuple containing the following parameters:
00125 ##
00126 ## @param url: string, url that will be opened
00127 ## @param url_port: int, url port that will be concatenated to the url string
00128 ## @param port: int, Adapter port used by MTConnect adapter.py module
00129 ## @param conn: Python httplib.HTTPConnection
00130 ## @param req: string, user specified selector url
00131 ##
00132 ## @return: response, "GET" response
00133 def xml_get_response(data):
00134     # Unpack data
00135     url, url_port, port, conn, req = data
00136     
00137     # Get response from url    
00138     rospy.logdebug('Attempting HTTP connection on url: %s:%s\tPort:%s' % (url, url_port, port))
00139     conn.request("GET", req)
00140     response = conn.getresponse()
00141     if response.status != 200:
00142         rospy.logerror("Request failed: %s - %d" % (response.reason, response.status))
00143         sys.exit(0)
00144     else:
00145         rospy.logdebug('Request --> %s, Status --> %s' % (response.reason, response.status))
00146     return response
00147 
00148 ## @brief xml_components Function documentation
00149 ##
00150 ## This function finds all elements in the updated XML.  If an action goal is required,
00151 ## the string acquired from the XML is parsed and returned with the appropriate type.
00152 ## For example, if the goal is "'ALUMINUM 6061-T6', 5.00, 2.50", the function will
00153 ## convert this string into the following list ['ALUMINUM 6061-T6', 5.00, 2.50] which contains
00154 ## the following types: [str, float, float]
00155 ##
00156 ## This function takes the following arguments:
00157 ## @param xml: XML data, read from response.read()
00158 ## @param ns: dictionary, xml namespace dictionary
00159 ## @param tag_list: dictionary, xml tag stored as tag:goal or tag:tag pairs
00160 ## @param get_goal: boolean, optional parameter, used when a action goal is required
00161 ## @param action_goals: dictionary, optional parameter, stored action goals by XML tag key
00162 ##
00163 ## Function returns:
00164 ## @return: nextSeq, int, next XML sequence for streaming XML via longpull.py
00165 ## @return: elements, Python Element object
00166 ## @return: [optional] action_goals, dictionary of unchanged xml_tag:goal pairs
00167 ## @return: [optional] request_goal, dictionary of updated xml_tag:goal pairs
00168 def xml_components(xml, ns, tag_list, get_goal = False, action_goals = None):
00169     # Extract XML Event elements
00170     root = ElementTree.fromstring(xml)
00171     header = root.find('.//m:Header', namespaces = ns)
00172     nextSeq = header.attrib['nextSequence']
00173    
00174     elements = []
00175     find_goal = None
00176     
00177     for tag, goals in tag_list.items():
00178         # Create a list of XML elements
00179         element_list = root.findall('.//m:' + tag, namespaces = ns)
00180         if element_list: # Element list is not empty
00181             for e in element_list:
00182                 elements.append(e)
00183 
00184         # Check if a goal must be captured
00185         if get_goal == True:
00186             request_goal = set_goal(tag, goals, root, ns, action_goals)
00187 
00188     if get_goal == True and request_goal is not None:
00189         return nextSeq, elements, request_goal
00190     elif get_goal == True and request_goal == None:
00191         return nextSeq, elements, action_goals
00192     else:
00193         return nextSeq, elements
00194 
00195 ## @brief set_goal Function documentation
00196 ##
00197 ## This function extracts the machine tool request goal from the Event tag specified in the configuration file.
00198 ## For example, during MaterialLoad the request goal tag is Material which is an Event that stores the material
00199 ## specifications: material type, material length, material diameter.
00200 ##
00201 ## The function takes the following arguments:
00202 ## @param tag: string of the Event tag, i.e. 'MaterialLoad'
00203 ## @param goals: dictionary of the goal (str):goal attributes (list of strings)
00204 ## @param root: XML ElementTree object that converted the XML chunk in string format to an ElementTree object
00205 ## @param ns: dictionary, XML namespace dictionary
00206 ## @param action_goals: dictionary, stored action goals by XML tag key
00207 ##
00208 ## The function returns:
00209 ## @return: action_goals, dictionary, hash table of XML tag:goal pairs
00210 ## @return: None type if the action goal tag is not present in the XML
00211 def set_goal(tag, goals, root, ns, action_goals):
00212     goal_tag = goals.keys()[0]
00213     find_goal = root.findall('.//m:' + goal_tag, namespaces = ns)
00214     
00215     if find_goal:
00216         rospy.loginfo('GOAL-ELEMENT SET--> %s' % find_goal[0].text)
00217         goal_conv = []
00218         tokens = find_goal[0].text.split(", ")
00219         for item in tokens:
00220             try:
00221                 goal_conv.append(float(item))
00222             except ValueError:
00223                 goal_conv.append(item)
00224         action_goals[tag] = goal_conv
00225         rospy.loginfo('Action Goals Set --> %s' % action_goals)
00226 
00227         return action_goals
00228     else:
00229         return None
00230 
00231 ## @brief add_event Function documentation
00232 ##
00233 ## This function creates instances of the Adapter Event class for
00234 ## each of the XML tags provided to the function.
00235 ##
00236 ## This function takes the following arguments:
00237 ## @param data: data is a tuple containing the following parameters:
00238 ##
00239 ## @param adapter: Adapter class object
00240 ## @param tag_list: list of XML tags culled from node configuration file
00241 ## @param di_dict: dictionary {string:string}, stored Adapter Event class instances for each XML tag
00242 ## @param init: boolean, user specified boolean to determine if the Adapter Events must be initialized  
00243 def add_event(data):
00244     # Unpack function arguments
00245     adapter, tag_list, di_dict, init = data
00246     
00247     for tag in tag_list:
00248         # Change tag to XML [name] attribute format if necessary
00249         if not tag.islower():
00250             data_item = split_event(tag)
00251         else:
00252             data_item = tag
00253         
00254         # Add Event to the MTConnect adapter
00255         di_dict[data_item] = Event(data_item)
00256         adapter.add_data_item(di_dict[data_item])
00257 
00258         # Output success
00259         rospy.loginfo('Added XML data_item --> %s' % data_item)
00260 
00261     if init == True:
00262         # Set initial states for robot actions
00263         adapter.begin_gather()
00264         for data_item, event in di_dict.items():
00265             event.set_value('READY')
00266         adapter.complete_gather()
00267     return
00268 
00269 ## @brief split_event Function documentation
00270 ##
00271 ## This function converts a data item Event string from CamelCase to camel_case
00272 ##
00273 ## This function takes and returns the following arguments:
00274 ## @param xml_tag: string, Event data item in CamelCase format
00275 ## @return: data_item, string, Event data item in camel_case format
00276 def split_event(xml_tag):
00277     tokens = re.findall(r'([A-Z][a-z]*)', xml_tag)
00278     tokenlist = [val.lower() for val in tokens]
00279     data_item = tokenlist[0] + '_' + tokenlist[1]
00280     return data_item
00281 
00282 ## @brief action_cb Function documentation
00283 ##
00284 ## This function sets the value of an Adapter Event.  It is used to port
00285 ## XML tag changes back to machine tool.
00286 ##
00287 ## This function takes the following arguments:
00288 ## @param data: data is a tuple containing the following parameters:
00289 ##
00290 ## @param adapter: Adapter class object
00291 ## @param di_dict: dictionary {string:string}, stored Adapter Event class instances for each XML tag
00292 ## @param data_item: string, data item used to locate the Adapter Event
00293 ## @param state: string, Event will be changed to this value
00294 def action_cb(data):
00295     # Unpack function arguments
00296     adapter, di_dict, data_item, state = data
00297     
00298     # Respond that goal is accepted
00299     #rospy.loginfo("Changing %s to '%s'" % (data_item, state))
00300     adapter.begin_gather()
00301     di_dict[data_item].set_value(state)
00302     adapter.complete_gather()
00303     return
00304 
00305 ## @brief type_check Function documentation
00306 ##
00307 ## This function checks the goal types and converts them to Python
00308 ## standard types.  It then verifies if the goal type matches the type
00309 ## specified in the goal message.
00310 ##
00311 ## This function takes the following arguments:
00312 ## @param goal_type: string, goal_type from goal msg.__slot_types
00313 ## @param request: varies, actual goal value that is a float, int, string, etc.
00314 ## @return boolean: True for a match, False for an error
00315 def type_check(goal_type, request):
00316     type_dict = {'bool': bool, 'int8': int, 'uint8': int,
00317                  'int16': int, 'unit16': int, 'int32': int,
00318                  'unit32': int, 'int64': long, 'unint64': long,
00319                  'float32': float, 'float64': float, 'string': str}
00320     return type_dict[goal_type] == type(request)


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