Desktop
/* $Id: hayes.c,v 1.1.2.42 2011/01/23 17:53:11 khorben Exp $ */
/* Copyright (c) 2011 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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <termios.h>
#include <errno.h>
#include <glib.h>
#include <System.h>
#include <Phone/modem.h>
#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))
/* Hayes */
/* private */
/* types */
typedef enum _HayesMode
{
HAYES_MODE_INIT = 0,
HAYES_MODE_COMMAND,
HAYES_MODE_DATA
} HayesMode;
typedef struct _Hayes
{
unsigned int retry;
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_name;
char * call_number;
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_70 = 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_CALL_WAITING_UNSOLLICITED_DISABLE,
HAYES_REQUEST_CALL_WAITING_UNSOLLICITED_ENABLE,
HAYES_REQUEST_CONNECTED_LINE_DISABLE,
HAYES_REQUEST_CONNECTED_LINE_ENABLE,
HAYES_REQUEST_CONTACT,
HAYES_REQUEST_EXTENDED_ERRORS,
HAYES_REQUEST_EXTENDED_RING_REPORTS,
HAYES_REQUEST_FUNCTIONAL,
HAYES_REQUEST_FUNCTIONAL_DISABLE,
HAYES_REQUEST_FUNCTIONAL_ENABLE,
HAYES_REQUEST_GPRS_ATTACHED,
HAYES_REQUEST_LOCAL_ECHO_DISABLE,
HAYES_REQUEST_LOCAL_ECHO_ENABLE,
HAYES_REQUEST_MESSAGE,
HAYES_REQUEST_MESSAGE_UNSOLLICITED_DISABLE,
HAYES_REQUEST_MESSAGE_UNSOLLICITED_ENABLE,
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_REGISTRATION,
HAYES_REQUEST_REGISTRATION_AUTOMATIC,
HAYES_REQUEST_REGISTRATION_UNSOLLICITED_DISABLE,
HAYES_REQUEST_REGISTRATION_UNSOLLICITED_ENABLE,
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, unsigned int retry);
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_authentication(HayesCommand * command,
HayesCommandStatus status, void * priv);
static HayesCommandStatus _on_request_battery_level(HayesCommand * command,
HayesCommandStatus status, void * priv);
static HayesCommandStatus _on_request_call(HayesCommand * command,
HayesCommandStatus status, void * priv);
static HayesCommandStatus _on_request_call_incoming(HayesCommand * command,
HayesCommandStatus status, void * priv);
static HayesCommandStatus _on_request_call_outgoing(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_functional(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 void _on_trigger_call_error(ModemPlugin * modem, char const * answer);
static void _on_trigger_cbc(ModemPlugin * modem, char const * answer);
static void _on_trigger_cfun(ModemPlugin * modem, char const * answer);
static void _on_trigger_cgatt(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_clip(ModemPlugin * modem, char const * answer);
static void _on_trigger_cme_error(ModemPlugin * modem, char const * answer);
static void _on_trigger_cms_error(ModemPlugin * modem, char const * answer);
static void _on_trigger_cmti(ModemPlugin * modem, char const * answer);
static void _on_trigger_connect(ModemPlugin * modem, char const * answer);
static void _on_trigger_colp(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_creg(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);
/* helpers */
static int _is_figure(int c);
static int _is_number(char const * number);
/* variables */
typedef enum _HayesConfig
{
HAYES_CONFIG_BAUDRATE = 0,
HAYES_CONFIG_HWFLOW,
HAYES_CONFIG_DEVICE
} HayesConfig;
#define HAYES_CONFIG_LAST HAYES_CONFIG_DEVICE
#define HAYES_CONFIG_COUNT (HAYES_CONFIG_LAST + 1)
static ModemConfig _hayes_config[HAYES_CONFIG_COUNT + 1] =
{
{ "baudrate", "Baudrate", MCT_UINT32, (void*)115200 },
{ "hwflow", "Hardware flow",MCT_BOOLEAN, (void*)1 },
{ "device", "Device", MCT_FILENAME, NULL },
{ NULL, NULL, MCT_NONE, NULL }
};
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_70 },
{ NULL, 0 }
};
static HayesRequestHandler _hayes_request_handlers[] =
{
{ HAYES_REQUEST_ALIVE, "AT",
_on_request_generic },
{ HAYES_REQUEST_BATTERY_LEVEL, "AT+CBC",
_on_request_battery_level },
{ HAYES_REQUEST_CALL_WAITING_UNSOLLICITED_DISABLE,"AT+CCWA=1",
_on_request_generic },
{ HAYES_REQUEST_CALL_WAITING_UNSOLLICITED_ENABLE,"AT+CCWA=1",
_on_request_generic },
{ HAYES_REQUEST_CONNECTED_LINE_DISABLE, "AT+COLP=0",
_on_request_generic },
{ HAYES_REQUEST_CONNECTED_LINE_ENABLE, "AT+COLP=1",
_on_request_generic },
{ HAYES_REQUEST_EXTENDED_ERRORS, "AT+CMEE=1",
_on_request_generic },
{ HAYES_REQUEST_EXTENDED_RING_REPORTS, "AT+CRC=1",
_on_request_generic },
{ HAYES_REQUEST_FUNCTIONAL, "AT+CFUN?",
_on_request_generic },
{ HAYES_REQUEST_FUNCTIONAL_DISABLE, "AT+CFUN=0",
_on_request_generic },
{ HAYES_REQUEST_FUNCTIONAL_ENABLE, "AT+CFUN=1",
_on_request_functional },
{ HAYES_REQUEST_GPRS_ATTACHED, "AT+CGATT?",
_on_request_generic },
{ HAYES_REQUEST_LOCAL_ECHO_DISABLE, "ATE0",
_on_request_generic },
{ HAYES_REQUEST_LOCAL_ECHO_ENABLE, "ATE1",
_on_request_generic },
{ HAYES_REQUEST_MESSAGE_UNSOLLICITED_DISABLE, "AT+CNMI=0",
_on_request_generic },
{ HAYES_REQUEST_MESSAGE_UNSOLLICITED_ENABLE, "AT+CNMI=1",
_on_request_generic }, /* XXX report error? */
{ HAYES_REQUEST_MODEL, "AT+CGMM",
_on_request_model },
{ HAYES_REQUEST_OPERATOR, "AT+COPS?",
_on_request_generic },
{ HAYES_REQUEST_OPERATOR_FORMAT_LONG, "AT+COPS=3,0",
_on_request_generic },
{ HAYES_REQUEST_OPERATOR_FORMAT_NUMERIC, "AT+COPS=3,2",
_on_request_generic },
{ HAYES_REQUEST_OPERATOR_FORMAT_SHORT, "AT+COPS=3,1",
_on_request_generic },
{ HAYES_REQUEST_PHONE_ACTIVE, "AT+CPAS",
_on_request_call },
{ HAYES_REQUEST_REGISTRATION, "AT+CREG?",
_on_request_generic },
{ HAYES_REQUEST_REGISTRATION_AUTOMATIC, "AT+COPS=0",
_on_request_generic },
{ HAYES_REQUEST_REGISTRATION_UNSOLLICITED_DISABLE,"AT+CREG=0",
_on_request_generic },
{ HAYES_REQUEST_REGISTRATION_UNSOLLICITED_ENABLE,"AT+CREG=2",
_on_request_generic },
{ HAYES_REQUEST_SIGNAL_LEVEL, "AT+CSQ",
_on_request_generic },
{ HAYES_REQUEST_SIM_PIN_VALID, "AT+CPIN?",
_on_request_authentication },
{ HAYES_REQUEST_VENDOR, "AT+CGMI",
_on_request_model },
{ HAYES_REQUEST_VERBOSE, "ATV1",
_on_request_generic },
{ HAYES_REQUEST_VERSION, "AT+CGMR",
_on_request_model },
{ MODEM_REQUEST_AUTHENTICATE, NULL,
_on_request_authentication },
{ MODEM_REQUEST_CALL, NULL,
_on_request_call_outgoing },
{ MODEM_REQUEST_CALL_ANSWER, "ATA",
_on_request_call_incoming },
{ MODEM_REQUEST_CALL_HANGUP, "ATH",
_on_request_call_status },
{ MODEM_REQUEST_CALL_PRESENTATION, NULL,
_on_request_generic },
{ 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 },
{ "+CFUN", _on_trigger_cfun },
{ "+CGATT", _on_trigger_cgatt },
{ "+CGMI", _on_trigger_cgmi },
{ "+CGMM", _on_trigger_cgmm },
{ "+CGMR", _on_trigger_cgmr },
{ "+CLIP", _on_trigger_clip },
{ "+CME ERROR", _on_trigger_cme_error },
{ "+CMS ERROR", _on_trigger_cms_error },
{ "+CMTI", _on_trigger_cmti },
{ "+COLP", _on_trigger_colp },
{ "+COPS", _on_trigger_cops },
{ "+CPAS", _on_trigger_cpas },
{ "+CPIN", _on_trigger_cpin },
{ "+CREG", _on_trigger_creg },
{ "+CRING", _on_trigger_cring },
{ "+CSQ", _on_trigger_csq },
{ "BUSY", _on_trigger_call_error },
{ "CONNECT", _on_trigger_connect },
{ "NO CARRIER", _on_trigger_call_error },
{ "NO DIALTONE",_on_trigger_call_error },
{ "RING", _on_trigger_cring }
};
/* public */
/* variables */
ModemPlugin plugin =
{
NULL,
"Hayes",
NULL,
_hayes_config,
_hayes_init,
_hayes_destroy,
_hayes_start,
_hayes_stop,
_hayes_request,
_hayes_trigger,
NULL
};
/* private */
/* functions */
static int _hayes_init(ModemPlugin * modem)
{
Hayes * hayes;
size_t i;
if((hayes = object_new(sizeof(*hayes))) == NULL)
return -1;
memset(hayes, 0, sizeof(*hayes));
modem->priv = hayes;
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;
}
return 0;
}
/* hayes_destroy */
static int _hayes_destroy(ModemPlugin * modem)
{
Hayes * hayes = modem->priv;
_hayes_stop(modem);
string_delete(hayes->authentication_name);
string_delete(hayes->call_number);
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);
object_delete(hayes);
return 0;
}
/* hayes_request */
static char * _request_attention(ModemPlugin * modem, ModemRequest * request);
static char * _request_attention_apn(char const * protocol, char const * apn);
static char * _request_attention_call(ModemPlugin * modem,
ModemRequest * request);
static char * _request_attention_sim_pin(ModemPlugin * modem,
char const * password);
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)
#ifdef DEBUG
return -modem->helper->error(modem->helper->modem,
"Unable to handle request", 1);
#else
return -1;
#endif
if((attention = _hayes_request_handlers[i].attention) == NULL)
{
if((p = _request_attention(modem, request)) == 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, ModemRequest * request)
{
char buf[32];
switch(request->type)
{
case MODEM_REQUEST_AUTHENTICATE:
if(strcmp(request->authenticate.name, "APN") == 0)
return _request_attention_apn(
request->authenticate.username,
request->authenticate.password);
if(strcmp(request->authenticate.name, "SIM PIN") == 0)
return _request_attention_sim_pin(modem,
request->authenticate.password);
break;
case MODEM_REQUEST_CALL:
return _request_attention_call(modem, request);
case MODEM_REQUEST_CALL_PRESENTATION:
snprintf(buf, sizeof(buf), "%s%u", "AT+CLIP=",
request->call_presentation.enabled
? 1 : 0);
return strdup(buf);
default:
break;
}
return NULL;
}
static char * _request_attention_apn(char const * protocol, char const * apn)
{
char * ret;
const char cmd[] = "AT+CGDCONT=1,";
size_t len;
if(protocol == NULL || apn == NULL)
return NULL;
len = sizeof(cmd) + strlen(protocol) + 2 + strlen(apn) + 3;
if((ret = malloc(len)) == NULL)
return NULL;
snprintf(ret, len, "%s\"%s\",\"%s\"", cmd, protocol, apn);
return ret;
}
static char * _request_attention_call(ModemPlugin * modem,
ModemRequest * request)
{
char * ret;
Hayes * hayes = modem->priv;
char const * number = request->call.number;
ModemEvent * event;
const char cmd[] = "ATD";
const char anonymous[] = "I";
const char voice[] = ";";
size_t len;
if(request->call.number == NULL)
request->call.number = "";
if(request->call.number[0] == '\0')
number = "L";
else if(!_is_number(request->call.number))
return NULL;
event = &hayes->events[MODEM_EVENT_TYPE_CALL];
free(hayes->call_number);
hayes->call_number = strdup(request->call.number);
if((event->call.number = hayes->call_number) == NULL)
return NULL;
len = sizeof(cmd) + strlen(number) + sizeof(anonymous) + sizeof(voice);
if((ret = malloc(len)) == NULL)
return NULL;
snprintf(ret, len, "%s%s%s%s", "ATD", number,
(request->call.anonymous) ? anonymous : "",
(request->call.call_type == MODEM_CALL_TYPE_VOICE)
? voice : "");
return ret;
}
static char * _request_attention_sim_pin(ModemPlugin * modem,
char const * password)
{
char * ret;
Hayes * hayes = modem->priv;
const char cmd[] = "AT+CPIN=";
size_t len;
char const * format;
if(password == NULL)
return NULL;
len = sizeof(cmd) + strlen(password) + 2;
if((ret = malloc(len)) == NULL)
return NULL;
format = (hayes->quirks & HAYES_QUIRK_CPIN_QUOTES) ? "%s\"%s\""
: "%s%s";
snprintf(ret, len, format, cmd, password);
return ret;
}
/* hayes_start */
static int _hayes_start(ModemPlugin * modem, unsigned int retry)
{
Hayes * hayes = modem->priv;
hayes->retry = retry;
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
memset(&request, 0, sizeof(request));
switch(event)
{
case MODEM_EVENT_TYPE_AUTHENTICATION:
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:
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_REGISTRATION;
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;
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_ACTIVE)
_hayes_parse_trigger(modem, hayes->rd_buf, command);
if((status = _hayes_command_get_status(command)) == HCS_SUCCESS
|| status == HCS_ERROR)
{
_hayes_queue_pop(modem);
_hayes_queue_push(modem);
}
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)
continue;
if(answer[len] == ':')
{
if(answer[++len] == ' ') /* skip the optional space */
len++;
}
else if(answer[len] != '\0')
continue;
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:
case HAYES_MODE_DATA:
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 * prefix = "";
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
if(hayes->mode == HAYES_MODE_DATA)
prefix = "+++\r\n"; /* FIXME does not seem to work */
size = strlen(prefix) + strlen(attention) + sizeof(suffix);
if((p = realloc(hayes->wr_buf, hayes->wr_buf_cnt + size)) == NULL)
return -modem->helper->error(modem->helper->modem, strerror(
errno), 1);
hayes->wr_buf = p;
snprintf(&hayes->wr_buf[hayes->wr_buf_cnt], size, "%s%s%s", prefix,
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;
_hayes_stop(modem);
_hayes_start(modem, hayes->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 = NULL;
command->priv = NULL;
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)
{
if(command->status == HCS_ACTIVE)
/* we don't expect any answer */
command->status = HCS_SUCCESS;
}
else
command->status = command->callback(command, command->status,
command->priv);
return command->status;
}
/* callbacks */
/* on_reset */
static int _reset_open(ModemPlugin * modem);
static int _reset_configure(ModemPlugin * modem, char const * device, int fd);
static unsigned int _reset_configure_baudrate(ModemPlugin * modem,
unsigned int baudrate);
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(modem)) < 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(ModemPlugin * modem)
{
char const * device = modem->config[HAYES_CONFIG_DEVICE].value;
int fd;
if(device == NULL)
device = "/dev/modem";
if((fd = open(device, O_RDWR | O_NONBLOCK)) < 0)
return -error_set_code(1, "%s: %s", device, strerror(errno));
if(_reset_configure(modem, device, fd) != 0)
{
close(fd);
return -1;
}
return fd;
}
static int _reset_configure(ModemPlugin * modem, char const * device, int fd)
{
unsigned int baudrate = (unsigned long)modem->config[
HAYES_CONFIG_BAUDRATE].value;
unsigned int hwflow = (modem->config[HAYES_CONFIG_HWFLOW].value
!= NULL) ? 1 : 0;
struct stat st;
int fl;
struct termios term;
baudrate = _reset_configure_baudrate(modem, baudrate);
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(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", device); /* go on anyway */
if(cfsetospeed(&term, baudrate) != 0)
error_set("%s", device); /* go on anyway */
if(tcsetattr(fd, TCSAFLUSH, &term) != 0)
return 1;
}
return 0;
}
static unsigned int _reset_configure_baudrate(ModemPlugin * modem,
unsigned int baudrate)
{
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:
error_set("%u%s", baudrate,
"Unsupported baudrate (using 115200)");
modem->helper->error(NULL, error_get(), 1);
return B115200;
}
}
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:
/* 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_MODEL;
_hayes_request(modem, &request);
request.type = HAYES_REQUEST_EXTENDED_ERRORS;
_hayes_request(modem, &request);
request.type = HAYES_REQUEST_FUNCTIONAL;
_hayes_request(modem, &request);
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;
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;
}
switch(hayes->mode)
{
case HAYES_MODE_INIT:
case HAYES_MODE_COMMAND:
_hayes_parse(modem);
break;
case HAYES_MODE_DATA:
/* FIXME transfer the data to another buffer */
hayes->rd_buf_cnt = 0;
break;
}
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;
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_authentication */
static HayesCommandStatus _on_request_authentication(HayesCommand * command,
HayesCommandStatus status, void * priv)
{
ModemPlugin * modem = priv;
Hayes * hayes = modem->priv;
ModemEvent * event = &hayes->events[MODEM_EVENT_TYPE_AUTHENTICATION];
ModemRequest request;
memset(&request, 0, sizeof(&request));
switch((status = _on_request_generic(command, status, priv)))
{
case HCS_ERROR:
event->authentication.status
= MODEM_AUTHENTICATION_STATUS_ERROR;
break;
case HCS_SUCCESS:
break;
default:
return status;
}
if(event->authentication.name != NULL)
modem->helper->event(modem->helper->modem, event);
if(event->authentication.status == MODEM_AUTHENTICATION_STATUS_OK)
{
request.type = HAYES_REQUEST_OPERATOR_FORMAT_LONG;
_hayes_request(modem, &request);
request.type = HAYES_REQUEST_REGISTRATION_UNSOLLICITED_ENABLE;
_hayes_request(modem, &request);
request.type = HAYES_REQUEST_REGISTRATION_AUTOMATIC;
_hayes_request(modem, &request);
/* force a registration report */
request.type = HAYES_REQUEST_REGISTRATION;
_hayes_request(modem, &request);
request.type = HAYES_REQUEST_MESSAGE_UNSOLLICITED_ENABLE;
_hayes_request(modem, &request);
_hayes_trigger(modem, MODEM_EVENT_TYPE_CALL);
}
return status;
}
/* 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 */
static HayesCommandStatus _on_request_call(HayesCommand * command,
HayesCommandStatus status, void * priv)
{
ModemPlugin * modem = priv;
Hayes * hayes = modem->priv;
ModemEvent * event = &hayes->events[MODEM_EVENT_TYPE_CALL];
if((status = _on_request_generic(command, status, priv)) != HCS_SUCCESS)
return status;
modem->helper->event(modem->helper->modem, event);
return status;
}
/* on_request_call_incoming */
static HayesCommandStatus _on_request_call_incoming(HayesCommand * command,
HayesCommandStatus status, void * priv)
{
ModemPlugin * modem = priv;
Hayes * hayes = modem->priv;
ModemEvent * event = &hayes->events[MODEM_EVENT_TYPE_CALL];
if((status = _on_request_generic(command, status, priv)) != HCS_SUCCESS
&& status != HCS_ERROR)
return status;
event->call.direction = MODEM_CALL_DIRECTION_INCOMING;
event->call.status = (status == HCS_SUCCESS)
? MODEM_CALL_STATUS_ACTIVE : MODEM_CALL_STATUS_NONE;
modem->helper->event(modem->helper->modem, event);
return status;
}
/* on_request_call_outgoing */
static HayesCommandStatus _on_request_call_outgoing(HayesCommand * command,
HayesCommandStatus status, void * priv)
{
ModemPlugin * modem = priv;
Hayes * hayes = modem->priv;
ModemEvent * event = &hayes->events[MODEM_EVENT_TYPE_CALL];
if((status = _on_request_generic(command, status, priv)) != HCS_SUCCESS
&& status != HCS_ERROR)
return status;
event->call.direction = MODEM_CALL_DIRECTION_OUTGOING;
event->call.status = (status == HCS_SUCCESS)
? MODEM_CALL_STATUS_ACTIVE : MODEM_CALL_STATUS_NONE;
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;
if((status = _on_request_generic(command, status, priv)) != HCS_SUCCESS
&& status != HCS_ERROR)
return status;
_hayes_trigger(modem, MODEM_EVENT_TYPE_CALL);
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_functional */
static HayesCommandStatus _on_request_functional(HayesCommand * command,
HayesCommandStatus status, void * priv)
{
ModemPlugin * modem = priv;
ModemRequest request;
memset(&request, 0, sizeof(request));
switch((status = _on_request_generic(command, status, priv)))
{
case HCS_TIMEOUT:
/* repeat request */
request.type = HAYES_REQUEST_FUNCTIONAL_ENABLE;
_hayes_request(modem, &request);
break;
default:
break;
}
return status;
}
/* 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_trigger_call_error */
static void _on_trigger_call_error(ModemPlugin * modem, char const * answer)
{
Hayes * hayes = modem->priv;
HayesCommand * command = (hayes->queue != NULL) ? hayes->queue->data
: NULL;
if(command != NULL)
_hayes_command_set_status(command, HCS_ERROR);
_hayes_trigger(modem, MODEM_EVENT_TYPE_CALL);
}
/* 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;
double f;
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;
switch((event->battery_level.status = u))
{
case MODEM_BATTERY_STATUS_CONNECTED:
case MODEM_BATTERY_STATUS_CHARGING:
f = v;
if(hayes->quirks & HAYES_QUIRK_BATTERY_70)
f /= 70.0;
else
f /= 100.0;
f = max(f, 0.0);
event->battery_level.level = min(f, 1.0);
break;
default:
event->battery_level.level = 0.0 / 0.0;
break;
}
}
/* on_trigger_cfun */
static void _on_trigger_cfun(ModemPlugin * modem, char const * answer)
{
unsigned int u;
ModemRequest request;
if(sscanf(answer, "%u", &u) != 1)
return;
memset(&request, 0, sizeof(request));
if(u != 1)
{
/* FIXME only enable if requested to */
request.type = HAYES_REQUEST_FUNCTIONAL_ENABLE;
return;
}
request.type = HAYES_REQUEST_EXTENDED_RING_REPORTS;
_hayes_request(modem, &request);
request.type = MODEM_REQUEST_CALL_PRESENTATION;
request.call_presentation.enabled = 1;
_hayes_request(modem, &request);
request.type = HAYES_REQUEST_CALL_WAITING_UNSOLLICITED_ENABLE;
_hayes_request(modem, &request);
request.type = HAYES_REQUEST_CONNECTED_LINE_ENABLE;
_hayes_request(modem, &request);
request.type = HAYES_REQUEST_SIM_PIN_VALID;
_hayes_request(modem, &request);
}
/* on_trigger_cgatt */
static void _on_trigger_cgatt(ModemPlugin * modem, char const * answer)
{
Hayes * hayes = modem->priv;
ModemEvent * event = &hayes->events[MODEM_EVENT_TYPE_REGISTRATION];
unsigned int u;
if(sscanf(answer, "%u", &u) != 1)
return;
free(hayes->registration_media);
if(u == 1)
hayes->registration_media = strdup("gprs");
else
hayes->registration_media = NULL;
event->registration.media = hayes->registration_media;
}
/* 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;
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;
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_clip */
static void _on_trigger_clip(ModemPlugin * modem, char const * answer)
{
Hayes * hayes = modem->priv;
ModemEvent * event = &hayes->events[MODEM_EVENT_TYPE_CALL];
char buf[32];
unsigned int u;
if(sscanf(answer, "\"%31[^\"]\",%u", buf, &u) != 2)
return;
buf[sizeof(buf) - 1] = '\0';
free(hayes->call_number);
switch(u)
{
case 145:
if((hayes->call_number = malloc(sizeof(buf) + 1))
== NULL)
break;
snprintf(hayes->call_number, sizeof(buf) + 1, "%s%s",
"+", buf);
break;
default:
hayes->call_number = strdup(buf);
break;
}
/* this is always an unsollicited event */
modem->helper->event(modem->helper->modem, event);
}
/* on_trigger_cme_error */
static void _on_trigger_cme_error(ModemPlugin * modem, char const * answer)
{
Hayes * hayes = modem->priv;
HayesCommand * command = (hayes->queue != NULL) ? hayes->queue->data
: NULL;
unsigned int u;
if(command != NULL)
_hayes_command_set_status(command, HCS_ERROR);
if(sscanf(answer, "%u", &u) != 1)
return;
switch(u)
{
case 11: /* SIM PIN required */
case 12: /* SIM PUK required */
_hayes_trigger(modem, MODEM_EVENT_TYPE_AUTHENTICATION);
break;
default: /* FIXME implement the rest */
break;
}
}
/* on_trigger_cms_error */
static void _on_trigger_cms_error(ModemPlugin * modem, char const * answer)
{
Hayes * hayes = modem->priv;
HayesCommand * command = (hayes->queue != NULL) ? hayes->queue->data
: NULL;
unsigned int u;
if(command != NULL)
_hayes_command_set_status(command, HCS_ERROR);
if(sscanf(answer, "%u", &u) != 1)
return;
switch(u)
{
case 311: /* SIM PIN required */
case 316: /* SIM PUK required */
_hayes_trigger(modem, MODEM_EVENT_TYPE_AUTHENTICATION);
break;
default: /* FIXME implement the rest */
break;
}
}
/* on_trigger_cmti */
static void _on_trigger_cmti(ModemPlugin * modem, char const * answer)
{
char buf[32];
unsigned int u;
ModemRequest request;
if(sscanf(answer, "\"%31[^\"]\",%u", buf, &u) != 2)
return;
buf[sizeof(buf) - 1] = '\0';
/* fetch the new message directly */
memset(&request, 0, sizeof(request));
request.type = MODEM_REQUEST_MESSAGE;
request.message.id = u;
_hayes_request(modem, &request);
}
/* on_trigger_connect */
static void _on_trigger_connect(ModemPlugin * modem, char const * answer)
{
Hayes * hayes = modem->priv;
HayesCommand * command = (hayes->queue != NULL) ? hayes->queue->data
: NULL;
if(command != NULL) /* XXX else report error? */
_hayes_command_set_status(command, HCS_SUCCESS);
hayes->mode = HAYES_MODE_DATA;
}
/* on_trigger_colp */
static void _on_trigger_colp(ModemPlugin * modem, char const * answer)
{
Hayes * hayes = modem->priv;
ModemEvent * event = &hayes->events[MODEM_EVENT_TYPE_CALL];
char buf[32];
unsigned int u;
if(sscanf(answer, "\"%31[^\"]\",%u", buf, &u) != 2)
return; /* FIXME there may be different or more information */
buf[sizeof(buf) - 1] = '\0';
free(hayes->call_number);
switch(u)
{
case 145:
if((hayes->call_number = malloc(sizeof(buf) + 1))
== NULL)
break;
snprintf(hayes->call_number, sizeof(buf) + 1, "%s%s",
"+", buf);
break;
default:
hayes->call_number = strdup(buf);
break;
}
event->call.number = hayes->call_number;
}
/* on_trigger_cops */
static void _on_trigger_cops(ModemPlugin * modem, char const * answer)
{
Hayes * hayes = modem->priv;
ModemEvent * event = &hayes->events[MODEM_EVENT_TYPE_REGISTRATION];
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;
}
/* this is usually worth an event */
modem->helper->event(modem->helper->modem, event);
}
/* 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];
unsigned int u;
if(sscanf(answer, "%u", &u) != 1)
return;
switch(u)
{
case 0:
event->call.status = MODEM_CALL_STATUS_NONE;
event->call.direction = MODEM_CALL_DIRECTION_NONE;
break;
case 3:
event->call.status = MODEM_CALL_STATUS_RINGING;
/* report event */
modem->helper->event(modem->helper->modem, event);
break;
case 4:
event->call.status = MODEM_CALL_STATUS_ACTIVE;
event->call.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];
char * p;
if(strcmp(answer, "READY") == 0)
event->authentication.status = MODEM_AUTHENTICATION_STATUS_OK;
else if(strcmp(answer, "SIM PIN") == 0)
{
free(hayes->authentication_name);
p = strdup(answer);
hayes->authentication_name = p;
event->authentication.name = p;
event->authentication.method = MODEM_AUTHENTICATION_METHOD_PIN;
event->authentication.status
= MODEM_AUTHENTICATION_STATUS_REQUIRED;
/* FIXME also provide remaining retries */
}
}
/* on_trigger_creg */
static void _on_trigger_creg(ModemPlugin * modem, char const * answer)
{
Hayes * hayes = modem->priv;
ModemEvent * event = &hayes->events[MODEM_EVENT_TYPE_REGISTRATION];
int res;
unsigned int u[4] = { 0, 0, 0, 0 };
ModemRequest request;
res = sscanf(answer, "%u,%u,%X,%X", &u[0], &u[1], &u[2], &u[3]);
if(res == 0)
return;
if(res == 1 || res == 3)
memmove(&u[1], u, sizeof(*u) * 3);
u[0] = event->registration.mode;
switch(u[1])
{
case 0:
u[0] = MODEM_REGISTRATION_MODE_DISABLED;
u[1] = MODEM_REGISTRATION_STATUS_NOT_SEARCHING;
break;
case 1:
u[1] = MODEM_REGISTRATION_STATUS_REGISTERED;
break;
case 2:
u[1] = MODEM_REGISTRATION_STATUS_SEARCHING;
break;
case 3:
u[1] = MODEM_REGISTRATION_STATUS_DENIED;
break;
case 5:
u[1] = MODEM_REGISTRATION_STATUS_REGISTERED;
break;
case 4: /* unknown */
default:
u[0] = MODEM_REGISTRATION_MODE_UNKNOWN;
u[1] = MODEM_REGISTRATION_STATUS_UNKNOWN;
break;
}
event->registration.mode = u[0];
switch((event->registration.status = u[1]))
{
case MODEM_REGISTRATION_STATUS_REGISTERED:
memset(&request, 0, sizeof(request));
request.type = HAYES_REQUEST_GPRS_ATTACHED;
_hayes_request(modem, &request);
request.type = HAYES_REQUEST_OPERATOR;
_hayes_request(modem, &request);
request.type = HAYES_REQUEST_SIGNAL_LEVEL;
_hayes_request(modem, &request);
break;
default:
event->registration.signal = 0.0 / 0.0;
/* this is usually an unsollicited event */
modem->helper->event(modem->helper->modem, event);
break;
}
}
/* 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];
if(strcmp(answer, "VOICE") == 0)
; /* FIXME implement */
event->call.status = MODEM_CALL_STATUS_RINGING;
event->call.direction = MODEM_CALL_DIRECTION_INCOMING;
event->call.number = "";
/* this is always an unsollicited event */
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;
/* this is usually worth an event */
modem->helper->event(modem->helper->modem, event);
}
/* helpers */
/* is_figure */
static int _is_figure(int c)
{
if(c >= '0' && c <= '9')
return 1;
if(c == '*' || c == '+' || c == '#')
return 1;
if(c >= 'A' && c <= 'D')
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;
}