/* * Asterisk -- A telephony toolkit for Linux. * * Distribute incoming calls to an agent pool. * * Copyright (C) 2002, James Sharp * * James Sharp <jsharp@psychoses.org> * * This program is free software, distributed under the terms of * the GNU General Public License */ #include <asterisk/lock.h> #include <asterisk/file.h> #include <asterisk/frame.h> #include <asterisk/logger.h> #include <asterisk/channel.h> #include <asterisk/pbx.h> #include <asterisk/module.h> #include <asterisk/translate.h> #include <asterisk/config.h> #include <unistd.h> #include <errno.h> #include <sys/ioctl.h> #include <string.h> #include <stdlib.h> #include <pthread.h> #include <sys/time.h> #include <netinet/in.h> static char *tdesc = "Automatic call distribution system"; static char *app = "ACD"; static char *synopsis = "Distribute an incoming call to a group of Asterisk devices"; static char *descrip = "Takes an incoming call and connects it to an Asterisk channel in a group of channels based on either \"Next available\" or \"Most idle\""; #define ACD_MAX_GROUPS 16 #define ACD_MAX_AGENTS_PERGROUP 48 #define ACD_CONFIG "acd.conf" STANDARD_LOCAL_USER; LOCAL_USER_DECL; static int acd_connect_to_acd_host(struct ast_config *); static int route_by_nextavail(struct ast_channel *, int); static int route_by_mostidle(struct ast_channel *, int); struct acd_agent { char *agent_device; time_t last_call_time; }; struct acd_agent_grouping { struct acd_agent agent[ACD_MAX_AGENTS_PERGROUP]; char *group_name; int defined_agents; char *routing_type; }; struct acd_agent_grouping acd_grouping[ACD_MAX_GROUPS]; static int acd_groups_defined; static char *acdgroups[ACD_MAX_GROUPS]; static int acd_exec(struct ast_channel *chan, void *data) { int dest_group, ret; dest_group = atoi((char *)data); ast_log(LOG_DEBUG,"Executing ACD acd_exec. Source channel %s. Destination group %i\n",chan->name,dest_group); /* Okay. Now we figure out what channel we need to route this call down to. */ /* One of two ways..."Next available" sends it down to the next non-busy channel */ /* "Most Idle" sends it down the channel for the agent that has been idle (non-busy) longest */ /* This is the "Next available" code */ if (!strcasecmp(acd_grouping[dest_group].routing_type,"nextavail")) ret = route_by_nextavail(chan, dest_group); /* if (!strcasecmp(acd_grouping[dest_group].routing_type,"mostidle")) ret = route_by_mostidle(chan, dest_group); */ return 0; } static int route_by_nextavail(struct ast_channel *chan, int dest_group) { struct ast_channel *destchan; struct ast_frame *f; int i, res; char *tech, *number; time_t t; /* Go through the list of agents in the destination group. Do an ast_request on each of them, if it returns NULL then the channel is definitely busy. */ /* If ast_request returns a channel, then it is probably not busy...but we'll have to check for that since it could possibly be busy */ while (chan->state == AST_STATE_UP) { for (i=0; i<acd_grouping[dest_group].defined_agents; i++) { tech = strdup(acd_grouping[dest_group].agent[i].agent_device); ast_log(LOG_DEBUG,"ast_request for %s\n",tech); number = strchr(tech,'/'); *number = '\0'; number++; destchan = ast_request(tech, chan->nativeformats, number); if (destchan == NULL) { ast_log(LOG_DEBUG,"ast_request for %s/%s returned NULL. Probably busy.\n",tech,number); free(tech); continue; } /* ast_request has given us a destination channel. Attempt a call down it. */ res = ast_call(destchan, number, 0); if (res) { /* If it returns an error, log it and go on to the next one */ ast_log(LOG_DEBUG, "ast call on acd peer returned %d\n", res); ast_hangup(destchan); continue; } f = ast_read(destchan); if (f->frametype == AST_FRAME_CONTROL) { switch(f->subclass) { case AST_CONTROL_ANSWER: case AST_CONTROL_RINGING: /* A valid channel. Connect the two */ time(&t); acd_grouping[dest_group].agent[i].last_call_time = t; res = ast_channel_make_compatible(chan, destchan); if (res < 0) { ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", chan->name, destchan->name); ast_hangup(destchan); return -1; } ast_bridge_call(chan,destchan,0); ast_hangup(destchan); return 0; break; default: /* Its busy or something's up. Go onto the next one */ continue; break; } } } ast_streamfile(chan,"acd-agent-busy",chan->language); ast_safe_sleep(chan,10000); } return -1; } int load_module(void) { struct ast_config *cfg; struct ast_variable *v; char *acdgroup; int i=0; int j=0; cfg = ast_load(ACD_CONFIG); if (!cfg) { ast_log(LOG_WARNING, "No such configuration file %s\n", ACD_CONFIG); return -1; } if (ast_variable_retrieve(cfg, "network", "hostname")) { acd_connect_to_acd_host(cfg); } acdgroup = ast_category_browse(cfg, NULL); /* Get all the ACD group names */ while(acdgroup) { if (strstr(acdgroup,"acdgroup")) { acdgroups[i] = strdup(acdgroup); i++; ast_log(LOG_DEBUG,"Registered ACD Group name %s\n",acdgroup); } acdgroup = ast_category_browse(cfg,acdgroup); } acd_groups_defined = i; /* Now that we've got the group names, browse through the "agent" IDs under each group context */ for (i=0;i<acd_groups_defined;i++) { j=0; acd_grouping[i].group_name = ast_variable_retrieve(cfg,acdgroups[i],"name"); acd_grouping[i].routing_type = ast_variable_retrieve(cfg,acdgroups[i],"routing"); if (acd_grouping[i].group_name == NULL) acd_grouping[i].group_name = strdup(acdgroups[i]); if (acd_grouping[i].routing_type == NULL) { ast_log(LOG_WARNING,"ACD Group \"%s\": No routing type defined. Should be \"nextavail\" or \"mostidle\"\n",acd_grouping[i].group_name); return -1; } if (strcasecmp(acd_grouping[i].routing_type,"nextavail") && strcasecmp(acd_grouping[i].routing_type,"mostidle")) { ast_log(LOG_WARNING,"ACD Group \"%s\": Routing type \"%s\". Should be \"nextavail\" or \"mostidle\"\n",acd_grouping[i].group_name,acd_grouping[i].routing_type); return -1; } v = ast_variable_browse(cfg,acdgroups[i]); while (v) { if (!strcasecmp(v->name,"agent")) { acd_grouping[i].agent[j].agent_device = strdup(v->value); ast_log(LOG_DEBUG,"Registered agent %s as part of ACD Group %s using Routing Type %s\n",acd_grouping[i].agent[j].agent_device, acd_grouping[i].group_name,acd_grouping[i].routing_type); j++; } v=v->next; } acd_grouping[i].defined_agents = j; } return ast_register_application(app, acd_exec, synopsis, descrip); } static int acd_connect_to_acd_host(struct ast_config *cfg) { return 0; } int unload_module(void) { int res; res = ast_unregister_application(app); return res; } int usecount(void) { return 0; } char *key() { return ASTERISK_GPL_KEY; } char *description(void) { return tdesc; }