Desktop

/* $Id: gsm.c,v 1.79 2010/05/07 12:52:38 khorben Exp $ */
/* Copyright (c) 2010 Pierre Pronchery <khorben@defora.org> */
/* 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 <http://www.gnu.org/licenses/>. */
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <libintl.h>
#include <glib.h>
#include "phone.h"
#include "gsm.h"
#define _(string) gettext(string)
#define N_(string) (string)
/* GSM */
/* private */
/* types */
typedef enum _GSMMode
{
GSM_MODE_INIT = 0, GSM_MODE_COMMAND, GSM_MODE_PDU
} GSMMode;
typedef enum _GSMPriority
{
GSM_PRIORITY_LOW = 0, GSM_PRIORITY_NORMAL, GSM_PRIORITY_HIGH,
GSM_PRIORITY_HIGHEST
} GSMPriority;
typedef enum _GSMQuirk
{
GSM_QUIRK_NONE = 0,
GSM_QUIRK_CPIN_QUOTES
} GSMQuirk;
typedef void (*GSMCommandCallback)(GSM * gsm);
typedef struct _GSMCommand
{
GSMPriority priority;
char * command;
GSMError error;
GSMCommandCallback callback;
GSMMode mode;
} GSMCommand;
typedef struct _GSMTrigger
{
char const * trigger;
size_t trigger_cnt;
int (*callback)(GSM * gsm, char const * result);
} GSMTrigger;
struct _GSM
{
/* settings */
char * device;
unsigned int baudrate;
unsigned int retry;
unsigned int hwflow;
unsigned int quirks;
/* callback */
GSMCallback callback;
gpointer callback_data;
GSMEvent event;
GSMStatus status;
/* queue */
GSList * queue;
/* internal */
GSMMode mode;
guint source;
GIOChannel * channel;
char * rd_buf;
size_t rd_buf_cnt;
guint rd_source;
char * wr_buf;
size_t wr_buf_cnt;
guint wr_source;
};
/* variables */
/* ANSWERS */
static char const * _gsm_errors[] = { "ERROR", "NO CARRIER", NULL };
/* CME ERROR */
static struct
{
int code;
char const * error;
} _gsm_cme_errors[] =
{
{ 0, N_("Phone failure") },
{ 1, N_("No connection to phone") },
{ 3, N_("Operation not allowed") },
{ 4, N_("Operation not supported") },
{ 10, N_("SIM not inserted") },
{ 11, N_("SIM PIN required") },
{ 12, N_("SIM PUK required") },
{ 13, N_("SIM failure") },
{ 14, N_("SIM busy") },
{ 15, N_("SIM wrong") },
{ 16, N_("Incorrect password") },
{ 17, N_("SIM PIN2 required") },
{ 18, N_("SIM PUK2 required") },
{ 20, N_("Memory full") },
{ 21, N_("Invalid index") },
{ 22, N_("Not found") },
{ 23, N_("Memory failure") },
{ 24, N_("Text string too long") },
{ 25, N_("Invalid characters in text string") },
{ 26, N_("Dial string too long") },
{ 27, N_("Invalid characters in dial string") },
{ 30, N_("No network service") },
{ 31, N_("Network timeout") },
{ 32, N_("Network not allowed - emergency calls only")},
{ 0, NULL }
};
/* CMS ERROR */
static struct
{
int code;
char const * error;
} _gsm_cms_errors[] =
{
{ 300, N_("ME failure") },
{ 301, N_("SMS service of ME reserved") },
{ 302, N_("Operation not allowed") },
{ 303, N_("Operation not supported") },
{ 304, N_("Invalid PDU mode parameter") },
{ 310, N_("SIM not inserted") },
{ 311, N_("SIM PIN required") },
{ 0, NULL }
};
/* models */
static struct
{
char const * model;
unsigned int quirks;
} _gsm_models[] =
{
{ "\"Neo1973 GTA02 Embedded GSM Modem\"",
GSM_QUIRK_CPIN_QUOTES },
{ NULL, 0 }
};
/* prototypes */
static int _is_code(char const * code);
static int _is_figure(int c);
static int _is_number(char const * number);
/* commands */
static GSMCommand * _gsm_command_new(char const * command);
static void _gsm_command_delete(GSMCommand * command);
static GSMMode _gsm_command_get_mode(GSMCommand * gsmc);
static GSMPriority _gsm_command_get_priority(GSMCommand * gsmc);
static void _gsm_command_set_callback(GSMCommand * gsmc,
GSMCommandCallback callback);
static void _gsm_command_set_error(GSMCommand * gsmc, GSMError error);
static void _gsm_command_set_mode(GSMCommand * gsmc, GSMMode mode);
static void _gsm_command_set_priority(GSMCommand * gsmc, GSMPriority priority);
/* events */
static int _gsm_event(GSM * gsm, GSMEventType type, ...);
static int _gsm_event_send(GSM * gsm, GSMEventType type);
static int _gsm_event_set_status(GSM * gsm, GSMStatus status);
/* modem commands */
static int _gsm_modem_answer(GSM * gsm);
static int _gsm_modem_call(GSM * gsm, GSMCallType calltype,
char const * number);
static int _gsm_modem_call_contact(GSM * gsm, GSMCallType calltype,
unsigned int index);
static int _gsm_modem_call_last(GSM * gsm, GSMCallType calltype);
static int _gsm_modem_enter_sim_pin(GSM * gsm, char const * code);
static int _gsm_modem_get_contact_list(GSM * gsm);
static int _gsm_modem_get_contacts(GSM * gsm, unsigned int start,
unsigned int end);
static int _gsm_modem_get_message_list(GSM * gsm);
static int _gsm_modem_get_messages(GSM * gsm, unsigned int start,
unsigned int end);
static int _gsm_modem_get_model(GSM * gsm);
static int _gsm_modem_get_operator(GSM * gsm);
static int _gsm_modem_get_registration(GSM * gsm);
static int _gsm_modem_get_signal_level(GSM * gsm);
static int _gsm_modem_is_pin_needed(GSM * gsm);
static int _gsm_modem_is_registered(GSM * gsm);
static int _gsm_modem_hangup(GSM * gsm);
static int _gsm_modem_send_message(GSM * gsm, char const * number,
char const * text);
static int _gsm_modem_set_echo(GSM * gsm, gboolean echo);
static int _gsm_modem_set_extended_errors(GSM * gsm, gboolean extended);
static int _gsm_modem_set_extended_ring_reports(GSM * gsm, gboolean extended);
static int _gsm_modem_set_functional(GSM * gsm, gboolean functional);
static int _gsm_modem_set_message_format(GSM * gsm, GSMMessageFormat format);
static int _gsm_modem_set_operator_format(GSM * gsm, GSMOperatorFormat format);
static int _gsm_modem_set_operator_mode(GSM * gsm, GSMOperatorMode mode);
static int _gsm_modem_set_registration_report(GSM * gsm,
GSMRegistrationReport report);
static int _gsm_modem_set_verbose(GSM * gsm, gboolean verbose);
/* parsing */
static int _gsm_parse(GSM * gsm);
static int _gsm_parse_line(GSM * gsm, char const * line, gboolean * answered);
/* queue management */
static GSMCommand * _gsm_queue(GSM * gsm, char const * command);
static int _gsm_queue_with_error(GSM * gsm, char const * command,
GSMError error);
static int _gsm_queue_full(GSM * gsm, GSMPriority priority,
char const * command, GSMError error,
GSMCommandCallback callback);
static int _gsm_queue_full_mode(GSM * gsm, GSMPriority priority,
char const * command, GSMError error,
GSMCommandCallback callback, GSMMode mode);
static int _gsm_queue_command(GSM * gsm, GSMCommand * command);
static void _gsm_queue_flush(GSM * gsm);
static void _gsm_queue_pop(GSM * gsm);
static int _gsm_queue_push(GSM * gsm);
/* triggers */
static int _gsm_trigger_cgmm(GSM * gsm, char const * result);
static int _gsm_trigger_cme_error(GSM * gsm, char const * result);
static int _gsm_trigger_cms_error(GSM * gsm, char const * result);
static int _gsm_trigger_cmgl(GSM * gsm, char const * result);
static int _gsm_trigger_cmgs(GSM * gsm, char const * result);
static int _gsm_trigger_cops(GSM * gsm, char const * result);
static int _gsm_trigger_cpbr(GSM * gsm, char const * result);
static int _gsm_trigger_cpin(GSM * gsm, char const * result);
static int _gsm_trigger_creg(GSM * gsm, char const * result);
static int _gsm_trigger_cring(GSM * gsm, char const * result);
static int _gsm_trigger_csq(GSM * gsm, char const * result);
/* triggers */
static GSMTrigger _gsm_triggers[] =
{
#define GSM_TRIGGER(trigger, callback) \
{ trigger, sizeof(trigger) - 1, _gsm_trigger_ ## callback }
GSM_TRIGGER("+CGMM: ", cgmm),
GSM_TRIGGER("+CME ERROR: ", cme_error),
GSM_TRIGGER("+CMS ERROR: ", cms_error),
GSM_TRIGGER("+CMGL: ", cmgl),
GSM_TRIGGER("+CMGS: ", cmgs),
GSM_TRIGGER("+COPS: ", cops),
GSM_TRIGGER("+CPBR: ", cpbr),
GSM_TRIGGER("+CPIN: ", cpin),
GSM_TRIGGER("+CREG: ", creg),
GSM_TRIGGER("+CRING: ", cring),
GSM_TRIGGER("+CSQ: ", csq),
{ NULL, 0, NULL }
};
/* callbacks */
static gboolean _on_reset(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);
/* public */
/* functions */
/* gsm_new */
static unsigned int _new_baudrate(unsigned int baudrate);
GSM * gsm_new(char const * device, unsigned int baudrate, unsigned int hwflow)
{
GSM * gsm;
if(device == NULL)
return NULL;
if((gsm = malloc(sizeof(*gsm))) == NULL)
return NULL;
/* settings */
gsm->device = strdup(device);
gsm->baudrate = _new_baudrate(baudrate);
gsm->retry = 1000;
gsm->hwflow = hwflow;
gsm->quirks = 0;
/* callback */
gsm->callback = NULL;
gsm->callback_data = NULL;
memset(&gsm->event, 0, sizeof(gsm->event));
gsm->status = GSM_STATUS_UNKNOWN;
/* queue */
gsm->queue = NULL;
/* internal */
gsm->mode = GSM_MODE_INIT;
gsm->source = 0;
gsm->channel = NULL;
gsm->rd_buf = NULL;
gsm->rd_buf_cnt = 0;
gsm->rd_source = 0;
gsm->wr_buf = NULL;
gsm->wr_buf_cnt = 0;
gsm->wr_source = 0;
/* error checking */
if(gsm->device == NULL || gsm->baudrate == 0)
{
gsm_delete(gsm);
return NULL;
}
gsm_reset(gsm, 0);
return gsm;
}
static unsigned int _new_baudrate(unsigned int baudrate)
{
char buf[256];
switch(baudrate)
{
case 1200:
return B1200;
case 2400:
return B2400;
case 4800:
return B4800;
case 9600:
return B9600;
case 19200:
return B19200;
case 38400:
return B38400;
#ifdef B76800
case 76800:
return B76800;
#endif
#ifdef B14400
case 14400:
return B14400;
#endif
#ifdef B28800
case 28800:
return B28800;
#endif
case 57600:
return B57600;
case 115200:
return B115200;
case 460800:
return B460800;
case 921600:
return B921600;
default:
snprintf(buf, sizeof(buf), "%u%s", baudrate,
_(": Unknown baudrate"));
return phone_error(NULL, buf, baudrate);
}
}
/* gsm_delete */
void gsm_delete(GSM * gsm)
{
if(gsm->rd_source != 0)
g_source_remove(gsm->rd_source);
gsm->rd_source = 0;
_gsm_queue_flush(gsm);
free(gsm->device);
free(gsm);
}
/* accessors */
/* gsm_get_retry */
unsigned int gsm_get_retry(GSM * gsm)
{
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() => %u\n", __func__, gsm->retry);
#endif
return gsm->retry;
}
/* gsm_set_callback */
void gsm_set_callback(GSM * gsm, GSMCallback callback, gpointer data)
{
gsm->callback = callback;
gsm->callback_data = data;
}
/* gsm_set_extended_errors */
int gsm_set_extended_errors(GSM * gsm, int extended)
{
return _gsm_modem_set_extended_errors(gsm, (extended != 0) ? TRUE
: FALSE);
}
/* gsm_set_extended_ring_reports */
int gsm_set_extended_ring_reports(GSM * gsm, gboolean extended)
{
return _gsm_modem_set_extended_ring_reports(gsm, extended);
}
/* gsm_set_functional */
int gsm_set_functional(GSM * gsm, int functional)
{
return _gsm_modem_set_functional(gsm, (functional != 0) ? TRUE : FALSE);
}
/* gsm_set_operator_format */
int gsm_set_operator_format(GSM * gsm, GSMOperatorFormat format)
{
return _gsm_modem_set_operator_format(gsm, format);
}
/* gsm_set_operator_mode */
int gsm_set_operator_mode(GSM * gsm, GSMOperatorMode mode)
{
return _gsm_modem_set_operator_mode(gsm, mode);
}
/* gsm_set_registration_report */
int gsm_set_registration_report(GSM * gsm, GSMRegistrationReport report)
{
int ret;
ret = _gsm_modem_set_registration_report(gsm, report);
ret |= gsm_fetch_registration(gsm);
return ret;
}
/* gsm_set_retry */
int gsm_set_retry(GSM * gsm, unsigned int retry)
{
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(%u)\n", __func__, retry);
#endif
gsm->retry = retry;
return 0;
}
/* useful */
/* gsm_answer */
int gsm_answer(GSM * gsm)
{
return _gsm_modem_answer(gsm);
}
/* gsm_call */
int gsm_call(GSM * gsm, GSMCallType calltype, char const * number)
{
if(number == NULL)
return _gsm_modem_call_last(gsm, calltype);
/* FIXME if the number is not valid try from the address book */
return _gsm_modem_call(gsm, calltype, number);
}
/* gsm_call_contact */
int gsm_call_contact(GSM * gsm, GSMCallType calltype, unsigned int index)
{
return _gsm_modem_call_contact(gsm, calltype, index);
}
/* gsm_enter_sim_pin */
int gsm_enter_sim_pin(GSM * gsm, char const * code)
{
if(code == NULL)
return _gsm_modem_is_pin_needed(gsm);
return _gsm_modem_enter_sim_pin(gsm, code);
}
/* gsm_fetch_contact_list */
int gsm_fetch_contact_list(GSM * gsm)
{
return _gsm_modem_get_contact_list(gsm);
}
/* gsm_fetch_contacts */
int gsm_fetch_contacts(GSM * gsm, unsigned int start, unsigned int end)
{
return _gsm_modem_get_contacts(gsm, start, end);
}
/* gsm_fetch_message_list */
int gsm_fetch_message_list(GSM * gsm)
{
return _gsm_modem_get_message_list(gsm);
}
/* gsm_fetch_messages */
int gsm_fetch_messages(GSM * gsm, unsigned int start, unsigned int end)
{
return _gsm_modem_get_messages(gsm, start, end);
}
/* gsm_fetch_operator */
int gsm_fetch_operator(GSM * gsm)
{
return _gsm_modem_get_operator(gsm);
}
/* gsm_fetch_registration */
int gsm_fetch_registration(GSM * gsm)
{
return _gsm_modem_get_registration(gsm);
}
/* gsm_fetch_signal_level */
int gsm_fetch_signal_level(GSM * gsm)
{
return _gsm_modem_get_signal_level(gsm);
}
/* gsm_hangup */
int gsm_hangup(GSM * gsm)
{
return _gsm_modem_hangup(gsm);
}
/* gsm_is_pin_needed */
int gsm_is_pin_needed(GSM * gsm)
{
return _gsm_modem_is_pin_needed(gsm);
}
/* gsm_is_registered */
int gsm_is_registered(GSM * gsm)
{
return _gsm_modem_is_registered(gsm);
}
/* gsm_reset */
int gsm_reset(GSM * gsm, unsigned int delay)
{
_gsm_queue_flush(gsm);
if(delay > 0)
gsm->source = g_timeout_add(delay, _on_reset, gsm);
else
gsm->source = g_idle_add(_on_reset, gsm);
return 0;
}
/* gsm_send_message */
int gsm_send_message(GSM * gsm, char const * number, char const * text)
{
return _gsm_modem_send_message(gsm, number, text);
}
/* private */
/* functions */
/* is_code */
static int _is_code(char const * code)
{
int c;
while((c = *(code++)) != '\0')
if(!isdigit(c))
return 0;
return 1;
}
/* 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;
}
/* commands */
/* gsm_command_new */
static GSMCommand * _gsm_command_new(char const * command)
{
GSMCommand * gsmc;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, command);
#endif
if((gsmc = malloc(sizeof(*gsmc))) == NULL)
return NULL; /* XXX report error */
gsmc->priority = GSM_PRIORITY_NORMAL;
gsmc->command = strdup(command);
gsmc->error = GSM_ERROR_UNKNOWN;
gsmc->callback = NULL;
gsmc->mode = GSM_MODE_COMMAND;
/* check errors */
if(gsmc->command == NULL)
{
_gsm_command_delete(gsmc);
return NULL;
}
return gsmc;
}
/* gsm_command_delete */
static void _gsm_command_delete(GSMCommand * gsmc)
{
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
free(gsmc->command);
free(gsmc);
}
/* gsm_command_get_mode */
static GSMMode _gsm_command_get_mode(GSMCommand * gsmc)
{
return gsmc->mode;
}
/* gsm_command_get_priority */
static GSMPriority _gsm_command_get_priority(GSMCommand * gsmc)
{
return gsmc->priority;
}
/* gsm_command_set_callback */
static void _gsm_command_set_callback(GSMCommand * gsmc,
GSMCommandCallback callback)
{
gsmc->callback = callback;
}
/* gsm_command_set_error */
static void _gsm_command_set_error(GSMCommand * gsmc, GSMError error)
{
gsmc->error = error;
}
/* gsm_command_set_mode */
static void _gsm_command_set_mode(GSMCommand * gsmc, GSMMode mode)
{
gsmc->mode = mode;
}
/* gsm_command_set_priority */
static void _gsm_command_set_priority(GSMCommand * gsmc, GSMPriority priority)
{
gsmc->priority = priority;
}
/* events */
/* gsm_event */
static int _gsm_event(GSM * gsm, GSMEventType type, ...)
{
va_list ap;
GSMEvent * event = &gsm->event;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(%d)\n", __func__, type);
#endif
va_start(ap, type);
switch(type)
{
case GSM_EVENT_TYPE_ERROR:
event->error.error = va_arg(ap, GSMError);
event->error.message = va_arg(ap, char *);
break;
case GSM_EVENT_TYPE_CONTACT:
event->contact.index = va_arg(ap, unsigned int);
event->contact.name = va_arg(ap, char *);
event->contact.number = va_arg(ap, char *);
break;
case GSM_EVENT_TYPE_CONTACT_LIST:
event->contact_list.start = va_arg(ap, unsigned int);
event->contact_list.end = va_arg(ap, unsigned int);
break;
case GSM_EVENT_TYPE_FUNCTIONAL:
event->functional.functional = va_arg(ap, unsigned int);
break;
case GSM_EVENT_TYPE_INCOMING_CALL:
event->incoming_call.calltype = va_arg(ap,
unsigned int);
break;
case GSM_EVENT_TYPE_MESSAGE_LIST:
event->message_list.start = va_arg(ap, unsigned int);
event->message_list.end = va_arg(ap, unsigned int);
break;
case GSM_EVENT_TYPE_MESSAGE_SENT:
event->message_sent.mr = va_arg(ap, unsigned int);
break;
case GSM_EVENT_TYPE_OPERATOR:
event->operator.mode = va_arg(ap, GSMOperatorMode);
event->operator.format = va_arg(ap, GSMOperatorFormat);
event->operator.operator = va_arg(ap, char *);
event->operator.lai = va_arg(ap, unsigned int);
break;
case GSM_EVENT_TYPE_REGISTRATION:
event->registration.n = va_arg(ap, unsigned int);
event->registration.stat = va_arg(ap, unsigned int);
event->registration.area = va_arg(ap, unsigned int);
event->registration.cell = va_arg(ap, unsigned int);
break;
case GSM_EVENT_TYPE_SIGNAL_LEVEL:
event->signal_level.level = va_arg(ap, gdouble);
break;
case GSM_EVENT_TYPE_SIM_PIN_VALID:
break;
case GSM_EVENT_TYPE_STATUS:
event->status.status = va_arg(ap, GSMStatus);
break;
}
va_end(ap);
return _gsm_event_send(gsm, type);
}
/* gsm_event_send */
static int _gsm_event_send(GSM * gsm, GSMEventType type)
{
int ret;
char buf[80];
gsm->event.type = type;
ret = gsm->callback(&gsm->event, gsm->callback_data);
if(ret == 0)
return 0;
snprintf(buf, sizeof(buf), "%u: %s", type, _("Event not handled"));
return phone_error(NULL, buf, ret);
}
/* gsm_event_set_status */
static int _gsm_event_set_status(GSM * gsm, GSMStatus status)
{
if(gsm->status == status)
return 0;
gsm->status = status;
return _gsm_event(gsm, GSM_EVENT_TYPE_STATUS, status);
}
/* modem commands */
/* gsm_modem_answer */
static int _gsm_modem_answer(GSM * gsm)
{
char const cmd[] = "ATA";
return _gsm_queue_with_error(gsm, cmd, GSM_ERROR_ANSWER_FAILED);
}
/* gsm_modem_call */
static int _gsm_modem_call(GSM * gsm, GSMCallType calltype, char const * number)
{
int ret;
char const cmd[] = "ATD";
char const * suffix = "";
size_t len;
char * buf;
switch(calltype)
{
case GSM_CALL_TYPE_DATA:
break;
case GSM_CALL_TYPE_VOICE:
suffix = ";";
break;
default:
return 1;
}
if(!_is_number(number))
return 1;
len = sizeof(cmd) + strlen(number) + strlen(suffix);
if((buf = malloc(len)) == NULL)
return 1;
snprintf(buf, len, "%s%s%s", cmd, number, suffix);
ret = _gsm_queue_full(gsm, GSM_PRIORITY_HIGH, buf,
GSM_ERROR_CALL_FAILED, NULL);
free(buf);
return ret;
}
/* gsm_modem_call_contact */
static int _gsm_modem_call_contact(GSM * gsm, GSMCallType calltype,
unsigned int index)
{
char const cmd[] = "ATD>";
char const * suffix = "";
char buf[32];
switch(calltype)
{
case GSM_CALL_TYPE_DATA:
break;
case GSM_CALL_TYPE_VOICE:
suffix = ";";
break;
default:
return 1;
}
snprintf(buf, sizeof(buf), "%s%u%s", cmd, index, suffix);
return _gsm_queue_full(gsm, GSM_PRIORITY_HIGH, buf,
GSM_ERROR_CALL_FAILED, NULL);
}
/* gsm_modem_call_last */
static int _gsm_modem_call_last(GSM * gsm, GSMCallType calltype)
{
char const cmddata[] = "ATDL";
char const cmdvoice[] = "ATDL;";
char const * cmd;
switch(calltype)
{
case GSM_CALL_TYPE_DATA:
cmd = cmddata;
break;
case GSM_CALL_TYPE_VOICE:
cmd = cmdvoice;
break;
default:
return 1;
}
return _gsm_queue_full(gsm, GSM_PRIORITY_HIGH, cmd,
GSM_ERROR_CALL_FAILED, NULL);
}
/* gsm_modem_enter_sim_pin */
static void _modem_enter_sim_pin_callback(GSM * gsm);
static int _gsm_modem_enter_sim_pin(GSM * gsm, char const * code)
{
int ret;
char const cmd[] = "AT+CPIN=";
size_t len;
char * buf;
if(!_is_code(code))
{
_gsm_event(gsm, GSM_EVENT_TYPE_ERROR, GSM_ERROR_SIM_PIN_WRONG);
return 1;
}
len = sizeof(cmd) + 1 + strlen(code) + 1;
if((buf = malloc(len)) == NULL)
return 1;
if(gsm->quirks & GSM_QUIRK_CPIN_QUOTES)
snprintf(buf, len, "%s\"%s\"", cmd, code);
else
snprintf(buf, len, "%s%s", cmd, code);
ret = _gsm_queue_full(gsm, GSM_PRIORITY_NORMAL, buf,
GSM_ERROR_SIM_PIN_WRONG, _modem_enter_sim_pin_callback);
free(buf);
return ret;
}
static void _modem_enter_sim_pin_callback(GSM * gsm)
{
_gsm_event_send(gsm, GSM_EVENT_TYPE_SIM_PIN_VALID);
/* do we need another PIN code? */
_gsm_modem_is_pin_needed(gsm);
}
/* gsm_modem_get_contact_list */
static int _gsm_modem_get_contact_list(GSM * gsm)
{
char const cmd[] = "AT+CPBR=?";
return _gsm_queue_with_error(gsm, cmd, GSM_ERROR_CONTACT_LIST_FAILED);
}
/* gsm_modem_get_contacts */
static int _gsm_modem_get_contacts(GSM * gsm, unsigned int start,
unsigned int end)
{
char cmd[32];
snprintf(cmd, sizeof(cmd), "%s%u,%u", "AT+CPBR=", start, end);
return _gsm_queue_full(gsm, GSM_PRIORITY_LOW, cmd,
GSM_ERROR_CONTACT_FETCH_FAILED, NULL);
}
/* gsm_modem_get_message_list */
static int _gsm_modem_get_message_list(GSM * gsm)
{
char const cmd[] = "AT+CMGL=?";
return _gsm_queue_with_error(gsm, cmd, GSM_ERROR_MESSAGE_LIST_FAILED);
}
/* gsm_modem_get_messages */
static int _gsm_modem_get_messages(GSM * gsm, unsigned int start,
unsigned int end)
{
char cmd[32];
snprintf(cmd, sizeof(cmd), "%s%u,%u", "AT+CMGR=", start, end);
return _gsm_queue_full(gsm, GSM_PRIORITY_LOW, cmd,
GSM_ERROR_MESSAGE_FETCH_FAILED, NULL);
}
/* gsm_modem_get_model */
static int _gsm_modem_get_model(GSM * gsm)
{
char const cmd[] = "AT+CGMM";
return (_gsm_queue(gsm, cmd) != NULL) ? 0 : 1;
}
/* gsm_modem_get_operator */
static int _gsm_modem_get_operator(GSM * gsm)
{
char const cmd[] = "AT+COPS?";
return (_gsm_queue(gsm, cmd) != NULL) ? 0 : 1;
}
/* gsm_modem_get_registration */
static int _gsm_modem_get_registration(GSM * gsm)
{
char const cmd[] = "AT+CREG?";
return (_gsm_queue(gsm, cmd) != NULL) ? 0 : 1;
}
/* gsm_modem_get_signal_level */
static int _gsm_modem_get_signal_level(GSM * gsm)
{
char const cmd[] = "AT+CSQ";
return _gsm_queue_with_error(gsm, cmd, GSM_ERROR_SIGNAL_LEVEL_FAILED);
}
/* gsm_modem_hangup */
static int _gsm_modem_hangup(GSM * gsm)
{
char const cmd[] = "ATH";
/* XXX probably should query the call status after that */
return _gsm_queue_full(gsm, GSM_PRIORITY_HIGH, cmd,
GSM_ERROR_HANGUP_FAILED, NULL);
}
/* gsm_modem_is_pin_needed */
static int _gsm_modem_is_pin_needed(GSM * gsm)
{
char const cmd[] = "AT+CPIN?";
return (_gsm_queue(gsm, cmd) != NULL) ? 0 : 1;
}
/* gsm_modem_is_registered */
static int _gsm_modem_is_registered(GSM * gsm)
{
char const cmd[] = "AT+CREG?";
return (_gsm_queue(gsm, cmd) != NULL) ? 0 : 1;
}
/* gsm_modem_reset */
static int _gsm_modem_reset(GSM * gsm)
{
char const cmd[] = "ATZ";
return _gsm_queue_full(gsm, GSM_PRIORITY_HIGH, cmd,
GSM_ERROR_RESET_FAILED, NULL);
}
/* gsm_modem_send_message */
static char * _number_to_address(char const * number);
static char * _text_to_sept(char const * text);
static int _gsm_modem_send_message(GSM * gsm, char const * number,
char const * text)
{
int ret = 1;
char const cmd1[] = "AT+CMGS=";
char * buf1;
size_t len1;
char const cmd2[] = "1100";
char * buf2;
size_t len2;
char * addr;
char * sept;
char const pid[] = "00";
char const dcs[] = "00";
char const vp[] = "AA";
GSMCommand * gsmc;
if(!_is_number(number) || text == NULL
|| _gsm_modem_set_message_format(gsm,
GSM_MESSAGE_FORMAT_PDU) != 0)
return _gsm_event(gsm, GSM_EVENT_TYPE_ERROR,
GSM_ERROR_MESSAGE_SEND_FAILED, NULL);
addr = _number_to_address(number);
sept = _text_to_sept(text);
len2 = sizeof(cmd2) + 2 + strlen(addr ? addr : "") + sizeof(pid)
+ sizeof(dcs) + 2 + strlen(sept ? sept : "") + 1;
buf2 = malloc(len2);
len1 = sizeof(cmd1) + 2;
buf1 = malloc(len1);
if(addr == NULL || sept == NULL || buf1 == NULL || buf2 == NULL)
{
free(addr);
free(sept);
free(buf1);
free(buf2);
return _gsm_event(gsm, GSM_EVENT_TYPE_ERROR,
GSM_ERROR_MESSAGE_SEND_FAILED, NULL);
}
snprintf(buf2, len2, "%s%02lX%s%s%s%s%02lX%s\x1a", cmd2,
number[0] == '+' ? strlen(number) - 1 : strlen(number),
addr, pid, dcs, vp, strlen(text), sept);
snprintf(buf1, len1, "%s%lu", cmd1, (len2 - 1) / 2);
free(addr);
free(sept);
if((gsmc = _gsm_command_new(buf1)) != NULL
&& (ret = _gsm_queue_command(gsm, gsmc)) == 0)
{
_gsm_command_set_error(gsmc, GSM_ERROR_MESSAGE_SEND_FAILED);
_gsm_command_set_mode(gsmc, GSM_MODE_PDU);
_gsm_command_set_priority(gsmc, GSM_PRIORITY_HIGHEST);
if((gsmc = _gsm_command_new(buf2)) != NULL
/* XXX if this fails we're stuck in PDU mode */
&& (ret = _gsm_queue_command(gsm, gsmc)) == 0)
{
_gsm_command_set_error(gsmc,
GSM_ERROR_MESSAGE_SEND_FAILED);
_gsm_command_set_priority(gsmc, GSM_PRIORITY_HIGHEST);
}
}
if(ret != 0)
_gsm_command_delete(gsmc);
free(buf1);
free(buf2);
return ret;
}
static char * _number_to_address(char const * number)
{
char * buf;
size_t len;
size_t i;
len = 2 + strlen(number) + 2;
if((buf = malloc(len)) == NULL)
return NULL;
snprintf(buf, len, "%02X", (number[0] == '+') ? 145 : 129);
if(number[0] == '+')
number++;
for(i = 2; i < len; i+=2)
{
if(number[i - 2] == '\0')
break;
buf[i] = number[i - 1];
buf[i + 1] = number[i - 2];
if(number[i - 1] == '\0')
{
buf[i] = 'F';
i += 2;
break;
}
}
buf[i] = '\0';
return buf;
}
/* this function is heavily inspired from gsmd, (c) 2007 OpenMoko, Inc. */
static char * _text_to_sept(char const * text)
{
char const tab[] = "0123456789ABCDEF";
unsigned char const * t = (unsigned char const *)text;
char * buf;
size_t len;
char * p;
size_t i;
unsigned char ch1;
unsigned char ch2;
int shift = 0;
len = strlen(text);
if((buf = malloc(len + 1)) == NULL)
return NULL;
p = buf;
for(i = 0; i < len; i++)
{
ch1 = t[i] & 0x7f;
ch1 = (ch1 >> shift);
ch2 = t[i + 1] & 0x7f;
ch2 = ch2 << (7 - shift);
ch1 = ch1 | ch2;
*(p++) = tab[(ch1 & 0xf0) >> 4];
*(p++) = tab[ch1 & 0x0f];
if(++shift == 7)
{
shift = 0;
i++;
}
}
*p = '\0';
return buf;
}
/* gsm_modem_set_echo */
static int _gsm_modem_set_echo(GSM * gsm, gboolean echo)
{
char cmd[] = "ATEX";
cmd[3] = echo ? '1' : '0';
return (_gsm_queue(gsm, cmd) != NULL) ? 0 : 1;
}
/* gsm_modem_set_extended_errors */
static int _gsm_modem_set_extended_errors(GSM * gsm, gboolean extended)
{
char cmd[] = "AT+CMEE=X";
cmd[8] = extended ? '1' : '0';
return (_gsm_queue(gsm, cmd) != NULL) ? 0 : 1;
}
/* gsm_modem_set_extended_ring_reports */
static int _gsm_modem_set_extended_ring_reports(GSM * gsm, gboolean extended)
{
char cmd[] = "AT+CRC=X";
cmd[7] = extended ? '1' : '0';
return (_gsm_queue(gsm, cmd) != NULL) ? 0 : 1;
}
/* gsm_modem_set_functional */
static int _gsm_modem_set_functional(GSM * gsm, gboolean functional)
{
char cmd[] = "AT+CFUN=X";
cmd[8] = functional ? '1' : '0';
return _gsm_queue_with_error(gsm, cmd, GSM_ERROR_FUNCTIONAL_FAILED);
}
/* gsm_modem_set_message_format */
static int _gsm_modem_set_message_format(GSM * gsm, GSMMessageFormat format)
{
char cmd[] = "AT+CMGF=X";
switch(format)
{
case GSM_MESSAGE_FORMAT_PDU:
case GSM_MESSAGE_FORMAT_TEXT:
break;
default:
return 1;
}
cmd[8] = format + '0';
return (_gsm_queue(gsm, cmd) != NULL) ? 0 : 1;
}
/* gsm_modem_set_operator_format */
static int _gsm_modem_set_operator_format(GSM * gsm, GSMOperatorFormat format)
{
char cmd[] = "AT+COPS=X,X";
switch(format)
{
case GSM_OPERATOR_FORMAT_LONG:
case GSM_OPERATOR_FORMAT_SHORT:
case GSM_OPERATOR_FORMAT_LAI:
break;
default:
return 1;
}
cmd[8] = GSM_OPERATOR_MODE_SET_FORMAT + '0';
cmd[10] = format + '0';
return (_gsm_queue(gsm, cmd) != NULL) ? 0 : 1;
}
/* gsm_modem_set_operator_mode */
static int _gsm_modem_set_operator_mode(GSM * gsm, GSMOperatorMode mode)
{
char cmd[] = "AT+COPS=X";
GSMCommand * gsmc;
switch(mode)
{
case GSM_OPERATOR_MODE_AUTOMATIC:
case GSM_OPERATOR_MODE_MANUAL:
case GSM_OPERATOR_MODE_DEREGISTER:
break;
case GSM_OPERATOR_MODE_MANUAL_WITH_FALLBACK:
/* FIXME implement this one too */
default:
return 1;
}
cmd[8] = mode + '0';
if((gsmc = _gsm_queue(gsm, cmd)) == NULL)
return 1;
return 0;
}
/* gsm_modem_set_registration_report */
static int _gsm_modem_set_registration_report(GSM * gsm,
GSMRegistrationReport report)
{
char cmd[] = "AT+CREG=X";
switch(report)
{
case GSM_REGISTRATION_REPORT_DISABLE_UNSOLLICITED:
case GSM_REGISTRATION_REPORT_ENABLE_UNSOLLICITED:
case GSM_REGISTRATION_REPORT_ENABLE_UNSOLLICITED_WITH_LOCATION:
break;
default:
return 1;
}
cmd[8] = report + '0';
return (_gsm_queue(gsm, cmd) != NULL) ? 0 : 1;
}
/* gsm_modem_set_verbose */
static int _gsm_modem_set_verbose(GSM * gsm, gboolean verbose)
{
char cmd[] = "ATVX";
cmd[3] = verbose ? '1' : '0';
return (_gsm_queue(gsm, cmd) != NULL) ? 0 : 1;
}
/* gsm_parse */
static int _parse_pdu(GSM * gsm);
static int _parse_do(GSM * gsm, size_t * i);
static int _gsm_parse(GSM * gsm)
{
int ret = 0;
size_t i = 0;
char * p;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() cnt=%lu\n", __func__, gsm->rd_buf_cnt);
#endif
while(i < gsm->rd_buf_cnt)
{
if(gsm->rd_buf[i++] != '\r' && gsm->rd_buf[i - 1] != '\n')
continue;
gsm->rd_buf[i - 1] = '\0';
if(i < gsm->rd_buf_cnt && gsm->rd_buf[i] == '\n')
i++;
if(gsm->rd_buf[0] != '\0')
ret |= _parse_do(gsm, &i);
gsm->rd_buf_cnt -= i;
memmove(gsm->rd_buf, &gsm->rd_buf[i], gsm->rd_buf_cnt);
if((p = realloc(gsm->rd_buf, gsm->rd_buf_cnt)) != NULL)
gsm->rd_buf = p; /* we can ignore errors... */
else if(gsm->rd_buf_cnt == 0)
gsm->rd_buf = NULL; /* ...except when it's not one */
i = 0;
}
if(gsm->mode == GSM_MODE_PDU)
return _parse_pdu(gsm);
return ret;
}
static int _parse_pdu(GSM * gsm)
{
size_t i = 0;
char * p;
if(gsm->rd_buf_cnt < 2)
return 0;
if(gsm->rd_buf[i++] == '>' && gsm->rd_buf[i++] == ' ')
{
_gsm_queue_pop(gsm);
_gsm_queue_push(gsm);
}
gsm->rd_buf_cnt -= i;
memmove(gsm->rd_buf, &gsm->rd_buf[i], gsm->rd_buf_cnt);
if((p = realloc(gsm->rd_buf, gsm->rd_buf_cnt)) != NULL)
gsm->rd_buf = p; /* we can ignore errors... */
else if(gsm->rd_buf_cnt == 0)
gsm->rd_buf = NULL; /* ...except when it's not one */
return 0;
}
static int _parse_do(GSM * gsm, size_t * i)
{
gboolean answered = FALSE;
if(gsm->mode == GSM_MODE_INIT)
{
if(strcmp(gsm->rd_buf, "OK") != 0)
return 0;
g_source_remove(gsm->source);
gsm->source = 0;
*i = gsm->rd_buf_cnt; /* XXX ugly: flush read buffer */
g_io_channel_flush(gsm->channel, NULL); /* XXX check errors? */
gsm->mode = GSM_MODE_COMMAND;
_gsm_modem_set_echo(gsm, FALSE);
_gsm_modem_set_verbose(gsm, TRUE);
#if 0 /* XXX errors are no longer detected as acknowledging a command */
_gsm_modem_set_extended_errors(gsm, TRUE);
#endif
_gsm_modem_set_extended_ring_reports(gsm, TRUE);
_gsm_modem_get_model(gsm);
_gsm_event_set_status(gsm, GSM_STATUS_INITIALIZED);
_gsm_queue_push(gsm);
}
else if(gsm->mode == GSM_MODE_COMMAND)
{
_gsm_parse_line(gsm, gsm->rd_buf, &answered);
if(answered)
{
_gsm_queue_pop(gsm);
_gsm_queue_push(gsm);
}
}
return 0;
}
/* gsm_parse_line */
static int _gsm_parse_line(GSM * gsm, char const * line, gboolean * answered)
{
size_t i;
GSMCommand * command;
GSMError error = GSM_ERROR_UNKNOWN;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, line);
#endif
if(answered != NULL)
*answered = FALSE;
if(strncmp(line, "AT", 2) == 0) /* ignore echo (tighter check?) */
return 0;
if(strcmp(line, "RING") == 0)
{
_gsm_event(gsm, GSM_EVENT_TYPE_INCOMING_CALL,
GSM_CALL_TYPE_UNKNOWN);
return 0;
}
if(strcmp(line, "OK") == 0)
{
if(answered != NULL)
*answered = TRUE;
if((command = g_slist_nth_data(gsm->queue, 0)) != NULL
&& command->callback != NULL)
command->callback(gsm);
return 0;
}
for(i = 0; _gsm_errors[i] != NULL; i++)
{
if(strcmp(_gsm_errors[i], line) != 0)
continue;
if(answered != NULL)
*answered = TRUE;
if((command = g_slist_nth_data(gsm->queue, 0)) != NULL)
error = command->error;
_gsm_event(gsm, GSM_EVENT_TYPE_ERROR, error, line);
return 0;
}
for(i = 0; _gsm_triggers[i].trigger != NULL; i++)
if(strncmp(line, _gsm_triggers[i].trigger,
_gsm_triggers[i].trigger_cnt) == 0)
return _gsm_triggers[i].callback(gsm,
&line[_gsm_triggers[i].trigger_cnt]);
/* XXX not handled... */
return 1;
}
/* queue management */
/* gsm_queue */
static GSMCommand * _gsm_queue(GSM * gsm, char const * command)
{
GSMCommand * gsmc;
if(command == NULL || command[0] == '\0')
return NULL;
if((gsmc = _gsm_command_new(command)) == NULL)
return NULL;
if(_gsm_queue_command(gsm, gsmc) == 0)
return gsmc;
_gsm_command_delete(gsmc);
return NULL;
}
/* gsm_queue_command */
static int _gsm_queue_command(GSM * gsm, GSMCommand * command)
{
GSMPriority priority;
GSList * l;
GSMCommand * p;
if(command == NULL)
return 1;
/* the GSM_PRIORITY_HIGHEST priority is meant to avoid races */
if((priority = _gsm_command_get_priority(command)) > GSM_PRIORITY_HIGH)
priority = GSM_PRIORITY_HIGH;
for(l = gsm->queue; l != NULL; l = l->next)
{
p = l->data;
if(_gsm_command_get_priority(p) < priority)
break;
}
if(l != NULL)
gsm->queue = g_slist_insert_before(gsm->queue, l, command);
else if(gsm->queue == NULL && gsm->mode == GSM_MODE_COMMAND)
{
gsm->queue = g_slist_append(gsm->queue, command);
_gsm_queue_push(gsm);
}
else if(gsm->mode == GSM_MODE_INIT && gsm->wr_source == 0)
{
gsm->queue = g_slist_append(gsm->queue, command);
_gsm_queue_push(gsm);
}
else
gsm->queue = g_slist_append(gsm->queue, command);
return 0;
}
/* gsm_queue_error */
static int _gsm_queue_with_error(GSM * gsm, char const * command,
GSMError error)
{
GSMCommand * gsmc;
if((gsmc = _gsm_queue(gsm, command)) == NULL)
return 1;
_gsm_command_set_error(gsmc, error);
return 0;
}
/* _gsm_queue_flush */
static void _gsm_queue_flush(GSM * gsm)
{
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
g_slist_foreach(gsm->queue, (GFunc)_gsm_command_delete, NULL);
g_slist_free(gsm->queue);
gsm->queue = NULL;
free(gsm->rd_buf);
gsm->rd_buf = NULL;
gsm->rd_buf_cnt = 0;
if(gsm->rd_source != 0)
{
g_source_remove(gsm->rd_source);
gsm->rd_source = 0;
}
free(gsm->wr_buf);
gsm->wr_buf = NULL;
gsm->wr_buf_cnt = 0;
if(gsm->wr_source != 0)
{
g_source_remove(gsm->wr_source);
gsm->wr_source = 0;
}
if(gsm->source != 0)
{
g_source_remove(gsm->source);
gsm->source = 0;
}
}
/* gsm_queue_full */
static int _gsm_queue_full(GSM * gsm, GSMPriority priority,
char const * command, GSMError error,
GSMCommandCallback callback)
{
return _gsm_queue_full_mode(gsm, priority, command, error,
callback, GSM_MODE_COMMAND);
}
/* gsm_queue_full_mode */
static int _gsm_queue_full_mode(GSM * gsm, GSMPriority priority,
char const * command, GSMError error,
GSMCommandCallback callback, GSMMode mode)
{
GSMCommand * gsmc;
if(command == NULL || command[0] == '\0')
return 1;
if((gsmc = _gsm_command_new(command)) == NULL)
return 1;
_gsm_command_set_priority(gsmc, priority);
_gsm_command_set_error(gsmc, error);
_gsm_command_set_callback(gsmc, callback);
_gsm_command_set_mode(gsmc, mode);
if(_gsm_queue_command(gsm, gsmc) == 0)
return 0;
_gsm_command_delete(gsmc);
return 1;
}
/* gsm_queue_pop */
static void _gsm_queue_pop(GSM * gsm)
{
GSMCommand * gsmc;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
if(gsm->queue == NULL)
return;
gsmc = gsm->queue->data;
_gsm_command_delete(gsmc);
gsm->queue = g_slist_remove(gsm->queue, gsmc);
#if 0 /* FIXME this is useless */
if(gsm->mode != GSM_MODE_COMMAND)
return;
#endif
}
/* gsm_queue_push */
static int _gsm_queue_push(GSM * gsm)
{
GSMCommand * gsmc;
char const suffix[] = "\r\n";
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
if(gsm->queue == NULL)
return 0;
gsmc = gsm->queue->data;
gsm->wr_buf_cnt = strlen(gsmc->command) + sizeof(suffix);
if((gsm->wr_buf = malloc(gsm->wr_buf_cnt)) == NULL)
return 1;
snprintf(gsm->wr_buf, gsm->wr_buf_cnt--, "%s%s", gsmc->command, suffix);
/* FIXME shouldn't it always be the case? flush input queue first? */
if(gsm->channel != NULL && gsm->wr_source == 0)
gsm->wr_source = g_io_add_watch(gsm->channel, G_IO_OUT,
_on_watch_can_write, gsm);
return 0;
}
/* triggers */
/* gsm_trigger_cgmm */
static int _gsm_trigger_cgmm(GSM * gsm, char const * result)
{
size_t i;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, result);
#endif
for(i = 0; _gsm_models[i].model != NULL; i++)
if(strcmp(_gsm_models[i].model, result) == 0)
break;
if(_gsm_models[i].model == NULL)
return 1; /* we do not know this model */
gsm->quirks = _gsm_models[i].quirks;
return 0;
}
/* gsm_trigger_cme_error */
static int _gsm_trigger_cme_error(GSM * gsm, char const * result)
{
int code;
char * p;
size_t i;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, result);
#endif
code = strtol(result, &p, 10);
if(result[0] == '\0' || *p != '\0')
return 1;
for(i = 0; _gsm_cme_errors[i].error != NULL; i++)
if(_gsm_cme_errors[i].code == code)
break;
if(_gsm_cme_errors[i].error == NULL)
return 1;
/* FIXME implement errors */
return _gsm_event(gsm, GSM_EVENT_TYPE_ERROR, GSM_ERROR_UNKNOWN,
_(_gsm_cme_errors[i].error));
}
/* gsm_trigger_cms_error */
static int _gsm_trigger_cms_error(GSM * gsm, char const * result)
{
int code;
char * p;
size_t i;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, result);
#endif
code = strtol(result, &p, 10);
if(result[0] == '\0' || *p != '\0')
return 1;
for(i = 0; _gsm_cms_errors[i].error != NULL; i++)
if(_gsm_cms_errors[i].code == code)
break;
if(_gsm_cms_errors[i].error == NULL)
return 1;
/* FIXME implement errors */
return _gsm_event(gsm, GSM_EVENT_TYPE_ERROR, GSM_ERROR_UNKNOWN,
_(_gsm_cms_errors[i].error));
}
/* gsm_trigger_cmgl */
static int _gsm_trigger_cmgl(GSM * gsm, char const * result)
{
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, result);
#endif
if(sscanf(result, "(%u-%u)", &gsm->event.message_list.start,
&gsm->event.message_list.end) != 2)
return 1;
return _gsm_event_send(gsm, GSM_EVENT_TYPE_MESSAGE_LIST);
}
/* gsm_trigger_cmgs */
static int _gsm_trigger_cmgs(GSM * gsm, char const * result)
{
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, result);
#endif
if(sscanf(result, "%u", &gsm->event.message_sent.mr) != 1)
return 1;
return _gsm_event_send(gsm, GSM_EVENT_TYPE_MESSAGE_SENT);
}
/* gsm_trigger_cops */
static int _gsm_trigger_cops(GSM * gsm, char const * result)
{
char operator[32];
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, result);
#endif
gsm->event.operator.operator = operator;
gsm->event.operator.lai = 0;
if(sscanf(result, "%u,%u,\"%31[^\"]\",%u", &gsm->event.operator.mode,
&gsm->event.operator.format, operator,
&gsm->event.operator.lai) >= 3)
return _gsm_event_send(gsm, GSM_EVENT_TYPE_OPERATOR);
return 1;
}
/* _gsm_trigger_cpbr */
static int _gsm_trigger_cpbr(GSM * gsm, char const * result)
{
unsigned int start;
unsigned int end;
unsigned int index;
char number[32];
char name[32];
gchar * p;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, result);
#endif
if(sscanf(result, "(%u-%u)", &start, &end) == 2)
return _gsm_event(gsm, GSM_EVENT_TYPE_CONTACT_LIST, start, end);
if(sscanf(result, "%u,\"%31[^\"]\",145,\"%31[^\"]\"", &index, number,
name) != 3
&& sscanf(result, "%u,\"%31[^\"]\",129,\"%31[^\"]\"",
&index, number, name) != 3)
return 1;
number[sizeof(number) - 1] = '\0';
name[sizeof(name) - 1] = '\0';
if((p = g_convert(name, -1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL))
!= NULL)
{
snprintf(name, sizeof(name), "%s", p);
g_free(p);
}
return _gsm_event(gsm, GSM_EVENT_TYPE_CONTACT, index, name,
number);
}
/* _gsm_trigger_cpin */
static int _gsm_trigger_cpin(GSM * gsm, char const * result)
{
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, result);
#endif
if(strcmp(result, "READY") == 0)
return _gsm_event_set_status(gsm, GSM_STATUS_READY);
if(strcmp(result, "SIM PIN") == 0)
return _gsm_event(gsm, GSM_EVENT_TYPE_ERROR,
GSM_ERROR_SIM_PIN_REQUIRED, NULL);
else
/* XXX nicer message */
return _gsm_event(gsm, GSM_EVENT_TYPE_ERROR, result);
}
/* gsm_trigger_creg */
static int _gsm_trigger_creg(GSM * gsm, char const * result)
{
int ret;
int res;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, result);
#endif
if((res = sscanf(result, "%u,%u,%X,%X", &gsm->event.registration.n,
&gsm->event.registration.stat,
&gsm->event.registration.area,
&gsm->event.registration.cell)) == 4)
ret = _gsm_event_send(gsm, GSM_EVENT_TYPE_REGISTRATION);
else if(res == 2)
{
gsm->event.registration.area = 0;
gsm->event.registration.cell = 0;
ret = _gsm_event_send(gsm, GSM_EVENT_TYPE_REGISTRATION);
}
else
return 1;
switch(gsm->event.registration.stat)
{
case GSM_REGISTRATION_STATUS_NOT_SEARCHING:
ret |= _gsm_event_set_status(gsm, GSM_STATUS_READY);
break;
case GSM_REGISTRATION_STATUS_NOT_REGISTERED:
ret |= _gsm_event_set_status(gsm,
GSM_STATUS_REGISTERING);
break;
case GSM_REGISTRATION_STATUS_DENIED:
ret |= _gsm_event_set_status(gsm,
GSM_STATUS_REGISTERING_DENIED);
break;
case GSM_REGISTRATION_STATUS_REGISTERED_HOME:
ret |= _gsm_event_set_status(gsm,
GSM_STATUS_REGISTERED_HOME);
break;
case GSM_REGISTRATION_STATUS_REGISTERED_ROAMING:
ret |= _gsm_event_set_status(gsm,
GSM_STATUS_REGISTERED_ROAMING);
break;
case GSM_REGISTRATION_STATUS_UNKNOWN:
default:
ret |= _gsm_event_set_status(gsm, GSM_STATUS_UNKNOWN);
break;
}
return ret;
}
/* gsm_trigger_cring */
static int _gsm_trigger_cring(GSM * gsm, char const * result)
{
GSMCallType calltype = GSM_CALL_TYPE_UNKNOWN;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, result);
#endif
/* XXX implement the other call types */
if(strcmp(result, "VOICE") == 0)
calltype = GSM_CALL_TYPE_VOICE;
return _gsm_event(gsm, GSM_EVENT_TYPE_INCOMING_CALL, calltype);
}
/* _gsm_trigger_csq */
static int _gsm_trigger_csq(GSM * gsm, char const * result)
{
unsigned int rssi;
unsigned int ber;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, result);
#endif
if(sscanf(result, "%u,%u", &rssi, &ber) != 2)
return 1;
gsm->event.signal_level.level = rssi;
if(rssi > 31)
gsm->event.signal_level.level /= 0.0;
else
gsm->event.signal_level.level /= 32;
return _gsm_event_send(gsm, GSM_EVENT_TYPE_SIGNAL_LEVEL);
}
/* callbacks */
/* on_reset */
static int _reset_do(int fd, unsigned int baudrate, unsigned int hwflow);
static gboolean _reset_settle(gpointer data);
static gboolean _on_reset(gpointer data)
{
GSM * gsm = data;
int fd;
char buf[256];
GError * error = NULL;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
gsm->source = 0;
if(gsm->channel != NULL)
{
/* XXX should the file descriptor also be freed? */
g_io_channel_shutdown(gsm->channel, TRUE, &error);
g_io_channel_unref(gsm->channel);
gsm->channel = NULL;
}
if((fd = open(gsm->device, O_RDWR | O_NONBLOCK)) < 0
|| _reset_do(fd, gsm->baudrate, gsm->hwflow) != 0)
{
snprintf(buf, sizeof(buf), "%s%s%s", gsm->device, ": ",
strerror(errno));
if(fd >= 0)
close(fd);
if(gsm->retry > 0)
gsm->source = g_timeout_add(gsm->retry, _on_reset, gsm);
return phone_error(NULL, buf, FALSE);
}
gsm->channel = g_io_channel_unix_new(fd);
if((g_io_channel_set_encoding(gsm->channel, NULL, &error))
!= G_IO_STATUS_NORMAL)
phone_error(NULL, error->message, 0);
g_io_channel_set_buffered(gsm->channel, FALSE);
gsm->rd_source = g_io_add_watch(gsm->channel, G_IO_IN,
_on_watch_can_read, gsm);
if(gsm->wr_buf_cnt > 0)
gsm->wr_source = g_io_add_watch(gsm->channel, G_IO_OUT,
_on_watch_can_write, gsm);
gsm->source = g_timeout_add(500, _reset_settle, gsm);
_reset_settle(gsm);
return FALSE;
}
static int _reset_do(int fd, unsigned int baudrate, unsigned int hwflow)
{
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 |= CS8;
term.c_cflag |= CREAD;
if(hwflow)
term.c_cflag |= CRTSCTS;
else
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(cfsetispeed(&term, 0) != 0) /* same speed as output speed */
phone_error(NULL, "/dev/modem", 0); /* go on anyway */
if(cfsetospeed(&term, baudrate) != 0)
phone_error(NULL, "/dev/modem", 0); /* go on anyway */
if(tcsetattr(fd, TCSAFLUSH, &term) != 0)
return 1;
}
return 0;
}
static gboolean _reset_settle(gpointer data)
{
GSM * gsm = data;
_gsm_modem_reset(gsm);
return TRUE;
}
/* on_watch_can_read */
static gboolean _on_watch_can_read(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()\n", __func__);
#endif
if(condition != G_IO_IN || source != gsm->channel)
return FALSE; /* should not happen */
if((p = realloc(gsm->rd_buf, gsm->rd_buf_cnt + 256)) == NULL)
return TRUE; /* XXX retries immediately (delay?) */
gsm->rd_buf = p;
status = g_io_channel_read_chars(source, &gsm->rd_buf[gsm->rd_buf_cnt],
256, &cnt, &error);
#ifdef DEBUG
fprintf(stderr, "%s", "DEBUG: MODEM: ");
fwrite(&gsm->rd_buf[gsm->rd_buf_cnt], sizeof(*p), cnt, stderr);
#endif
gsm->rd_buf_cnt += cnt;
switch(status)
{
case G_IO_STATUS_NORMAL:
break;
case G_IO_STATUS_ERROR:
phone_error(NULL, error->message, 0);
case G_IO_STATUS_EOF:
default: /* should not happen... */
if(gsm->retry > 0)
gsm_reset(gsm, gsm->retry);
gsm->rd_source = 0;
return FALSE;
}
_gsm_parse(gsm);
return TRUE;
}
/* on_watch_can_write */
static gboolean _on_watch_can_write(GIOChannel * source, GIOCondition condition,
gpointer data)
{
GSM * gsm = data;
gsize cnt = 0;
GError * error = NULL;
GIOStatus status;
char * p;
GSMCommand * gsmc;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() cnt=%lu\n", __func__, gsm->wr_buf_cnt);
#endif
if(condition != G_IO_OUT || source != gsm->channel)
return FALSE; /* should not happen */
status = g_io_channel_write_chars(source, gsm->wr_buf, gsm->wr_buf_cnt,
&cnt, &error);
#ifdef DEBUG
fprintf(stderr, "%s", "DEBUG: PHONE: ");
fwrite(gsm->wr_buf, sizeof(*p), cnt, stderr);
#endif
if(cnt != 0) /* some data may have been written anyway */
{
gsm->wr_buf_cnt -= cnt;
memmove(gsm->wr_buf, &gsm->wr_buf[cnt], gsm->wr_buf_cnt);
if((p = realloc(gsm->wr_buf, gsm->wr_buf_cnt)) != NULL)
gsm->wr_buf = p; /* we can ignore errors... */
else if(gsm->wr_buf_cnt == 0)
gsm->wr_buf = NULL; /* ...except when it's not one */
}
switch(status)
{
case G_IO_STATUS_NORMAL:
break;
case G_IO_STATUS_ERROR:
phone_error(NULL, error->message, 0);
case G_IO_STATUS_EOF:
default: /* should not happen */
if(gsm->retry > 0)
gsm_reset(gsm, gsm->retry);
gsm->wr_source = 0;
return FALSE;
}
if(gsm->wr_buf_cnt > 0) /* there is more data to write */
return TRUE;
gsm->wr_source = 0;
if(gsm->mode == GSM_MODE_INIT)
_gsm_queue_pop(gsm);
else if(gsm->queue != NULL && (gsmc = gsm->queue->data) != NULL)
gsm->mode = _gsm_command_get_mode(gsmc);
return FALSE;
}