regular status reporting disabled back again
[gnokii.git] / common / data / at-emulator.c
1 /*
2
3   $Id$
4
5   G N O K I I
6
7   A Linux/Unix toolset and driver for Nokia mobile phones.
8
9   Released under the terms of the GNU GPL, see file COPYING for more details.
10
11   This file provides a virtual modem or "AT" interface to the GSM phone by
12   calling code in gsm-api.c. Inspired by and in places copied from the Linux
13   kernel AT Emulator IDSN code by Fritz Elfert and others.
14   
15 */
16
17 #include "config.h"
18
19 #define         __data_at_emulator_c
20
21
22 #include <stdio.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <signal.h>
26 #include <grp.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <unistd.h>
32 #include <ctype.h>
33
34 #ifndef WIN32
35
36   #include <termios.h>
37
38 #endif
39
40 #include "config.h"
41 #include "misc.h"
42 #include "gsm-common.h"
43 #include "gsm-api.h"
44 #include "data/at-emulator.h"
45 #include "data/virtmodem.h"
46 #include "data/datapump.h"
47
48 #define MAX_LINE_LENGTH 256
49
50         /* Global variables */
51 bool ATEM_Initialised = false;  /* Set to true once initialised */
52 extern bool    CommandMode;
53 extern int ConnectCount;
54
55 static char ModelName[80]; /* This seems to be needed to avoid seg-faults */
56 static char PortName[80];
57
58
59         /* Local variables */
60 extern int      PtyRDFD;        /* File descriptor for reading and writing to/from */
61 extern int      PtyWRFD;        /* pty interface - only different in debug mode. */ 
62
63 static u8       ModemRegisters[MAX_MODEM_REGISTERS];
64 static char     CmdBuffer[MAX_CMD_BUFFERS][CMD_BUFFER_LENGTH];
65 static int      CurrentCmdBuffer;
66 static int      CurrentCmdBufferIndex;
67 static bool     VerboseResponse;        /* Switch betweek numeric (4) and text responses (ERROR) */
68 static char    IncomingCallNo;
69 static int     MessageFormat;          /* Message Format (text or pdu) */
70
71 static void     ATEM_ParseAT(char *cmd_buffer);
72
73         /* Current command parser */
74 static void     (*Parser)(char *);
75 //void  (*Parser)(char *) = ATEM_ParseAT; /* Current command parser */
76
77 static GSM_MemoryType   SMSType;
78 static int      SMSNumber;
79
80 static void     ATEM_InitRegisters(void);
81
82         /* If initialised in debug mode, stdin/out is used instead
83            of ptys for interface. */
84 bool    ATEM_Initialise(int read_fd, int write_fd, char *model, char *port)
85 {
86         PtyRDFD = read_fd;
87         PtyWRFD = write_fd;
88
89         strncpy(ModelName,model,80);
90         strncpy(PortName,port,80);
91
92                 /* Initialise command buffer variables */
93         CurrentCmdBuffer = 0;
94         CurrentCmdBufferIndex = 0;
95         
96                 /* Default to verbose reponses */
97         VerboseResponse = true;
98
99                 /* Initialise registers */
100         ATEM_InitRegisters();
101         
102                 /* Initial parser is AT routine */
103         Parser = ATEM_ParseAT;
104         
105                 /* Setup defaults for AT*C interpreter. */
106         SMSNumber = 1;
107         SMSType = GMT_ME;
108
109                 /* Default message format is PDU */
110         MessageFormat = PDU_MODE;
111
112         /* Set the call passup so that we get notified of incoming calls */
113         GSM->DialData(NULL,-1,&ATEM_CallPassup);
114
115                 /* We're ready to roll... */
116         ATEM_Initialised = true;
117         return (true);
118 }
119
120         /* Initialise the "registers" used by the virtual modem. */
121 static void     ATEM_InitRegisters(void) 
122 {
123
124         ModemRegisters[REG_RINGATA] = 0;
125         ModemRegisters[REG_RINGCNT] = 2;
126         ModemRegisters[REG_ESC] = '+';
127         ModemRegisters[REG_CR] = 10;
128         ModemRegisters[REG_LF] = 13;
129         ModemRegisters[REG_BS] = 8;
130         ModemRegisters[S35]=7;
131         ModemRegisters[REG_ECHO] = BIT_ECHO;
132
133 }
134
135
136 /* This gets called to indicate an incoming call */
137
138 void ATEM_CallPassup(char c)
139 {
140         if ((c >= 0) && (c < 9)) {
141                 ATEM_ModemResult(MR_RING);              
142                 IncomingCallNo = c;
143         }
144 }
145
146 static void     ATEM_StringOut(char *buffer);
147
148     /* Handler called when characters received from serial port.
149        calls state machine code to process it. */
150
151 void    ATEM_HandleIncomingData(char *buffer, int length)
152 {
153         int count;
154         unsigned char out_buf[3];       
155
156         for (count = 0; count < length ; count++) {
157                         /* Echo character if appropriate. */
158                 if (ModemRegisters[REG_ECHO] & BIT_ECHO) {
159                         out_buf[0] = buffer[count];
160                         out_buf[1] = 0;
161                         ATEM_StringOut(out_buf);
162                 }
163
164                         /* If it's a command terminator character, parse what
165                            we have so far then go to next buffer. */
166                 if (buffer[count] == ModemRegisters[REG_CR] ||
167                     buffer[count] == ModemRegisters[REG_LF]) {
168
169                         CmdBuffer[CurrentCmdBuffer][CurrentCmdBufferIndex] = 0x00;
170                         Parser(CmdBuffer[CurrentCmdBuffer]);
171
172                         CurrentCmdBuffer++;
173                         if (CurrentCmdBuffer >= MAX_CMD_BUFFERS) {
174                                 CurrentCmdBuffer = 0;
175                         }
176                         CurrentCmdBufferIndex = 0;
177                 } else {
178                         CmdBuffer[CurrentCmdBuffer][CurrentCmdBufferIndex] = buffer[count];
179                         CurrentCmdBufferIndex++;
180                         if (CurrentCmdBufferIndex >= CMD_BUFFER_LENGTH) {
181                                 CurrentCmdBufferIndex = CMD_BUFFER_LENGTH;
182                         }
183                 }
184         }
185 }     
186
187 static bool     ATEM_CommandPlusC(char **buf);
188 static bool     ATEM_CommandPlusG(char **buf);
189 static int              ATEM_GetNum(char **p);
190 static void     ATEM_ParseSMS(char *cmd_buffer);
191
192         /* Parser for standard AT commands.  cmd_buffer must be null terminated. */
193 static void     ATEM_ParseAT(char *cmd_buffer)
194 {
195         char *buf;
196         char number[30];
197
198         if (strncasecmp (cmd_buffer, "AT", 2) != 0) {
199                 ATEM_ModemResult(MR_ERROR);
200                 return;
201         }
202
203         for (buf = &cmd_buffer[2]; *buf;) {
204                 switch (toupper(*buf)) {
205
206                 case 'Z':
207                         buf++;
208                         break;
209                 case 'A':
210                         buf++;
211                         /* For now we'll also initialise the datapump + rlp code again */
212                         DP_Initialise(PtyRDFD, PtyWRFD);
213                         GSM->DialData(NULL, -1, &DP_CallPassup);
214                         GSM->AnswerCall(IncomingCallNo);
215                         CommandMode = false;
216                         return;
217                         break;
218                 case 'D':
219                         /* Dial Data :-) */
220                         /* FIXME - should parse this better */
221                         /* For now we'll also initialise the datapump + rlp code again */
222                         DP_Initialise(PtyRDFD, PtyWRFD);
223                         buf++;
224                         if (toupper(*buf) == 'T') buf++;
225                         if (*buf == ' ') buf++;
226                         strncpy(number, buf, 30);
227                         if (ModemRegisters[S35] == 0) GSM->DialData(number, 1, &DP_CallPassup);
228                         else GSM->DialData(number, 0, &DP_CallPassup);
229                         ATEM_StringOut("\n\r");
230                         CommandMode = false;
231                         return;
232                         break;
233                 case 'H':
234                         /* Hang Up */
235                         buf++;
236                         RLP_SetUserRequest(Disc_Req, true);
237                         GSM->CancelCall();
238                         break;
239                 case 'S':
240                         /* Change registers - only no. 35 for now */
241                         buf++;
242                         if (memcmp(buf, "35=", 3) == 0) {
243                                 buf += 3;
244                                 ModemRegisters[S35] = *buf - '0';
245                                 buf++;
246                         }
247                         break;
248                   /* E - Turn Echo on/off */
249                 case 'E':
250                         buf++;
251                         switch (ATEM_GetNum(&buf)) {
252                         case 0:
253                                 ModemRegisters[REG_ECHO] &= ~BIT_ECHO;
254                                 break;
255                         case 1:
256                                 ModemRegisters[REG_ECHO] |= BIT_ECHO;
257                                 break;
258                         default:
259                                 ATEM_ModemResult(MR_ERROR);
260                                 return;
261                         }
262                         break;
263                         
264                   /* Handle AT* commands (Nokia proprietary I think) */
265                 case '*':
266                         buf++;
267                         if (!strcasecmp(buf, "NOKIATEST")) {
268                                 ATEM_ModemResult(MR_OK); /* FIXME? */
269                                 return;
270                         } else {
271                                 if (!strcasecmp(buf, "C")) {
272                                         ATEM_ModemResult(MR_OK);
273                                         Parser = ATEM_ParseSMS;
274                                         return;
275                                 }
276                         }
277                         break;
278                         
279                                 /* + is the precursor to another set of commands +CSQ, +FCLASS etc. */
280                 case '+':
281                         buf++;
282                         switch (toupper(*buf)) {
283                         case 'C':
284                                 buf++;
285                                 /* Returns true if error occured */
286                                 if (ATEM_CommandPlusC(&buf) == true) {
287                                         return; 
288                                 }
289                                 break;
290                                 
291                         case 'G':
292                                 buf++;
293                                 /* Returns true if error occured */
294                                 if (ATEM_CommandPlusG(&buf) == true) {
295                                         return; 
296                                 }
297                                 break;
298                                 
299                         default:
300                                 ATEM_ModemResult(MR_ERROR);
301                                 return;
302                         }
303                         break;
304                         
305                 default: 
306                         ATEM_ModemResult(MR_ERROR);
307                         return;
308                 }
309         }
310         
311         ATEM_ModemResult(MR_OK);
312 }
313
314 static GSM_Error ATEM_ReadSMS(int number, GSM_MemoryType type, GSM_SMSMessage *message)
315 {
316         GSM_Error error;
317
318         message->MemoryType = type;
319         message->Location = number;
320         error = GSM->GetSMSMessage(message);
321
322         return error;
323 }
324
325 static void ATEM_PrintSMS(char *line, GSM_SMSMessage *message, int mode)
326 {
327         switch (mode) {
328         case INTERACT_MODE:
329                 gsprintf(line, MAX_LINE_LENGTH, _("\n\rDate/time: %d/%d/%d %d:%02d:%02d Sender: %s Msg Center: %s\n\rText: %s\n\r"), message->Time.Day, message->Time.Month, message->Time.Year, message->Time.Hour, message->Time.Minute, message->Time.Second, message->Sender, message->MessageCenter.Number, message->MessageText);
330                 break;
331         case TEXT_MODE:
332                 if (message->Coding==GSM_Coding_8bit)
333                         gsprintf(line, MAX_LINE_LENGTH, _("\"%s\",\"%s\",,\"%02d/%02d/%02d,%02d:%02d:%02d+%02d\"\n\r%s"), (message->Status ? _("REC READ") : _("REC UNREAD")), message->Sender, message->Time.Year, message->Time.Month, message->Time.Day, message->Time.Hour, message->Time.Minute, message->Time.Second, message->Time.Timezone, _("<Not implemented>"));
334                 else
335                         gsprintf(line, MAX_LINE_LENGTH, _("\"%s\",\"%s\",,\"%02d/%02d/%02d,%02d:%02d:%02d+%02d\"\n\r%s"), (message->Status ? _("REC READ") : _("REC UNREAD")), message->Sender, message->Time.Year, message->Time.Month, message->Time.Day, message->Time.Hour, message->Time.Minute, message->Time.Second, message->Time.Timezone, message->MessageText);
336                 break;
337         case PDU_MODE:
338                 gsprintf(line, MAX_LINE_LENGTH, _("<Not implemented>"));
339                 break;
340         default:
341                 gsprintf(line, MAX_LINE_LENGTH, _("<Unknown mode>"));
342                 break;
343         }
344 }
345
346 static void ATEM_EraseSMS(int number, GSM_MemoryType type)
347 {
348         GSM_SMSMessage message;
349         message.MemoryType = type;
350         message.Location = number;
351         if (GSM->DeleteSMSMessage(&message) == GE_NONE) {
352                 ATEM_ModemResult(MR_OK);
353         } else {
354                 ATEM_ModemResult(MR_ERROR);
355         }
356 }
357
358
359 static void ATEM_HandleSMS()
360 {
361         GSM_SMSMessage  message;
362         GSM_Error       error;
363         char            buffer[MAX_LINE_LENGTH];
364
365         error = ATEM_ReadSMS(SMSNumber, SMSType, &message);
366         switch (error) {
367         case GE_NONE:
368                 ATEM_PrintSMS(buffer, &message, INTERACT_MODE);
369                 ATEM_StringOut(buffer);
370                 break;
371         default:
372                 gsprintf(buffer, MAX_LINE_LENGTH, _("\n\rNo message under number %d\n\r"), SMSNumber);
373                 ATEM_StringOut(buffer);
374                 break;
375         }
376         return;
377 }
378
379 static void     ATEM_ParseDIR(char *cmd_buffer);
380
381         /* Parser for SMS interactive mode */
382 static void     ATEM_ParseSMS(char *buff)
383 {
384         if (!strcasecmp(buff, "HELP")) {
385                 ATEM_StringOut(_("\n\rThe following commands work...\n\r"));
386                 ATEM_StringOut("DIR\n\r");
387                 ATEM_StringOut("EXIT\n\r");
388                 ATEM_StringOut("HELP\n\r");
389                 return;
390         }
391
392         if (!strcasecmp(buff, "DIR")) {
393                 SMSNumber = 1;
394                 ATEM_HandleSMS();
395                 Parser = ATEM_ParseDIR;
396                 return;
397         }
398         if (!strcasecmp(buff, "EXIT")) {
399                 Parser = ATEM_ParseAT;
400                 ATEM_ModemResult(MR_OK);
401                 return;
402         } 
403         ATEM_ModemResult(MR_ERROR);
404 }
405
406         /* Parser for DIR sub mode of SMS interactive mode. */
407 static void     ATEM_ParseDIR(char *buff)
408 {
409         switch (toupper(*buff)) {
410                 case 'P':
411                         SMSNumber--;
412                         ATEM_HandleSMS();
413                         return;
414                 case 'N':
415                         SMSNumber++;
416                         ATEM_HandleSMS();
417                         return;
418                 case 'D':
419                         ATEM_EraseSMS(SMSNumber, SMSType);
420                         return;
421                 case 'Q':
422                         Parser= ATEM_ParseSMS;
423                         ATEM_ModemResult(MR_OK);
424                         return;
425         }
426         ATEM_ModemResult(MR_ERROR);
427 }
428  
429         /* Handle AT+C commands, this is a quick hack together at this
430            stage. */
431 static bool     ATEM_CommandPlusC(char **buf)
432 {
433         float           rflevel;
434         GSM_RFUnits     rfunits = GRF_CSQ;
435         char            buffer[MAX_LINE_LENGTH], buffer2[MAX_LINE_LENGTH];
436         int             status, index;
437         GSM_Error       error;
438         GSM_SMSMessage  message;
439
440         if (strncasecmp(*buf, "SQ", 2) == 0) {
441                 buf[0] += 2;
442
443                 if (GSM->GetRFLevel(&rfunits, &rflevel) == GE_NONE) {
444                         gsprintf(buffer, MAX_LINE_LENGTH, "\n\r+CSQ: %0.0f, 99", rflevel);
445                         ATEM_StringOut(buffer);
446                         return (false);
447                 } else {
448                         return (true);
449                 }
450         }
451                 /* AT+CGMI is Manufacturer information for the ME (phone) so
452                    it should be Nokia rather than gnokii... */
453         if (strncasecmp(*buf, "GMI", 3) == 0) {
454                 buf[0] += 3;
455                 ATEM_StringOut(_("\n\rNokia Mobile Phones"));
456                 return (false);
457         }
458
459                 /* AT+CGSN is IMEI */
460         if (strncasecmp(*buf, "GSN", 3) == 0) {
461                 buf[0] += 3;
462                 if (GSM->GetIMEI(buffer2) == GE_NONE) {
463                         gsprintf(buffer, MAX_LINE_LENGTH, "\n\r%s", buffer2);
464                         ATEM_StringOut(buffer);
465                         return (false);
466                 } else {
467                         return (true);
468                 }
469         }
470
471                 /* AT+CGMR is Revision (hardware) */
472         if (strncasecmp(*buf, "GMR", 3) == 0) {
473                 buf[0] += 3;
474                 if (GSM->GetRevision(buffer2) == GE_NONE) {
475                         gsprintf(buffer, MAX_LINE_LENGTH, "\n\r%s", buffer2);
476                         ATEM_StringOut(buffer);
477                         return (false);
478                 } else {
479                         return (true);
480                 }
481         }
482
483                 /* AT+CGMM is Model code  */
484         if (strncasecmp(*buf, "GMM", 3) == 0) {
485                 buf[0] += 3;
486                 if (GSM->GetModel(buffer2) == GE_NONE) {
487                         gsprintf(buffer, MAX_LINE_LENGTH, "\n\r%s", buffer2);
488                         ATEM_StringOut(buffer);
489                         return (false);
490                 } else {
491                         return (true);
492                 }
493         }
494
495                 /* AT+CMGF is mode selection for message format  */
496         if (strncasecmp(*buf, "MGF", 3) == 0) {
497                 buf[0] += 3;
498                 switch (**buf) {
499                 case '=':
500                         buf[0]++;
501                         switch (**buf) {
502                         case '0':
503                                 buf[0]++;
504                                 MessageFormat = PDU_MODE;
505                                 break;
506                         case '1':
507                                 buf[0]++;
508                                 MessageFormat = TEXT_MODE;
509                                 break;
510                         default:
511                                 return (true);
512                         }
513                         break;
514                 case '?':
515                         buf[0]++;
516                         gsprintf(buffer, MAX_LINE_LENGTH, "\n\r+CMGF: %d", MessageFormat);
517                         ATEM_StringOut(buffer);
518                         break;
519                 default:
520                         return (true);
521                 }
522                 return (false);
523         }
524
525                 /* AT+CMGR is reading a message */
526         if (strncasecmp(*buf, "MGR", 3) == 0) {
527                 buf[0] += 3;
528                 switch (**buf) {
529                 case '=':
530                         buf[0]++;
531                         index = atoi(*buf);
532                         buf[0] += strlen(*buf);
533
534                         error = ATEM_ReadSMS(index, SMSType, &message);
535                         switch (error) {
536                         case GE_NONE:
537                                 ATEM_PrintSMS(buffer2, &message, MessageFormat);
538                                 gsprintf(buffer, MAX_LINE_LENGTH, "\n\r+CMGR: %s", buffer2);
539                                 ATEM_StringOut(buffer);
540                                 break;
541                         default:
542                                 gsprintf(buffer, MAX_LINE_LENGTH, "\n\r+CMS ERROR: %d\n\r", error);
543                                 ATEM_StringOut(buffer);
544                                 return (true);
545                         }
546                         break;
547                 default:
548                         return (true);
549                 }
550                 return (false);
551         }
552
553                 /* AT+CMGL is listing messages */
554         if (strncasecmp(*buf, "MGL", 3) == 0) {
555                 buf[0]+=3;
556                 status = -1;
557
558                 switch (**buf) {
559                 case 0:
560                 case '=':
561                         buf[0]++;
562                         /* process <stat> parameter */
563                         if (*(*buf-1) == 0 || /* i.e. no parameter given */
564                                 strcasecmp(*buf, "1") == 0 ||
565                                 strcasecmp(*buf, "3") == 0 ||
566                                 strcasecmp(*buf, "\"REC READ\"") == 0 ||
567                                 strcasecmp(*buf, "\"STO SENT\"") == 0) {
568                                 status = GSS_SENTREAD;
569                         } else if (strcasecmp(*buf, "0") == 0 ||
570                                 strcasecmp(*buf, "2") == 0 ||
571                                 strcasecmp(*buf, "\"REC UNREAD\"") == 0 ||
572                                 strcasecmp(*buf, "\"STO UNSENT\"") == 0) {
573                                 status = GSS_NOTSENTREAD;
574                         } else if (strcasecmp(*buf, "4") == 0 ||
575                                 strcasecmp(*buf, "\"ALL\"") == 0) {
576                                 status = 4; /* ALL */
577                         } else {
578                                 return true;
579                         }
580                         buf[0] += strlen(*buf);
581
582                         /* check all message storages */
583                         for (index = 1; index <= 20; index++) {
584                                 error = ATEM_ReadSMS(index, SMSType, &message);
585                                 switch (error) {
586                                 case GE_NONE:
587                                         /* print messsage if it has the required status */
588                                         if (message.Status == status || status == 4 /* ALL */) {
589                                                 ATEM_PrintSMS(buffer2, &message, MessageFormat);
590                                                 gsprintf(buffer, MAX_LINE_LENGTH, "\n\r+CMGL: %d,%s", index, buffer2);
591                                                 ATEM_StringOut(buffer);
592                                         }
593                                         break;
594                                 case GE_EMPTYSMSLOCATION:
595                                         /* don't care if this storage is empty */
596                                         break;
597                                 default:
598                                         /* print other error codes and quit */
599                                         gsprintf(buffer, MAX_LINE_LENGTH, "\n\r+CMS ERROR: %d\n\r", error);
600                                         ATEM_StringOut(buffer);
601                                         return (true);
602                                 }
603                         }
604                         break;
605                 default:
606                         return (true);
607                 }
608                 return (false);
609         }
610
611         return (true);
612 }
613
614         /* AT+G commands.  Some of these responses are a bit tongue in cheek... */
615 static bool     ATEM_CommandPlusG(char **buf)
616 {
617         char            buffer[MAX_LINE_LENGTH];
618
619                 /* AT+GMI is Manufacturer information for the TA (Terminal Adaptor) */
620         if (strncasecmp(*buf, "MI", 3) == 0) {
621                 buf[0] += 2;
622
623                 ATEM_StringOut(_("\n\rHugh Blemings, Pavel Janík ml. and others..."));
624                 return (false);
625         }
626
627                 /* AT+GMR is Revision information for the TA (Terminal Adaptor) */
628         if (strncasecmp(*buf, "MR", 3) == 0) {
629                 buf[0] += 2;
630                 gsprintf(buffer, MAX_LINE_LENGTH, "\n\r%s %s %s", VERSION, __TIME__, __DATE__);
631
632                 ATEM_StringOut(buffer);
633                 return (false);
634         }
635
636                 /* AT+GMM is Model information for the TA (Terminal Adaptor) */
637         if (strncasecmp(*buf, "MM", 3) == 0) {
638                 buf[0] += 2;
639
640                 gsprintf(buffer, MAX_LINE_LENGTH, _("\n\rgnokii configured for %s on %s"), ModelName, PortName);
641                 ATEM_StringOut(buffer);
642                 return (false);
643         }
644
645                 /* AT+GSN is Serial number for the TA (Terminal Adaptor) */
646         if (strncasecmp(*buf, "SN", 3) == 0) {
647                 buf[0] += 2;
648
649                 gsprintf(buffer, MAX_LINE_LENGTH, _("\n\rnone built in, choose your own"));
650                 ATEM_StringOut(buffer);
651                 return (false);
652         }
653
654         return (true);
655 }
656
657         /* Send a result string back.  There is much work to do here, see
658            the code in the isdn driver for an idea of where it's heading... */
659 void    ATEM_ModemResult(int code) 
660 {
661         char    buffer[16];
662
663         if (VerboseResponse == false) {
664                 sprintf(buffer, "\n\r%d\n\r", code);
665                 ATEM_StringOut(buffer);
666         } else {
667                 switch (code) {
668                         case MR_OK:     
669                                         ATEM_StringOut("\n\rOK\n\r");
670                                         break;
671
672                         case MR_ERROR:
673                                         ATEM_StringOut("\n\rERROR\n\r");
674                                         break;
675
676                         case MR_CARRIER:
677                                         ATEM_StringOut("\n\rCARRIER\n\r");
678                                         break;
679
680                         case MR_CONNECT:
681                                         ATEM_StringOut("\n\rCONNECT\n\r");
682                                         break;
683
684                         case MR_NOCARRIER:
685                                         ATEM_StringOut("\n\rNO CARRIER\n\r");
686                                         break;
687                         case MR_RING:
688                                         ATEM_StringOut("RING\n\r");
689                                         break;
690                         default:
691                                         ATEM_StringOut(_("\n\rUnknown Result Code!\n\r"));
692                                         break;
693                 }
694         }
695
696 }
697
698
699         /* Get integer from char-pointer, set pointer to end of number
700            stolen basically verbatim from ISDN code.  */
701 static int ATEM_GetNum(char **p)
702 {
703         int v = -1;
704
705         while (*p[0] >= '0' && *p[0] <= '9') {
706                 v = ((v < 0) ? 0 : (v * 10)) + (int) ((*p[0]++) - '0');
707         }
708
709         return v;
710 }
711
712         /* Write string to virtual modem port, either pty or
713            STDOUT as appropriate.  This function is only used during
714            command mode - data pump is used when connected.  */
715 static void     ATEM_StringOut(char *buffer)
716 {
717         int             count = 0;
718         char    out_char;
719
720         while (count < strlen(buffer)) {
721
722                         /* Translate CR/LF/BS as appropriate */
723                 switch (buffer[count]) {
724                         case '\r':
725                                 out_char = ModemRegisters[REG_CR];
726                                 break;
727                         case '\n':
728                                 out_char = ModemRegisters[REG_LF];
729                                 break;
730                         case '\b':
731                                 out_char = ModemRegisters[REG_BS];
732                                 break;
733                         default:
734                                 out_char = buffer[count];
735                                 break;
736                 }
737
738                 write(PtyWRFD, &out_char, 1);
739                 count ++;
740         }
741
742 }