Download

/******************************************************************************
 This is the weather by ZIP code demo in which the user enters the ZIP code 
 via the keypad and the HTTP GET transaction is performed asynchronously.
****************************************************************************/
#include <winsock2.h>
#include <shlwapi.h>
#include <wchar.h>
#include <string>
#include <iostream>

// Header for the XML parser that we use

#import <msxml4.dll> 
using namespace MSXML2;

// Header for the telephony, text-to-speech and speech recognition classes

#include "ClientCall.h"
using namespace IFB;

// Details for web service which furnishes the content that we speak - the weather forecast

static char szGet[]        = "GET /forecastrss?p=00000 HTTP/1.0\r\n"
                             "Host: weather.yahooapis.com\r\n"
                             "Accept: */*\r\n\r\n"; 

static char szServer[]     = "weather.yahooapis.com";

static WORD        wPort = 80;
static sockaddr_in server; 

// The namespace used in XML document returned  

static wchar_t wszNamespaces[] = L"xmlns:yweather='http://xml.weather.yahoo.com/ns/rss/1.0'";

// Header / payload separator

static char szSeparator[]  = "\r\n\r\n";

// Logical end of message 

static char szTerminator[] = "</rss>";

// The size of the query string

static int  lenGet       = sizeof(szGet) - 1;
static int  offsetGet    = 0;

// Various prompts that we speak

static wchar_t wszPrompt[]     = L"Please enter a five digit ZIP code or press the pound key to quit.";
static wchar_t wszGoodbye[]    = L"Good bye.";
static wchar_t wszWelcome[]    = L"Welcome to the weather demonstration application.";
static wchar_t wszPatience[]   = L"Please wait";
static wchar_t wszRegrets[]    = L"We are sorry but we had trouble determining the weather for that ZIP 

code. "
                                 L"If the ZIP code is valid you can try again later.";

// Function to play "music on hold"

void HoldForEvent(ClientCall &, HANDLE, LPCSTR);

// This structure and the functions whose prototypes are just  
// below should be made into a class. We don't do that because we 
// want to salvage as much of this sample as we can for the C sample

struct HTTPCONTEXT
{
 SOCKET       sock;
 HANDLE       hEvent;
 DWORD        dwSent;
 DWORD        dwRcvd;
 DWORD        dwErrRcv;
 DWORD        dwErrSnd;
 OVERLAPPED   ovl;
 char         szResp[4096];
 char         nullbyte; 
 std::wstring weather;
 bool         iopending;
 char         szGet[1];
};

// Functions to handle the HTTP post to the web service

HTTPCONTEXT * AllocateServiceContext(int, LPCSTR);
void          SetServiceEvent(HTTPCONTEXT *, HANDLE);
void          ConnectToService(HTTPCONTEXT *);
void          MakeServiceRequest(HTTPCONTEXT *, LPCSTR);
void          ReceiveServiceResponse(HTTPCONTEXT *);
bool          FormatServiceResponse(HTTPCONTEXT *);
void          DisconnectFromService(HTTPCONTEXT *, bool);
void          DeallocateServiceContext(HTTPCONTEXT *);
void CALLBACK RecvDone(DWORD, DWORD, WSAOVERLAPPED *, DWORD);
void CALLBACK SendDone(DWORD, DWORD, WSAOVERLAPPED *, DWORD);

// Functions to parse the XML document returned by 
// the web service use MSXML 

std::wstring GetCity(IXMLDOMDocument2Ptr &);
std::wstring GetState(IXMLDOMDocument2Ptr &);
std::wstring GetWind(IXMLDOMDocument2Ptr &);
std::wstring GetOutlook(IXMLDOMDocument2Ptr &);
std::wstring GetTemperature(IXMLDOMDocument2Ptr &);
std::wstring Direction(int);

/******************************************************************************
 Unlike the .Net samples where we use a hash table, we maintain two parallel
 string arrays for translating wind directions to pronounceable phrases so
 that we can use the same implementation in the C sample
******************************************************************************/
LPCWSTR pszStates[52][2] = { L"AL", L"Alabama", 
                             L"AK", L"Alaska",
                             L"AZ", L"Arizona",
                             L"AR", L"Arkansas", 
                             L"CA", L"California",
                             L"CO", L"Colorado",
                             L"CT", L"Connecticut",
                             L"DE", L"Delaware",
                             L"DC", L"D C",
                             L"FL", L"Florida",
                             L"GA", L"Georgia",
                             L"HI", L"Hawaii",
                             L"ID", L"Idaho",
                             L"IL", L"Illinois",
                             L"IN", L"Indiana",
                             L"IA", L"Iowa",
                             L"KS", L"Kansas",
                             L"KY", L"Kentucky",
                             L"LA", L"Louisiana",
                             L"ME", L"Maine",
                             L"MD", L"Maryland",
                             L"MA", L"Massachusetts",
                             L"MI", L"Michigan",
                             L"MN", L"Minnesota",
                             L"MS", L"Mississippi",
                             L"MO", L"Missouri", 
                             L"MT", L"Montana",
                             L"NE", L"Nebraska",
                             L"NV", L"Nevada",
                             L"NH", L"New Hampshire",
                             L"NJ", L"New Jersey",
                             L"NM", L"New Mexico",
                             L"NY", L"New York",
                             L"NC", L"North Carolina",
                             L"ND", L"North Dakota",
                             L"OH", L"Ohio",
                             L"OK", L"Oklahoma",
                             L"OR", L"Oregon",
                             L"PA", L"Pennsylvania",
                             L"PR", L"Puerto Rico",
                             L"RI", L"Rhode Island",
                             L"SC", L"South Carolina",
                             L"SD", L"South Dakota",
                             L"TN", L"Tennessee",
                             L"TX", L"Texas",
                             L"UT", L"Utah",
                             L"VT", L"Vermont",
                             L"VA", L"Virginia",
                             L"WA", L"Washington",
                             L"WV", L"West Virginia",
                             L"WI", L"Wisconsin",
                             L"WY", L"Wyoming" };

/******************************************************************************
 This exported function is called once when the application is first loaded
 by the server or the simulator
******************************************************************************/
BOOL CALLBACK Initialize(HANDLE hShutdown)
{
 char    *p;
 BOOL    bOK;
 hostent *phe;
 
 bOK = FALSE;

 // Get web service address

 phe = gethostbyname(szServer);

 if ( !phe )
  std::cout << "C++ Weather application: could not resolve the web service address." << std::endl;

 else 
 {
  // Save the address of the web service 

  memset(&server, 0, sizeof(server));
  memcpy(&server.sin_addr, phe->h_addr, sizeof(phe->h_addr));

  server.sin_port   = htons(wPort);
  server.sin_family = phe->h_addrtype;

  if ( p = strstr(szGet, "00000") )
  {
   offsetGet = p - szGet;
   bOK = TRUE;
  }
 }
 
 return bOK;
}

/******************************************************************************
 The exported Answer() method comprises the entire voice user interface and
 is called each time a new call arrives on the line bound to this application
******************************************************************************/
void CALLBACK Answer(ClientCall &call)
{
 int         i, j;
 char        c, szZip[6] = { 0 };
 HANDLE      hEvent;
 HTTPCONTEXT *pctx;
 Synthesizer *psyn;

 // We create an instance of the structure used to manage the communication 
 // with the web service. The event we create is signalled each time a network
 // operation to the web service completes

 pctx = AllocateServiceContext(lenGet, szGet);
 hEvent = CreateEvent(0, TRUE, FALSE, 0);
 SetServiceEvent(pctx, hEvent);
 
 try
 {
  // We want an exception if the user hangs up

  call.hangupThrows(true);

  // Get an instance of the synthesizer to speak the greeting and wait for it

  psyn = call.getSynthesizer();
  psyn->speak(wszWelcome);
  psyn->wait();

  while ( true )
  {
   // Prompt the caller for input

   psyn->speak(wszPrompt);

   // Wait for 5 keypresses, but no more than 20 seconds, # quits

   if ( !call.inputWait(5, '#', 20000) )
    break;

   // We have no patience if we got less than 5 characters

   if ( call.inputAvailable() < 5 )
    break;

   // Pull keystrokes from the typeahead buffer and build a ZIP code from them

   for ( i = j = 0; i < 5; i++ )
   {
    c = call.getChar();

    if ( (c >= '0') && (c <= '9') )
     szZip[j++] = c;

    szZip[j] = 0;
   }

   // If the user entered a terminator we quit

   if ( strlen(szZip) != 5 )
    break;

   // Tell the user to wait

   psyn->speak(wszPatience);

   // Initiate connection to web service
   // Play music on hold while connecting
   
   ConnectToService(pctx);
   HoldForEvent(call, hEvent, "strumming");

   // Initiate HTTP POST to web service
   // Play music on hold while posting

   MakeServiceRequest(pctx, szZip);
   HoldForEvent(call, hEvent, "strumming");

   // Initiate reception of web service response
   // Play music on hold while receiving data

   ReceiveServiceResponse(pctx);
   HoldForEvent(call, hEvent, "strumming");

   // Format the response from the web service

   if ( !FormatServiceResponse(pctx) )
    pctx->weather = wszRegrets;

   // Stop the music on hold, wait a bit and speak the forecast
 
   call.stopPlaying();
   call.wait(500);
   psyn->speak( pctx->weather.c_str() );

   // While speaking the weather, disconnect from the web service
   // and update the call detail record for this call

   DisconnectFromService(pctx, false);
   call.cdrStatusMessage(0, szZip);
   call.cdrStatusMessage(1, pctx->weather.c_str() );

   // Let the user hear his forecast before we
   // ask if he wants to go again

   psyn->wait();
  }

  // Be polite and say good bye before we hang up

  psyn->speak(wszGoodbye);
  psyn->wait();
 }

 // If the user hangs up we'll just cancel the web service request

 catch ( ClientCallTermination e )
 {
  DisconnectFromService(pctx, true);
  std::cout << "C++ weather demo exception: " << e.what() << std::endl;
 }

 // Deallocate the event and the context structures that we allocated

 CloseHandle(hEvent);
 DeallocateServiceContext(pctx);
}

/******************************************************************************
 This function plays "music on hold" while a network operation is in progress.

 Because we call this function multiple times we make sure that we don't 
 unnecessarily queue up more messages than we need. That guarantees that we'll 
 never wait for longer than the length of the music clip to be played. It's not 
 at all necessary to do that here. It would be useful if we wanted to impose a 
 timeout on our waits.
******************************************************************************/
void HoldForEvent(ClientCall &call, HANDLE hEvent, LPCSTR pszMusic)
{
 int millis;

 while ( true )
 {
  // No sense carrying on if caller is gone

  if ( call.isDisconnected() )
   break;

  // We end the wait on network events or on errors

  if ( WaitForSingleObject(hEvent, 0) != WAIT_TIMEOUT )
   break;

  // If there is a clip playing we determine how long it is

  millis = call.messageTimeRemaining();

  // Otherwise we queue up a new one and determine its length

  if ( millis == 0 )
   millis = call.playMessage(pszMusic);
   
  // Whether we started it or not, wait for clip to end, or 
  // network event to be signalled, or caller to hang up

  call.wait(hEvent, millis);
 }
}

/******************************************************************************
 This allocates the structure that maintains the state of the HTTP 
 POST to the web service 
******************************************************************************/
HTTPCONTEXT * AllocateServiceContext(int len, LPCSTR pszGet)
{
 int  offset;
 char *p;

 len = sizeof(HTTPCONTEXT) + len;
 p = new char[len];
 memset(p, 0, len);

 offset = offsetof(HTTPCONTEXT, szGet);
 strcpy(p + offset, szGet);

 return (HTTPCONTEXT *) p;
}

/******************************************************************************
 The completion of a network operation signals an event so that the caller 
 can track progress
******************************************************************************/
void SetServiceEvent(HTTPCONTEXT *pctx, HANDLE hEvent)
{
 pctx->hEvent = hEvent;
}

/******************************************************************************
 Attempt to connect to the web service asynchronously
******************************************************************************/
void ConnectToService(HTTPCONTEXT *pctx)
{
 char dont = true;

 pctx->dwRcvd   = 0;
 pctx->dwSent   = 0;
 pctx->dwErrRcv = 0;
 pctx->dwErrSnd = 0;

 memset(&pctx->szResp, 0, sizeof(pctx->szResp));

 pctx->sock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, 
                        0, 0, WSA_FLAG_OVERLAPPED);

 if ( pctx->sock != INVALID_SOCKET )
 {
  setsockopt(pctx->sock, SOL_SOCKET, SO_DONTLINGER, &dont, sizeof(dont));
  WSAEventSelect(pctx->sock, pctx->hEvent, FD_CONNECT);
  ResetEvent(pctx->hEvent);
  WSAConnect(pctx->sock, (sockaddr *) &server, sizeof(server), 0, 0, 0, 0);
 }
}

/******************************************************************************
 Make a request of the web service by appending the ZIP code to the POST, 
 then send it asynchronously
******************************************************************************/
void MakeServiceRequest(HTTPCONTEXT *pctx, LPCSTR pszZip)
{
 DWORD  dwErr;
 WSABUF buf;

 memcpy(&pctx->szGet[offsetGet], pszZip, strlen(pszZip));

 buf.buf = &pctx->szGet[0];
 buf.len = lenGet;

 memset(&pctx->ovl, 0, sizeof(pctx->ovl));
 pctx->ovl.hEvent = (HANDLE) pctx;

 pctx->iopending = true;
 ResetEvent(pctx->hEvent);

 if ( WSASend(pctx->sock, &buf, 1, &pctx->dwSent, 0, &pctx->ovl, &SendDone) != 0 )
  if ( (dwErr = WSAGetLastError()) != WSA_IO_PENDING )
  {
   pctx->iopending = false;
   pctx->dwErrSnd = dwErr;
   SetEvent(pctx->hEvent);
  }

 return;
}

/******************************************************************************
 Initiate the asynchronous reception of the response from the web service
******************************************************************************/
void ReceiveServiceResponse(HTTPCONTEXT *pctx)
{
 DWORD  dwErr, dwFlags, dwRcvd;
 WSABUF buf;

 if ( (pctx->dwSent == 0) || (pctx->dwErrSnd != 0) )
  SetEvent(pctx->hEvent);

 else
 {
  buf.buf = &pctx->szResp[0] + pctx->dwRcvd;
  buf.len = sizeof(pctx->szResp) - pctx->dwRcvd;

  memset(&pctx->ovl, 0, sizeof(pctx->ovl));
  pctx->ovl.hEvent = (HANDLE) pctx;

  pctx->iopending = true;
  ResetEvent(pctx->hEvent);

  dwFlags = 0;
  if ( WSARecv(pctx->sock, &buf, 1, &dwRcvd, &dwFlags, &pctx->ovl, &RecvDone) != 0 )
   if ( (dwErr = WSAGetLastError()) != WSA_IO_PENDING )
   {
    pctx->iopending = false;
    pctx->dwErrRcv = dwErr;
    SetEvent(pctx->hEvent);
   }
 }
}

/******************************************************************************
 Create a two sentence forecast from the web service response
******************************************************************************/
bool FormatServiceResponse(HTTPCONTEXT *pctx)
{
 bool                ok;
 LPCSTR              pszXML;
 HRESULT             hr;
 VARIANT             prop;
 std::wstring        city, state, wind, outlook, temp;
 IXMLDOMDocument2Ptr doc;

 // Assume the worst

 ok = false;

 // Find the beginning of the payload

 if ( pszXML = strstr(pctx->szResp, szSeparator) )
 {
  // so we can advance past the header to it

  pszXML += sizeof(szSeparator) - 1;

  // Get an XML parser and load the response into it, synchronously

  doc.CreateInstance(L"Msxml2.DomDocument.4.0");
  doc->async = false;
  doc->loadXML(_bstr_t(pszXML));

  // Oh, how we love X-Path

  prop.vt = VT_BSTR;
  prop.bstrVal = _bstr_t(L"XPath");
  hr = doc->setProperty(_bstr_t(L"SelectionLanguage"), prop);

  // and its namespaces

  prop.bstrVal = _bstr_t(wszNamespaces);
  hr = doc->setProperty(_bstr_t(L"SelectionNamespaces"), prop);

  // Pull the wind, location, temperature and outlook from the response

  wind    = GetWind(doc);
  city    = GetCity(doc);
  state   = GetState(doc);
  temp    = GetTemperature(doc);
  outlook = GetOutlook(doc);

  // The parsing functions return empty strings on failure

  if ( city.length() && outlook.length() && temp.length() && wind.length() )
  {
   // Build a two sentence response

   pctx->weather = L"In ";
   pctx->weather.append(city);
   pctx->weather.append(state);
   pctx->weather.append(L", the weather is ");
   pctx->weather.append(outlook);
   pctx->weather.append(L" with a temperature of "); 
   pctx->weather.append(temp);
   pctx->weather.append(L" degrees Fahrenheit. The winds are ");
   pctx->weather.append(wind);
   pctx->weather.append(L".");

   ok = true;
  }
 }
 
 return ok;
}

/******************************************************************************
 Break the connection to the web service
******************************************************************************/
void DisconnectFromService(HTTPCONTEXT *pctx, bool cancel)
{
 shutdown(pctx->sock, SD_BOTH);
 closesocket(pctx->sock);
 pctx->sock = INVALID_SOCKET;

 if ( cancel )
 {
  while ( pctx->iopending )
   SleepEx(100, TRUE);
 }
}

/******************************************************************************
 Deallocate the context structure
******************************************************************************/
void DeallocateServiceContext(HTTPCONTEXT *pctx)
{
 char *p;

 p = (char *) pctx;
 delete [] p;
}

/******************************************************************************
 Completion routine called after the request is delivered to the web service
******************************************************************************/
void CALLBACK SendDone(DWORD dwErr, DWORD cb, WSAOVERLAPPED *povl, DWORD dwFlags)
{
 HTTPCONTEXT *pctx = (HTTPCONTEXT *) (povl->hEvent);

 pctx->dwSent    = cb;
 pctx->dwErrSnd  = dwErr;
 pctx->iopending = false;

 SetEvent(pctx->hEvent);
}

/******************************************************************************
 When each read completes we check to see if we got all of the response
******************************************************************************/
bool IsRecvComplete(char *pszResp)
{
 char *p;
 bool complete;

 complete = false;

 if ( p = strstr(pszResp, szTerminator) )
 {
  p[ sizeof(szTerminator) - 1 ] = 0; 
  complete = true;
 }

 else
  complete = false;

 return complete;
}

/******************************************************************************
 Completion routine called everytime we receive data from the web service
******************************************************************************/
void CALLBACK RecvDone(DWORD dwErr, DWORD cb, WSAOVERLAPPED *povl, DWORD dwFlags)
{
 HTTPCONTEXT *pctx = (HTTPCONTEXT *) (povl->hEvent);

 pctx->dwRcvd  += cb;
 pctx->dwErrRcv = dwErr;

 if ( (cb == 0) || IsRecvComplete(pctx->szResp) )
 {
  pctx->iopending = false;
  SetEvent(pctx->hEvent);
 }

 else
  ReceiveServiceResponse(pctx);
}

/******************************************************************************
 Extracts and formats the city information
******************************************************************************/
std::wstring GetCity(IXMLDOMDocument2Ptr &doc)
{
 BSTR           temp;
 HRESULT        hr;
 std::wstring   city;
 IXMLDOMNodePtr node;

 city = L"";

 if ( node = doc->selectSingleNode(_bstr_t(L"//channel/yweather:location")) )
 {
  hr = node->attributes->getNamedItem(_bstr_t(L"city"))->get_text(&temp);
 
  if ( SUCCEEDED(hr) )
  {
   city = std::wstring(temp);
   SysFreeString(temp);
  }
 }

 return city;
}

/******************************************************************************
 Extracts and formats the city information
******************************************************************************/
std::wstring GetState(IXMLDOMDocument2Ptr &doc)
{
 int            i, count;
 BSTR           temp;
 HRESULT        hr;
 std::wstring   state;
 IXMLDOMNodePtr node;

 state = L" ";

 if ( node = doc->selectSingleNode(_bstr_t(L"//channel/yweather:location")) )
 {
  hr = node->attributes->getNamedItem(_bstr_t(L"region"))->get_text(&temp);
 
  if ( SUCCEEDED(hr) )
  {
   count = sizeof(pszStates) / sizeof( pszStates[0] );

   for ( i = 0; i < count; i++ )
    if ( wcscmp(temp, pszStates[i][0] ) == 0 )
    {
     state.append(pszStates[i][1]);
     break;
    } 
   
   SysFreeString(temp);
  }
 }

 return state;
}

/******************************************************************************
  Pulls the wind speed and direction from the XML document
******************************************************************************/
std::wstring GetWind(IXMLDOMDocument2Ptr &doc)
{
 int            degrees;
 BSTR           temp1, temp2;
 HRESULT        hr1, hr2;
 std::wstring   wind;
 IXMLDOMNodePtr node;

 wind = L"";

 if ( node = doc->selectSingleNode(_bstr_t(L"//channel/yweather:wind")) )
 {
  hr1 = node->attributes->getNamedItem(_bstr_t(L"direction"))->get_text(&temp1);
  hr2 = node->attributes->getNamedItem(_bstr_t(L"speed"))->get_text(&temp2);

  if ( SUCCEEDED(hr1) && SUCCEEDED(hr2) )
  {
   degrees = _wtoi(temp1);

   wind = L"from the ";
   wind.append( Direction(degrees) );
   wind.append(L" at ");
   wind.append(temp2);
   wind.append(L" miles per hour");
  }

  if ( SUCCEEDED(hr1) )
   SysFreeString(temp1);

  if ( SUCCEEDED(hr2) ) 
   SysFreeString(temp2);
 }

 return wind;
}

/******************************************************************************
 Extracts and formats the current weather condition
******************************************************************************/
std::wstring GetOutlook(IXMLDOMDocument2Ptr &doc)
{
 BSTR           temp;
 HRESULT        hr;
 std::wstring   outlook;
 IXMLDOMNodePtr node;

 outlook = L"";

 if ( node = doc->selectSingleNode(_bstr_t(L"//channel/item/yweather:condition")) )
 {
  hr = node->attributes->getNamedItem(_bstr_t(L"text"))->get_text(&temp);

  if ( SUCCEEDED(hr) )
  {
   outlook = std::wstring(temp);
   SysFreeString(temp);
  }
 }

 return outlook;
}

/******************************************************************************
 Extracts and formats the current temperature
******************************************************************************/
std::wstring GetTemperature(IXMLDOMDocument2Ptr &doc)
{
 BSTR           bstr;
 HRESULT        hr;
 std::wstring   temp;
 IXMLDOMNodePtr node;

 temp = L"";

 if ( node = doc->selectSingleNode(_bstr_t(L"//channel/item/yweather:condition")) )
 {
  hr = node->attributes->getNamedItem(_bstr_t(L"temp"))->get_text(&bstr);

  if ( SUCCEEDED(hr) )
  {
   temp = std::wstring(bstr);
   SysFreeString(bstr);
  }
 }

 return temp;
}

/******************************************************************************
 Translates a compass reading to text
******************************************************************************/
std::wstring Direction(int degrees)
{
 std::wstring dir;

 if ( degrees <= 11 )
  dir = L"north";

 else if ( degrees <= 33 )
  dir = L"north northeast";

 else if ( degrees <= 56 )
  dir = L"northeast";

 else if ( degrees <= 78 )
  dir = L"east northeast";

 else if ( degrees <= 101 )
  dir = L"east";

 else if ( degrees <= 123 )
  dir = L"east southeast";

 else if ( degrees <= 146 )
  dir = L"southeast";

 else if ( degrees <= 168 )
  dir = L"south southeast";

 else if ( degrees <= 191 )
  dir = L"south";

 else if ( degrees <= 213 )
  dir = L"south southwest";

 else if ( degrees <= 236 )
  dir = L"southwest";

 else if ( degrees <= 258 )
  dir = L"west southwest";

 else if ( degrees <= 281 )
  dir = L"west";

 else if ( degrees <= 303 )
  dir = L"west northwest";

 else if ( degrees <= 326 )
  dir = L"northwest";

 else if ( degrees <= 348 )
  dir = L"north northwest";

 else 
  dir = L"north";

 return dir;
}