7 A Linux/Unix toolset and driver for Nokia mobile phones.
9 Released under the terms of the GNU GPL, see file COPYING for more details.
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.
19 #define __data_at_emulator_c
29 #include <sys/types.h>
42 #include "gsm-common.h"
44 #include "data/at-emulator.h"
45 #include "data/virtmodem.h"
46 #include "data/datapump.h"
48 #define MAX_LINE_LENGTH 256
50 /* Global variables */
51 bool ATEM_Initialised = false; /* Set to true once initialised */
52 extern bool CommandMode;
53 extern int ConnectCount;
55 static char ModelName[80]; /* This seems to be needed to avoid seg-faults */
56 static char PortName[80];
60 extern int PtyRDFD; /* File descriptor for reading and writing to/from */
61 extern int PtyWRFD; /* pty interface - only different in debug mode. */
63 static u8 ModemRegisters[MAX_MODEM_REGISTERS];
64 static char CmdBuffer[MAX_CMD_BUFFERS][CMD_BUFFER_LENGTH];
65 static int CurrentCmdBuffer;
66 static int CurrentCmdBufferIndex;
67 static bool VerboseResponse; /* Switch betweek numeric (4) and text responses (ERROR) */
68 static char IncomingCallNo;
69 static int MessageFormat; /* Message Format (text or pdu) */
71 static void ATEM_ParseAT(char *cmd_buffer);
73 /* Current command parser */
74 static void (*Parser)(char *);
75 //void (*Parser)(char *) = ATEM_ParseAT; /* Current command parser */
77 static GSM_MemoryType SMSType;
80 static void ATEM_InitRegisters(void);
82 /* If initialised in debug mode, stdin/out is used instead
83 of ptys for interface. */
84 bool ATEM_Initialise(int read_fd, int write_fd, char *model, char *port)
89 strncpy(ModelName,model,80);
90 strncpy(PortName,port,80);
92 /* Initialise command buffer variables */
94 CurrentCmdBufferIndex = 0;
96 /* Default to verbose reponses */
97 VerboseResponse = true;
99 /* Initialise registers */
100 ATEM_InitRegisters();
102 /* Initial parser is AT routine */
103 Parser = ATEM_ParseAT;
105 /* Setup defaults for AT*C interpreter. */
109 /* Default message format is PDU */
110 MessageFormat = PDU_MODE;
112 /* Set the call passup so that we get notified of incoming calls */
113 GSM->DialData(NULL,-1,&ATEM_CallPassup);
115 /* We're ready to roll... */
116 ATEM_Initialised = true;
120 /* Initialise the "registers" used by the virtual modem. */
121 static void ATEM_InitRegisters(void)
124 ModemRegisters[REG_RINGATA] = 0;
125 ModemRegisters[REG_RINGCNT] = 2;
126 ModemRegisters[REG_ESC] = '+';
127 ModemRegisters[REG_CR] = 10;
128 ModemRegisters[REG_LF] = 13;
129 ModemRegisters[REG_BS] = 8;
130 ModemRegisters[S35]=7;
131 ModemRegisters[REG_ECHO] = BIT_ECHO;
136 /* This gets called to indicate an incoming call */
138 void ATEM_CallPassup(char c)
140 if ((c >= 0) && (c < 9)) {
141 ATEM_ModemResult(MR_RING);
146 static void ATEM_StringOut(char *buffer);
148 /* Handler called when characters received from serial port.
149 calls state machine code to process it. */
151 void ATEM_HandleIncomingData(char *buffer, int length)
154 unsigned char out_buf[3];
156 for (count = 0; count < length ; count++) {
157 /* Echo character if appropriate. */
158 if (ModemRegisters[REG_ECHO] & BIT_ECHO) {
159 out_buf[0] = buffer[count];
161 ATEM_StringOut(out_buf);
164 /* If it's a command terminator character, parse what
165 we have so far then go to next buffer. */
166 if (buffer[count] == ModemRegisters[REG_CR] ||
167 buffer[count] == ModemRegisters[REG_LF]) {
169 CmdBuffer[CurrentCmdBuffer][CurrentCmdBufferIndex] = 0x00;
170 Parser(CmdBuffer[CurrentCmdBuffer]);
173 if (CurrentCmdBuffer >= MAX_CMD_BUFFERS) {
174 CurrentCmdBuffer = 0;
176 CurrentCmdBufferIndex = 0;
178 CmdBuffer[CurrentCmdBuffer][CurrentCmdBufferIndex] = buffer[count];
179 CurrentCmdBufferIndex++;
180 if (CurrentCmdBufferIndex >= CMD_BUFFER_LENGTH) {
181 CurrentCmdBufferIndex = CMD_BUFFER_LENGTH;
187 static bool ATEM_CommandPlusC(char **buf);
188 static bool ATEM_CommandPlusG(char **buf);
189 static int ATEM_GetNum(char **p);
190 static void ATEM_ParseSMS(char *cmd_buffer);
192 /* Parser for standard AT commands. cmd_buffer must be null terminated. */
193 static void ATEM_ParseAT(char *cmd_buffer)
198 if (strncasecmp (cmd_buffer, "AT", 2) != 0) {
199 ATEM_ModemResult(MR_ERROR);
203 for (buf = &cmd_buffer[2]; *buf;) {
204 switch (toupper(*buf)) {
211 /* For now we'll also initialise the datapump + rlp code again */
212 DP_Initialise(PtyRDFD, PtyWRFD);
213 GSM->DialData(NULL, -1, &DP_CallPassup);
214 GSM->AnswerCall(IncomingCallNo);
220 /* FIXME - should parse this better */
221 /* For now we'll also initialise the datapump + rlp code again */
222 DP_Initialise(PtyRDFD, PtyWRFD);
224 if (toupper(*buf) == 'T') buf++;
225 if (*buf == ' ') buf++;
226 strncpy(number, buf, 30);
227 if (ModemRegisters[S35] == 0) GSM->DialData(number, 1, &DP_CallPassup);
228 else GSM->DialData(number, 0, &DP_CallPassup);
229 ATEM_StringOut("\n\r");
236 RLP_SetUserRequest(Disc_Req, true);
240 /* Change registers - only no. 35 for now */
242 if (memcmp(buf, "35=", 3) == 0) {
244 ModemRegisters[S35] = *buf - '0';
248 /* E - Turn Echo on/off */
251 switch (ATEM_GetNum(&buf)) {
253 ModemRegisters[REG_ECHO] &= ~BIT_ECHO;
256 ModemRegisters[REG_ECHO] |= BIT_ECHO;
259 ATEM_ModemResult(MR_ERROR);
264 /* Handle AT* commands (Nokia proprietary I think) */
267 if (!strcasecmp(buf, "NOKIATEST")) {
268 ATEM_ModemResult(MR_OK); /* FIXME? */
271 if (!strcasecmp(buf, "C")) {
272 ATEM_ModemResult(MR_OK);
273 Parser = ATEM_ParseSMS;
279 /* + is the precursor to another set of commands +CSQ, +FCLASS etc. */
282 switch (toupper(*buf)) {
285 /* Returns true if error occured */
286 if (ATEM_CommandPlusC(&buf) == true) {
293 /* Returns true if error occured */
294 if (ATEM_CommandPlusG(&buf) == true) {
300 ATEM_ModemResult(MR_ERROR);
306 ATEM_ModemResult(MR_ERROR);
311 ATEM_ModemResult(MR_OK);
314 static GSM_Error ATEM_ReadSMS(int number, GSM_MemoryType type, GSM_SMSMessage *message)
318 message->MemoryType = type;
319 message->Location = number;
320 error = GSM->GetSMSMessage(message);
325 static void ATEM_PrintSMS(char *line, GSM_SMSMessage *message, int mode)
329 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);
332 if (message->Coding==GSM_Coding_8bit)
333 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>"));
335 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);
338 gsprintf(line, MAX_LINE_LENGTH, _("<Not implemented>"));
341 gsprintf(line, MAX_LINE_LENGTH, _("<Unknown mode>"));
346 static void ATEM_EraseSMS(int number, GSM_MemoryType type)
348 GSM_SMSMessage message;
349 message.MemoryType = type;
350 message.Location = number;
351 if (GSM->DeleteSMSMessage(&message) == GE_NONE) {
352 ATEM_ModemResult(MR_OK);
354 ATEM_ModemResult(MR_ERROR);
359 static void ATEM_HandleSMS()
361 GSM_SMSMessage message;
363 char buffer[MAX_LINE_LENGTH];
365 error = ATEM_ReadSMS(SMSNumber, SMSType, &message);
368 ATEM_PrintSMS(buffer, &message, INTERACT_MODE);
369 ATEM_StringOut(buffer);
372 gsprintf(buffer, MAX_LINE_LENGTH, _("\n\rNo message under number %d\n\r"), SMSNumber);
373 ATEM_StringOut(buffer);
379 static void ATEM_ParseDIR(char *cmd_buffer);
381 /* Parser for SMS interactive mode */
382 static void ATEM_ParseSMS(char *buff)
384 if (!strcasecmp(buff, "HELP")) {
385 ATEM_StringOut(_("\n\rThe following commands work...\n\r"));
386 ATEM_StringOut("DIR\n\r");
387 ATEM_StringOut("EXIT\n\r");
388 ATEM_StringOut("HELP\n\r");
392 if (!strcasecmp(buff, "DIR")) {
395 Parser = ATEM_ParseDIR;
398 if (!strcasecmp(buff, "EXIT")) {
399 Parser = ATEM_ParseAT;
400 ATEM_ModemResult(MR_OK);
403 ATEM_ModemResult(MR_ERROR);
406 /* Parser for DIR sub mode of SMS interactive mode. */
407 static void ATEM_ParseDIR(char *buff)
409 switch (toupper(*buff)) {
419 ATEM_EraseSMS(SMSNumber, SMSType);
422 Parser= ATEM_ParseSMS;
423 ATEM_ModemResult(MR_OK);
426 ATEM_ModemResult(MR_ERROR);
429 /* Handle AT+C commands, this is a quick hack together at this
431 static bool ATEM_CommandPlusC(char **buf)
434 GSM_RFUnits rfunits = GRF_CSQ;
435 char buffer[MAX_LINE_LENGTH], buffer2[MAX_LINE_LENGTH];
438 GSM_SMSMessage message;
440 if (strncasecmp(*buf, "SQ", 2) == 0) {
443 if (GSM->GetRFLevel(&rfunits, &rflevel) == GE_NONE) {
444 gsprintf(buffer, MAX_LINE_LENGTH, "\n\r+CSQ: %0.0f, 99", rflevel);
445 ATEM_StringOut(buffer);
451 /* AT+CGMI is Manufacturer information for the ME (phone) so
452 it should be Nokia rather than gnokii... */
453 if (strncasecmp(*buf, "GMI", 3) == 0) {
455 ATEM_StringOut(_("\n\rNokia Mobile Phones"));
459 /* AT+CGSN is IMEI */
460 if (strncasecmp(*buf, "GSN", 3) == 0) {
462 if (GSM->GetIMEI(buffer2) == GE_NONE) {
463 gsprintf(buffer, MAX_LINE_LENGTH, "\n\r%s", buffer2);
464 ATEM_StringOut(buffer);
471 /* AT+CGMR is Revision (hardware) */
472 if (strncasecmp(*buf, "GMR", 3) == 0) {
474 if (GSM->GetRevision(buffer2) == GE_NONE) {
475 gsprintf(buffer, MAX_LINE_LENGTH, "\n\r%s", buffer2);
476 ATEM_StringOut(buffer);
483 /* AT+CGMM is Model code */
484 if (strncasecmp(*buf, "GMM", 3) == 0) {
486 if (GSM->GetModel(buffer2) == GE_NONE) {
487 gsprintf(buffer, MAX_LINE_LENGTH, "\n\r%s", buffer2);
488 ATEM_StringOut(buffer);
495 /* AT+CMGF is mode selection for message format */
496 if (strncasecmp(*buf, "MGF", 3) == 0) {
504 MessageFormat = PDU_MODE;
508 MessageFormat = TEXT_MODE;
516 gsprintf(buffer, MAX_LINE_LENGTH, "\n\r+CMGF: %d", MessageFormat);
517 ATEM_StringOut(buffer);
525 /* AT+CMGR is reading a message */
526 if (strncasecmp(*buf, "MGR", 3) == 0) {
532 buf[0] += strlen(*buf);
534 error = ATEM_ReadSMS(index, SMSType, &message);
537 ATEM_PrintSMS(buffer2, &message, MessageFormat);
538 gsprintf(buffer, MAX_LINE_LENGTH, "\n\r+CMGR: %s", buffer2);
539 ATEM_StringOut(buffer);
542 gsprintf(buffer, MAX_LINE_LENGTH, "\n\r+CMS ERROR: %d\n\r", error);
543 ATEM_StringOut(buffer);
553 /* AT+CMGL is listing messages */
554 if (strncasecmp(*buf, "MGL", 3) == 0) {
562 /* process <stat> parameter */
563 if (*(*buf-1) == 0 || /* i.e. no parameter given */
564 strcasecmp(*buf, "1") == 0 ||
565 strcasecmp(*buf, "3") == 0 ||
566 strcasecmp(*buf, "\"REC READ\"") == 0 ||
567 strcasecmp(*buf, "\"STO SENT\"") == 0) {
568 status = GSS_SENTREAD;
569 } else if (strcasecmp(*buf, "0") == 0 ||
570 strcasecmp(*buf, "2") == 0 ||
571 strcasecmp(*buf, "\"REC UNREAD\"") == 0 ||
572 strcasecmp(*buf, "\"STO UNSENT\"") == 0) {
573 status = GSS_NOTSENTREAD;
574 } else if (strcasecmp(*buf, "4") == 0 ||
575 strcasecmp(*buf, "\"ALL\"") == 0) {
576 status = 4; /* ALL */
580 buf[0] += strlen(*buf);
582 /* check all message storages */
583 for (index = 1; index <= 20; index++) {
584 error = ATEM_ReadSMS(index, SMSType, &message);
587 /* print messsage if it has the required status */
588 if (message.Status == status || status == 4 /* ALL */) {
589 ATEM_PrintSMS(buffer2, &message, MessageFormat);
590 gsprintf(buffer, MAX_LINE_LENGTH, "\n\r+CMGL: %d,%s", index, buffer2);
591 ATEM_StringOut(buffer);
594 case GE_EMPTYSMSLOCATION:
595 /* don't care if this storage is empty */
598 /* print other error codes and quit */
599 gsprintf(buffer, MAX_LINE_LENGTH, "\n\r+CMS ERROR: %d\n\r", error);
600 ATEM_StringOut(buffer);
614 /* AT+G commands. Some of these responses are a bit tongue in cheek... */
615 static bool ATEM_CommandPlusG(char **buf)
617 char buffer[MAX_LINE_LENGTH];
619 /* AT+GMI is Manufacturer information for the TA (Terminal Adaptor) */
620 if (strncasecmp(*buf, "MI", 3) == 0) {
623 ATEM_StringOut(_("\n\rHugh Blemings, Pavel JanÃk ml. and others..."));
627 /* AT+GMR is Revision information for the TA (Terminal Adaptor) */
628 if (strncasecmp(*buf, "MR", 3) == 0) {
630 gsprintf(buffer, MAX_LINE_LENGTH, "\n\r%s %s %s", VERSION, __TIME__, __DATE__);
632 ATEM_StringOut(buffer);
636 /* AT+GMM is Model information for the TA (Terminal Adaptor) */
637 if (strncasecmp(*buf, "MM", 3) == 0) {
640 gsprintf(buffer, MAX_LINE_LENGTH, _("\n\rgnokii configured for %s on %s"), ModelName, PortName);
641 ATEM_StringOut(buffer);
645 /* AT+GSN is Serial number for the TA (Terminal Adaptor) */
646 if (strncasecmp(*buf, "SN", 3) == 0) {
649 gsprintf(buffer, MAX_LINE_LENGTH, _("\n\rnone built in, choose your own"));
650 ATEM_StringOut(buffer);
657 /* Send a result string back. There is much work to do here, see
658 the code in the isdn driver for an idea of where it's heading... */
659 void ATEM_ModemResult(int code)
663 if (VerboseResponse == false) {
664 sprintf(buffer, "\n\r%d\n\r", code);
665 ATEM_StringOut(buffer);
669 ATEM_StringOut("\n\rOK\n\r");
673 ATEM_StringOut("\n\rERROR\n\r");
677 ATEM_StringOut("\n\rCARRIER\n\r");
681 ATEM_StringOut("\n\rCONNECT\n\r");
685 ATEM_StringOut("\n\rNO CARRIER\n\r");
688 ATEM_StringOut("RING\n\r");
691 ATEM_StringOut(_("\n\rUnknown Result Code!\n\r"));
699 /* Get integer from char-pointer, set pointer to end of number
700 stolen basically verbatim from ISDN code. */
701 static int ATEM_GetNum(char **p)
705 while (*p[0] >= '0' && *p[0] <= '9') {
706 v = ((v < 0) ? 0 : (v * 10)) + (int) ((*p[0]++) - '0');
712 /* Write string to virtual modem port, either pty or
713 STDOUT as appropriate. This function is only used during
714 command mode - data pump is used when connected. */
715 static void ATEM_StringOut(char *buffer)
720 while (count < strlen(buffer)) {
722 /* Translate CR/LF/BS as appropriate */
723 switch (buffer[count]) {
725 out_char = ModemRegisters[REG_CR];
728 out_char = ModemRegisters[REG_LF];
731 out_char = ModemRegisters[REG_BS];
734 out_char = buffer[count];
738 write(PtyWRFD, &out_char, 1);