1 /*
2
3 $Id$
4
5 G N O K I I
6
7 A Linux/Unix toolset and driver for the mobile phones.
8
9 This file is part of gnokii.
10
11 Gnokii is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
15
16 Gnokii is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with gnokii; if not, write to the Free Software
23 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24
25 Copyright (C) 1999-2000 Hugh Blemings & Pavel Janik ml.
26 Copyright (C) 2001-2004 Pawel Kot
27 Copyright (C) 2002-2004 BORBELY Zoltan
28
29 This file provides a virtual modem or "AT" interface to the GSM phone by
30 calling code in gsm-api.c. Inspired by and in places copied from the Linux
31 kernel AT Emulator IDSN code by Fritz Elfert and others.
32
33 */
34
35 #include "config.h"
36
37 #include <stdio.h>
38 #include <errno.h>
39 #include <fcntl.h>
40 #include <signal.h>
41 #include <grp.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <sys/types.h>
45 #include <sys/stat.h>
46 #include <time.h>
47 #include <ctype.h>
48
49 #ifndef WIN32
50 # include <termios.h>
51 #endif
52
53 #include "misc.h"
54 #include "gnokii.h"
55 #include "compat.h"
56 #include "data/at-emulator.h"
57 #include "data/datapump.h"
58
59 #define MAX_LINE_LENGTH 256
60
61 #define MAX_CMD_BUFFERS (2)
62 #define CMD_BUFFER_LENGTH (100)
63
64 /* Definition of some special Registers of AT-Emulator, pinched in
65 part from ISDN driver in Linux kernel */
66 #define REG_RINGATA 0
67 #define REG_RINGCNT 1
68 #define REG_ESC 2
69 #define REG_CR 3
70 #define REG_LF 4
71 #define REG_BS 5
72 #define S22 22
73 #define S35 35
74 #define REG_CTRLZ 100
75 #define REG_ESCAPE 101
76
77 #define REG_QUIET 14
78 #define BIT_QUIET 4
79 #define REG_VERBOSE 14
80 #define BIT_VERBOSE 8
81 #define REG_ECHO 14
82 #define BIT_ECHO 2
83
84
85 #define MAX_MODEM_REGISTERS 102
86
87 /* Message format definitions */
88 #define PDU_MODE 0
89 #define TEXT_MODE 1
90 #define INTERACT_MODE 2
91
92 /* Global variables */
93 bool gn_atem_initialised = false; /* Set to true once initialised */
94 extern bool CommandMode;
95 extern int ConnectCount;
96
97 struct gn_statemachine *sm;
98 gn_data data;
99
100 static gn_sms sms;
101 static gn_call_info callinfo;
102 static char imei[GN_IMEI_MAX_LENGTH], model[GN_MODEL_MAX_LENGTH], revision[GN_REVISION_MAX_LENGTH], manufacturer[GN_MANUFACTURER_MAX_LENGTH];
103
104 /* Local variables */
105 static int PtyRDFD; /* File descriptor for reading and writing to/from */
106 static int PtyWRFD; /* pty interface - only different in debug mode. */
107
108 static u8 ModemRegisters[MAX_MODEM_REGISTERS];
109 static char CmdBuffer[MAX_CMD_BUFFERS][CMD_BUFFER_LENGTH];
110 static int CurrentCmdBuffer;
111 static int CurrentCmdBufferIndex;
112 static int IncomingCallNo;
113 static int MessageFormat; /* Message Format (text or pdu) */
114 static int CallerIDMode;
115
116 /* Current command parser */
117 static void (*Parser)(char *); /* Current command parser */
118 /* void (*Parser)(char *) = gn_atem_at_parse; */
119
120 static gn_memory_type SMSType;
121 static int SMSNumber;
122
123 /* If initialised in debug mode, stdin/out is used instead
124 of ptys for interface. */
gn_atem_initialise(int read_fd,int write_fd,struct gn_statemachine * vmsm)125 bool gn_atem_initialise(int read_fd, int write_fd, struct gn_statemachine *vmsm)
126 {
127 PtyRDFD = read_fd;
128 PtyWRFD = write_fd;
129
130 gn_data_clear(&data);
131 memset(&sms, 0, sizeof(sms));
132 memset(&callinfo, 0, sizeof(callinfo));
133
134 data.sms = &sms;
135 data.call_info = &callinfo;
136 data.manufacturer = manufacturer;
137 data.model = model;
138 data.revision = revision;
139 data.imei = imei;
140
141 sm = vmsm;
142
143 /* Initialise command buffer variables */
144 CurrentCmdBuffer = 0;
145 CurrentCmdBufferIndex = 0;
146
147 /* Initialise registers */
148 gn_atem_registers_init();
149
150 /* Initial parser is AT routine */
151 Parser = gn_atem_at_parse;
152
153 /* Setup defaults for AT*C interpreter. */
154 SMSNumber = 1;
155 SMSType = GN_MT_ME;
156
157 /* Default message format is PDU */
158 MessageFormat = PDU_MODE;
159
160 /* Set the call passup so that we get notified of incoming calls */
161 data.call_notification = gn_atem_call_passup;
162 gn_sm_functions(GN_OP_SetCallNotification, &data, sm);
163
164 /* query model, revision and imei */
165 if (gn_sm_functions(GN_OP_Identify, &data, sm) != GN_ERR_NONE)
166 return false;
167
168 /* We're ready to roll... */
169 gn_atem_initialised = true;
170 return (true);
171 }
172
173
174 /* Initialise the "registers" used by the virtual modem. */
gn_atem_registers_init(void)175 void gn_atem_registers_init(void)
176 {
177 memset(ModemRegisters, 0, sizeof(ModemRegisters));
178
179 ModemRegisters[REG_RINGATA] = 0;
180 ModemRegisters[REG_RINGCNT] = 0;
181 ModemRegisters[REG_ESC] = '+';
182 ModemRegisters[REG_CR] = 13;
183 ModemRegisters[REG_LF] = 10;
184 ModemRegisters[REG_BS] = 8;
185 ModemRegisters[S35] = 7;
186 ModemRegisters[REG_ECHO] |= BIT_ECHO;
187 ModemRegisters[REG_VERBOSE] |= BIT_VERBOSE;
188 ModemRegisters[REG_CTRLZ] = 26;
189 ModemRegisters[REG_ESCAPE] = 27;
190
191 CallerIDMode = 0;
192 }
193
194
gn_atem_hangup_phone(void)195 static void gn_atem_hangup_phone(void)
196 {
197 if (IncomingCallNo > 0) {
198 rlp_user_request_set(Disc_Req, true);
199 gn_sm_loop(10, sm);
200 }
201 if (IncomingCallNo > 0) {
202 data.call_info->call_id = IncomingCallNo;
203 gn_sm_functions(GN_OP_CancelCall, &data, sm);
204 IncomingCallNo = -1;
205 }
206 dp_Initialise(PtyRDFD, PtyWRFD);
207 }
208
209
gn_atem_answer_phone(void)210 static void gn_atem_answer_phone(void)
211 {
212 /* For now we'll also initialise the datapump + rlp code again */
213 dp_Initialise(PtyRDFD, PtyWRFD);
214 data.call_notification = dp_CallPassup;
215 gn_sm_functions(GN_OP_SetCallNotification, &data, sm);
216 data.call_info->call_id = IncomingCallNo;
217 gn_sm_functions(GN_OP_AnswerCall, &data, sm);
218 CommandMode = false;
219 }
220
221
222 /* This gets called to indicate an incoming call */
gn_atem_call_passup(gn_call_status CallStatus,gn_call_info * CallInfo,struct gn_statemachine * state,void * callback_data)223 void gn_atem_call_passup(gn_call_status CallStatus, gn_call_info *CallInfo, struct gn_statemachine *state, void *callback_data)
224 {
225 dprintf("gn_atem_call_passup called with %d\n", CallStatus);
226
227 switch (CallStatus) {
228 case GN_CALL_Incoming:
229 gn_atem_modem_result(MR_RING);
230 IncomingCallNo = CallInfo->call_id;
231 ModemRegisters[REG_RINGCNT]++;
232 gn_atem_cid_out(CallInfo);
233 if (ModemRegisters[REG_RINGATA] != 0) gn_atem_answer_phone();
234 break;
235 case GN_CALL_LocalHangup:
236 case GN_CALL_RemoteHangup:
237 IncomingCallNo = -1;
238 break;
239 default:
240 break;
241 }
242 }
243
244 /* This gets called to output caller id info of incoming call */
gn_atem_cid_out(gn_call_info * CallInfo)245 void gn_atem_cid_out(gn_call_info *CallInfo)
246 {
247 struct tm *now;
248 time_t nowh;
249 char buf[14]; /* 7 for "DATE = " + 4 digits + \n + \r + \0 */
250
251 nowh = time(NULL);
252 now = localtime(&nowh);
253
254 switch (CallerIDMode) {
255 case 0: /* no output */
256 break;
257 case 1: /* formatted CID */
258 snprintf(buf, sizeof(buf), "DATE = %02d%02d\r\n", now->tm_mon + 1, now->tm_mday);
259 gn_atem_string_out(buf);
260
261 snprintf(buf, sizeof(buf), "TIME = %02d%02d\r\n", now->tm_hour, now->tm_min);
262 gn_atem_string_out(buf);
263
264 /* TO DO: handle P and O numbers */
265 gn_atem_string_out("NMBR = ");
266 gn_atem_string_out(1 + CallInfo->number); /* skip leading "+" */
267 gn_atem_string_out("\r\nNAME = ");
268 gn_atem_string_out(CallInfo->name);
269 gn_atem_string_out("\r\n");
270
271 /* FIX ME: do a real emulation of rings after the first one (at a lower level than this) */
272 gn_atem_modem_result(MR_RING);
273
274 break;
275
276 }
277 }
278
279 /* Handler called when characters received from serial port.
280 calls state machine code to process it. */
gn_atem_incoming_data_handle(char * buffer,int length)281 void gn_atem_incoming_data_handle(char *buffer, int length)
282 {
283 int count;
284 unsigned char out_buf[3];
285
286 for (count = 0; count < length ; count++) {
287
288 /* If it's a command terminator character, parse what
289 we have so far then go to next buffer. */
290 if (buffer[count] == ModemRegisters[REG_CR] ||
291 buffer[count] == ModemRegisters[REG_LF] ||
292 buffer[count] == ModemRegisters[REG_CTRLZ] ||
293 buffer[count] == ModemRegisters[REG_ESCAPE]) {
294
295 /* Echo character if appropriate. */
296 if (buffer[count] == ModemRegisters[REG_CR] &&
297 (ModemRegisters[REG_ECHO] & BIT_ECHO)) {
298 gn_atem_string_out("\r\n");
299 }
300
301 /* Save CTRL-Z and ESCAPE for the parser */
302 if (buffer[count] == ModemRegisters[REG_CTRLZ] ||
303 buffer[count] == ModemRegisters[REG_ESCAPE])
304 CmdBuffer[CurrentCmdBuffer][CurrentCmdBufferIndex++] = buffer[count];
305
306 CmdBuffer[CurrentCmdBuffer][CurrentCmdBufferIndex] = 0x00;
307
308 Parser(CmdBuffer[CurrentCmdBuffer]);
309
310 CurrentCmdBuffer++;
311 if (CurrentCmdBuffer >= MAX_CMD_BUFFERS) {
312 CurrentCmdBuffer = 0;
313 }
314 CurrentCmdBufferIndex = 0;
315
316 } else if (buffer[count] == ModemRegisters[REG_BS]) {
317 if (CurrentCmdBufferIndex > 0) {
318 /* Echo character if appropriate. */
319 if (ModemRegisters[REG_ECHO] & BIT_ECHO) {
320 gn_atem_string_out("\b \b");
321 }
322
323 CurrentCmdBufferIndex--;
324 }
325 } else {
326 /* Echo character if appropriate. */
327 if (ModemRegisters[REG_ECHO] & BIT_ECHO) {
328 out_buf[0] = buffer[count];
329 out_buf[1] = 0;
330 gn_atem_string_out((char *)out_buf);
331 }
332
333 /* Collect it to command buffer */
334 CmdBuffer[CurrentCmdBuffer][CurrentCmdBufferIndex++] = buffer[count];
335 if (CurrentCmdBufferIndex >= CMD_BUFFER_LENGTH) {
336 CurrentCmdBufferIndex = CMD_BUFFER_LENGTH;
337 }
338 }
339 }
340 }
341
342
343 /* Parser for standard AT commands. cmd_buffer must be null terminated. */
gn_atem_at_parse(char * cmd_buffer)344 void gn_atem_at_parse(char *cmd_buffer)
345 {
346 char *buf;
347 int regno, val;
348 char str[256];
349
350 if (!cmd_buffer[0]) return;
351
352 if (strncasecmp (cmd_buffer, "AT", 2) != 0) {
353 gn_atem_modem_result(MR_ERROR);
354 return;
355 }
356
357 for (buf = &cmd_buffer[2]; *buf;) {
358 switch (toupper(*buf)) {
359
360 case 'Z':
361 /* Reset modem */
362 buf++;
363 switch (gn_atem_num_get(&buf)) {
364 case -1:
365 case 0: /* reset and load stored profile 0 */
366 case 1: /* reset and load stored profile 1 */
367 gn_atem_hangup_phone();
368 gn_atem_registers_init();
369 break;
370 default:
371 gn_atem_modem_result(MR_ERROR);
372 return;
373 }
374 break;
375
376 case 'A':
377 /* Answer call */
378 buf++;
379 gn_atem_answer_phone();
380 return;
381 break;
382
383 case 'D':
384 /* Dial Data :-) */
385 /* FIXME - should parse this better */
386 /* For now we'll also initialise the datapump + rlp code again */
387 dp_Initialise(PtyRDFD, PtyWRFD);
388 buf++;
389 if (toupper(*buf) == 'T' || toupper(*buf) == 'P') buf++;
390 while (*buf == ' ') buf++;
391 data.call_notification = dp_CallPassup;
392 gn_sm_functions(GN_OP_SetCallNotification, &data, sm);
393 snprintf(data.call_info->number, sizeof(data.call_info->number), "%s", buf);
394 if (ModemRegisters[S35] == 0)
395 data.call_info->type = GN_CALL_DigitalData;
396 else
397 data.call_info->type = GN_CALL_NonDigitalData;
398 data.call_info->send_number = GN_CALL_Default;
399 CommandMode = false;
400 if (gn_sm_functions(GN_OP_MakeCall, &data, sm) != GN_ERR_NONE) {
401 CommandMode = true;
402 dp_CallPassup(GN_CALL_RemoteHangup, NULL, NULL, NULL);
403 } else {
404 IncomingCallNo = data.call_info->call_id;
405 gn_sm_loop(10, sm);
406 }
407 return;
408 break;
409
410 case 'H':
411 /* Hang Up */
412 buf++;
413 switch (gn_atem_num_get(&buf)) {
414 case -1:
415 case 0: /* hook off the phone */
416 gn_atem_hangup_phone();
417 break;
418 case 1: /* hook on the phone */
419 break;
420 default:
421 gn_atem_modem_result(MR_ERROR);
422 return;
423 }
424 break;
425
426 case 'S':
427 /* Change registers */
428 buf++;
429 regno = gn_atem_num_get(&buf);
430 if (regno < 0 || regno >= MAX_MODEM_REGISTERS) {
431 gn_atem_modem_result(MR_ERROR);
432 return;
433 }
434 if (*buf == '=') {
435 buf++;
436 val = gn_atem_num_get(&buf);
437 if (val < 0 || val > 255) {
438 gn_atem_modem_result(MR_ERROR);
439 return;
440 }
441 ModemRegisters[regno] = val;
442 } else if (*buf == '?') {
443 buf++;
444 snprintf(str, sizeof(str), "%d\r\n", ModemRegisters[regno]);
445 gn_atem_string_out(str);
446 } else {
447 gn_atem_modem_result(MR_ERROR);
448 return;
449 }
450 break;
451
452 case 'E':
453 /* E - Turn Echo on/off */
454 buf++;
455 switch (gn_atem_num_get(&buf)) {
456 case -1:
457 case 0:
458 ModemRegisters[REG_ECHO] &= ~BIT_ECHO;
459 break;
460 case 1:
461 ModemRegisters[REG_ECHO] |= BIT_ECHO;
462 break;
463 default:
464 gn_atem_modem_result(MR_ERROR);
465 return;
466 }
467 break;
468
469 case 'Q':
470 /* Q - Turn Quiet on/off */
471 buf++;
472 switch (gn_atem_num_get(&buf)) {
473 case -1:
474 case 0:
475 ModemRegisters[REG_QUIET] &= ~BIT_QUIET;
476 break;
477 case 1:
478 ModemRegisters[REG_QUIET] |= BIT_QUIET;
479 break;
480 default:
481 gn_atem_modem_result(MR_ERROR);
482 return;
483 }
484 break;
485
486 case 'V':
487 /* V - Turn Verbose on/off */
488 buf++;
489 switch (gn_atem_num_get(&buf)) {
490 case -1:
491 case 0:
492 ModemRegisters[REG_VERBOSE] &= ~BIT_VERBOSE;
493 break;
494 case 1:
495 ModemRegisters[REG_VERBOSE] |= BIT_VERBOSE;
496 break;
497 default:
498 gn_atem_modem_result(MR_ERROR);
499 return;
500 }
501 break;
502
503 case 'X':
504 /* X - Set verbosity of the result messages */
505 buf++;
506 switch (gn_atem_num_get(&buf)) {
507 case -1:
508 case 0: val = 0x00; break;
509 case 1: val = 0x40; break;
510 case 2: val = 0x50; break;
511 case 3: val = 0x60; break;
512 case 4: val = 0x70; break;
513 case 5: val = 0x10; break;
514 default:
515 gn_atem_modem_result(MR_ERROR);
516 return;
517 }
518 ModemRegisters[S22] = (ModemRegisters[S22] & 0x8f) | val;
519 break;
520
521 case 'I':
522 /* I - info */
523 buf++;
524 switch (gn_atem_num_get(&buf)) {
525 case -1:
526 case 0: /* terminal id */
527 snprintf(str, sizeof(str), "%d\r\n", ModemRegisters[39]);
528 gn_atem_string_out(str);
529 break;
530 case 1: /* serial number (IMEI) */
531 snprintf(str, sizeof(str), "%s\r\n", imei);
532 gn_atem_string_out(str);
533 break;
534 case 2: /* phone revision */
535 snprintf(str, sizeof(str), "%s\r\n", revision);
536 gn_atem_string_out(str);
537 break;
538 case 3: /* modem revision */
539 gn_atem_string_out("gnokiid " VERSION "\r\n");
540 break;
541 case 4: /* OEM string */
542 snprintf(str, sizeof(str), "%s %s\r\n", manufacturer, model);
543 gn_atem_string_out(str);
544 break;
545 default:
546 gn_atem_modem_result(MR_ERROR);
547 return;
548 }
549 break;
550
551 /* Handle AT* commands (Nokia proprietary I think) */
552 case '*':
553 buf++;
554 if (!strcasecmp(buf, "NOKIATEST")) {
555 gn_atem_modem_result(MR_OK); /* FIXME? */
556 return;
557 } else {
558 if (!strcasecmp(buf, "C")) {
559 gn_atem_modem_result(MR_OK);
560 Parser = gn_atem_sms_parse;
561 return;
562 }
563 }
564 break;
565
566 /* + is the precursor to another set of commands */
567 case '+':
568 buf++;
569 switch (toupper(*buf)) {
570 case 'C':
571 buf++;
572 /* Returns true if error occured */
573 if (gn_atem_command_plusc(&buf) == true) {
574 gn_atem_modem_result(MR_ERROR);
575 return;
576 }
577 break;
578
579 case 'G':
580 buf++;
581 /* Returns true if error occured */
582 if (gn_atem_command_plusg(&buf) == true) {
583 gn_atem_modem_result(MR_ERROR);
584 return;
585 }
586 break;
587
588 default:
589 gn_atem_modem_result(MR_ERROR);
590 return;
591 }
592 break;
593
594 /* # is the precursor to another set of commands */
595 case '#':
596 buf++;
597 /* Returns true if error occured */
598 if (gn_atem_command_diesis(&buf) == true) {
599 gn_atem_modem_result(MR_ERROR);
600 return;
601 }
602 break;
603
604 default:
605 gn_atem_modem_result(MR_ERROR);
606 return;
607 }
608 }
609
610 gn_atem_modem_result(MR_OK);
611 }
612
613
gn_atem_sms_print(char * line,gn_sms * message,int mode)614 static void gn_atem_sms_print(char *line, gn_sms *message, int mode)
615 {
616 switch (mode) {
617 case INTERACT_MODE:
618 gsprintf(line, MAX_LINE_LENGTH,
619 _("\r\nDate/time: %d/%d/%d %d:%02d:%02d Sender: %s Msg Center: %s\r\nText: %s\r\n"),
620 message->smsc_time.day, message->smsc_time.month, message->smsc_time.year,
621 message->smsc_time.hour, message->smsc_time.minute, message->smsc_time.second,
622 message->remote.number, message->smsc.number, message->user_data[0].u.text);
623 break;
624 case TEXT_MODE:
625 if ((message->dcs.type == GN_SMS_DCS_GeneralDataCoding) &&
626 (message->dcs.u.general.alphabet == GN_SMS_DCS_8bit))
627 gsprintf(line, MAX_LINE_LENGTH,
628 _("\"%s\",\"%s\",,\"%02d/%02d/%02d,%02d:%02d:%02d+%02d\"\r\n%s"),
629 (message->status ? _("REC READ") : _("REC UNREAD")),
630 message->remote.number,
631 message->smsc_time.year, message->smsc_time.month, message->smsc_time.day,
632 message->smsc_time.hour, message->smsc_time.minute, message->smsc_time.second,
633 message->time.timezone, _("<Not implemented>"));
634 else
635 gsprintf(line, MAX_LINE_LENGTH,
636 _("\"%s\",\"%s\",,\"%02d/%02d/%02d,%02d:%02d:%02d+%02d\"\r\n%s"),
637 (message->status ? _("REC READ") : _("REC UNREAD")),
638 message->remote.number,
639 message->smsc_time.year, message->smsc_time.month, message->smsc_time.day,
640 message->smsc_time.hour, message->smsc_time.minute, message->smsc_time.second,
641 message->time.timezone, message->user_data[0].u.text);
642 break;
643 case PDU_MODE:
644 gsprintf(line, MAX_LINE_LENGTH, _("0,<Not implemented>"));
645 break;
646 default:
647 gsprintf(line, MAX_LINE_LENGTH, _("<Unknown mode>"));
648 break;
649 }
650 }
651
652
gn_atem_sms_handle()653 static void gn_atem_sms_handle()
654 {
655 gn_error error;
656 char buffer[MAX_LINE_LENGTH];
657
658 data.sms->memory_type = SMSType;
659 data.sms->number = SMSNumber;
660 error = gn_sms_get(&data, sm);
661
662 switch (error) {
663 case GN_ERR_NONE:
664 gn_atem_sms_print(buffer, data.sms, INTERACT_MODE);
665 gn_atem_string_out(buffer);
666 break;
667 default:
668 gsprintf(buffer, MAX_LINE_LENGTH, _("\r\nNo message under number %d\r\n"), SMSNumber);
669 gn_atem_string_out(buffer);
670 break;
671 }
672 return;
673 }
674
675 /* Parser for SMS interactive mode */
gn_atem_sms_parse(char * buff)676 void gn_atem_sms_parse(char *buff)
677 {
678 if (!strcasecmp(buff, "HELP")) {
679 gn_atem_string_out(_("\r\nThe following commands work...\r\n"));
680 gn_atem_string_out("DIR\r\n");
681 gn_atem_string_out("EXIT\r\n");
682 gn_atem_string_out("HELP\r\n");
683 return;
684 }
685
686 if (!strcasecmp(buff, "DIR")) {
687 SMSNumber = 1;
688 gn_atem_sms_handle();
689 Parser = gn_atem_dir_parse;
690 return;
691 }
692 if (!strcasecmp(buff, "EXIT")) {
693 Parser = gn_atem_at_parse;
694 gn_atem_modem_result(MR_OK);
695 return;
696 }
697 gn_atem_modem_result(MR_ERROR);
698 }
699
700 /* Parser for DIR sub mode of SMS interactive mode. */
gn_atem_dir_parse(char * buff)701 void gn_atem_dir_parse(char *buff)
702 {
703 switch (toupper(*buff)) {
704 case 'P':
705 SMSNumber--;
706 gn_atem_sms_handle();
707 return;
708 case 'N':
709 SMSNumber++;
710 gn_atem_sms_handle();
711 return;
712 case 'D':
713 data.sms->memory_type = SMSType;
714 data.sms->number = SMSNumber;
715 if (gn_sm_functions(GN_OP_DeleteSMS, &data, sm) == GN_ERR_NONE) {
716 gn_atem_modem_result(MR_OK);
717 } else {
718 gn_atem_modem_result(MR_ERROR);
719 }
720 return;
721 case 'Q':
722 Parser= gn_atem_sms_parse;
723 gn_atem_modem_result(MR_OK);
724 return;
725 }
726 gn_atem_modem_result(MR_ERROR);
727 }
728
729 /* Parser for entering message content (+CMGS) */
gn_atem_sms_parseText(char * buff)730 void gn_atem_sms_parseText(char *buff)
731 {
732 static int index = 0;
733 int i, length;
734 char buffer[MAX_LINE_LENGTH];
735 gn_error error;
736
737 length = strlen(buff);
738
739 sms.user_data[0].type = GN_SMS_DATA_Text;
740
741 for (i = 0; i < length; i++) {
742
743 if (buff[i] == ModemRegisters[REG_CTRLZ]) {
744 /* Exit SMS text mode with sending */
745 sms.user_data[0].u.text[index] = 0;
746 sms.user_data[0].length = index;
747 index = 0;
748 Parser = gn_atem_at_parse;
749 dprintf("Sending SMS to %s (text: %s)\n", data.sms->remote.number, data.sms->user_data[0].u.text);
750
751 /* FIXME: set more SMS fields before sending */
752 error = gn_sms_send(&data, sm);
753
754 if (error == GN_ERR_NONE) {
755 gsprintf(buffer, MAX_LINE_LENGTH, "+CMGS: %d\r\n", data.sms->number);
756 gn_atem_string_out(buffer);
757 gn_atem_modem_result(MR_OK);
758 } else {
759 gn_atem_modem_result(MR_ERROR);
760 }
761 return;
762 } else if (buff[i] == ModemRegisters[REG_ESCAPE]) {
763 /* Exit SMS text mode without sending */
764 sms.user_data[0].u.text[index] = 0;
765 sms.user_data[0].length = index;
766 index = 0;
767 Parser = gn_atem_at_parse;
768 gn_atem_modem_result(MR_OK);
769 return;
770 } else {
771 /* Appent next char to message text */
772 sms.user_data[0].u.text[index++] = buff[i];
773 }
774 }
775
776 /* reached the end of line so insert \n and wait for more */
777 sms.user_data[0].u.text[index++] = '\n';
778 gn_atem_string_out("\r\n> ");
779 }
780
781 /* Handle AT+C commands, this is a quick hack together at this
782 stage. */
gn_atem_command_plusc(char ** buf)783 bool gn_atem_command_plusc(char **buf)
784 {
785 float rflevel = -1;
786 gn_rf_unit rfunits = GN_RF_CSQ;
787 char buffer[MAX_LINE_LENGTH], buffer2[MAX_LINE_LENGTH];
788 int status, index;
789 gn_error error;
790
791 if (strncasecmp(*buf, "SQ", 2) == 0) {
792 buf[0] += 2;
793
794 data.rf_unit = &rfunits;
795 data.rf_level = &rflevel;
796 if (gn_sm_functions(GN_OP_GetRFLevel, &data, sm) == GN_ERR_NONE) {
797 gsprintf(buffer, MAX_LINE_LENGTH, "+CSQ: %0.0f, 99\r\n", *(data.rf_level));
798 gn_atem_string_out(buffer);
799 return (false);
800 } else {
801 return (true);
802 }
803 }
804
805 /* AT+CGMI is Manufacturer information for the ME (phone) so
806 it should be Nokia rather than gnokii... */
807 if (strncasecmp(*buf, "GMI", 3) == 0) {
808 buf[0] += 3;
809 gn_atem_string_out(_("Nokia Mobile Phones\r\n"));
810 return (false);
811 }
812
813 /* AT+CGSN is IMEI */
814 if (strncasecmp(*buf, "GSN", 3) == 0) {
815 buf[0] += 3;
816 snprintf(data.imei, GN_IMEI_MAX_LENGTH, "+CME ERROR: 0");
817 if (gn_sm_functions(GN_OP_GetImei, &data, sm) == GN_ERR_NONE) {
818 gsprintf(buffer, MAX_LINE_LENGTH, "%s\r\n", data.imei);
819 gn_atem_string_out(buffer);
820 return (false);
821 } else {
822 return (true);
823 }
824 }
825
826 /* AT+CGMR is Revision (hardware) */
827 if (strncasecmp(*buf, "GMR", 3) == 0) {
828 buf[0] += 3;
829 snprintf(data.revision, GN_REVISION_MAX_LENGTH, "+CME ERROR: 0");
830 if (gn_sm_functions(GN_OP_GetRevision, &data, sm) == GN_ERR_NONE) {
831 gsprintf(buffer, MAX_LINE_LENGTH, "%s\r\n", data.revision);
832 gn_atem_string_out(buffer);
833 return (false);
834 } else {
835 return (true);
836 }
837 }
838
839 /* AT+CGMM is Model code */
840 if (strncasecmp(*buf, "GMM", 3) == 0) {
841 buf[0] += 3;
842 snprintf(data.model, GN_MODEL_MAX_LENGTH, "+CME ERROR: 0");
843 if (gn_sm_functions(GN_OP_GetModel, &data, sm) == GN_ERR_NONE) {
844 gsprintf(buffer, MAX_LINE_LENGTH, "%s\r\n", data.model);
845 gn_atem_string_out(buffer);
846 return (false);
847 } else {
848 return (true);
849 }
850 }
851
852 /* AT+CMGD is deleting a message */
853 if (strncasecmp(*buf, "MGD", 3) == 0) {
854 buf[0] += 3;
855 switch (**buf) {
856 case '=':
857 buf[0]++;
858 index = atoi(*buf);
859 buf[0] += strlen(*buf);
860
861 data.sms->memory_type = SMSType;
862 data.sms->number = index;
863 error = gn_sm_functions(GN_OP_DeleteSMS, &data, sm);
864
865 switch (error) {
866 case GN_ERR_NONE:
867 break;
868 default:
869 gsprintf(buffer, MAX_LINE_LENGTH, "\r\n+CMS ERROR: %d\r\n", error);
870 gn_atem_string_out(buffer);
871 return (true);
872 }
873 break;
874 default:
875 return (true);
876 }
877 return (false);
878 }
879
880 /* AT+CMGF is mode selection for message format */
881 if (strncasecmp(*buf, "MGF", 3) == 0) {
882 buf[0] += 3;
883 switch (**buf) {
884 case '=':
885 buf[0]++;
886 switch (**buf) {
887 case '0':
888 buf[0]++;
889 MessageFormat = PDU_MODE;
890 break;
891 case '1':
892 buf[0]++;
893 MessageFormat = TEXT_MODE;
894 break;
895 default:
896 return (true);
897 }
898 break;
899 case '?':
900 buf[0]++;
901 gsprintf(buffer, MAX_LINE_LENGTH, "+CMGF: %d\r\n", MessageFormat);
902 gn_atem_string_out(buffer);
903 break;
904 default:
905 return (true);
906 }
907 return (false);
908 }
909
910 /* AT+CMGR is reading a message */
911 if (strncasecmp(*buf, "MGR", 3) == 0) {
912 buf[0] += 3;
913 switch (**buf) {
914 case '=':
915 buf[0]++;
916 index = atoi(*buf);
917 buf[0] += strlen(*buf);
918
919 data.sms->memory_type = SMSType;
920 data.sms->number = index;
921 error = gn_sms_get(&data, sm);
922
923 switch (error) {
924 case GN_ERR_NONE:
925 gn_atem_sms_print(buffer2, data.sms, MessageFormat);
926 gsprintf(buffer, MAX_LINE_LENGTH, "+CMGR: %s\r\n", buffer2);
927 gn_atem_string_out(buffer);
928 break;
929 default:
930 gsprintf(buffer, MAX_LINE_LENGTH, "\r\n+CMS ERROR: %d\r\n", error);
931 gn_atem_string_out(buffer);
932 return (true);
933 }
934 break;
935 default:
936 return (true);
937 }
938 return (false);
939 }
940
941 /* AT+CMGS is sending a message */
942 if (strncasecmp(*buf, "MGS", 3) == 0) {
943 buf[0] += 3;
944 switch (**buf) {
945 case '=':
946 buf[0]++;
947 if (sscanf(*buf, "\"%[+0-9a-zA-Z ]\"", sms.remote.number)) {
948 Parser = gn_atem_sms_parseText;
949 buf[0] += strlen(*buf);
950 gn_atem_string_out("\r\n> ");
951 }
952 return (true);
953 default:
954 return (true);
955 }
956 return (false);
957 }
958
959 /* AT+CMGL is listing messages */
960 if (strncasecmp(*buf, "MGL", 3) == 0) {
961 buf[0] += 3;
962 status = -1;
963
964 switch (**buf) {
965 case 0:
966 case '=':
967 buf[0]++;
968 /* process <stat> parameter */
969 if (*(*buf-1) == 0 || /* i.e. no parameter given */
970 strcasecmp(*buf, "1") == 0 ||
971 strcasecmp(*buf, "3") == 0 ||
972 strcasecmp(*buf, "\"REC READ\"") == 0 ||
973 strcasecmp(*buf, "\"STO SENT\"") == 0) {
974 status = GN_SMS_Sent;
975 } else if (strcasecmp(*buf, "0") == 0 ||
976 strcasecmp(*buf, "2") == 0 ||
977 strcasecmp(*buf, "\"REC UNREAD\"") == 0 ||
978 strcasecmp(*buf, "\"STO UNSENT\"") == 0) {
979 status = GN_SMS_Unsent;
980 } else if (strcasecmp(*buf, "4") == 0 ||
981 strcasecmp(*buf, "\"ALL\"") == 0) {
982 status = 4; /* ALL */
983 } else {
984 return true;
985 }
986 buf[0] += strlen(*buf);
987
988 /* check all message storages */
989 for (index = 1; index <= 20; index++) {
990
991 data.sms->memory_type = SMSType;
992 data.sms->number = index;
993 error = gn_sms_get(&data, sm);
994
995 switch (error) {
996 case GN_ERR_NONE:
997 /* print messsage if it has the required status */
998 if (data.sms->status == status || status == 4 /* ALL */) {
999 gn_atem_sms_print(buffer2, data.sms, MessageFormat);
1000 gsprintf(buffer, MAX_LINE_LENGTH, "+CMGL: %d,%s\r\n", index, buffer2);
1001 gn_atem_string_out(buffer);
1002 }
1003 break;
1004 case GN_ERR_EMPTYLOCATION:
1005 /* don't care if this storage is empty */
1006 break;
1007 default:
1008 /* print other error codes and quit */
1009 gsprintf(buffer, MAX_LINE_LENGTH, "\r\n+CMS ERROR: %d\r\n", error);
1010 gn_atem_string_out(buffer);
1011 return (true);
1012 }
1013 }
1014 break;
1015 default:
1016 return (true);
1017 }
1018 return (false);
1019 }
1020
1021 return (true);
1022 }
1023
1024 /* AT+G commands. Some of these responses are a bit tongue in cheek... */
gn_atem_command_plusg(char ** buf)1025 bool gn_atem_command_plusg(char **buf)
1026 {
1027 char buffer[MAX_LINE_LENGTH];
1028
1029 /* AT+GMI is Manufacturer information for the TA (Terminal Adaptor) */
1030 if (strncasecmp(*buf, "MI", 3) == 0) {
1031 buf[0] += 2;
1032
1033 gn_atem_string_out(_("Hugh Blemings, Pavel Janik ml. and others...\r\n"));
1034 return (false);
1035 }
1036
1037 /* AT+GMR is Revision information for the TA (Terminal Adaptor) */
1038 if (strncasecmp(*buf, "MR", 3) == 0) {
1039 buf[0] += 2;
1040 gsprintf(buffer, MAX_LINE_LENGTH, "%s %s %s\r\n", VERSION, __TIME__, __DATE__);
1041
1042 gn_atem_string_out(buffer);
1043 return (false);
1044 }
1045
1046 /* AT+GMM is Model information for the TA (Terminal Adaptor) */
1047 if (strncasecmp(*buf, "MM", 3) == 0) {
1048 buf[0] += 2;
1049
1050 gsprintf(buffer, MAX_LINE_LENGTH, _("gnokii configured on %s for models %s\r\n"), sm->config.port_device, sm->driver.phone.models);
1051 gn_atem_string_out(buffer);
1052 return (false);
1053 }
1054
1055 /* AT+GSN is Serial number for the TA (Terminal Adaptor) */
1056 if (strncasecmp(*buf, "SN", 3) == 0) {
1057 buf[0] += 2;
1058
1059 gsprintf(buffer, MAX_LINE_LENGTH, _("none built in, choose your own\r\n"));
1060 gn_atem_string_out(buffer);
1061 return (false);
1062 }
1063
1064 return (true);
1065 }
1066
1067 /* Handle AT# commands */
gn_atem_command_diesis(char ** buf)1068 bool gn_atem_command_diesis(char **buf)
1069 {
1070 int number;
1071 char buffer[MAX_LINE_LENGTH];
1072
1073 if (strncasecmp(*buf, "CID", 3) == 0) {
1074 buf[0] += 3;
1075 switch (**buf) {
1076 case '?':
1077 buf[0]++;
1078 gsprintf(buffer, MAX_LINE_LENGTH, "%d\r\n", CallerIDMode);
1079 gn_atem_string_out(buffer);
1080 return (false);
1081 case '=':
1082 buf[0]++;
1083 if (**buf == '?') {
1084 buf[0]++;
1085 gn_atem_string_out("0,1\r\n");
1086 return (false);
1087 } else {
1088 number = gn_atem_num_get(buf);
1089 if ( number == 0 || number == 1 ) {
1090 CallerIDMode = number;
1091 return (false);
1092 }
1093 }
1094 }
1095 }
1096 return (true);
1097 }
1098
1099 /* Send a result string back. There is much work to do here, see
1100 the code in the isdn driver for an idea of where it's heading... */
gn_atem_modem_result(int code)1101 void gn_atem_modem_result(int code)
1102 {
1103 char buffer[16];
1104
1105 if (!(ModemRegisters[REG_VERBOSE] & BIT_VERBOSE)) {
1106 snprintf(buffer, sizeof(buffer), "%d\r\n", code);
1107 gn_atem_string_out(buffer);
1108 } else {
1109 switch (code) {
1110 case MR_OK:
1111 gn_atem_string_out("OK\r\n");
1112 break;
1113
1114 case MR_ERROR:
1115 gn_atem_string_out("ERROR\r\n");
1116 break;
1117
1118 case MR_CARRIER:
1119 gn_atem_string_out("CARRIER\r\n");
1120 break;
1121
1122 case MR_CONNECT:
1123 gn_atem_string_out("CONNECT\r\n");
1124 break;
1125
1126 case MR_NOCARRIER:
1127 gn_atem_string_out("NO CARRIER\r\n");
1128 break;
1129 case MR_RING:
1130 gn_atem_string_out("RING\r\n");
1131 break;
1132 default:
1133 gn_atem_string_out(_("\r\nUnknown Result Code!\r\n"));
1134 break;
1135 }
1136 }
1137
1138 }
1139
1140
1141 /* Get integer from char-pointer, set pointer to end of number
1142 stolen basically verbatim from ISDN code. */
gn_atem_num_get(char ** p)1143 int gn_atem_num_get(char **p)
1144 {
1145 int v = -1;
1146
1147 while (*p[0] >= '0' && *p[0] <= '9') {
1148 v = ((v < 0) ? 0 : (v * 10)) + (int) ((*p[0]++) - '0');
1149 }
1150
1151 return v;
1152 }
1153
1154 /* Write string to virtual modem port, either pty or
1155 STDOUT as appropriate. This function is only used during
1156 command mode - data pump is used when connected. */
gn_atem_string_out(char * buffer)1157 void gn_atem_string_out(char *buffer)
1158 {
1159 int count = 0;
1160 char out_char;
1161
1162 while (count < strlen(buffer)) {
1163
1164 /* Translate CR/LF/BS as appropriate */
1165 switch (buffer[count]) {
1166 case '\r':
1167 out_char = ModemRegisters[REG_CR];
1168 break;
1169 case '\n':
1170 out_char = ModemRegisters[REG_LF];
1171 break;
1172 case '\b':
1173 out_char = ModemRegisters[REG_BS];
1174 break;
1175 default:
1176 out_char = buffer[count];
1177 break;
1178 }
1179
1180 if (write(PtyWRFD, &out_char, 1) < 0) {
1181 fprintf(stderr, _("Failed to output string.\n"));
1182 perror("write");
1183 return;
1184 }
1185 count++;
1186 }
1187 }
1188