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