Skip to content
bdferris edited this page Feb 19, 2012 · 1 revision

OneBusAway has a phone number you can call to have real-time arrival information read to you over the phone. Systems that provide such functionality are typically called Interactive Voice Response (IVR) systems. We describe the implementation details of our system below.

The Phone Server Architecture

Let's walk through what happens when you make a phone call into the system. When you place a call, it gets converted into VOIP and is connected to our [http://www.asterisk.org/ Asterisk] server. Asterisk is kind of like Apache httpd for phone calls. Asterisk accepts the call and passes it off to a Java daemon that will service the actual call. This is kind of like a CGI script in the web world. That Java daemon entry point is in the onebusaway-phone module, specifically the class:

org.onebusaway.phone.PhoneServerMain

All the server does is wire up a bunch of beans using Spring, including the appropriate listeners for incoming calls. See the Spring context file for more details:

src/main/resources/org/onebusaway/phone/application-context.xml

For the most part, you don't need to worry about too many of the previous steps, but it helps to know what's going on.

Generating Call Responses with an MVC Framework

I wrote a library called onebusaway-probable-calls that I use to manage the task of generating a phone tree, handling actions, and generating responses to the user. It's kind of like xwork and Struts for phone calls, which are used for handling actions and generating results for web requests in OneBusAway.

The general idea is that a phone key presses by the user get mapped to Action classes in the actions package:

org.onebusaway.phone.actions.*

The action is responsible for calling OneBusAway service methods to perform the appropriate action and gather any result objects. The action then passes of to a template class that is responsible for generating all the text that will be read to the user. All the templates can be found in the following package:

org.onebusaway.phone.templates.*

In an MVC world, we'd call that these templates the View layer while the Actions are in the Control layer and all the result object passed between the View and the Control is the Model.

The mapping between Actions and Templates is setup in the following file:

src/main/resources/org/onebusaway/phone/xwork.xml file

= An Example Request =

When a call first comes into the system, the "/welcome" action path is initially request (as configured in the "agiEntryPoint" bean in the application-context.xml file). We consult our action mapping document to see how the Action is wired up:

<action name="/welcome" class="org.onebusaway.phone.actions.DefaultAction">
  <result>/welcome</result>
</action>

The "/welcome" action is just mapped to the DefaultAction, since it doesn't really do anything. Instead, the result is more important. The result is named "/welcome" as well, which maps to

org.onebusaway.phone.templates.WelcomeTemplate

Note the @AgiTemplateId("/welcome") at the top of WelcomeTemplate... that's where the /welcome result connection is made.

The WelcomeTemplate configure a number of mappings between user key presses and next actions:

addAction("1", "/stop/index");
addAction("2", "/find_your_stop");
addAction("3", "/bookmarks/index");
addAction("4", "/bookmarks/manage");
addAction("5", "/most_recent");
addAction("6", "/search/index");

It then adds a message to be read to the user:

addMessage(Messages.WELCOME_ACTION);

Note that messages are typically just keys in a message class. In this case, it's the

org.onebusaway.phone.templates.Messages

class. Where are the actual message strings defined? Check out the

src/main/resources/org/onebusaway/phone/templates/package_en_US.properties

file. This defines the message strings for the en_US locale (US English) and gives us the ability to define messages for other languages if need be.

The template fires off the messages to be read to the user. The user can then press a key to determine the next action. Most users will press "1" so that they can enter a stop id and hear real-time arrival information for that stop. That key press is mapped to the "/stop/index" action. Consulting our action mapping again, we see:

<action name="/stop/index" class="org.onebusaway.phone.actions.DefaultAction">
    <result>/stop/index</result>
</action>

Again, we don't need to do any real processing at this point, so we use the DefaultAction again and map to the "/stop/index" result, which is defined in the

org.onebusaway.phone.templates.stops.IndexTemplate

template class. It just reads a message asking the user to enter their stop number followed by the # sign. When the user does, the result is sent to the "/stop/byCode" action, setting the "stopCode" parameter of the action to the stop id entered by the user.

The action mapping for the "/stop/byCode" action looks like:

<action name="/stop/byCode" class="org.onebusaway.phone.actions.schedule.StopForCodeAction">
    <result type="chain">/stop/arrivalsAndDeparturesForStopId</result>
    <result name="input">/stop/invalidStopNumber</result>
    <result name="needDefaultSearchLocation">/stop/needDefaultSearchLocation</result>
</action>

Here we finally have an action that does some actual work. In this case, the StopForCodeAction does a search for a StopBean with stop code entered by the user. Assuming the StopBean is found, control is passed of to the default result, which in this case is a chain result:

<result type="chain">/stop/arrivalsAndDeparturesForStopId</result>

A chaining result passes control to another action instead of a result template. In this case, the action mapping looks like:

<action name="/stop/arrivalsAndDeparturesForStopId" class="org.onebusaway.phone.actions.schedule.ArrivalsAndDeparturesForStopIdAction">
  <result>/stop/arrivalsAndDepartures</result>
</action>

The ArrivalsAndDeparturesForStopIdAction consults the StopBean and looks for arrival and departure information for that stop. Here, control is finally passed to a a template which constructs the text for reading the real-time arrival information to the user.

Running the Phone Server

Before you can run the phone server, you need to do some setup. In the following directory:

src/main/default-resources

you'll find two files:

data-sources.xml
pronunciations.xml

These two files are responsible for setting up the various data-sources used by the phone server and also setting up some pronunciation hints for tricky to say words.

Copy both of these files into your

src/main/resources

directory.

At this point, you can launch the phone server by running the following main class:

org.onebusaway.phone.PhoneServerMain

If all goes well, the server should startup with no exceptions.

Normally, to actually use the phone service would require setting up Asterisk and a VOIP client. It's a major hassle.

To make things easier, I've added a VERY simple phone client that you can run to interact with the phone service itself. It doesn't do any text-to-speech. Instead, it just prints the text from the server and let's you press keys to control the call.

To run the simple phone client, run:

org.onebusaway.phone.client.SimplePhoneClient