ethercat_listener.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 #
00003 # Software License Agreement (BSD License)
00004 #
00005 # Copyright (c) 2009, Willow Garage, Inc.
00006 # All rights reserved.
00007 #
00008 # Redistribution and use in source and binary forms, with or without
00009 # modification, are permitted provided that the following conditions
00010 # are met:
00011 #
00012 #  * Redistributions of source code must retain the above copyright
00013 #    notice, this list of conditions and the following disclaimer.
00014 #  * Redistributions in binary form must reproduce the above
00015 #    copyright notice, this list of conditions and the following
00016 #    disclaimer in the documentation and/or other materials provided
00017 #    with the distribution.
00018 #  * Neither the name of Willow Garage, Inc. nor the names of its
00019 #    contributors may be used to endorse or promote products derived
00020 #    from this software without specific prior written permission.
00021 #
00022 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00023 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00024 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00025 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00026 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00027 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00028 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00029 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00030 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00031 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00032 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00033 # POSSIBILITY OF SUCH DAMAGE.
00034 #
00035 
00036 ##\author Kevin Watts
00037 ##\brief Listens to pr2_etherCAT/motors_halted, makes sure etherCAT state is OK
00038 
00039 from __future__ import with_statement
00040 PKG = 'pr2_hardware_test_monitor'
00041 
00042 import roslib; roslib.load_manifest(PKG)
00043 
00044 from std_msgs.msg import Bool
00045 from std_srvs.srv import *
00046 from diagnostic_msgs.msg import DiagnosticArray
00047 
00048 import rospy
00049 
00050 import threading
00051 import re
00052 import itertools
00053 
00054 from pr2_hw_listener import PR2HWListenerBase
00055 
00056 from collections import deque
00057 
00058 DROPS_PER_HOUR = 10
00059 
00060 ENCODER_ERRORS_FIELD = 'Num encoder_errors'
00061 
00062 class EthercatListener(PR2HWListenerBase):
00063     def __init__(self):
00064         self._mutex = threading.Lock()
00065 
00066         self._cal = False
00067         self._ok = True
00068         self._update_time = -1
00069         self._diag_update_time = -1
00070 
00071         # Records dropped packets. New drops appended to deque
00072         # Deque is kept =< max length of self._drops_per_hour
00073         self._dropped_times = deque()
00074 
00075         self._net_drops = 0
00076 
00077         self._encoder_errors_detected = False
00078         self._last_encoder_errors_update = rospy.get_time()
00079         # Stores encoder errors by motor for each motor
00080         self._encoder_errors_cnt = {}
00081 
00082         self._drops_per_hour = DROPS_PER_HOUR
00083 
00084         # A list of expected device names, or None if we don't care
00085         self._expected_devices = None
00086         self._incorrect_devices = None
00087         self._device_name_re = re.compile('EtherCAT Device \((.*)\).*')
00088         
00089         self._params = []
00090 
00091     def create(self, params):
00092        # Give it 10 seconds to start up
00093         try:
00094             rospy.wait_for_service('pr2_etherCAT/halt_motors', 10)
00095             self._srv_ok = True
00096         except Exception, e:
00097             self._srv_ok = False
00098             rospy.logerr('Unable to find halt motors service. Unable to initialize ethercat listener')
00099             return False
00100 
00101         if params.has_key('drops_per_hour'):            
00102             self._drops_per_hour = params['drops_per_hour'] 
00103 
00104         if params.has_key('expected_devices'):
00105             self._expected_devices = params['expected_devices']
00106             
00107         self._params = params
00108 
00109         self._reset_motors = rospy.ServiceProxy('pr2_etherCAT/reset_motors', Empty)
00110 
00111         self._halt_motors = rospy.ServiceProxy('pr2_etherCAT/halt_motors', Empty)
00112 
00113         # Make this persistent in case the master goes down
00114         self._halt_motors2 = rospy.ServiceProxy('pr2_etherCAT/halt_motors', Empty, persistent = True)
00115 
00116         self._ecat_sub = rospy.Subscriber('pr2_etherCAT/motors_halted', Bool, self._motors_cb)
00117 
00118         self._cal_sub = rospy.Subscriber('calibrated', Bool, self._cal_cb)
00119 
00120         self._diag_sub = rospy.Subscriber('/diagnostics', DiagnosticArray, self._diag_cb)
00121 
00122         return True
00123 
00124     # Try twice to halt motors, using persistant service for one try
00125     def halt(self):
00126         try:
00127             self._halt_motors2()
00128             self._halt_motors()
00129 
00130         except Exception, e:
00131             try:
00132                 # Redo, with new persistent service
00133                 self._halt_motors2 = rospy.ServiceProxy('pr2_etherCAT/halt_motors', Empty, persistent = True)
00134                 self._halt_motors2()
00135 
00136                 return
00137             except Exception, e:
00138                 rospy.logerr('Unable to halt motors. pr2_etherCAT may have died')
00139                 
00140 
00141     def reset(self):
00142         try:
00143             self._reset_motors()
00144         except Exception, e:
00145             rospy.logerr('Unable to reset motors. pr2_etherCAT may have died')
00146 
00147         with self._mutex:
00148             self._dropped_times.clear()
00149             self._encoder_errors_detected = False
00150 
00151     def _update_drops(self, stat, now):
00152         if stat.name != 'EtherCAT Master':
00153             raise Exception('Diagnostic status with invalid name! Expected \"EtherCAT Master\", got: %s',
00154                             stat.name)
00155 
00156         drops = -1
00157         lates = -1
00158         for kv in stat.values:
00159             if kv.key == 'Dropped Packets':
00160                 drops = int(kv.value)
00161             elif kv.key == 'RX Late Packet':
00162                 lates = int(kv.value)
00163 
00164         if (drops == -1 or lates == -1):
00165             raise Exception("Diagnostics didn't contain data for dropped or late packets. Drops: %d. Lates: %d" %
00166                             (drops, lates))
00167 
00168         # For every new dropped packet, we add the current timestamp to our deque
00169         new_net_drops = drops - lates
00170         if new_net_drops > self._net_drops:
00171             for i in range(min(new_net_drops - self._net_drops, self._drops_per_hour)):
00172                 self._dropped_times.append(now)
00173 
00174         self._net_drops = new_net_drops
00175         
00176         # Clean up the buffer to the max_length
00177         while len(self._dropped_times) > self._drops_per_hour:
00178             self._dropped_times.popleft()
00179 
00180         self._diag_update_time = rospy.get_time()
00181 
00182     def _update_encoder_errors(self, stat):
00183         """
00184         Check for encoder errors for every motor. 
00185         
00186         Updates cache of encoder errors. Reports encoder error detected if
00187         it can't find the number of encoder errors, or if encoder errors count
00188         increases.
00189         """
00190         if not stat.name.startswith('EtherCAT Device ('):
00191             raise Exception("Invalid diagnostic status name: %s" % stat.name)
00192 
00193         # Ignore led_projector, no encoder errors
00194         if stat.name.find('led_projector') > -1:
00195             return
00196 
00197         last_errors = self._encoder_errors_cnt.get(stat.name, 0)
00198         
00199         curr_errors = -1
00200         for kv in stat.values:
00201             if kv.key == ENCODER_ERRORS_FIELD:
00202                 curr_errors = int(kv.value)
00203 
00204         if curr_errors < 0:
00205             rospy.logerr('Unable to find encoder errors for motor %s', stat.name)
00206             self._encoder_errors_detected = True
00207             return 
00208                
00209         self._last_encoder_errors_update = rospy.get_time()
00210 
00211         if curr_errors > last_errors:
00212             self._encoder_errors_detected = True
00213             
00214         # Update cache with last value for encoder status
00215         self._encoder_errors_cnt[stat.name] = curr_errors
00216         
00217         
00218 
00219     def _diag_cb(self, msg):
00220         is_ethercat_diag_msg = False
00221         devices = []
00222         with self._mutex:
00223             now = msg.header.stamp.to_sec()
00224             for stat in msg.status:
00225                 if stat.name == 'EtherCAT Master':
00226                     self._update_drops(stat, now)
00227                     is_ethercat_diag_msg = True
00228                 elif stat.name.startswith('EtherCAT Device ('):
00229                     self._update_encoder_errors(stat)
00230                     m = self._device_name_re.match(stat.name)
00231                     if m:
00232                         device_name = m.group(1)
00233                         devices.append(device_name)
00234 
00235         if is_ethercat_diag_msg and (self._expected_devices is not None):
00236             # check list of present devices against expected list of devices
00237             if self._expected_devices != devices:
00238                 with self._mutex:
00239                     self._incorrect_devices = devices
00240 
00241     def _cal_cb(self, msg):
00242         with self._mutex:
00243             self._cal = msg.data
00244 
00245     def _motors_cb(self, msg):
00246         with self._mutex:
00247             self._ok = not msg.data
00248             self._update_time = rospy.get_time()
00249     
00250     def _is_dropping_pkts(self):
00251         """
00252         Check if we're dropping packets. 
00253         A drop is true if we've had more than 10 dropped packets in last hour.
00254          
00255         @return bool : True if dropping packets
00256         """
00257         now = rospy.get_time()
00258 
00259         if len(self._dropped_times) < self._drops_per_hour:
00260             return False
00261 
00262         return abs(now - self._dropped_times[0]) < 3600
00263             
00264 
00265     def check_ok(self):
00266         msg = ''
00267         stat = 0
00268         with self._mutex:
00269             if not self._cal:
00270                 stat = 1
00271                 msg = 'Uncalibrated'
00272 
00273             if not self._ok:
00274                 stat = 2
00275                 msg = 'Motors Halted'
00276 
00277             if self._incorrect_devices is not None:
00278                 stat = 2
00279                 msg = 'Wrong devices'
00280                 dev_list = itertools.izip_longest(self._expected_devices, self._incorrect_devices, fillvalue ='<NOTHING>')
00281                 for (expected_dev, incorrect_dev) in dev_list:
00282                     if expected_dev != incorrect_dev:
00283                         msg = 'Expected %s, found %s' % (expected_dev, incorrect_dev)
00284                         break
00285 
00286             # Error if we've had dropped packets
00287             if self._is_dropping_pkts():
00288                 stat = 2
00289                 msg = 'Dropping Packets'
00290 
00291             # Encoder errors check, #4814
00292             if self._encoder_errors_detected:
00293                 stat = 2
00294                 msg = 'Encoder Errors'
00295 
00296             if rospy.get_time() - self._last_encoder_errors_update > 3.0:
00297                 stat = 3
00298                 msg = 'No MCB Encoder Status'
00299 
00300             if rospy.get_time() - self._diag_update_time > 3.0:
00301                 stat = 3
00302                 msg = 'No MCB Diagnostics'
00303 
00304             if rospy.get_time() - self._update_time > 3.0:
00305                 stat = 3
00306                 msg = 'EtherCAT Stale'
00307                 if self._update_time == -1:
00308                     msg = 'No EtherCAT Data'
00309         
00310         return stat, msg, []
00311     


pr2_hardware_test_monitor
Author(s): Kevin Watts
autogenerated on Sat Dec 28 2013 17:54:19