Hello World

The classic. Let’s look at how to make a simple app in three lines of code:

channel.answer()

channel.say('hello world')

channel.hangup()

Breaking it down:

channel.answer()

The channel object is a powerful and necessary part of any voice application. Here, we call the answer function using Lua dot syntax so that when we call into this app, we get an answer.

Tip: Take some time to check out the API Documentation for the channel object. The API docs are a great starting place and provide a lot of the basic functionality of a telecom app.

channel.say('hello world')

Our app has answered the call. Now we want it to say something. The say function takes a string as a parameter to pass to the text-to-speech (TTS) engine.

channel.hangup()

Easy enough, but not to be forgotten. We want to make sure we hang up the call in order to disconnect from the caller.

That’s it! Three lines to a functioning app. In Sample #2, we’ll build on this basic example to include interaction with the caller.

Caller Interaction

If you’ve gone through the Setting Up section and cloned an app’s Git repo already, this example should look familiar to you. It’s the base application that we give you with each new app that is added.

channel.answer()
channel.say("This is an example application. Enter any number followed by the pound sign.")

local digits = channel.gather()

channel.say(digits)
channel.hangup()

Breaking it down:

local digits = channel.gather()

Here’s where the interaction comes in. We’ve declared a variable “digits” and assigned the gather function to it. The gather function is used to prompt the caller to input information. In this case, we’re using the default options (1-100 digits). When the caller enters their digits and presses the # key to end the gather, their digits are then assigned to the variable “digits.”

channel.say(digits)

Now we repeat the digits back to the user by passing the variable “digits” into the say function instead of a string as we used previously in Sample #1.

In Sample #3, we’ll tie the user input to options to build a simple IVR.

Simple Menu

Traditionally, menus have been written as a series of if/else statements, making for a somewhat cumbersome method. We’ll first look at the traditional way of writing a menu with the Summit platform, then we’ll look at our super simple, improved solution using the menu class.

local speech = require "summit.speech"

channel.answer()

local digit = channel.gather(
    { play=speech("Press 1 to hear an emergency compliment. Press 2 to get knocked down a notch."), maxDigits=1 }
)

if digit == '1' then
    channel.say('Your hair looks great today. It also looked really good two days ago.')
elseif digit == '2' then
    channel.say("Those shoes maybe weren't the best choice with that shirt.")
else
    channel.say('Please press either 1 or 2.')
end
channel.hangup()

Breaking it down:

if digit == '1' then
    channel.say('Your hair looks great today. It also looked really good two days ago.')
elseif digit == '2' then
    channel.say("Those shoes maybe weren't the best choice with that shirt.")
else
    channel.say('Please press either 1 or 2.')
end

This should be fairly self-explanatory. We first collect a digit from the caller. Then we make decisions based on what that digit was, making sure to give an option in the event that the caller enters something other than 1 or 2.

This method works fine, but it’s prone to errors and could become somewhat difficult to manage when we add additional options or need to make changes.

Let’s look at the simplified way using our menu class:

channel.answer()

local menu = require 'summit.menu'

local my_ivr = menu()

function positive_affirmation()
    channel.say("Your hair looks great today. It also looked really good two days ago.")
end
function negative_response()
    channel.say("Those shoes maybe weren't the best choice with that shirt.")
end
function invalid_response()
    channel.say("Invalid input. Press 1 or 2.")
end


my_ivr.add('1', "Press 1 to hear an emergency compliment.", positive_affirmation)
my_ivr.add('2', "Press 2 to get knocked down a notch.", negative_response)
my_ivr.default(invalid_response)

my_ivr.run()

channel.hangup()

Breaking it down:

local menu = require 'summit.menu'

First, we need to tell our application that we’ll be using the menu class and include it with our app.

local my_ivr = menu()

We declare a variable “my_ivr” and assign the menu class to it.

function positive_affirmation()
    channel.say("Your hair looks great today. It also looked really good two days ago.")
end
function negative_response()
    channel.say("Those shoes maybe weren't the best choice with that shirt.")
end
function invalid_response()
    channel.say("Invalid input. Press 1 or 2.")
end

Then we make some responses to our options.

my_ivr.add('1', "Press 1 to hear an emergency compliment.", positive_affirmation)
my_ivr.add('2', "Press 2 to get knocked down a notch.", negative_response )
my_ivr.default(invalid_response)

Now we use the add function to add options to “my_ivr”, and the default function to give our menu a fallback.

my_ivr.run()

Lastly, we run the menu. The run function will build our menu with the appropriate options, use channel.gather() to get input from the user, and call the function registered for that user input.

HTTP Request

Let’s put together all the concepts we’ve sampled so far to create an app that will tell you the weather based on a caller inputting a 5-digit ZIP Code.

local http = require 'summit.http'
local menu = require 'summit.menu'
local speech = require 'summit.speech'
local json = require 'json'


channel.answer()

local zip = channel.gather({play=speech("Enter your 5-digit zip code to hear current weather conditions"), maxDigits=5, minDigits=5})

local params = {q=zip, units="imperial", mode="json"}

local res, err = http.get('http://api.openweathermap.org/data/2.5/weather', {data=params})

if err then
    channel.say('Unable to contact weather service, goodbye.')
    channel.hangup()
end

local res_content = json:decode(res.content)

local my_ivr = menu()

function the_temperature()
    channel.say("It is currently ".. res_content.main.temp)
end
function the_conditions()
    channel.say(res_content["weather"][1]["description"])
end
function invalid_response()
    channel.say("Please press either 1 or 2.")
end

my_ivr.add('1', "Press 1 to hear the temperature.", the_temperature)
my_ivr.add('2', "Press 2 to hear the conditions.", the_conditions )
my_ivr.default(invalid_response)

my_ivr.run()

channel.hangup()

Breaking it down

local http = require 'summit.http'
local json = require 'summit.menu'
local speech = require 'summit.speech'
local json = require 'json'

We’re using the OpenWeatherMap API to look up current conditions via zip code. We need to include the HTTP request library so we can pull in a JSON file from OpenWeatherMap. We’re including the JSON library so we can easily work with the data later. We're also going to use the speech library, so it's included here.

local zip = channel.gather({play=speech("Enter your 5-digit zip code to hear current weather conditions"), maxDigits=5, minDigits=5})

Here, we use the speech library in our gather to ask for a 5-digit zip code.

local params = {q=zip, units="imperial", mode="json"} local res, err = http.get('http://api.openweathermap.org/data/2.5/weather', {data=params})

We declared a variable of “params” that holds a table of key/value pairs to be sent as parameters with the request.

Next, we use the get function to make a request to a URL and pass in the data option with our “params” variable.

if err then
   channel.say('Unable to contact weather service, goodbye.')
  channel.hangup()
end

Make sure to include instructions for what to do if your request gets an error response. You can use the say function or hangup function, as we have here, to inform the caller of what has occurred and then terminate the call.

local res_content = json:decode(res.content)

Next, we’ll take the response (a JSON object) and use the decode function in the JSON library to turn it into a lua table where we can easily access it’s contents.

Now we use the simple menu method we learned in Sample #3 to construct some responses using data from our HTTP response.

function the_temperature()
    channel.say("It is currently ".. res_content.main.temp)
end
function the_conditions()
    channel.say(res_content["weather"][1]["description"])
end
function invalid_response()
    channel.say("Please press either 1 or 2.")
end

my_ivr.add('1', "Press 1 to hear the temperature.", the_temperature)
my_ivr.add('2', "Press 2 to hear the conditions.", the_conditions )
my_ivr.default(invalid_response)

my_ivr.run()