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