/* $Id: hayes.c,v 1.1.2.9 2011/01/21 11:43:44 khorben Exp $ */ /* Copyright (c) 2011 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 #include #include /* Hayes */ /* private */ /* types */ typedef enum _HayesMode { HAYES_MODE_INIT = 0, HAYES_MODE_COMMAND } HayesMode; typedef struct _Hayes { char * device; unsigned int baudrate; unsigned int retry; unsigned int hwflow; unsigned int quirks; /* modem */ guint source; guint timeout; GIOChannel * channel; char * rd_buf; size_t rd_buf_cnt; guint rd_source; char * wr_buf; size_t wr_buf_cnt; guint wr_source; /* queue */ HayesMode mode; GSList * queue; /* internal */ ModemEvent events[MODEM_EVENT_TYPE_COUNT]; char * authentication_status_name; char * model_name; char * model_vendor; char * model_version; char * registration_media; char * registration_operator; } Hayes; typedef enum _HayesCommandPriority { HCP_NORMAL = 0, HCP_IMMEDIATE } HayesCommandPriority; typedef enum _HayesCommandStatus { HCS_PENDING = 0, HCS_QUEUED, HCS_ACTIVE, HCS_TIMEOUT, HCS_ERROR, HCS_SUCCESS } HayesCommandStatus; typedef struct _HayesCommand HayesCommand; typedef HayesCommandStatus (*HayesCommandCallback)(HayesCommand * command, HayesCommandStatus status, void * priv); struct _HayesCommand { HayesCommandPriority priority; HayesCommandStatus status; /* request */ char * attention; unsigned int timeout; HayesCommandCallback callback; void * priv; /* answer */ char * answer; }; typedef enum _HayesQuirk { HAYES_QUIRK_BATTERY_69 = 0x1, HAYES_QUIRK_CPIN_QUOTES = 0x2, HAYES_QUIRK_WANT_SMSC_IN_PDU = 0x4 } HayesQuirk; typedef struct _HayesRequestHandler { unsigned int type; char const * attention; HayesCommandCallback callback; } HayesRequestHandler; typedef struct _HayesTriggerHandler { char const * trigger; void (*callback)(ModemPlugin * modem, char const * answer); } HayesTriggerHandler; /* constants */ enum { HAYES_REQUEST_ALIVE = MODEM_REQUEST_COUNT, HAYES_REQUEST_BATTERY_LEVEL, HAYES_REQUEST_CONTACT, HAYES_REQUEST_EXTENDED_ERRORS, HAYES_REQUEST_EXTENDED_RING_REPORTS, HAYES_REQUEST_LOCAL_ECHO_DISABLE, HAYES_REQUEST_LOCAL_ECHO_ENABLE, HAYES_REQUEST_MESSAGE, HAYES_REQUEST_MODEL, HAYES_REQUEST_OPERATOR, HAYES_REQUEST_OPERATOR_FORMAT_SHORT, HAYES_REQUEST_OPERATOR_FORMAT_LONG, HAYES_REQUEST_OPERATOR_FORMAT_NUMERIC, HAYES_REQUEST_PHONE_ACTIVE, HAYES_REQUEST_SIGNAL_LEVEL, HAYES_REQUEST_SIM_PIN_VALID, HAYES_REQUEST_VENDOR, HAYES_REQUEST_VERBOSE, HAYES_REQUEST_VERSION }; /* prototypes */ /* modem */ static int _hayes_init(ModemPlugin * modem); static int _hayes_destroy(ModemPlugin * modem); static int _hayes_start(ModemPlugin * modem); static int _hayes_stop(ModemPlugin * modem); static int _hayes_request(ModemPlugin * modem, ModemRequest * request); static int _hayes_trigger(ModemPlugin * modem, ModemEventType event); /* parser */ static int _hayes_parse(ModemPlugin * modem); static int _hayes_parse_trigger(ModemPlugin * modem, char const * answer, HayesCommand * command); /* queue */ static int _hayes_queue_command(ModemPlugin * modem, HayesCommand * command); static int _hayes_queue_command_full(ModemPlugin * modem, char const * attention, HayesCommandCallback callback); static void _hayes_queue_flush(ModemPlugin * modem); static int _hayes_queue_pop(ModemPlugin * modem); static int _hayes_queue_push(ModemPlugin * modem); static int _hayes_reset(ModemPlugin * modem); /* commands */ static HayesCommand * _hayes_command_new(char const * attention); static void _hayes_command_delete(HayesCommand * command); static char const * _hayes_command_get_answer(HayesCommand * command); static char const * _hayes_command_get_attention(HayesCommand * command); static char * _hayes_command_get_line(HayesCommand * command, char const * prefix); static HayesCommandStatus _hayes_command_get_status(HayesCommand * command); static unsigned int _hayes_command_get_timeout(HayesCommand * command); static void _hayes_command_set_callback(HayesCommand * command, HayesCommandCallback callback, void * priv); static void _hayes_command_set_priority(HayesCommand * command, HayesCommandPriority priority); static void _hayes_command_set_status(HayesCommand * command, HayesCommandStatus status); static void _hayes_command_set_timeout(HayesCommand * command, unsigned int timeout); static int _hayes_command_answer_append(HayesCommand * command, char const * answer); static HayesCommandStatus _hayes_command_callback(HayesCommand * command); /* callbacks */ static gboolean _on_reset(gpointer data); static gboolean _on_timeout(gpointer data); static gboolean _on_watch_can_read(GIOChannel * source, GIOCondition condition, gpointer data); static gboolean _on_watch_can_write(GIOChannel * source, GIOCondition condition, gpointer data); static HayesCommandStatus _on_request_battery_level(HayesCommand * command, HayesCommandStatus status, void * priv); static HayesCommandStatus _on_request_call_status(HayesCommand * command, HayesCommandStatus status, void * priv); static HayesCommandStatus _on_request_contacts(HayesCommand * command, HayesCommandStatus status, void * priv); static HayesCommandStatus _on_request_generic(HayesCommand * command, HayesCommandStatus status, void * priv); static HayesCommandStatus _on_request_messages(HayesCommand * command, HayesCommandStatus status, void * priv); static HayesCommandStatus _on_request_model(HayesCommand * command, HayesCommandStatus status, void * priv); static HayesCommandStatus _on_request_registration(HayesCommand * command, HayesCommandStatus status, void * priv); static void _on_trigger_cbc(ModemPlugin * modem, char const * answer); static void _on_trigger_cgmi(ModemPlugin * modem, char const * answer); static void _on_trigger_cgmm(ModemPlugin * modem, char const * answer); static void _on_trigger_cgmr(ModemPlugin * modem, char const * answer); static void _on_trigger_cops(ModemPlugin * modem, char const * answer); static void _on_trigger_cpas(ModemPlugin * modem, char const * answer); static void _on_trigger_cpin(ModemPlugin * modem, char const * answer); static void _on_trigger_cring(ModemPlugin * modem, char const * answer); static void _on_trigger_csq(ModemPlugin * modem, char const * answer); /* variables */ static const struct { char const * model; unsigned int quirks; } _hayes_quirks[] = { { "\"Neo1973 Embedded GSM Modem\"", HAYES_QUIRK_CPIN_QUOTES | HAYES_QUIRK_WANT_SMSC_IN_PDU }, { "\"Neo1973 GTA01/GTA02 Embedded GSM Modem\"", HAYES_QUIRK_CPIN_QUOTES | HAYES_QUIRK_WANT_SMSC_IN_PDU }, { "\"Neo1973 GTA02 Embedded GSM Modem\"", HAYES_QUIRK_CPIN_QUOTES | HAYES_QUIRK_WANT_SMSC_IN_PDU }, { "Nokia N900", HAYES_QUIRK_BATTERY_69 }, { NULL, 0 } }; static HayesRequestHandler _hayes_request_handlers[] = { { HAYES_REQUEST_ALIVE, "AT", NULL }, { HAYES_REQUEST_BATTERY_LEVEL, "AT+CBC", _on_request_battery_level }, { HAYES_REQUEST_EXTENDED_ERRORS, "AT+CMEE=1", NULL }, { HAYES_REQUEST_EXTENDED_RING_REPORTS, "AT+CRC=1", NULL }, { HAYES_REQUEST_LOCAL_ECHO_DISABLE, "ATE0", NULL }, { HAYES_REQUEST_LOCAL_ECHO_ENABLE, "ATE1", NULL }, { HAYES_REQUEST_MODEL, "AT+CGMM", _on_request_model }, { HAYES_REQUEST_OPERATOR, "AT+COPS?", _on_request_registration }, { HAYES_REQUEST_OPERATOR_FORMAT_LONG, "AT+COPS=3,0", NULL }, { HAYES_REQUEST_OPERATOR_FORMAT_NUMERIC, "AT+COPS=3,2", NULL }, { HAYES_REQUEST_OPERATOR_FORMAT_SHORT, "AT+COPS=3,1", NULL }, { HAYES_REQUEST_PHONE_ACTIVE, "AT+CPAS",_on_request_call_status }, { HAYES_REQUEST_SIGNAL_LEVEL, "AT+CSQ", _on_request_registration }, { HAYES_REQUEST_SIM_PIN_VALID, "AT+CPIN?", NULL }, { HAYES_REQUEST_VENDOR, "AT+CGMI", _on_request_model }, { HAYES_REQUEST_VERBOSE,"ATV1", NULL }, { HAYES_REQUEST_VERSION,"AT+CGMR", _on_request_model }, { MODEM_REQUEST_CALL_ANSWER, "ATA", _on_request_call_status }, { MODEM_REQUEST_CALL_HANGUP, "ATH", _on_request_call_status }, { MODEM_REQUEST_CONTACT_LIST, "AT+CPBR=?", _on_request_contacts }, { MODEM_REQUEST_MESSAGE_LIST, "AT+CMGL=4", _on_request_messages } }; static HayesTriggerHandler _hayes_trigger_handlers[] = { { "+CBC", _on_trigger_cbc }, { "+CGMI", _on_trigger_cgmi }, { "+CGMM", _on_trigger_cgmm }, { "+CGMR", _on_trigger_cgmr }, { "+COPS", _on_trigger_cops }, { "+CPAS", _on_trigger_cpas }, { "+CPIN", _on_trigger_cpin }, { "+CRING", _on_trigger_cring }, { "+CSQ", _on_trigger_csq } }; /* public */ /* variables */ ModemPlugin plugin = { NULL, "Hayes", NULL, _hayes_init, _hayes_destroy, _hayes_start, _hayes_stop, _hayes_request, _hayes_trigger, NULL }; /* private */ /* functions */ static int _hayes_init(ModemPlugin * modem) { Hayes * hayes; char const * p; size_t i; if((hayes = object_new(sizeof(*hayes))) == NULL) return -1; memset(hayes, 0, sizeof(*hayes)); modem->priv = hayes; if((p = modem->helper->config_get(modem->helper->modem, NULL, "modem_device")) == NULL) p = "/dev/ttyU0"; hayes->device = string_new(p); hayes->baudrate = 115200; /* FIXME fetch from configuration */ hayes->retry = 1000; hayes->hwflow = 0; /* FIXME fetch from configuration */ hayes->mode = HAYES_MODE_INIT; for(i = 0; i < sizeof(hayes->events) / sizeof(*hayes->events); i++) { hayes->events[i].type = i; if(i == MODEM_EVENT_TYPE_REGISTRATION) hayes->events[i].registration.signal = 0.0 / 0.0; } if(hayes->device == NULL) return _hayes_destroy(modem); return 0; } /* hayes_destroy */ static int _hayes_destroy(ModemPlugin * modem) { Hayes * hayes = modem->priv; _hayes_stop(modem); string_delete(hayes->authentication_status_name); string_delete(hayes->model_name); string_delete(hayes->model_vendor); string_delete(hayes->model_version); string_delete(hayes->registration_media); string_delete(hayes->registration_operator); string_delete(hayes->device); object_delete(hayes); return 0; } /* hayes_request */ static char * _request_attention(ModemPlugin * modem, ModemRequestType request); static int _hayes_request(ModemPlugin * modem, ModemRequest * request) { int ret; size_t i; size_t count = sizeof(_hayes_request_handlers) / sizeof(*_hayes_request_handlers); char const * attention; char * p = NULL; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(%u)\n", __func__, (request != NULL) ? request->type : (unsigned)-1); #endif if(request == NULL) return -1; for(i = 0; i < count; i++) if(_hayes_request_handlers[i].type == request->type) break; if(i == count) return -1; if((attention = _hayes_request_handlers[i].attention) == NULL) { if((p = _request_attention(modem, request->type)) == NULL) return -1; attention = p; } ret = _hayes_queue_command_full(modem, attention, _hayes_request_handlers[i].callback); free(p); return ret; } static char * _request_attention(ModemPlugin * modem, ModemRequestType request) { /* FIXME only useful if some requests have to be built on the fly */ switch(request) { default: break; } return NULL; } /* hayes_start */ static int _hayes_start(ModemPlugin * modem) { Hayes * hayes = modem->priv; hayes->source = g_idle_add(_on_reset, modem); return 0; } /* hayes_stop */ static int _hayes_stop(ModemPlugin * modem) { Hayes * hayes = modem->priv; GError * error = NULL; _hayes_queue_flush(modem); if(hayes->channel != NULL) { /* XXX should the file descriptor also be closed? */ g_io_channel_shutdown(hayes->channel, TRUE, &error); g_io_channel_unref(hayes->channel); hayes->channel = NULL; } /* FIXME some more? */ return 0; } /* hayes_trigger */ static int _hayes_trigger(ModemPlugin * modem, ModemEventType event) { int ret = 0; ModemRequest request; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(%u)\n", __func__, event); #endif switch(event) { case MODEM_EVENT_TYPE_AUTHENTICATION_STATUS: request.type = HAYES_REQUEST_SIM_PIN_VALID; return _hayes_request(modem, &request); case MODEM_EVENT_TYPE_BATTERY_LEVEL: request.type = HAYES_REQUEST_BATTERY_LEVEL; return _hayes_request(modem, &request); case MODEM_EVENT_TYPE_CALL_STATUS: request.type = HAYES_REQUEST_PHONE_ACTIVE; return _hayes_request(modem, &request); case MODEM_EVENT_TYPE_MODEL: request.type = HAYES_REQUEST_VENDOR; ret |= _hayes_request(modem, &request); request.type = HAYES_REQUEST_MODEL; ret |= _hayes_request(modem, &request); request.type = HAYES_REQUEST_VERSION; ret |= _hayes_request(modem, &request); break; case MODEM_EVENT_TYPE_REGISTRATION: request.type = HAYES_REQUEST_OPERATOR_FORMAT_LONG; ret |= _hayes_request(modem, &request); request.type = HAYES_REQUEST_OPERATOR; ret |= _hayes_request(modem, &request); request.type = HAYES_REQUEST_SIGNAL_LEVEL; ret |= _hayes_request(modem, &request); break; case MODEM_EVENT_TYPE_CONTACT: /* do not make sense */ case MODEM_EVENT_TYPE_ERROR: case MODEM_EVENT_TYPE_MESSAGE: case MODEM_EVENT_TYPE_MESSAGE_SENT: ret = -1; break; } return ret; } /* parser */ static int _parse_do(ModemPlugin * modem); static int _hayes_parse(ModemPlugin * modem) { Hayes * hayes = modem->priv; int ret = 0; size_t i = 0; char * p; #ifdef DEBUG fprintf(stderr, "DEBUG: %s() cnt=%lu\n", __func__, (unsigned long)hayes->rd_buf_cnt); #endif while(i < hayes->rd_buf_cnt) { if(hayes->rd_buf[i++] != '\r' && hayes->rd_buf[i - 1] != '\n') continue; hayes->rd_buf[i - 1] = '\0'; if(i < hayes->rd_buf_cnt && hayes->rd_buf[i] == '\n') i++; if(hayes->rd_buf[0] != '\0') ret |= _parse_do(modem); hayes->rd_buf_cnt -= i; memmove(hayes->rd_buf, &hayes->rd_buf[i], hayes->rd_buf_cnt); if((p = realloc(hayes->rd_buf, hayes->rd_buf_cnt)) != NULL) hayes->rd_buf = p; /* we can ignore errors... */ else if(hayes->rd_buf_cnt == 0) hayes->rd_buf = NULL; /* ...except when it's not one */ i = 0; } #if 0 if(hayes->mode == HAYES_MODE_PDU) return _parse_pdu(modem); #endif return ret; } static int _parse_do(ModemPlugin * modem) { Hayes * hayes = modem->priv; HayesCommand * command = (hayes->queue != NULL) ? hayes->queue->data : NULL; HayesCommandStatus status; /* FIXME only when non-empty, and not OK/ERROR/NO CARRIER... */ if(command == NULL) /* this was most likely unsollicited */ return _hayes_parse_trigger(modem, hayes->rd_buf, NULL); if(_hayes_command_answer_append(command, hayes->rd_buf) != 0) return -1; if((status = _hayes_command_callback(command)) == HCS_SUCCESS || status == HCS_ERROR) { _hayes_queue_pop(modem); _hayes_queue_push(modem); } else if(status == HCS_ACTIVE) return _hayes_parse_trigger(modem, hayes->rd_buf, command); return 0; } /* hayes_parse_trigger */ static int _hayes_parse_trigger(ModemPlugin * modem, char const * answer, HayesCommand * command) { size_t i; size_t count = sizeof(_hayes_trigger_handlers) / sizeof(*_hayes_trigger_handlers); size_t len; HayesTriggerHandler * th; char const * p; int j; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(modem, \"%s\", command)\n", __func__, answer); #endif /* if the trigger is obvious return directly */ for(i = 0; i < count; i++) { th = &_hayes_trigger_handlers[i]; len = strlen(th->trigger); if(strncmp(th->trigger, answer, len) != 0 || answer[len] != ':') continue; if(answer[++len] == ' ') /* skip the optional space */ len++; th->callback(modem, &answer[len]); return 0; } /* if the answer has no prefix choose it from the command issued */ if(command == NULL || (p = _hayes_command_get_attention(command)) == NULL || strncmp(p, "AT", 2) != 0) return 0; for(i = 0; i < count; i++) { th = &_hayes_trigger_handlers[i]; len = strlen(th->trigger); if(strncmp(th->trigger, &p[2], len) != 0 || isalnum((j = p[2 + len]))) continue; th->callback(modem, answer); return 0; } return 0; } /* queue */ /* hayes_queue_command */ static int _hayes_queue_command(ModemPlugin * modem, HayesCommand * command) { Hayes * hayes = modem->priv; GSList * queue; switch(hayes->mode) { case HAYES_MODE_INIT: /* FIXME should ignore commands sent at this point */ case HAYES_MODE_COMMAND: queue = hayes->queue; hayes->queue = g_slist_append(hayes->queue, command); _hayes_command_set_status(command, HCS_QUEUED); if(queue == NULL) _hayes_queue_push(modem); break; } return 0; } /* hayes_queue_command_full */ static int _hayes_queue_command_full(ModemPlugin * modem, char const * attention, HayesCommandCallback callback) { HayesCommand * command; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, attention); #endif if((command = _hayes_command_new(attention)) == NULL) return -modem->helper->error(modem->helper->modem, error_get(), 1); _hayes_command_set_callback(command, callback, modem); if(_hayes_queue_command(modem, command) != 0) { _hayes_command_delete(command); return -1; } return 0; } /* hayes_queue_flush */ static void _hayes_queue_flush(ModemPlugin * modem) { Hayes * hayes = modem->priv; g_slist_foreach(hayes->queue, (GFunc)_hayes_command_delete, NULL); g_slist_free(hayes->queue); hayes->queue = NULL; free(hayes->rd_buf); hayes->rd_buf = NULL; hayes->rd_buf_cnt = 0; if(hayes->rd_source != 0) g_source_remove(hayes->rd_source); hayes->rd_source = 0; hayes->wr_buf = NULL; hayes->wr_buf_cnt = 0; if(hayes->wr_source != 0) g_source_remove(hayes->wr_source); hayes->wr_source = 0; if(hayes->source != 0) g_source_remove(hayes->source); hayes->source = 0; if(hayes->timeout != 0) g_source_remove(hayes->timeout); hayes->timeout = 0; } /* hayes_queue_pop */ static int _hayes_queue_pop(ModemPlugin * modem) { Hayes * hayes = modem->priv; HayesCommand * command; #ifdef DEBUG fprintf(stderr, "DEBUG: %s()\n", __func__); #endif if(hayes->timeout != 0) g_source_remove(hayes->timeout); hayes->timeout = 0; if(hayes->queue == NULL) /* nothing to send */ return 0; command = hayes->queue->data; /* XXX assumes it's valid */ _hayes_command_delete(command); hayes->queue = g_slist_remove(hayes->queue, command); return 0; } /* hayes_queue_push */ static int _hayes_queue_push(ModemPlugin * modem) { Hayes * hayes = modem->priv; HayesCommand * command; char const * attention; const char suffix[2] = "\r\n"; size_t size; char * p; if(hayes->queue == NULL) /* nothing to send */ return 0; command = hayes->queue->data; attention = _hayes_command_get_attention(command); #ifdef DEBUG fprintf(stderr, "DEBUG: %s() pushing \"%s\"\n", __func__, attention); #endif /* FIXME prepend escape sequence if we're in PPP mode */ size = hayes->wr_buf_cnt + strlen(attention) + sizeof(suffix); if((p = realloc(hayes->wr_buf, size)) == NULL) return -modem->helper->error(modem->helper->modem, strerror( errno), 1); hayes->wr_buf = p; sprintf(&hayes->wr_buf[hayes->wr_buf_cnt], "%s%s", attention, suffix); hayes->wr_buf_cnt = size; _hayes_command_set_status(command, HCS_ACTIVE); if(hayes->channel != NULL && hayes->wr_source == 0) hayes->wr_source = g_io_add_watch(hayes->channel, G_IO_OUT, _on_watch_can_write, modem); if(hayes->timeout != 0) g_source_remove(hayes->timeout); if((hayes->timeout = _hayes_command_get_timeout(command)) != 0) hayes->timeout = g_timeout_add(hayes->timeout, _on_timeout, modem); return 0; } /* hayes_reset */ static int _hayes_reset(ModemPlugin * modem) { Hayes * hayes = modem->priv; unsigned int retry = hayes->retry; /* FIXME risky */ _hayes_destroy(modem); if(_hayes_init(modem) != 0) return -1; hayes = modem->priv; hayes->retry = retry; return 0; } /* commands */ static HayesCommand * _hayes_command_new(char const * attention) { HayesCommand * command; if((command = object_new(sizeof(*command))) == NULL) return NULL; command->priority = HCP_NORMAL; command->status = HCS_PENDING; command->attention = string_new(attention); command->timeout = 30000; command->callback = _on_request_generic; command->priv = command; command->answer = NULL; if(command->attention == NULL) { _hayes_command_delete(command); return NULL; } return command; } /* hayes_command_delete */ static void _hayes_command_delete(HayesCommand * command) { string_delete(command->attention); string_delete(command->answer); object_delete(command); } /* hayes_command_get_answer */ static char const * _hayes_command_get_answer(HayesCommand * command) { return command->answer; } /* hayes_command_get_attention */ static char const * _hayes_command_get_attention(HayesCommand * command) { return command->attention; } /* hayes_command_get_line */ static char * _hayes_command_get_line(HayesCommand * command, char const * prefix) { /* FIXME also return the other lines matching */ char * ret; char const * answer = command->answer; size_t len; char * p; if(prefix == NULL) return NULL; len = strlen(prefix); while(answer != NULL) if(strncmp(answer, prefix, len) == 0 && strncmp(&answer[len], ": ", 2) == 0) { if((ret = string_new(&answer[len + 2])) != NULL && (p = strchr(ret, '\n')) != NULL) *p = '\0'; return ret; } else if((answer = strchr(answer, '\n')) != NULL) answer++; return NULL; } /* hayes_command_get_status */ static HayesCommandStatus _hayes_command_get_status(HayesCommand * command) { return command->status; } /* hayes_command_get_timeout */ static unsigned int _hayes_command_get_timeout(HayesCommand * command) { return command->timeout; } /* hayes_command_set_callback */ static void _hayes_command_set_callback(HayesCommand * command, HayesCommandCallback callback, void * priv) { command->callback = callback; command->priv = priv; } /* hayes_command_set_priority */ static void _hayes_command_set_priority(HayesCommand * command, HayesCommandPriority priority) { command->priority = priority; } /* hayes_command_set_status */ static void _hayes_command_set_status(HayesCommand * command, HayesCommandStatus status) { command->status = status; } /* hayes_command_set_timeout */ static void _hayes_command_set_timeout(HayesCommand * command, unsigned int timeout) { command->timeout = timeout; } /* hayes_command_answer_append */ static int _hayes_command_answer_append(HayesCommand * command, char const * answer) { char * p; if(answer == NULL) return 0; if(command->answer == NULL) p = string_new_append(answer, "\n", NULL); else p = string_new_append(command->answer, answer, "\n", NULL); if(p == NULL) return -1; free(command->answer); command->answer = p; return 0; } /* hayes_command_callback */ static HayesCommandStatus _hayes_command_callback(HayesCommand * command) { if(command->callback == NULL) return HCS_SUCCESS; /* XXX may need to change */ return command->callback(command, command->status, command->priv); } /* callbacks */ /* on_reset */ static int _reset_open(Hayes * hayes); static int _reset_configure(Hayes * hayes, int fd); static gboolean _reset_settle(gpointer data); static HayesCommandStatus _on_reset_callback(HayesCommand * command, HayesCommandStatus status, void * priv); static gboolean _on_reset(gpointer data) { ModemPlugin * modem = data; Hayes * hayes = modem->priv; GError * error = NULL; int fd; #ifdef DEBUG fprintf(stderr, "DEBUG: %s()\n", __func__); #endif hayes->source = 0; if(hayes->channel != NULL) { /* XXX should the file descriptor also be closed? */ g_io_channel_shutdown(hayes->channel, TRUE, &error); g_io_channel_unref(hayes->channel); hayes->channel = NULL; } if((fd = _reset_open(hayes)) < 0) { modem->helper->error(NULL, error_get(), 1); if(hayes->retry > 0) hayes->source = g_timeout_add(hayes->retry, _on_reset, modem); return FALSE; } hayes->channel = g_io_channel_unix_new(fd); if((g_io_channel_set_encoding(hayes->channel, NULL, &error)) != G_IO_STATUS_NORMAL) modem->helper->error(modem->helper->modem, error->message, 1); g_io_channel_set_buffered(hayes->channel, FALSE); hayes->rd_source = g_io_add_watch(hayes->channel, G_IO_IN, _on_watch_can_read, modem); _reset_settle(modem); return FALSE; } static int _reset_open(Hayes * hayes) { int fd; if((fd = open(hayes->device, O_RDWR | O_NONBLOCK)) < 0) return -error_set_code(1, "%s: %s", hayes->device, strerror( errno)); if(_reset_configure(hayes, fd) != 0) { close(fd); return -1; } return fd; } static int _reset_configure(Hayes * hayes, int fd) { struct stat st; int fl; struct termios term; if(flock(fd, LOCK_EX | LOCK_NB) != 0) return 1; fl = fcntl(fd, F_GETFL, 0); if(fcntl(fd, F_SETFL, fl & ~O_NONBLOCK) == -1) return 1; if(fstat(fd, &st) != 0) return 1; if(st.st_mode & S_IFCHR) /* character special */ { if(tcgetattr(fd, &term) != 0) return 1; term.c_cflag &= ~(CSIZE | PARENB); term.c_cflag |= CS8; term.c_cflag |= CREAD; term.c_cflag |= CLOCAL; if(hayes->hwflow != 0) term.c_cflag |= CRTSCTS; else term.c_cflag &= ~CRTSCTS; term.c_iflag = (IGNPAR | IGNBRK); term.c_lflag = 0; term.c_oflag = 0; term.c_cc[VMIN] = 1; term.c_cc[VTIME] = 0; if(cfsetispeed(&term, 0) != 0) /* same speed as output speed */ error_set("%s", hayes->device); /* go on anyway */ if(cfsetospeed(&term, hayes->baudrate) != 0) error_set("%s", hayes->device); /* go on anyway */ if(tcsetattr(fd, TCSAFLUSH, &term) != 0) return 1; } return 0; } static gboolean _reset_settle(gpointer data) { ModemPlugin * modem = data; HayesCommand * command; if((command = _hayes_command_new("ATZ")) == NULL) { modem->helper->error(modem->helper->modem, error_get(), 1); return FALSE; } _hayes_command_set_callback(command, _on_reset_callback, modem); _hayes_command_set_priority(command, HCP_IMMEDIATE); _hayes_command_set_timeout(command, 500); if(_hayes_queue_command(modem, command) != 0) { modem->helper->error(modem->helper->modem, error_get(), 1); _hayes_command_delete(command); } return FALSE; } static HayesCommandStatus _on_reset_callback(HayesCommand * command, HayesCommandStatus status, void * priv) { ModemPlugin * modem = priv; Hayes * hayes = modem->priv; ModemRequest request; #ifdef DEBUG fprintf(stderr, "DEBUG: %s()\n", __func__); #endif if(hayes->timeout != 0) g_source_remove(hayes->timeout); hayes->timeout = 0; switch(status) { case HCS_PENDING: /* should not happen */ case HCS_QUEUED: case HCS_SUCCESS: case HCS_ERROR: break; case HCS_ACTIVE:/* XXX means a reply was obtained */ status = _on_request_generic(command, status, modem); if(status != HCS_SUCCESS && status != HCS_ERROR) return HCS_ACTIVE; hayes->mode = HAYES_MODE_COMMAND; request.type = HAYES_REQUEST_LOCAL_ECHO_DISABLE; _hayes_request(modem, &request); request.type = HAYES_REQUEST_VERBOSE; _hayes_request(modem, &request); request.type = HAYES_REQUEST_EXTENDED_ERRORS; _hayes_request(modem, &request); request.type = HAYES_REQUEST_EXTENDED_RING_REPORTS; _hayes_request(modem, &request); _hayes_trigger(modem, MODEM_EVENT_TYPE_MODEL); _hayes_trigger(modem, MODEM_EVENT_TYPE_AUTHENTICATION_STATUS); _hayes_trigger(modem, MODEM_EVENT_TYPE_REGISTRATION); _hayes_trigger(modem, MODEM_EVENT_TYPE_CALL_STATUS); return HCS_SUCCESS; case HCS_TIMEOUT: /* try again */ _reset_settle(modem); break; } return HCS_ERROR; /* destroy and queue again */ } /* on_timeout */ static gboolean _on_timeout(gpointer data) { ModemPlugin * modem = data; Hayes * hayes = modem->priv; HayesCommand * command; #ifdef DEBUG fprintf(stderr, "DEBUG: %s()\n", __func__); #endif hayes->timeout = 0; if(hayes->queue == NULL || (command = hayes->queue->data) == NULL) return FALSE; _hayes_command_set_status(command, HCS_TIMEOUT); _hayes_command_callback(command); _hayes_queue_pop(modem); _hayes_queue_push(modem); return FALSE; } /* on_watch_can_read */ static gboolean _on_watch_can_read(GIOChannel * source, GIOCondition condition, gpointer data) { ModemPlugin * modem = data; Hayes * hayes = modem->priv; gsize cnt = 0; GError * error = NULL; GIOStatus status; char * p; #ifdef DEBUG fprintf(stderr, "DEBUG: %s()\n", __func__); #endif if(condition != G_IO_IN || source != hayes->channel) return FALSE; /* should not happen */ if((p = realloc(hayes->rd_buf, hayes->rd_buf_cnt + 256)) == NULL) return TRUE; /* XXX retries immediately (delay?) */ hayes->rd_buf = p; status = g_io_channel_read_chars(source, &hayes->rd_buf[hayes->rd_buf_cnt], 256, &cnt, &error); #ifdef DEBUG fprintf(stderr, "%s", "DEBUG: MODEM: "); fwrite(&hayes->rd_buf[hayes->rd_buf_cnt], sizeof(*p), cnt, stderr); #endif hayes->rd_buf_cnt += cnt; switch(status) { case G_IO_STATUS_NORMAL: break; case G_IO_STATUS_ERROR: modem->helper->error(modem->helper->modem, error->message, 1); case G_IO_STATUS_EOF: default: /* should not happen... */ if(hayes->retry > 0) _hayes_reset(modem); hayes->rd_source = 0; return FALSE; } _hayes_parse(modem); return TRUE; } /* on_watch_can_write */ static gboolean _on_watch_can_write(GIOChannel * source, GIOCondition condition, gpointer data) { ModemPlugin * modem = data; Hayes * hayes = modem->priv; gsize cnt = 0; GError * error = NULL; GIOStatus status; char * p; #ifdef DEBUG fprintf(stderr, "DEBUG: %s() cnt=%lu\n", __func__, (unsigned long)hayes->wr_buf_cnt); #endif if(condition != G_IO_OUT || source != hayes->channel) return FALSE; /* should not happen */ status = g_io_channel_write_chars(source, hayes->wr_buf, hayes->wr_buf_cnt, &cnt, &error); #ifdef DEBUG fprintf(stderr, "%s", "DEBUG: PHONE: "); fwrite(hayes->wr_buf, sizeof(*p), cnt, stderr); #endif if(cnt != 0) /* some data may have been written anyway */ { hayes->wr_buf_cnt -= cnt; memmove(hayes->wr_buf, &hayes->wr_buf[cnt], hayes->wr_buf_cnt); if((p = realloc(hayes->wr_buf, hayes->wr_buf_cnt)) != NULL) hayes->wr_buf = p; /* we can ignore errors... */ else if(hayes->wr_buf_cnt == 0) hayes->wr_buf = NULL; /* ...except when it's not one */ } switch(status) { case G_IO_STATUS_NORMAL: break; case G_IO_STATUS_ERROR: modem->helper->error(modem->helper->modem, error->message, 1); case G_IO_STATUS_EOF: default: /* should not happen */ hayes->wr_source = 0; if(hayes->retry > 0) _hayes_reset(modem); return FALSE; } if(hayes->wr_buf_cnt > 0) /* there is more data to write */ return TRUE; hayes->wr_source = 0; return FALSE; } /* on_request_battery_level */ static HayesCommandStatus _on_request_battery_level(HayesCommand * command, HayesCommandStatus status, void * priv) { ModemPlugin * modem = priv; Hayes * hayes = modem->priv; ModemEvent * event = &hayes->events[MODEM_EVENT_TYPE_BATTERY_LEVEL]; if((status = _on_request_generic(command, status, priv)) != HCS_SUCCESS) return status; modem->helper->event(modem->helper->modem, event); return status; } /* on_request_call_status */ static HayesCommandStatus _on_request_call_status(HayesCommand * command, HayesCommandStatus status, void * priv) { ModemPlugin * modem = priv; Hayes * hayes = modem->priv; ModemEvent * event = &hayes->events[MODEM_EVENT_TYPE_CALL_STATUS]; if((status = _on_request_generic(command, status, priv)) != HCS_SUCCESS) return status; modem->helper->event(modem->helper->modem, event); return status; } /* on_request_contacts */ static HayesCommandStatus _on_request_contacts(HayesCommand * command, HayesCommandStatus status, void * priv) { /* FIXME implement */ return _on_request_generic(command, status, priv); } /* on_request_generic */ static HayesCommandStatus _on_request_generic(HayesCommand * command, HayesCommandStatus status, void * priv) { char const * answer; if((answer = _hayes_command_get_answer(command)) == NULL) return HCS_ERROR; while(answer != NULL) /* FIXME also handle BUSY/NO CARRIER/CONNECT/etc */ if(strncmp(answer, "OK\n", 3) == 0) return HCS_SUCCESS; else if(strncmp(answer, "ERROR\n", 6) == 0) return HCS_ERROR; else if((answer = strchr(answer, '\n')) != NULL) answer++; return HCS_ACTIVE; } /* on_request_messages */ static HayesCommandStatus _on_request_messages(HayesCommand * command, HayesCommandStatus status, void * priv) { /* FIXME implement */ return _on_request_generic(command, status, priv); } /* on_request_model */ static HayesCommandStatus _on_request_model(HayesCommand * command, HayesCommandStatus status, void * priv) { ModemPlugin * modem = priv; Hayes * hayes = modem->priv; ModemEvent * event = &hayes->events[MODEM_EVENT_TYPE_MODEL]; if((status = _on_request_generic(command, status, priv)) != HCS_SUCCESS) return status; modem->helper->event(modem->helper->modem, event); return status; } /* on_request_registration */ static HayesCommandStatus _on_request_registration(HayesCommand * command, HayesCommandStatus status, void * priv) { ModemPlugin * modem = priv; Hayes * hayes = modem->priv; ModemEvent * event = &hayes->events[MODEM_EVENT_TYPE_REGISTRATION]; if((status = _on_request_generic(command, status, priv)) != HCS_SUCCESS) return status; #if 0 /* XXX really detect it */ event->registration.media = hayes->registration_media; #endif modem->helper->event(modem->helper->modem, event); return status; } /* on_trigger_cbc */ static void _on_trigger_cbc(ModemPlugin * modem, char const * answer) { Hayes * hayes = modem->priv; ModemEvent * event = &hayes->events[MODEM_EVENT_TYPE_BATTERY_LEVEL]; int res; unsigned int u; unsigned int v; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, answer); #endif if((res = sscanf(answer, "%u,%u", &u, &v)) != 2) return; event->battery_level.status = MODEM_BATTERY_STATUS_UNKNOWN; if(u == 0) u = MODEM_BATTERY_STATUS_CONNECTED; else if(u == 1) u = MODEM_BATTERY_STATUS_CHARGING; else if(u == 2) u = MODEM_BATTERY_STATUS_NONE; else if(u == 3) u = MODEM_BATTERY_STATUS_ERROR; else u = MODEM_BATTERY_STATUS_UNKNOWN; event->battery_level.status = u; event->battery_level.level = v; if(hayes->quirks & HAYES_QUIRK_BATTERY_69) event->battery_level.level /= 69.0; else event->battery_level.level /= 100.0; } /* on_trigger_cgmi */ static void _on_trigger_cgmi(ModemPlugin * modem, char const * answer) { Hayes * hayes = modem->priv; ModemEvent * event = &hayes->events[MODEM_EVENT_TYPE_MODEL]; char * p; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, answer); #endif if((p = strdup(answer)) == NULL) return; /* XXX report error? */ free(hayes->model_vendor); hayes->model_vendor = p; event->model.vendor = p; } /* on_trigger_cgmm */ static void _on_trigger_cgmm(ModemPlugin * modem, char const * answer) { Hayes * hayes = modem->priv; ModemEvent * event = &hayes->events[MODEM_EVENT_TYPE_MODEL]; char * p; size_t i; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, answer); #endif if((p = strdup(answer)) == NULL) return; /* XXX report error? */ free(hayes->model_name); hayes->model_name = p; event->model.name = p; /* determine known quirks */ for(i = 0; _hayes_quirks[i].model != NULL; i++) if(strcmp(_hayes_quirks[i].model, p) == 0) { hayes->quirks = _hayes_quirks[i].quirks; #ifdef DEBUG fprintf(stderr, "DEBUG: %s() quirks=%u\n", __func__, hayes->quirks); #endif break; } } /* on_trigger_cgmr */ static void _on_trigger_cgmr(ModemPlugin * modem, char const * answer) /* FIXME the output may be multi-line */ { Hayes * hayes = modem->priv; ModemEvent * event = &hayes->events[MODEM_EVENT_TYPE_MODEL]; char * p; if((p = strdup(answer)) == NULL) return; /* XXX report error? */ free(hayes->model_version); hayes->model_version = p; event->model.version = p; } /* on_trigger_cops */ static void _on_trigger_cops(ModemPlugin * modem, char const * answer) { Hayes * hayes = modem->priv; ModemEvent * event = &hayes->events[MODEM_EVENT_TYPE_CALL_STATUS]; unsigned int u; unsigned int v; char buf[32]; unsigned int w; if(sscanf(answer, "%u,%u,\"%31[^\"]\",%u", &u, &v, buf, &w) < 3) return; /* FIXME is also valid with 1 result */ buf[sizeof(buf) - 1] = '\0'; free(hayes->registration_operator); hayes->registration_operator = strdup(buf); event->registration._operator = hayes->registration_operator; switch(u) { case 0: u = MODEM_REGISTRATION_MODE_AUTOMATIC; break; case 1: u = MODEM_REGISTRATION_MODE_MANUAL; break; case 2: u = MODEM_REGISTRATION_MODE_DISABLED; break; case 3: /* only for setting the format */ default: u = event->registration.mode; break; } event->registration.mode = u; } /* on_trigger_cpas */ static void _on_trigger_cpas(ModemPlugin * modem, char const * answer) { Hayes * hayes = modem->priv; ModemEvent * event = &hayes->events[MODEM_EVENT_TYPE_CALL_STATUS]; unsigned int u; if(sscanf(answer, "%u", &u) != 1) return; switch(u) { case 0: event->call_status.status = MODEM_CALL_STATUS_NONE; event->call_status.direction = MODEM_CALL_DIRECTION_NONE; break; case 3: event->call_status.status = MODEM_CALL_STATUS_RINGING; event->call_status.direction = MODEM_CALL_DIRECTION_INCOMING; /* report event */ modem->helper->event(modem->helper->modem, event); break; case 4: event->call_status.status = MODEM_CALL_STATUS_ACTIVE; event->call_status.direction = MODEM_CALL_DIRECTION_NONE; break; case 2: /* XXX unknown */ default: break; } } /* on_trigger_cpin */ static void _on_trigger_cpin(ModemPlugin * modem, char const * answer) { Hayes * hayes = modem->priv; ModemEvent * event = &hayes->events[ MODEM_EVENT_TYPE_AUTHENTICATION_STATUS]; char * p; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, answer); #endif if(strcmp(answer, "READY") == 0) event->authentication_status.status = MODEM_AUTHENTICATION_STATUS_OK; else if(strcmp(answer, "SIM PIN") == 0) { free(hayes->authentication_status_name); p = strdup(answer); hayes->authentication_status_name = p; event->authentication_status.name = p; event->authentication_status.method = MODEM_AUTHENTICATION_METHOD_PIN; event->authentication_status.status = MODEM_AUTHENTICATION_STATUS_REQUIRED; /* FIXME also provide remaining retries */ } } /* on_trigger_cring */ static void _on_trigger_cring(ModemPlugin * modem, char const * answer) { Hayes * hayes = modem->priv; ModemEvent * event = &hayes->events[MODEM_EVENT_TYPE_CALL_STATUS]; if(strcmp(answer, "VOICE") == 0) ; /* FIXME implement */ event->call_status.status = MODEM_CALL_STATUS_RINGING; event->call_status.direction = MODEM_CALL_DIRECTION_INCOMING; event->call_status.number = ""; modem->helper->event(modem->helper->modem, event); } /* on_trigger_csq */ static void _on_trigger_csq(ModemPlugin * modem, char const * answer) { Hayes * hayes = modem->priv; ModemEvent * event = &hayes->events[MODEM_EVENT_TYPE_REGISTRATION]; unsigned int u; unsigned int v; if(sscanf(answer, "%u,%u", &u, &v) != 2) return; if(u > 31) event->registration.signal = 0.0 / 0.0; else event->registration.signal = (u / 32) + 0.0; }