/* $Id: gsm.c,v 1.5 2010/04/24 17:51:29 khorben Exp $ */ /* Copyright (c) 2010 Pierre Pronchery */ /* This file is part of DeforaOS Desktop Phone */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include "phone.h" #include "gsm.h" /* GSM */ /* private */ /* types */ struct _GSM { char * device; unsigned int baudrate; unsigned int retry; GIOChannel * channel; char * rd_buf; size_t rd_buf_cnt; char * wr_buf; size_t wr_buf_cnt; /* internal */ guint source; guint rd_io; guint wr_io; }; /* prototypes */ static int _is_figure(int c); static int _is_number(char const * number); /* callbacks */ static gboolean _on_reset(gpointer data); static gboolean _on_watch_read(GIOChannel * source, GIOCondition condition, gpointer data); static gboolean _on_watch_write(GIOChannel * source, GIOCondition condition, gpointer data); /* public */ /* functions */ /* gsm_new */ GSM * gsm_new(char const * device, unsigned int baudrate) { GSM * gsm; if(device == NULL) return NULL; if((gsm = malloc(sizeof(*gsm))) == NULL) return NULL; gsm->device = strdup(device); gsm->baudrate = baudrate; gsm->retry = 1000; gsm->channel = NULL; gsm->rd_buf = NULL; gsm->rd_buf_cnt = 0; gsm->wr_buf = NULL; gsm->wr_buf_cnt = 0; gsm->source = 0; gsm->rd_io = 0; gsm->wr_io = 0; if(gsm->device == NULL) { gsm_delete(gsm); return NULL; } gsm_reset(gsm, 0); return gsm; } /* gsm_delete */ void gsm_delete(GSM * gsm) { free(gsm->device); if(gsm->channel != NULL) { g_io_channel_shutdown(gsm->channel, TRUE, NULL); g_io_channel_unref(gsm->channel); } free(gsm->rd_buf); free(gsm->wr_buf); if(gsm->source != 0) g_source_remove(gsm->source); if(gsm->rd_io != 0) g_source_remove(gsm->rd_io); if(gsm->wr_io != 0) g_source_remove(gsm->wr_io); free(gsm); } /* accessors */ /* gsm_get_retry */ unsigned int gsm_get_retry(GSM * gsm) { return gsm->retry; } /* gsm_set_retry */ void gsm_set_retry(GSM * gsm, unsigned int retry) { gsm->retry = retry; } /* useful */ /* gsm_call */ int gsm_call(GSM * gsm, char const * number) { #ifdef DEBUG fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, number); #endif /* FIXME check current status before calling */ return gsm_modem_call(gsm, number); } /* gsm_hangup */ int gsm_hangup(GSM * gsm) { /* FIXME check current status before hanging up */ return gsm_modem_hangup(gsm); } /* gsm_modem_call */ int gsm_modem_call(GSM * gsm, char const * number) { int ret; char const cmd[] = "ATD"; size_t len; char * buf; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, number); #endif if(!_is_number(number)) return 1; len = sizeof(cmd) + strlen(number) + 2; if((buf = malloc(len)) == NULL) return phone_error(NULL, "malloc", 1); snprintf(buf, len, "%s%s\r\n", cmd, number); ret = gsm_modem_queue(gsm, buf); free(buf); return ret; } /* gsm_modem_hangup */ int gsm_modem_hangup(GSM * gsm) { return gsm_modem_queue(gsm, "\r\nATH\r\n"); } /* gsm_modem_queue */ int gsm_modem_queue(GSM * gsm, char const * command) { size_t len = strlen(command); char * p; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, command); #endif if((p = realloc(gsm->wr_buf, gsm->wr_buf_cnt + len)) == NULL) return phone_error(NULL, "malloc", 1); gsm->wr_buf = p; memcpy(&gsm->wr_buf[gsm->wr_buf_cnt], command, len); gsm->wr_buf_cnt += len; if(gsm->channel != NULL && gsm->wr_buf_cnt > 0 && gsm->wr_io == 0) gsm->wr_io = g_io_add_watch(gsm->channel, G_IO_OUT, _on_watch_write, gsm); return 0; } /* gsm_modem_reset */ int gsm_modem_reset(GSM * gsm) { /* FIXME queue commands in sequence and prepend this one to the list */ return gsm_modem_queue(gsm, "\r\nATZ\r\n"); } /* gsm_reset */ void gsm_reset(GSM * gsm, unsigned int delay) { GError * error = NULL; free(gsm->rd_buf); gsm->rd_buf = NULL; gsm->rd_buf_cnt = 0; free(gsm->wr_buf); gsm->wr_buf = NULL; gsm->wr_buf_cnt = 0; if(gsm->source != 0) { g_source_remove(gsm->source); gsm->source = 0; } if(gsm->rd_io != 0) { g_source_remove(gsm->rd_io); gsm->rd_io = 0; } if(gsm->wr_io != 0) { g_source_remove(gsm->wr_io); gsm->wr_io = 0; } if(gsm->channel != NULL) { g_io_channel_shutdown(gsm->channel, TRUE, &error); g_io_channel_unref(gsm->channel); gsm->channel = NULL; } if(delay > 0) gsm->source = g_timeout_add(delay, _on_reset, gsm); else gsm->source = g_idle_add(_on_reset, gsm); } /* private */ /* functions */ /* is_figure */ static int _is_figure(int c) { if(c >= '0' && c <= '9') return 1; if(c == '*' || c == '+' || c == '#') return 1; return 0; } /* is_number */ static int _is_number(char const * number) { if(number == NULL || number[0] == '\0') return 0; while(*number != '\0') if(!_is_figure(*(number++))) return 0; return 1; } /* callbacks */ /* on_reset */ static int _reset_do(GSM * gsm, int fd); static gboolean _on_reset(gpointer data) { GSM * gsm = data; int fd; GError * error = NULL; gsm->source = 0; if((fd = open(gsm->device, O_RDWR | O_NONBLOCK)) < 0) return phone_error(NULL, "open", FALSE); if(_reset_do(gsm, fd) != 0) { close(fd); if(gsm->retry > 0) gsm->source = g_timeout_add(gsm->retry, _on_reset, gsm); return FALSE; } gsm->channel = g_io_channel_unix_new(fd); if((g_io_channel_set_encoding(gsm->channel, NULL, &error)) != G_IO_STATUS_NORMAL) /* XXX ugly */ fprintf(stderr, "ERROR: %s() g_io_channel_set_encoding\n", __func__); g_io_channel_set_buffered(gsm->channel, FALSE); gsm->rd_io = g_io_add_watch(gsm->channel, G_IO_IN, _on_watch_read, gsm); gsm_modem_reset(gsm); return FALSE; } static int _reset_do(GSM * gsm, int fd) { struct stat st; int fl; struct termios term; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(%d)\n", __func__, fd); #endif if(flock(fd, LOCK_EX | LOCK_NB) != 0) return phone_error(NULL, "flock", 1); if(fstat(fd, &st) != 0) return phone_error(NULL, "fstat", 1); #ifdef DEBUG fprintf(stderr, "DEBUG: %s(%d) mode 0%o\n", __func__, fd, st.st_mode); #endif if(st.st_mode & S_IFCHR) /* character special */ { if(tcgetattr(fd, &term) != 0) return phone_error(NULL, "tcgetattr", 1); switch(gsm->baudrate) /* XXX rewrite this a nicer way */ { case 4800: term.c_cflag = B4800; break; case 9600: term.c_cflag = B9600; break; case 19200: term.c_cflag = B19200; break; case 38400: term.c_cflag = B38400; break; case 115200: term.c_cflag = B115200; break; default: errno = EINVAL; return phone_error(NULL, "baudrate", 1); } term.c_cflag |= CS8; term.c_cflag |= CREAD; term.c_cflag |= CLOCAL; term.c_iflag = (IGNPAR | IGNBRK); term.c_lflag = 0; term.c_oflag = 0; term.c_cc[VMIN] = 1; term.c_cc[VTIME] = 0; if(tcsetattr(fd, TCSAFLUSH, &term) != 0) return phone_error(NULL, "tcsetattr", 1); } fl = fcntl(fd, F_GETFL, 0); if(fcntl(fd, F_SETFL, fl & ~O_NONBLOCK) == -1) return phone_error(NULL, "fcntl", 1); return 0; } /* on_watch_read */ static gboolean _on_watch_read(GIOChannel * source, GIOCondition condition, gpointer data) { GSM * gsm = data; char buf[256]; gsize cnt = 0; GError * error = NULL; GIOStatus status; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(%d)\n", __func__, condition); #endif if(condition != G_IO_IN || source != gsm->channel) return FALSE; /* FIXME really implement */ status = g_io_channel_read_chars(source, buf, sizeof(buf), &cnt, &error); #ifdef DEBUG fprintf(stderr, "DEBUG: %s() cnt=%lu\n", __func__, cnt); #endif switch(status) { case G_IO_STATUS_NORMAL: break; case G_IO_STATUS_ERROR: /* XXX report error, do not break */ fprintf(stderr, "%s%s\n", "phone: read: ", error->message); case G_IO_STATUS_EOF: default: /* should not happen... */ if(gsm->retry > 0) gsm_reset(gsm, gsm->retry); return FALSE; } /* FIXME parse and interpret the output */ #ifdef DEBUG fprintf(stderr, "DEBUG: %s() \"", __func__); fwrite(buf, sizeof(*buf), cnt, stderr); fputs("\"\n", stderr); #endif return TRUE; } /* on_watch_write */ static gboolean _on_watch_write(GIOChannel * source, GIOCondition condition, gpointer data) { GSM * gsm = data; gsize cnt = 0; GError * error = NULL; GIOStatus status; char * p; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(%d)\n", __func__, condition); #endif if(condition != G_IO_OUT || source != gsm->channel) return FALSE; #ifdef DEBUG fprintf(stderr, "DEBUG: %s() write %zu\n", __func__, gsm->wr_buf_cnt); #endif status = g_io_channel_write_chars(source, gsm->wr_buf, gsm->wr_buf_cnt, &cnt, &error); #ifdef DEBUG fprintf(stderr, "DEBUG: %s() wrote %lu \"", __func__, cnt); fwrite(gsm->wr_buf, sizeof(*gsm->wr_buf), cnt, stderr); fputs("\"\n", stderr); #endif if(cnt != 0) /* data may have be written anyway */ { memmove(gsm->wr_buf, &gsm->wr_buf[cnt], gsm->wr_buf_cnt - cnt); gsm->wr_buf_cnt -= cnt; if((p = realloc(gsm->wr_buf, gsm->wr_buf_cnt)) != NULL) gsm->wr_buf = p; } switch(status) { case G_IO_STATUS_NORMAL: break; case G_IO_STATUS_ERROR: /* XXX report error, do not break */ #ifdef DEBUG perror("phone: write"); #else fprintf(stderr, "%s%s\n", "phone: write: ", error->message); #endif case G_IO_STATUS_EOF: default: /* should not happen... */ if(gsm->retry > 0) gsm_reset(gsm, gsm->retry); return FALSE; } if(gsm->wr_buf_cnt > 0) /* there is more data to write */ return TRUE; gsm->wr_io = 0; return FALSE; }