#include "modem.h" #include "lock.h" /* Baudrate for the TTY. Should be greater the 64000. */ #define BAUD_RATE B115200 struct modem *modem_open(const char *dev) { struct modem *m; int fd = -1, n; struct termios ts, ts2; assert(dev); if (device_lock(dev) != 0) return -1; if ((fd = open(dev, O_RDWR|O_NDELAY)) < 0) { daemon_log(LOG_ERR, "Failed to open device <%s>: %s", dev, strerror(errno)); goto fail; } if ((n = fcntl(fd, F_GETFL, 0)) < 0 || fcntl(fd, F_SETFL, n & ~O_NDELAY) < 0) { daemon_log(LOG_ERR, "Failed to remove O_NDELAY flag from device: %s", strerror(errno)); goto fail; } memset(&ts, 0, sizeof ts); ts.c_cflag = CRTSCTS | IGNPAR | HUPCL | CS8; ts.c_iflag = IGNPAR; ts.c_oflag = 0; ts.c_lflag = 0; ts.c_cc[VMIN] = 1; ts.c_cc[VTIME] = 0; cfsetospeed(&ts, BAUD_RATE); cfsetispeed(&ts, BAUD_RATE); tcflush(fd, TCIFLUSH); if (tcsetattr(fd, TCSANOW, &ts) < 0) { daemon_log(LOG_ERR, "Failed to set TTY attributes: %s", strerror(errno)); goto fail; } if (tcgetattr(fd, &ts2) < 0 || memcmp(&ts, &ts2) != 0) { daemon_log(LOG_ERR, "Failed to set TTY attributes"); goto fail; } m = malloc(sizeof(struct modem)); assert(m); memset(m, 0, sizeof(struct modem)); m->dev = strdup(dev); assert(m->dev); m->buffio = buffio_new(fd); assert(m->buffio); m->state = MODEM_STATE_INIT; m->child_pid = -1; modem_timeout(m, 10); modem_next_command(m); return m; fail: if (fd >= 0) close(fd); device_unlock(dev); return NULL; } void modem_close(struct modem *m) { assert(m); if (m->child_pid != -1) child_process_kill(m->child_pid); buffio_free(m->buffio); device_unlock(m->dev); free(m->dev); free(m); } static void* oop_timeout_cb(oop_source *source,struct timeval tv,void *user) { struct modem *m = (struct modem*) user; assert(source && user); daemon_log(LOG_ERR, "Timeout reached for device <%s>", m->device); return OOP_HALT; } static void modem_timeout(struct modem *m, int t) { struct timeval tv = { t, 0 }; assert(m); assert(event_source && event_source->on_time); event_source->cancel_time(event_source, m->timeout, oop_timeout_cb, m); if (t > 0) { gettimeofday(&m->timeout); m->tv_sec += t; assert(event_source && event_source->on_time); event_source->on_time(event_source, m->timeout, oop_timeout_cb, m); } } static void* oop_init_cb(oop_source *source, int fd, oop_event event, void *user) { struct modem *m = (struct modem*) user; assert(source && user); assert(m && m->fd == fd && m->mode == MODEM_STATE_INIT); if (event == OOP_READ) { if (modem_read(m) < 0) return OOP_HALT; } else if (event == OOP_WRITE) { modem_write(m); } return OOP_CONTINUE; } static void* oop_audio_simple_cb(oop_source *source, int fd, oop_event event, void *user) { struct modem *m = (struct modem*) user; assert(source && user); assert(m && m->mode == MODEM_STATE_AUDIO_SIMPLE); if (event == OOP_READ) { modem_read(m); } else if (event == OOP_WRITE) { modem_write(m); } return OOP_CONTINUE; } static void* oop_audio_shbuf_cb(oop_source *source, int fd, oop_event event, void *user) { struct modem *m = (struct modem*) user; assert(source && user); assert(m && m->mode == MODEM_STATE_AUDIO_SHBUF); if (event == OOP_READ) { modem_read(m); } else if (event == OOP_WRITE) { modem_write(m); } return OOP_CONTINUE; } #define INIT_AT_COMMANDS 12 static const char* const init_at_commands[INIT_AT_COMMANDS*2] = { "\nAT&F\n", "OK\r\n", "ATI\n", "Linux ISDN\r\nOK\r\n", "AT&B16\n", "OK\r\n", "AT+FCLASS=8\n", "OK\r\n", "AT+VSM=6\n", "OK\r\n", "ATS18=1\n", "OK\r\n", "ATS14=4\n", "OK\r\n", "ATS13.4=0\n", "OK\r\n", "ATS13.0=1\n", "OK\r\n", "ATS23=1\n", "OK\r\n", "AT&E%s\n", "OK\r\n", "ATS0=0\n", "OK\r\n" }; static void modem_input_init_cb(struct buffio *b, void *user) { struct modem *m = user; assert(b && m && m->buffio == b); switch (m->state) { case MODEM_STATE_INIT: { char *p; assert(m->command_index < INIT_AT_COMMANDS); p = init_at_commands[m->command_index*2+1]; if (buffio_find_input(m->buffio, p)) { m->command_index++; if (m->command_index >= INIT_AT_COMMANDS) { m->state = MODEM_STATE_CALLER_NUMBER_EXPECT; modem_input_init_cb(b, user); } else modem_next_command(m); } break; } case MODEM_STATE_CALLER_NUMBER_EXPECT: if (buffio_find_input(m->buffio, "CALLER NUMBER: ")) { m->state = MODEM_STATE_CALLER_NUMBER; modem_input_init_cb(b, user); } break; case MODEM_STATE_CALLER_NUMBER: { char c[64]; if (buffio_read_line(m->buffio, c, sizeof(c))) { free(m->caller_number); m->caller_number = strdup(c); assert(m->caller_number); m->state = MODEM_STATE_RING_EXPECT; modem_input_init_cb(b, user); } break; } case MODEM_STATE_RING_EXPECT: if (buffio_find_input(m->buffio, "RING/")) { m->state = MODEM_STATE_RING; modem_input_init_cb(b, user); } break; case MODEM_STATE_RING: { char c[64]; if (buffio_read_line(m->buffio, c, sizeof(c))) { free(m->ring_number); m->ring_number = strdup(c); assert(m->ring_number); if (msntab_check_call(m->ring_number, m->caller_number)) { modem_send_command(m, "ATA\n"); m->state = MODEM_STATE_ANSWER; } else { m->state = MODEM_STATE_CALLER_NUMBER_EXPECT; modem_input_init_cb(b, user); } } break; } case MODEM_STATE_ANSWER: if (buffio_find_input(m->buffio, "VCON\r\n")) { buffio_command(m->buffio, "AT+VTX+VRX"); m->state = MODEM_STATE_VTXVRX; } break; case MODEM_STATE_VTXVRX: if (buffio_find_input(m->buffio, "CONNECT\r\n\r\nCONNECT\r\n")) { m->state = MODEM_STATE_CONNECTION; modem_input_init_cb(b, user); } break; case MODEM_STATE_CONNECTION: break; case MODEM_STATE_DONE: break; } } static void modem_send_command(struct modem *m, const char *p) { assert(m && m->buffio); buffio_input_flush(m->buffio); m->buffio->input_cb = modem_input_cb; buffio_print(m->buffio, p); } static void modem_next_command(struct modem *m) { char *p; char tmp[64]; assert(m && m->buffio && (m->state == MODEM_STATE_INIT || m->state == MODEM_STATE_DONE)); assert(m->command_index < INIT_AT_COMMANDS) p = init_at_commands[m->command_index*2]; if (m->command_index == 10) { snprintf(tmp, sizeof(tmp), p, m->local_msn); p = tmp; } buffio_input_flush(m->buffio); m->buffio->input_cb = modem_input_cb; buffio_command(m->buffio, p); } // { DLE, DC4, DLE, ETX, 'A', 'T', 'H', '\n', "AT&F\n" },