Skip to content
Snippets Groups Projects
app_rpt.c 176 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * Asterisk -- An open source telephony toolkit.
    
     * Copyright (C) 2002-2005, Jim Dixon, WB6NIL
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * Jim Dixon, WB6NIL <jim@lambdatel.com>
    
     * Serious contributions by Steve RoDgers, WA6ZFT <hwstar@rodgers.sdcoxmail.com>
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * See http://www.asterisk.org for more information about
     * the Asterisk project. Please do not directly contact
     * any of the maintainers of this project for assistance;
     * the project provides a web site, mailing lists and IRC
     * channels for your use.
     *
    
    Mark Spencer's avatar
    Mark Spencer committed
     * This program is free software, distributed under the terms of
    
     * the GNU General Public License Version 2. See the LICENSE file
     * at the top of the source tree.
     */
    
    
     * \brief Radio Repeater / Remote Base program 
    
     * \author Jim Dixon, WB6NIL <jim@lambdatel.com>
     *
     * \note Serious contributions by Steve RoDgers, WA6ZFT <hwstar@rodgers.sdcoxmail.com>
    
     * 
     * See http://www.zapatatelephony.org/app_rpt.html
     *
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * Repeater / Remote Functions:
     * "Simple" Mode:  * - autopatch access, # - autopatch hangup
     * Normal mode:
    
     * See the function list in rpt.conf
    
     *
     *  To send an asterisk (*) while dialing or talking on phone,
     *  use the autopatch acess code.
    
     * status cmds:
     *
     *  1 - Force ID
     *  2 - Give Time of Day
     *  3 - Give software Version
     *
     * cop (control operator) cmds:
     *
     *  1 - System warm boot
     *  2 - System enable
     *  3 - System disable
    
     *  4 - Test Tone On
     *  5 - Dump System Variables on Console (debug)
     *  6 - PTT (phone mode only)
    
     *
     * ilink cmds:
     *
     *  1 - Disconnect specified link
     *  2 - Connect specified link -- monitor only
     *  3 - Connect specified link -- tranceive
     *  4 - Enter command mode on specified link
     *  5 - System status
     *  6 - Disconnect all links
     *
     * remote cmds:
     *
    
     *  1 - Recall Memory MM  (*000-*099) (Gets memory from rpt.conf)
     *  2 - Set VFO MMMMM*KKK*O   (Mhz digits, Khz digits, Offset)
     *  3 - Set Rx PL Tone HHH*D*
     *  4 - Set Tx PL Tone HHH*D* (Not currently implemented with DHE RBI-1)
     *  5 - Link Status (long)
     *  6 - Set operating mode M (FM, USB, LSB, AM, etc)
    
     *  100 - RX PL off (Default)
     *  101 - RX PL On
     *  102 - TX PL Off (Default)
     *  103 - TX PL On
     *  104 - Low Power
     *  105 - Med Power
     *  106 - Hi Power
    
     *  107 - Bump Down 20 Hz
     *  108 - Bump Down 100 Hz
     *  109 - Bump Down 500 Hz
     *  110 - Bump Up 20 Hz
     *  111 - Bump Up 100 Hz
     *  112 - Bump Up 500 Hz
     *  113 - Scan Down Slow
     *  114 - Scan Down Medium
     *  115 - Scan Down Fast
     *  116 - Scan Up Slow
     *  117 - Scan Up Medium
     *  118 - Scan Up Fast
     *  119 - Transmit allowing auto-tune
     *  140 - Link Status (brief)
    
    /*** MODULEINFO
    	<depend>zaptel</depend>
    	<defaultenabled>no</defaultenabled>
     ***/
    
    
    /* The following is JUST GROSS!! There is some soft of underlying problem,
       probably in channel_iax2.c, that causes an IAX2 connection to sometimes
       stop transmitting randomly. We have been working for weeks to try to
       locate it and fix it, but to no avail We finally decided to put our
       tail between our legs, and just make the radio system re-connect upon
       network failure. This just shouldnt have to be done. For normal operation,
       comment-out the following line */
    #define	RECONNECT_KLUDGE 
    
    
    /* maximum digits in DTMF buffer, and seconds after * for DTMF command timeout */
    
    
    #define	MAXMACRO 2048
    #define	MACROTIME 100
    #define	MACROPTIME 500
    
    #define	DISC_TIME 10000  /* report disc after 10 seconds of no connect */
    #define	MAX_RETRIES 5
    
    
    Jim Dixon's avatar
    Jim Dixon committed
    #define	REDUNDANT_TX_TIME 2000
    
    
    #define	RETRY_TIMER_MS 5000
    
    #define	FUNCTIONS "functions"
    #define TELEMETRY "telemetry"
    #define MORSE "morse"
    #define	FUNCCHAR '*'
    #define	ENDCHAR '#'
    
    enum{ID,PROC,TERM,COMPLETE,UNKEY,REMDISC,REMALREADY,REMNOTFOUND,REMGO,
    
    	CONNECTED,CONNFAIL,STATUS,TIMEOUT,ID1, STATS_TIME,
    
    	STATS_VERSION, IDTALKOVER, ARB_ALPHA, TEST_TONE, REV_PATCH,
    
    	TAILMSG, MACRO_NOTFOUND, MACRO_BUSY};
    
    enum {REM_SIMPLEX,REM_MINUS,REM_PLUS};
    
    enum {REM_LOWPWR,REM_MEDPWR,REM_HIPWR};
    
    
    enum {DC_INDETERMINATE, DC_REQ_FLUSH, DC_ERROR, DC_COMPLETE, DC_DOKEY};
    
    enum {SOURCE_RPT, SOURCE_LNK, SOURCE_RMT, SOURCE_PHONE, SOURCE_DPHONE};
    
    enum {DLY_TELEM, DLY_ID, DLY_UNKEY, DLY_CALLTERM};
    
    
    enum {REM_MODE_FM,REM_MODE_USB,REM_MODE_LSB,REM_MODE_AM};
    
    
    enum {HF_SCAN_OFF,HF_SCAN_DOWN_SLOW,HF_SCAN_DOWN_QUICK,
          HF_SCAN_DOWN_FAST,HF_SCAN_UP_SLOW,HF_SCAN_UP_QUICK,HF_SCAN_UP_FAST};
    
    #include "asterisk.h"
    
    ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
    
    
    Jim Dixon's avatar
    Jim Dixon committed
    #include <signal.h>
    
    Mark Spencer's avatar
    Mark Spencer committed
    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    
    Mark Spencer's avatar
    Mark Spencer committed
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <errno.h>
    #include <dirent.h>
    #include <ctype.h>
    #include <sys/stat.h>
    #include <sys/time.h>
    #include <sys/file.h>
    #include <sys/ioctl.h>
    
    Mark Spencer's avatar
    Mark Spencer committed
    #include <math.h>
    #include <tonezone.h>
    #include <linux/zaptel.h>
    
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #include "asterisk/utils.h"
    #include "asterisk/lock.h"
    #include "asterisk/file.h"
    #include "asterisk/logger.h"
    #include "asterisk/channel.h"
    #include "asterisk/callerid.h"
    #include "asterisk/pbx.h"
    #include "asterisk/module.h"
    #include "asterisk/translate.h"
    
    #include "asterisk/options.h"
    #include "asterisk/cli.h"
    #include "asterisk/config.h"
    #include "asterisk/say.h"
    #include "asterisk/localtime.h"
    
    
    static  char *tdesc = "Radio Repeater / Remote Base  version 0.42  02/25/2006";
    
    Jim Dixon's avatar
    Jim Dixon committed
    
    
    static char *app = "Rpt";
    
    static char *synopsis = "Radio Repeater/Remote Base Control System";
    
    static char *descrip = 
    
    "  Rpt(nodename[|options]):  Radio Remote Link or Remote Base Link Endpoint Process.\n"
    "\n"
    "    Not specifying an option puts it in normal endpoint mode (where source\n"
    "    IP and nodename are verified).\n"
    "\n"
    "    Options are as follows:\n"
    "\n"
    "        X - Normal endpoint mode WITHOUT security check. Only specify\n"
    "            this if you have checked security already (like with an IAX2\n"
    "            user/password or something).\n"
    "\n"
    "        Rannounce-string[|timeout[|timeout-destination]] - Amateur Radio\n"
    "            Reverse Autopatch. Caller is put on hold, and announcement (as\n"
    "            specified by the 'announce-string') is played on radio system.\n"
    "            Users of radio system can access autopatch, dial specified\n"
    "            code, and pick up call. Announce-string is list of names of\n"
    "            recordings, or \"PARKED\" to substitute code for un-parking,\n"
    "            or \"NODE\" to substitute node number.\n"
    "\n"
    "        P - Phone Control mode. This allows a regular phone user to have\n"
    "            full control and audio access to the radio system. For the\n"
    "            user to have DTMF control, the 'phone_functions' parameter\n"
    "            must be specified for the node in 'rpt.conf'. An additional\n"
    "            function (cop,6) must be listed so that PTT control is available.\n"
    "\n"
    "        D - Dumb Phone Control mode. This allows a regular phone user to\n"
    "            have full control and audio access to the radio system. In this\n"
    "            mode, the PTT is activated for the entire length of the call.\n"
    "            For the user to have DTMF control (not generally recomended in\n"
    "            this mode), the 'dphone_functions' parameter must be specified\n"
    "            for the node in 'rpt.conf'. Otherwise no DTMF control will be\n"
    "            available to the phone user.\n"
    "\n";
    
    static int debug = 0;  /* FIXME Set this >0 for extra debug output */
    
    char *discstr = "!!DISCONNECT!!";
    
    static char *remote_rig_ft897="ft897";
    static char *remote_rig_rbi="rbi";
    
    Mark Spencer's avatar
    Mark Spencer committed
    LOCAL_USER_DECL;
    
    #define	MSWAIT 200
    #define	HANGTIME 5000
    #define	TOTIME 180000
    #define	IDTIME 300000
    #define	MAXRPTS 20
    
    Mark Spencer's avatar
    Mark Spencer committed
    
    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;
    
    Jim Dixon's avatar
    Jim Dixon committed
    	char	hasconnected;
    
    	char	disced;
    
    Jim Dixon's avatar
    Jim Dixon committed
    	char	killme;
    
    	long	elaptime;
    	long	disctime;
    	long 	retrytimer;
    
    Jim Dixon's avatar
    Jim Dixon committed
    	long	retxtimer;
    
    	int	retries;
    
    	struct ast_channel *chan;	
    	struct ast_channel *pchan;	
    } ;
    
    struct rpt_tele
    {
    	struct rpt_tele *next;
    	struct rpt_tele *prev;
    	struct rpt *rpt;
    
    struct function_table_tag
    {
    	char action[ACTIONSIZE];
    
    	int (*function)(struct rpt *myrpt, char *param, char *digitbuf, 
    		int command_source, struct rpt_link *mylink);
    
    } ;
    
    /* Used to store the morse code patterns */
    
    struct morse_bits
    {		  
    	int len;
    	int ddcomb;
    } ;
    
    struct telem_defaults
    {
    	char name[20];
    	char value[80];
    } ;
    
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    static struct rpt
    {
    	char *name;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char *rxchanname;
    	char *txchanname;
    	char *ourcontext;
    	char *ourcallerid;
    	char *acctcode;
    
    	char *functions;
    	char *link_functions;
    
    	char *phone_functions;
    	char *dphone_functions;
    	char *nodes;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int hangtime;
    	int totime;
    	int idtime;
    
    	char tounkeyed;
    	char tonotify;
    	char enable;
    
    	char rem_dtmfbuf[MAXDTMF];
    	char cmdnode[50];
    	struct ast_channel *rxchannel,*txchannel;
    
    	struct ast_channel *pchannel,*txpchannel, *remchannel;
    
    	struct rpt_tele tele;
    	pthread_t rpt_call_thread,rpt_thread;
    
    	time_t dtmf_time,rem_dtmf_time,dtmf_time_rem;
    
    	int tailtimer,totimer,idtimer,txconf,conf,callmode,cidx,scantimer,tmsgtimer;
    
    Jim Dixon's avatar
    Jim Dixon committed
    	long	retxtimer;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char exten[AST_MAX_EXTENSION];
    
    	char freq[MAXREMSTR],rxpl[MAXREMSTR],txpl[MAXREMSTR];
    	char offset;
    	char powerlevel;
    	char txplon;
    	char rxplon;
    
    	char remmode;
    	char tunerequest;
    	char hfscanmode;
    	int hfscanstatus;
    	char lastlinknode[MAXNODESTR];
    
    Jim Dixon's avatar
    Jim Dixon committed
    	char stopgen;
    
    	int phone_longestfunc;
    	int dphone_longestfunc;
    
    	int link_longestfunc;
    	int longestfunc;
    
    	int longestnode;
    	int threadrestarts;		
    
    	int tailmessagetime;
    	int tailsquashedtime;
    	char *tailmessages[500];
    	int tailmessagemax;
    	int tailmessagen;
    
    	time_t disgorgetime;
    	time_t lastthreadrestarttime;
    
    	long	macrotimer;
    	char	*macro;
    	char	*startupmacro;
    	char	*memory;
    
    
    #ifdef	APP_RPT_LOCK_DEBUG
    
    #warning COMPILING WITH LOCK-DEBUGGING ENABLED!!
    
    #define	MAXLOCKTHREAD 100
    
    #define rpt_mutex_lock(x) _rpt_mutex_lock(x,myrpt,__LINE__)
    #define rpt_mutex_unlock(x) _rpt_mutex_unlock(x,myrpt,__LINE__)
    
    struct lockthread
    {
    	pthread_t id;
    	int lockcount;
    	int lastlock;
    	int lastunlock;
    } lockthreads[MAXLOCKTHREAD];
    
    
    struct by_lightning
    {
    	int line;
    	struct timeval tv;
    	struct rpt *rpt;
    	struct lockthread lockthread;
    } lock_ring[32];
    
    
    int lock_ring_index = 0;
    
    AST_MUTEX_DEFINE_STATIC(locklock);
    
    static struct lockthread *get_lockthread(pthread_t id)
    {
    int	i;
    
    	for(i = 0; i < MAXLOCKTHREAD; i++)
    	{
    		if (lockthreads[i].id == id) return(&lockthreads[i]);
    	}
    	return(NULL);
    }
    
    static struct lockthread *put_lockthread(pthread_t id)
    {
    int	i;
    
    	for(i = 0; i < MAXLOCKTHREAD; i++)
    	{
    		if (lockthreads[i].id == id)
    			return(&lockthreads[i]);
    	}
    	for(i = 0; i < MAXLOCKTHREAD; i++)
    	{
    		if (!lockthreads[i].id)
    		{
    			lockthreads[i].lockcount = 0;
    			lockthreads[i].lastlock = 0;
    			lockthreads[i].lastunlock = 0;
    			lockthreads[i].id = id;
    			return(&lockthreads[i]);
    		}
    	}
    	return(NULL);
    }
    
    
    static void rpt_mutex_spew(void)
    {
    	struct by_lightning lock_ring_copy[32];
    	int lock_ring_index_copy;
    	int i,j;
    	long long diff;
    	char a[100];
    	struct timeval lasttv;
    
    	ast_mutex_lock(&locklock);
    	memcpy(&lock_ring_copy, &lock_ring, sizeof(lock_ring_copy));
    	lock_ring_index_copy = lock_ring_index;
    	ast_mutex_unlock(&locklock);
    
    	lasttv.tv_sec = lasttv.tv_usec = 0;
    	for(i = 0 ; i < 32 ; i++)
    	{
    		j = (i + lock_ring_index_copy) % 32;
    		strftime(a,sizeof(a) - 1,"%m/%d/%Y %H:%M:%S",
    			localtime(&lock_ring_copy[j].tv.tv_sec));
    		diff = 0;
    		if(lasttv.tv_sec)
    		{
    			diff = (lock_ring_copy[j].tv.tv_sec - lasttv.tv_sec)
    				* 1000000;
    			diff += (lock_ring_copy[j].tv.tv_usec - lasttv.tv_usec);
    		}
    		lasttv.tv_sec = lock_ring_copy[j].tv.tv_sec;
    		lasttv.tv_usec = lock_ring_copy[j].tv.tv_usec;
    		if (!lock_ring_copy[j].tv.tv_sec) continue;
    		if (lock_ring_copy[j].line < 0)
    		{
    			ast_log(LOG_NOTICE,"LOCKDEBUG [#%d] UNLOCK app_rpt.c:%d node %s pid %x diff %lld us at %s.%06d\n",
    				i - 31,-lock_ring_copy[j].line,lock_ring_copy[j].rpt->name,(int) lock_ring_copy[j].lockthread.id,diff,a,(int)lock_ring_copy[j].tv.tv_usec);
    		}
    		else
    		{
    			ast_log(LOG_NOTICE,"LOCKDEBUG [#%d] LOCK app_rpt.c:%d node %s pid %x diff %lld us at %s.%06d\n",
    				i - 31,lock_ring_copy[j].line,lock_ring_copy[j].rpt->name,(int) lock_ring_copy[j].lockthread.id,diff,a,(int)lock_ring_copy[j].tv.tv_usec);
    		}
    	}
    }
    
    
    static void _rpt_mutex_lock(ast_mutex_t *lockp, struct rpt *myrpt, int line)
    {
    struct lockthread *t;
    pthread_t id;
    
    	id = pthread_self();
    	ast_mutex_lock(&locklock);
    	t = put_lockthread(id);
    	if (!t)
    	{
    		ast_mutex_unlock(&locklock);
    		return;
    	}
    	if (t->lockcount)
    	{
    		int lastline = t->lastlock;
    		ast_mutex_unlock(&locklock);
    		ast_log(LOG_NOTICE,"rpt_mutex_lock: Double lock request line %d node %s pid %x, last lock was line %d\n",line,myrpt->name,(int) t->id,lastline);
    		rpt_mutex_spew();
    		return;
    	}
    	t->lastlock = line;
    	t->lockcount = 1;
    	gettimeofday(&lock_ring[lock_ring_index].tv, NULL);
    	lock_ring[lock_ring_index].rpt = myrpt;
    	memcpy(&lock_ring[lock_ring_index].lockthread,t,sizeof(struct lockthread));
    	lock_ring[lock_ring_index++].line = line;
    	if(lock_ring_index == 32)
    		lock_ring_index = 0;
    	ast_mutex_unlock(&locklock);
    	ast_mutex_lock(lockp);
    }
    
    
    static void _rpt_mutex_unlock(ast_mutex_t *lockp, struct rpt *myrpt, int line)
    {
    struct lockthread *t;
    pthread_t id;
    
    	id = pthread_self();
    	ast_mutex_lock(&locklock);
    	t = put_lockthread(id);
    	if (!t)
    	{
    		ast_mutex_unlock(&locklock);
    		return;
    	}
    	if (!t->lockcount)
    	{
    		int lastline = t->lastunlock;
    		ast_mutex_unlock(&locklock);
    		ast_log(LOG_NOTICE,"rpt_mutex_lock: Double un-lock request line %d node %s pid %x, last un-lock was line %d\n",line,myrpt->name,(int) t->id,lastline);
    		rpt_mutex_spew();
    		return;
    	}
    	t->lastunlock = line;
    	t->lockcount = 0;
    	gettimeofday(&lock_ring[lock_ring_index].tv, NULL);
    	lock_ring[lock_ring_index].rpt = myrpt;
    	memcpy(&lock_ring[lock_ring_index].lockthread,t,sizeof(struct lockthread));
    	lock_ring[lock_ring_index++].line = -line;
    	if(lock_ring_index == 32)
    		lock_ring_index = 0;
    	ast_mutex_unlock(&locklock);
    	ast_mutex_unlock(lockp);
    }
    
    #else  /* APP_RPT_LOCK_DEBUG */
    
    #define rpt_mutex_lock(x) ast_mutex_lock(x)
    #define rpt_mutex_unlock(x) ast_mutex_unlock(x)
    
    #endif  /* APP_RPT_LOCK_DEBUG */
    
    
    /*
    * CLI extensions
    */
    
    /* Debug mode */
    static int rpt_do_debug(int fd, int argc, char *argv[]);
    
    static int rpt_do_dump(int fd, int argc, char *argv[]);
    
    
    static char debug_usage[] =
    "Usage: rpt debug level {0-7}\n"
    "       Enables debug messages in app_rpt\n";
    
    
    static char dump_usage[] =
    "Usage: rpt dump <nodename>\n"
    "       Dumps struct debug info to log\n";
    
    
    static struct ast_cli_entry  cli_debug =
    
            { { "rpt", "debug", "level" }, rpt_do_debug, 
    		"Enable app_rpt debugging", debug_usage };
    
    static struct ast_cli_entry  cli_dump =
            { { "rpt", "dump" }, rpt_do_dump,
    		"Dump app_rpt structs for debugging", dump_usage };
    
    static struct telem_defaults tele_defs[] = {
    	{"ct1","|t(350,0,100,3072)(500,0,100,3072)(660,0,100,3072)"},
    	{"ct2","|t(660,880,150,3072)"},
    	{"ct3","|t(440,0,150,3072)"},
    	{"ct4","|t(550,0,150,3072)"},
    	{"ct5","|t(660,0,150,3072)"},
    	{"ct6","|t(880,0,150,3072)"},
    	{"ct7","|t(660,440,150,3072)"},
    	{"ct8","|t(700,1100,150,3072)"},
    	{"remotemon","|t(1600,0,75,2048)"},
    	{"remotetx","|t(2000,0,75,2048)(0,0,75,0)(1600,0,75,2048)"},
    	{"cmdmode","|t(900,904,200,2048)"},
    	{"functcomplete","|t(1000,0,100,2048)(0,0,100,0)(1000,0,100,2048)"}
    } ;
    
    /*
    * Forward decl's - these suppress compiler warnings when funcs coded further down the file than thier invokation
    */
    
    static int setrbi(struct rpt *myrpt);
    
    
    
    /*
    * Define function protos for function table here
    */
    
    
    static int function_ilink(struct rpt *myrpt, char *param, char *digitbuf, int command_source, struct rpt_link *mylink);
    static int function_autopatchup(struct rpt *myrpt, char *param, char *digitbuf, int command_source, struct rpt_link *mylink);
    static int function_autopatchdn(struct rpt *myrpt, char *param, char *digitbuf, int command_source, struct rpt_link *mylink);
    static int function_status(struct rpt *myrpt, char *param, char *digitbuf, int command_source, struct rpt_link *mylink);
    static int function_cop(struct rpt *myrpt, char *param, char *digitbuf, int command_source, struct rpt_link *mylink);
    static int function_remote(struct rpt *myrpt, char *param, char *digitbuf, int command_source, struct rpt_link *mylink);
    
    static int function_macro(struct rpt *myrpt, char *param, char *digitbuf, int command_source, struct rpt_link *mylink);
    
    /*
    * Function table
    */
    
    static struct function_table_tag function_table[] = {
    	{"cop", function_cop},
    	{"autopatchup", function_autopatchup},
    	{"autopatchdn", function_autopatchdn},
    	{"ilink", function_ilink},
    	{"status", function_status},
    
    	{"remote", function_remote},
    	{"macro", function_macro}
    
    static int finddelim(char *str,char *strp[])
    {
    int     i,inquo;
    
            inquo = 0;
            i = 0;
            strp[i++] = str;
            if (!*str)
               {
                    strp[0] = 0;
                    return(0);
               }
            for(; *str; str++)
               {
                    if (*str == QUOTECHR)
                       {
                            if (inquo)
                               {
                                    *str = 0;
                                    inquo = 0;
                               }
                            else
                               {
                                    strp[i - 1] = str + 1;
                                    inquo = 1;
                               }
    		}
                    if ((*str == DELIMCHR) && (!inquo))
                    {
                            *str = 0;
                            strp[i++] = str + 1;
                    }
               }
            strp[i] = 0;
            return(i);
    
    }
    
    
    static int myatoi(char *str)
    {
    int	ret;
    
    	if (str == NULL) return -1;
    
    	/* leave this %i alone, non-base-10 input is useful here */
    	if (sscanf(str,"%i",&ret) != 1) return -1;
    
    /*
    * Enable or disable debug output at a given level at the console
    */
                                                                                                                                     
    static int rpt_do_debug(int fd, int argc, char *argv[])
    {
    	int newlevel;
    
            if (argc != 4)
                    return RESULT_SHOWUSAGE;
            newlevel = myatoi(argv[3]);
            if((newlevel < 0) || (newlevel > 7))
                    return RESULT_SHOWUSAGE;
            if(newlevel)
                    ast_cli(fd, "app_rpt Debugging enabled, previous level: %d, new level: %d\n", debug, newlevel);
            else
                    ast_cli(fd, "app_rpt Debugging disabled\n");
    
            debug = newlevel;                                                                                                                          
            return RESULT_SUCCESS;
    }
    
    
    /*
    * Dump rpt struct debugging onto console
    */
    
    static int rpt_do_dump(int fd, int argc, char *argv[])
    {
    	int i;
    
            if (argc != 3)
                    return RESULT_SHOWUSAGE;
    
    	for(i = 0; i < nrpts; i++)
    	{
    		if (!strcmp(argv[2],rpt_vars[i].name))
    		{
    			rpt_vars[i].disgorgetime = time(NULL) + 10; /* Do it 10 seconds later */
    		        ast_cli(fd, "app_rpt struct dump requested for node %s\n",argv[2]);
    		        return RESULT_SUCCESS;
    		}
    	}
    	return RESULT_FAILURE;
    }
    
    
    static int play_tone_pair(struct ast_channel *chan, int f1, int f2, int duration, int amplitude)
    {
    
    	int res;
    
            if ((res = ast_tonepair_start(chan, f1, f2, duration, amplitude)))
                    return res;
                                                                                                                                                
            while(chan->generatordata) {
    
    		if (ast_safe_sleep(chan,1)) return -1;
    	}
    
    }
    
    static int play_tone(struct ast_channel *chan, int freq, int duration, int amplitude)
    {
    	return play_tone_pair(chan, freq, 0, duration, amplitude);
    }
    
    static int play_silence(struct ast_channel *chan, int duration)
    {
    	return play_tone_pair(chan, 0, 0, duration, 0);
    }
    
    
    static int send_morse(struct ast_channel *chan, char *string, int speed, int freq, int amplitude)
    {
    
    static struct morse_bits mbits[] = {
    		{0, 0}, /* SPACE */
    		{0, 0}, 
    		{6, 18},/* " */
    		{0, 0},
    		{7, 72},/* $ */
    		{0, 0},
    		{0, 0},
    		{6, 30},/* ' */
    		{5, 13},/* ( */
    		{6, 29},/* ) */
    		{0, 0},
    		{5, 10},/* + */
    		{6, 51},/* , */
    		{6, 33},/* - */
    		{6, 42},/* . */
    		{5, 9}, /* / */
    		{5, 31},/* 0 */
    		{5, 30},/* 1 */
    		{5, 28},/* 2 */
    		{5, 24},/* 3 */
    		{5, 16},/* 4 */
    		{5, 0}, /* 5 */
    		{5, 1}, /* 6 */
    		{5, 3}, /* 7 */
    		{5, 7}, /* 8 */
    		{5, 15},/* 9 */
    		{6, 7}, /* : */
    		{6, 21},/* ; */
    		{0, 0},
    		{5, 33},/* = */
    		{0, 0},
    		{6, 12},/* ? */
    		{0, 0},
            	{2, 2}, /* A */
     		{4, 1}, /* B */
    		{4, 5}, /* C */
    		{3, 1}, /* D */
    		{1, 0}, /* E */
    		{4, 4}, /* F */
    		{3, 3}, /* G */
    		{4, 0}, /* H */
    		{2, 0}, /* I */
    		{4, 14},/* J */
    		{3, 5}, /* K */
    		{4, 2}, /* L */
    		{2, 3}, /* M */
    		{2, 1}, /* N */
    		{3, 7}, /* O */
    		{4, 6}, /* P */
    		{4, 11},/* Q */
    		{3, 2}, /* R */
    		{3, 0}, /* S */
    		{1, 1}, /* T */
    		{3, 4}, /* U */
    		{4, 8}, /* V */
    		{3, 6}, /* W */
    		{4, 9}, /* X */
    		{4, 13},/* Y */
    		{4, 3}  /* Z */
    	};
    
    
    	int dottime;
    	int dashtime;
    	int intralettertime;
    	int interlettertime;
    	int interwordtime;
    	int len, ddcomb;
    	int res;
    	int c;
    
    	int i;
    	int flags;
    			
    
    	res = 0;
    	
    	/* Approximate the dot time from the speed arg. */
    	
    	dottime = 900/speed;
    	
    	/* Establish timing releationships */
    	
    	dashtime = 3 * dottime;
    	intralettertime = dottime;
    	interlettertime = dottime * 4 ;
    	interwordtime = dottime * 7;
    	
    	for(;(*string) && (!res); string++){
    	
    		c = *string;
    		
    		/* Convert lower case to upper case */
    		
    		if((c >= 'a') && (c <= 'z'))
    			c -= 0x20;
    		
    		/* Can't deal with any char code greater than Z, skip it */
    		
    		if(c  > 'Z')
    			continue;
    		
    		/* If space char, wait the inter word time */
    					
    		if(c == ' '){
    			if(!res)
    				res = play_silence(chan, interwordtime);
    			continue;
    		}
    		
    		/* Subtract out control char offset to match our table */
    		
    		c -= 0x20;
    		
    		/* Get the character data */
    		
    		len = mbits[c].len;
    		ddcomb = mbits[c].ddcomb;
    		
    		/* Send the character */
    		
    		for(; len ; len--){
    			if(!res)
    				res = play_tone(chan, freq, (ddcomb & 1) ? dashtime : dottime, amplitude);
    			if(!res)
    				res = play_silence(chan, intralettertime);
    			ddcomb >>= 1;
    		}
    		
    		/* Wait the interletter time */
    		
    		if(!res)
    			res = play_silence(chan, interlettertime - intralettertime);
    	}
    	
    	/* Wait for all the frames to be sent */
    	
    	if (!res) 
    		res = ast_waitstream(chan, "");
    	ast_stopstream(chan);
    
    	
    	/*
    	* Wait for the zaptel driver to physically write the tone blocks to the hardware
    	*/
    
    	for(i = 0; i < 20 ; i++){
    		flags =  ZT_IOMUX_WRITEEMPTY | ZT_IOMUX_NOWAIT; 
    		res = ioctl(chan->fds[0], ZT_IOMUX, &flags);
    		if(flags & ZT_IOMUX_WRITEEMPTY)
    			break;
    		if( ast_safe_sleep(chan, 50)){
    			res = -1;
    			break;
    		}
    	}
    
    	
    
    	return res;
    }
    
    static int send_tone_telemetry(struct ast_channel *chan, char *tonestring)
    {
    	char *stringp;
    	char *tonesubset;
    	int f1,f2;
    	int duration;
    	int amplitude;
    	int res;
    
    	int i;
    	int flags;
    
    	
    	res = 0;
    	
    	stringp = ast_strdupa(tonestring);
    
    	for(;tonestring;){
    		tonesubset = strsep(&stringp,")");
    		if(!tonesubset)
    			break;
    		if(sscanf(tonesubset,"(%d,%d,%d,%d", &f1, &f2, &duration, &amplitude) != 4)
    			break;
    		res = play_tone_pair(chan, f1, f2, duration, amplitude);
    		if(res)
    			break;
    	}
    	if(!res)
    		res = play_tone_pair(chan, 0, 0, 100, 0); /* This is needed to ensure the last tone segment is timed correctly */
    	
    	if (!res) 
    		res = ast_waitstream(chan, "");
    	ast_stopstream(chan);
    
    
    	/*
    	* Wait for the zaptel driver to physically write the tone blocks to the hardware
    	*/