Posts Tagged ‘tutorial’

Building a full-featured conference call application with Tropo and PHP

Friday, June 24th, 2011

Tropo’s conference function makes it easy for you to join multiple callers onto a single call together. One line of code and you have a conference call…

<?php conference('someid'); ?>

That’s it. A basic conference line. Use that as your application and everyone calling into your number will be joined into a single conference call. How many users? By default, Tropo allows up to 100 users into a single conference. We can do more if you need it, but you might want to think through your use case a bit first. 100 people all talking on one line gets crazy fast.

Now let’s take this basic concept and turn it into a full-fledged application. Our requirements for the conference call start with allowing the entry of a conference ID so that this number can be used for more than one call.

<?php
$response = ask('Enter your conference ID', array(
    'choices' => '[4 DIGITS]'
));
conference($response->value);
?>

We’d also like to allow conference IDs of multiple lengths (instead of just 4 digits), and to let the caller tell us when they’re done with entry by pressing #. How about conference IDs of 3-10 digits?

<?php
$response = ask('Enter your conference ID. Press the pound key when finished.', array(
    'choices' => '[3-10 DIGITS]',
    'terminator' => '#'
));
conference($response->value);
?>

In noisy rooms offices, background noise sometimes get misinterpreted as input. Since we’re only accepting numeric conference IDs, let’s turn off speech recognition and restrict input to the keypad

<?php
$response = ask('Enter your conference ID. Press the pound key when finished.', array(
    'choices' => '[3-10 DIGITS]',
    'terminator' => '#',
    'mode' => 'keypad'
));
conference($response->value);
?>

Let’s tell them if their conference ID was accepted and notify them they’re entering the room.

<?php
$response = ask('Enter your conference ID. Press the pound key when finished.', array(
    'choices' => '[3-10 DIGITS]',
    'terminator' => '#',
    'mode' => 'keypad'
));
say('Conference ID ' . $response->value . ' accepted. You will now be placed into the conference. Please announce yourself.');
conference($response->value);
?>

Hmm. That conference id is being read as a number (one thousand two hundred thirty four) instead of as digits (one two three four). A little ssml magic will fix that.

<?php
$response = ask('Enter your conference ID. Press the pound key when finished.', array(
    'choices' => '[3-10 DIGITS]',
    'terminator' => '#',
    'mode' => 'keypad'
));
say('<speak>Conference ID <say-as interpret-as="vxml:digits">' . $response->value . '</say-as> accepted. You will now be placed into the conference. Please announce yourself.</speak>');
conference($response->value);
?>

Often I hit the wrong conference id and need to hang up and try again. For our conference line, how about we let someone exit the room and pick another one.

We’ll add a loop that loops as long as the call is active. Once someone exits, the loop starts over and they’ll be prompted for their conference id again. We’ll also add a terminator to the conference so someone can exit and let them know that option is available.

<?php
answer();
while ($currentCall->isActive()) {
	$response = ask('Enter your conference ID. Press the pound key when finished.', array(
	    'choices' => '[3-10 DIGITS]',
	    'terminator' => '#',
	    'mode' => 'keypad'
	));
	say('<speak>Conference ID <say-as interpret-as="vxml:digits">' . $response->value . '</say-as> accepted. You will now be placed into the conference. Press pound to exit without disconnecting. Please announce yourself.</speak>');
	conference($response->value, array('terminator' => '#'));
}
?>

What if someone doesn’t enter a conference id? Or enters too few or too many numbers? Let’s check the return of the ask function to see if the ask succeeded, failed, or timed out. If it’s a timeout or failure, play a message and the loop starts again, asking for the conference id again.

<?php
answer();
while ($currentCall->isActive()) {
	$response = ask('Enter your conference ID. Press the pound key when finished.', array(
	    'choices' => '[3-10 DIGITS]',
	    'terminator' => '#',
	    'mode' => 'keypad'
	));
	switch($response->name) {
    case 'choice':
      say('<speak>Conference ID <say-as interpret-as="vxml:digits">' . $response->value . '</say-as> accepted. You will now be placed into the conference. Press pound to exit without disconnecting. Please announce yourself.</speak>');
      conference($response->value, array('terminator' => '#'));
      // Pause a moment before asking for another conference.
      sleep(1);
      break;
    case 'badChoice':
      say('Sorry, that is not a valid conference ID.', array('voice' => $voice));
      break;
    case 'silenceTimeout':
    case 'timeout':
      say('Sorry, I didn\'t hear anything.', array('voice' => $voice));
      break;
  }
}
?>

Now that we are handling a timeout, the default timeout seems way too long for this type of application. If the user doesn’t enter a conference ID in 8 seconds, prompt again.

<?php
answer();
while ($currentCall->isActive()) {
	$response = ask('Enter your conference ID. Press the pound key when finished.', array(
	    'choices' => '[3-10 DIGITS]',
	    'terminator' => '#',
	    'mode' => 'keypad',
	    'timeout' => 8
	));
	switch($response->name) {
    case 'choice':
      say('<speak>Conference ID <say-as interpret-as="vxml:digits">' . $response->value . '</say-as> accepted. You will now be placed into the conference. Press pound to exit without disconnecting. Please announce yourself.</speak>');
      conference($response->value, array('terminator' => '#'));
      // Pause a moment before asking for another conference.
      sleep(1);
      break;
    case 'badChoice':
      say('Sorry, that is not a valid conference ID.', array('voice' => $voice));
      break;
    case 'silenceTimeout':
    case 'timeout':
      say('Sorry, I didn\'t hear anything.', array('voice' => $voice));
      break;
  }
}
?>

What if we want to restrict the conference IDs that someone can use? Maybe create a conference application that uses reservations and creates rooms for people. We could fetch a list of currently-valid conference IDs from a database. In this sample, I’m just going to hard-code those in an array.

<?php
$pins = array();
$pins['1337'] = '';
$pins['1234'] = '';
$pins['2600'] = '';
answer();
while ($currentCall->isActive()) {
	$response = ask('Enter your conference ID. Press the pound key when finished.', array(
	    'choices' => '[3-10 DIGITS]',
	    'terminator' => '#',
	    'mode' => 'keypad',
	    'timeout' => 8
	));
	switch($response->name) {
    case 'choice':
	    if (!array_key_exists($response->value, $pins)) {
	      say('Sorry, that is not a valid conference ID.');
	      break;
	    }
      say('<speak>Conference ID <say-as interpret-as="vxml:digits">' . $response->value . '</say-as> accepted. You will now be placed into the conference. Press pound to exit without disconnecting. Please announce yourself.</speak>');
      conference($response->value, array('terminator' => '#'));
      // Pause a moment before asking for another conference.
      sleep(1);
      break;
    case 'badChoice':
      say('Sorry, that is not a valid conference ID.', array('voice' => $voice));
      break;
    case 'silenceTimeout':
    case 'timeout':
      say('Sorry, I didn\'t hear anything.', array('voice' => $voice));
      break;
  }
}
?>

Now for a fun feature. Let’s let conference ID owners — the room administrator — get a text message any time someone joins their conference room. Never miss a conference call again. And for kicks, send a text message when they leave, too.

<?php
// An array of conference IDs and phone numbers to alert.
// If a conference ID is used that has a phone number attached,
// when someone joins or leaves that conference, the attached phone
// number will get an SMS alerting them.
$pins = array();
$pins['1337'] = '14075551212';
$pins['1234'] = '19255556789';
$pins['2600'] = '';
answer();

while ($currentCall->isActive()) {
  $response = ask('Enter your conference ID. Press the pound key when finished.', array(
      'choices' => '[3-10 DIGITS]',
      'terminator' => '#',
      'mode' => 'keypad',
      'timeout' => 8
  ));
  switch($response->name) {
    case 'choice':
      if (!array_key_exists($response->value, $pins)) {
        say('Sorry, that is not a valid conference ID.');
        break;
      }
      if (array_key_exists($response->value, $pins) && !empty($pins[$response->value])) {
        // Send an alert that someone has entered the conference
        message($currentCall->callerID . ' has entered conference ' . $response->value, array('to' => $pins[$response->value], 'network' => 'SMS'));
      }
      say('<speak>Conference ID <say-as interpret-as="vxml:digits">' . $response->value . '</say-as> accepted. You will now be placed into the conference. Press pound to exit without disconnecting. Please announce yourself.</speak>');
      conference($response->value, array('terminator' => '#'));
      if (array_key_exists($response->value, $pins) && !empty($pins[$response->value])) {
        // Send an alert that someone has left the conference
        message($currentCall->callerID . ' has left conference ' . $response->value, array('to' => $pins[$response->value], 'network' => 'SMS'));
      }
      // Pause a moment before asking for another conference.
      sleep(1);
      break;
    case 'badChoice':
      say('Sorry, that is not a valid conference ID.', array('voice' => $voice));
      break;
    case 'silenceTimeout':
    case 'timeout':
      say('Sorry, I didn\'t hear anything.', array('voice' => $voice));
      break;
  }
}
?>

And there’s a full-featured conference calling application in about 50 lines of code. We started with a basic one-liner and slowly added features to build a conference calling service that rivals the professional conference applications. The full code is available from the Tropo Samples repository on Github. The download on Github has some additional configuration, like allowing you to set a voice for all the prompts and turn on and off the feature that restricts this to known conference IDs.

How robust is the code? This exact application is what we use every day as our conference calling line. Calls with customers, sales prospects, and each other all happen over a conference line backed by the code in this tutorial.

Building a call tree with Tropo

Friday, December 3rd, 2010

If you have kids participating in activities, chances are you’ve gotten calls from automated phone trees. If the little league game is rained out, the team manager can make one phone call and have it blasted out to the whole team. School fundraiser due tomorrow? Don’t call 500 kids, call one number and let the system relay your message to everyone.

Chris Matthieu wrote up a good example earlier today of using 5 lines of Ruby code to create a broadcast SMS system. Send a text message into your Tropo number and a predefined list of people receive the message.

Since we can’t let the Ruby guys have all the fun, here’s an example in PHP of a phone tree. A caller records a message, has the opportunity to listen to it and re-record it as often as they want, and when they’re satisfied, blast it to all the other members of the group. This example is more than 5 lines of code, but it adds some features like security, user confirmation, and some sending intelligence to prevent the message from being sent to the person who recorded it in the first place.

Here’s a full walkthrough of the code. If you want to follow along with your own copy, grab the code from our Tropo samples on Github.

<?php
// An array of phone numbers that are part of the phone tree.
$members = array('2125551212', '4155551313');

if (!in_array($currentCall->callerID, $members)) {
  // The caller isn't a member of the phone tree.
  say('Sorry, only members of the group can send to this group.');
  hangup();
}

The script starts out with an array of numbers that are members of this phone tree. If the caller’s caller ID doesn’t appear in the list, they hear a message telling them they’re not a member of the group and then Tropo hangs up. The members array is hard-coded here, but if you were doing this for real, you’d probably put these in a database and create a tiny web service to fetch the list of numbers from. In fact, you could even pass the caller’s caller ID to your web service and use that to figure out which group they’re in.

Next is the meat of the record/listen/send loop. It’s a pair of nested while loops and some flags that are used to tell if the loop should keep going. Here’s the code, and we’ll step through it in a moment.

$done = FALSE;
while ($done == FALSE) {
  $recording = record('Reecord your message at the tone. Press pound when finished.', array('terminator' => '#'));
  // reset selection to 1 since this is a new recording.
  $selection = 1;
  while ($selection == 1) {
    $choice = ask('To listen to your message, press 1. To re reecord your message, press 2. To send your message, press 3.', array('choices' => '1,2,3', 'mode' => 'dtmf'));
    $selection = $choice->value;
    if ($selection == 1) {
      // Play the message, then reprompt.
      say($recording->value);
    } elseif ($selection == 3) {
      // Break out of the recording loop.
      $done = TRUE;
      continue 2;
    }
    // Pause a moment before replaying the prompt.
    sleep(1);
  }
}

Here’s the walk through to help you understand how those loops work. The basic structure of the loop is this…

while ('still-recording') {
	// Record the message
	while ('verifying-recording') {
		// Play the menu: listen, re-record, or send
		if ('listen') {
			// play the message, then return to "while ('verifying-recording')"
			// so the menu plays again.
		} elseif ('send') {
			// exit both while loops. go to line 15
		}
		// Neither of those things happened, so end the 'verifying-recording' 
		// loop and return to the 'still-recording' loop.
	}
}
// All done recording.

Now here’s the actual code

$done = FALSE;
while ($done == FALSE) {
  $recording = record('Reecord your message at the tone. Press pound when finished.', array('terminator' => '#'));

As long as we’re not done (the $done flag will be set to true when the caller selects to send the message), do this loop. The first step of the loop is to gather their recording. The record() function is normally used to send a recording to a server of your choice, but if you don’t give it a URL to send to, a temporary recording will be made and deleted when the call ends.

And “record” in the prompt is being spelled “reecord” since the word can be pronounced with either a long or short E sound. Tropo pronounces “record” with a short E, so this intentional misspelling forces it to pronounce it the way we want it.

	// reset selection to 1 since this is a new recording.
	$selection = 1;
	while ($selection == 1) {
	  $choice = ask('To listen to your message, press 1. To re reecord your message, press 2. To send your message, press 3.', array('choices' => '1,2,3', 'mode' => 'dtmf'));

The variable $selection will contain the menu choice. If they select 1, it will play the message and then replay the menu. Since the the first thing we want the caller to do after recording is select from the menu, we start with the selection as 1. The menu gets played and then…

$selection = $choice->value;
if ($selection == 1) {
  // Play the message, then reprompt.
  say($recording->value);
} elseif ($selection == 3) {
  // Break out of the recording loop.
  $done = TRUE;
  continue 2;
}
// Pause a moment before replaying the prompt.
sleep(1);

Grab the user’s selection from the menu on line 19, and if they selected 1 (listen to the message) the message is played. If they picked 2 (re-record) then nothing happens. The inner while loop ends naturally because $selection isn’t 1 and they end up back at the outer while loop where they hear the record prompt again. In either case, we pause for 1 second so the prompts don’t all run together.

If they picked 3 (re-record), then we break out of both the inner loop and the outer loop and continue on with the program.

// If we've gotten here, then it's time to send the message.
say("Sending your message now. Please wait.");

We’ve told the caller that their message is being sent and ask them to wait while we do it.

Because we didn’t provide a recordURI for the record() function, Tropo uses a temporary recording and will hold the message until the session is over. Once the caller hangs up the recording is deleted. Because of this, the sample application makes the caller wait on the line until the messages have all been delivered.

In a real-world application, you would supply a recordURI and store your message there, playing it to the phone tree without making the caller stay on the line. Once the caller selected to send the message, the could just hang up and the message would get sent out behind the scenes.

// Loop over the members list and send the message.
foreach ($members as $to) {
  _log("Sending to " . $to);
  // Don't send to the person who recorded it.
  if ($to != $currentCall->callerID) {
    message('You have a message from the phone tree. ' . $recording->value, array('to' => '+1' . $to, 'callerID' => $currentCall->calledID));    
  }
}

Line 44 starts looping over the list of members and then logs which member we’re sending to. On line 47, we prevent the person who sent the message from receiving it. Line 48 makes the call and sends the message. The temporary recording is stored at a URL specified in $recording->value so by including that URL in the say() Tropo will play the audio.

say("All done. Goodbye.");
hangup();

Finally, we let the caller know that his recording has been delivered to all the people on his phone tree and hang up.

So there you have it, a couple of dozen lines of PHP code that builds a full featured phone tree. The full code’s available from our Tropo samples on Github.

The making of the Tropo USB Drive

Sunday, November 7th, 2010

Part of the Tropo meetup kits is a solid metal 2 GB USB drive that has a bottle opener on one end. They’re laser engraved with the Tropo logo, and people we’ve given them to think they’re pretty nifty.

As part of the drive, we wanted to load some Tropo samples and software, so that people who get them have some Tropo code to help them get going. Like most promotional USB drive makers, the manufacturer of our drives offers a data load service for a pretty tiny fee. But we wanted more.

We’re constantly adding new samples, updating existing ones, and adding features to our code libraries. We wanted to make sure that people who get our drives have the latest and greatest software, and loading something a few weeks ahead of time simply wasn’t good enough for us.

Perhaps you’d like to try the same thing, so here’s how we create the software load for our USB drives.

The whole system relies upon git, the distributed version control system. Git has a concept called submodules that allows you to essentially embed one git repository inside another, while still maintaining the original repository for updates, history, branches, and all the other goodness you get from a SCM.

By using submodules and git, we can create a process that ensures we always update to the latest version of the various packages prior to loading them on the drives.

The first part of this describes how to set up your git repository and use submodules to construct your software pack. The second part will describe the installation process and how it’s streamlined to make it easy to load the software on the drives.

I created a git repository called “usb-pack” and inside it created the directory structure we wanted to use. The USB pack is up on Github if you want to follow along.

git init usb-pack

Our sample code would go in a directory called “examples,” our WebAPI libraries in a “library” directory, and some various applications that use Tropo in an “applications” directory on the drive. I even wanted to include the Phono SDK and some sample Phono apps.

I wanted a directory structure that looked like this…

The “examples” directory will be a clone of the Tropo samples git repository, so I didn’t actually create that directory yet. It will be created in a minute when I add the submodules in. The other directories are mostly containers for a collection of submodules, though, so they need to be created up front.

mkdir applications

Nothing special about that. Just create the directories.

I then went and added the git modules that we wanted to include as submodules in the appropriate directory using a command like this…

git submodule add git://github.com/tropo/tropo-webapi-php libraries/tropo-webapi-php

This creates a linkage between the repository at github.com/tropo/tropo-webapi-php and a new directory called tropo-webapi-php in the libraries directory. I did the same thing for the various applications in the applications directory and the Phono directory.

It’s not necessary for all the git repositories to be in the same place, or belong to the same github account, or even be on github. They can be any git repository anywhere that’s accessible to you. For example, Zhao Lu’s Openvoice, the Ruby on Rails clone of Google Voice, is in his own git repository, but is included as a submodule inside applications.

For the examples directory, since I wanted it in the root, but called “examples” and not “tropo-samples” like the repository is named, it was as simple as this:

git submodule add git://github.com/tropo/tropo-samples examples

Some of the other directories like “logos” and “bin” contain files that aren’t submodules at all. The only place they exist is in this repository. You can mix and match submodules and your own files. In fact, in the phono and other directories, I’ve added README files to explain what they are.

I also created a README that would appear in the root directory so when someone pops the USB drive in, they can see what it’s for.

One thing we discovered when setting up the modules is that it’s best to use the git://github.com/{username}/{repository} format to fetch your submodule repositories. Using the http access or the git@github.com:{user}/{repository}.git access to the repositories caused various sorts of permissions errors for some users.

In a future post, I’ll describe how we built the installer and how to get the software on the drives.

Tutorial – Adding Tropo to Google Wave

Tuesday, February 16th, 2010

There is another great tutorial that Zhao Lu (aka – @zlu) has done, showing how to use Tropo with Google Wave. Zhao takesĀ JRuby, the Rave gem (created by Jason Rush, aka @diminish7) and creating a Google Wave Robot that allows you to create a callable conference in a Wave.

The complete tutorial and code example is available on Github @ http://github.com/zlu/tropo-wave-tutorial. Thanks again to Zhao!