From ce18e66db2ffb391aadee3307a6aee27595f3587 Mon Sep 17 00:00:00 2001 From: Mark Spencer Date: Thu, 17 Feb 2005 14:57:36 +0000 Subject: Add placeholder IVR application support (static version) git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@5040 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- app.c | 177 +++++++++++++++++++++++++++++++++++++++++++++++++ apps/Makefile | 4 ++ apps/app_ivrdemo.c | 112 +++++++++++++++++++++++++++++++ include/asterisk/app.h | 47 +++++++++++++ 4 files changed, 340 insertions(+) create mode 100755 apps/app_ivrdemo.c diff --git a/app.c b/app.c index 8cc7af17e..c1a02914d 100755 --- a/app.c +++ b/app.c @@ -1208,3 +1208,180 @@ int ast_record_review(struct ast_channel *chan, const char *playfile, const char cmd = 0; return cmd; } + +#define RES_UPONE (1 << 16) +#define RES_EXIT (1 << 17) +#define RES_REPEAT (1 << 18) +#define RES_RESTART ((1 << 19) | RES_REPEAT) + +static int ast_ivr_menu_run_internal(struct ast_channel *chan, struct ast_ivr_menu *menu, void *cbdata); +static int ivr_dispatch(struct ast_channel *chan, struct ast_ivr_option *option, char *exten, void *cbdata) +{ + int res; + switch(option->action) { + case AST_ACTION_UPONE: + return RES_UPONE; + case AST_ACTION_EXIT: + return RES_EXIT | (((unsigned long)(option->adata)) & 0xffff); + case AST_ACTION_REPEAT: + return RES_REPEAT | (((unsigned long)(option->adata)) & 0xffff); + case AST_ACTION_RESTART: + return RES_RESTART ; + case AST_ACTION_NOOP: + return 0; + case AST_ACTION_BACKGROUND: + res = ast_streamfile(chan, (char *)option->adata, chan->language); + if (!res) { + res = ast_waitstream(chan, AST_DIGIT_ANY); + } else { + ast_log(LOG_NOTICE, "Unable to find file '%s'!\n", (char *)option->adata); + res = 0; + } + return res; + case AST_ACTION_PLAYBACK: + res = ast_streamfile(chan, (char *)option->adata, chan->language); + if (!res) { + res = ast_waitstream(chan, ""); + } else { + ast_log(LOG_NOTICE, "Unable to find file '%s'!\n", (char *)option->adata); + res = 0; + } + return res; + case AST_ACTION_MENU: + res = ast_ivr_menu_run_internal(chan, (struct ast_ivr_menu *)option->adata, cbdata); + return res; + case AST_ACTION_CALLBACK: + case AST_ACTION_PLAYLIST: + case AST_ACTION_TRANSFER: + case AST_ACTION_WAITOPTION: + ast_log(LOG_NOTICE, "Unimplemented dispatch function %d, ignoring!\n", option->action); + return 0; + default: + ast_log(LOG_NOTICE, "Unknown dispatch function %d, ignoring!\n", option->action); + return 0; + }; + return -1; +} + +static int option_exists(struct ast_ivr_menu *menu, char *option) +{ + int x; + for (x=0;menu->options[x].option;x++) + if (!strcasecmp(menu->options[x].option, option)) + return x; + return -1; +} + +static int option_matchmore(struct ast_ivr_menu *menu, char *option) +{ + int x; + for (x=0;menu->options[x].option;x++) + if ((!strncasecmp(menu->options[x].option, option, strlen(option))) && + (menu->options[x].option[strlen(option)])) + return x; + return -1; +} + +static int read_newoption(struct ast_channel *chan, struct ast_ivr_menu *menu, char *exten, int maxexten) +{ + int res=0; + int ms; + while(option_matchmore(menu, exten)) { + ms = chan->pbx ? chan->pbx->dtimeout : 5000; + if (strlen(exten) >= maxexten - 1) + break; + res = ast_waitfordigit(chan, ms); + if (res < 1) + break; + exten[strlen(exten) + 1] = '\0'; + exten[strlen(exten)] = res; + } + return res > 0 ? 0 : res; +} + +static int ast_ivr_menu_run_internal(struct ast_channel *chan, struct ast_ivr_menu *menu, void *cbdata) +{ + /* Execute an IVR menu structure */ + int res=0; + int pos = 0; + int retries = 0; + char exten[AST_MAX_EXTENSION] = "s"; + if (option_exists(menu, "s") < 0) { + strcpy(exten, "g"); + if (option_exists(menu, "g") < 0) { + ast_log(LOG_WARNING, "No 's' nor 'g' extension in menu '%s'!\n", menu->title); + return -1; + } + } + while(!res) { + while(menu->options[pos].option) { + if (!strcasecmp(menu->options[pos].option, exten)) { + res = ivr_dispatch(chan, menu->options + pos, exten, cbdata); + if (res < 0) + break; + else if (res & RES_UPONE) + return 0; + else if (res & RES_EXIT) + return res; + else if (res & RES_REPEAT) { + int maxretries = res & 0xffff; + if (res & RES_RESTART) + retries = 0; + else + retries++; + if (!maxretries) + maxretries = 3; + if ((maxretries > 0) && (retries >= maxretries)) + return -2; + else { + if (option_exists(menu, "g") > -1) + strcpy(exten, "g"); + else if (option_exists(menu, "s") > -1) + strcpy(exten, "s"); + } + pos=0; + } else if (res && strchr(AST_DIGIT_ANY, res)) { + ast_log(LOG_DEBUG, "Got start of extension, %c\n", res); + exten[1] = '\0'; + exten[0] = res; + if ((res = read_newoption(chan, menu, exten, sizeof(exten)))) + break; + if (!option_exists(menu, exten)) { + if (option_exists(menu, "i")) { + strcpy(exten, "i"); + pos = 0; + continue; + } else { + ast_log(LOG_DEBUG, "Aborting on invalid entry, with no 'i' option!\n"); + res = -2; + break; + } + } else { + pos = 0; + continue; + } + } + } + pos++; + } + ast_log(LOG_DEBUG, "Stopping option '%s', res is %d\n", exten, res); + pos = 0; + if (!strcasecmp(exten, "s")) + strcpy(exten, "g"); + else if (strcasecmp(exten, "t")) + strcpy(exten, "t"); + else + break; + } + return res; +} + +int ast_ivr_menu_run(struct ast_channel *chan, struct ast_ivr_menu *menu, void *cbdata) +{ + int res; + res = ast_ivr_menu_run_internal(chan, menu, cbdata); + /* Hide internal coding */ + if (res > 0) + res = 0; + return res; +} diff --git a/apps/Makefile b/apps/Makefile index d66ff3149..a8a4e3b62 100755 --- a/apps/Makefile +++ b/apps/Makefile @@ -45,6 +45,10 @@ endif #APPS+=app_sql_postgres.so #APPS+=app_sql_odbc.so #APPS+=app_rpt.so +# +# Experimental things +# +#APPS+=app_ivrdemo.so APPS+=$(shell if [ -f /usr/include/linux/zaptel.h ]; then echo "app_zapras.so app_meetme.so app_flash.so app_zapbarge.so app_zapscan.so" ; fi) APPS+=$(shell if [ -f /usr/local/include/zaptel.h ]; then echo "app_zapras.so app_meetme.so app_flash.so app_zapbarge.so app_zapscan.so" ; fi) diff --git a/apps/app_ivrdemo.c b/apps/app_ivrdemo.c new file mode 100755 index 000000000..744c6755d --- /dev/null +++ b/apps/app_ivrdemo.c @@ -0,0 +1,112 @@ +/* + * Asterisk -- A telephony toolkit for Linux. + * + * Skeleton application + * + * Copyright (C) 1999, Mark Spencer + * + * Mark Spencer + * + * This program is free software, distributed under the terms of + * the GNU General Public License + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static char *tdesc = "IVR Demo Application"; +static char *app = "IVRDemo"; +static char *synopsis = +" This is a skeleton application that shows you the basic structure to create your\n" +"own asterisk applications and demonstrates the IVR demo.\n"; + +static int ivr_demo_func(struct ast_channel *chan, void *data) +{ + ast_verbose("IVR Demo, data is %s!\n", (char *)data); + return 0; +} + +static struct ast_ivr_menu ivr_submenu = { + "IVR Demo Sub Menu", 0, { + { "s", AST_ACTION_BACKGROUND, "demo-abouttotry" }, + { "1", AST_ACTION_PLAYBACK, "digits/1" }, + { "1", AST_ACTION_PLAYBACK, "digits/1" }, + { "1", AST_ACTION_RESTART }, + { "2", AST_ACTION_PLAYLIST, "digits/2;digits/3" }, + { "*", AST_ACTION_REPEAT }, + { "#", AST_ACTION_UPONE }, + { NULL }, +}}; + +static struct ast_ivr_menu ivr_demo = { + "IVR Demo Main Menu", 0, { + { "s", AST_ACTION_BACKGROUND, "demo-congrats" }, + { "g", AST_ACTION_BACKGROUND, "demo-instruct" }, + { "g", AST_ACTION_WAITOPTION }, + { "1", AST_ACTION_PLAYBACK, "digits/1" }, + { "1", AST_ACTION_RESTART }, + { "2", AST_ACTION_MENU, &ivr_submenu }, + { "2", AST_ACTION_RESTART }, + { "i", AST_ACTION_PLAYBACK, "invalid" }, + { "i", AST_ACTION_REPEAT, (void *)2 }, + { "#", AST_ACTION_EXIT }, + { NULL }, +}}; + +STANDARD_LOCAL_USER; + +LOCAL_USER_DECL; + +static int skel_exec(struct ast_channel *chan, void *data) +{ + int res=0; + struct localuser *u; + if (!data) { + ast_log(LOG_WARNING, "skel requires an argument (filename)\n"); + return -1; + } + LOCAL_USER_ADD(u); + /* Do our thing here */ + if (chan->_state != AST_STATE_UP) + res = ast_answer(chan); + if (!res) + res = ast_ivr_menu_run(chan, &ivr_demo, data); + LOCAL_USER_REMOVE(u); + return res; +} + +int unload_module(void) +{ + STANDARD_HANGUP_LOCALUSERS; + return ast_unregister_application(app); +} + +int load_module(void) +{ + return ast_register_application(app, skel_exec, synopsis, tdesc); +} + +char *description(void) +{ + return tdesc; +} + +int usecount(void) +{ + int res; + STANDARD_USECOUNT(res); + return res; +} + +char *key() +{ + return ASTERISK_GPL_KEY; +} diff --git a/include/asterisk/app.h b/include/asterisk/app.h index 2761c1ef3..f94d75348 100755 --- a/include/asterisk/app.h +++ b/include/asterisk/app.h @@ -18,6 +18,53 @@ #if defined(__cplusplus) || defined(c_plusplus) extern "C" { #endif + +/* IVR stuff */ + +/* Callback function for IVR, returns 0 on completion, -1 on hangup or digit if + interrupted */ +typedef int (*ast_ivr_callback)(struct ast_channel *chan, char *option, void *cbdata); + +typedef enum { + AST_ACTION_UPONE, /* adata is unused */ + AST_ACTION_EXIT, /* adata is the return value for ast_ivr_menu_run if channel was not hungup */ + AST_ACTION_CALLBACK, /* adata is an ast_ivr_callback */ + AST_ACTION_PLAYBACK, /* adata is file to play */ + AST_ACTION_BACKGROUND, /* adata is file to play */ + AST_ACTION_PLAYLIST, /* adata is list of files, separated by ; to play */ + AST_ACTION_MENU, /* adata is a pointer to an ast_ivr_menu */ + AST_ACTION_REPEAT, /* adata is max # of repeats, cast to a pointer */ + AST_ACTION_RESTART, /* adata is like repeat, but resets repeats to 0 */ + AST_ACTION_TRANSFER, /* adata is a string with exten[@context] */ + AST_ACTION_WAITOPTION, /* adata is a timeout, or 0 for defaults */ + AST_ACTION_NOOP, /* adata is unused */ +} ast_ivr_action; + +struct ast_ivr_option { + char *option; + ast_ivr_action action; + void *adata; +}; + +/* + Special "options" are: + "s" - "start here (one time greeting)" + "g" - "greeting/instructions" + "t" - "timeout" + "h" - "hangup" + "i" - "invalid selection" + +*/ + +struct ast_ivr_menu { + char *title; /* Title of menu */ + unsigned int flags; /* Flags */ + struct ast_ivr_option options[]; /* All options */ +}; + +/*! Runs an IVR menu, returns 0 on successful completion, -1 on hangup, or -2 on user error in menu */ +extern int ast_ivr_menu_run(struct ast_channel *c, struct ast_ivr_menu *menu, void *cbdata); + /*! Plays a stream and gets DTMF data from a channel */ /*! * \param c Which channel one is interacting with -- cgit v1.2.3