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 from __future__ import with_statement
00035 import roslib; roslib.load_manifest('pr2_camera_synchronizer')
00036 from dynamic_reconfigure.client import Client as DynamicReconfigureClient
00037 from dynamic_reconfigure.server import Server as DynamicReconfigureServer
00038 from ethercat_trigger_controllers.srv import SetMultiWaveform as SetMultiWaveform
00039 from ethercat_trigger_controllers.srv import SetMultiWaveformRequest as SetMultiWaveformRequest
00040 from ethercat_trigger_controllers.msg import MultiWaveform as MultiWaveform
00041 from ethercat_trigger_controllers.msg import MultiWaveformTransition as MultiWaveformTransition
00042 import pr2_camera_synchronizer.cfg.CameraSynchronizerConfig as Config
00043 import wge100_camera.cfg.WGE100CameraConfig as WGEConfig
00044 
00045 import rospy
00046 import time
00047 import math
00048 import threading
00049 import signal
00050 
00051 from pr2_camera_synchronizer.cfg import CameraSynchronizerConfig as ConfigType
00052 from pr2_camera_synchronizer.levels import *
00053 from diagnostic_msgs.msg import DiagnosticStatus, KeyValue, DiagnosticArray
00054 
00055 ETHERCAT_INTERVAL = 0.001
00056 
00057 param_rate = "rate"
00058 param_trig_mode = "trig_mode"
00059 camera_parameters = [ param_rate, param_trig_mode ]
00060 
00061 param_proj_rate = "projector_rate"
00062 
00063 def roundToEthercat(val):
00064     return ETHERCAT_INTERVAL * round(val / ETHERCAT_INTERVAL)
00065 
00066 asynchronous_updaters = []
00067 def killAsynchronousUpdaters():
00068     for updater in asynchronous_updaters:
00069         updater.kill()
00070 class AsynchronousUpdater(threading.Thread):
00071     def __init__(self, f, name):
00072         threading.Thread.__init__(self)
00073         self.name = name
00074         self.f = f
00075         self.allargs = None
00076         self.cv = threading.Condition()
00077         asynchronous_updaters.append(self)
00078         self.exiting = False
00079         self.idle = True
00080         self.start()
00081 
00082     def update(self, *args, **nargs):
00083         with self.cv:
00084             self.allargs = (args, nargs)
00085             self.cv.notify()
00086             
00087 
00088     def run(self):
00089         
00090         while True:
00091             
00092             with self.cv:
00093                 if self.exiting:
00094                     break
00095                 if self.allargs == None:
00096                   
00097                    self.idle = True
00098                    self.cv.wait()
00099                    self.idle_end_time = rospy.get_time()
00100                    self.idle = False
00101                   
00102                 allargs = self.allargs
00103                 self.allargs = None
00104             if allargs != None:
00105                 try:
00106                     self.f(*allargs[0], **allargs[1])
00107                 except Exception, e:
00108                     rospy.logerr("AsynchronousUpdater failed with exception: %s"%str(e))
00109                     pass
00110 
00111     def kill(self):
00112         
00113         
00114         with self.cv:
00115             self.exiting = True
00116             self.allargs = None
00117             self.cv.notify()
00118 
00119     def getStatus(self): 
00120         if self.idle:
00121             return self.name, 0
00122         interval = rospy.get_time() - self.idle_end_time
00123         return self.name, interval
00124 
00125 class MultiTriggerController:
00126   def __init__(self, name):
00127     self.period = 0
00128     self.zero_offset = 0
00129     self.clear_waveform()
00130     self.name = name
00131     self.async = AsynchronousUpdater(self.async_update, "Controller "+name)
00132     self.service = None
00133     self.transitions = []
00134 
00135   def clear_waveform(self):
00136     self.transitions = []
00137                                                     
00138   def add_sample(self, time, value, topic):
00139     time = roundToEthercat(time) + 0.5 * ETHERCAT_INTERVAL
00140     self.transitions.append(MultiWaveformTransition(time, value, topic))
00141   
00142   def async_update(self, period, zero_offset, transitions):
00143       try:
00144           if self.service == None:
00145               service_name = self.name+"/set_waveform"
00146               rospy.wait_for_service(service_name)
00147               self.service = rospy.ServiceProxy(service_name, SetMultiWaveform)
00148               
00149 
00150           
00151           waveform = MultiWaveform(period, zero_offset, transitions)
00152           
00153           rslt = self.service(waveform)
00154           if not rslt.success:
00155               rospy.logerr("Error setting waveform %s: %s"%(self.name, rslt.status_message))
00156           
00157       except KeyboardInterrupt: 
00158           print "Aborted trigger update on", self.name
00159 
00160   def update(self):
00161       
00162       
00163       
00164       self.async.update(self.period, self.zero_offset, self.transitions)
00165 
00166 class ProsilicaInhibitTriggerController(MultiTriggerController):
00167     def __init__(self, name, param, true_val, false_val):
00168         MultiTriggerController.__init__(self, name)
00169         self.param = param
00170         self.true_val = true_val
00171         self.false_val = false_val
00172 
00173     def process_update(self, config, level):
00174         self.period = 1
00175         self.zero_offset = 0
00176         self.clear_waveform()
00177         self.add_sample(0, { True: self.true_val, False: self.false_val}[config[self.param]], '-')
00178         MultiTriggerController.update(self)
00179 
00180 class ProjectorTriggerController(MultiTriggerController):
00181     def __init__(self, name, proj):
00182         MultiTriggerController.__init__(self, name)
00183         self.proj = proj
00184 
00185     def update(self):
00186         self.period = self.proj.repeat_period
00187         self.zero_offset = self.proj.zero_offset
00188         self.clear_waveform()
00189         self.high_val = 0xf
00190         if (self.proj.needed == False and self.proj.mode == Config.CameraSynchronizer_ProjectorAuto) or \
00191                 self.proj.mode == Config.CameraSynchronizer_ProjectorOff:
00192             self.high_val = 0xe
00193         for i in range(0, len(self.proj.pulse_starts)):
00194           self.add_sample(self.proj.pulse_starts[i], self.high_val, 'on_time')
00195           self.add_sample(self.proj.pulse_ends[i], 0xe, 'off_time')
00196         MultiTriggerController.update(self)
00197 
00198 class SingleCameraTriggerController(MultiTriggerController):
00199   def __init__(self, name, camera):
00200     MultiTriggerController.__init__(self, name)
00201     self.camera = camera
00202 
00203   def update(self):
00204     if self.camera.reset_cameras:
00205       self.camera_reset()
00206       return
00207     self.clear_waveform()
00208     if not self.camera.ext_trig:
00209       self.period = 1
00210       self.add_sample(0, 0, "-")
00211     else:
00212       self.period = 2 * self.camera.period
00213       self.zero_offset = -self.camera.imager_period 
00214       first_pulse_start = self.camera.end_offset
00215       second_pulse_start = self.camera.period + first_pulse_start
00216       first_frame_end = first_pulse_start + self.camera.imager_period
00217       extra_pulse_start = (first_pulse_start + first_frame_end) / 2  
00218       trigger_name = "trigger"
00219       self.add_sample(first_pulse_start, 1, trigger_name)
00220       self.add_sample((first_pulse_start + extra_pulse_start) / 2, 0, "-")
00221       self.add_sample(extra_pulse_start, 1, "-")
00222       self.add_sample((extra_pulse_start + first_frame_end) / 2, 0, "-")
00223       self.add_sample(second_pulse_start, 1, trigger_name)
00224       self.add_sample((second_pulse_start + self.period) / 2, 0, "-")
00225       self.camera.trig_rising = True
00226       self.camera.trigger_name = self.name+"/"+trigger_name
00227     
00228     
00229     MultiTriggerController.update(self)
00230 
00231   def camera_reset(self):
00232     self.clear_waveform()
00233     self.period = 1.5
00234     self.add_sample(0, 0, "-")
00235     self.add_sample(0.1, 1, "-")
00236     MultiTriggerController.update(self)
00237 
00238 class DualCameraTriggerController(SingleCameraTriggerController):
00239   def __init__(self, name, camera1, camera2):
00240     SingleCameraTriggerController.__init__(self, name, None)
00241     self.cameras = [camera1, camera2]
00242 
00243   def update(self):
00244     if self.cameras[0].reset_cameras or self.cameras[1].reset_cameras:
00245       self.camera_reset()
00246       return
00247     
00248     if self.cameras[0].period != self.cameras[1].period or \
00249        self.cameras[0].imager_period != self.cameras[1].imager_period:
00250        rospy.logerr("Cameras on same trigger have different period/imager_period settings.")
00251     
00252     self.clear_waveform()
00253     if self.cameras[0].end_offset == self.cameras[1].end_offset:
00254         
00255         self.camera = self.cameras[0]
00256         SingleCameraTriggerController.update(self)
00257         self.cameras[1].trigger_name = self.cameras[0].trigger_name
00258         self.cameras[1].trig_rising = self.cameras[0].trig_rising
00259         return
00260     
00261     if self.cameras[0].end_offset > self.cameras[1].end_offset:
00262         
00263         self.cameras.reverse()
00264 
00265     
00266     assert self.cameras[0].proj == self.cameras[1].proj
00267     assert self.cameras[0].period == self.cameras[1].period 
00268 
00269     self.cameras[0].trig_rising = True
00270     self.cameras[1].trig_rising = False
00271 
00272     self.clear_waveform()
00273     self.period = 2 * self.cameras[1].period
00274     self.zero_offset = -self.cameras[0].imager_period 
00275 
00276     first_rise = self.cameras[0].end_offset
00277     first_fall = self.cameras[1].end_offset
00278     first_rise_end = first_rise + self.cameras[0].imager_period
00279     second_rise = self.cameras[0].end_offset + self.cameras[0].period
00280     second_fall = self.cameras[1].end_offset + self.cameras[1].period
00281     extra_rise = (2 * first_fall + first_rise_end) / 3                            
00282     extra_fall = (first_fall + 2 * first_rise_end) / 3
00283                                               
00284     trigger_name = [ "trigger_"+camera.name for camera in self.cameras ]
00285     self.add_sample(first_rise,  1, trigger_name[0])
00286     self.add_sample(first_fall,  0, trigger_name[1])
00287     self.add_sample(extra_rise,  1, "-")
00288     self.add_sample(extra_fall,  0, "-")
00289     self.add_sample(second_rise, 1, trigger_name[0])
00290     self.add_sample(second_fall, 0, trigger_name[1])
00291     for i in range(0,2):
00292       self.cameras[i].trigger_name = self.name+"/"+trigger_name[i]
00293       
00294     
00295     
00296     MultiTriggerController.update(self)
00297 
00298 class Projector:
00299   def process_update(self, config, level):
00300     
00301     
00302     
00303     
00304     
00305     
00306     
00307     
00308     
00309     self.needed = False 
00310 
00311     if level & lvl_projector == 0:
00312       return; 
00313 
00314     base_period = 1.0 / config["projector_rate"]
00315     self.pulse_length = config["projector_pulse_length"]
00316     pulse_shift = config["projector_pulse_shift"]
00317     self.zero_offset = roundToEthercat(config["projector_tweak"])
00318     self.mode = config["projector_mode"]
00319 
00320     base_period = roundToEthercat(base_period)
00321     
00322     base_period = max(base_period, 4 * ETHERCAT_INTERVAL)
00323     
00324     self.repeat_period = 4.0 * base_period
00325 
00326     
00327     self.pulse_length = min(self.pulse_length, math.floor(base_period / ETHERCAT_INTERVAL / 2.0 - 1) * ETHERCAT_INTERVAL)
00328     self.pulse_length = math.ceil(self.pulse_length / ETHERCAT_INTERVAL) * ETHERCAT_INTERVAL
00329 
00330     min_second_start = base_period
00331     max_second_start = 2 * base_period - 2 * ETHERCAT_INTERVAL - 2 * self.pulse_length
00332     second_start = min_second_start * (1 - pulse_shift) + max_second_start * pulse_shift
00333     second_start = roundToEthercat(second_start)
00334     config["projector_pulse_shift"] = (second_start - min_second_start) / (max_second_start - min_second_start)
00335 
00336     self.pulse_starts = [
00337         0,
00338         second_start,
00339         2 * base_period,
00340         2 * base_period + second_start + self.pulse_length + 2 * ETHERCAT_INTERVAL
00341         ]
00342     self.pulse_ends = [ start + self.pulse_length for start in self.pulse_starts ]
00343 
00344     self.proj_end_offset = self.pulse_length + ETHERCAT_INTERVAL
00345     self.alt_end_offset = second_start + self.pulse_length + ETHERCAT_INTERVAL
00346     self.noproj_end_offset = second_start - ETHERCAT_INTERVAL
00347 
00348     self.proj_exposure_duration = self.pulse_length + ETHERCAT_INTERVAL * 1.5
00349     self.noproj_max_exposure = second_start - self.pulse_length - ETHERCAT_INTERVAL * 1.5
00350 
00351     config["projector_rate"] = 1 / base_period
00352     config["projector_pulse_length"] = self.pulse_length
00353     config["projector_tweak"] = self.zero_offset
00354 
00355 class Camera:
00356   def __init__(self, node_name, proj, level, **paramnames):
00357     self.paramnames = paramnames
00358     self.name = node_name
00359     self.level = level
00360     self.proj = proj
00361     self.reconfigure_client = None
00362     self.async = AsynchronousUpdater(self.async_apply_update, "Camera "+node_name)
00363     self.trig_rising = True
00364 
00365   def param(self, config, name):
00366     return config[self.paramnames[name]]
00367 
00368   def setparam(self, config, name, value):
00369     config[self.paramnames[name]] = value
00370 
00371   
00372   def process_update(self, config, level):
00373     
00374     
00375     
00376     
00377     
00378     
00379     self.trigger_name = "not_set" 
00380     
00381     
00382     
00383     
00384     
00385     
00386     self.period = 1.0 / self.param(config, param_rate)
00387     projector_rate = config[param_proj_rate]
00388     self.reset_cameras = config["camera_reset"]
00389     self.ext_trig = True
00390     self.register_set = WGEConfig.WGE100Camera_PrimaryRegisterSet
00391 
00392     projector_limits_exposure = True
00393 
00394     trig_mode = self.param(config, param_trig_mode)
00395     
00396     if trig_mode == Config.CameraSynchronizer_InternalTrigger:
00397       self.ext_trig = False
00398       self.imager_period = self.period
00399       self.end_offset = -1
00400       projector_limits_exposure = False
00401     else:
00402       if self.proj.mode == Config.CameraSynchronizer_ProjectorOff:
00403           trig_mode = Config.CameraSynchronizer_IgnoreProjector
00404 
00405       if trig_mode == Config.CameraSynchronizer_AlternateProjector or trig_mode == Config.CameraSynchronizer_WithProjector:
00406           self.proj.needed = True
00407           
00408       
00409           
00410       
00411       
00412       if trig_mode == Config.CameraSynchronizer_AlternateProjector:
00413         n = round(self.period / self.proj.repeat_period - 0.5)
00414         n = max(n, 0)
00415         self.period = (n + 0.5) * self.proj.repeat_period
00416         
00417       elif trig_mode == Config.CameraSynchronizer_IgnoreProjector:
00418         self.period = roundToEthercat(self.period)
00419       else:
00420         n = round(2 * self.period / self.proj.repeat_period - 1)
00421         n = max(n, 0)
00422         self.period = (n + 1) * self.proj.repeat_period / 2.0
00423         
00424 
00425       
00426       if trig_mode == Config.CameraSynchronizer_IgnoreProjector:
00427         self.end_offset = 0
00428         projector_limits_exposure = False
00429       if trig_mode == Config.CameraSynchronizer_AlternateProjector:
00430         self.end_offset = self.proj.alt_end_offset 
00431         self.register_set = WGEConfig.WGE100Camera_Auto
00432       elif trig_mode == Config.CameraSynchronizer_WithProjector:
00433         self.end_offset = self.proj.proj_end_offset
00434         self.register_set = WGEConfig.WGE100Camera_AlternateRegisterSet
00435       else:
00436         self.end_offset = self.proj.noproj_end_offset
00437 
00438       
00439       if trig_mode == Config.CameraSynchronizer_IgnoreProjector:
00440           self.imager_period = self.period - ETHERCAT_INTERVAL
00441       else:
00442           self.imager_period = self.proj.repeat_period / 2 - ETHERCAT_INTERVAL
00443 
00444     
00445     if projector_limits_exposure:
00446         self.max_exposure = self.proj.noproj_max_exposure
00447         
00448     else:
00449         self.max_exposure = self.imager_period * 0.95
00450         
00451 
00452     self.setparam(config, param_rate, 1/self.period)
00453     self.setparam(config, param_trig_mode, trig_mode)
00454 
00455   def async_apply_update(self, reconfig_request):
00456       try:    
00457           
00458           if self.reconfigure_client == None:
00459               
00460               self.reconfigure_client = DynamicReconfigureClient(self.name)
00461               
00462 
00463           self.reconfigure_client.update_configuration(reconfig_request)
00464           
00465           
00466       except KeyboardInterrupt: 
00467           print "Aborted camera update on", self.name
00468       
00469   def apply_update(self):
00470       reconfig_request = {
00471               "ext_trig" : self.ext_trig,
00472               "trig_rate" : 1.0 / self.period,
00473               "imager_rate" : 1.0 / self.imager_period,
00474               "trig_timestamp_topic" : self.trigger_name,
00475               "rising_edge_trig" : self.trig_rising,
00476               "register_set" : self.register_set,
00477               "auto_exposure_alternate" : False,
00478               "exposure_alternate" : self.proj.proj_exposure_duration,
00479               "max_exposure" : self.max_exposure,
00480               }
00481       
00482 
00483       self.async.update(reconfig_request)
00484 
00485 
00486 
00487 
00488 
00489 
00490 
00491 
00492 
00493 
00494 
00495 
00496 
00497 
00498 
00499 
00500 
00501 class CameraSynchronizer:
00502   def __init__(self, forearm=True):
00503     stereo_camera_names = [ "narrow_stereo", "wide_stereo" ] 
00504     forearm_camera_names = [ "forearm_r", "forearm_l" ]
00505     self.camera_names = stereo_camera_names
00506     if forearm:
00507         self.camera_names = self.camera_names + forearm_camera_names
00508     
00509     parameters = [ param_rate, param_trig_mode ]
00510     camera_parameters = dict((name, dict((suffix, name+"_"+suffix) for suffix in parameters)) for name in self.camera_names)
00511     
00512     
00513     for camera in stereo_camera_names:
00514       camera_parameters[camera][param_rate] = "stereo_rate"
00515       camera_parameters[camera]["node_name"] = camera+"_both"
00516     if forearm:
00517       for camera in forearm_camera_names:
00518         camera_parameters[camera]["node_name"] = camera[-1]+"_forearm_cam"
00519     for i in range(0, len(self.camera_names)):
00520       camera_parameters[self.camera_names[i]]["level"] = 1 << i; 
00521 
00522     self.projector = Projector()
00523     self.cameras = dict((name, Camera(proj = self.projector, **camera_parameters[name])) for name in self.camera_names)
00524     self.prosilica_inhibit = ProsilicaInhibitTriggerController('prosilica_inhibit_projector_controller', "prosilica_projector_inhibit", 0x0A, 0x00)
00525     
00526     self.controllers = [
00527       ProjectorTriggerController('projector_trigger', self.projector),
00528       DualCameraTriggerController('head_camera_trigger', *[self.cameras[name] for name in stereo_camera_names])]
00529     if forearm:
00530       self.controllers = self.controllers + [
00531           SingleCameraTriggerController('r_forearm_cam_trigger', self.cameras["forearm_r"]),
00532           SingleCameraTriggerController('l_forearm_cam_trigger', self.cameras["forearm_l"]),
00533         ]
00534 
00535     self.server = DynamicReconfigureServer(ConfigType, self.reconfigure)
00536 
00537   @staticmethod
00538   def print_threads():
00539     threads = threading.enumerate()
00540     n = 0
00541     for t in threads:
00542         if not t.isDaemon() and t != threading.currentThread():
00543             try: 
00544                print t.name
00545             except:
00546                 print "Unknown thread ", t
00547             n = n + 1
00548     return n
00549 
00550   def kill(self):
00551     print "\nWaiting for all threads to die..."
00552     killAsynchronousUpdaters()
00553     
00554     while self.print_threads() > 0:         
00555         print "\nStill waiting for all threads to die..."
00556         time.sleep(1)
00557     print
00558 
00559   def reconfigure(self, config, level):
00560     
00561     
00562     self.projector.process_update(config, level)
00563     self.prosilica_inhibit.process_update(config, level)
00564     
00565     for camera in self.cameras.values():
00566       camera.process_update(config, level)
00567     
00568     
00569     
00570     
00571     for controller in self.controllers:
00572         controller.update();
00573     for camera in self.cameras.values():
00574         camera.apply_update()
00575     
00576     self.config = config
00577     return config
00578   
00579   def update_diagnostics(self):
00580     da = DiagnosticArray()
00581     ds = DiagnosticStatus()
00582     ds.name = rospy.get_caller_id().lstrip('/') + ": Tasks"
00583     in_progress = 0;
00584     longest_interval = 0;
00585     for updater in list(asynchronous_updaters):
00586         (name, interval) = updater.getStatus()
00587         if interval == 0:
00588             msg = "Idle"
00589         else:
00590             in_progress = in_progress + 1
00591             msg = "Update in progress (%i s)"%interval
00592         longest_interval = max(interval, longest_interval)
00593         ds.values.append(KeyValue(name, msg))
00594     if in_progress == 0:
00595         ds.message = "Idle"
00596     else:
00597         ds.message = "Updates in progress: %i"%in_progress
00598     if longest_interval > 10:
00599         ds.level = 1
00600         ds.message = "Update is taking too long: %i"%in_progress
00601     ds.hardware_id = "none"
00602     da.status.append(ds)
00603     da.header.stamp = rospy.get_rostime()
00604     self.diagnostic_pub.publish(da)
00605 
00606   def spin(self):
00607     self.diagnostic_pub = rospy.Publisher("/diagnostics", DiagnosticArray)
00608     try:
00609       reset_count = 0
00610       rospy.loginfo("Camera synchronizer is running...")
00611       controller_update_count = 0
00612       while not rospy.is_shutdown():
00613           if self.config['camera_reset'] == True:
00614               reset_count = reset_count + 1
00615               if reset_count > 3:
00616                   self.server.update_configuration({'camera_reset' : False})
00617           else:
00618               reset_count = 0
00619           self.update_diagnostics()
00620           
00621           controller_update_count += 1
00622           if controller_update_count >= 10:
00623               controller_update_count = 0
00624               for controller in self.controllers:
00625                   controller.update();
00626           rospy.sleep(1)
00627     finally:
00628       rospy.signal_shutdown("Main thread exiting")
00629       self.kill()
00630       print "Main thread exiting"