]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
Version 0.1.9 from FTP
authorMark Spencer <markster@digium.com>
Tue, 19 Jun 2001 16:17:52 +0000 (16:17 +0000)
committerMark Spencer <markster@digium.com>
Tue, 19 Jun 2001 16:17:52 +0000 (16:17 +0000)
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@335 65c4cc65-6c06-0410-ace0-fbb531ad65f3

channels/chan_modem.c
channels/chan_modem_bestdata.c [new file with mode: 0755]

index 9c4f7d234fb4d48875f952bdd82b0846d283eb44..ea5757e2f583734e46bd145ad82ff42850df3d14 100755 (executable)
@@ -53,7 +53,7 @@ static char context[AST_MAX_EXTENSION]= "default";
 static char language[MAX_LANGUAGE] = "";
 
 /* Initialization String */
-static char initstr[AST_MAX_INIT_STR] = "ATE1Q0";
+static char initstr[AST_MAX_INIT_STR] = "ATE0Q0";
 
 /* Default MSN */
 static char msn[AST_MAX_EXTENSION]="";
@@ -161,9 +161,10 @@ int ast_unregister_modem_driver(struct ast_modem_driver *mc)
 
 static int modem_call(struct ast_channel *ast, char *idest, int timeout)
 {
+       static int modem_hangup(struct ast_channel *ast);
        struct ast_modem_pvt *p;
        int ms = timeout;
-       char rdest[80], *where;
+       char rdest[80], *where, dstr[100];
        strncpy(rdest, idest, sizeof(rdest));
        strtok(rdest, ":");
        where = strtok(NULL, ":");
@@ -172,9 +173,17 @@ static int modem_call(struct ast_channel *ast, char *idest, int timeout)
                return -1;
        }
        p = ast->pvt->pvt;
-       if ((ast->state != AST_STATE_DOWN) && (ast->state != AST_STATE_RESERVED)) {
-               ast_log(LOG_WARNING, "modem_call called on %s, neither down nor reserved\n", ast->name);
-               return -1;
+       strcpy(dstr,where + p->stripmsd);
+       /* if not a transfer or just sending tones, must be in correct state */
+       if (strcasecmp(rdest, "transfer") && strcasecmp(rdest,"sendtones")) {
+               if ((ast->state != AST_STATE_DOWN) && (ast->state != AST_STATE_RESERVED)) {
+                       ast_log(LOG_WARNING, "modem_call called on %s, neither down nor reserved\n", ast->name);
+                       return -1;
+               }
+       } 
+       if (!strcasecmp(rdest,"transfer")) /* if a transfer, put in transfer stuff */
+       {
+               sprintf(dstr,"!,%s",where + p->stripmsd);
        }
        if (!strcasecmp(where, "handset")) {
                if (p->mc->setdev)
@@ -187,7 +196,7 @@ static int modem_call(struct ast_channel *ast, char *idest, int timeout)
                        if (p->mc->setdev(p, MODEM_DEV_TELCO_SPK))
                                return -1;
                if (p->mc->dial)
-                       p->mc->dial(p, where + p->stripmsd);
+                       p->mc->dial(p, dstr);
                ast->state = AST_STATE_DIALING;
                while((ast->state != AST_STATE_UP) && (ms > 0)) {
                        ms = ast_waitfor(ast, ms);
@@ -205,14 +214,20 @@ static int modem_call(struct ast_channel *ast, char *idest, int timeout)
 
 int ast_modem_send(struct ast_modem_pvt *p, char *cmd, int len)
 {
-       int res;
+       int i;
+       usleep(5000);
        if (!len) {
-               fprintf(p->f, "%s\r\n", cmd);
-               res = ast_modem_expect(p, cmd, ECHO_TIMEOUT);
-               if (res) {
-                       ast_log(LOG_WARNING, "Unexpected reply %s\n", p->response);
-                       return -1;
-               }
+               for(i = 0; cmd[i];)
+                  {
+                       if (fwrite(cmd + i,1,1,p->f) != 1)
+                          {
+                               if (errno == EWOULDBLOCK) continue;
+                               return -1;
+                          }
+                       i++;
+                  }
+               tcdrain(fileno(p->f)); 
+               fprintf(p->f,"\r\n");
                return 0;
        } else {
                if (fwrite(cmd, 1, len, p->f) < len)
@@ -223,18 +238,50 @@ int ast_modem_send(struct ast_modem_pvt *p, char *cmd, int len)
 
 int ast_modem_read_response(struct ast_modem_pvt *p, int timeout)
 {
-       int res = -1;
+       int res = -1,c,i;
        timeout *= 1000;
-       strncpy(p->response, "(No Response)", sizeof(p->response));
+       p->response[0] = 0;
+       c = i = 0;
        do {
                res = ast_waitfor_n_fd(&p->fd, 1, &timeout, NULL);
                if (res < 0) {
+                       strncpy(p->response, "(No Response)", sizeof(p->response));
                        return -1;
                }
-               /* Read a response */
-               fgets(p->response, sizeof(p->response), p->f);
-               return 0;
+                 /* get no more then buffer length */
+               while(i < sizeof(p->response) - 1)
+               {
+                       c = fgetc(p->f);  /* get a char */
+                       if (c < 1) /* if error */
+                       {
+                                 /* if nothing in buffer, go back into timeout stuff */
+                               if (errno == EWOULDBLOCK) break;
+                               /* return as error */
+                               strncpy(p->response, "(No Response)", sizeof(p->response));
+                               return -1;
+                       }
+                         /* save char */
+                       p->response[i++] = c;
+                       p->response[i] = 0;                     
+                         /* if end of input */
+                       if (c == '\n') break;
+               }
+               if (c >= 0)  /* if input terminated normally */
+               {
+                         /* ignore just CR/LF */
+                       if (!strcmp(p->response,"\r\n"))
+                       {
+                                 /* reset input buffer stuff */
+                               i = 0; 
+                               p->response[0] = 0;
+                       }
+                       else /* otherwise return with info in buffer */
+                       {
+                               return 0;
+                       }
+               }
        } while(timeout > 0);
+       strncpy(p->response, "(No Response)", sizeof(p->response));
        return -1;
 }
 
@@ -250,7 +297,7 @@ int ast_modem_expect(struct ast_modem_pvt *p, char *result, int timeout)
                }
                /* Read a response */
                fgets(p->response, sizeof(p->response), p->f);
-#if 0
+#if    0
                fprintf(stderr, "Modem said: %s", p->response);
 #endif
                if (!strncasecmp(p->response, result, strlen(result))) 
@@ -273,11 +320,12 @@ void ast_modem_trim(char *s)
 
 static int modem_setup(struct ast_modem_pvt *p, int baudrate)
 {
+
        /* Make sure there's a modem there and that it's in a reasonable 
           mode.  Set the baud rate, etc.  */
        char identity[256];
        char *ident = NULL;
-       char etx[2] = { 0x10, 0x03 };
+       char etx[2] = { 0x10, '!' }; 
        if (option_debug)
                ast_log(LOG_DEBUG, "Setting up modem %s\n", p->dev);
        if (ast_modem_send(p, etx, 2)) {
@@ -288,6 +336,7 @@ static int modem_setup(struct ast_modem_pvt *p, int baudrate)
                ast_log(LOG_WARNING, "Failed to send enter?\n");
                return -1;
        }
+       usleep(10000);
        /* Read any outstanding stuff */
        while(!ast_modem_read_response(p, 0));
        if (ast_modem_send(p, "ATZ", 0)) {
@@ -394,6 +443,7 @@ static int modem_answer(struct ast_channel *ast)
        return res;
 }
 
+#if    0
 static char modem_2digit(char c)
 {
        if (c == 12)
@@ -405,7 +455,7 @@ static char modem_2digit(char c)
        else
                return '?';
 }
-
+#endif
 static struct ast_frame *modem_read(struct ast_channel *ast)
 {
        struct ast_modem_pvt *p = ast->pvt->pvt;
@@ -469,6 +519,7 @@ static void modem_mini_packet(struct ast_modem_pvt *i)
 {
        struct ast_frame *fr;
        fr = i->mc->read(i);
+       if (!fr) return;
        if (fr->frametype == AST_FRAME_CONTROL) {
                if (fr->subclass == AST_CONTROL_RING) {
                        ast_modem_new(i, AST_STATE_RING);
diff --git a/channels/chan_modem_bestdata.c b/channels/chan_modem_bestdata.c
new file mode 100755 (executable)
index 0000000..59fbca1
--- /dev/null
@@ -0,0 +1,596 @@
+/*
+ * Asterisk -- A telephony toolkit for Linux.
+ *
+ * BestData 56SX-92 Voice Modem Driver (Conexant)
+ * 
+ * Copyright (C) 1999, Mark Spencer and 2001 Jim Dixon
+ *
+ * Mark Spencer <markster@linux-support.net>
+ * Jim Dixon <jim@lambdatel.com>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License
+ */
+
+#include <stdio.h>
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <pthread.h>
+#include <asterisk/vmodem.h>
+#include <asterisk/module.h>
+#include <asterisk/frame.h>
+#include <asterisk/logger.h>
+#include <asterisk/options.h>
+
+#define STATE_COMMAND  0
+#define STATE_VOICE    1
+#define        STATE_VOICEPLAY 2
+
+#define VRA "40"                       /* Number of 100ms of non-ring after a ring cadence after which we consider the lien to be answered */
+#define VRN "25"                       /* Number of 100ms of non-ring with no cadence after which we assume an answer */
+
+#define        RINGT   7000
+
+static char *breakcmd = "\020!";
+
+static char *desc = "BestData (Conexant V.90 Chipset) VoiceModem Driver";
+
+int usecnt;
+pthread_mutex_t usecnt_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static char *bestdata_idents[] = {
+       /* Identify BestData Modem */
+       "ACF3_V1.010-V90_P21_FSH",
+       NULL
+};
+
+static int bestdata_startrec(struct ast_modem_pvt *p)
+{
+static int bestdata_break(struct ast_modem_pvt *p);
+
+       if (p->ministate != STATE_COMMAND) bestdata_break(p);
+       if (ast_modem_send(p, "AT+VRX", 0) ||
+            ast_modem_expect(p, "CONNECT", 5)) {
+               ast_log(LOG_WARNING, "Unable to start recording\n");
+               return -1;
+       }
+       p->ministate = STATE_VOICE;
+       return 0;
+}
+
+static int bestdata_startplay(struct ast_modem_pvt *p)
+{
+static int bestdata_break(struct ast_modem_pvt *p);
+
+       if (p->ministate != STATE_COMMAND) bestdata_break(p);
+       if (ast_modem_send(p, "AT+VTX", 0) ||
+            ast_modem_expect(p, "CONNECT", 5)) {
+               ast_log(LOG_WARNING, "Unable to start recording\n");
+               return -1;
+       }
+       p->ministate = STATE_VOICEPLAY;
+       return 0;
+}
+
+static int bestdata_break(struct ast_modem_pvt *p)
+{
+       if (ast_modem_send(p, breakcmd, 2)) {
+               ast_log(LOG_WARNING, "Failed to break\n");
+               return -1;
+       }
+       p->ministate = STATE_COMMAND;
+       usleep(10000);
+       /* Read any outstanding junk */
+       while(!ast_modem_read_response(p, 1));
+       if (ast_modem_send(p, "AT", 0)) {
+               /* Modem might be stuck in some weird mode, try to get it out */
+               ast_modem_send(p, "+++", 3);
+               if (ast_modem_expect(p, "OK", 10)) {
+                       ast_log(LOG_WARNING, "Modem is not responding\n");
+                       return -1;
+               }
+               if (ast_modem_send(p, "AT", 0)) {
+                       ast_log(LOG_WARNING, "Modem is not responding\n");
+                       return -1;
+               }
+       }
+       if (ast_modem_expect(p, "OK", 5)) {
+               ast_log(LOG_WARNING, "Modem did not respond properly\n");
+               return -1;
+       }
+       return 0;
+}
+
+static int bestdata_init(struct ast_modem_pvt *p)
+{
+       if (option_debug)
+               ast_log(LOG_DEBUG, "bestdata_init()\n");
+       if (bestdata_break(p))
+               return -1;
+       /* Force into command mode */
+       p->ministate = STATE_COMMAND;
+       if (ast_modem_send(p, "AT+FCLASS=8", 0) ||
+            ast_modem_expect(p, "OK", 5)) {
+               ast_log(LOG_WARNING, "Unable to set to voice mode\n");
+               return -1;
+       }
+       if (ast_modem_send(p, "AT+VSM=1,8000,0,0", 0) ||
+            ast_modem_expect(p, "OK", 5)) {
+               ast_log(LOG_WARNING, "Unable to set to 8000 Hz sampling\n");
+               return -1;
+       }
+       if (ast_modem_send(p, "AT+VLS=0", 0) ||
+            ast_modem_expect(p, "OK", 5)) {
+               ast_log(LOG_WARNING, "Unable to set to telco interface\n");
+               return -1;
+       }
+       if (ast_modem_send(p, "AT+VRA=" VRA, 0) ||
+            ast_modem_expect(p, "OK", 5)) {
+               ast_log(LOG_WARNING, "Unable to set to 'ringback goes away' timer\n");
+               return -1;
+       }
+       if (ast_modem_send(p, "AT+VRN=" VRN, 0) ||
+            ast_modem_expect(p, "OK", 5)) {
+               ast_log(LOG_WARNING, "Unable to set to 'ringback never came timer'\n");
+               return -1;
+       }
+       if (ast_modem_send(p, "AT+VTD=63", 0) ||
+            ast_modem_expect(p, "OK", 5)) {
+               ast_log(LOG_WARNING, "Unable to set to tone detection\n");
+               return -1;
+       }
+       if (ast_modem_send(p, "AT+VCID=1", 0) ||
+            ast_modem_expect(p, "OK", 5)) {
+               ast_log(LOG_WARNING, "Unable to enable Caller*ID\n");
+               return -1;
+       }
+       return 0;
+}
+
+static struct ast_frame *bestdata_handle_escape(struct ast_modem_pvt *p, char esc)
+{
+       char name[30],nmbr[30];
+       time_t  now;
+
+       /* Handle escaped characters -- but sometimes we call it directly as 
+          a quick way to cause known responses */
+       p->fr.frametype = AST_FRAME_NULL;
+       p->fr.subclass = 0;
+       p->fr.data = NULL;
+       p->fr.datalen = 0;
+       p->fr.timelen = 0;
+       p->fr.offset = 0;
+       p->fr.mallocd = 0;
+       if (esc)
+               ast_log(LOG_DEBUG, "Escaped character '%c'\n", esc);
+       
+       switch(esc) {
+       case 'R': /* Pseudo ring */
+               time(&now);
+               if (now > (p->lastring + (RINGT / 1000)))
+                  { /* if stale, treat as new */
+                       p->gotclid = 0;
+                  }
+               if (p->gotclid)
+                  {
+                       p->fr.frametype = AST_FRAME_CONTROL;
+                       p->fr.subclass = AST_CONTROL_RING;
+                  }
+               p->ringt = RINGT;
+               time(&p->lastring);
+               return &p->fr;
+       case 'X': /* Caller-ID Spill */
+               if (p->gotclid) return &p->fr;
+               name[0] = nmbr[0] = 0;
+               for(;;)
+                  {
+                       char res[1000];
+
+                       if (ast_modem_read_response(p, 5)) break;
+                       strncpy(res, p->response, sizeof(res));
+                       ast_modem_trim(res);
+                       if (!strncmp(res,"\020.",2)) break;
+                       if (!strncmp(res,"NAME",4)) strcpy(name,res + 7);
+                       if (!strncmp(res,"NMBR",4)) strcpy(nmbr,res + 7);
+                  }
+               p->gotclid = 1;
+               if ((!strcmp(name,"O")) || (!strcmp(name,"P"))) name[0] = 0;
+               if ((!strcmp(nmbr,"O")) || (!strcmp(nmbr,"P"))) nmbr[0] = 0;
+               if ((name[0]) && (nmbr[0])) snprintf(p->cid,sizeof(p->cid),
+                       "\"%s\" <%s>",name,nmbr);
+               else if (name[0]) snprintf(p->cid,sizeof(p->cid),
+                       "\"%s\"",name);
+               else if (nmbr[0]) snprintf(p->cid,sizeof(p->cid),
+                       "%s",nmbr);
+               if (p->owner) p->owner->callerid = strdup(p->cid);
+               return &p->fr;
+       case '@': /* response from "OK" in command mode */
+               if (p->owner)
+                       p->owner->state = AST_STATE_UP;
+               if (bestdata_startrec(p)) return NULL;
+               p->fr.frametype = AST_FRAME_CONTROL;
+               p->fr.subclass = AST_CONTROL_RING;
+               return &p->fr;
+       case 'b': /* Busy signal */
+               p->fr.frametype = AST_FRAME_CONTROL;
+               p->fr.subclass = AST_CONTROL_BUSY;
+               return &p->fr;
+       case 'o': /* Overrun */
+               ast_log(LOG_WARNING, "Overflow on modem, flushing buffers\n");
+               if (ast_modem_send(p, "\0x10E", 2)) 
+                       ast_log(LOG_WARNING, "Unable to flush buffers\n");
+               return &p->fr;  
+       case '0':  /* All the DTMF characters */
+       case '1':
+       case '2':
+       case '3':
+       case '4':
+       case '5':
+       case '6':
+       case '7':
+       case '8':
+       case '9':
+       case '*':
+       case '#':
+       case 'A':
+       case 'B':
+       case 'C':
+       case 'D':
+               p->dtmfrx = esc;  /* save this for when its done */
+               return &p->fr;  
+       case '/': /* Start of DTMF tone shielding */
+               p->dtmfrx = ' ';
+               return &p->fr;  
+       case '~': /* DTMF transition to off */
+               if (p->dtmfrx > ' ')
+                  {
+                       p->fr.frametype = AST_FRAME_DTMF;
+                       p->fr.subclass = p->dtmfrx;
+                  }
+               p->dtmfrx = 0;
+               return &p->fr;  
+       case 'u': /* Underrun */
+               ast_log(LOG_WARNING, "Data underrun\n");
+               /* Fall Through */
+       case CHAR_ETX: /* End Transmission */
+       case 'd': /* Dialtone */
+       case 'c': /* Calling Tone */
+       case 'e': /* European version */
+       case 'a': /* Answer Tone */
+       case 'f': /* Bell Answer Tone */
+       case 'T': /* Timing mark */
+       case 't': /* Handset off hook */
+       case 'h': /* Handset hungup */
+       case 0: /* Pseudo signal */
+               /* Ignore */
+               return &p->fr;  
+       default:
+               ast_log(LOG_DEBUG, "Unknown Escaped character '%c' (%d)\n", esc, esc);
+       }
+       return &p->fr;
+}
+
+static struct ast_frame *bestdata_read(struct ast_modem_pvt *p)
+{
+       char result[256];
+       short *b;
+       struct ast_frame *f=NULL;
+       int res;
+       int x;
+
+       if (p->ministate == STATE_COMMAND) {
+               /* Read the first two bytes, first, in case it's a control message */
+               fread(result, 1, 2, p->f);
+               if (result[0] == CHAR_DLE) {
+                       return bestdata_handle_escape(p, result[1]);
+               } else {
+                       if (p->ringt) /* if ring timeout specified */
+                          {
+                               x = fileno(p->f);
+                               res = ast_waitfor_n_fd(&x, 1, &p->ringt, NULL);
+                               if (res < 0) {
+                                       return NULL;
+                               }
+                          }
+                       if ((result[0] == '\n') || (result[0] == '\r'))
+                               return bestdata_handle_escape(p, 0);
+                       /* Read the rest of the line */
+                       fgets(result + 2, sizeof(result) - 2, p->f);
+                       ast_modem_trim(result);
+                       if (!strcasecmp(result, "OK")) {
+                               /* If we're in immediate mode, reply now */
+                               if (p->mode == MODEM_MODE_IMMEDIATE)
+                                       return bestdata_handle_escape(p, '@');
+                       } else
+                       if (!strcasecmp(result, "BUSY")) {
+                               /* Same as a busy signal */
+                               return bestdata_handle_escape(p, 'b');
+                       } else
+                       if (!strcasecmp(result, "RING")) {
+                               return bestdata_handle_escape(p, 'R');
+                       } else
+                       if (!strcasecmp(result, "NO DIALTONE")) {
+                               /* There's no dialtone, so the line isn't working */
+                               ast_log(LOG_WARNING, "Device '%s' lacking dialtone\n", p->dev);
+                               return NULL;
+                       }
+                       ast_log(LOG_DEBUG, "Modem said '%s'\n", result);
+                       return bestdata_handle_escape(p, 0);
+               }
+       } else {
+                 /* if playing, start recording instead */
+               if (p->ministate == STATE_VOICEPLAY)
+                  {
+                       if (bestdata_startrec(p)) return NULL;
+                  }
+               /* We have to be more efficient in voice mode */
+               b = (short *)(p->obuf + p->obuflen);
+               while (p->obuflen/2 < 240) {
+                       /* Read ahead the full amount */
+                       res = fread(result, 1, 240 - p->obuflen/2, p->f);
+                       if (res < 1) {
+                               /* If there's nothing there, just continue on */
+                               if (errno == EAGAIN)
+                                       return bestdata_handle_escape(p, 0);
+                               ast_log(LOG_WARNING, "Read failed: %s\n", strerror(errno));
+                       }
+                       for (x=0;x<res;x++) {
+                               /* Process all the bytes that we've read */
+                               if (result[x] == CHAR_DLE) {
+                                       /* We assume there is no more than one signal frame among our
+                                          data.  */
+                                       if (f) ast_log(LOG_WARNING, "Warning: Dropped a signal frame\n");
+                                         /* if not a DLE in the data */
+                                       if (result[++x] != CHAR_DLE)
+                                          {
+                                               /* If bestdata_handle_escape says NULL, say it now, doesn't matter
+                                                  what else is there, the connection is dead. */
+                                               f = bestdata_handle_escape(p, result[x]);
+                                               if (p->dtmfrx) continue;
+                                               return(f);
+                                          }
+                               }
+                               /* Generate a 16-bit signed linear value from our 
+                                  unsigned 8-bit value */
+                               *(b++) = (((short)result[x]) - 127) * 0xff;
+                               p->obuflen += 2;
+                       }
+                       if (f) break;
+               }
+               /* If we have a control frame, return it now */
+               if (f) return f;
+               /* If we get here, we have a complete voice frame */
+               p->fr.frametype = AST_FRAME_VOICE;
+               p->fr.subclass = AST_FORMAT_SLINEAR;
+               p->fr.timelen = 30;
+               p->fr.data = p->obuf;
+               p->fr.datalen = p->obuflen;
+               p->fr.mallocd = 0;
+               p->fr.offset = AST_FRIENDLY_OFFSET;
+               p->fr.src = __FUNCTION__;
+               if (option_debug)
+                       ast_log(LOG_DEBUG, "bestdata_read(voice frame)\n");
+               p->obuflen = 0;
+               return &p->fr;
+       }
+       return NULL;
+}
+
+static int bestdata_write(struct ast_modem_pvt *p, struct ast_frame *f)
+{
+unsigned char  c,buf[32768]; /* I hope we dont have frames larger then 16K */
+int    i,j;
+short  *sp;
+unsigned long u;
+#define        DLE     16
+
+       if (p->owner && (p->owner->state == AST_STATE_UP) && 
+               (p->ministate != STATE_VOICEPLAY) && bestdata_startplay(p)) return -1;
+       sp = (short *) f->data;
+         /* stick DLE's in ahead of anything else */
+       for(i = 0,j = 0; i < f->datalen / 2; i++)
+          {
+               *sp *= 3;
+               u = *sp++ + 32768;
+               c = u >> 8;
+               if (c == DLE) buf[j++] = DLE;
+               buf[j++] = c;
+          }
+       do i = fwrite(buf,1,j,p->f);
+       while ((i == -1) && (errno == EWOULDBLOCK));
+       if (i != j)
+          {
+               ast_log(LOG_WARNING,"modem short write!!\n");
+               return -1;
+          }
+       fflush(p->f);
+       if (option_debug)
+               ast_log(LOG_DEBUG, "bestdata_write()\n");
+       return 0;
+}
+
+static char *bestdata_identify(struct ast_modem_pvt *p)
+{
+       char identity[256];
+       char mfr[80];
+       char mdl[80];
+       char rev[80];
+       ast_modem_send(p, "AT+FMM", 0);
+       ast_modem_read_response(p, 5);
+       strncpy(mdl, p->response, sizeof(mdl));
+       ast_modem_trim(mdl);
+       ast_modem_expect(p, "OK", 5);
+       ast_modem_send(p, "AT+FMI", 0);
+       ast_modem_read_response(p, 5);
+       strncpy(mfr, p->response, sizeof(mfr));
+       ast_modem_trim(mfr);
+       ast_modem_expect(p, "OK", 5);
+       ast_modem_send(p, "AT+FMR", 0);
+       ast_modem_read_response(p, 5);
+       strncpy(rev, p->response, sizeof(rev));
+       ast_modem_trim(rev);
+       ast_modem_expect(p, "OK", 5);
+       snprintf(identity, sizeof(identity), "%s Model %s Revision %s", mfr, mdl, rev);
+       return strdup(identity);
+}
+
+static void bestdata_incusecnt()
+{
+       ast_pthread_mutex_lock(&usecnt_lock);
+       usecnt++;
+       ast_pthread_mutex_unlock(&usecnt_lock);
+       ast_update_use_count();
+}
+
+static void bestdata_decusecnt()
+{
+       ast_pthread_mutex_lock(&usecnt_lock);
+       usecnt++;
+       ast_pthread_mutex_unlock(&usecnt_lock);
+       ast_update_use_count();
+}
+
+static int bestdata_answer(struct ast_modem_pvt *p)
+{
+       p->ringt = 0;
+       p->lastring = 0;
+       if (ast_modem_send(p, "AT+VLS=1", 0) ||
+            ast_modem_expect(p, "OK", 10)) {
+               ast_log(LOG_WARNING, "Unable to answer: %s", p->response);
+               return -1;
+       }
+       return 0;
+}
+
+static int bestdata_dialdigit(struct ast_modem_pvt *p, char digit)
+{
+       char cmd[80];
+
+       if (p->ministate != STATE_COMMAND) bestdata_break(p);
+       snprintf(cmd, sizeof(cmd), "AT+VTS=%c", digit);
+       if (ast_modem_send(p, cmd, 0) ||
+            ast_modem_expect(p, "OK", 10)) {
+               ast_log(LOG_WARNING, "Unable to answer: %s", p->response);
+               return -1;
+       }
+       return 0;
+}
+
+static int bestdata_dial(struct ast_modem_pvt *p, char *stuff)
+{
+       char cmd[800],a[20];
+       int i,j;
+
+       if (p->ministate != STATE_COMMAND)
+          {
+               bestdata_break(p);
+               strcpy(cmd,"AT+VTS=");
+               j = strlen(cmd);
+               for(i = 0; stuff[i]; i++)
+                  {
+                       switch(stuff[i])
+                          {
+                           case '!' :
+                               a[0] = stuff[i];
+                               a[1] = 0;
+                               break;
+                           case ',':
+                               strcpy(a,"[,,100]");
+                               break;
+                           default:
+                               sprintf(a,"{%c,7}",stuff[i]);
+                          }
+                       if (stuff[i + 1]) strcat(a,",");
+                       strcpy(cmd + j,a);
+                       j += strlen(a);
+                  }
+          }
+       else
+          {
+               snprintf(cmd, sizeof(cmd), "ATD%c %s", p->dialtype,stuff);
+          }
+       if (ast_modem_send(p, cmd, 0)) {
+               ast_log(LOG_WARNING, "Unable to dial\n");
+               return -1;
+       }
+       return 0;
+}
+
+static int bestdata_hangup(struct ast_modem_pvt *p)
+{
+       if (bestdata_break(p))
+               return -1;
+       /* Hangup by switching to data, then back to voice */
+       if (ast_modem_send(p, "ATH", 0) ||
+            ast_modem_expect(p, "OK", 8)) {
+               ast_log(LOG_WARNING, "Unable to set to data mode\n");
+               return -1;
+       }
+       if (ast_modem_send(p, "AT+FCLASS=8", 0) ||
+            ast_modem_expect(p, "OK", 5)) {
+               ast_log(LOG_WARNING, "Unable to set to voice mode\n");
+               return -1;
+       }
+       p->gotclid = 0;
+       p->ringt = 0;
+       p->lastring = 0;
+       p->dtmfrx = 0;
+       return 0;
+}
+
+static struct ast_modem_driver bestdata_driver =
+{
+       "BestData",
+       bestdata_idents,
+       AST_FORMAT_SLINEAR,
+       0,              /* Not full duplex */
+       bestdata_incusecnt,     /* incusecnt */
+       bestdata_decusecnt,     /* decusecnt */
+       bestdata_identify,      /* identify */
+       bestdata_init,  /* init */
+       NULL,   /* setdev */
+       bestdata_read,
+       bestdata_write,
+       bestdata_dial,  /* dial */
+       bestdata_answer,        /* answer */
+       bestdata_hangup,        /* hangup */
+       bestdata_startrec,      /* start record */
+       NULL,   /* stop record */
+       bestdata_startplay,     /* start playback */
+       NULL,   /* stop playback */
+       NULL,   /* set silence supression */
+       bestdata_dialdigit,     /* dialdigit */
+};
+
+
+
+int usecount(void)
+{
+       int res;
+       ast_pthread_mutex_lock(&usecnt_lock);
+       res = usecnt;
+       ast_pthread_mutex_unlock(&usecnt_lock);
+       return res;
+}
+
+int load_module(void)
+{
+       return ast_register_modem_driver(&bestdata_driver);
+}
+
+int unload_module(void)
+{
+       return ast_unregister_modem_driver(&bestdata_driver);
+}
+
+char *description()
+{
+       return desc;
+}
+
+char *key()
+{
+       return ASTERISK_GPL_KEY;
+}