7 A Linux/Unix toolset and driver for Nokia mobile phones.
9 Copyright (C) 1999, 2000 Hugh Blemings & Pavel Janík ml.
11 Released under the terms of the GNU GPL, see file COPYING for more details.
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.
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
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
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
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
30 Revision 1.6 2001/11/27 12:19:01 pkot
31 Cleanup, indentation, ANSI complaint preprocesor symbols (Jan Kratochvil, me)
33 Revision 1.5 2001/11/08 16:34:19 pkot
34 Updates to work with new libsms
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)
40 Revision 1.3 2001/02/21 19:56:59 chris
41 More fiddling with the directory layout
46 #define __data_at_emulator_c
56 #include <sys/types.h>
69 #include "gsm-common.h"
71 #include "data/at-emulator.h"
72 #include "data/virtmodem.h"
73 #include "data/datapump.h"
75 #define MAX_LINE_LENGTH 256
77 /* Global variables */
78 bool ATEM_Initialised = false; /* Set to true once initialised */
79 extern bool CommandMode;
80 extern int ConnectCount;
82 char ModelName[80]; /* This seems to be needed to avoid seg-faults */
87 int PtyRDFD; /* File descriptor for reading and writing to/from */
88 int PtyWRFD; /* pty interface - only different in debug mode. */
90 u8 ModemRegisters[MAX_MODEM_REGISTERS];
91 char CmdBuffer[MAX_CMD_BUFFERS][CMD_BUFFER_LENGTH];
93 int CurrentCmdBufferIndex;
94 bool VerboseResponse; /* Switch betweek numeric (4) and text responses (ERROR) */
96 int MessageFormat; /* Message Format (text or pdu) */
98 /* Current command parser */
99 void (*Parser)(char *);
100 //void (*Parser)(char *) = ATEM_ParseAT; /* Current command parser */
102 GSM_MemoryType SMSType;
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)
112 strncpy(ModelName,model,80);
113 strncpy(PortName,port,80);
115 /* Initialise command buffer variables */
116 CurrentCmdBuffer = 0;
117 CurrentCmdBufferIndex = 0;
119 /* Default to verbose reponses */
120 VerboseResponse = true;
122 /* Initialise registers */
123 ATEM_InitRegisters();
125 /* Initial parser is AT routine */
126 Parser = ATEM_ParseAT;
128 /* Setup defaults for AT*C interpreter. */
132 /* Default message format is PDU */
133 MessageFormat = PDU_MODE;
135 /* Set the call passup so that we get notified of incoming calls */
136 GSM->DialData(NULL,-1,&ATEM_CallPassup);
138 /* We're ready to roll... */
139 ATEM_Initialised = true;
143 /* Initialise the "registers" used by the virtual modem. */
144 void ATEM_InitRegisters(void)
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;
159 /* This gets called to indicate an incoming call */
161 void ATEM_CallPassup(char c)
163 if ((c >= 0) && (c < 9)) {
164 ATEM_ModemResult(MR_RING);
170 /* Handler called when characters received from serial port.
171 calls state machine code to process it. */
173 void ATEM_HandleIncomingData(char *buffer, int length)
176 unsigned char out_buf[3];
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];
183 ATEM_StringOut(out_buf);
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]) {
191 CmdBuffer[CurrentCmdBuffer][CurrentCmdBufferIndex] = 0x00;
192 Parser(CmdBuffer[CurrentCmdBuffer]);
195 if (CurrentCmdBuffer >= MAX_CMD_BUFFERS) {
196 CurrentCmdBuffer = 0;
198 CurrentCmdBufferIndex = 0;
200 CmdBuffer[CurrentCmdBuffer][CurrentCmdBufferIndex] = buffer[count];
201 CurrentCmdBufferIndex++;
202 if (CurrentCmdBufferIndex >= CMD_BUFFER_LENGTH) {
203 CurrentCmdBufferIndex = CMD_BUFFER_LENGTH;
210 /* Parser for standard AT commands. cmd_buffer must be null terminated. */
211 void ATEM_ParseAT(char *cmd_buffer)
216 if (strncasecmp (cmd_buffer, "AT", 2) != 0) {
217 ATEM_ModemResult(MR_ERROR);
221 for (buf = &cmd_buffer[2]; *buf;) {
222 switch (toupper(*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);
238 /* FIXME - should parse this better */
239 /* For now we'll also initialise the datapump + rlp code again */
240 DP_Initialise(PtyRDFD, PtyWRFD);
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");
254 RLP_SetUserRequest(Disc_Req, true);
258 /* Change registers - only no. 35 for now */
260 if (memcmp(buf, "35=", 3) == 0) {
262 ModemRegisters[S35] = *buf - '0';
266 /* E - Turn Echo on/off */
269 switch (ATEM_GetNum(&buf)) {
271 ModemRegisters[REG_ECHO] &= ~BIT_ECHO;
274 ModemRegisters[REG_ECHO] |= BIT_ECHO;
277 ATEM_ModemResult(MR_ERROR);
282 /* Handle AT* commands (Nokia proprietary I think) */
285 if (!strcasecmp(buf, "NOKIATEST")) {
286 ATEM_ModemResult(MR_OK); /* FIXME? */
289 if (!strcasecmp(buf, "C")) {
290 ATEM_ModemResult(MR_OK);
291 Parser = ATEM_ParseSMS;
297 /* + is the precursor to another set of commands +CSQ, +FCLASS etc. */
300 switch (toupper(*buf)) {
303 /* Returns true if error occured */
304 if (ATEM_CommandPlusC(&buf) == true) {
311 /* Returns true if error occured */
312 if (ATEM_CommandPlusG(&buf) == true) {
318 ATEM_ModemResult(MR_ERROR);
324 ATEM_ModemResult(MR_ERROR);
329 ATEM_ModemResult(MR_OK);
332 static GSM_Error ATEM_ReadSMS(int number, GSM_MemoryType type, GSM_SMSMessage *message)
336 message->MemoryType = type;
337 message->Number = number;
338 error = GSM->GetSMSMessage(message);
343 static void ATEM_PrintSMS(char *line, GSM_SMSMessage *message, int 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);
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>"));
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);
357 gsprintf(line, MAX_LINE_LENGTH, _("<Not implemented>"));
360 gsprintf(line, MAX_LINE_LENGTH, _("<Unknown mode>"));
365 static void ATEM_EraseSMS(int number, GSM_MemoryType type)
367 GSM_SMSMessage message;
368 message.MemoryType = type;
369 message.Number = number;
370 if (GSM->DeleteSMSMessage(&message) == GE_NONE) {
371 ATEM_ModemResult(MR_OK);
373 ATEM_ModemResult(MR_ERROR);
378 static void ATEM_HandleSMS()
380 GSM_SMSMessage message;
382 char buffer[MAX_LINE_LENGTH];
384 error = ATEM_ReadSMS(SMSNumber, SMSType, &message);
387 ATEM_PrintSMS(buffer, &message, INTERACT_MODE);
388 ATEM_StringOut(buffer);
391 gsprintf(buffer, MAX_LINE_LENGTH, _("\n\rNo message under number %d\n\r"), SMSNumber);
392 ATEM_StringOut(buffer);
398 /* Parser for SMS interactive mode */
399 void ATEM_ParseSMS(char *buff)
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");
409 if (!strcasecmp(buff, "DIR")) {
412 Parser = ATEM_ParseDIR;
415 if (!strcasecmp(buff, "EXIT")) {
416 Parser = ATEM_ParseAT;
417 ATEM_ModemResult(MR_OK);
420 ATEM_ModemResult(MR_ERROR);
423 /* Parser for DIR sub mode of SMS interactive mode. */
424 void ATEM_ParseDIR(char *buff)
426 switch (toupper(*buff)) {
436 ATEM_EraseSMS(SMSNumber, SMSType);
439 Parser= ATEM_ParseSMS;
440 ATEM_ModemResult(MR_OK);
443 ATEM_ModemResult(MR_ERROR);
446 /* Handle AT+C commands, this is a quick hack together at this
448 bool ATEM_CommandPlusC(char **buf)
451 GSM_RFUnits rfunits = GRF_CSQ;
452 char buffer[MAX_LINE_LENGTH], buffer2[MAX_LINE_LENGTH];
455 GSM_SMSMessage message;
457 if (strncasecmp(*buf, "SQ", 2) == 0) {
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);
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) {
472 ATEM_StringOut(_("\n\rNokia Mobile Phones"));
476 /* AT+CGSN is IMEI */
477 if (strncasecmp(*buf, "GSN", 3) == 0) {
479 if (GSM->GetIMEI(buffer2) == GE_NONE) {
480 gsprintf(buffer, MAX_LINE_LENGTH, "\n\r%s", buffer2);
481 ATEM_StringOut(buffer);
488 /* AT+CGMR is Revision (hardware) */
489 if (strncasecmp(*buf, "GMR", 3) == 0) {
491 if (GSM->GetRevision(buffer2) == GE_NONE) {
492 gsprintf(buffer, MAX_LINE_LENGTH, "\n\r%s", buffer2);
493 ATEM_StringOut(buffer);
500 /* AT+CGMM is Model code */
501 if (strncasecmp(*buf, "GMM", 3) == 0) {
503 if (GSM->GetModel(buffer2) == GE_NONE) {
504 gsprintf(buffer, MAX_LINE_LENGTH, "\n\r%s", buffer2);
505 ATEM_StringOut(buffer);
512 /* AT+CMGF is mode selection for message format */
513 if (strncasecmp(*buf, "MGF", 3) == 0) {
521 MessageFormat = PDU_MODE;
525 MessageFormat = TEXT_MODE;
533 gsprintf(buffer, MAX_LINE_LENGTH, "\n\r+CMGF: %d", MessageFormat);
534 ATEM_StringOut(buffer);
542 /* AT+CMGR is reading a message */
543 if (strncasecmp(*buf, "MGR", 3) == 0) {
548 sscanf(*buf, "%d", &index);
549 buf[0] += strlen(*buf);
551 error = ATEM_ReadSMS(index, SMSType, &message);
554 ATEM_PrintSMS(buffer2, &message, MessageFormat);
555 gsprintf(buffer, MAX_LINE_LENGTH, "\n\r+CMGR: %s", buffer2);
556 ATEM_StringOut(buffer);
559 gsprintf(buffer, MAX_LINE_LENGTH, "\n\r+CMS ERROR: %d\n\r", error);
560 ATEM_StringOut(buffer);
570 /* AT+CMGL is listing messages */
571 if (strncasecmp(*buf, "MGL", 3) == 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) {
586 } else if (strcasecmp(*buf, "0") == 0 ||
587 strcasecmp(*buf, "2") == 0 ||
588 strcasecmp(*buf, "\"REC UNREAD\"") == 0 ||
589 strcasecmp(*buf, "\"STO UNSENT\"") == 0) {
591 } else if (strcasecmp(*buf, "4") == 0 ||
592 strcasecmp(*buf, "\"ALL\"") == 0) {
593 status = 4; /* ALL */
597 buf[0] += strlen(*buf);
599 /* check all message storages */
600 for (index = 1; index <= 20; index++) {
601 error = ATEM_ReadSMS(index, SMSType, &message);
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);
611 case GE_EMPTYSMSLOCATION:
612 /* don't care if this storage is empty */
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);
631 /* AT+G commands. Some of these responses are a bit tongue in cheek... */
632 bool ATEM_CommandPlusG(char **buf)
634 char buffer[MAX_LINE_LENGTH];
636 /* AT+GMI is Manufacturer information for the TA (Terminal Adaptor) */
637 if (strncasecmp(*buf, "MI", 3) == 0) {
640 ATEM_StringOut(_("\n\rHugh Blemings, Pavel Janík ml. and others..."));
644 /* AT+GMR is Revision information for the TA (Terminal Adaptor) */
645 if (strncasecmp(*buf, "MR", 3) == 0) {
647 gsprintf(buffer, MAX_LINE_LENGTH, "\n\r%s %s %s", VERSION, __TIME__, __DATE__);
649 ATEM_StringOut(buffer);
653 /* AT+GMM is Model information for the TA (Terminal Adaptor) */
654 if (strncasecmp(*buf, "MM", 3) == 0) {
657 gsprintf(buffer, MAX_LINE_LENGTH, _("\n\rgnokii configured for %s on %s"), ModelName, PortName);
658 ATEM_StringOut(buffer);
662 /* AT+GSN is Serial number for the TA (Terminal Adaptor) */
663 if (strncasecmp(*buf, "SN", 3) == 0) {
666 gsprintf(buffer, MAX_LINE_LENGTH, _("\n\rnone built in, choose your own"));
667 ATEM_StringOut(buffer);
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)
680 if (VerboseResponse == false) {
681 sprintf(buffer, "\n\r%d\n\r", code);
682 ATEM_StringOut(buffer);
686 ATEM_StringOut("\n\rOK\n\r");
690 ATEM_StringOut("\n\rERROR\n\r");
694 ATEM_StringOut("\n\rCARRIER\n\r");
698 ATEM_StringOut("\n\rCONNECT\n\r");
702 ATEM_StringOut("\n\rNO CARRIER\n\r");
705 ATEM_StringOut("RING\n\r");
708 ATEM_StringOut(_("\n\rUnknown Result Code!\n\r"));
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)
722 while (*p[0] >= '0' && *p[0] <= '9') {
723 v = ((v < 0) ? 0 : (v * 10)) + (int) ((*p[0]++) - '0');
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)
737 while (count < strlen(buffer)) {
739 /* Translate CR/LF/BS as appropriate */
740 switch (buffer[count]) {
742 out_char = ModemRegisters[REG_CR];
745 out_char = ModemRegisters[REG_LF];
748 out_char = ModemRegisters[REG_BS];
751 out_char = buffer[count];
755 write(PtyWRFD, &out_char, 1);