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