bridge_server.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 import urllib2
00029 from Queue import Queue
00030 from threading import Thread, Timer
00031 from importlib import import_module
00032 from httplib import HTTPConnection
00033 from xml.etree import ElementTree
00034 
00035 # Import custom ROS-MTConnect function library
00036 import bridge_library
00037 
00038 # Import custom Python modules for MTConnect Adapter interface
00039 path, file = os.path.split(__file__)
00040 sys.path.append(os.path.realpath(path) + '/src')
00041 from data_item import Event, SimpleCondition, Sample, ThreeDSample
00042 from mtconnect_adapter import Adapter
00043 from long_pull import LongPull
00044 
00045 # Import ROS Python modules
00046 import roslib
00047 import rospy
00048 import actionlib
00049 
00050 ## @class GenericActionServer
00051 ## @brief The GenericActionServer
00052 ## will launch a ROS node that blocks until a dedicated action request is made by the robot,
00053 ## such as 'OpenDoor'.  Once a request is received, the callback function will monitor
00054 ## the machine tool and complete the 'READY' handshake before returning the request result
00055 ## to the dedicated action client.
00056 ##
00057 ## Dedicated action client data items are specified by a configuration file that must be included with the
00058 ## main program during execution. If this file is not provided, the node will terminate
00059 ## with an error message indicating the need for this file.
00060 ##
00061 ## Command line example:
00062 ##
00063 ##     bridge_server.py -i bridge_server_config.yaml
00064 ##     bridge_server.py -input bridge_server_config.yaml
00065 ##
00066 ## The class contains the following methods:
00067 ## setup_topic_data -- sets up class instance variables.
00068 ## execute_cb -- triggered by a ROS action client, monitors machine tool response sequence and submits 'READY' handshake.
00069 ## xml_callback -- parses xml stream and stores xml into a data queue.
00070 class GenericActionServer():
00071     ## @brief Constructor for a GenericActionServer
00072     def __init__(self):
00073         # Initialize ROS generic client node
00074         rospy.init_node('ActionServer')
00075         
00076         # Setup MTConnect to ROS Conversion
00077         self.config = bridge_library.obtain_dataMap()
00078         self.msg_parameters = ['url', 'url_port', 'machine_tool', 'xml_namespace', 'adapter_port']
00079         self.url = self.config[self.msg_parameters[0]]
00080         self.url_port = self.config[self.msg_parameters[1]]
00081         self.mtool = self.config[self.msg_parameters[2]]
00082         self.ns = dict(m = self.config[self.msg_parameters[3]])
00083         self.port = self.config[self.msg_parameters[4]]
00084         
00085         # Check for url connectivity, dwell until system timeout
00086         bridge_library.check_connectivity((1,self.url,self.url_port))
00087         
00088         # Setup MTConnect Adapter for robot status data items
00089         self.adapter = Adapter((self.url, self.port))
00090         
00091         # Create empty lists for actions, message type handles, etc.
00092         self.lib_manifests = []
00093         self.type_handle = None
00094         self.action_list = {}
00095         self.capture_xml = False
00096         self.di_dict = {} # XML data item dictionary to store MTConnect Adapter Events
00097         
00098         # Create a dictionary of _results to return upon action success
00099         self._resultDict = {}
00100         
00101         # Create a dictionary of action servers
00102         self._as = {}
00103         
00104         # Create a dictionary of server names
00105         self.server_name = {}
00106         
00107         # Setup action client data per the config file
00108         self.setup_topic_data()
00109         
00110         # Add data items to the MTConnect Adapter - must be unique data items
00111         bridge_library.add_event((self.adapter, self.action_list, self.di_dict, True))
00112         
00113         # Start the adapter
00114         rospy.loginfo('Start the Robot Link adapter')
00115         self.adapter.start()
00116         
00117         # Establish XML connection, read in current XML
00118         while True:
00119             try:
00120                 self.conn = HTTPConnection(self.url, self.url_port)
00121                 response = bridge_library.xml_get_response((self.url, self.url_port, self.port, self.conn, self.mtool + "/current"))
00122                 body = response.read()
00123                 break
00124             except socket.error as e:
00125                 rospy.loginfo('HTTP connection error %s' % e)
00126                 time.sleep(10)
00127         
00128         # Parse the XML and determine the current sequence and XML Event elements
00129         seq, elements = bridge_library.xml_components(body, self.ns, self.action_list)
00130         
00131         # Start a streaming XML connection
00132         response = bridge_library.xml_get_response((self.url, self.url_port, self.port, self.conn, self.mtool + "/sample?interval=1000&count=1000&from=" + seq))
00133         
00134         # Create queue for streaming XML
00135         self.XML_queue = Queue()
00136         
00137         # Create XML polling thread
00138         lp = LongPull(response)
00139         xml_thread = Thread(target = lp.long_pull, args = (self.xml_callback,))
00140         xml_thread.daemon = True
00141         xml_thread.start()
00142     
00143     ## @brief This function imports the topic package and creates an action server for each
00144     ## package specified in the configuration file.
00145     ## 
00146     ## Data is stored in the following class attributes:
00147     ## 
00148     ##    self.lib_manifests --> used to track which manifests have been loaded
00149     ##    self.type_handle   --> used for ROS SimpleActionClient, stores package name with action messages
00150     ##    self.action_list   --> used for ROS SimpleActionClient, stores CNC action request strings
00151     ##    self.server_name   --> dictionary of data_item:DataItem pairs in str:str format
00152     ##    self._resultDict   --> dictionary of result class instances, i.e. {'MaterialLoad':mtconnect_msgs.msg._MaterialLoadResult.MaterialLoadResult()} 
00153     ##    self._as           --> dictionary of ROS action servers referenced by DataItem
00154     def setup_topic_data(self):
00155         for package, action in self.config.items():
00156             if package not in self.msg_parameters: # Only one ROS package in config by design
00157                 # Load package manifest if unique
00158                 if package not in self.lib_manifests:
00159                     roslib.load_manifest(package)
00160                     self.lib_manifests.append(package)
00161 
00162                 # Import module
00163                 rospy.loginfo('Importing --> ' + package + '.msg')
00164                 self.type_handle = import_module(package + '.msg')
00165                 
00166                 # Iterate through requests and create action result class instances and action servers
00167                 for request in action:
00168                     # Capture action name for action client callback reference
00169                     self.action_list[request] = request
00170                     
00171                     # Store the ROS server name in a hash table
00172                     di_conv = bridge_library.split_event(request)
00173                     self.server_name[di_conv] = request
00174                     
00175                     # Create a dictionary of result class instances
00176                     request_class = getattr(self.type_handle, request + 'Result')
00177                     self._resultDict[request] = request_class()
00178                     
00179                     # Create instance of the action class
00180                     action_class = getattr(self.type_handle, request + 'Action')
00181                     
00182                     # Create action server for requested action, store in a hash table
00183                     self._as[request] = actionlib.SimpleActionServer(request + 'Client', action_class, self.execute_cb, False)
00184                     
00185                     # Start the action server
00186                     self._as[request].start()
00187         return
00188     
00189     ## @brief Callback function that monitors the machine tool action sequence for 'ACTIVE',
00190     ## 'COMPLETE', and 'READY' data item states.  Once the machine tool is completed with the
00191     ## requested action and sends the machine tool 'READY' signal, the ROS action server
00192     ## returns set_succeeded to the ROS action client that requested the action.
00193     ## 
00194     ## @param goal: from action client, class instance of the data item action goal, i.e. goal = mtconnect_msgs.msg.OpenDoorGoal()
00195     def execute_cb(self, goal):
00196         # If goal is OpenDoor, use the goal defined in the message as your key, access it as goal.__slots__[0]
00197         action = goal.__slots__[0]
00198         
00199         # Empty function -- assumes action was successful
00200         rospy.loginfo('In %s Callback -- determining action request result.' % self.server_name[action])
00201         
00202         # Check to make sure machine tool is READY, if not, ABORT the robot action request
00203         
00204         # Change to XML Tag format
00205         tokens = re.findall(r'([A-Z][a-z]*)', self.server_name[action])
00206         tokenlist = [val.upper() for val in tokens]
00207         di_tag = tokenlist[0] + '_' + tokenlist[1]
00208         
00209         # Check current status of XML Tag
00210         check_response = bridge_library.xml_get_response((self.url, self.url_port, self.port, self.conn, self.mtool + "/current?path=//DataItem[@type='" + di_tag + "']"))
00211         body = check_response.read()
00212         _, element = bridge_library.xml_components(body, self.ns, {self.server_name[action]:self.server_name[action]})
00213         
00214         # Complete the action request
00215         if element[0].text == 'READY': # Execute the action request
00216             # Submit goal value to MTConnect via agent (i.e. goal.open_door = 'ACTIVE')
00217             bridge_library.action_cb((self.adapter, self.di_dict, action, 'ACTIVE'))
00218             
00219             robot_hold = 0 # Used to make sure you only change robot state once after 'COMPLETE' received from CNC
00220             
00221             # Start capturing XML
00222             self.capture_xml = True
00223             
00224             # While loop to poll CNC XML until READY is received for Robot action request
00225             dwell = True
00226             while dwell == True:
00227                 try:
00228                     # Obtain XML chunk
00229                     if not self.XML_queue.empty():
00230                         chunk = self.XML_queue.get()
00231                         
00232                         # Parse the XML and determine the current sequence and XML Event elements
00233                         root = ElementTree.fromstring(chunk)
00234                         element_list = root.findall('.//m:' + self.server_name[action], namespaces = self.ns)
00235                         if len(element_list) > 1:
00236                             rospy.logdebug('XML --> %s' % chunk)
00237                         if element_list:
00238                             # Must iterate -- multiple elements possible for a single tag
00239                             for element in element_list:
00240                                 if element.text == 'ACTIVE':
00241                                     # Set accepted back to action client
00242                                     pass
00243                                 
00244                                 # While polling monitor CNC response for COMPLETE, submit READY handshake
00245                                 elif element.text == 'COMPLETE' and robot_hold == 0:
00246                                     bridge_library.action_cb((self.adapter, self.di_dict, action, 'READY'))
00247                                     robot_hold = 1
00248                                 
00249                                 elif element.text == 'READY' and robot_hold == 1:
00250                                     dwell = False
00251                                     self.capture_xml = False
00252                                     
00253                                     # When response is READY, set server result and communicate as below:
00254                                     # Extract action attribute
00255                                     result_attribute = self._resultDict[self.server_name[action]].__slots__[0]
00256                                     
00257                                     # Set the attribute per the ROS to MTConnect conversion
00258                                     setattr(self._resultDict[self.server_name[action]], result_attribute, 'READY')
00259                                     
00260                                     # Indicate a successful action
00261                                     self._as[self.server_name[action]].set_succeeded(self._resultDict[self.server_name[action]])
00262                                     rospy.loginfo('In %s Callback -- action succeeded.' % self.server_name[action])
00263                                 
00264                                 elif element.text == 'FAIL':
00265                                     bridge_library.action_cb((self.adapter, self.di_dict, action, 'FAIL'))
00266                                     dwell = False
00267                                     self.capture_xml = False
00268                                     
00269                                     # When response is FAIL, set server result and communicate as below:
00270                                     # Extract action attribute.  For CNC Actions, only one result --> i.e. OpenDoorResult.__slots__[0] = 'door_ready'
00271                                     result_attribute = self._resultDict[self.server_name[action]].__slots__[0]
00272                                     
00273                                     # Set the attribute per the ROS to MTConnect conversion
00274                                     setattr(self._resultDict[self.server_name[action]], result_attribute, 'FAIL')
00275                                     
00276                                     # Indicate an aborted action
00277                                     self._as[self.server_name[action]].set_aborted(self._resultDict[self.server_name[action]])
00278                                     rospy.loginfo('In %s Callback -- action aborted by CNC.' % self.server_name[action])
00279                         
00280                         # Release the queue
00281                         self.XML_queue.task_done()
00282                 except rospy.ROSInterruptException:
00283                     rospy.loginfo('program interrupted before completion')
00284         else: # Abort the action request, machine tool not ready or faulted
00285             # Extract action attribute.  For CNC Actions, only one result --> i.e. OpenDoorResult.__slots__[0] = 'door_ready'
00286             result_attribute = self._resultDict[self.server_name[action]].__slots__[0]
00287             
00288             # Set the attribute per the ROS to MTConnect conversion
00289             setattr(self._resultDict[self.server_name[action]], result_attribute, 'NOT_READY')
00290             
00291             # Indicate an aborted action
00292             self._as[self.server_name[action]].set_aborted(self._resultDict[self.server_name[action]])
00293             rospy.loginfo('In %s Callback -- action aborted, machine tool NOT_READY, FAULTED, or UNAVAILABLE.' % self.server_name[action])
00294         return
00295 
00296     ## @brief Processes the xml chunk provided by the LongPull class instance and stores the result
00297     ## into the XML_queue since the tags have changed.  The queue is used to ensure capture of updated
00298     ## tags while the execute_cb function is being executed.
00299     ## @param chunk: xml data, read from response.read()
00300     def xml_callback(self, chunk):
00301         #rospy.loginfo('*******************In PROCESS_XML callback***************')
00302         try:
00303             if self.capture_xml == True:
00304                 self.XML_queue.put(chunk)
00305                 if self.XML_queue.qsize() > 1:
00306                     rospy.logdebug('STORED XML INTO QUEUE, WAITING ON ROS ACTION SERVER, QUEUE SIZE --> %s' % self.XML_queue.qsize())
00307         except Exception as e:
00308             rospy.logerr("Bridge Server: Process XML callback failed: %s, releasing lock" % e)
00309         finally:
00310             pass
00311         #rospy.loginfo('*******************Done with PROCESS_XML callback***************')
00312         return
00313 
00314 if __name__ == '__main__':
00315     try:
00316         rospy.loginfo('Started Generic Action Server')
00317         robot_action = GenericActionServer()
00318         rospy.spin()
00319     except rospy.ROSInterruptException:
00320         rospy.loginfo('program interrupted before completion')
00321         xml_thread.join()


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