Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
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
00072
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
00080 self._encoder_errors_cnt = {}
00081
00082 self._drops_per_hour = DROPS_PER_HOUR
00083
00084
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
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
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
00125 def halt(self):
00126 try:
00127 self._halt_motors2()
00128 self._halt_motors()
00129
00130 except Exception, e:
00131 try:
00132
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
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
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
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
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
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
00287 if self._is_dropping_pkts():
00288 stat = 2
00289 msg = 'Dropping Packets'
00290
00291
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