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