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