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