trans_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 
00038 ##\brief Listens to transmissions of specified joints, halts motors if error detected.
00039 
00040 from __future__ import with_statement
00041 PKG = 'pr2_hardware_test_monitor'
00042 
00043 import roslib
00044 roslib.load_manifest(PKG)
00045 
00046 from pr2_mechanism_msgs.msg import MechanismStatistics
00047 from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus, KeyValue
00048 from std_srvs.srv import *
00049 from std_msgs.msg import Bool
00050 
00051 import rospy
00052 
00053 import threading
00054 
00055 from pr2_hw_listener import PR2HWListenerBase
00056 
00057 GRACE_HITS = 5 # Max number of errors before halt motors called
00058 
00059 TURRET_NAME = 'fl_caster_rotation_joint'
00060 L_WHEEL_NAME = 'fl_caster_l_wheel_joint'
00061 R_WHEEL_NAME = 'fl_caster_r_wheel_joint'
00062 
00063 # From URDF
00064 WHEEL_RADIUS = 0.074792
00065 WHEEL_OFFSET = 0.049
00066 
00067 ALLOWED_SLIP = 0.020 # (2.0cm/interval)
00068 UPDATE_INTERVAL = 0.25 
00069 
00070 class CasterPosition(object):
00071     def __init__(self, msg):
00072         self.turret = None
00073         self.l_wheel = None
00074         self.r_wheel = None
00075         self.turret_cal = False
00076 
00077         if msg is None:
00078             return
00079 
00080         for js in msg:
00081             if js.name == TURRET_NAME:
00082                 self.turret_cal = js.is_calibrated
00083                 self.turret = js.position
00084             if js.name == L_WHEEL_NAME:
00085                 self.l_wheel = js.position
00086             if js.name == R_WHEEL_NAME:
00087                 self.r_wheel = js.position
00088     
00089     def has_data(self):
00090         return self.turret_cal and self.turret is not None and self.l_wheel is not None and self.r_wheel is not None
00091 
00092 def check_position(new, old):
00093     if not new.has_data() or not old.has_data():
00094         return True, None, None
00095 
00096     # Calculate turret rotation
00097     turret = new.turret - old.turret
00098     
00099     # Calculate wheel travel from offset
00100     wheel_dx = turret * WHEEL_OFFSET
00101 
00102     # Distances wheels actually moved
00103     r_dx = (new.r_wheel - old.r_wheel) * WHEEL_RADIUS
00104     l_dx = -1 * (new.l_wheel - old.l_wheel) * WHEEL_RADIUS
00105 
00106     # Error
00107     r_err = r_dx - wheel_dx
00108     l_err = l_dx - wheel_dx
00109 
00110     if (abs(r_err) > ALLOWED_SLIP) or (abs(l_err) > ALLOWED_SLIP):
00111         return False, r_err, l_err
00112     return True, r_err, l_err
00113 
00114 ##\brief Makes sure caster doesn't slip or drive forward
00115 class CasterSlipListener(object):
00116     def __init__(self):
00117         self._ok = True
00118         self._update_time = 0
00119                 
00120         self.last_position = CasterPosition(None)
00121 
00122         self._max_l_err_pos = None
00123         self._max_r_err_pos = None
00124 
00125         self._max_l_err_neg = None
00126         self._max_r_err_neg = None
00127 
00128         self._max_l_err_pos_reset = None
00129         self._max_r_err_pos_reset = None
00130 
00131         self._max_l_err_neg_reset = None
00132         self._max_r_err_neg_reset = None
00133 
00134         self._num_errors = 0
00135         self._num_errors_since_reset = 0
00136 
00137         self.diag = DiagnosticStatus()
00138         self.stat = 0
00139         self.msg = ''
00140             
00141     def create(self, params):
00142         return True
00143 
00144     def reset(self):
00145         self._ok = True
00146         self._num_errors_since_reset = 0
00147 
00148         self._max_l_err_pos_reset = None
00149         self._max_r_err_pos_reset = None
00150 
00151         self._max_l_err_neg_reset = None
00152         self._max_r_err_neg_reset = None
00153 
00154     def update(self, msg):
00155         if rospy.get_time() - self._update_time < UPDATE_INTERVAL:
00156             return self._ok
00157 
00158         self._update_time = rospy.get_time()
00159 
00160         position = CasterPosition(msg.joint_statistics)
00161 
00162         ok, r_err, l_err = check_position(position, self.last_position)
00163         if not ok:
00164             self._ok = False
00165             self._num_errors += 1
00166             self._num_errors_since_reset += 1
00167         
00168         if r_err is None:
00169             pass
00170         elif r_err > 0:
00171             self._max_r_err_pos = max(self._max_r_err_pos, abs(r_err))
00172             self._max_r_err_pos_reset = max(self._max_r_err_pos_reset, abs(r_err))
00173         else:
00174             self._max_r_err_neg = max(self._max_r_err_neg, abs(r_err))
00175             self._max_r_err_neg_reset = max(self._max_r_err_neg_reset, abs(r_err))
00176 
00177         if l_err is None:
00178             pass
00179         elif l_err > 0:
00180             self._max_l_err_pos = max(self._max_l_err_pos, abs(l_err))
00181             self._max_l_err_pos_reset = max(self._max_l_err_pos_reset, abs(l_err))
00182         else:
00183             self._max_l_err_neg = max(self._max_l_err_neg, abs(l_err))
00184             self._max_l_err_neg_reset = max(self._max_l_err_neg_reset, abs(l_err))
00185 
00186         self.last_position = position
00187 
00188         return self._ok
00189 
00190     def get_status(self):
00191         stat = 0
00192         if not self._ok:
00193             stat = 2
00194 
00195         if rospy.get_time() - self._update_time > 3:
00196             stat = 3
00197                 
00198         diag = DiagnosticStatus()
00199         diag.level = stat
00200         diag.name = 'Caster Slip Listener'
00201         diag.message = 'OK'
00202         if diag.level == 2:
00203             diag.message = 'Caster Slipping'
00204         if diag.level == 3:
00205             diag.message = 'Caster Stale'
00206             diag.level = 2
00207 
00208         diag.values.append(KeyValue("Turret", str(TURRET_NAME)))
00209         diag.values.append(KeyValue("R Wheel", str(R_WHEEL_NAME)))
00210         diag.values.append(KeyValue("L Wheel", str(L_WHEEL_NAME)))
00211         diag.values.append(KeyValue("Turret Position", str(self.last_position.turret)))
00212         diag.values.append(KeyValue("R Wheel Position", str(self.last_position.r_wheel)))
00213         diag.values.append(KeyValue("L Wheel Position", str(self.last_position.l_wheel)))
00214         diag.values.append(KeyValue("Max Pos. Left Slip", str(self._max_l_err_pos)))
00215         diag.values.append(KeyValue("Max Neg. Left Slip", str(self._max_l_err_neg)))
00216         diag.values.append(KeyValue("Max Pos. Right Slip", str(self._max_r_err_pos)))
00217         diag.values.append(KeyValue("Max Neg. Right Slip", str(self._max_r_err_neg)))
00218         diag.values.append(KeyValue("Max Pos. Left Slip (Reset)", str(self._max_l_err_pos_reset)))
00219         diag.values.append(KeyValue("Max Neg. Left Slip (Reset)", str(self._max_l_err_neg_reset)))
00220         diag.values.append(KeyValue("Max Pos. Right Slip (Reset)", str(self._max_r_err_pos_reset)))
00221         diag.values.append(KeyValue("Max Neg. Right Slip (Reset)", str(self._max_r_err_neg_reset)))
00222         diag.values.append(KeyValue("Wheel Offset", str(WHEEL_OFFSET)))
00223         diag.values.append(KeyValue("Wheel Diameter", str(WHEEL_RADIUS)))
00224         diag.values.append(KeyValue("Allowed Slip", str(ALLOWED_SLIP)))
00225         diag.values.append(KeyValue("Update Interval", str(UPDATE_INTERVAL)))
00226         diag.values.append(KeyValue("Total Errors", str(self._num_errors)))
00227         diag.values.append(KeyValue("Errors Since Reset", str(self._num_errors_since_reset)))
00228 
00229         return diag
00230 
00231 
00232 ##\brief Loads individual joint listeners, monitors all robot transmissions
00233 class TransmissionListener(PR2HWListenerBase):
00234     def __init__(self):
00235         self._joint_monitors = []
00236         self._mech_sub = None
00237         self._halt_motors = rospy.ServiceProxy('pr2_etherCAT/halt_motors', Empty)
00238 
00239         self._trans_sub = rospy.Subscriber('pr2_transmission_check/transmission_status', Bool, self._trans_cb)
00240         self._reset_trans = rospy.ServiceProxy('pr2_transmission_check/reset_transmission_check', Empty)
00241 
00242         self._mutex = threading.Lock()
00243         self._ok = True # Status callback OK
00244         self._jms_ok = True # Joint monitors OK
00245         self._last_msg_time = 0
00246         
00247     def create(self, params):
00248         for joint, joint_param in params.iteritems():
00249             # Ignore setup params
00250             if joint == 'type' or joint == 'file':
00251                 continue
00252 
00253             # NOTE: Not creating JointTransmissionListeners because pr2_transmission_check
00254             # can do this for us.
00255             if joint == 'caster_slip':
00256                 joint_mon = CasterSlipListener()
00257                 if not joint_mon.create(joint_param):
00258                     rospy.logerr('Unable to create CasterSlipListener')
00259                     return False
00260                 self._joint_monitors.append(joint_mon)
00261 
00262         if self._joint_monitors:
00263             self._mech_sub = rospy.Subscriber('mechanism_statistics', MechanismStatistics, self._callback)
00264 
00265         return True
00266 
00267     def _trans_cb(self, msg):
00268         with self._mutex:
00269             self._last_msg_time = rospy.get_time()
00270 
00271             was_ok = self._ok
00272             self._ok = msg.data
00273 
00274             if not self._ok and was_ok:
00275                 rospy.logerr('Halting motors, broken transmission.')
00276                 try:
00277                     self._halt_motors()
00278                 except Exception, e:
00279                     import traceback
00280                     rospy.logerr('Caught exception trying to halt motors: %s', traceback.format_exc())
00281 
00282 
00283         
00284     def _callback(self, msg):
00285         with self._mutex:
00286             self._last_msg_time = rospy.get_time()
00287             
00288             was_ok = self._jms_ok
00289             
00290             for joint_mon in self._joint_monitors:
00291                 ok = joint_mon.update(msg)
00292                 self._jms_ok = ok and self._jms_ok
00293                 
00294 
00295         # Halt if broken
00296         if not self._jms_ok and was_ok:
00297             rospy.logerr('Halting motors, caster slipping')
00298             try:
00299                 self._halt_motors()
00300             except Exception, e:
00301                 import traceback
00302                 rospy.logerr('Caught exception trying to halt motors: %s', traceback.format_exc())
00303 
00304     def reset(self):
00305         with self._mutex:
00306             self._ok = True
00307             self._jms_ok = True
00308             for joint_mon in self._joint_monitors:
00309                 joint_mon.reset()
00310 
00311             try:
00312                 self._reset_trans()
00313             except Exception, e:
00314                 import traceback
00315                 rospy.logerr("Unable to reset tranmission checker\n%s" % traceback.format_exc())
00316             
00317     def check_ok(self):
00318         with self._mutex:
00319             if self._last_msg_time == 0:
00320                 return 3, "No trans status", None
00321             if rospy.get_time() - self._last_msg_time > 3:
00322                 return 3, "Trans status stale", None
00323 
00324             if self._ok and self._jms_ok:
00325                 status = DiagnosticStatus.OK
00326                 msg = ''
00327             elif self._ok:  
00328                 status = DiagnosticStatus.ERROR
00329                 msg = 'Caster Slipping'
00330             else:
00331                 status = DiagnosticStatus.ERROR
00332                 msg = 'Transmission Broken'
00333         
00334             diag_stats = []
00335             for joint_mon in self._joint_monitors:
00336                 diag_stats.append(joint_mon.get_status())
00337                 
00338         return status, msg, diag_stats


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