synchronizer_classes.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # Software License Agreement (BSD License)
3 #
4 # Copyright (c) 2008, Willow Garage, Inc.
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions
9 # are met:
10 #
11 # * Redistributions of source code must retain the above copyright
12 # notice, this list of conditions and the following disclaimer.
13 # * Redistributions in binary form must reproduce the above
14 # copyright notice, this list of conditions and the following
15 # disclaimer in the documentation and/or other materials provided
16 # with the distribution.
17 # * Neither the name of Willow Garage, Inc. nor the names of its
18 # contributors may be used to endorse or promote products derived
19 # from this software without specific prior written permission.
20 #
21 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 # POSSIBILITY OF SUCH DAMAGE.
33 
34 from __future__ import with_statement
35 import roslib; roslib.load_manifest('pr2_camera_synchronizer')
36 from dynamic_reconfigure.client import Client as DynamicReconfigureClient
37 from dynamic_reconfigure.server import Server as DynamicReconfigureServer
38 from ethercat_trigger_controllers.srv import SetMultiWaveform as SetMultiWaveform
39 from ethercat_trigger_controllers.srv import SetMultiWaveformRequest as SetMultiWaveformRequest
40 from ethercat_trigger_controllers.msg import MultiWaveform as MultiWaveform
41 from ethercat_trigger_controllers.msg import MultiWaveformTransition as MultiWaveformTransition
42 import pr2_camera_synchronizer.cfg.CameraSynchronizerConfig as Config
43 import wge100_camera.cfg.WGE100CameraConfig as WGEConfig
44 
45 import rospy
46 import time
47 import math
48 import threading
49 import signal
50 
51 from pr2_camera_synchronizer.cfg import CameraSynchronizerConfig as ConfigType
53 from diagnostic_msgs.msg import DiagnosticStatus, KeyValue, DiagnosticArray
54 
55 ETHERCAT_INTERVAL = 0.001
56 
57 param_rate = "rate"
58 param_trig_mode = "trig_mode"
59 camera_parameters = [ param_rate, param_trig_mode ]
60 
61 param_proj_rate = "projector_rate"
62 
63 def roundToEthercat(val):
64  return ETHERCAT_INTERVAL * round(val / ETHERCAT_INTERVAL)
65 
66 asynchronous_updaters = []
68  for updater in asynchronous_updaters:
69  updater.kill()
70 class AsynchronousUpdater(threading.Thread):
71  def __init__(self, f, name):
72  threading.Thread.__init__(self)
73  self.name = name
74  self.f = f
75  self.allargs = None
76  self.cv = threading.Condition()
77  asynchronous_updaters.append(self)
78  self.exiting = False
79  self.idle = True
80  self.start()
81 
82  def update(self, *args, **nargs):
83  with self.cv:
84  self.allargs = (args, nargs)
85  self.cv.notify()
86  #print "update", self.allargs
87 
88  def run(self):
89  #print "run"
90  while True:
91  #print "Starting loop on", self.name
92  with self.cv:
93  if self.exiting:
94  break
95  if self.allargs == None:
96  #print "start wait"
97  self.idle = True
98  self.cv.wait()
99  self.idle_end_time = rospy.get_time()
100  self.idle = False
101  #print "end wait"
102  allargs = self.allargs
103  self.allargs = None
104  if allargs != None:
105  try:
106  self.f(*allargs[0], **allargs[1])
107  except Exception as e:
108  rospy.logerr("AsynchronousUpdater failed with exception: %s"%str(e))
109  pass
110 
111  def kill(self):
112  #print "kill"
113  #print "Killing", self.name
114  with self.cv:
115  self.exiting = True
116  self.allargs = None
117  self.cv.notify()
118 
119  def getStatus(self): # For diagnostics
120  if self.idle:
121  return self.name, 0
122  interval = rospy.get_time() - self.idle_end_time
123  return self.name, interval
124 
126  def __init__(self, name):
127  self.period = 0
128  self.zero_offset = 0
129  self.clear_waveform()
130  self.name = name
131  self.async_ = AsynchronousUpdater(self.async_update, "Controller "+name)
132  self.service = None
133  self.transitions = []
134 
135  def clear_waveform(self):
136  self.transitions = []
137 
138  def add_sample(self, time, value, topic):
139  time = roundToEthercat(time) + 0.5 * ETHERCAT_INTERVAL
140  self.transitions.append(MultiWaveformTransition(time, value, topic))
141 
142  def async_update(self, period, zero_offset, transitions):
143  try:
144  if self.service == None:
145  service_name = self.name+"/set_waveform"
146  rospy.wait_for_service(service_name)
147  self.service = rospy.ServiceProxy(service_name, SetMultiWaveform)
148  #print "Service", service_name, "exists."
149 
150  #print "Trigger async_update got proxy on", self.name
151  waveform = MultiWaveform(period, zero_offset, transitions)
152  #print "Updating waveform ", self.name, waveform
153  rslt = self.service(waveform)
154  if not rslt.success:
155  rospy.logerr("Error setting waveform %s: %s"%(self.name, rslt.status_message))
156  #print "Done updating waveform ", self.name
157  except KeyboardInterrupt: # Handle CTRL+C
158  print("Aborted trigger update on", self.name)
159 
160  def update(self):
161  # Run the update using an Asynchronous Updater so that if something
162  # locks up, the rest of the node can keep working.
163  #print "Trigger update on", self.name
164  self.async_.update(self.period, self.zero_offset, self.transitions)
165 
167  def __init__(self, name, param, true_val, false_val):
168  MultiTriggerController.__init__(self, name)
169  self.param = param
170  self.true_val = true_val
171  self.false_val = false_val
172 
173  def process_update(self, config, level):
174  self.period = 1
175  self.zero_offset = 0
176  self.clear_waveform()
177  self.add_sample(0, { True: self.true_val, False: self.false_val}[config[self.param]], '-')
178  MultiTriggerController.update(self)
179 
181  def __init__(self, name, proj):
182  MultiTriggerController.__init__(self, name)
183  self.proj = proj
184 
185  def update(self):
186  self.period = self.proj.repeat_period
187  self.zero_offset = self.proj.zero_offset
188  self.clear_waveform()
189  self.high_val = 0xf
190  if (self.proj.needed == False and self.proj.mode == Config.CameraSynchronizer_ProjectorAuto) or \
191  self.proj.mode == Config.CameraSynchronizer_ProjectorOff:
192  self.high_val = 0xe
193  for i in range(0, len(self.proj.pulse_starts)):
194  self.add_sample(self.proj.pulse_starts[i], self.high_val, 'on_time')
195  self.add_sample(self.proj.pulse_ends[i], 0xe, 'off_time')
196  MultiTriggerController.update(self)
197 
199  def __init__(self, name, camera):
200  MultiTriggerController.__init__(self, name)
201  self.camera = camera
202 
203  def update(self):
204  if self.camera.reset_cameras:
205  self.camera_reset()
206  return
207  self.clear_waveform()
208  if not self.camera.ext_trig:
209  self.period = 1
210  self.add_sample(0, 0, "-")
211  else:
212  self.period = 2 * self.camera.period
213  self.zero_offset = -self.camera.imager_period
214  first_pulse_start = self.camera.end_offset
215  second_pulse_start = self.camera.period + first_pulse_start
216  first_frame_end = first_pulse_start + self.camera.imager_period
217  extra_pulse_start = (first_pulse_start + first_frame_end) / 2
218  trigger_name = "trigger"
219  self.add_sample(first_pulse_start, 1, trigger_name)
220  self.add_sample((first_pulse_start + extra_pulse_start) / 2, 0, "-")
221  self.add_sample(extra_pulse_start, 1, "-")
222  self.add_sample((extra_pulse_start + first_frame_end) / 2, 0, "-")
223  self.add_sample(second_pulse_start, 1, trigger_name)
224  self.add_sample((second_pulse_start + self.period) / 2, 0, "-")
225  self.camera.trig_rising = True
226  self.camera.trigger_name = self.name+"/"+trigger_name
227 
228  #print "About to update trigger", self.name
229  MultiTriggerController.update(self)
230 
231  def camera_reset(self):
232  self.clear_waveform()
233  self.period = 1.5
234  self.add_sample(0, 0, "-")
235  self.add_sample(0.1, 1, "-")
236  MultiTriggerController.update(self)
237 
239  def __init__(self, name, camera1, camera2):
240  SingleCameraTriggerController.__init__(self, name, None)
241  self.cameras = [camera1, camera2]
242 
243  def update(self):
244  if self.cameras[0].reset_cameras or self.cameras[1].reset_cameras:
245  self.camera_reset()
246  return
247 
248  if self.cameras[0].period != self.cameras[1].period or \
249  self.cameras[0].imager_period != self.cameras[1].imager_period:
250  rospy.logerr("Cameras on same trigger have different period/imager_period settings.")
251 
252  self.clear_waveform()
253  if self.cameras[0].end_offset == self.cameras[1].end_offset:
254  # This works because all cameras have the same imager period.
255  self.camera = self.cameras[0]
256  SingleCameraTriggerController.update(self)
257  self.cameras[1].trigger_name = self.cameras[0].trigger_name
258  self.cameras[1].trig_rising = self.cameras[0].trig_rising
259  return
260 
261  if self.cameras[0].end_offset > self.cameras[1].end_offset:
262  # Reduction to the case where cameras[0] happens first.
263  self.cameras.reverse()
264 
265  # Now we are sure that camera 1 happens strictly before camera 2.
266  assert self.cameras[0].proj == self.cameras[1].proj
267  assert self.cameras[0].period == self.cameras[1].period # Will fail if both stereo can be alternate or if they are updated in the wrong order.
268 
269  self.cameras[0].trig_rising = True
270  self.cameras[1].trig_rising = False
271 
272  self.clear_waveform()
273  self.period = 2 * self.cameras[1].period
274  self.zero_offset = -self.cameras[0].imager_period
275 
276  first_rise = self.cameras[0].end_offset
277  first_fall = self.cameras[1].end_offset
278  first_rise_end = first_rise + self.cameras[0].imager_period
279  second_rise = self.cameras[0].end_offset + self.cameras[0].period
280  second_fall = self.cameras[1].end_offset + self.cameras[1].period
281  extra_rise = (2 * first_fall + first_rise_end) / 3
282  extra_fall = (first_fall + 2 * first_rise_end) / 3
283 
284  trigger_name = [ "trigger_"+camera.name for camera in self.cameras ]
285  self.add_sample(first_rise, 1, trigger_name[0])
286  self.add_sample(first_fall, 0, trigger_name[1])
287  self.add_sample(extra_rise, 1, "-")
288  self.add_sample(extra_fall, 0, "-")
289  self.add_sample(second_rise, 1, trigger_name[0])
290  self.add_sample(second_fall, 0, trigger_name[1])
291  for i in range(0,2):
292  self.cameras[i].trigger_name = self.name+"/"+trigger_name[i]
293  #print self.cameras[i].trigger_name
294 
295  #print "About to update trigger", self.name
296  MultiTriggerController.update(self)
297 
298 class Projector:
299  def process_update(self, config, level):
300  # Produces:
301  # MEMBERS
302  # proj_end_offset, alt_end_offset, noproj_end_offset
303  # pulse_starts, pulse_ends
304  # repeat_period
305  #
306  # PARAMETERS
307  # projector_rate and projector_pulse_length
308  #print "Setting needed to false."
309  self.needed = False # If a camera needs us, the camera will say so.
310 
311  if level & lvl_projector == 0:
312  return; # The projector's configuration is unchanged.
313 
314  base_period = 1.0 / config["projector_rate"]
315  self.pulse_length = config["projector_pulse_length"]
316  pulse_shift = config["projector_pulse_shift"]
317  self.zero_offset = roundToEthercat(config["projector_tweak"])
318  self.mode = config["projector_mode"]
319 
320  base_period = roundToEthercat(base_period)
321  # Minimum is guard, pulse, guard, offset_pulse
322  base_period = max(base_period, 4 * ETHERCAT_INTERVAL)
323 
324  self.repeat_period = 4.0 * base_period
325 
326  # Pulse can't be so long that it would coalesce with next one.
327  self.pulse_length = min(self.pulse_length, math.floor(base_period / ETHERCAT_INTERVAL / 2.0 - 1) * ETHERCAT_INTERVAL)
328  self.pulse_length = math.ceil(self.pulse_length / ETHERCAT_INTERVAL) * ETHERCAT_INTERVAL
329 
330  min_second_start = base_period
331  max_second_start = 2 * base_period - 2 * ETHERCAT_INTERVAL - 2 * self.pulse_length
332  second_start = min_second_start * (1 - pulse_shift) + max_second_start * pulse_shift
333  second_start = roundToEthercat(second_start)
334  config["projector_pulse_shift"] = (second_start - min_second_start) / (max_second_start - min_second_start)
335 
336  self.pulse_starts = [
337  0,
338  second_start,
339  2 * base_period,
340  2 * base_period + second_start + self.pulse_length + 2 * ETHERCAT_INTERVAL
341  ]
342  self.pulse_ends = [ start + self.pulse_length for start in self.pulse_starts ]
343 
344  self.proj_end_offset = self.pulse_length + ETHERCAT_INTERVAL
345  self.alt_end_offset = second_start + self.pulse_length + ETHERCAT_INTERVAL
346  self.noproj_end_offset = second_start - ETHERCAT_INTERVAL
347 
348  self.proj_exposure_duration = self.pulse_length + ETHERCAT_INTERVAL * 1.5
349  self.noproj_max_exposure = second_start - self.pulse_length - ETHERCAT_INTERVAL * 1.5
350 
351  config["projector_rate"] = 1 / base_period
352  config["projector_pulse_length"] = self.pulse_length
353  config["projector_tweak"] = self.zero_offset
354 
355 class Camera:
356  def __init__(self, node_name, proj, level, **paramnames):
357  self.paramnames = paramnames
358  self.name = node_name
359  self.level = level
360  self.proj = proj
361  self.reconfigure_client = None
362  self.async_ = AsynchronousUpdater(self.async_apply_update, "Camera "+node_name)
363  self.trig_rising = True
364 
365  def param(self, config, name):
366  return config[self.paramnames[name]]
367 
368  def setparam(self, config, name, value):
369  config[self.paramnames[name]] = value
370 
371  # Uses the new configuration to compute new camera state.
372  def process_update(self, config, level):
373  # Produces:
374  # MEMBERS
375  # period, ext_trig, imager_period, end_offset
376  #
377  # PARAMETERS
378  # "param_rate"
379  self.trigger_name = "not_set" # Will be set by the CameraTriggerController
380 
381  # Took level checking out as it was causing problems with the projector
382  # needed flag.
383  #if self.level & level == 0:
384  # return # This camera's configuration is unchanged.
385 
386  self.period = 1.0 / self.param(config, param_rate)
387  projector_rate = config[param_proj_rate]
388  self.reset_cameras = config["camera_reset"]
389  self.ext_trig = True
390  self.register_set = WGEConfig.WGE100Camera_PrimaryRegisterSet
391 
392  projector_limits_exposure = True
393 
394  trig_mode = self.param(config, param_trig_mode)
395  # Internal triggering.
396  if trig_mode == Config.CameraSynchronizer_InternalTrigger:
397  self.ext_trig = False
398  self.imager_period = self.period
399  self.end_offset = -1
400  projector_limits_exposure = False
401  else:
402  if self.proj.mode == Config.CameraSynchronizer_ProjectorOff:
403  trig_mode = Config.CameraSynchronizer_IgnoreProjector
404 
405  if trig_mode == Config.CameraSynchronizer_AlternateProjector or trig_mode == Config.CameraSynchronizer_WithProjector:
406  self.proj.needed = True
407  #print self.name, "setting needed to true."
408  #else:
409  #print self.name, "not setting needed to true."
410 
411  # Set the period
412  if trig_mode == Config.CameraSynchronizer_AlternateProjector:
413  n = round(self.period / self.proj.repeat_period - 0.5)
414  n = max(n, 0)
415  self.period = (n + 0.5) * self.proj.repeat_period
416  #print "Case 1", n
417  elif trig_mode == Config.CameraSynchronizer_IgnoreProjector:
418  self.period = roundToEthercat(self.period)
419  else:
420  n = round(2 * self.period / self.proj.repeat_period - 1)
421  n = max(n, 0)
422  self.period = (n + 1) * self.proj.repeat_period / 2.0
423  #print "Case 2", n, self.period
424 
425  # Set the end_offset
426  if trig_mode == Config.CameraSynchronizer_IgnoreProjector:
427  self.end_offset = 0
428  projector_limits_exposure = False
429  if trig_mode == Config.CameraSynchronizer_AlternateProjector:
430  self.end_offset = self.proj.alt_end_offset
431  self.register_set = WGEConfig.WGE100Camera_Auto
432  elif trig_mode == Config.CameraSynchronizer_WithProjector:
433  self.end_offset = self.proj.proj_end_offset
434  self.register_set = WGEConfig.WGE100Camera_AlternateRegisterSet
435  else:
436  self.end_offset = self.proj.noproj_end_offset
437 
438  # Pick the imager period
439  if trig_mode == Config.CameraSynchronizer_IgnoreProjector:
440  self.imager_period = self.period - ETHERCAT_INTERVAL
441  else:
442  self.imager_period = self.proj.repeat_period / 2 - ETHERCAT_INTERVAL
443 
444  #print "Camera period", self.name, self.period, self.imager_period, self.proj.repeat_period
445  if projector_limits_exposure:
446  self.max_exposure = self.proj.noproj_max_exposure
447  #print "Exposurei projector", self.max_exposure
448  else:
449  self.max_exposure = self.imager_period * 0.95
450  #print "Exposurei imager", self.max_exposure
451 
452  self.setparam(config, param_rate, 1/self.period)
453  self.setparam(config, param_trig_mode, trig_mode)
454 
455  def async_apply_update(self, reconfig_request):
456  try:
457  #print "**** Start", self.name
458  if self.reconfigure_client == None:
459  #print "**** Making client", self.name
460  self.reconfigure_client = DynamicReconfigureClient(self.name)
461  #print "**** Made client", self.name
462 
463  self.reconfigure_client.update_configuration(reconfig_request)
464  #print "**** Reconfigured client", self.name
465  #print "Done updating camera ", self.name
466  except KeyboardInterrupt: # Handle CTRL+C
467  print("Aborted camera update on", self.name)
468 
469  def apply_update(self):
470  reconfig_request = {
471  "ext_trig" : self.ext_trig,
472  "trig_rate" : 1.0 / self.period,
473  "imager_rate" : 1.0 / self.imager_period,
474  "trig_timestamp_topic" : self.trigger_name,
475  "rising_edge_trig" : self.trig_rising,
476  "register_set" : self.register_set,
477  "auto_exposure_alternate" : False,
478  "exposure_alternate" : self.proj.proj_exposure_duration,
479  "max_exposure" : self.max_exposure,
480  }
481  #print self.name, reconfig_request
482 
483  self.async_.update(reconfig_request)
484 
485 # Need to set:
486 # Global period if synchronized
487 # Camera
488 # Frame rate
489 # Trigger mode
490 # Exposure alt
491 # Maximum exposure alt
492 # Alternate enabled
493 # Alternate only
494 # Trigger name
495 # Trigger
496 # Full waveform
497 # Projector
498 # Full waveform
499 # Prosilica setting
500 
502  def __init__(self, forearm=True):
503  stereo_camera_names = [ "narrow_stereo", "wide_stereo" ] # narrow must be first as it can be alternate, and hence has more period restrictions.
504  forearm_camera_names = [ "forearm_r", "forearm_l" ]
505  self.camera_names = stereo_camera_names
506  if forearm:
507  self.camera_names = self.camera_names + forearm_camera_names
508  # Parameter names are pretty symmetric. Build them up automatically.
509  parameters = [ param_rate, param_trig_mode ]
510  camera_parameters = dict((name, dict((suffix, name+"_"+suffix) for suffix in parameters)) for name in self.camera_names)
511  # Some parameters are common to the wide and narrow cameras. Also store
512  # the node names.
513  for camera in stereo_camera_names:
514  camera_parameters[camera][param_rate] = "stereo_rate"
515  camera_parameters[camera]["node_name"] = camera+"_both"
516  if forearm:
517  for camera in forearm_camera_names:
518  camera_parameters[camera]["node_name"] = camera[-1]+"_forearm_cam"
519  for i in range(0, len(self.camera_names)):
520  camera_parameters[self.camera_names[i]]["level"] = 1 << i; # This only works because of the specific levels in the .cfg file.
521 
523  self.cameras = dict((name, Camera(proj = self.projector, **camera_parameters[name])) for name in self.camera_names)
524  self.prosilica_inhibit = ProsilicaInhibitTriggerController('prosilica_inhibit_projector_controller', "prosilica_projector_inhibit", 0x0A, 0x00)
525 
526  self.controllers = [
527  ProjectorTriggerController('projector_trigger', self.projector),
528  DualCameraTriggerController('head_camera_trigger', *[self.cameras[name] for name in stereo_camera_names])]
529  if forearm:
530  self.controllers = self.controllers + [
531  SingleCameraTriggerController('r_forearm_cam_trigger', self.cameras["forearm_r"]),
532  SingleCameraTriggerController('l_forearm_cam_trigger', self.cameras["forearm_l"]),
533  ]
534 
535  self.server = DynamicReconfigureServer(ConfigType, self.reconfigure)
536 
537  @staticmethod
539  threads = threading.enumerate()
540  n = 0
541  for t in threads:
542  if not t.isDaemon() and t != threading.currentThread():
543  try:
544  print(t.name)
545  except:
546  print("Unknown thread ", t)
547  n = n + 1
548  return n
549 
550  def kill(self):
551  print("\nWaiting for all threads to die...")
553  #time.sleep(1)
554  while self.print_threads() > 0:
555  print("\nStill waiting for all threads to die...")
556  time.sleep(1)
557  print()
558 
559  def reconfigure(self, config, level):
560  # print "Reconfigure", config
561  # Reconfigure the projector.
562  self.projector.process_update(config, level)
563  self.prosilica_inhibit.process_update(config, level)
564  # Reconfigure the cameras.
565  for camera in self.cameras.values():
566  camera.process_update(config, level)
567  #for trig_controller in self.trig_controllers:
568  # trig_controller.update()
569  #for camera in self.cameras.keys():
570  # camera.update()
571  for controller in self.controllers:
572  controller.update();
573  for camera in self.cameras.values():
574  camera.apply_update()
575  #print config
576  self.config = config
577  return config
578 
580  da = DiagnosticArray()
581  ds = DiagnosticStatus()
582  ds.name = rospy.get_caller_id().lstrip('/') + ": Tasks"
583  in_progress = 0;
584  longest_interval = 0;
585  for updater in list(asynchronous_updaters):
586  (name, interval) = updater.getStatus()
587  if interval == 0:
588  msg = "Idle"
589  else:
590  in_progress = in_progress + 1
591  msg = "Update in progress (%i s)"%interval
592  longest_interval = max(interval, longest_interval)
593  ds.values.append(KeyValue(name, msg))
594  if in_progress == 0:
595  ds.message = "Idle"
596  else:
597  ds.message = "Updates in progress: %i"%in_progress
598  if longest_interval > 10:
599  ds.level = 1
600  ds.message = "Update is taking too long: %i"%in_progress
601  ds.hardware_id = "none"
602  da.status.append(ds)
603  da.header.stamp = rospy.get_rostime()
604  self.diagnostic_pub.publish(da)
605 
606  def spin(self):
607  self.diagnostic_pub = rospy.Publisher("/diagnostics", DiagnosticArray)
608  try:
609  reset_count = 0
610  rospy.loginfo("Camera synchronizer is running...")
611  controller_update_count = 0
612  while not rospy.is_shutdown():
613  if self.config['camera_reset'] == True:
614  reset_count = reset_count + 1
615  if reset_count > 3:
616  self.server.update_configuration({'camera_reset' : False})
617  else:
618  reset_count = 0
619  self.update_diagnostics()
620  # In case the controllers got restarted, refresh their state.
621  controller_update_count += 1
622  if controller_update_count >= 10:
623  controller_update_count = 0
624  for controller in self.controllers:
625  controller.update();
626  rospy.sleep(1)
627  finally:
628  rospy.signal_shutdown("Main thread exiting")
629  self.kill()
630  print("Main thread exiting")
def __init__(self, node_name, proj, level, paramnames)


pr2_camera_synchronizer
Author(s): Blaise Gassend
autogenerated on Tue Jun 1 2021 02:50:50