Add Speech Recognition

👍

You're ready for this how-to guide if you've got the following:

A FreeClimb account
A registered application
A configured FreeClimb Number
Your tools and language installed


Node.js

Using the FreeClimb library, an asynchronous out dial request can be created. At minimum a request to create an out dial request requires a To and From number which the user would need to provide in this example. The callConnectUrl and the statusCallbackUrl in your App Config specifies the callback for connected out dial calls and status updates respectively. Successful invocation of the create method indicates the asynchronous out dial request has successfully been queued. All subsequent updates for the request will be directed to the URLs provided.

Create your package.json file and save in the root directory of your project:

{
  "name": "node-speech-recognition-how-to-guide",
  "version": "1.0.0",
  "license": "MIT",
  "dependencies": {
    "@freeclimb/sdk": "^1.0.0",
    "body-parser": "^1.19.0",
    "dotenv": "^8.1.0",
    "express": "^4.17.1"
  }
}

Install the package by running the following in the command line/terminal:

yarn install

Example code:

// Imports and Setup
require('dotenv').config()
const express = require('express')
const bodyParser = require('body-parser')
const app = express()
app.use(bodyParser.json())
const freeclimbSDK = require('@freeclimb/sdk')
 
const port = process.env.PORT || 80
const host = process.env.HOST
const accountId = process.env.ACCOUNT_ID
const apiKey = process.env.API_KEY
const applicationId = process.env.APPLICATION_ID
const freeclimb = freeclimbSDK(accountId, apiKey)
// Your account ID & api key can be found under API credentials on the FreeClimb Dashboard
//Invoke create method to initiate the asynchronous outdial request
freeclimb.api.calls.create(to, from, applicationId).catch(err => {/** Handle Errors */ })

The callConnectUrl provided in App Config will be the recipient of FreeClimb requests for additional actions upon call answer. The additional PerCL actions are contained in the context of the response. In this sample both the Say and GetSpeech PerCL actions are utilized. The GetSpeech PerCL actionUrl provided will be invoked upon completion of the action.

// Handles incoming calls. Set with 'Call Connect URL' in App Config
app.post('/incomingCall', (req, res) => {
  const say = freeclimb.percl.say("Please select a color. Select green, red, or yellow.")
  const options = {
    grammarType: freeclimb.enums.grammarType.URL,
    prompts: [say]
  }
  const getSpeech = freeclimb.percl.getSpeech(`${host}/colorSelectDone`, `${host}/grammarFile`, options)
  const percl = freeclimb.percl.build(getSpeech)
  // Convert PerCL container to JSON and append to response
  res.status(200).json(percl)
})

For a grammar file to be used by FreeClimb, a download URL for the grammar file must be provided. To host the file on the same server as your FreeClimb app, use the following code:

app.get('/grammarFile', function (req, res) {
  const file = `${__dirname}/colorGrammar.xml`
  res.download(file)
})

Successful completion of the GetSpeech PerCL action will result in the receipt of a FreeClimb request at the actionUrl defined in the GetSpeech request. Upon receipt of the request additional PerCL actions are created to speak the selected color and gracefully terminate the out dial call. The sample uses additional Say and Hangup PerCL actions to speak the selected color and gracefully terminate the call.

app.post('/colorSelectDone', (req, res) => {
  const getSpeechActionResponse = req.body
  // Check if recognition was successful
  if (getSpeechActionResponse.reason === freeclimb.enums.getSpeechReason.RECOGNITION) {
    // Get the result
    const color = getSpeechActionResponse.recognitionResult
    say = freeclimb.percl.say(`Selected color was ${color}`)
  } else {
    say = freeclimb.percl.say('There was an error in selecting a color')
  }
  const hangup = freeclimb.percl.hangup()
  const percl = freeclimb.percl.build(say, hangup)
  res.status(200).json(percl)
})

Upon termination of the call the statusCallbackUrl provided in the App Config receives a FreeClimb request and an empty response is provided.

Handle Status Updates:

// Specify this route with 'Status Callback URL' in App Config
app.post('/status', (req, res) => {
  // handle status changes
  res.status(200)
})

Start the server:

app.listen(port, () => {
  console.log(`Starting server on port ${port}`)
})

The custom grammar used was:

<?xml version="1.0" encoding="UTF-8"?>
 
<grammar xml:lang="en-US" tag-format="semantics-ms/1.0" version="1.0" root="PersyColor" mode="voice" xmlns="http://www.w3.org/2001/06/grammar">
<rule id="PersyColor" scope="public">
        <item>
                <item repeat="0-1"><ruleref uri="#UMFILLER"/></item>
                <item>
                    <one-of>
                        <item>green<tag>$._value = "GREEN";</tag></item>
                        <item>red<tag>$._value = "RED";</tag></item>
                        <item>yellow<tag>$._value = "YELLOW";</tag></item>
                    </one-of>
                </item>
                <item repeat="0-1"><ruleref uri="#TRAILERS"/></item>
        </item>
</rule>
 
<rule id="UMFILLER">
        <one-of>
                <item> uh </item>
                <item> um </item>
                <item> hm </item>
                <item> ah </item>
                <item> er </item>
        </one-of>
</rule>
 
<rule id="TRAILERS">
        <one-of>
                <item> maam </item>
                <item> sir </item>
        </one-of>
</rule>
 
</grammar>

Java

To initiate any interaction with FreeClimb a FreeClimbClient object must be created. Using the CallsRequester created upon successful creation of the FreeClimbClient an asynchronous out dial request can be created. At minimum a request to create an out dial request requires a To, From and callConnectUrl. In this sample a statusCallbackUrl is also included. Successful invocation of the create method indicates the asynchronous out dial request has successfully been queued. All subsequent updates for the request will be directed to the URLs provided.

Create your build.gradle file and save it to the root directory in your project:

/*
 * This file was generated by the Gradle 'init' task.
 *
 * This is a general purpose Gradle build.
 * Learn how to create Gradle builds at https://guides.gradle.org/creating-new-gradle-builds
 */

buildscript {
    repositories {
        mavenCentral()
        maven { url 'https://jitpack.io' }
    }
  //Add the dependency
    dependencies {
        classpath "org.springframework.boot:spring-boot-gradle-plugin:2.1.6.RELEASE"
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

bootJar {
    baseName = 'gs-spring-boot'
    version =  '0.1.0'
}

repositories {
    mavenCentral()
    maven { url 'https://jitpack.io' }
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

dependencies {
    compile "org.springframework.boot:spring-boot-starter-web"
    testCompile "junit:junit"
    compile 'com.github.FreeClimbAPI:FreeClimb-Java-SDK:3.0.0'
}

sourceSets {
    main {
        java {
            srcDirs = ['src'] // changed line
        }
    }
}

Build the file by running the following in your terminal/command line:

gradle build

Example code:

import com.vailsys.freeclimb.webhooks.call.VoiceCallback;
import com.vailsys.freeclimb.webhooks.percl.GetSpeechActionCallback;
import com.vailsys.freeclimb.webhooks.percl.SpeechReason;
 
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
 
import com.vailsys.freeclimb.percl.PerCLScript;
import com.vailsys.freeclimb.percl.Say;
import com.vailsys.freeclimb.percl.GetSpeech;
import com.vailsys.freeclimb.percl.GetSpeechNestable;
import com.vailsys.freeclimb.percl.GrammarType;
import com.vailsys.freeclimb.percl.Hangup;
import com.vailsys.freeclimb.percl.Language;
import com.vailsys.freeclimb.percl.Pause;
import com.vailsys.freeclimb.api.call.Call;
import com.vailsys.freeclimb.api.call.CallStatus;
 
import java.io.File;
import java.util.LinkedList;
 
import javax.servlet.http.HttpServletResponse;
 
import com.vailsys.freeclimb.api.FreeClimbClient;
import com.vailsys.freeclimb.api.FreeClimbException;
try {
      // Create FreeClimbClient object
 		 	// Your account ID & api key can be found under API credentials on the FreeClimb Dashboard
      FreeClimbClient client = new FreeClimbClient(accountId, apiKey);
 
      Call call = client.calls.create(toNumber, fromNumber, applicationId);
    } catch (FreeClimbException ex) {
      // Exception throw upon failure
    }

The callConnectUrl provided in the App config will be the recipient of FreeClimb request for additional actions upon call answer. The additional PerCL actions are contained in the context of the response. In this sample both the Say and GetSpeech PerCL actions are utilized. The GetSpeech PerCL actionUrl provided will be invoked upon completion of the action.

@RequestMapping(value = {
      "/InboundCall" }, method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
  public String inboundCall(@RequestBody String body) {
    VoiceCallback callStatusCallback;
    try {
      // Convert JSON into call status callback object
      callStatusCallback = VoiceCallback.createFromJson(body);
    } catch (FreeClimbException pe) {
      PerCLScript errorScript = new PerCLScript();
      Say sayError = new Say("There was a problem processing the incoming call.");
      errorScript.add(sayError);
      return errorScript.toJson();
    }
 
    // Create an empty PerCL script container
    PerCLScript script = new PerCLScript();
 
    // Verify call is in the InProgress state
    if (callStatusCallback.getCallStatus() == CallStatus.IN_PROGRESS) {
      // Create PerCL get speech script (see grammar file contents below)
      GetSpeech getSpeech = new GetSpeech(selectColorDone, grammarDownload);
      // Set location and type of grammar as well as the grammar rule
      getSpeech.setGrammarType(GrammarType.URL);
      getSpeech.setGrammarRule("FreeClimbColor");
 
      // Create PerCL GetSpeechNestable list
      LinkedList<GetSpeechNestable> prompts = new LinkedList<>();
 
      // Create PerCL say script with US English as the language
      Say say = new Say("Please select a color. Select green, red, or yellow.");
      say.setLanguage(Language.ENGLISH_US);
 
      // Add PerCL say script to GetSpeechNestable list
      prompts.add(say);
 
      // Set GetSpeechNestable list as PerCL get speech prompt list
      getSpeech.setPrompts(prompts);
 
      // Add PerCL get speech script to PerCL container
      script.add(getSpeech);
    }
    return script.toJson();
 
  }

Successful completion of the GetSpeech PerCL action will result in the receipt of a FreeClimb request at the actionUrl defined in the GetSpeech request. Upon receipt of the request addition PerCL actions are created to speak the selected color and gracefully terminate the out dial call. The sample uses additional Say and Hangup PerCL actions to speak the selected color and gracefully terminate the call.

@RequestMapping(value = {
      "/SelectColorDone" }, method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
  public String recordCallBack(@RequestBody String body) {
    GetSpeechActionCallback getSpeechActionCallback;
    try {
      // convert JSON into get speech status callback object
      getSpeechActionCallback = GetSpeechActionCallback.createFromJson(body);
    } catch (FreeClimbException pe) {
      PerCLScript errorScript = new PerCLScript();
      Say sayError = new Say("Error with get speech callback.");
      errorScript.add(sayError);
      return errorScript.toJson();
    }
 
    // Create an empty PerCL script container
    PerCLScript script = new PerCLScript();
 
    // Check if recognition was successful
    if (getSpeechActionCallback.getReason() == SpeechReason.RECOGNITION) {
      // Create PerCL say script with US English as the language
      Say say = new Say("Selected color was " + getSpeechActionCallback.getRecognitionResult());
      say.setLanguage(Language.ENGLISH_US);
      // Add PerCL say script to PerCL container
      script.add(say);
    } else {
      // Create PerCL say script with english as the language
      Say say = new Say("There was an error in selecting a color.");
      say.setLanguage(Language.ENGLISH_US);
      // Add PerCL say script to PerCL container
      script.add(say);
    }
 
    // Create PerCL pause script with a duration of 100 milliseconds
    Pause pause = new Pause(100);
    // Add PerCL pause script to PerCL container
    script.add(pause);
 
    // Create PerCL say script with US English as the language
    Say sayGoodbye = new Say("Goodbye");
    sayGoodbye.setLanguage(Language.ENGLISH_US);
    // Add PerCL say script to PerCL container
    script.add(sayGoodbye);
    // Create PerCL hangup script
    Hangup hangup = new Hangup();
    // Add PerCL hangup script to PerCL container
    script.add(hangup);
 
    // Convert PerCL container to JSON and append to response
    return script.toJson();
  }

The getSpeech PerCL command will request the grammar file from the URL specified. Here the grammar file is downloaded and sent to FreeClimb.

@RequestMapping(value = "/grammarFile", method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
 @ResponseBody
 public FileSystemResource getFile(HttpServletResponse response) {
   response.setContentType("application/xml");
   response.setHeader("Content-Disposition", "attachment; filename=\"colorGrammar.xml\"");
   return new FileSystemResource(new File("colorGrammar.xml"));
 }

Upon termination of the call the statusCallbackUrl provided in the App Config receives a FreeClimb request and an empty response is provided.

@RequestMapping(value = { "/Status" }, method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
  public String ColorSelectionStatus(@RequestBody String str) {
    // Create an empty PerCL script container
    PerCLScript script = new PerCLScript();
    // Convert PerCL container to JSON and append to response
    return script.toJson();
  }

C#

To initiate any interaction with FreeClimb a FreeClimbClient object must be created. Using the CallsRequester created upon successful creation of the FreeClimbClient an asynchronous out dial request can be created. At minimum a request to create an out dial request requires a To, From and applicationId. Successful invocation of the create method indicates the asynchronous out dial request has successfully been queued. All subsequent updates for the request will be directed to the Urls provided.

Example code:

try {
    // Create the FreeClimbClient
  	// Your account ID & api key can be found under API credentials on the FreeClimb Dashboard
    FreeClimbClient client = new FreeClimbClient (accountId, apiKey);
    // Create a Call
    Call call = client.getCallsRequester.create (phoneNumber, // To
    freeClimbPhoneNumber, // From,
    applicationId); // Application to Handle the call
}
catch (FreeClimbException ex) {
        System.Console.Write (ex.Message);
}

The callConnectUrl of the provided application in the original create request will be the recipient of FreeClimb request for additional actions upon call answer. The additional PerCL actions are contained in the context of the response. In this sample both the Say and GetSpeech PerCL actions are utilized. The GetSpeech PerCL actionUrl provided will be invoked upon completion of the action.

Example code:

[HttpPost] // POST /voice
public ActionResult SelectColorCallAnswered (CallStatusCallback callStatusCallback) {
  // Create an empty PerCL script container
  PerCLScript script = new PerCLScript ();
  // Verify call is in the InProgress state
  if (callStatusCallback.getDialCallStatus == ECallStatus.InProgress) {
    // Create PerCL get speech script (see grammar file content below)
    string actionUrl = AppUrl + "/voice/SelectColorDone";
    string grammarFile = AppUrl + "/grammars/FreeClimbColor.xml";
    GetSpeech getSpeech = new GetSpeech (actionUrl, grammarFile);
    // Set location and type of grammar as well as the grammar rule
    getSpeech.setGrammarType (EGrammarType.Url);
    getSpeech.setGrammarRule ("FreeClimbColor");

    // Create PerCL say script with US English as the language
    Say say = new Say ();
    say.setLanguage (ELanguage.EnglishUS);
    // Set prompt for color selection
    say.setText ("Please select a color. Select green, red or yellow.");

    // Add PerCL say script to PerCL get speech prompt list
    getSpeech.setPrompts (say);

    // Add PerCL get speech script to PerCL container
    script.Add (getSpeech);
  }

  // Convert PerCL container to JSON and append to response
  return Content (script.toJson (), "application/json");
}

Successful completion of the GetSpeech PerCL action will result in the receipt of a FreeClimb request at the actionUrl defined in the GetSpeech request. Upon receipt of the request additional PerCL actions are created to speak the selected color and gracefully terminate the out dial call. The sample uses additional Say and Hangup PerCL actions to speak the selected color and gracefully terminate the call.

Example code:

[HttpPost("SelectColorDone")]
public ActionResult SelectColorDone (GetSpeechActionCallback getSpeechStatusCallback) {
  // Create an empty PerCL script container
  PerCLScript script = new PerCLScript ();

  // Check if recognition was successful
  if (getSpeechStatusCallback.getReason == ESpeechTermReason.Recognition) {
    // Create PerCL say script with US English as the language
    Say say = new Say ();
    say.setLanguage (ELanguage.EnglishUS);
    // Set prompt to speak the selected color
    say.setText (string.Format ("Selected color was {0}", (getSpeechStatusCallback.getRecognitionResult).ToLower ()));

    // Add PerCL say script to PerCL container
    script.Add (say);
  } else {
    // Create PerCL say script with US English as the language
    Say say = new Say ();
    say.setLanguage (ELanguage.EnglishUS);
    // Set prompt to indicated selection error
    say.setText ("There was an error in selecting a color.");

    // Add PerCL say script to PerCL container
    script.Add (say);
  }

  // Create PerCL pause script with a duration of 100 milliseconds
  Pause pause = new Pause (100);

  // Add PerCL pause script to PerCL container
  script.Add (pause);

  // Create PerCL say script with US English as the language
  Say sayGoodbye = new Say ();
  sayGoodbye.setLanguage (ELanguage.EnglishUS);
  // Set prompt
  sayGoodbye.setText("Goodbye");
  // Add PerCL say script to PerCL container
  script.Add (sayGoodbye);

  // Create PerCL hangup script
  Hangup hangup = new Hangup ();

  // Add PerCL hangup script to PerCL container
  script.Add (new Hangup ());

  // Convert PerCL container to JSON and append to response
  return Content (script.toJson (), "application/json");
}

The custom grammar used was:

<grammar xml:lang="en-US" tag-format="semantics-ms/1.0" version="1.0" root="FreeClimbColor" mode="voice" xmlns="http://www.w3.org/2001/06/grammar">
<rule id="FreeClimbColor" scope="public">
        <item>
                <item repeat="0-1"><ruleref uri="#UMFILLER"/></item>
                <item>
                    <one-of>
                        <item>green<tag>$._value = "GREEN";</tag></item>
                        <item>red<tag>$._value = "RED";</tag></item>
                        <item>yellow<tag>$._value = "YELLOW";</tag></item>
                    </one-of>
                </item>
                <item repeat="0-1"><ruleref uri="#TRAILERS"/></item>
        </item>
</rule>
 
<rule id="UMFILLER">
        <one-of>
                <item> uh </item>
                <item> um </item>
                <item> hm </item>
                <item> ah </item>
                <item> er </item>
        </one-of>
</rule>
 
<rule id="TRAILERS">
        <one-of>
                <item> maam </item>
                <item> sir </item>
        </one-of>
</rule>
 
</grammar>