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