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"