def broadcastButtonProgram(text, voice, ttsEngine):

        # Have we broadcast a button program before?
        if RoboComm.ROS_BUTTON_PROGRAM_BROADCASTER is None:
            # No, let's create a ros publisher:
            if SpeakeasyUtils.rosMasterRunning():
		        # Declare us to be a ROS node.
		        # Allow multiple GUIs to run simultaneously. Therefore
		        # the anonymous=True:
                RoboComm.ROS_BUTTON_PROGRAM_BROADCASTER = rospy.Publisher('SpeakEasyButtonProgram', SpeakEasyButtonProgram);
                nodeInfo = rospy.init_node('speakeasy_remote_gui', anonymous=True);
            else:
                raise NotImplementedError("ROS master must be running to broadcast button programs.")
            
        msg = SpeakEasyButtonProgram()
        msg.text = text
        msg.voiceName = voice
        msg.engineName = ttsEngine
        RoboComm.ROS_BUTTON_PROGRAM_BROADCASTER.publish(msg)
    def __init__(self):
        '''
        Initialize sound to play at the robot. Initializes
        self.sound_file_names to a list of sound file names
        for use with play-sound calls to the Robot via ROS.
        @raise NotImplementedError: if ROS initialization failed.
        @raise IOError: if SpeakEasy node is online, but service call to it failed. 
        '''
        
        if not ROS_IMPORT_OK:
            raise NotImplementedError("ROS is not installed on this machine.");
        
        self.rosSpeakEasyNodeAvailable = False;
        self.latestCapabilitiesReply = None;
        
        self.nodeStatusLock   = threading.Lock();
        self.textToSpeechLock = threading.Lock();
        self.musicLock        = threading.Lock();
        self.soundLock        = threading.Lock();
        
        # Place to remember threads that repeat voice/sound/music.
        # These lists are used when stopping those threads:
        self.speechThreads = [];
        self.soundThreads  = [];
        self.musicThreads  = [];
        

        # init_node hangs indefinitely if roscore is not running.
        # Therefore: check for that. 
        
        if not SpeakeasyUtils.rosMasterRunning():
            raise NotImplementedError("The roscore process is not running.");
        
        # We now know that ROS is installed, and that roscore is running.
        
        # Publishers of requests for sound effects, music, and text-to-speech:
        self.rosSoundRequestor = rospy.Publisher('speakeasy_sound_req', SpeakEasySound);
        self.rosMusicRequestor = rospy.Publisher('speakeasy_music_req', SpeakEasyMusic);
        self.rosTTSRequestor = rospy.Publisher('speakeasy_text_to_speech_req', SpeakEasyTextToSpeech);
        
        # Declare us to be a ROS node.
        # Allow multiple GUIs to run simultaneously. Therefore
        # the anonymous=True:
        nodeInfo = rospy.init_node('speakeasy_remote_gui', anonymous=True);
        # Don't know why, but without a bit of delay after this init, the first
        # published message will not be transmitted, no matter what the message type:
        rospy.sleep(1.0);
    
        # Wait for Ros SpeakEasy service for a limited time; there might be none:
        waitTime = 4; # seconds
        secsWaited = 0;
        while not self.robotAvailable() and (secsWaited < waitTime):
            rospy.loginfo("No SpeakEasy node available. Keep checking for %d more second(s)..." % (waitTime - secsWaited));
            rospy.sleep(1);
            secsWaited += 1;
        if secsWaited >= waitTime:
            rospy.logerr("Speech/sound/music capabilities service is offline.");
            self.rosSpeakEasyNodeAvailable = False;    
            raise NotImplementedError("Speech capabilities service is offline.");
                
        rospy.loginfo("Speech capabilities service online.");