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
00040 import roslib
00041 import rospy
00042 import threading
00043 import os
00044 import logging
00045 import sys
00046 import traceback
00047 import tempfile
00048 from diagnostic_msgs.msg import DiagnosticStatus, KeyValue, DiagnosticArray
00049 from sound_play.msg import SoundRequest, SoundRequestAction, SoundRequestResult, SoundRequestFeedback
00050 import actionlib
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067 def sleep(t):
00068 try:
00069 rospy.sleep(t)
00070 except:
00071 pass
00072
00073
00074 class soundtype:
00075 STOPPED = 0
00076 LOOPING = 1
00077 COUNTING = 2
00078
00079 def __init__(self, file, device, volume = 1.0):
00080 self.lock = threading.RLock()
00081 self.state = self.STOPPED
00082
00083 if device:
00084 pass
00085
00086
00087
00088 if (":" in file):
00089 uri = file
00090 elif os.path.isfile(file):
00091 uri = "file://" + os.path.abspath(file)
00092 else:
00093 rospy.logerr('Error: URI is invalid: %s'%file)
00094
00095 self.uri = uri
00096 self.volume = volume
00097
00098
00099 self.staleness = 1
00100 self.file = file
00101
00102
00103
00104
00105
00106
00107
00108
00109
00110 def __del__(self):
00111
00112 self.stop()
00113
00114 def update(self):
00115
00116 self.state = self.STOPPED
00117
00118 def loop(self):
00119 self.lock.acquire()
00120 try:
00121 self.staleness = 0
00122
00123 if self.state == self.COUNTING:
00124 self.stop()
00125
00126 if self.state == self.STOPPED:
00127 pass
00128
00129
00130 self.state = self.LOOPING
00131 finally:
00132 self.lock.release()
00133
00134 def stop(self):
00135 if self.state != self.STOPPED:
00136 self.lock.acquire()
00137 try:
00138
00139 self.state = self.STOPPED
00140 finally:
00141 self.lock.release()
00142
00143 def single(self):
00144 self.lock.acquire()
00145 try:
00146 rospy.logdebug("Playing %s"%self.uri)
00147 self.staleness = 0
00148 if self.state == self.LOOPING:
00149 self.stop()
00150
00151
00152
00153 self.state = self.COUNTING
00154 finally:
00155 self.lock.release()
00156
00157 def command(self, cmd):
00158 if cmd == SoundRequest.PLAY_STOP:
00159 self.stop()
00160 elif cmd == SoundRequest.PLAY_ONCE:
00161 self.single()
00162 elif cmd == SoundRequest.PLAY_START:
00163 self.loop()
00164
00165 def get_staleness(self):
00166 self.lock.acquire()
00167 position = 0
00168 duration = 0
00169 try:
00170 pass
00171
00172
00173 except Exception, e:
00174 position = 0
00175 duration = 0
00176 finally:
00177 self.lock.release()
00178
00179 if position != duration:
00180 self.staleness = 0
00181 else:
00182 self.staleness = self.staleness + 1
00183 return self.staleness
00184
00185 def get_playing(self):
00186 return self.state == self.COUNTING
00187
00188 class soundplay:
00189 _feedback = SoundRequestFeedback()
00190 _result = SoundRequestResult()
00191
00192 def stopdict(self,dict):
00193 for sound in dict.values():
00194 sound.stop()
00195
00196 def stopall(self):
00197 self.stopdict(self.builtinsounds)
00198 self.stopdict(self.filesounds)
00199 self.stopdict(self.voicesounds)
00200
00201 def select_sound(self, data):
00202 if data.sound == SoundRequest.PLAY_FILE:
00203 if not data.arg2:
00204 if not data.arg in self.filesounds.keys():
00205 rospy.logdebug('command for uncached wave: "%s"'%data.arg)
00206 try:
00207 self.filesounds[data.arg] = soundtype(data.arg, self.device)
00208 except:
00209 rospy.logerr('Error setting up to play "%s". Does this file exist on the machine on which sound_play is running?'%data.arg)
00210 return
00211 else:
00212 rospy.logdebug('command for cached wave: "%s"'%data.arg)
00213 sound = self.filesounds[data.arg]
00214 else:
00215 absfilename = os.path.join(roslib.packages.get_pkg_dir(data.arg2), data.arg)
00216 if not absfilename in self.filesounds.keys():
00217 rospy.logdebug('command for uncached wave: "%s"'%absfilename)
00218 try:
00219 self.filesounds[absfilename] = soundtype(absfilename, self.device)
00220 except:
00221 rospy.logerr('Error setting up to play "%s" from package "%s". Does this file exist on the machine on which sound_play is running?'%(data.arg, data.arg2))
00222 return
00223 else:
00224 rospy.logdebug('command for cached wave: "%s"'%absfilename)
00225 sound = self.filesounds[absfilename]
00226 elif data.sound == SoundRequest.SAY:
00227 if not data.arg in self.voicesounds.keys():
00228 rospy.logdebug('command for uncached text: "%s"' % data.arg)
00229 txtfile = tempfile.NamedTemporaryFile(prefix='sound_play', suffix='.txt')
00230 (wavfile,wavfilename) = tempfile.mkstemp(prefix='sound_play', suffix='.wav')
00231 txtfilename=txtfile.name
00232 os.close(wavfile)
00233 voice = data.arg2
00234 try:
00235 txtfile.write(data.arg)
00236 txtfile.flush()
00237 rospy.loginfo("text2wave -eval '("+voice+")' "+txtfilename+" -o "+wavfilename)
00238 '''
00239 os.system("text2wave -eval '("+voice+")' "+txtfilename+" -o "+wavfilename)
00240 try:
00241 if os.stat(wavfilename).st_size == 0:
00242 raise OSError # So we hit the same catch block
00243 except OSError:
00244 rospy.logerr("text2wave -eval '("+voice+")' "+txtfilename+" -o "+wavfilename)
00245 rospy.logerr('Sound synthesis failed. Is festival installed? Is a festival voice installed? Try running "rosdep satisfy sound_play|sh". Refer to http://wiki.ros.org/sound_play/Troubleshooting')
00246 return
00247 '''
00248 self.voicesounds[data.arg] = soundtype(wavfilename, self.device)
00249 finally:
00250 txtfile.close()
00251 else:
00252 rospy.logdebug('command for cached text: "%s"'%data.arg)
00253 sound = self.voicesounds[data.arg]
00254 else:
00255 rospy.logdebug('command for builtin wave: %i'%data.sound)
00256 if not data.sound in self.builtinsounds:
00257 params = self.builtinsoundparams[data.sound]
00258 self.builtinsounds[data.sound] = soundtype(params[0], self.device, params[1])
00259 sound = self.builtinsounds[data.sound]
00260 if sound.staleness != 0 and data.command != SoundRequest.PLAY_STOP:
00261
00262 rospy.logdebug("activating %i %s"%(data.sound,data.arg))
00263 self.active_sounds = self.active_sounds + 1
00264 sound.staleness = 0
00265
00266
00267
00268 return sound
00269
00270 def callback(self,data):
00271 if not self.initialized:
00272 return
00273 self.mutex.acquire()
00274
00275 self.stopall()
00276 try:
00277 if data.sound == SoundRequest.ALL and data.command == SoundRequest.PLAY_STOP:
00278 self.stopall()
00279 else:
00280 sound = self.select_sound(data)
00281 sound.command(data.command)
00282 except Exception, e:
00283 rospy.logerr('Exception in callback: %s'%str(e))
00284 rospy.loginfo(traceback.format_exc())
00285 finally:
00286 self.mutex.release()
00287 rospy.logdebug("done callback")
00288
00289
00290 def cleanupdict(self, dict):
00291 purgelist = []
00292 for (key,sound) in dict.iteritems():
00293 try:
00294 staleness = sound.get_staleness()
00295 except Exception, e:
00296 rospy.logerr('Exception in cleanupdict for sound (%s): %s'%(str(key),str(e)))
00297 staleness = 100
00298
00299 if staleness >= 10:
00300 purgelist.append(key)
00301 if staleness == 0:
00302 self.active_sounds = self.active_sounds + 1
00303 for key in purgelist:
00304 rospy.logdebug('Purging %s from cache'%key)
00305 dict[key].stop()
00306 del dict[key]
00307
00308 def cleanup(self):
00309 self.mutex.acquire()
00310 try:
00311 self.active_sounds = 0
00312 self.cleanupdict(self.filesounds)
00313 self.cleanupdict(self.voicesounds)
00314 self.cleanupdict(self.builtinsounds)
00315 except:
00316 rospy.loginfo('Exception in cleanup: %s'%sys.exc_info()[0])
00317 finally:
00318 self.mutex.release()
00319
00320 def diagnostics(self, state):
00321 try:
00322 da = DiagnosticArray()
00323 ds = DiagnosticStatus()
00324 ds.name = rospy.get_caller_id().lstrip('/') + ": Node State"
00325 if state == 0:
00326 ds.level = DiagnosticStatus.OK
00327 ds.message = "%i sounds playing"%self.active_sounds
00328 ds.values.append(KeyValue("Active sounds", str(self.active_sounds)))
00329 ds.values.append(KeyValue("Allocated sound channels", str(self.num_channels)))
00330 ds.values.append(KeyValue("Buffered builtin sounds", str(len(self.builtinsounds))))
00331 ds.values.append(KeyValue("Buffered wave sounds", str(len(self.filesounds))))
00332 ds.values.append(KeyValue("Buffered voice sounds", str(len(self.voicesounds))))
00333 elif state == 1:
00334 ds.level = DiagnosticStatus.WARN
00335 ds.message = "Sound device not open yet."
00336 else:
00337 ds.level = DiagnosticStatus.ERROR
00338 ds.message = "Can't open sound device. See http://wiki.ros.org/sound_play/Troubleshooting"
00339 da.status.append(ds)
00340 da.header.stamp = rospy.get_rostime()
00341 self.diagnostic_pub.publish(da)
00342 except Exception, e:
00343 rospy.loginfo('Exception in diagnostics: %s'%str(e))
00344
00345 def execute_cb(self, data):
00346 data = data.sound_request
00347 if not self.initialized:
00348 return
00349 self.mutex.acquire()
00350
00351 self.stopall()
00352 try:
00353 if data.sound == SoundRequest.ALL and data.command == SoundRequest.PLAY_STOP:
00354 self.stopall()
00355 else:
00356 sound = self.select_sound(data)
00357 sound.command(data.command)
00358
00359 r = rospy.Rate(1)
00360 start_time = rospy.get_rostime()
00361 success = True
00362 while sound.get_playing():
00363 sound.update()
00364 if self._as.is_preempt_requested():
00365 rospy.loginfo('sound_play action: Preempted')
00366 sound.stop()
00367 self._as.set_preempted()
00368 success = False
00369 break
00370
00371 self._feedback.playing = sound.get_playing()
00372 self._feedback.stamp = rospy.get_rostime() - start_time
00373 self._as.publish_feedback(self._feedback)
00374 r.sleep()
00375
00376 if success:
00377 self._result.playing = self._feedback.playing
00378 self._result.stamp = self._feedback.stamp
00379 rospy.loginfo('sound_play action: Succeeded')
00380 self._as.set_succeeded(self._result)
00381
00382 except Exception, e:
00383 rospy.logerr('Exception in actionlib callback: %s'%str(e))
00384 rospy.loginfo(traceback.format_exc())
00385 finally:
00386 self.mutex.release()
00387 rospy.logdebug("done actionlib callback")
00388
00389 def __init__(self):
00390 rospy.init_node('sound_play')
00391 self.device = rospy.get_param("~device", str())
00392 self.diagnostic_pub = rospy.Publisher("/diagnostics", DiagnosticArray, queue_size=1)
00393 rootdir = os.path.join(roslib.packages.get_pkg_dir('sound_play'),'sounds')
00394
00395 self.builtinsoundparams = {
00396 SoundRequest.BACKINGUP : (os.path.join(rootdir, 'BACKINGUP.ogg'), 0.1),
00397 SoundRequest.NEEDS_UNPLUGGING : (os.path.join(rootdir, 'NEEDS_UNPLUGGING.ogg'), 1),
00398 SoundRequest.NEEDS_PLUGGING : (os.path.join(rootdir, 'NEEDS_PLUGGING.ogg'), 1),
00399 SoundRequest.NEEDS_UNPLUGGING_BADLY : (os.path.join(rootdir, 'NEEDS_UNPLUGGING_BADLY.ogg'), 1),
00400 SoundRequest.NEEDS_PLUGGING_BADLY : (os.path.join(rootdir, 'NEEDS_PLUGGING_BADLY.ogg'), 1),
00401 }
00402
00403 self.no_error = True
00404 self.initialized = False
00405 self.active_sounds = 0
00406
00407 self.mutex = threading.Lock()
00408 sub = rospy.Subscriber("robotsound", SoundRequest, self.callback)
00409 self._as = actionlib.SimpleActionServer('sound_play', SoundRequestAction, execute_cb=self.execute_cb, auto_start = False)
00410 self._as.start()
00411
00412 self.mutex.acquire()
00413 self.sleep(0.5)
00414 self.diagnostics(1)
00415
00416 while not rospy.is_shutdown():
00417 while not rospy.is_shutdown():
00418 self.init_vars()
00419 self.no_error = True
00420 self.initialized = True
00421 self.mutex.release()
00422 try:
00423 self.idle_loop()
00424
00425
00426 except:
00427 rospy.loginfo('Exception in idle_loop: %s'%sys.exc_info()[0])
00428 finally:
00429 self.mutex.acquire()
00430
00431 self.diagnostics(2)
00432 self.mutex.release()
00433
00434 def init_vars(self):
00435 self.num_channels = 10
00436 self.builtinsounds = {}
00437 self.filesounds = {}
00438 self.voicesounds = {}
00439 self.hotlist = []
00440 if not self.initialized:
00441 rospy.loginfo('sound_play node is ready to play sound')
00442
00443 def sleep(self, duration):
00444 try:
00445 rospy.sleep(duration)
00446 except rospy.exceptions.ROSInterruptException:
00447 pass
00448
00449 def idle_loop(self):
00450 self.last_activity_time = rospy.get_time()
00451 while (rospy.get_time() - self.last_activity_time < 10 or
00452 len(self.builtinsounds) + len(self.voicesounds) + len(self.filesounds) > 0) \
00453 and not rospy.is_shutdown():
00454
00455 self.diagnostics(0)
00456 self.sleep(1)
00457 self.cleanup()
00458
00459
00460 if __name__ == '__main__':
00461 soundplay()