/****************************************************************************** This is the weather by ZIP code demo in which the user speaks the digits of the ZIP code whose weather forecast he wants 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; const int ZipGrammarId = 22; // We use off the wall numbers for the grammar IDs const int QuitGrammarId = 67; // to make the point that they are completely arbitrary const int ReqdConfidence = 6500; // Minimum confidence value to accept recognition // Various prompts that we speak static wchar_t wszPrompt[] = L"Please tell me the ZIP code for the town whose weather " L"forecast you want to hear."; static wchar_t wszSayWhat[] = L"I'm sorry, but I didn't get that."; 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); // Function to apologize when we fail to recognize the caller's request void NoRecognition(Recognizer *, Synthesizer *); // 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" }; /****************************************************************************** Each digit in the "phrase" recognized is a property of the recognition. We enumerate the propery names here. ******************************************************************************/ LPCSTR pszProperties[5] = { "Digit1", "Digit2", "Digit3", "Digit4", "Digit5" }; /****************************************************************************** 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, conf; char szZip[6] = { 0 }; bool done; HANDLE hEvent; Recognizer *prec; Synthesizer *psyn; HTTPCONTEXT *pctx; // 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 recognizer // Load a grammar that handles the request for the ZIP code // Load another that handles the request to quit the application prec = call.getRecognizer(); prec->loadGrammar("ZIP", ZipGrammarId); prec->loadGrammar("Quit", QuitGrammarId); // Get an instance of the synthesizer to speak the greeting and wait for it psyn = call.getSynthesizer(); psyn->speak(wszWelcome); psyn->wait(); done = false; while ( !call.isDisconnected() && !done ) { // Start listening first and then prompt the caller prec->listen(); psyn->speak(wszPrompt); // Wait for the recognizer to tell us how it did. // We will wait at most 10 seconds for the user to begin to speak. // In no case will we wait more than 30 seconds. conf = prec->wait(10000, 30000); // Stop listening prec->stopListening(); // Dump the state of the recognition for debugging prec->dump(); // On an error or disconnect we are done if ( conf < 0 ) done = true; // We need at least minimum confidence to proceed. In this application we use // 65% as a confidence threshold for recognition. You can use whatever makes // sense in your application. A more robust application might have two thresholds. // Confidence scores below the lower number might cause the application to // prompt again, confidence scores above the higher number might cause the // application to accept the recognition, and scores between the two numbers // might require confirmation. if ( conf >= ReqdConfidence ) { // If we recognized a phrase in the Quit grammar then we are done if ( prec->getGrammarId() == QuitGrammarId ) done = true; // Otherwise we need a five digit ZIP code else { memset(szZip, 0, sizeof(szZip)); for (i = 0; i < 5; i++ ) prec->getPropertyText(pszProperties[i], &szZip[i], 2); if ( strlen(szZip) != 5 ) conf = 0; } } // Quit if the user said good bye if ( done ) break; // If we don't have the required confidence // we apologize and then try again else if ( conf < ReqdConfidence ) { NoRecognition(prec, psyn); continue; } // 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 connection 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); } } /****************************************************************************** We apologize if we don't understand the user ******************************************************************************/ void NoRecognition(Recognizer *prec, Synthesizer *psyn) { prec->reset(); prec->listen(); psyn->speak(wszSayWhat); psyn->wait(); } /****************************************************************************** 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; }