1986 lines
51 KiB
C
1986 lines
51 KiB
C
|
/*
|
||
|
Copyright (C) 2009 - 2010
|
||
|
|
||
|
Artem Makhutov <artem@makhutov.org>
|
||
|
http://www.makhutov.org
|
||
|
|
||
|
Dmitry Vagin <dmitry2004@yandex.ru>
|
||
|
|
||
|
bg <bg_one@mail.ru>
|
||
|
|
||
|
Copyright (C) 2020 Max von Buelow <max@m9x.de>
|
||
|
*/
|
||
|
#include "ast_config.h"
|
||
|
|
||
|
#include <asterisk/logger.h> /* ast_debug() */
|
||
|
#include <asterisk/pbx.h> /* ast_pbx_start() */
|
||
|
|
||
|
#include "ast_compat.h" /* asterisk compatibility fixes */
|
||
|
|
||
|
#include "at_response.h"
|
||
|
#include "mutils.h" /* STRLEN() */
|
||
|
#include "at_queue.h"
|
||
|
#include "chan_dongle.h"
|
||
|
#include "at_parse.h"
|
||
|
#include "char_conv.h"
|
||
|
#include "manager.h"
|
||
|
#include "channel.h" /* channel_queue_hangup() channel_queue_control() */
|
||
|
#include "smsdb.h"
|
||
|
#include "error.h"
|
||
|
|
||
|
#define CCWA_STATUS_NOT_ACTIVE 0
|
||
|
#define CCWA_STATUS_ACTIVE 1
|
||
|
|
||
|
#define CLCC_CALL_TYPE_VOICE 0
|
||
|
#define CLCC_CALL_TYPE_DATA 1
|
||
|
#define CLCC_CALL_TYPE_FAX 2
|
||
|
|
||
|
static const at_response_t at_responses_list[] = {
|
||
|
|
||
|
AT_RESPONSES_TABLE(AT_RES_AS_STRUCTLIST)
|
||
|
|
||
|
/* The hackish way to define the duplicated responses in the meantime */
|
||
|
#define DEF_STR(str) str,STRLEN(str)
|
||
|
{ RES_CNUM, "+CNUM",DEF_STR("ERROR+CNUM:") },
|
||
|
{ RES_ERROR,"ERROR",DEF_STR("COMMAND NOT SUPPORT\r") },
|
||
|
#undef DEF_STR
|
||
|
};
|
||
|
|
||
|
|
||
|
EXPORT_DEF const at_responses_t at_responses = { at_responses_list, 2, ITEMS_OF(at_responses_list), RES_MIN, RES_MAX};
|
||
|
|
||
|
/*!
|
||
|
* \brief Get the string representation of the given AT response
|
||
|
* \param res -- the response to process
|
||
|
* \return a string describing the given response
|
||
|
*/
|
||
|
|
||
|
EXPORT_DEF const char* at_res2str (at_res_t res)
|
||
|
{
|
||
|
if((int)res >= at_responses.name_first && (int)res <= at_responses.name_last)
|
||
|
return at_responses.responses[res - at_responses.name_first].name;
|
||
|
return "UNDEFINED";
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle OK response
|
||
|
* \param pvt -- pvt structure
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_ok (struct pvt* pvt, at_res_t res)
|
||
|
{
|
||
|
const at_queue_task_t * task = at_queue_head_task (pvt);
|
||
|
const at_queue_cmd_t * ecmd = at_queue_task_cmd(task);
|
||
|
|
||
|
if(!ecmd)
|
||
|
{
|
||
|
ast_log (LOG_ERROR, "[%s] Received unexpected 'OK'\n", PVT_ID(pvt));
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if(ecmd->res == RES_OK || ecmd->res == RES_CMGR)
|
||
|
{
|
||
|
switch (ecmd->cmd)
|
||
|
{
|
||
|
case CMD_AT:
|
||
|
case CMD_AT_Z:
|
||
|
case CMD_AT_E:
|
||
|
case CMD_AT_U2DIAG:
|
||
|
case CMD_AT_CGMI:
|
||
|
case CMD_AT_CGMM:
|
||
|
case CMD_AT_CGMR:
|
||
|
case CMD_AT_CMEE:
|
||
|
case CMD_AT_CGSN:
|
||
|
case CMD_AT_CIMI:
|
||
|
case CMD_AT_CPIN:
|
||
|
case CMD_AT_CCWA_SET:
|
||
|
case CMD_AT_CCWA_STATUS:
|
||
|
case CMD_AT_CHLD_2:
|
||
|
case CMD_AT_CHLD_3:
|
||
|
case CMD_AT_CSCA:
|
||
|
case CMD_AT_CLCC:
|
||
|
case CMD_AT_CLIR:
|
||
|
ast_debug (3, "[%s] %s sent successfully\n", PVT_ID(pvt), at_cmd2str (ecmd->cmd));
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_COPS_INIT:
|
||
|
ast_debug (1, "[%s] Operator select parameters set\n", PVT_ID(pvt));
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_CREG_INIT:
|
||
|
ast_debug (1, "[%s] registration info enabled\n", PVT_ID(pvt));
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_CREG:
|
||
|
ast_debug (1, "[%s] registration query sent\n", PVT_ID(pvt));
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_CNUM:
|
||
|
ast_debug (1, "[%s] Subscriber phone number query successed\n", PVT_ID(pvt));
|
||
|
break;
|
||
|
|
||
|
/* These two are expected to be called in order */
|
||
|
case CMD_AT_CVOICE:
|
||
|
ast_debug (1, "[%s] Dongle has voice support\n", PVT_ID(pvt));
|
||
|
pvt->has_voice = 1;
|
||
|
pvt->has_voice_quectel = 0;
|
||
|
break;
|
||
|
case CMD_AT_QPCMV:
|
||
|
ast_debug (1, "[%s] Dongle has Quectel voice support\n", PVT_ID(pvt));
|
||
|
pvt->has_voice = 1;
|
||
|
pvt->has_voice_quectel = 1;
|
||
|
break;
|
||
|
/*
|
||
|
case CMD_AT_CLIP:
|
||
|
ast_debug (1, "[%s] Calling line indication disabled\n", PVT_ID(pvt));
|
||
|
break;
|
||
|
*/
|
||
|
case CMD_AT_CSSN:
|
||
|
ast_debug (1, "[%s] Supplementary Service Notification enabled successful\n", PVT_ID(pvt));
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_CMGF:
|
||
|
ast_debug (1, "[%s] SMS operation mode set to PDU\n", PVT_ID(pvt));
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_CPMS:
|
||
|
ast_debug (1, "[%s] SMS storage location is established\n", PVT_ID(pvt));
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_CNMI:
|
||
|
ast_debug (1, "[%s] SMS new message indication enabled\n", PVT_ID(pvt));
|
||
|
ast_debug (1, "[%s] Dongle has sms support\n", PVT_ID(pvt));
|
||
|
|
||
|
pvt->has_sms = 1;
|
||
|
|
||
|
if (!pvt->initialized)
|
||
|
{
|
||
|
pvt->timeout = DATA_READ_TIMEOUT;
|
||
|
pvt->initialized = 1;
|
||
|
ast_verb (3, "[%s] Dongle initialized and ready\n", PVT_ID(pvt));
|
||
|
manager_event_device_status(PVT_ID(pvt), "Initialize");
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_D:
|
||
|
pvt->dialing = 1;
|
||
|
if (task->cpvt != &pvt->sys_chan) {
|
||
|
pvt->last_dialed_cpvt = task->cpvt;
|
||
|
}
|
||
|
/* fall through */
|
||
|
case CMD_AT_A:
|
||
|
case CMD_AT_CHLD_2x:
|
||
|
/* not work, ^CONN: appear before OK for CHLD_ANSWER
|
||
|
task->cpvt->answered = 1;
|
||
|
task->cpvt->needhangup = 1;
|
||
|
*/
|
||
|
CPVT_SET_FLAGS(task->cpvt, CALL_FLAG_NEED_HANGUP);
|
||
|
ast_debug (1, "[%s] %s sent successfully for call id %d\n", PVT_ID(pvt), at_cmd2str (ecmd->cmd), task->cpvt->call_idx);
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_CFUN:
|
||
|
/* in case of reset */
|
||
|
pvt->ring = 0;
|
||
|
pvt->dialing = 0;
|
||
|
pvt->cwaiting = 0;
|
||
|
break;
|
||
|
case CMD_AT_DDSETEX:
|
||
|
ast_debug (1, "[%s] %s sent successfully\n", PVT_ID(pvt), at_cmd2str (ecmd->cmd));
|
||
|
if (!pvt->initialized)
|
||
|
{
|
||
|
pvt->timeout = DATA_READ_TIMEOUT;
|
||
|
pvt->initialized = 1;
|
||
|
ast_verb (3, "[%s] Dongle initialized and ready\n", PVT_ID(pvt));
|
||
|
manager_event_device_status(PVT_ID(pvt), "Initialize");
|
||
|
}
|
||
|
break;
|
||
|
case CMD_AT_CHUP:
|
||
|
case CMD_AT_CHLD_1x:
|
||
|
CPVT_RESET_FLAGS(task->cpvt, CALL_FLAG_NEED_HANGUP);
|
||
|
ast_debug (1, "[%s] Successful hangup for call idx %d\n", PVT_ID(pvt), task->cpvt->call_idx);
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_CMGS:
|
||
|
ast_debug (1, "[%s] Sending sms message in progress\n", PVT_ID(pvt));
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_SMSTEXT:
|
||
|
pvt->outgoing_sms = 0;
|
||
|
pvt_try_restate(pvt);
|
||
|
|
||
|
/* TODO: move to +CMGS: handler */
|
||
|
ast_verb (3, "[%s] Successfully sent SMS message %p\n", PVT_ID(pvt), task);
|
||
|
ast_log (LOG_NOTICE, "[%s] Successfully sent SMS message %p\n", PVT_ID(pvt), task);
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_DTMF:
|
||
|
ast_debug (1, "[%s] DTMF sent successfully for call idx %d\n", PVT_ID(pvt), task->cpvt->call_idx);
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_CUSD:
|
||
|
ast_verb (3, "[%s] Successfully sent USSD %p\n", PVT_ID(pvt), task);
|
||
|
ast_log (LOG_NOTICE, "[%s] Successfully sent USSD %p\n", PVT_ID(pvt), task);
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_COPS:
|
||
|
ast_debug (1, "[%s] Provider query successfully\n", PVT_ID(pvt));
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_CMGR:
|
||
|
ast_debug (1, "[%s] SMS message see later\n", PVT_ID(pvt));
|
||
|
at_retrieve_next_sms(&pvt->sys_chan, at_cmd_suppress_error_mode(ecmd->flags));
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_CMGD:
|
||
|
ast_debug (1, "[%s] SMS message deleted successfully\n", PVT_ID(pvt));
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_CSQ:
|
||
|
ast_debug (1, "[%s] Got signal strength result\n", PVT_ID(pvt));
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_CLVL:
|
||
|
pvt->volume_sync_step++;
|
||
|
if(pvt->volume_sync_step == VOLUME_SYNC_DONE)
|
||
|
{
|
||
|
ast_debug (1, "[%s] Volume level synchronized\n", PVT_ID(pvt));
|
||
|
pvt->volume_sync_step = VOLUME_SYNC_BEGIN;
|
||
|
}
|
||
|
break;
|
||
|
case CMD_USER:
|
||
|
break;
|
||
|
default:
|
||
|
ast_log (LOG_ERROR, "[%s] Received 'OK' for unhandled command '%s'\n", PVT_ID(pvt), at_cmd2str (ecmd->cmd));
|
||
|
break;
|
||
|
}
|
||
|
at_queue_handle_result (pvt, res);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ast_log (LOG_ERROR, "[%s] Received 'OK' when expecting '%s', ignoring\n", PVT_ID(pvt), at_res2str (ecmd->res));
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void log_cmd_response_error(const struct pvt* pvt, const at_queue_cmd_t *ecmd, const char *fmt, ...)
|
||
|
{
|
||
|
va_list ap;
|
||
|
char tempbuff[512];
|
||
|
|
||
|
if (at_cmd_suppress_error_mode(ecmd->flags) == SUPPRESS_ERROR_ENABLED) {
|
||
|
if (DEBUG_ATLEAST(1)) {
|
||
|
ast_log(AST_LOG_DEBUG, "[%s] Command response error suppressed:\n", PVT_ID(pvt));
|
||
|
va_start(ap, fmt);
|
||
|
vsnprintf(tempbuff, 512, fmt, ap);
|
||
|
va_end(ap);
|
||
|
ast_log(AST_LOG_DEBUG, "%s", tempbuff);
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
va_start(ap, fmt);
|
||
|
vsnprintf(tempbuff, 512, fmt, ap);
|
||
|
ast_log(LOG_ERROR, "%s", tempbuff);
|
||
|
va_end(ap);
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle ERROR response
|
||
|
* \param pvt -- pvt structure
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_error (struct pvt* pvt, at_res_t res)
|
||
|
{
|
||
|
const at_queue_task_t * task = at_queue_head_task(pvt);
|
||
|
const at_queue_cmd_t * ecmd = at_queue_task_cmd(task);
|
||
|
|
||
|
if (ecmd && (ecmd->res == RES_OK || ecmd->res == RES_CMGR || ecmd->res == RES_SMS_PROMPT))
|
||
|
{
|
||
|
switch (ecmd->cmd)
|
||
|
{
|
||
|
/* critical errors */
|
||
|
case CMD_AT:
|
||
|
case CMD_AT_Z:
|
||
|
case CMD_AT_E:
|
||
|
case CMD_AT_CLCC:
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Command '%s' failed\n", PVT_ID(pvt), at_cmd2str (ecmd->cmd));
|
||
|
/* mean disconnected from device */
|
||
|
goto e_return;
|
||
|
|
||
|
/* not critical errors */
|
||
|
case CMD_AT_U2DIAG:
|
||
|
case CMD_AT_CCWA_SET:
|
||
|
case CMD_AT_CCWA_STATUS:
|
||
|
case CMD_AT_CNUM:
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Command '%s' failed\n", PVT_ID(pvt), at_cmd2str (ecmd->cmd));
|
||
|
/* mean ignore error */
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_CGMI:
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Getting manufacturer info failed\n", PVT_ID(pvt));
|
||
|
goto e_return;
|
||
|
|
||
|
case CMD_AT_CGMM:
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Getting model info failed\n", PVT_ID(pvt));
|
||
|
goto e_return;
|
||
|
|
||
|
case CMD_AT_CGMR:
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Getting firmware info failed\n", PVT_ID(pvt));
|
||
|
goto e_return;
|
||
|
|
||
|
case CMD_AT_CMEE:
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Setting error verbosity level failed\n", PVT_ID(pvt));
|
||
|
goto e_return;
|
||
|
|
||
|
case CMD_AT_CGSN:
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Getting IMEI number failed\n", PVT_ID(pvt));
|
||
|
goto e_return;
|
||
|
|
||
|
case CMD_AT_CIMI:
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Getting IMSI number failed\n", PVT_ID(pvt));
|
||
|
goto e_return;
|
||
|
|
||
|
case CMD_AT_CPIN:
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Error checking PIN state\n", PVT_ID(pvt));
|
||
|
goto e_return;
|
||
|
|
||
|
case CMD_AT_COPS_INIT:
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Error setting operator select parameters\n", PVT_ID(pvt));
|
||
|
goto e_return;
|
||
|
|
||
|
case CMD_AT_CREG_INIT:
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Error enabling registration info\n", PVT_ID(pvt));
|
||
|
goto e_return;
|
||
|
|
||
|
case CMD_AT_CREG:
|
||
|
ast_debug (1, "[%s] Error getting registration info\n", PVT_ID(pvt));
|
||
|
break;
|
||
|
|
||
|
/* These two are expected to be called in order */
|
||
|
case CMD_AT_CVOICE:
|
||
|
ast_debug(1, "[%s] Dongle has NO (CVOICE) voice support\n", PVT_ID(pvt));
|
||
|
pvt->has_voice = 0;
|
||
|
break;
|
||
|
case CMD_AT_QPCMV:
|
||
|
ast_debug(1, "[%s] Dongle has NO (QPCMV) voice support\n", PVT_ID(pvt));
|
||
|
pvt->has_voice_quectel = 0;
|
||
|
|
||
|
if (pvt->has_voice == 0) {
|
||
|
ast_log(LOG_WARNING, "[%s] Dongle has NO voice support\n", PVT_ID(pvt));
|
||
|
}
|
||
|
break;
|
||
|
/*
|
||
|
case CMD_AT_CLIP:
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Error enabling calling line indication\n", PVT_ID(pvt));
|
||
|
goto e_return;
|
||
|
*/
|
||
|
case CMD_AT_CSSN:
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Error Supplementary Service Notification activation failed\n", PVT_ID(pvt));
|
||
|
goto e_return;
|
||
|
|
||
|
case CMD_AT_CMGF:
|
||
|
case CMD_AT_CPMS:
|
||
|
case CMD_AT_CNMI:
|
||
|
ast_debug (1, "[%s] Command '%s' failed\n", PVT_ID(pvt), at_cmd2str (ecmd->cmd));
|
||
|
ast_debug (1, "[%s] No SMS support\n", PVT_ID(pvt));
|
||
|
|
||
|
pvt->has_sms = 0;
|
||
|
|
||
|
if (!pvt->initialized)
|
||
|
{
|
||
|
if (pvt->has_voice)
|
||
|
{
|
||
|
/* continue initialization in other job at cmd CMD_AT_CSQ */
|
||
|
if (at_enqueue_initialization(task->cpvt, CMD_AT_CSQ))
|
||
|
{
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Error querying signal strength\n", PVT_ID(pvt));
|
||
|
goto e_return;
|
||
|
}
|
||
|
|
||
|
pvt->timeout = DATA_READ_TIMEOUT;
|
||
|
pvt->initialized = 1;
|
||
|
/* FIXME: say 'initialized and ready' but disconnect */
|
||
|
// ast_verb (3, "[%s] Dongle initialized and ready\n", PVT_ID(pvt));
|
||
|
}
|
||
|
goto e_return;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_A:
|
||
|
case CMD_AT_CHLD_2x:
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Answer failed for call idx %d\n", PVT_ID(pvt), task->cpvt->call_idx);
|
||
|
queue_hangup (task->cpvt->channel, 0);
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_CHLD_3:
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Can't begin conference call idx %d\n", PVT_ID(pvt), task->cpvt->call_idx);
|
||
|
queue_hangup(task->cpvt->channel, 0);
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_CLIR:
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Setting CLIR failed\n", PVT_ID(pvt));
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_CHLD_2:
|
||
|
if (!CPVT_TEST_FLAG(task->cpvt, CALL_FLAG_HOLD_OTHER) ||
|
||
|
task->cpvt->state != CALL_STATE_INIT) {
|
||
|
break;
|
||
|
}
|
||
|
/* fall through */
|
||
|
case CMD_AT_D:
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Dial failed\n", PVT_ID(pvt));
|
||
|
queue_control_channel (task->cpvt, AST_CONTROL_CONGESTION);
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_DDSETEX:
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] %s (setup voice) failed\n", PVT_ID(pvt), at_cmd2str(ecmd->cmd));
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_CHUP:
|
||
|
case CMD_AT_CHLD_1x:
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Error sending hangup for call idx %d\n", PVT_ID(pvt), task->cpvt->call_idx);
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_CMGR:
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Error reading SMS message\n", PVT_ID(pvt));
|
||
|
at_retrieve_next_sms(&pvt->sys_chan, at_cmd_suppress_error_mode(ecmd->flags));
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_CMGD:
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Error deleting SMS message\n", PVT_ID(pvt));
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_CMGS:
|
||
|
case CMD_AT_SMSTEXT:
|
||
|
pvt->outgoing_sms = 0;
|
||
|
pvt_try_restate(pvt);
|
||
|
|
||
|
{
|
||
|
char payload[SMSDB_PAYLOAD_MAX_LEN];
|
||
|
char dst[SMSDB_DST_MAX_LEN];
|
||
|
ssize_t payload_len = smsdb_outgoing_clear(task->uid, dst, payload);
|
||
|
if (payload_len >= 0) {
|
||
|
ast_verb (3, "[%s] Error payload: %.*s\n", PVT_ID(pvt), (int) payload_len, payload);
|
||
|
channel_var_t vars[] =
|
||
|
{
|
||
|
{ "SMS_REPORT_PAYLOAD", payload },
|
||
|
{ "SMS_REPORT_TS", "" },
|
||
|
{ "SMS_REPORT_DT", "" },
|
||
|
{ "SMS_REPORT_SUCCESS", "0" },
|
||
|
{ "SMS_REPORT_TYPE", "i" },
|
||
|
{ "SMS_REPORT", "" },
|
||
|
{ NULL, NULL },
|
||
|
};
|
||
|
start_local_channel(pvt, "report", dst, vars);
|
||
|
manager_event_report(PVT_ID(pvt), payload, payload_len, "", "", 0, 0, "");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ast_verb (3, "[%s] Error sending SMS message %p\n", PVT_ID(pvt), task);
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Error sending SMS message %p %s\n", PVT_ID(pvt), task, at_cmd2str (ecmd->cmd));
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_DTMF:
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Error sending DTMF\n", PVT_ID(pvt));
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_COPS:
|
||
|
ast_debug (1, "[%s] Could not get provider name\n", PVT_ID(pvt));
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_CLVL:
|
||
|
ast_debug (1, "[%s] Audio level synchronization failed at step %d/%d\n", PVT_ID(pvt), pvt->volume_sync_step, VOLUME_SYNC_DONE-1);
|
||
|
pvt->volume_sync_step = VOLUME_SYNC_BEGIN;
|
||
|
break;
|
||
|
|
||
|
case CMD_AT_CUSD:
|
||
|
ast_verb (3, "[%s] Error sending USSD %p\n", PVT_ID(pvt), task);
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Error sending USSD %p\n", PVT_ID(pvt), task);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Received 'ERROR' for unhandled command '%s'\n", PVT_ID(pvt), at_cmd2str (ecmd->cmd));
|
||
|
break;
|
||
|
}
|
||
|
at_queue_handle_result (pvt, res);
|
||
|
}
|
||
|
else if (ecmd)
|
||
|
{
|
||
|
switch (ecmd->cmd) {
|
||
|
case CMD_AT_CMGR:
|
||
|
at_retrieve_next_sms(&pvt->sys_chan, at_cmd_suppress_error_mode(ecmd->flags));
|
||
|
break;
|
||
|
default:
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Received 'ERROR' when expecting '%s', ignoring\n", PVT_ID(pvt), at_res2str (ecmd->res));
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
log_cmd_response_error(pvt, ecmd, "[%s] Received unexpected 'ERROR'\n", PVT_ID(pvt));
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
e_return:
|
||
|
at_queue_handle_result (pvt, res);
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle ^RSSI response Here we get the signal strength.
|
||
|
* \param pvt -- pvt structure
|
||
|
* \param str -- string containing response (null terminated)
|
||
|
* \param len -- string lenght
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_rssi (struct pvt* pvt, const char* str)
|
||
|
{
|
||
|
int rssi = at_parse_rssi (str);
|
||
|
|
||
|
if (rssi == -1)
|
||
|
{
|
||
|
ast_debug (2, "[%s] Error parsing RSSI event '%s'\n", PVT_ID(pvt), str);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
pvt->rssi = rssi;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle ^MODE response Here we get the link mode (GSM, UMTS, EDGE...).
|
||
|
* \param pvt -- pvt structure
|
||
|
* \param str -- string containing response (null terminated)
|
||
|
* \param len -- string lenght
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_mode (struct pvt* pvt, char* str, size_t len)
|
||
|
{
|
||
|
int mode;
|
||
|
int submode;
|
||
|
|
||
|
int rv = at_parse_mode (str, &mode, &submode);
|
||
|
if(rv)
|
||
|
{
|
||
|
ast_debug (2, "[%s] Error parsing MODE event '%.*s'\n", PVT_ID(pvt), (int) len, str);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pvt->linkmode = mode;
|
||
|
pvt->linksubmode = submode;
|
||
|
}
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
static void request_clcc(struct pvt* pvt)
|
||
|
{
|
||
|
if (at_enqueue_clcc(&pvt->sys_chan))
|
||
|
{
|
||
|
ast_log(LOG_ERROR, "[%s] Error enqueue List Current Calls request\n", PVT_ID(pvt));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle ^ORIG response
|
||
|
* \param pvt -- pvt structure
|
||
|
* \param str -- string containing response (null terminated)
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_orig (struct pvt* pvt, const char* str)
|
||
|
{
|
||
|
int call_index;
|
||
|
int call_type;
|
||
|
struct cpvt * cpvt = pvt->last_dialed_cpvt;
|
||
|
|
||
|
pvt->last_dialed_cpvt = NULL;
|
||
|
if(!cpvt)
|
||
|
{
|
||
|
ast_log (LOG_ERROR, "[%s] ^ORIG '%s' for unknown ATD\n", PVT_ID(pvt), str);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* parse ORIG info in the following format:
|
||
|
* ^ORIG:<call_index>,<call_type>
|
||
|
*/
|
||
|
|
||
|
if (sscanf (str, "^ORIG:%d,%d", &call_index, &call_type) != 2)
|
||
|
{
|
||
|
ast_log (LOG_ERROR, "[%s] Error parsing ORIG event '%s'\n", PVT_ID(pvt), str);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
ast_debug (1, "[%s] ORIG Received call_index: %d call_type %d\n", PVT_ID(pvt), call_index, call_type);
|
||
|
|
||
|
if (call_type == CLCC_CALL_TYPE_VOICE)
|
||
|
{
|
||
|
|
||
|
if(call_index >= MIN_CALL_IDX && call_index <= MAX_CALL_IDX)
|
||
|
{
|
||
|
/* set REAL call idx */
|
||
|
/* WARNING if direction mismatch
|
||
|
cpvt->dir = CALL_DIR_OUTGOING;
|
||
|
*/
|
||
|
cpvt->call_idx = call_index;
|
||
|
change_channel_state(cpvt, CALL_STATE_DIALING, 0);
|
||
|
/* TODO: move to CONN ? */
|
||
|
if(pvt->volume_sync_step == VOLUME_SYNC_BEGIN)
|
||
|
{
|
||
|
pvt->volume_sync_step = VOLUME_SYNC_BEGIN;
|
||
|
if (at_enqueue_volsync(cpvt))
|
||
|
{
|
||
|
ast_log (LOG_ERROR, "[%s] Error synchronize audio level\n", PVT_ID(pvt));
|
||
|
}
|
||
|
else
|
||
|
pvt->volume_sync_step++;
|
||
|
}
|
||
|
|
||
|
request_clcc(pvt);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* FIXME: and reset call if no-voice, bad call_index !
|
||
|
*/
|
||
|
ast_log (LOG_ERROR, "[%s] ORIG event for non-voice call type '%d' index %d\n", PVT_ID(pvt), call_type, call_index);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
#if 0
|
||
|
/*!
|
||
|
* \brief Handle ^CONF response
|
||
|
* \param pvt -- pvt structure
|
||
|
* \param str -- string containing response (null terminated)
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_conf (struct pvt* pvt, const char* str)
|
||
|
{
|
||
|
int call_index;
|
||
|
struct cpvt * cpvt;
|
||
|
|
||
|
/*
|
||
|
* parse CONF info in the following format:
|
||
|
* ^CONF: <call_index>
|
||
|
*/
|
||
|
|
||
|
if (sscanf (str, "^CONF:%d", &call_index) != 1)
|
||
|
{
|
||
|
ast_log (LOG_ERROR, "[%s] Error parsing CONF event '%s'\n", PVT_ID(pvt), str);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
ast_debug (1, "[%s] CONF Received call_index %d\n", PVT_ID(pvt), call_index);
|
||
|
|
||
|
cpvt = pvt_find_cpvt(pvt, call_index);
|
||
|
if(cpvt)
|
||
|
{
|
||
|
channel_change_state(cpvt, CALL_STATE_ALERTING, 0);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
#endif /* 0 */
|
||
|
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle ^CEND response
|
||
|
* \param pvt -- pvt structure
|
||
|
* \param str -- string containing response (null terminated)
|
||
|
* \param len -- string lenght
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_cend (struct pvt * pvt, const char* str)
|
||
|
{
|
||
|
int call_index = 0;
|
||
|
int duration = 0;
|
||
|
int end_status = 0;
|
||
|
int cc_cause = 0;
|
||
|
struct cpvt * cpvt;
|
||
|
|
||
|
request_clcc(pvt);
|
||
|
|
||
|
/*
|
||
|
* parse CEND info in the following format:
|
||
|
* ^CEND:<call_index>,<duration>,<end_status>[,<cc_cause>]
|
||
|
*/
|
||
|
|
||
|
if (sscanf (str, "^CEND:%d,%d,%d,%d", &call_index, &duration, &end_status, &cc_cause) != 4)
|
||
|
{
|
||
|
ast_debug (1, "[%s] Could not parse all CEND parameters\n", PVT_ID(pvt));
|
||
|
}
|
||
|
|
||
|
ast_debug (1, "[%s] CEND: call_index %d duration %d end_status %d cc_cause %d Line disconnected\n"
|
||
|
, PVT_ID(pvt), call_index, duration, end_status, cc_cause);
|
||
|
|
||
|
cpvt = pvt_find_cpvt(pvt, call_index);
|
||
|
if (cpvt)
|
||
|
{
|
||
|
CPVT_RESET_FLAGS(cpvt, CALL_FLAG_NEED_HANGUP);
|
||
|
PVT_STAT(pvt, calls_duration[cpvt->dir]) += duration;
|
||
|
change_channel_state(cpvt, CALL_STATE_RELEASED, cc_cause);
|
||
|
manager_event_cend(PVT_ID(pvt), call_index, duration, end_status, cc_cause);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// ast_log (LOG_ERROR, "[%s] CEND event for unknown call idx '%d'\n", PVT_ID(pvt), call_index);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle +CSCA response
|
||
|
* \param pvt -- pvt structure
|
||
|
* \param str -- string containing response (null terminated)
|
||
|
* \param len -- string lenght
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
static int at_response_csca (struct pvt* pvt, char* str)
|
||
|
{
|
||
|
char * csca;
|
||
|
|
||
|
if(at_parse_csca(str, &csca))
|
||
|
{
|
||
|
ast_debug (1, "[%s] Could not parse CSCA response '%s'\n", PVT_ID(pvt), str);
|
||
|
return -1;
|
||
|
}
|
||
|
ast_copy_string (pvt->sms_scenter, csca, sizeof (pvt->sms_scenter));
|
||
|
|
||
|
ast_debug (1, "[%s] CSCA: %s\n", PVT_ID(pvt), pvt->sms_scenter);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle ^CONN response
|
||
|
* \param pvt -- pvt structure
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_conn (struct pvt* pvt, const char* str)
|
||
|
{
|
||
|
int call_index;
|
||
|
int call_type;
|
||
|
struct cpvt * cpvt;
|
||
|
|
||
|
pvt->ring = 0;
|
||
|
pvt->dialing = 0;
|
||
|
pvt->cwaiting = 0;
|
||
|
|
||
|
request_clcc(pvt);
|
||
|
|
||
|
/*
|
||
|
* parse CONN info in the following format:
|
||
|
* ^CONN:<call_index>,<call_type>
|
||
|
*/
|
||
|
if (sscanf (str, "^CONN:%d,%d", &call_index, &call_type) != 2)
|
||
|
{
|
||
|
ast_log (LOG_ERROR, "[%s] Error parsing CONN event '%s'\n", PVT_ID(pvt), str);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
ast_debug (1, "[%s] CONN Received call_index %d call_type %d\n", PVT_ID(pvt), call_index, call_type);
|
||
|
|
||
|
if (call_type == CLCC_CALL_TYPE_VOICE)
|
||
|
{
|
||
|
cpvt = pvt_find_cpvt(pvt, call_index);
|
||
|
if(cpvt)
|
||
|
{
|
||
|
/* FIXME: delay until CLCC handle?
|
||
|
*/
|
||
|
PVT_STAT(pvt, calls_answered[cpvt->dir]) ++;
|
||
|
change_channel_state(cpvt, CALL_STATE_ACTIVE, 0);
|
||
|
if(CPVT_TEST_FLAG(cpvt, CALL_FLAG_CONFERENCE))
|
||
|
at_enqueue_conference(cpvt);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
at_enqueue_hangup(&pvt->sys_chan, call_index);
|
||
|
ast_log (LOG_ERROR, "[%s] answered incoming call with not exists call idx %d, hanging up!\n", PVT_ID(pvt), call_index);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
ast_log (LOG_ERROR, "[%s] answered not voice incoming call type '%d' idx %d, skipped\n", PVT_ID(pvt), call_type, call_index);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
static int start_pbx(struct pvt* pvt, const char * number, int call_idx, call_state_t state)
|
||
|
{
|
||
|
struct cpvt* cpvt;
|
||
|
|
||
|
/* TODO: pass also Subscriber number or other DID info for exten */
|
||
|
#if ASTERISK_VERSION_NUM >= 120000 /* 12+ */
|
||
|
struct ast_channel * channel = new_channel(
|
||
|
pvt, AST_STATE_RING, number, call_idx, CALL_DIR_INCOMING, state,
|
||
|
pvt->has_subscriber_number ? pvt->subscriber_number : CONF_SHARED(pvt, exten),
|
||
|
NULL, NULL);
|
||
|
#else /* 12- */
|
||
|
struct ast_channel * channel = new_channel(
|
||
|
pvt, AST_STATE_RING, number, call_idx, CALL_DIR_INCOMING, state,
|
||
|
pvt->has_subscriber_number ? pvt->subscriber_number : CONF_SHARED(pvt, exten),
|
||
|
NULL);
|
||
|
#endif /* ^12- */
|
||
|
|
||
|
if (!channel)
|
||
|
{
|
||
|
ast_log (LOG_ERROR, "[%s] Unable to allocate channel for incoming call\n", PVT_ID(pvt));
|
||
|
|
||
|
if (at_enqueue_hangup(&pvt->sys_chan, call_idx))
|
||
|
{
|
||
|
ast_log (LOG_ERROR, "[%s] Error sending AT+CHUP command\n", PVT_ID(pvt));
|
||
|
}
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
cpvt = ast_channel_tech_pvt(channel);
|
||
|
// FIXME: not execute if channel_new() failed
|
||
|
CPVT_SET_FLAGS(cpvt, CALL_FLAG_NEED_HANGUP);
|
||
|
|
||
|
/* ast_pbx_start() usually failed if asterisk.conf minmemfree
|
||
|
* set too low, try drop buffer cache
|
||
|
* sync && echo 3 >/proc/sys/vm/drop_caches
|
||
|
*/
|
||
|
if (ast_pbx_start (channel))
|
||
|
{
|
||
|
ast_channel_tech_pvt_set(channel, NULL);
|
||
|
cpvt_free(cpvt);
|
||
|
|
||
|
ast_hangup (channel);
|
||
|
ast_log (LOG_ERROR, "[%s] Unable to start pbx on incoming call\n", PVT_ID(pvt));
|
||
|
// TODO: count fails and reset incoming when count reach limit ?
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle +CLCC response
|
||
|
* \param pvt -- pvt structure
|
||
|
* \param str -- string containing response (null terminated)
|
||
|
* \param len -- string lenght
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_clcc (struct pvt* pvt, char* str)
|
||
|
{
|
||
|
struct cpvt * cpvt;
|
||
|
unsigned call_idx, dir, state, mode, mpty, type;
|
||
|
unsigned all = 0;
|
||
|
unsigned held = 0;
|
||
|
char * number;
|
||
|
char *p;
|
||
|
|
||
|
if (pvt->initialized)
|
||
|
{
|
||
|
/* I think man is good until he proves the reverse */
|
||
|
AST_LIST_TRAVERSE(&pvt->chans, cpvt, entry)
|
||
|
{
|
||
|
CPVT_RESET_FLAGS(cpvt, CALL_FLAG_ALIVE);
|
||
|
}
|
||
|
|
||
|
for(;;)
|
||
|
{
|
||
|
p = strchr(str, '\r');
|
||
|
if(at_parse_clcc(str, &call_idx, &dir, &state, &mode, &mpty, &number, &type) == 0)
|
||
|
{
|
||
|
ast_debug (3, "[%s] CLCC callidx %u dir %u state %u mode %u mpty %u number %s type %u\n", PVT_ID(pvt), call_idx, dir, state, mode, mpty, number, type);
|
||
|
if(mode == CLCC_CALL_TYPE_VOICE && state <= CALL_STATE_WAITING)
|
||
|
{
|
||
|
cpvt = pvt_find_cpvt(pvt, call_idx);
|
||
|
if(cpvt)
|
||
|
{
|
||
|
/* cpvt alive */
|
||
|
CPVT_SET_FLAGS(cpvt, CALL_FLAG_ALIVE);
|
||
|
if(dir == cpvt->dir)
|
||
|
{
|
||
|
if(mpty)
|
||
|
CPVT_SET_FLAGS(cpvt, CALL_FLAG_MULTIPARTY);
|
||
|
else
|
||
|
CPVT_RESET_FLAGS(cpvt, CALL_FLAG_MULTIPARTY);
|
||
|
if(dir == CALL_DIR_INCOMING && (state == CALL_STATE_INCOMING || state == CALL_STATE_WAITING))
|
||
|
{
|
||
|
if(cpvt->channel)
|
||
|
{
|
||
|
/* FIXME: unprotected channel access */
|
||
|
int rings = ast_channel_rings(cpvt->channel);
|
||
|
rings += pvt->rings;
|
||
|
ast_channel_rings_set(cpvt->channel, rings);
|
||
|
pvt->rings = 0;
|
||
|
}
|
||
|
}
|
||
|
if(state != cpvt->state)
|
||
|
{
|
||
|
change_channel_state(cpvt, state, 0);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ast_log (LOG_ERROR, "[%s] CLCC call idx %d direction mismatch %d/%d\n", PVT_ID(pvt), cpvt->call_idx, dir, cpvt->dir);
|
||
|
}
|
||
|
}
|
||
|
else if(dir == CALL_DIR_INCOMING && (state == CALL_STATE_INCOMING || state == CALL_STATE_WAITING))
|
||
|
{
|
||
|
if(state == CALL_STATE_INCOMING)
|
||
|
PVT_STAT(pvt, in_calls) ++;
|
||
|
else
|
||
|
PVT_STAT(pvt, cw_calls) ++;
|
||
|
if(pvt_enabled(pvt))
|
||
|
{
|
||
|
/* TODO: give dialplan level user tool for checking device is voice enabled or not */
|
||
|
if(start_pbx(pvt, number, call_idx, state) == 0)
|
||
|
{
|
||
|
PVT_STAT(pvt, in_calls_handled) ++;
|
||
|
if(!pvt->has_voice)
|
||
|
ast_log (LOG_WARNING, "[%s] pbx started for device not voice capable\n", PVT_ID(pvt));
|
||
|
}
|
||
|
else
|
||
|
PVT_STAT(pvt, in_pbx_fails) ++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
all++;
|
||
|
switch(state)
|
||
|
{
|
||
|
case CALL_STATE_WAITING:
|
||
|
pvt->cwaiting = 1;
|
||
|
pvt->ring = 0;
|
||
|
pvt->dialing = 0;
|
||
|
break;
|
||
|
|
||
|
case CALL_STATE_ONHOLD:
|
||
|
held++;
|
||
|
break;
|
||
|
|
||
|
case CALL_STATE_DIALING:
|
||
|
case CALL_STATE_ALERTING:
|
||
|
pvt->dialing = 1;
|
||
|
pvt->cwaiting = 0;
|
||
|
pvt->ring = 0;
|
||
|
break;
|
||
|
|
||
|
case CALL_STATE_INCOMING:
|
||
|
pvt->ring = 1;
|
||
|
pvt->dialing = 0;
|
||
|
pvt->cwaiting = 0;
|
||
|
break;
|
||
|
default:;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ast_log (LOG_ERROR, "[%s] can't parse CLCC line '%s'\n", PVT_ID(pvt), str);
|
||
|
}
|
||
|
if(p)
|
||
|
{
|
||
|
++p;
|
||
|
if(p[0] == '\n')
|
||
|
++p;
|
||
|
if(p[0])
|
||
|
{
|
||
|
str = p;
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
/* or -1 ? */
|
||
|
return 0;
|
||
|
}
|
||
|
/* unhold first held call */
|
||
|
if(all == held)
|
||
|
{
|
||
|
/* HW BUG 2: when no active call exists not way to enable voice again on activated from hold call
|
||
|
call will be activated but no voice
|
||
|
*/
|
||
|
ast_debug (1, "[%s] all %u call held, try activate some\n", PVT_ID(pvt), all);
|
||
|
if (at_enqueue_flip_hold(&pvt->sys_chan))
|
||
|
{
|
||
|
ast_log (LOG_ERROR, "[%s] can't flip active and hold/waiting calls \n", PVT_ID(pvt));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* dead cpvt only here */
|
||
|
AST_LIST_TRAVERSE(&pvt->chans, cpvt, entry)
|
||
|
{
|
||
|
if(!CPVT_TEST_FLAG(cpvt, CALL_FLAG_ALIVE))
|
||
|
change_channel_state(cpvt, CALL_STATE_RELEASED, 0);
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle +CCWA response
|
||
|
* \param pvt -- pvt structure
|
||
|
* \param str -- string containing response (null terminated)
|
||
|
* \param len -- string lenght
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_ccwa(struct pvt* pvt, char* str)
|
||
|
{
|
||
|
int status, n;
|
||
|
unsigned class;
|
||
|
|
||
|
/*
|
||
|
* CCWA may be in form:
|
||
|
* in response of AT+CCWA=?
|
||
|
* +CCWA: (0,1)
|
||
|
* in response of AT+CCWA=?
|
||
|
* +CCWA: <n>
|
||
|
* in response of "AT+CCWA=[<n>[,<mode>[,<class>]]]"
|
||
|
* +CCWA: <status>,<class1>
|
||
|
* unsolicited result code
|
||
|
* +CCWA: <number>,<type>,<class>,[<alpha>][,<CLI validity>[,<subaddr>,<satype>[,<priority>]]]
|
||
|
*
|
||
|
*/
|
||
|
if (sscanf(str, "+CCWA: (%u-%u)", &status, &class) == 2)
|
||
|
return 0;
|
||
|
|
||
|
n = sscanf (str, "+CCWA:%d,%d", &status, &class);
|
||
|
if(n == 1)
|
||
|
return 0;
|
||
|
else if (n == 2)
|
||
|
{
|
||
|
if ((class & CCWA_CLASS_VOICE) && (status == CCWA_STATUS_NOT_ACTIVE || status == CCWA_STATUS_ACTIVE))
|
||
|
{
|
||
|
pvt->has_call_waiting = status == CCWA_STATUS_ACTIVE ? 1 : 0;
|
||
|
ast_log (LOG_NOTICE, "Call waiting is %s on device %s\n", status ? "enabled" : "disabled", PVT_ID(pvt));
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (pvt->initialized)
|
||
|
{
|
||
|
// if (sscanf (str, "+CCWA: \"%*[+0-9*#ABCabc]\",%*d,%d", &class) == 1)
|
||
|
if (at_parse_ccwa(str, &class) == 0)
|
||
|
{
|
||
|
// if (CONF_SHARED(pvt, callwaiting) != CALL_WAITING_DISALLOWED && class == CCWA_CLASS_VOICE)
|
||
|
if (class == CCWA_CLASS_VOICE)
|
||
|
{
|
||
|
pvt->rings++;
|
||
|
pvt->cwaiting = 1;
|
||
|
request_clcc(pvt);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
ast_log (LOG_ERROR, "[%s] can't parse CCWA line '%s'\n", PVT_ID(pvt), str);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle RING response
|
||
|
* \param pvt -- pvt structure
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_ring (struct pvt* pvt)
|
||
|
{
|
||
|
if (pvt->initialized)
|
||
|
{
|
||
|
pvt->ring = 1;
|
||
|
pvt->dialing = 0;
|
||
|
pvt->cwaiting = 0;
|
||
|
|
||
|
pvt->rings++;
|
||
|
|
||
|
request_clcc(pvt);
|
||
|
|
||
|
/* We only want to syncronize volume on the first ring and if no channels yes */
|
||
|
if (pvt->volume_sync_step == VOLUME_SYNC_BEGIN && PVT_NO_CHANS(pvt))
|
||
|
{
|
||
|
if (at_enqueue_volsync(&pvt->sys_chan))
|
||
|
{
|
||
|
ast_log (LOG_ERROR, "[%s] Error synchronize audio level\n", PVT_ID(pvt));
|
||
|
}
|
||
|
else
|
||
|
pvt->volume_sync_step++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Poll for SMS messages
|
||
|
* \param pvt -- pvt structure
|
||
|
* \retval 0 success
|
||
|
* \retval -1 failure
|
||
|
*/
|
||
|
int
|
||
|
at_poll_sms (struct pvt *pvt)
|
||
|
{
|
||
|
/* poll all SMSs stored in device */
|
||
|
if (CONF_SHARED(pvt, disablesms) == 0)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i != SMS_INDEX_MAX; i++)
|
||
|
{
|
||
|
if (at_enqueue_retrieve_sms(&pvt->sys_chan, i, SUPPRESS_ERROR_ENABLED))
|
||
|
{
|
||
|
ast_log (LOG_ERROR, "[%s] Error sending CMGR to retrieve SMS message #%d\n", PVT_ID(pvt), i);
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle +CMTI response
|
||
|
* \param pvt -- pvt structure
|
||
|
* \param str -- string containing response (null terminated)
|
||
|
* \param len -- string lenght
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_cmti (struct pvt* pvt, const char* str)
|
||
|
{
|
||
|
// FIXME: check format in PDU mode
|
||
|
int index = at_parse_cmti (str);
|
||
|
|
||
|
if (CONF_SHARED(pvt, disablesms))
|
||
|
{
|
||
|
ast_log (LOG_WARNING, "[%s] SMS reception has been disabled in the configuration.\n", PVT_ID(pvt));
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (index > -1)
|
||
|
{
|
||
|
ast_debug (1, "[%s] Incoming SMS message\n", PVT_ID(pvt));
|
||
|
|
||
|
if (at_enqueue_retrieve_sms(&pvt->sys_chan, index, SUPPRESS_ERROR_DISABLED))
|
||
|
{
|
||
|
ast_log (LOG_ERROR, "[%s] Error sending CMGR to retrieve SMS message\n", PVT_ID(pvt));
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* Not sure why this happens, but we don't want to disconnect standing calls.
|
||
|
* [Jun 14 19:57:57] ERROR[3056]: at_response.c:1173 at_response_cmti:
|
||
|
* [m1-1] Error parsing incoming sms message alert '+CMTI: "SM",-1' */
|
||
|
ast_log(LOG_WARNING, "[%s] Error parsing incoming sms message alert '%s', ignoring\n", PVT_ID(pvt), str);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle +CDSI response
|
||
|
* \param pvt -- pvt structure
|
||
|
* \param str -- string containing response (null terminated)
|
||
|
* \param len -- string lenght
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_cdsi (struct pvt* pvt, const char* str)
|
||
|
{
|
||
|
// FIXME: check format in PDU mode
|
||
|
int index = at_parse_cdsi (str);
|
||
|
|
||
|
if (CONF_SHARED(pvt, disablesms))
|
||
|
{
|
||
|
ast_log (LOG_WARNING, "[%s] SMS reception has been disabled in the configuration.\n", PVT_ID(pvt));
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (index > -1)
|
||
|
{
|
||
|
ast_debug (1, "[%s] Incoming SMS message\n", PVT_ID(pvt));
|
||
|
|
||
|
if (at_enqueue_retrieve_sms(&pvt->sys_chan, index, SUPPRESS_ERROR_DISABLED))
|
||
|
{
|
||
|
ast_log (LOG_ERROR, "[%s] Error sending CMGR to retrieve SMS message\n", PVT_ID(pvt));
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* Not sure why this happens, but we don't want to disconnect standing calls.
|
||
|
* [Jun 14 19:57:57] ERROR[3056]: at_response.c:1173 at_response_cmti:
|
||
|
* [m1-1] Error parsing incoming sms message alert '+CMTI: "SM",-1' */
|
||
|
ast_log(LOG_WARNING, "[%s] Error parsing incoming sms message alert '%s', ignoring\n", PVT_ID(pvt), str);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle +CMGR response
|
||
|
* \param pvt -- pvt structure
|
||
|
* \param str -- string containing response (null terminated)
|
||
|
* \param len -- string lenght
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_cmgr(struct pvt* pvt, char * str, size_t len)
|
||
|
{
|
||
|
char oa[512] = "", sca[512] = "";
|
||
|
char scts[64], dt[64];
|
||
|
int mr, st;
|
||
|
char msg[4096];
|
||
|
int res;
|
||
|
char text_base64[40800];
|
||
|
size_t msg_len = sizeof(msg);
|
||
|
int tpdu_type;
|
||
|
pdu_udh_t udh;
|
||
|
pdu_udh_init(&udh);
|
||
|
char fullmsg[160 * 255];
|
||
|
int fullmsg_len;
|
||
|
int csms_cnt;
|
||
|
char buf[512];
|
||
|
char payload[SMSDB_PAYLOAD_MAX_LEN];
|
||
|
ssize_t payload_len;
|
||
|
int status_report[256];
|
||
|
|
||
|
const struct at_queue_cmd * ecmd = at_queue_head_cmd(pvt);
|
||
|
|
||
|
manager_event_message("DongleNewCMGR", PVT_ID(pvt), str);
|
||
|
if (ecmd) {
|
||
|
if (ecmd->res == RES_CMGR || ecmd->cmd == CMD_USER) {
|
||
|
at_queue_handle_result (pvt, RES_CMGR);
|
||
|
|
||
|
res = at_parse_cmgr(str, len, &tpdu_type, sca, sizeof(sca), oa, sizeof(oa), scts, &mr, &st, dt, msg, &msg_len, &udh);
|
||
|
if (res < 0) {
|
||
|
ast_log(LOG_WARNING, "[%s] Error parsing incoming message: %s\n", PVT_ID(pvt), error2str(chan_dongle_err));
|
||
|
goto receive_next_no_delete;
|
||
|
}
|
||
|
switch (PDUTYPE_MTI(tpdu_type)) {
|
||
|
case PDUTYPE_MTI_SMS_STATUS_REPORT:
|
||
|
ast_verb(1, "[%s] Got status report with ref %d from %s and status code %d\n", PVT_ID(pvt), mr, oa, st);
|
||
|
snprintf(buf, 64, "Delivered\r\nForeignID: %d", mr);
|
||
|
payload_len = smsdb_outgoing_part_status(pvt->imsi, oa, mr, st, status_report, payload);
|
||
|
if (payload_len >= 0) {
|
||
|
int success = 1;
|
||
|
char status_report_str[255 * 4 + 1];
|
||
|
int srroff = 0;
|
||
|
for (int i = 0; status_report[i] != -1; ++i) {
|
||
|
success &= !(status_report[i] & 0x40);
|
||
|
sprintf(status_report_str + srroff, "%03d,", status_report[i]);
|
||
|
srroff += 4;
|
||
|
}
|
||
|
status_report_str[srroff] = '\0';
|
||
|
ast_verb(1, "[%s] Success: %d; Payload: %.*s; Report string: %s\n", PVT_ID(pvt), success, (int) payload_len, payload, status_report_str);
|
||
|
payload[payload_len] = '\0';
|
||
|
channel_var_t vars[] =
|
||
|
{
|
||
|
{ "SMS_REPORT_PAYLOAD", payload } ,
|
||
|
{ "SMS_REPORT_TS", scts },
|
||
|
{ "SMS_REPORT_DT", dt },
|
||
|
{ "SMS_REPORT_SUCCESS", success ? "1" : "0" },
|
||
|
{ "SMS_REPORT_TYPE", "e" },
|
||
|
{ "SMS_REPORT", status_report_str },
|
||
|
{ NULL, NULL },
|
||
|
};
|
||
|
start_local_channel(pvt, "report", oa, vars);
|
||
|
manager_event_report(PVT_ID(pvt), payload, payload_len, scts, dt, success, 1, status_report_str);
|
||
|
}
|
||
|
break;
|
||
|
case PDUTYPE_MTI_SMS_DELIVER:
|
||
|
ast_debug (1, "[%s] Successfully read SM\n", PVT_ID(pvt));
|
||
|
if (udh.parts > 1) {
|
||
|
ast_verb (1, "[%s] Got SM part from %s: '%s'; [ref=%d, parts=%d, order=%d]\n", PVT_ID(pvt), oa, msg, udh.ref, udh.parts, udh.order);
|
||
|
csms_cnt = smsdb_put(pvt->imsi, oa, udh.ref, udh.parts, udh.order, msg, fullmsg);
|
||
|
if (csms_cnt <= 0) {
|
||
|
ast_log(LOG_ERROR, "[%s] Error putting SMS to SMSDB\n", PVT_ID(pvt));
|
||
|
goto receive_as_is;
|
||
|
}
|
||
|
if (csms_cnt < udh.parts) {
|
||
|
ast_verb (1, "[%s] Waiting for following parts\n", PVT_ID(pvt));
|
||
|
goto receive_next;
|
||
|
}
|
||
|
fullmsg_len = strlen(fullmsg);
|
||
|
} else {
|
||
|
receive_as_is:
|
||
|
ast_verb (1, "[%s] Got single SM from %s: '%s'\n", PVT_ID(pvt), oa, msg);
|
||
|
strncpy(fullmsg, msg, msg_len);
|
||
|
fullmsg[msg_len] = '\0';
|
||
|
fullmsg_len = msg_len;
|
||
|
}
|
||
|
|
||
|
ast_verb (1, "[%s] Got full SMS from %s: '%s'\n", PVT_ID(pvt), oa, fullmsg);
|
||
|
ast_base64encode (text_base64, (unsigned char*)fullmsg, fullmsg_len, sizeof(text_base64));
|
||
|
|
||
|
manager_event_new_sms(PVT_ID(pvt), oa, fullmsg);
|
||
|
manager_event_new_sms_base64(PVT_ID(pvt), oa, text_base64);
|
||
|
{
|
||
|
channel_var_t vars[] =
|
||
|
{
|
||
|
{ "SMS", fullmsg } ,
|
||
|
{ "SMS_BASE64", text_base64 },
|
||
|
{ "SMS_TS", scts },
|
||
|
{ NULL, NULL },
|
||
|
};
|
||
|
start_local_channel (pvt, "sms", oa, vars);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ast_log (LOG_ERROR, "[%s] Received '+CMGR' when expecting '%s' response to '%s', ignoring\n", PVT_ID(pvt),
|
||
|
at_res2str (ecmd->res), at_cmd2str (ecmd->cmd));
|
||
|
}
|
||
|
receive_next:
|
||
|
if (CONF_SHARED(pvt, autodeletesms) && pvt->incoming_sms_index != -1U)
|
||
|
{
|
||
|
at_enqueue_delete_sms(&pvt->sys_chan, pvt->incoming_sms_index);
|
||
|
}
|
||
|
receive_next_no_delete:
|
||
|
at_retrieve_next_sms(&pvt->sys_chan, at_cmd_suppress_error_mode(ecmd->flags));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ast_log (LOG_WARNING, "[%s] Received unexpected '+CMGR'\n", PVT_ID(pvt));
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Send an SMS message from the queue.
|
||
|
* \param pvt -- pvt structure
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_sms_prompt (struct pvt* pvt)
|
||
|
{
|
||
|
const struct at_queue_cmd * ecmd = at_queue_head_cmd (pvt);
|
||
|
if (ecmd && ecmd->res == RES_SMS_PROMPT)
|
||
|
{
|
||
|
at_queue_handle_result (pvt, RES_SMS_PROMPT);
|
||
|
}
|
||
|
else if (ecmd)
|
||
|
{
|
||
|
ast_log (LOG_ERROR, "[%s] Received sms prompt when expecting '%s' response to '%s', ignoring\n", PVT_ID(pvt),
|
||
|
at_res2str (ecmd->res), at_cmd2str (ecmd->cmd));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ast_log (LOG_ERROR, "[%s] Received unexpected sms prompt\n", PVT_ID(pvt));
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle CUSD response
|
||
|
* \param pvt -- pvt structure
|
||
|
* \param str -- string containing response (null terminated)
|
||
|
* \param len -- string lenght
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_cusd (struct pvt * pvt, char * str, size_t len)
|
||
|
{
|
||
|
static const char * const types[] = {
|
||
|
"USSD Notify",
|
||
|
"USSD Request",
|
||
|
"USSD Terminated by network",
|
||
|
"Other local client has responded",
|
||
|
"Operation not supported",
|
||
|
"Network time out",
|
||
|
};
|
||
|
|
||
|
ssize_t res;
|
||
|
int type;
|
||
|
char* cusd;
|
||
|
int dcs;
|
||
|
char cusd_utf8_str[1024];
|
||
|
char text_base64[16384];
|
||
|
char typebuf[2];
|
||
|
const char* typestr;
|
||
|
|
||
|
manager_event_message("DongleNewCUSD", PVT_ID(pvt), str);
|
||
|
|
||
|
if (at_parse_cusd (str, &type, &cusd, &dcs))
|
||
|
{
|
||
|
ast_verb (1, "[%s] Error parsing CUSD: '%.*s'\n", PVT_ID(pvt), (int) len, str);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
if(type < 0 || type >= (int)ITEMS_OF(types))
|
||
|
{
|
||
|
ast_log (LOG_WARNING, "[%s] Unknown CUSD type: %d\n", PVT_ID(pvt), type);
|
||
|
}
|
||
|
|
||
|
typestr = enum2str(type, types, ITEMS_OF(types));
|
||
|
|
||
|
typebuf[0] = type + '0';
|
||
|
typebuf[1] = 0;
|
||
|
|
||
|
// sanitize DCS
|
||
|
if (dcs & 0x40) {
|
||
|
dcs = (dcs & 0xc) >> 2;
|
||
|
if (dcs == 3) dcs = 0;
|
||
|
} else {
|
||
|
dcs = 0;
|
||
|
}
|
||
|
|
||
|
ast_verb (1, "[%s] USSD DCS=%d (0: gsm7, 1: ascii, 2: ucs2)\n", PVT_ID(pvt), dcs);
|
||
|
if (dcs == 0) { // GSM-7
|
||
|
uint16_t out_ucs2[1024];
|
||
|
int cusd_nibbles = unhex(cusd, (uint8_t*)cusd);
|
||
|
res = gsm7_unpack_decode(cusd, cusd_nibbles, out_ucs2, sizeof(out_ucs2) / 2, 0, 0, 0);
|
||
|
if (res < 0) {
|
||
|
return -1;
|
||
|
}
|
||
|
res = ucs2_to_utf8(out_ucs2, res, cusd_utf8_str, sizeof(cusd_utf8_str) - 1);
|
||
|
} else if (dcs == 1) { // ASCII
|
||
|
res = strlen(cusd);
|
||
|
if (res > (ssize_t)sizeof(cusd_utf8_str) - 1) {
|
||
|
res = -1;
|
||
|
} else {
|
||
|
memcpy(cusd_utf8_str, cusd, res);
|
||
|
}
|
||
|
} else if (dcs == 2) { // UCS-2
|
||
|
int cusd_nibbles = unhex(cusd, (uint8_t*)cusd);
|
||
|
res = ucs2_to_utf8((const uint16_t*)cusd, (cusd_nibbles + 1) / 4,
|
||
|
cusd_utf8_str, sizeof(cusd_utf8_str) - 1);
|
||
|
} else {
|
||
|
res = -1;
|
||
|
}
|
||
|
if (res < 0) {
|
||
|
return -1;
|
||
|
}
|
||
|
cusd_utf8_str[res] = '\0';
|
||
|
|
||
|
ast_verb (1, "[%s] Got USSD type %d '%s': '%s'\n", PVT_ID(pvt), type, typestr, cusd_utf8_str);
|
||
|
ast_base64encode (text_base64, (unsigned char*)cusd_utf8_str, res, sizeof(text_base64));
|
||
|
|
||
|
// TODO: pass type
|
||
|
manager_event_new_ussd(PVT_ID(pvt), cusd_utf8_str);
|
||
|
manager_event_message("DongleNewUSSDBase64", PVT_ID(pvt), text_base64);
|
||
|
|
||
|
{
|
||
|
channel_var_t vars[] =
|
||
|
{
|
||
|
{ "USSD_TYPE", typebuf },
|
||
|
{ "USSD_TYPE_STR", ast_strdupa(typestr) },
|
||
|
{ "USSD", cusd_utf8_str },
|
||
|
{ "USSD_BASE64", text_base64 },
|
||
|
{ NULL, NULL },
|
||
|
};
|
||
|
start_local_channel(pvt, "ussd", "ussd", vars);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle +CPIN response
|
||
|
* \param pvt -- pvt structure
|
||
|
* \param str -- string containing response (null terminated)
|
||
|
* \param len -- string lenght
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_cpin (struct pvt* pvt, char* str, size_t len)
|
||
|
{
|
||
|
int rv = at_parse_cpin (str, len);
|
||
|
switch(rv)
|
||
|
{
|
||
|
case -1:
|
||
|
ast_log (LOG_ERROR, "[%s] Error parsing +CPIN message: %s\n", PVT_ID(pvt), str);
|
||
|
break;
|
||
|
case 1:
|
||
|
ast_log (LOG_ERROR, "Dongle %s needs PIN code!\n", PVT_ID(pvt));
|
||
|
break;
|
||
|
case 2:
|
||
|
ast_log (LOG_ERROR, "Dongle %s needs PUK code!\n", PVT_ID(pvt));
|
||
|
break;
|
||
|
}
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle ^SMMEMFULL response This event notifies us, that the sms storage is full
|
||
|
* \param pvt -- pvt structure
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_smmemfull (struct pvt* pvt)
|
||
|
{
|
||
|
ast_log (LOG_ERROR, "[%s] SMS storage is full\n", PVT_ID(pvt));
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle +CSQ response Here we get the signal strength and bit error rate
|
||
|
* \param pvt -- pvt structure
|
||
|
* \param str -- string containing response (null terminated)
|
||
|
* \param len -- string lenght
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
static int at_response_csq (struct pvt* pvt, const char* str)
|
||
|
{
|
||
|
int rssi;
|
||
|
int rv = at_parse_csq (str, &rssi);
|
||
|
|
||
|
if(rv)
|
||
|
ast_debug (2, "[%s] Error parsing +CSQ result '%s'\n", PVT_ID(pvt), str);
|
||
|
else
|
||
|
pvt->rssi = rssi;
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle +CNUM response Here we get our own phone number
|
||
|
* \param pvt -- pvt structure
|
||
|
* \param str -- string containing response (null terminated)
|
||
|
* \param len -- string lenght
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_cnum (struct pvt* pvt, char* str)
|
||
|
{
|
||
|
char* number = at_parse_cnum (str);
|
||
|
|
||
|
if (number)
|
||
|
{
|
||
|
ast_copy_string (pvt->subscriber_number, number, sizeof (pvt->subscriber_number));
|
||
|
if(pvt->subscriber_number[0] != 0)
|
||
|
pvt->has_subscriber_number = 1;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
ast_copy_string (pvt->subscriber_number, "Unknown", sizeof (pvt->subscriber_number));
|
||
|
pvt->has_subscriber_number = 0;
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle +COPS response Here we get the GSM provider name
|
||
|
* \param pvt -- pvt structure
|
||
|
* \param str -- string containing response (null terminated)
|
||
|
* \param len -- string lenght
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_cops (struct pvt* pvt, char* str)
|
||
|
{
|
||
|
char* provider_name = at_parse_cops (str);
|
||
|
|
||
|
if (provider_name)
|
||
|
{
|
||
|
ast_copy_string (pvt->provider_name, provider_name, sizeof (pvt->provider_name));
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
ast_copy_string (pvt->provider_name, "NONE", sizeof (pvt->provider_name));
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle +CREG response Here we get the GSM registration status
|
||
|
* \param pvt -- pvt structure
|
||
|
* \param str -- string containing response (null terminated)
|
||
|
* \param len -- string lenght
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_creg (struct pvt* pvt, char* str, size_t len)
|
||
|
{
|
||
|
int d;
|
||
|
char* lac;
|
||
|
char* ci;
|
||
|
|
||
|
if (at_enqueue_cops(&pvt->sys_chan))
|
||
|
{
|
||
|
ast_log (LOG_ERROR, "[%s] Error sending query for provider name\n", PVT_ID(pvt));
|
||
|
}
|
||
|
|
||
|
if (at_parse_creg (str, len, &d, &pvt->gsm_reg_status, &lac, &ci))
|
||
|
{
|
||
|
ast_verb (1, "[%s] Error parsing CREG: '%.*s'\n", PVT_ID(pvt), (int) len, str);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (d)
|
||
|
{
|
||
|
//#ifdef ISSUE_CCWA_STATUS_CHECK
|
||
|
/* only if gsm_registered 0 -> 1 ? */
|
||
|
if(!pvt->gsm_registered && CONF_SHARED(pvt, callwaiting) != CALL_WAITING_AUTO)
|
||
|
at_enqueue_set_ccwa(&pvt->sys_chan, CONF_SHARED(pvt, callwaiting));
|
||
|
//#endif
|
||
|
pvt->gsm_registered = 1;
|
||
|
manager_event_device_status(PVT_ID(pvt), "Register");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pvt->gsm_registered = 0;
|
||
|
manager_event_device_status(PVT_ID(pvt), "Unregister");
|
||
|
}
|
||
|
|
||
|
if (lac)
|
||
|
{
|
||
|
ast_copy_string (pvt->location_area_code, lac, sizeof (pvt->location_area_code));
|
||
|
}
|
||
|
|
||
|
if (ci)
|
||
|
{
|
||
|
ast_copy_string (pvt->cell_id, ci, sizeof (pvt->cell_id));
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle AT+CGMI response
|
||
|
* \param pvt -- pvt structure
|
||
|
* \param str -- string containing response (null terminated)
|
||
|
* \param len -- string lenght
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_cgmi (struct pvt* pvt, const char* str)
|
||
|
{
|
||
|
ast_copy_string (pvt->manufacturer, str, sizeof (pvt->manufacturer));
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle AT+CGMM response
|
||
|
* \param pvt -- pvt structure
|
||
|
* \param str -- string containing response (null terminated)
|
||
|
* \param len -- string lenght
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
#/* */
|
||
|
static int at_response_cgmm (struct pvt* pvt, const char* str)
|
||
|
{
|
||
|
ast_copy_string (pvt->model, str, sizeof (pvt->model));
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle AT+CGMR response
|
||
|
* \param pvt -- pvt structure
|
||
|
* \param str -- string containing response (null terminated)
|
||
|
* \param len -- string lenght
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_cgmr (struct pvt* pvt, const char* str)
|
||
|
{
|
||
|
ast_copy_string (pvt->firmware, str, sizeof (pvt->firmware));
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle AT+CGSN response
|
||
|
* \param pvt -- pvt structure
|
||
|
* \param str -- string containing response (null terminated)
|
||
|
* \param len -- string lenght
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_cgsn (struct pvt* pvt, const char* str)
|
||
|
{
|
||
|
ast_copy_string (pvt->imei, str, sizeof (pvt->imei));
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Handle AT+CIMI response
|
||
|
* \param pvt -- pvt structure
|
||
|
* \param str -- string containing response (null terminated)
|
||
|
* \param len -- string lenght
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
static int at_response_cimi (struct pvt* pvt, const char* str)
|
||
|
{
|
||
|
ast_copy_string (pvt->imsi, str, sizeof (pvt->imsi));
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void at_response_busy(struct pvt* pvt, enum ast_control_frame_type control)
|
||
|
{
|
||
|
const struct at_queue_task * task = at_queue_head_task (pvt);
|
||
|
struct cpvt* cpvt = task->cpvt;
|
||
|
|
||
|
if(cpvt == &pvt->sys_chan)
|
||
|
cpvt = pvt->last_dialed_cpvt;
|
||
|
|
||
|
if(cpvt)
|
||
|
{
|
||
|
CPVT_SET_FLAGS(cpvt, CALL_FLAG_NEED_HANGUP);
|
||
|
queue_control_channel (cpvt, control);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* \brief Do response
|
||
|
* \param pvt -- pvt structure
|
||
|
* \param iovcnt -- number of elements array pvt->d_read_iov
|
||
|
* \param at_res -- result type
|
||
|
* \retval 0 success
|
||
|
* \retval -1 error
|
||
|
*/
|
||
|
|
||
|
int at_response (struct pvt* pvt, const struct iovec iov[2], int iovcnt, at_res_t at_res)
|
||
|
{
|
||
|
char* str;
|
||
|
size_t len;
|
||
|
const at_queue_task_t *task = at_queue_head_task(pvt);
|
||
|
const at_queue_cmd_t *ecmd = at_queue_task_cmd(task);
|
||
|
|
||
|
|
||
|
if(iov[0].iov_len + iov[1].iov_len > 0)
|
||
|
{
|
||
|
len = iov[0].iov_len + iov[1].iov_len - 1;
|
||
|
|
||
|
if (iovcnt == 2)
|
||
|
{
|
||
|
ast_debug (5, "[%s] iovcnt == 2\n", PVT_ID(pvt));
|
||
|
|
||
|
str = alloca(len + 1);
|
||
|
memcpy(str, iov[0].iov_base, iov[0].iov_len);
|
||
|
memcpy(str + iov[0].iov_len, iov[1].iov_base, iov[1].iov_len);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
str = iov[0].iov_base;
|
||
|
}
|
||
|
str[len] = '\0';
|
||
|
|
||
|
// ast_debug (5, "[%s] [%.*s]\n", PVT_ID(pvt), (int) len, str);
|
||
|
|
||
|
if(ecmd && ecmd->cmd == CMD_USER) {
|
||
|
ast_verb(1, "[%s] Got Response for user's command:'%s'\n", PVT_ID(pvt), str);
|
||
|
ast_log(LOG_NOTICE, "[%s] Got Response for user's command:'%s'\n", PVT_ID(pvt), str);
|
||
|
}
|
||
|
switch (at_res)
|
||
|
{
|
||
|
case RES_BOOT:
|
||
|
case RES_CSSI:
|
||
|
case RES_CSSU:
|
||
|
case RES_SRVST:
|
||
|
case RES_CVOICE:
|
||
|
case RES_CPMS:
|
||
|
case RES_CONF:
|
||
|
return 0;
|
||
|
|
||
|
case RES_CMGS:
|
||
|
{
|
||
|
int res = at_parse_cmgs(str);
|
||
|
|
||
|
char payload[SMSDB_PAYLOAD_MAX_LEN];
|
||
|
char dst[SMSDB_DST_MAX_LEN];
|
||
|
ssize_t payload_len = smsdb_outgoing_part_put(task->uid, res, dst, payload);
|
||
|
if (payload_len >= 0) {
|
||
|
ast_verb (3, "[%s] Error payload: %.*s\n", PVT_ID(pvt), (int) payload_len, payload);
|
||
|
channel_var_t vars[] =
|
||
|
{
|
||
|
{ "SMS_REPORT_PAYLOAD", payload },
|
||
|
{ "SMS_REPORT_TS", "" },
|
||
|
{ "SMS_REPORT_DT", "" },
|
||
|
{ "SMS_REPORT_SUCCESS", "1" },
|
||
|
{ "SMS_REPORT_TYPE", "i" },
|
||
|
{ "SMS_REPORT", "" },
|
||
|
{ NULL, NULL },
|
||
|
};
|
||
|
start_local_channel(pvt, "report", dst, vars);
|
||
|
manager_event_report(PVT_ID(pvt), payload, payload_len, "", "", 1, 0, "");
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
|
||
|
case RES_OK:
|
||
|
at_response_ok (pvt, at_res);
|
||
|
return 0;
|
||
|
|
||
|
case RES_RSSI:
|
||
|
/* An error here is not fatal. Just keep going. */
|
||
|
at_response_rssi (pvt, str);
|
||
|
break;
|
||
|
case RES_MODE:
|
||
|
/* An error here is not fatal. Just keep going. */
|
||
|
at_response_mode (pvt, str, len);
|
||
|
return 0;
|
||
|
|
||
|
case RES_ORIG:
|
||
|
return at_response_orig (pvt, str);
|
||
|
|
||
|
case RES_CEND:
|
||
|
return at_response_cend (pvt, str);
|
||
|
|
||
|
case RES_CONN:
|
||
|
return at_response_conn (pvt, str);
|
||
|
|
||
|
case RES_CREG:
|
||
|
/* An error here is not fatal. Just keep going. */
|
||
|
at_response_creg (pvt, str, len);
|
||
|
return 0;
|
||
|
|
||
|
case RES_COPS:
|
||
|
/* An error here is not fatal. Just keep going. */
|
||
|
at_response_cops (pvt, str);
|
||
|
return 0;
|
||
|
|
||
|
case RES_CSQ:
|
||
|
/* An error here is not fatal. Just keep going. */
|
||
|
at_response_csq (pvt, str);
|
||
|
break;
|
||
|
|
||
|
case RES_CMS_ERROR:
|
||
|
case RES_ERROR:
|
||
|
return at_response_error (pvt, at_res);
|
||
|
|
||
|
case RES_RING:
|
||
|
return at_response_ring (pvt);
|
||
|
|
||
|
case RES_SMMEMFULL:
|
||
|
return at_response_smmemfull (pvt);
|
||
|
/*
|
||
|
case RES_CLIP:
|
||
|
return at_response_clip (pvt, str, len);
|
||
|
*/
|
||
|
case RES_CDSI:
|
||
|
return at_response_cdsi (pvt, str);
|
||
|
case RES_CMTI:
|
||
|
return at_response_cmti (pvt, str);
|
||
|
|
||
|
case RES_CMGR:
|
||
|
return at_response_cmgr (pvt, str, len);
|
||
|
|
||
|
case RES_SMS_PROMPT:
|
||
|
return at_response_sms_prompt (pvt);
|
||
|
|
||
|
case RES_CUSD:
|
||
|
/* An error here is not fatal. Just keep going. */
|
||
|
at_response_cusd (pvt, str, len);
|
||
|
break;
|
||
|
case RES_CLCC:
|
||
|
return at_response_clcc (pvt, str);
|
||
|
|
||
|
case RES_CCWA:
|
||
|
return at_response_ccwa (pvt, str);
|
||
|
|
||
|
case RES_BUSY:
|
||
|
ast_log (LOG_ERROR, "[%s] Receive BUSY\n", PVT_ID(pvt));
|
||
|
at_response_busy(pvt, AST_CONTROL_BUSY);
|
||
|
break;
|
||
|
|
||
|
case RES_NO_DIALTONE:
|
||
|
ast_log (LOG_ERROR, "[%s] Receive NO DIALTONE\n", PVT_ID(pvt));
|
||
|
at_response_busy(pvt, AST_CONTROL_CONGESTION);
|
||
|
break;
|
||
|
case RES_NO_CARRIER:
|
||
|
ast_log (LOG_ERROR, "[%s] Receive NO CARRIER\n", PVT_ID(pvt));
|
||
|
at_response_busy(pvt, AST_CONTROL_CONGESTION);
|
||
|
break;
|
||
|
case RES_CPIN:
|
||
|
/* fatal */
|
||
|
return at_response_cpin (pvt, str, len);
|
||
|
|
||
|
case RES_CNUM:
|
||
|
/* An error here is not fatal. Just keep going. */
|
||
|
at_response_cnum (pvt, str);
|
||
|
return 0;
|
||
|
|
||
|
case RES_CSCA:
|
||
|
/* An error here is not fatal. Just keep going. */
|
||
|
at_response_csca (pvt, str);
|
||
|
return 0;
|
||
|
|
||
|
case RES_PARSE_ERROR:
|
||
|
ast_log (LOG_ERROR, "[%s] Error parsing result\n", PVT_ID(pvt));
|
||
|
return -1;
|
||
|
|
||
|
case COMPATIBILITY_RES_START_AT_MINUSONE:
|
||
|
/* ??? */
|
||
|
case RES_UNKNOWN:
|
||
|
if (ecmd)
|
||
|
{
|
||
|
switch (ecmd->cmd)
|
||
|
{
|
||
|
case CMD_AT_CGMI:
|
||
|
ast_debug (1, "[%s] Got AT_CGMI data (manufacturer info)\n", PVT_ID(pvt));
|
||
|
return at_response_cgmi (pvt, str);
|
||
|
|
||
|
case CMD_AT_CGMM:
|
||
|
ast_debug (1, "[%s] Got AT_CGMM data (model info)\n", PVT_ID(pvt));
|
||
|
return at_response_cgmm (pvt, str);
|
||
|
|
||
|
case CMD_AT_CGMR:
|
||
|
ast_debug (1, "[%s] Got AT+CGMR data (firmware info)\n", PVT_ID(pvt));
|
||
|
return at_response_cgmr (pvt, str);
|
||
|
|
||
|
case CMD_AT_CGSN:
|
||
|
ast_debug (1, "[%s] Got AT+CGSN data (IMEI number)\n", PVT_ID(pvt));
|
||
|
return at_response_cgsn (pvt, str);
|
||
|
|
||
|
case CMD_AT_CIMI:
|
||
|
ast_debug (1, "[%s] Got AT+CIMI data (IMSI number)\n", PVT_ID(pvt));
|
||
|
return at_response_cimi (pvt, str);
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
ast_debug (1, "[%s] Ignoring unknown result: '%.*s'\n", PVT_ID(pvt), (int) len, str);
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|