From 2ee28d0ccf59240a3dbd9a1040a6b877371db730 Mon Sep 17 00:00:00 2001 From: Jim Dixon Date: Mon, 21 Jun 2004 04:24:50 +0000 Subject: [PATCH] Majorly updated app_rpt.c allowing linking of repeaters/remote bases using (IAX2) and supporting remote base nodes as well (also added visual documentation in rpt_flow.pdf) git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@3252 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- apps/app_rpt.c | 1873 +++++++++++++++++++++++++++++++++------------ apps/rpt_flow.pdf | Bin 0 -> 51935 bytes 2 files changed, 1384 insertions(+), 489 deletions(-) create mode 100755 apps/rpt_flow.pdf diff --git a/apps/app_rpt.c b/apps/app_rpt.c index e6973725c8..a4992cb1e3 100755 --- a/apps/app_rpt.c +++ b/apps/app_rpt.c @@ -3,11 +3,11 @@ * Asterisk -- A telephony toolkit for Linux. * * Radio Repeater / Remote Base program - * version 0.2 5/30/04 + * version 0.4 6/19/04 * - * Copyright (C) 2002-2004, Jim Dixon + * Copyright (C) 2002-2004, Jim Dixon, WB6NIL * - * Jim Dixon + * Jim Dixon, WB6NIL * * This program is free software, distributed under the terms of * the GNU General Public License @@ -15,10 +15,13 @@ * Repeater / Remote Functions: * "Simple" Mode: * - autopatch access, # - autopatch hangup * Normal mode: - * *0 - autopatch access - * *1 - remote base off - * *2 - remote base monitor - * *3 - remote base tranceive + * *0 - autopatch off + * *1XXX - remote link off + * *2XXX - remote link monitor + * *3XXX - remote link tranceive + * *4XXX - remote link command mode + * *6 - autopatch access/send (*) + * *7 - system status * *8 - force ID * *9 - system reset * @@ -27,29 +30,44 @@ */ /* number of digits for function after *. Must be at least 1 */ -#define FUNCTION_LEN 1 +#define FUNCTION_LEN 4 +/* string containing all of the 1 digit functions */ +#define SHORTFUNCS "056789" /* maximum digits in DTMF buffer, and seconds after * for DTMF command timeout */ #define MAXDTMF 10 #define DTMF_TIMEOUT 3 +#define NODES "nodes" + +#define MAXCONNECTTIME 5000 + +#define MAXNODESTR 300 + enum {REM_OFF,REM_MONITOR,REM_TX}; +enum{ID,PROC,TERM,COMPLETE,UNKEY,REMDISC,REMALREADY,REMNOTFOUND,REMGO, + CONNECTED,CONNFAIL,STATUS}; + +#include #include #include #include #include +#include #include #include #include #include #include +#include +#include #include #include #include #include -#include +#include #include #include #include @@ -61,15 +79,21 @@ enum {REM_OFF,REM_MONITOR,REM_TX}; #include #include #include - -#ifdef __linux__ #include -#else -#include -#endif /* __linux__ */ -static char *tdesc = "Radio Repeater / Remote Base version 0.2 05/30/2004"; +static char *tdesc = "Radio Repeater / Remote Base version 0.3 06/18/2004"; +static char *app = "Rpt"; + +static char *synopsis = "Radio Repeater/Remote Base Control System"; + +static char *descrip = +" Rpt(sysname): Radio Remote Link or Remote Base Link Endpoint Process.\n"; + static int debug = 0; +static int nrpts = 0; + +struct ast_config *cfg; + STANDARD_LOCAL_USER; LOCAL_USER_DECL; @@ -81,236 +105,351 @@ LOCAL_USER_DECL; static pthread_t rpt_master_thread; +struct rpt; + +struct rpt_link +{ + struct rpt_link *next; + struct rpt_link *prev; + char mode; /* 1 if in tx mode */ + char isremote; + char name[MAXNODESTR]; /* identifier (routing) string */ + char lasttx; + char lastrx; + char connected; + char outbound; + long elaptime; + struct ast_channel *chan; + struct ast_channel *pchan; +} ; + +struct rpt_tele +{ + struct rpt_tele *next; + struct rpt_tele *prev; + struct rpt *rpt; + int mode; + struct rpt_link mylink; + pthread_t threadid; +} ; + static struct rpt { char *name; + ast_mutex_t lock; char *rxchanname; char *txchanname; - char *rem_rxchanname; - char *rem_txchanname; char *ourcontext; char *ourcallerid; char *acctcode; - char *idrecording; + char *ident; char *tonezone; + struct rpt_link links; int hangtime; int totime; int idtime; + char exttx; + char localtx; char remoterx; char remotetx; - char remotemode; + char remoteon; char simple; - struct ast_channel *rxchannel,*txchannel,*rem_rxchannel; - struct ast_channel *rem_txchannel,*pchannel; - int tailtimer,totimer,idtimer,txconf,pconf,callmode,cidx; - pthread_t rpt_id_thread,rpt_term_thread,rpt_proc_thread,rpt_call_thread; - char mydtmf,iding,terming,teleing,comping,procing; + char remote; + char dtmfbuf[MAXDTMF]; + char rem_dtmfbuf[MAXDTMF]; + char cmdnode[50]; + struct ast_channel *rxchannel,*txchannel; + struct ast_channel *pchannel,*txpchannel; + struct rpt_tele tele; + pthread_t rpt_call_thread,rpt_thread; + time_t rem_dtmf_time; + int tailtimer,totimer,idtimer,txconf,conf,callmode,cidx; + int dtmfidx,rem_dtmfidx; + char mydtmf; char exten[AST_MAX_EXTENSION]; } rpt_vars[MAXRPTS]; - -static void *rpt_id(void *this) -{ -ZT_CONFINFO ci; /* conference info */ -int res; -struct rpt *myrpt = (struct rpt *)this; -struct ast_channel *mychannel; - - /* allocate a pseudo-channel thru asterisk */ - mychannel = ast_request("zap",AST_FORMAT_SLINEAR,"pseudo"); - if (!mychannel) - { - fprintf(stderr,"rpt:Sorry unable to obtain pseudo channel\n"); - pthread_exit(NULL); - } - /* make a conference for the tx */ - ci.chan = 0; - ci.confno = myrpt->txconf; /* use the tx conference */ - ci.confmode = ZT_CONF_CONFANN; - /* first put the channel on the conference in announce mode */ - if (ioctl(mychannel->fds[0],ZT_SETCONF,&ci) == -1) - { - ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n"); - pthread_exit(NULL); - } - myrpt->iding = 1; - ast_stopstream(mychannel); - res = ast_streamfile(mychannel, myrpt->idrecording, mychannel->language); - if (!res) - res = ast_waitstream(mychannel, ""); - else { - ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name); - res = 0; - } - myrpt->iding = 0; - ast_stopstream(mychannel); - ast_hangup(mychannel); - pthread_exit(NULL); -} - -static void *rpt_proc(void *this) -{ -ZT_CONFINFO ci; /* conference info */ -int res; -struct rpt *myrpt = (struct rpt *)this; -struct ast_channel *mychannel; - - /* wait a little bit */ - usleep(1500000); - /* allocate a pseudo-channel thru asterisk */ - mychannel = ast_request("zap",AST_FORMAT_SLINEAR,"pseudo"); - if (!mychannel) - { - fprintf(stderr,"rpt:Sorry unable to obtain pseudo channel\n"); - pthread_exit(NULL); - } - /* make a conference for the tx */ - ci.chan = 0; - ci.confno = myrpt->txconf; /* use the tx conference */ - ci.confmode = ZT_CONF_CONFANN; - /* first put the channel on the conference in announce mode */ - if (ioctl(mychannel->fds[0],ZT_SETCONF,&ci) == -1) - { - ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n"); - pthread_exit(NULL); - } - myrpt->procing = 1; - ast_stopstream(mychannel); - res = ast_streamfile(mychannel, "callproceeding", mychannel->language); - if (!res) - res = ast_waitstream(mychannel, ""); - else { - ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name); - res = 0; - } - myrpt->procing = 0; - ast_stopstream(mychannel); - ast_hangup(mychannel); - pthread_exit(NULL); -} - -static void *rpt_term(void *this) +static void *rpt_tele_thread(void *this) { ZT_CONFINFO ci; /* conference info */ -int res; -struct rpt *myrpt = (struct rpt *)this; -struct ast_channel *mychannel; +int res = 0,hastx,imdone = 0; +struct rpt_tele *mytele = (struct rpt_tele *)this; +struct rpt *myrpt; +struct rpt_link *l,*m,linkbase; +struct ast_channel *mychannel; - /* wait a little bit */ - usleep(1500000); + /* get a pointer to myrpt */ + myrpt = mytele->rpt; /* allocate a pseudo-channel thru asterisk */ mychannel = ast_request("zap",AST_FORMAT_SLINEAR,"pseudo"); if (!mychannel) { fprintf(stderr,"rpt:Sorry unable to obtain pseudo channel\n"); + ast_mutex_lock(&myrpt->lock); + remque((struct qelem *)mytele); + ast_mutex_unlock(&myrpt->lock); + free(mytele); pthread_exit(NULL); } /* make a conference for the tx */ ci.chan = 0; - ci.confno = myrpt->txconf; /* use the tx conference */ + ci.confno = myrpt->conf; /* use the tx conference */ ci.confmode = ZT_CONF_CONFANN; /* first put the channel on the conference in announce mode */ if (ioctl(mychannel->fds[0],ZT_SETCONF,&ci) == -1) { ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n"); + ast_mutex_lock(&myrpt->lock); + remque((struct qelem *)mytele); + ast_mutex_unlock(&myrpt->lock); + free(mytele); + ast_hangup(mychannel); pthread_exit(NULL); } - myrpt->terming = 1; - ast_stopstream(mychannel); - res = ast_streamfile(mychannel, "callterminated", mychannel->language); - if (!res) - res = ast_waitstream(mychannel, ""); - else { - ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name); - res = 0; - } - myrpt->terming = 0; ast_stopstream(mychannel); - ast_hangup(mychannel); - pthread_exit(NULL); -} - -static void *rpt_complete(void *this) -{ -ZT_CONFINFO ci; /* conference info */ -int res; -struct rpt *myrpt = (struct rpt *)this; -struct ast_channel *mychannel; - - /* wait a little bit */ - usleep(1000000); - /* allocate a pseudo-channel thru asterisk */ - mychannel = ast_request("zap",AST_FORMAT_SLINEAR,"pseudo"); - if (!mychannel) + switch(mytele->mode) { - fprintf(stderr,"rpt:Sorry unable to obtain pseudo channel\n"); - pthread_exit(NULL); + case ID: + res = ast_streamfile(mychannel, myrpt->ident, mychannel->language); + break; + case PROC: + /* wait a little bit longer */ + usleep(1500000); + res = ast_streamfile(mychannel, "rpt/callproceeding", mychannel->language); + break; + case TERM: + /* wait a little bit longer */ + usleep(1500000); + res = ast_streamfile(mychannel, "rpt/callterminated", mychannel->language); + break; + case COMPLETE: + /* wait a little bit */ + usleep(1000000); + res = ast_streamfile(mychannel, "rpt/functioncomplete", mychannel->language); + break; + case UNKEY: + /* wait a little bit */ + usleep(1000000); + hastx = 0; + l = myrpt->links.next; + if (l != &myrpt->links) + { + ast_mutex_lock(&myrpt->lock); + while(l != &myrpt->links) + { + if (l->mode) hastx++; + l = l->next; + } + ast_mutex_unlock(&myrpt->lock); + res = ast_streamfile(mychannel, + ((!hastx) ? "rpt/remote_monitor" : "rpt/remote_tx"), + mychannel->language); + if (!res) + res = ast_waitstream(mychannel, ""); + else + ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name); + ast_stopstream(mychannel); + } + /* if in remote cmd mode, indicate it */ + if (myrpt->cmdnode[0]) + { + ast_safe_sleep(mychannel,200); + res = ast_streamfile(mychannel, "rpt/remote_cmd", mychannel->language); + if (!res) + res = ast_waitstream(mychannel, ""); + else + ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name); + ast_stopstream(mychannel); + } + imdone = 1; + break; + case REMDISC: + /* wait a little bit */ + usleep(1000000); + res = ast_streamfile(mychannel, "rpt/node", mychannel->language); + if (!res) + res = ast_waitstream(mychannel, ""); + else + ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name); + ast_stopstream(mychannel); + ast_say_character_str(mychannel,mytele->mylink.name,NULL,mychannel->language); + res = ast_streamfile(mychannel, ((mytele->mylink.connected) ? + "rpt/remote_disc" : "rpt/remote_busy"), mychannel->language); + break; + case REMALREADY: + /* wait a little bit */ + usleep(1000000); + res = ast_streamfile(mychannel, "rpt/remote_already", mychannel->language); + break; + case REMNOTFOUND: + /* wait a little bit */ + usleep(1000000); + res = ast_streamfile(mychannel, "rpt/remote_notfound", mychannel->language); + break; + case REMGO: + /* wait a little bit */ + usleep(1000000); + res = ast_streamfile(mychannel, "rpt/remote_go", mychannel->language); + break; + case CONNECTED: + /* wait a little bit */ + usleep(1000000); + res = ast_streamfile(mychannel, "rpt/node", mychannel->language); + if (!res) + res = ast_waitstream(mychannel, ""); + else + ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name); + ast_stopstream(mychannel); + ast_say_character_str(mychannel,mytele->mylink.name,NULL,mychannel->language); + res = ast_streamfile(mychannel, "rpt/connected", mychannel->language); + break; + case CONNFAIL: + res = ast_streamfile(mychannel, "rpt/node", mychannel->language); + if (!res) + res = ast_waitstream(mychannel, ""); + else + ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name); + ast_stopstream(mychannel); + ast_say_character_str(mychannel,mytele->mylink.name,NULL,mychannel->language); + res = ast_streamfile(mychannel, "rpt/connection_failed", mychannel->language); + break; + case STATUS: + /* wait a little bit */ + usleep(1000000); + hastx = 0; + linkbase.next = &linkbase; + linkbase.prev = &linkbase; + ast_mutex_lock(&myrpt->lock); + /* make our own list of links */ + l = myrpt->links.next; + while(l != &myrpt->links) + { + m = malloc(sizeof(struct rpt_link)); + if (!m) + { + ast_log(LOG_WARNING, "Cannot alloc memory on %s\n", mychannel->name); + pthread_exit(NULL); + } + memcpy(m,l,sizeof(struct rpt_link)); + m->next = m->prev = NULL; + insque((struct qelem *)m,(struct qelem *)linkbase.next); + l = l->next; + } + ast_mutex_unlock(&myrpt->lock); + res = ast_streamfile(mychannel, "rpt/node", mychannel->language); + if (!res) + res = ast_waitstream(mychannel, ""); + else + ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name); + ast_stopstream(mychannel); + ast_say_character_str(mychannel,myrpt->name,NULL,mychannel->language); + if (!res) + res = ast_waitstream(mychannel, ""); + else + ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name); + ast_stopstream(mychannel); + if (myrpt->callmode) + { + hastx = 1; + res = ast_streamfile(mychannel, "rpt/autopatch_on", mychannel->language); + if (!res) + res = ast_waitstream(mychannel, ""); + else + ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name); + ast_stopstream(mychannel); + } + l = linkbase.next; + while(l != &linkbase) + { + hastx = 1; + res = ast_streamfile(mychannel, "rpt/node", mychannel->language); + if (!res) + res = ast_waitstream(mychannel, ""); + else + ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name); + ast_stopstream(mychannel); + ast_say_character_str(mychannel,l->name,NULL,mychannel->language); + if (!res) + res = ast_waitstream(mychannel, ""); + else + ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name); + ast_stopstream(mychannel); + res = ast_streamfile(mychannel, ((l->mode) ? + "rpt/tranceive" : "rpt/monitor"), mychannel->language); + if (!res) + res = ast_waitstream(mychannel, ""); + else + ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name); + ast_stopstream(mychannel); + l = l->next; + } + if (!hastx) + { + res = ast_streamfile(mychannel, "rpt/repeat_only", mychannel->language); + if (!res) + res = ast_waitstream(mychannel, ""); + else + ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name); + ast_stopstream(mychannel); + } + /* destroy our local link queue */ + l = linkbase.next; + while(l != &linkbase) + { + m = l; + l = l->next; + remque((struct qelem *)m); + free(m); + } + imdone = 1; + break; } - /* make a conference for the tx */ - ci.chan = 0; - ci.confno = myrpt->txconf; /* use the tx conference */ - ci.confmode = ZT_CONF_CONFANN; - /* first put the channel on the conference in announce mode */ - if (ioctl(mychannel->fds[0],ZT_SETCONF,&ci) == -1) + if (!imdone) { - ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n"); - pthread_exit(NULL); - } - myrpt->comping = 1; - ast_stopstream(mychannel); - res = ast_streamfile(mychannel, "functioncomplete", mychannel->language); - if (!res) - res = ast_waitstream(mychannel, ""); - else { - ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name); - res = 0; + if (!res) + res = ast_waitstream(mychannel, ""); + else { + ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name); + res = 0; + } } - myrpt->comping = 0; ast_stopstream(mychannel); + ast_mutex_lock(&myrpt->lock); + remque((struct qelem *)mytele); + ast_mutex_unlock(&myrpt->lock); + free(mytele); ast_hangup(mychannel); pthread_exit(NULL); } -static void *rpt_remote_telemetry(void *this) +static void rpt_telemetry(struct rpt *myrpt,int mode,struct rpt_link *mylink) { -ZT_CONFINFO ci; /* conference info */ -int res; -struct rpt *myrpt = (struct rpt *)this; -struct ast_channel *mychannel; +struct rpt_tele *tele; +pthread_attr_t attr; - /* wait a little bit */ - usleep(1000000); - /* allocate a pseudo-channel thru asterisk */ - mychannel = ast_request("zap",AST_FORMAT_SLINEAR,"pseudo"); - if (!mychannel) + tele = malloc(sizeof(struct rpt_tele)); + if (!tele) { - fprintf(stderr,"rpt:Sorry unable to obtain pseudo channel\n"); + ast_log(LOG_WARNING, "Unable to allocate memory\n"); pthread_exit(NULL); + return; } - /* make a conference for the tx */ - ci.chan = 0; - ci.confno = myrpt->txconf; /* use the tx conference */ - ci.confmode = ZT_CONF_CONFANN; - /* first put the channel on the conference in announce mode */ - if (ioctl(mychannel->fds[0],ZT_SETCONF,&ci) == -1) + /* zero it out */ + memset((char *)tele,0,sizeof(struct rpt_tele)); + tele->rpt = myrpt; + tele->mode = mode; + ast_mutex_lock(&myrpt->lock); + memset(&tele->mylink,0,sizeof(struct rpt_link)); + if (mylink) { - ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n"); - pthread_exit(NULL); - } - myrpt->teleing = 1; - ast_stopstream(mychannel); - res = ast_streamfile(mychannel, - ((myrpt->remotemode == REM_MONITOR) ? "remote_monitor" : "remote_tx"), - mychannel->language); - if (!res) - res = ast_waitstream(mychannel, ""); - else { - ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name); - res = 0; - } - myrpt->teleing = 0; - ast_hangup(mychannel); - pthread_exit(NULL); + memcpy(&tele->mylink,mylink,sizeof(struct rpt_link)); + } + insque((struct qelem *)tele,(struct qelem *)myrpt->tele.next); + ast_mutex_unlock(&myrpt->lock); + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_create(&tele->threadid,&attr,rpt_tele_thread,(void *) tele); + return; } static void *rpt_call(void *this) @@ -331,7 +470,7 @@ struct ast_channel *mychannel,*genchannel; pthread_exit(NULL); } ci.chan = 0; - ci.confno = myrpt->pconf; /* use the pseudo conference */ + ci.confno = myrpt->conf; /* use the pseudo conference */ ci.confmode = ZT_CONF_REALANDPSEUDO | ZT_CONF_TALKER | ZT_CONF_LISTENER | ZT_CONF_PSEUDO_TALKER | ZT_CONF_PSEUDO_LISTENER; /* first put the channel on the conference */ @@ -351,7 +490,7 @@ struct ast_channel *mychannel,*genchannel; pthread_exit(NULL); } ci.chan = 0; - ci.confno = myrpt->pconf; + ci.confno = myrpt->conf; ci.confmode = ZT_CONF_REALANDPSEUDO | ZT_CONF_TALKER | ZT_CONF_LISTENER | ZT_CONF_PSEUDO_TALKER | ZT_CONF_PSEUDO_LISTENER; /* first put the channel on the conference */ @@ -410,7 +549,9 @@ struct ast_channel *mychannel,*genchannel; { ast_hangup(mychannel); ast_hangup(genchannel); + ast_mutex_lock(&myrpt->lock); myrpt->callmode = 0; + ast_mutex_unlock(&myrpt->lock); pthread_exit(NULL); } if (res == 0) continue; @@ -419,7 +560,9 @@ struct ast_channel *mychannel,*genchannel; { ast_hangup(mychannel); ast_hangup(genchannel); + ast_mutex_lock(&myrpt->lock); myrpt->callmode = 0; + ast_mutex_unlock(&myrpt->lock); pthread_exit(NULL); } if ((f->frametype == AST_FRAME_CONTROL) && @@ -428,7 +571,9 @@ struct ast_channel *mychannel,*genchannel; ast_frfree(f); ast_hangup(mychannel); ast_hangup(genchannel); + ast_mutex_lock(&myrpt->lock); myrpt->callmode = 0; + ast_mutex_unlock(&myrpt->lock); pthread_exit(NULL); } ast_frfree(f); @@ -440,7 +585,9 @@ struct ast_channel *mychannel,*genchannel; { ast_hangup(mychannel); ast_hangup(genchannel); + ast_mutex_lock(&myrpt->lock); myrpt->callmode = 0; + ast_mutex_unlock(&myrpt->lock); pthread_exit(NULL); } if (myrpt->ourcallerid && *myrpt->ourcallerid) @@ -459,18 +606,22 @@ struct ast_channel *mychannel,*genchannel; ast_log(LOG_WARNING, "Unable to start PBX!!\n"); ast_hangup(mychannel); ast_hangup(genchannel); + ast_mutex_lock(&myrpt->lock); myrpt->callmode = 0; + ast_mutex_unlock(&myrpt->lock); pthread_exit(NULL); } + ast_mutex_lock(&myrpt->lock); myrpt->callmode = 3; - while(myrpt->callmode) { if ((!mychannel->pvt) && (myrpt->callmode != 4)) { myrpt->callmode = 4; + ast_mutex_unlock(&myrpt->lock); /* start congestion tone */ tone_zone_play_tone(genchannel->fds[0],ZT_TONE_CONGESTION); + ast_mutex_lock(&myrpt->lock); } if (myrpt->mydtmf) { @@ -481,122 +632,480 @@ struct ast_channel *mychannel,*genchannel; wf.data = NULL; wf.datalen = 0; wf.samples = 0; + ast_mutex_unlock(&myrpt->lock); ast_write(genchannel,&wf); + ast_mutex_lock(&myrpt->lock); myrpt->mydtmf = 0; } + ast_mutex_unlock(&myrpt->lock); usleep(25000); + ast_mutex_lock(&myrpt->lock); } + ast_mutex_unlock(&myrpt->lock); tone_zone_play_tone(genchannel->fds[0],-1); if (mychannel->pvt) ast_softhangup(mychannel,AST_SOFTHANGUP_DEV); ast_hangup(genchannel); + ast_mutex_lock(&myrpt->lock); myrpt->callmode = 0; + ast_mutex_unlock(&myrpt->lock); pthread_exit(NULL); } -static void process_dtmf(char *cmd,struct rpt *myrpt) +static void send_link_dtmf(struct rpt *myrpt,char c) +{ +char str[300]; +struct ast_frame wf; +struct rpt_link *l; + + sprintf(str,"D %s %s %d %c",myrpt->cmdnode,myrpt->name,++(myrpt->dtmfidx),c); + wf.frametype = AST_FRAME_TEXT; + wf.subclass = 0; + wf.offset = 0; + wf.mallocd = 1; + wf.datalen = strlen(str) + 1; + wf.samples = 0; + l = myrpt->links.next; + /* first, see if our dude is there */ + while(l != &myrpt->links) + { + if (!l->isremote) + { + /* if we found it, write it and were done */ + if (!strcmp(l->name,myrpt->cmdnode)) + { + wf.data = strdup(str); + ast_write(l->chan,&wf); + return; + } + } + l = l->next; + } + l = myrpt->links.next; + /* if not, give it to everyone */ + while(l != &myrpt->links) + { + if (!l->isremote) + { + wf.data = strdup(str); + ast_write(l->chan,&wf); + } + l = l->next; + } + return; +} + +static void process_dtmf(char *cmd,struct rpt *myrpt, int allow_linkcmd) { pthread_attr_t attr; +char *tele,tmp[300],deststr[300],*val,*s,*s1; +struct rpt_link *l; ZT_CONFINFO ci; /* conference info */ - switch(atoi(cmd)) + switch(atoi(cmd) / 1000) { - case 0: /* autopatch on / send asterisk (*) */ + case 6: /* autopatch on / send asterisk (*) */ + ast_mutex_lock(&myrpt->lock); /* if on call, force * into current audio stream */ if ((myrpt->callmode == 2) || (myrpt->callmode == 3)) { myrpt->mydtmf = '*'; + ast_mutex_unlock(&myrpt->lock); break; } - if (myrpt->callmode) return; + if (myrpt->callmode) + { + ast_mutex_unlock(&myrpt->lock); + return; + } myrpt->callmode = 1; myrpt->cidx = 0; myrpt->exten[myrpt->cidx] = 0; + ast_mutex_unlock(&myrpt->lock); pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - pthread_create(&myrpt->rpt_call_thread,&attr,rpt_call,(void *)myrpt); + pthread_create(&myrpt->rpt_call_thread,&attr,rpt_call,(void *) myrpt); + return; + case 0: /* autopatch off */ + ast_mutex_lock(&myrpt->lock); + if (!myrpt->callmode) + { + ast_mutex_unlock(&myrpt->lock); + return; + } + myrpt->callmode = 0; + ast_mutex_unlock(&myrpt->lock); + rpt_telemetry(myrpt,TERM,NULL); return; case 9: /* master reset */ + ast_mutex_lock(&myrpt->lock); myrpt->callmode = 0; - /* fall thru intentionally */ + ast_mutex_unlock(&myrpt->lock); + l = myrpt->links.next; + /* disconnect all of the remote stuff */ + while(l != &myrpt->links) + { + ast_softhangup(l->chan,AST_SOFTHANGUP_DEV); + l = l->next; + } + break; case 1: /* remote base off */ - if (myrpt->rem_rxchannel == NULL) return; - myrpt->remotemode = REM_OFF; - ci.chan = 0; - ci.confno = 0; - ci.confmode = 0; - /* Take off conf */ - if (ioctl(myrpt->rem_rxchannel->fds[0],ZT_SETCONF,&ci) == -1) + val = ast_variable_retrieve(cfg,NODES,cmd + 1); + if (!val) { - ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n"); - pthread_exit(NULL); + rpt_telemetry(myrpt,REMNOTFOUND,NULL); + return; } - /* Take off conf */ - if (ioctl(myrpt->rem_txchannel->fds[0],ZT_SETCONF,&ci) == -1) + strncpy(tmp,val,sizeof(tmp) - 1); + s = tmp; + s1 = strsep(&s,","); + ast_mutex_lock(&myrpt->lock); + l = myrpt->links.next; + /* try to find this one in queue */ + while(l != &myrpt->links) { - ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n"); - pthread_exit(NULL); + /* if found matching string */ + if (!strcmp(l->name,cmd + 1)) break; + l = l->next; } - break; + if (l != &myrpt->links) /* if found */ + { + ast_mutex_unlock(&myrpt->lock); + ast_softhangup(l->chan,AST_SOFTHANGUP_DEV); + break; + } + ast_mutex_unlock(&myrpt->lock); + return; case 2: /* remote base monitor */ - if (myrpt->rem_rxchannel == NULL) return; - myrpt->remotemode = REM_MONITOR; - if (myrpt->remoterx && (!myrpt->remotetx)) - { - ci.chan = 0; - ci.confno = myrpt->pconf; - ci.confmode = ZT_CONF_CONF | ZT_CONF_TALKER; - /* Put on conf */ - if (ioctl(myrpt->rem_rxchannel->fds[0],ZT_SETCONF,&ci) == -1) - { - ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n"); - pthread_exit(NULL); + val = ast_variable_retrieve(cfg,NODES,cmd + 1); + if (!val) + { + rpt_telemetry(myrpt,REMNOTFOUND,NULL); + return; + } + strncpy(tmp,val,sizeof(tmp) - 1); + s = tmp; + s1 = strsep(&s,","); + ast_mutex_lock(&myrpt->lock); + l = myrpt->links.next; + /* try to find this one in queue */ + while(l != &myrpt->links) + { + /* if found matching string */ + if (!strcmp(l->name,cmd + 1)) break; + l = l->next; + } + /* if found */ + if (l != &myrpt->links) + { + /* if already in this mode, just ignore */ + if (!l->mode) { + ast_mutex_unlock(&myrpt->lock); + rpt_telemetry(myrpt,REMALREADY,NULL); + return; } + ast_softhangup(l->chan,AST_SOFTHANGUP_DEV); + usleep(500000); + } + ast_mutex_unlock(&myrpt->lock); + /* establish call in monitor mode */ + l = malloc(sizeof(struct rpt_link)); + if (!l) + { + ast_log(LOG_WARNING, "Unable to malloc\n"); + pthread_exit(NULL); + } + /* zero the silly thing */ + memset((char *)l,0,sizeof(struct rpt_link)); + sprintf(deststr,"IAX2/%s",s1); + tele = strchr(deststr,'/'); + if (!tele) + { + fprintf(stderr,"rpt:Dial number (%s) must be in format tech/number\n",deststr); + pthread_exit(NULL); + } + *tele++ = 0; + l->isremote = (s && ast_true(s)); + strncpy(l->name,cmd + 1,MAXNODESTR - 1); + l->chan = ast_request(deststr,AST_FORMAT_SLINEAR,tele); + if (l->chan) + { + ast_set_read_format(l->chan,AST_FORMAT_SLINEAR); + ast_set_write_format(l->chan,AST_FORMAT_SLINEAR); + l->chan->whentohangup = 0; + l->chan->appl = "Apprpt"; + l->chan->data = "(Remote Rx)"; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "rpt (remote) initiating call to %s/%s on %s\n", + deststr,tele,l->chan->name); + l->chan->callerid = strdup(myrpt->name); + ast_call(l->chan,tele,0); + } + else + { + free(l); + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Unable to place call to %s/%s on %s\n", + deststr,tele,l->chan->name); + return; + } + /* allocate a pseudo-channel thru asterisk */ + l->pchan = ast_request("zap",AST_FORMAT_SLINEAR,"pseudo"); + if (!l->pchan) + { + fprintf(stderr,"rpt:Sorry unable to obtain pseudo channel\n"); + pthread_exit(NULL); + } + ast_set_read_format(l->pchan,AST_FORMAT_SLINEAR); + ast_set_write_format(l->pchan,AST_FORMAT_SLINEAR); + /* make a conference for the pseudo-one */ + ci.chan = 0; + ci.confno = myrpt->conf; + ci.confmode = ZT_CONF_CONF | ZT_CONF_LISTENER | ZT_CONF_TALKER; + /* first put the channel on the conference in proper mode */ + if (ioctl(l->pchan->fds[0],ZT_SETCONF,&ci) == -1) + { + ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n"); + pthread_exit(NULL); } + ast_mutex_lock(&myrpt->lock); + /* insert at end of queue */ + insque((struct qelem *)l,(struct qelem *)myrpt->links.next); + ast_mutex_unlock(&myrpt->lock); break; case 3: /* remote base tranceieve */ - if (myrpt->rem_rxchannel == NULL) return; - myrpt->remotemode = REM_TX; - if (myrpt->remoterx && (!myrpt->remotetx)) - { - ci.chan = 0; - ci.confno = myrpt->pconf; - ci.confmode = ZT_CONF_CONF | ZT_CONF_TALKER; - /* Put on conf */ - if (ioctl(myrpt->rem_rxchannel->fds[0],ZT_SETCONF,&ci) == -1) + val = ast_variable_retrieve(cfg,NODES,cmd + 1); + if (!val) + { + rpt_telemetry(myrpt,REMNOTFOUND,NULL); + return; + } + strncpy(tmp,val,sizeof(tmp) - 1); + s = tmp; + s1 = strsep(&s,","); + ast_mutex_lock(&myrpt->lock); + l = myrpt->links.next; + /* try to find this one in queue */ + while(l != &myrpt->links) + { + /* if found matching string */ + if (!strcmp(l->name,cmd + 1)) break; + } + /* if found */ + if (l != &myrpt->links) + { + /* if already in this mode, just ignore */ + if (l->mode) { - ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n"); - pthread_exit(NULL); + ast_mutex_unlock(&myrpt->lock); + rpt_telemetry(myrpt,REMALREADY,NULL); + return; } + ast_softhangup(l->chan,AST_SOFTHANGUP_DEV); + usleep(500000); } - break; - case 8: /* force ID */ - myrpt->idtimer = 0; - return; - default: - return; - } - /* send function complete */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - pthread_create(&myrpt->rpt_call_thread,&attr,rpt_complete,(void *)myrpt); -} - -/* single thread with one file (request) to dial */ -static void *rpt(void *this) -{ -struct rpt *myrpt = (struct rpt *)this; -char *tele; -int ms = MSWAIT,lasttx,keyed,val,dtmfidx; -char dtmfbuf[MAXDTMF]; -struct ast_channel *who; + ast_mutex_unlock(&myrpt->lock); + /* establish call in tranceive mode */ + l = malloc(sizeof(struct rpt_link)); + if (!l) + { + ast_log(LOG_WARNING, "Unable to malloc\n"); + pthread_exit(NULL); + } + /* zero the silly thing */ + memset((char *)l,0,sizeof(struct rpt_link)); + l->mode = 1; + strncpy(l->name,cmd + 1,MAXNODESTR - 1); + l->isremote = (s && ast_true(s)); + sprintf(deststr,"IAX2/%s",s1); + tele = strchr(deststr,'/'); + if (!tele) + { + fprintf(stderr,"rpt:Dial number must be in format tech/number\n"); + pthread_exit(NULL); + } + *tele++ = 0; + l->chan = ast_request(deststr,AST_FORMAT_SLINEAR,tele); + if (l->chan) + { + ast_set_read_format(l->chan,AST_FORMAT_SLINEAR); + ast_set_write_format(l->chan,AST_FORMAT_SLINEAR); + l->chan->whentohangup = 0; + l->chan->appl = "Apprpt"; + l->chan->data = "(Remote Rx)"; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "rpt (remote) initiating call to %s/%s on %s\n", + deststr,tele,l->chan->name); + l->chan->callerid = strdup(myrpt->name); + ast_call(l->chan,tele,999); + } + else + { + free(l); + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Unable to place call to %s/%s on %s\n", + deststr,tele,l->chan->name); + return; + } + /* allocate a pseudo-channel thru asterisk */ + l->pchan = ast_request("zap",AST_FORMAT_SLINEAR,"pseudo"); + if (!l->pchan) + { + fprintf(stderr,"rpt:Sorry unable to obtain pseudo channel\n"); + pthread_exit(NULL); + } + ast_set_read_format(l->pchan,AST_FORMAT_SLINEAR); + ast_set_write_format(l->pchan,AST_FORMAT_SLINEAR); + /* make a conference for the tx */ + ci.chan = 0; + ci.confno = myrpt->conf; + ci.confmode = ZT_CONF_CONF | ZT_CONF_LISTENER | ZT_CONF_TALKER; + /* first put the channel on the conference in proper mode */ + if (ioctl(l->pchan->fds[0],ZT_SETCONF,&ci) == -1) + { + ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n"); + pthread_exit(NULL); + } + ast_mutex_lock(&myrpt->lock); + /* insert at end of queue */ + insque((struct qelem *)l,(struct qelem *)myrpt->links.next); + ast_mutex_unlock(&myrpt->lock); + break; + case 4: /* remote cmd mode */ + /* if doesnt allow link cmd, return */ + if ((!allow_linkcmd) || (myrpt->links.next == &myrpt->links)) return; + /* if already in cmd mode, or selected self, forget it */ + if ((myrpt->cmdnode[0]) || (!strcmp(myrpt->name,cmd + 1))) + { + rpt_telemetry(myrpt,REMALREADY,NULL); + return; + } + /* node must at least exist in list */ + val = ast_variable_retrieve(cfg,NODES,cmd + 1); + if (!val) + { + rpt_telemetry(myrpt,REMNOTFOUND,NULL); + return; + } + ast_mutex_lock(&myrpt->lock); + myrpt->dtmfidx = -1; + myrpt->dtmfbuf[0] = 0; + myrpt->rem_dtmfidx = -1; + myrpt->rem_dtmfbuf[0] = 0; + strcpy(myrpt->cmdnode,cmd + 1); + ast_mutex_unlock(&myrpt->lock); + rpt_telemetry(myrpt,REMGO,NULL); + return; + case 7: /* system status */ + rpt_telemetry(myrpt,STATUS,NULL); + return; + case 8: /* force ID */ + myrpt->idtimer = 0; + return; + default: + return; + } + rpt_telemetry(myrpt,COMPLETE,NULL); +} + +static void handle_link_data(struct rpt *myrpt, char *str) +{ +char tmp[300],cmd[300],dest[300],src[300],c; +int seq; +struct rpt_link *l; +struct ast_frame wf; + + /* if we are a remote, we dont want to do this */ + if (myrpt->remote) return; + wf.frametype = AST_FRAME_TEXT; + wf.subclass = 0; + wf.offset = 0; + wf.mallocd = 1; + wf.datalen = strlen(str) + 1; + wf.samples = 0; + l = myrpt->links.next; + /* put string in our buffer */ + strncpy(tmp,str,sizeof(tmp) - 1); + if (sscanf(tmp,"%s %s %s %d %c",cmd,dest,src,&seq,&c) != 5) + { + ast_log(LOG_WARNING, "Unable to parse link string %s\n",str); + return; + } + if (strcmp(cmd,"D")) + { + ast_log(LOG_WARNING, "Unable to parse link string %s\n",str); + return; + } + /* if not for me, redistribute to all links */ + if (strcmp(dest,myrpt->name)) + { + l = myrpt->links.next; + while(l != &myrpt->links) + { + if (!l->isremote) + { + wf.data = strdup(str); + ast_write(l->chan,&wf); + } + l = l->next; + } + return; + } + ast_mutex_lock(&myrpt->lock); + if (c == '*') + { + myrpt->rem_dtmfidx = 0; + myrpt->rem_dtmfbuf[myrpt->rem_dtmfidx] = 0; + time(&myrpt->rem_dtmf_time); + ast_mutex_unlock(&myrpt->lock); + return; + } + else if ((c != '#') && (myrpt->rem_dtmfidx >= 0)) + { + time(&myrpt->rem_dtmf_time); + if (myrpt->rem_dtmfidx < MAXDTMF) + { + myrpt->rem_dtmfbuf[myrpt->rem_dtmfidx++] = c; + myrpt->rem_dtmfbuf[myrpt->rem_dtmfidx] = 0; + /* if to terminate function now */ + if ((myrpt->rem_dtmfidx == 1) && strchr(SHORTFUNCS,c)) + { + while(myrpt->rem_dtmfidx < FUNCTION_LEN) + myrpt->rem_dtmfbuf[myrpt->rem_dtmfidx++] = '0'; + myrpt->rem_dtmfbuf[myrpt->rem_dtmfidx] = 0; + } + } + if (myrpt->rem_dtmfidx == FUNCTION_LEN) + { + strcpy(cmd,myrpt->rem_dtmfbuf); + myrpt->rem_dtmfbuf[0] = 0; + myrpt->rem_dtmfidx = -1; + ast_mutex_unlock(&myrpt->lock); + process_dtmf(cmd,myrpt,0); + return; + } + } + ast_mutex_unlock(&myrpt->lock); + return; +} + +/* single thread with one file (request) to dial */ +static void *rpt(void *this) +{ +struct rpt *myrpt = (struct rpt *)this; +char *tele; +int ms = MSWAIT,lasttx,keyed,val,remrx; +struct ast_channel *who; ZT_CONFINFO ci; /* conference info */ time_t dtmf_time,t; +struct rpt_link *l,*m; pthread_attr_t attr; + ast_mutex_lock(&myrpt->lock); tele = strchr(myrpt->rxchanname,'/'); if (!tele) { fprintf(stderr,"rpt:Dial number must be in format tech/number\n"); + ast_mutex_unlock(&myrpt->lock); pthread_exit(NULL); } *tele++ = 0; @@ -616,6 +1125,7 @@ pthread_attr_t attr; else { fprintf(stderr,"rpt:Sorry unable to obtain Rx channel\n"); + ast_mutex_unlock(&myrpt->lock); pthread_exit(NULL); } if (myrpt->txchanname) @@ -624,6 +1134,7 @@ pthread_attr_t attr; if (!tele) { fprintf(stderr,"rpt:Dial number must be in format tech/number\n"); + ast_mutex_unlock(&myrpt->lock); pthread_exit(NULL); } *tele++ = 0; @@ -643,6 +1154,7 @@ pthread_attr_t attr; else { fprintf(stderr,"rpt:Sorry unable to obtain Tx channel\n"); + ast_mutex_unlock(&myrpt->lock); pthread_exit(NULL); } } @@ -650,85 +1162,23 @@ pthread_attr_t attr; { myrpt->txchannel = myrpt->rxchannel; } - myrpt->rem_rxchannel = NULL; - myrpt->rem_txchannel = NULL; - myrpt->remoterx = 0; - myrpt->remotemode = REM_OFF; - if (myrpt->rem_rxchanname) - { - tele = strchr(myrpt->rem_rxchanname,'/'); - if (!tele) - { - fprintf(stderr,"rpt:Dial number must be in format tech/number\n"); - pthread_exit(NULL); - } - *tele++ = 0; - myrpt->rem_rxchannel = ast_request(myrpt->rem_rxchanname,AST_FORMAT_SLINEAR,tele); - if (myrpt->rem_rxchannel) - { - ast_set_read_format(myrpt->rem_rxchannel,AST_FORMAT_SLINEAR); - ast_set_write_format(myrpt->rem_rxchannel,AST_FORMAT_SLINEAR); - myrpt->rem_rxchannel->whentohangup = 0; - myrpt->rem_rxchannel->appl = "Apprpt"; - myrpt->rem_rxchannel->data = "(Repeater/Remote Rx)"; - if (option_verbose > 2) - ast_verbose(VERBOSE_PREFIX_3 "rpt (RemoteRx) initiating call to %s/%s on %s\n", - myrpt->rem_rxchanname,tele,myrpt->rem_rxchannel->name); - ast_call(myrpt->rem_rxchannel,tele,999); - } - else - { - fprintf(stderr,"rpt:Sorry unable to obtain RemoteRx channel\n"); - pthread_exit(NULL); - } - if (myrpt->rem_txchanname) /* if in remote base mode */ - { - tele = strchr(myrpt->rem_txchanname,'/'); - if (!tele) - { - fprintf(stderr,"rpt:Dial number must be in format tech/number\n"); - pthread_exit(NULL); - } - *tele++ = 0; - myrpt->rem_txchannel = ast_request(myrpt->rem_txchanname,AST_FORMAT_SLINEAR,tele); - if (myrpt->rem_txchannel) - { - ast_set_read_format(myrpt->rem_txchannel,AST_FORMAT_SLINEAR); - ast_set_write_format(myrpt->rem_txchannel,AST_FORMAT_SLINEAR); - myrpt->rem_txchannel->whentohangup = 0; - myrpt->rem_txchannel->appl = "Apprpt"; - myrpt->rem_txchannel->data = "(Repeater/Remote Tx)"; - if (option_verbose > 2) - ast_verbose(VERBOSE_PREFIX_3 "rpt (RemoteTx) initiating call to %s/%s on %s\n", - myrpt->rem_txchanname,tele,myrpt->rem_txchannel->name); - ast_call(myrpt->rem_txchannel,tele,999); - } - else - { - fprintf(stderr,"rpt:Sorry unable to obtain Tx channel\n"); - pthread_exit(NULL); - } - } - else - { - myrpt->rem_txchannel = myrpt->rem_rxchannel; - } - } /* allocate a pseudo-channel thru asterisk */ myrpt->pchannel = ast_request("zap",AST_FORMAT_SLINEAR,"pseudo"); if (!myrpt->pchannel) { fprintf(stderr,"rpt:Sorry unable to obtain pseudo channel\n"); + ast_mutex_unlock(&myrpt->lock); pthread_exit(NULL); } /* make a conference for the tx */ ci.chan = 0; ci.confno = -1; /* make a new conf */ ci.confmode = ZT_CONF_CONF | ZT_CONF_LISTENER; - /* first put the channel on the conference in announce mode */ + /* first put the channel on the conference in proper mode */ if (ioctl(myrpt->txchannel->fds[0],ZT_SETCONF,&ci) == -1) { ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n"); + ast_mutex_unlock(&myrpt->lock); pthread_exit(NULL); } /* save tx conference number */ @@ -741,50 +1191,75 @@ pthread_attr_t attr; if (ioctl(myrpt->pchannel->fds[0],ZT_SETCONF,&ci) == -1) { ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n"); + ast_mutex_unlock(&myrpt->lock); pthread_exit(NULL); } /* save pseudo channel conference number */ - myrpt->pconf = ci.confno; + myrpt->conf = ci.confno; + /* allocate a pseudo-channel thru asterisk */ + myrpt->txpchannel = ast_request("zap",AST_FORMAT_SLINEAR,"pseudo"); + if (!myrpt->txpchannel) + { + fprintf(stderr,"rpt:Sorry unable to obtain pseudo channel\n"); + ast_mutex_unlock(&myrpt->lock); + pthread_exit(NULL); + } + /* make a conference for the tx */ + ci.chan = 0; + ci.confno = myrpt->txconf; + ci.confmode = ZT_CONF_CONF | ZT_CONF_TALKER ; + /* first put the channel on the conference in proper mode */ + if (ioctl(myrpt->txpchannel->fds[0],ZT_SETCONF,&ci) == -1) + { + ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n"); + ast_mutex_unlock(&myrpt->lock); + pthread_exit(NULL); + } /* Now, the idea here is to copy from the physical rx channel buffer into the pseudo tx buffer, and from the pseudo rx buffer into the tx channel buffer */ + myrpt->links.next = &myrpt->links; + myrpt->links.prev = &myrpt->links; myrpt->tailtimer = 0; myrpt->totimer = 0; myrpt->idtimer = 0; + myrpt->callmode = 0; + ast_mutex_unlock(&myrpt->lock); lasttx = 0; - myrpt->remotetx = 0; keyed = 0; - myrpt->callmode = 0; - dtmfidx = -1; - dtmfbuf[0] = 0; + myrpt->dtmfidx = -1; + myrpt->dtmfbuf[0] = 0; + myrpt->rem_dtmfidx = -1; + myrpt->rem_dtmfbuf[0] = 0; dtmf_time = 0; + myrpt->rem_dtmf_time = 0; val = 0; ast_channel_setoption(myrpt->rxchannel,AST_OPTION_TONE_VERIFY,&val,sizeof(char),0); val = 1; ast_channel_setoption(myrpt->rxchannel,AST_OPTION_RELAXDTMF,&val,sizeof(char),0); - if (myrpt->rem_rxchannel) - { - val = 0; - ast_channel_setoption(myrpt->rem_rxchannel,AST_OPTION_TONE_VERIFY,&val,sizeof(char),0); - val = 1; - ast_channel_setoption(myrpt->rem_rxchannel,AST_OPTION_RELAXDTMF,&val,sizeof(char),0); - } while (ms >= 0) { struct ast_frame *f; - struct ast_channel *cs[5]; - int totx,rem_totx,elap,n; + struct ast_channel *cs[300]; + int totx,elap,n,toexit; if (ast_check_hangup(myrpt->rxchannel)) break; if (ast_check_hangup(myrpt->txchannel)) break; - if (myrpt->rem_rxchannel) + if (ast_check_hangup(myrpt->pchannel)) break; + if (ast_check_hangup(myrpt->txpchannel)) break; + ast_mutex_lock(&myrpt->lock); + myrpt->localtx = keyed; + l = myrpt->links.next; + remrx = 0; + while(l != &myrpt->links) { - if (ast_check_hangup(myrpt->rem_rxchannel)) break; - if (ast_check_hangup(myrpt->rem_txchannel)) break; + if (l->lastrx) remrx = 1; + l = l->next; } - totx = (keyed || myrpt->callmode || myrpt->iding || myrpt->terming - || ((myrpt->remotemode != REM_OFF) && myrpt->remoterx) || - myrpt->teleing || myrpt->comping || myrpt->procing); + totx = (keyed || myrpt->callmode || + (myrpt->tele.next != &myrpt->tele)); + myrpt->exttx = totx; + totx |= remrx; if (!totx) myrpt->totimer = myrpt->totime; else myrpt->tailtimer = myrpt->hangtime; totx = (totx || myrpt->tailtimer) && myrpt->totimer; @@ -793,6 +1268,7 @@ pthread_attr_t attr; if ((!totx) && (!myrpt->totimer) && myrpt->callmode && keyed) { myrpt->totimer = myrpt->totime; + ast_mutex_unlock(&myrpt->lock); continue; } /* if timed-out and in circuit busy after call */ @@ -803,97 +1279,83 @@ pthread_attr_t attr; if (totx && (!myrpt->idtimer)) { myrpt->idtimer = myrpt->idtime; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - pthread_create(&myrpt->rpt_id_thread,&attr,rpt_id,(void *) myrpt); + ast_mutex_unlock(&myrpt->lock); + rpt_telemetry(myrpt,ID,NULL); + ast_mutex_lock(&myrpt->lock); } if (totx && (!lasttx)) { lasttx = 1; + ast_mutex_unlock(&myrpt->lock); ast_indicate(myrpt->txchannel,AST_CONTROL_RADIO_KEY); + ast_mutex_lock(&myrpt->lock); } if ((!totx) && lasttx) { lasttx = 0; + ast_mutex_unlock(&myrpt->lock); ast_indicate(myrpt->txchannel,AST_CONTROL_RADIO_UNKEY); - } - rem_totx = ((keyed && (myrpt->remotemode == REM_TX)) && myrpt->totimer); - if (rem_totx && (!myrpt->remotetx)) - { - myrpt->remotetx = 1; - ci.chan = 0; - ci.confno = 0; - ci.confmode = 0; - /* Take off conf */ - if (ioctl(myrpt->rem_rxchannel->fds[0],ZT_SETCONF,&ci) == -1) - { - ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n"); - pthread_exit(NULL); - } - ast_indicate(myrpt->rem_txchannel,AST_CONTROL_RADIO_KEY); - ci.chan = 0; - ci.confno = myrpt->txconf; - ci.confmode = ZT_CONF_CONF | ZT_CONF_LISTENER; - /* Put the channel on the conference in listener mode */ - if (ioctl(myrpt->rem_txchannel->fds[0],ZT_SETCONF,&ci) == -1) - { - ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n"); - pthread_exit(NULL); - } - } - if ((!rem_totx) && myrpt->remotetx) - { - myrpt->remotetx = 0; - ast_indicate(myrpt->rem_txchannel,AST_CONTROL_RADIO_UNKEY); - ci.chan = 0; - ci.confno = 0; - ci.confmode = 0; - /* Take off conf */ - if (ioctl(myrpt->rem_txchannel->fds[0],ZT_SETCONF,&ci) == -1) - { - ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n"); - pthread_exit(NULL); - } - if (myrpt->remotemode != REM_OFF) - { - ci.chan = 0; - ci.confno = myrpt->pconf; - ci.confmode = ZT_CONF_CONF | ZT_CONF_TALKER; - /* Put on conf */ - if (ioctl(myrpt->rem_rxchannel->fds[0],ZT_SETCONF,&ci) == -1) - { - ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n"); - pthread_exit(NULL); - } - } + ast_mutex_lock(&myrpt->lock); } time(&t); /* if DTMF timeout */ - if ((dtmfidx >= 0) && ((dtmf_time + DTMF_TIMEOUT) < t)) + if ((!myrpt->cmdnode[0]) && (myrpt->dtmfidx >= 0) && ((dtmf_time + DTMF_TIMEOUT) < t)) { - dtmfidx = -1; - dtmfbuf[0] = 0; + myrpt->dtmfidx = -1; + myrpt->dtmfbuf[0] = 0; + } + /* if remote DTMF timeout */ + if ((myrpt->rem_dtmfidx >= 0) && ((myrpt->rem_dtmf_time + DTMF_TIMEOUT) < t)) + { + myrpt->rem_dtmfidx = -1; + myrpt->rem_dtmfbuf[0] = 0; } n = 0; cs[n++] = myrpt->rxchannel; cs[n++] = myrpt->pchannel; + cs[n++] = myrpt->txpchannel; if (myrpt->txchannel != myrpt->rxchannel) cs[n++] = myrpt->txchannel; - if (myrpt->rem_rxchannel) + l = myrpt->links.next; + while(l != &myrpt->links) { - cs[n++] = myrpt->rem_rxchannel; - if (myrpt->rem_txchannel != myrpt->rem_rxchannel) - cs[n++] = myrpt->rem_txchannel; + cs[n++] = l->chan; + cs[n++] = l->pchan; + l = l->next; } + ast_mutex_unlock(&myrpt->lock); ms = MSWAIT; who = ast_waitfor_n(cs,n,&ms); if (who == NULL) ms = 0; elap = MSWAIT - ms; + ast_mutex_lock(&myrpt->lock); + l = myrpt->links.next; + while(l != &myrpt->links) + { + /* ignore non-timing channels */ + if (l->elaptime < 0) + { + l = l->next; + continue; + } + l->elaptime += elap; + /* if connection has taken too long */ + if ((l->elaptime > MAXCONNECTTIME) && + (l->chan->_state != AST_STATE_UP)) + { + ast_mutex_unlock(&myrpt->lock); + ast_softhangup(l->chan,AST_SOFTHANGUP_DEV); + rpt_telemetry(myrpt,CONNFAIL,l); + ast_mutex_lock(&myrpt->lock); + } + l = l->next; + } if (myrpt->tailtimer) myrpt->tailtimer -= elap; if (myrpt->tailtimer < 0) myrpt->tailtimer = 0; if (myrpt->totimer) myrpt->totimer -= elap; if (myrpt->totimer < 0) myrpt->totimer = 0; if (myrpt->idtimer) myrpt->idtimer -= elap; if (myrpt->idtimer < 0) myrpt->idtimer = 0; + ast_mutex_unlock(&myrpt->lock); if (!ms) continue; if (who == myrpt->rxchannel) /* if it was a read from rx */ { @@ -912,28 +1374,60 @@ pthread_attr_t attr; char c; c = (char) f->subclass; /* get DTMF char */ + if (c == '#') + { + /* if in simple mode, kill autopatch */ + if (myrpt->simple && myrpt->callmode) + { + myrpt->callmode = 0; + rpt_telemetry(myrpt,TERM,NULL); + continue; + } + ast_mutex_lock(&myrpt->lock); + if (myrpt->cmdnode[0]) + { + myrpt->cmdnode[0] = 0; + myrpt->dtmfidx = -1; + myrpt->dtmfbuf[0] = 0; + ast_mutex_unlock(&myrpt->lock); + rpt_telemetry(myrpt,COMPLETE,NULL); + } else ast_mutex_unlock(&myrpt->lock); + continue; + } + if (myrpt->cmdnode[0]) + { + send_link_dtmf(myrpt,c); + continue; + } if (!myrpt->simple) { if (c == '*') { - dtmfidx = 0; - dtmfbuf[dtmfidx] = 0; + myrpt->dtmfidx = 0; + myrpt->dtmfbuf[myrpt->dtmfidx] = 0; time(&dtmf_time); continue; } - else if ((c != '#') && (dtmfidx >= 0)) + else if ((c != '#') && (myrpt->dtmfidx >= 0)) { time(&dtmf_time); - if (dtmfidx < MAXDTMF) + if (myrpt->dtmfidx < MAXDTMF) { - dtmfbuf[dtmfidx++] = c; - dtmfbuf[dtmfidx] = 0; + myrpt->dtmfbuf[myrpt->dtmfidx++] = c; + myrpt->dtmfbuf[myrpt->dtmfidx] = 0; + /* if to terminate function now */ + if ((myrpt->dtmfidx == 1) && strchr(SHORTFUNCS,c)) + { + while(myrpt->dtmfidx < FUNCTION_LEN) + myrpt->dtmfbuf[myrpt->dtmfidx++] = '0'; + myrpt->dtmfbuf[myrpt->dtmfidx] = 0; + } } - if (dtmfidx == FUNCTION_LEN) + if (myrpt->dtmfidx == FUNCTION_LEN) { - process_dtmf(dtmfbuf,myrpt); - dtmfbuf[0] = 0; - dtmfidx = -1; + process_dtmf(myrpt->dtmfbuf,myrpt,1); + myrpt->dtmfbuf[0] = 0; + myrpt->dtmfidx = -1; continue; } } @@ -951,14 +1445,6 @@ pthread_attr_t attr; continue; } } - if (myrpt->callmode && (c == '#')) - { - myrpt->callmode = 0; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - pthread_create(&myrpt->rpt_term_thread,&attr,rpt_term,(void *) myrpt); - continue; - } if (myrpt->callmode == 1) { myrpt->exten[myrpt->cidx++] = c; @@ -967,9 +1453,7 @@ pthread_attr_t attr; if (ast_exists_extension(myrpt->pchannel,myrpt->ourcontext,myrpt->exten,1,NULL)) { myrpt->callmode = 2; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - pthread_create(&myrpt->rpt_proc_thread,&attr,rpt_proc,(void *) myrpt); + rpt_telemetry(myrpt,PROC,NULL); } /* if can continue, do so */ if (ast_canmatch_extension(myrpt->pchannel,myrpt->ourcontext,myrpt->exten,1,NULL)) continue; @@ -1001,11 +1485,10 @@ pthread_attr_t attr; { if (debug) printf("@@@@ rx un-key\n"); keyed = 0; - if (myrpt->remotemode != REM_OFF) + /* if we have remotes, twiddle */ + if (myrpt->cmdnode[0] || (myrpt->links.next != &myrpt->links)) { - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - pthread_create(&myrpt->rpt_proc_thread,&attr,rpt_remote_telemetry,(void *) myrpt); + rpt_telemetry(myrpt,UNKEY,NULL); } } } @@ -1022,9 +1505,7 @@ pthread_attr_t attr; } if (f->frametype == AST_FRAME_VOICE) { - ast_write(myrpt->txchannel,f); - if (myrpt->remotemode == REM_TX) - ast_write(myrpt->rem_txchannel,f); + ast_write(myrpt->txpchannel,f); } if (f->frametype == AST_FRAME_CONTROL) { @@ -1058,65 +1539,129 @@ pthread_attr_t attr; ast_frfree(f); continue; } - if (who == myrpt->rem_rxchannel) /* if it was a read from rx */ + toexit = 0; + l = myrpt->links.next; + while(l != &myrpt->links) { - f = ast_read(myrpt->rem_rxchannel); - if (!f) + if (who == l->chan) /* if it was a read from rx */ { - if (debug) printf("@@@@ rpt:Hung Up\n"); + ast_mutex_lock(&myrpt->lock); + remrx = 0; + /* see if any other links are receiving */ + m = myrpt->links.next; + while(m != &myrpt->links) + { + /* if not us, count it */ + if ((m != l) && (m->lastrx)) remrx = 1; + m = m->next; + } + ast_mutex_unlock(&myrpt->lock); + totx = (((l->isremote) ? myrpt->localtx : + myrpt->exttx) || remrx) && l->mode; + if (l->lasttx != totx) + { + if (totx) + { + ast_indicate(l->chan,AST_CONTROL_RADIO_KEY); + } + else + { + ast_indicate(l->chan,AST_CONTROL_RADIO_UNKEY); + } + } + l->lasttx = totx; + f = ast_read(l->chan); + if (!f) + { + ast_mutex_lock(&myrpt->lock); + /* remove from queue */ + remque((struct qelem *) l); + ast_mutex_unlock(&myrpt->lock); + rpt_telemetry(myrpt,REMDISC,l); + /* hang-up on call to device */ + ast_hangup(l->chan); + ast_hangup(l->pchan); + free(l); + break; + } + if (f->frametype == AST_FRAME_VOICE) + { + ast_write(l->pchan,f); + } + if (f->frametype == AST_FRAME_TEXT) + { + handle_link_data(myrpt,f->data); + } + if (f->frametype == AST_FRAME_CONTROL) + { + if (f->subclass == AST_CONTROL_ANSWER) + { + l->connected = 1; + l->elaptime = -1; + rpt_telemetry(myrpt,CONNECTED,l); + } + /* if RX key */ + if (f->subclass == AST_CONTROL_RADIO_KEY) + { + if (debug) printf("@@@@ rx key\n"); + l->lastrx = 1; + } + /* if RX un-key */ + if (f->subclass == AST_CONTROL_RADIO_UNKEY) + { + if (debug) printf("@@@@ rx un-key\n"); + l->lastrx = 0; + } + if (f->subclass == AST_CONTROL_HANGUP) + { + ast_frfree(f); + ast_mutex_lock(&myrpt->lock); + /* remove from queue */ + remque((struct qelem *) l); + ast_mutex_unlock(&myrpt->lock); + rpt_telemetry(myrpt,REMDISC,l); + /* hang-up on call to device */ + ast_hangup(l->chan); + ast_hangup(l->pchan); + free(l); + break; + } + } + ast_frfree(f); break; } - if (f->frametype == AST_FRAME_CONTROL) + if (who == l->pchan) { - if (f->subclass == AST_CONTROL_HANGUP) + f = ast_read(l->pchan); + if (!f) { if (debug) printf("@@@@ rpt:Hung Up\n"); - ast_frfree(f); + toexit = 1; break; } - /* if RX key */ - if (f->subclass == AST_CONTROL_RADIO_KEY) + if (f->frametype == AST_FRAME_VOICE) { - if (debug) printf("@@@@ remote rx key\n"); - if (!myrpt->remotetx) - { - myrpt->remoterx = 1; - ci.chan = 0; - ci.confno = myrpt->pconf; - ci.confmode = ZT_CONF_CONF | ZT_CONF_TALKER; - /* Put on conf */ - if (ioctl(myrpt->rem_rxchannel->fds[0],ZT_SETCONF,&ci) == -1) - { - ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n"); - pthread_exit(NULL); - } - } + ast_write(l->chan,f); } - /* if RX un-key */ - if (f->subclass == AST_CONTROL_RADIO_UNKEY) + if (f->frametype == AST_FRAME_CONTROL) { - if (debug) printf("@@@@ remote rx un-key\n"); - if (!myrpt->remotetx) + if (f->subclass == AST_CONTROL_HANGUP) { - myrpt->remoterx = 0; - ci.chan = 0; - ci.confno = 0; - ci.confmode = 0; - /* Take off conf */ - if (ioctl(myrpt->rem_rxchannel->fds[0],ZT_SETCONF,&ci) == -1) - { - ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n"); - pthread_exit(NULL); - } + if (debug) printf("@@@@ rpt:Hung Up\n"); + ast_frfree(f); + toexit = 1; + break; } } + ast_frfree(f); + break; } - ast_frfree(f); - continue; + l = l->next; } - if (who == myrpt->rem_txchannel) /* if it was a read from remote tx */ + if (toexit) break; + if (who == myrpt->txpchannel) /* if it was a read from remote tx */ { - f = ast_read(myrpt->rem_txchannel); + f = ast_read(myrpt->txpchannel); if (!f) { if (debug) printf("@@@@ rpt:Hung Up\n"); @@ -1134,25 +1679,32 @@ pthread_attr_t attr; ast_frfree(f); continue; } - } + ast_mutex_lock(&myrpt->lock); ast_hangup(myrpt->pchannel); + ast_hangup(myrpt->txpchannel); ast_hangup(myrpt->rxchannel); if (myrpt->txchannel != myrpt->rxchannel) ast_hangup(myrpt->txchannel); - if (myrpt->rem_rxchannel) + l = myrpt->links.next; + while(l != &myrpt->links) { - ast_hangup(myrpt->rem_rxchannel); - if (myrpt->rem_txchannel != myrpt->rem_rxchannel) - ast_hangup(myrpt->rem_txchannel); + struct rpt_link *ll = l; + /* remove from queue */ + remque((struct qelem *) l); + /* hang-up on call to device */ + ast_hangup(l->chan); + ast_hangup(l->pchan); + l = l->next; + free(ll); } + ast_mutex_unlock(&myrpt->lock); if (debug) printf("@@@@ rpt:Hung up channel\n"); - pthread_exit(NULL); + pthread_exit(NULL); return NULL; } static void *rpt_master(void *ignore) { -struct ast_config *cfg; char *this,*val; int i,n; @@ -1170,17 +1722,19 @@ int i,n; n = 0; while((this = ast_category_browse(cfg,this)) != NULL) { + if (!strcmp(this,NODES)) continue; ast_log(LOG_DEBUG,"Loading config for repeater %s\n",this); + ast_mutex_init(&rpt_vars[n].lock); + rpt_vars[n].tele.next = &rpt_vars[n].tele; + rpt_vars[n].tele.prev = &rpt_vars[n].tele; rpt_vars[n].name = this; rpt_vars[n].rxchanname = ast_variable_retrieve(cfg,this,"rxchannel"); rpt_vars[n].txchanname = ast_variable_retrieve(cfg,this,"txchannel"); - rpt_vars[n].rem_rxchanname = ast_variable_retrieve(cfg,this,"remote_rxchannel"); - rpt_vars[n].rem_txchanname = ast_variable_retrieve(cfg,this,"remote_txchannel"); rpt_vars[n].ourcontext = ast_variable_retrieve(cfg,this,"context"); if (!rpt_vars[n].ourcontext) rpt_vars[n].ourcontext = this; rpt_vars[n].ourcallerid = ast_variable_retrieve(cfg,this,"callerid"); rpt_vars[n].acctcode = ast_variable_retrieve(cfg,this,"accountcode"); - rpt_vars[n].idrecording = ast_variable_retrieve(cfg,this,"idrecording"); + rpt_vars[n].ident = ast_variable_retrieve(cfg,this,"idrecording"); val = ast_variable_retrieve(cfg,this,"hangtime"); if (val) rpt_vars[n].hangtime = atoi(val); else rpt_vars[n].hangtime = HANGTIME; @@ -1193,40 +1747,381 @@ int i,n; val = ast_variable_retrieve(cfg,this,"simple"); if (val) rpt_vars[n].simple = ast_true(val); else rpt_vars[n].simple = 0; + val = ast_variable_retrieve(cfg,this,"remote"); + if (val) rpt_vars[n].remote = ast_true(val); + else rpt_vars[n].remote = 0; rpt_vars[n].tonezone = ast_variable_retrieve(cfg,this,"tonezone"); n++; } + nrpts = n; ast_log(LOG_DEBUG, "Total of %d repeaters configured.\n",n); /* start em all */ for(i = 0; i < n; i++) { if (!rpt_vars[i].rxchanname) { - ast_log(LOG_WARNING,"Did not specify rxchanname for repeater %s\n",rpt_vars[i].name); + ast_log(LOG_WARNING,"Did not specify rxchanname for node %s\n",rpt_vars[i].name); pthread_exit(NULL); } - if (!rpt_vars[i].idrecording) + /* if is a remote, dont start one for it */ + if (rpt_vars[i].remote) continue; + if (!rpt_vars[i].ident) { - ast_log(LOG_WARNING,"Did not specify idrecording for repeater %s\n",rpt_vars[i].name); + ast_log(LOG_WARNING,"Did not specify ident for node %s\n",rpt_vars[i].name); pthread_exit(NULL); } - pthread_create(&rpt_vars[i].rpt_id_thread,NULL,rpt,(void *) &rpt_vars[i]); + pthread_create(&rpt_vars[i].rpt_thread,NULL,rpt,(void *) &rpt_vars[i]); } /* wait for first one to die (should be never) */ - pthread_join(rpt_vars[0].rpt_id_thread,NULL); + pthread_join(rpt_vars[0].rpt_thread,NULL); pthread_exit(NULL); } +static int rpt_exec(struct ast_channel *chan, void *data) +{ + int res=-1,i,keyed = 0,rem_totx; + struct localuser *u; + char tmp[256]; + char *options,*stringp,*tele; + struct rpt *myrpt; + struct ast_frame *f; + struct ast_channel *who; + struct ast_channel *cs[20]; + struct rpt_link *l; + ZT_CONFINFO ci; /* conference info */ + int ms,elap; + + if (!data || ast_strlen_zero((char *)data)) { + ast_log(LOG_WARNING, "Rpt requires an argument (system node)\n"); + return -1; + } + strncpy(tmp, (char *)data, sizeof(tmp)-1); + stringp=tmp; + strsep(&stringp, "|"); + options = strsep(&stringp, "|"); + myrpt = NULL; + /* see if we can find our specified one */ + for(i = 0; i < nrpts; i++) + { + /* if name matches, assign it and exit loop */ + if (!strcmp(tmp,rpt_vars[i].name)) + { + myrpt = &rpt_vars[i]; + break; + } + } + if (myrpt == NULL) + { + ast_log(LOG_WARNING, "Cannot find specified system node %s\n",tmp); + return -1; + } + /* if is not a remote */ + if (!myrpt->remote) + { + char *b,*b1; + + /* look at callerid to see what node this comes from */ + if (!chan->callerid) /* if doesnt have callerid */ + { + ast_log(LOG_WARNING, "Trying to use busy link on %s\n",tmp); + return -1; + } + ast_callerid_parse(chan->callerid,&b,&b1); + ast_shrink_phone_number(b1); + if (!strcmp(myrpt->name,b1)) + { + ast_log(LOG_WARNING, "Trying to link to self!!\n"); + return -1; + } + ast_mutex_lock(&myrpt->lock); + l = myrpt->links.next; + /* try to find this one in queue */ + while(l != &myrpt->links) + { + /* if found matching string */ + if (!strcmp(l->name,b1)) break; + } + /* if found */ + if (l != &myrpt->links) + { + /* remove from queue */ + remque((struct qelem *) l); + ast_mutex_unlock(&myrpt->lock); + /* hang-up on call to device */ + ast_hangup(l->chan); + ast_hangup(l->pchan); + free(l); + usleep(500000); + } else + ast_mutex_unlock(&myrpt->lock); + /* establish call in tranceive mode */ + l = malloc(sizeof(struct rpt_link)); + if (!l) + { + ast_log(LOG_WARNING, "Unable to malloc\n"); + pthread_exit(NULL); + } + /* zero the silly thing */ + memset((char *)l,0,sizeof(struct rpt_link)); + l->mode = 1; + strncpy(l->name,b1,MAXNODESTR - 1); + l->isremote = 0; + l->chan = chan; + l->connected = 1; + ast_set_read_format(l->chan,AST_FORMAT_SLINEAR); + ast_set_write_format(l->chan,AST_FORMAT_SLINEAR); + /* allocate a pseudo-channel thru asterisk */ + l->pchan = ast_request("zap",AST_FORMAT_SLINEAR,"pseudo"); + if (!l->pchan) + { + fprintf(stderr,"rpt:Sorry unable to obtain pseudo channel\n"); + pthread_exit(NULL); + } + ast_set_read_format(l->pchan,AST_FORMAT_SLINEAR); + ast_set_write_format(l->pchan,AST_FORMAT_SLINEAR); + /* make a conference for the tx */ + ci.chan = 0; + ci.confno = myrpt->conf; + ci.confmode = ZT_CONF_CONF | ZT_CONF_LISTENER | ZT_CONF_TALKER; + /* first put the channel on the conference in proper mode */ + if (ioctl(l->pchan->fds[0],ZT_SETCONF,&ci) == -1) + { + ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n"); + pthread_exit(NULL); + } + ast_mutex_lock(&myrpt->lock); + /* insert at end of queue */ + insque((struct qelem *)l,(struct qelem *)myrpt->links.next); + ast_mutex_unlock(&myrpt->lock); + if (chan->_state != AST_STATE_UP) { + ast_answer(chan); + } + return AST_PBX_KEEPALIVE; + } + /* if remote, error if anyone else already linked */ + if (myrpt->remoteon) + { + ast_mutex_unlock(&myrpt->lock); + ast_log(LOG_WARNING, "Trying to use busy link on %s\n",tmp); + return -1; + } + LOCAL_USER_ADD(u); + tele = strchr(myrpt->rxchanname,'/'); + if (!tele) + { + fprintf(stderr,"rpt:Dial number must be in format tech/number\n"); + ast_mutex_unlock(&myrpt->lock); + pthread_exit(NULL); + } + *tele++ = 0; + ast_mutex_lock(&myrpt->lock); + myrpt->rxchannel = ast_request(myrpt->rxchanname,AST_FORMAT_SLINEAR,tele); + if (myrpt->rxchannel) + { + ast_set_read_format(myrpt->rxchannel,AST_FORMAT_SLINEAR); + ast_set_write_format(myrpt->rxchannel,AST_FORMAT_SLINEAR); + myrpt->rxchannel->whentohangup = 0; + myrpt->rxchannel->appl = "Apprpt"; + myrpt->rxchannel->data = "(Repeater Rx)"; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "rpt (Rx) initiating call to %s/%s on %s\n", + myrpt->rxchanname,tele,myrpt->rxchannel->name); + ast_mutex_unlock(&myrpt->lock); + ast_call(myrpt->rxchannel,tele,999); + ast_mutex_lock(&myrpt->lock); + } + else + { + fprintf(stderr,"rpt:Sorry unable to obtain Rx channel\n"); + ast_mutex_unlock(&myrpt->lock); + pthread_exit(NULL); + } + *--tele = '/'; + if (myrpt->txchanname) + { + tele = strchr(myrpt->txchanname,'/'); + if (!tele) + { + fprintf(stderr,"rpt:Dial number must be in format tech/number\n"); + ast_mutex_unlock(&myrpt->lock); + pthread_exit(NULL); + } + *tele++ = 0; + myrpt->txchannel = ast_request(myrpt->txchanname,AST_FORMAT_SLINEAR,tele); + if (myrpt->txchannel) + { + ast_set_read_format(myrpt->txchannel,AST_FORMAT_SLINEAR); + ast_set_write_format(myrpt->txchannel,AST_FORMAT_SLINEAR); + myrpt->txchannel->whentohangup = 0; + myrpt->txchannel->appl = "Apprpt"; + myrpt->txchannel->data = "(Repeater Rx)"; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "rpt (Tx) initiating call to %s/%s on %s\n", + myrpt->txchanname,tele,myrpt->txchannel->name); + ast_mutex_unlock(&myrpt->lock); + ast_call(myrpt->txchannel,tele,999); + ast_mutex_lock(&myrpt->lock); + } + else + { + fprintf(stderr,"rpt:Sorry unable to obtain Tx channel\n"); + ast_mutex_unlock(&myrpt->lock); + pthread_exit(NULL); + } + *--tele = '/'; + } + else + { + myrpt->txchannel = myrpt->rxchannel; + } + myrpt->remoterx = 0; + myrpt->remotetx = 0; + myrpt->remoteon = 1; + ast_mutex_unlock(&myrpt->lock); + ast_set_write_format(chan, ast_best_codec(chan->nativeformats)); + ast_set_read_format(chan, ast_best_codec(chan->nativeformats)); + /* if we are on 2w loop and are a remote, turn EC on */ + if (myrpt->remote && (myrpt->rxchannel == myrpt->txchannel)) + { + i = 128; + ioctl(myrpt->rxchannel->fds[0],ZT_ECHOCANCEL,&i); + } + if (chan->_state != AST_STATE_UP) { + ast_answer(chan); + } + cs[0] = chan; + cs[1] = myrpt->rxchannel; + for(;;) + { + if (ast_check_hangup(chan)) break; + if (ast_check_hangup(myrpt->rxchannel)) break; + ms = MSWAIT; + who = ast_waitfor_n(cs,2,&ms); + if (who == NULL) ms = 0; + elap = MSWAIT - ms; + if (!ms) continue; + rem_totx = keyed; + if (rem_totx && (!myrpt->remotetx)) + { + myrpt->remotetx = 1; + ast_indicate(myrpt->txchannel,AST_CONTROL_RADIO_KEY); + } + if ((!rem_totx) && myrpt->remotetx) + { + myrpt->remotetx = 0; + ast_indicate(myrpt->txchannel,AST_CONTROL_RADIO_UNKEY); + } + if (who == chan) /* if it was a read from incomming */ + { + f = ast_read(chan); + if (!f) + { + if (debug) printf("@@@@ link:Hung Up\n"); + break; + } + if (f->frametype == AST_FRAME_VOICE) + { + /* if not transmitting, zero-out audio */ + if (!myrpt->remotetx) + memset(f->data,0,f->datalen); + ast_write(myrpt->txchannel,f); + } + else if (f->frametype == AST_FRAME_TEXT) + { + handle_link_data(myrpt,f->data); + } + else if (f->frametype == AST_FRAME_CONTROL) + { + if (f->subclass == AST_CONTROL_HANGUP) + { + if (debug) printf("@@@@ rpt:Hung Up\n"); + ast_frfree(f); + break; + } + /* if RX key */ + if (f->subclass == AST_CONTROL_RADIO_KEY) + { + if (debug) printf("@@@@ rx key\n"); + keyed = 1; + } + /* if RX un-key */ + if (f->subclass == AST_CONTROL_RADIO_UNKEY) + { + if (debug) printf("@@@@ rx un-key\n"); + keyed = 0; + } + } + ast_frfree(f); + continue; + } + if (who == myrpt->rxchannel) /* if it was a read from radio */ + { + f = ast_read(myrpt->rxchannel); + if (!f) + { + if (debug) printf("@@@@ link:Hung Up\n"); + break; + } + if (f->frametype == AST_FRAME_VOICE) + { + if ((myrpt->remote) && (myrpt->remotetx)) + memset(f->data,0,f->datalen); + ast_write(chan,f); + } + else if (f->frametype == AST_FRAME_CONTROL) + { + if (f->subclass == AST_CONTROL_HANGUP) + { + if (debug) printf("@@@@ rpt:Hung Up\n"); + ast_frfree(f); + break; + } + /* if RX key */ + if (f->subclass == AST_CONTROL_RADIO_KEY) + { + if (debug) printf("@@@@ remote rx key\n"); + if (!myrpt->remotetx) + { + ast_indicate(chan,AST_CONTROL_RADIO_KEY); + myrpt->remoterx = 1; + } + } + /* if RX un-key */ + if (f->subclass == AST_CONTROL_RADIO_UNKEY) + { + if (debug) printf("@@@@ remote rx un-key\n"); + if (!myrpt->remotetx) + { + ast_indicate(chan,AST_CONTROL_RADIO_UNKEY); + myrpt->remoterx = 0; + } + } + } + ast_frfree(f); + continue; + } + + } + ast_mutex_lock(&myrpt->lock); + if (myrpt->rxchannel != myrpt->txchannel) ast_hangup(myrpt->txchannel); + ast_hangup(myrpt->rxchannel); + myrpt->remoteon = 0; + ast_mutex_unlock(&myrpt->lock); + LOCAL_USER_REMOVE(u); + return res; +} + int unload_module(void) { STANDARD_HANGUP_LOCALUSERS; + return ast_unregister_application(app); return 0; } int load_module(void) { pthread_create(&rpt_master_thread,NULL,rpt_master,NULL); - return 0; + return ast_register_application(app, rpt_exec, synopsis, descrip); } char *description(void) diff --git a/apps/rpt_flow.pdf b/apps/rpt_flow.pdf new file mode 100755 index 0000000000000000000000000000000000000000..2085af0f20e151e1eca7a086a976532296a4ad03 GIT binary patch literal 51935 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 rbt3qS)=zF-Hk{_Fn$BqYMJB7pw|QbK0J literal 0 Hc-jL100001 -- 2.47.2