/* Copyright (C) 2009 - 2010 Artem Makhutov http://www.makhutov.org Dmitry Vagin bg */ /* Copyright (C) 2009 - 2010 Artem Makhutov Artem Makhutov http://www.makhutov.org Copyright (C) 2020 Max von Buelow */ #include "ast_config.h" #include #include "at_command.h" //#include ".h" #include "at_queue.h" #include "char_conv.h" /* char_to_hexstr_7bit() */ #include "chan_dongle.h" /* struct pvt */ #include "pdu.h" /* build_pdu() */ #include "smsdb.h" #include "error.h" static const char cmd_at[] = "AT\r"; static const char cmd_chld1x[] = "AT+CHLD=1%d\r"; static const char cmd_chld2[] = "AT+CHLD=2\r"; static const char cmd_clcc[] = "AT+CLCC\r"; static const char cmd_ddsetex2[] = "AT^DDSETEX=2\r"; static const char cmd_qpcmv10[] = "AT+QPCMV=1,0\r"; /*! * \brief Format and fill generic command * \param cmd -- the command structure * \param format -- printf format string * \param ap -- list of arguments * \return 0 on success */ static int at_fill_generic_cmd_va (at_queue_cmd_t * cmd, const char * format, va_list ap) { char buf[4096]; cmd->length = vsnprintf (buf, sizeof(buf)-1, format, ap); buf[cmd->length] = 0; cmd->data = ast_strdup(buf); if(!cmd->data) return -1; cmd->flags &= ~ATQ_CMD_FLAG_STATIC; return 0; } /*! * \brief Format and fill generic command * \param cmd -- the command structure * \param format -- printf format string * \return 0 on success */ static int __attribute__ ((format(printf, 2, 3))) at_fill_generic_cmd (at_queue_cmd_t * cmd, const char * format, ...) { va_list ap; int rv; va_start(ap, format); rv = at_fill_generic_cmd_va(cmd, format, ap); va_end(ap); return rv; } /*! * \brief Enqueue generic command * \param pvt -- pvt structure * \param cmd -- at command * \param prio -- queue priority of this command * \param format -- printf format string including AT command text * \return 0 on success */ static int __attribute__ ((format(printf, 4, 5))) at_enqueue_generic(struct cpvt *cpvt, at_cmd_t cmd, int prio, const char *format, ...) { va_list ap; int rv; at_queue_cmd_t at_cmd = ATQ_CMD_DECLARE_DYN(cmd); va_start(ap, format); rv = at_fill_generic_cmd_va(&at_cmd, format, ap); va_end(ap); if(!rv) rv = at_queue_insert(cpvt, &at_cmd, 1, prio); return rv; } /*! * \brief Enqueue initialization commands * \param cpvt -- cpvt structure * \param from_command -- begin initialization from this command in list * \return 0 on success */ EXPORT_DEF int at_enqueue_initialization(struct cpvt *cpvt, at_cmd_t from_command) { static const at_queue_cmd_t st_cmds[] = { /* AT */ ATQ_CMD_DECLARE_ST(CMD_AT, cmd_at), /* optional, reload configuration */ ATQ_CMD_DECLARE_ST(CMD_AT_Z, "ATZ\r"), /* disable echo */ ATQ_CMD_DECLARE_ST(CMD_AT_E, "ATE0\r"), /* optional, Enable or disable some devices */ ATQ_CMD_DECLARE_DYN(CMD_AT_U2DIAG), /* Get manufacturer info */ ATQ_CMD_DECLARE_ST(CMD_AT_CGMI, "AT+CGMI\r"), /* Get Product name */ ATQ_CMD_DECLARE_ST(CMD_AT_CGMM, "AT+CGMM\r"), /* Get software version */ ATQ_CMD_DECLARE_ST(CMD_AT_CGMR, "AT+CGMR\r"), /* set MS Error Report to 'ERROR' only. * TODO: change to 1 or 2 and add support in response handlers */ ATQ_CMD_DECLARE_ST(CMD_AT_CMEE, "AT+CMEE=0\r"), /* IMEI Read */ ATQ_CMD_DECLARE_ST(CMD_AT_CGSN, "AT+CGSN\r"), /* IMSI Read */ ATQ_CMD_DECLARE_ST(CMD_AT_CIMI, "AT+CIMI\r"), /* check is password authentication requirement and the * remainder validation times */ ATQ_CMD_DECLARE_ST(CMD_AT_CPIN, "AT+CPIN?\r"), /* Read operator name */ ATQ_CMD_DECLARE_ST(CMD_AT_COPS_INIT, "AT+COPS=0,0\r"), /* GSM registration status setting */ ATQ_CMD_DECLARE_STI(CMD_AT_CREG_INIT, "AT+CREG=2\r"), /* GSM registration status */ ATQ_CMD_DECLARE_ST(CMD_AT_CREG, "AT+CREG?\r"), /* Get Subscriber number */ ATQ_CMD_DECLARE_STI(CMD_AT_CNUM, "AT+CNUM\r"), /* read the current voice mode, and return sampling * rate, data bit, frame period */ ATQ_CMD_DECLARE_STI(CMD_AT_CVOICE, "AT^CVOICE?\r"), ATQ_CMD_DECLARE_STI(CMD_AT_QPCMV, "AT+QPCMV?\r"), /* for Quectel */ /* Get SMS Service center address */ ATQ_CMD_DECLARE_ST(CMD_AT_CSCA, "AT+CSCA?\r"), /* activate Supplementary Service Notification with CSSI and CSSU */ ATQ_CMD_DECLARE_ST(CMD_AT_CSSN, "AT+CSSN=1,1\r"), /* Set Message Format */ ATQ_CMD_DECLARE_ST(CMD_AT_CMGF, "AT+CMGF=0\r"), /* SMS Storage Selection */ ATQ_CMD_DECLARE_ST(CMD_AT_CPMS, "AT+CPMS=\"SM\",\"SM\",\"SM\"\r"), /* New SMS Notification Setting +CNMI=[[,[,[,[,]]]]] */ /* pvt->initialized = 1 after successful of CMD_AT_CNMI */ ATQ_CMD_DECLARE_ST(CMD_AT_CNMI, "AT+CNMI=2,1,0,2,0\r"), /* Query Signal quality */ ATQ_CMD_DECLARE_ST(CMD_AT_CSQ, "AT+CSQ\r"), }; unsigned in, out; int begin = -1; int err; char * ptmp1 = NULL; pvt_t * pvt = cpvt->pvt; at_queue_cmd_t cmds[ITEMS_OF(st_cmds)]; /* customize list */ for(in = out = 0; in < ITEMS_OF(st_cmds); in++) { if(begin == -1) { if(st_cmds[in].cmd == from_command) begin = in; else continue; } if(st_cmds[in].cmd == CMD_AT_Z && !CONF_SHARED(pvt, resetdongle)) continue; if(st_cmds[in].cmd == CMD_AT_U2DIAG && CONF_SHARED(pvt, u2diag) == -1) continue; memcpy(&cmds[out], &st_cmds[in], sizeof(st_cmds[in])); if(cmds[out].cmd == CMD_AT_U2DIAG) { err = at_fill_generic_cmd(&cmds[out], "AT^U2DIAG=%d\r", CONF_SHARED(pvt, u2diag)); if(err) goto failure; ptmp1 = cmds[out].data; } if(cmds[out].cmd == from_command) begin = out; out++; } if(out > 0) return at_queue_insert(cpvt, cmds, out, 0); return 0; failure: if(ptmp1) ast_free(ptmp1); return err; } /*! * \brief Enqueue the AT+COPS? command * \param cpvt -- cpvt structure * \return 0 on success */ EXPORT_DEF int at_enqueue_cops(struct cpvt *cpvt) { static const char cmd[] = "AT+COPS?\r"; static at_queue_cmd_t at_cmd = ATQ_CMD_DECLARE_ST(CMD_AT_COPS, cmd); if (at_queue_insert_const(cpvt, &at_cmd, 1, 0) != 0) { chan_dongle_err = E_QUEUE; return -1; } return 0; } /* SMS sending */ static int at_enqueue_pdu(struct cpvt *cpvt, const char *pdu, size_t length, size_t tpdulen, int uid) { char buf[8+25+1]; at_queue_cmd_t at_cmd[] = { { CMD_AT_CMGS, RES_SMS_PROMPT, ATQ_CMD_FLAG_DEFAULT, { ATQ_CMD_TIMEOUT_MEDIUM, 0}, NULL, 0 }, { CMD_AT_SMSTEXT, RES_OK, ATQ_CMD_FLAG_DEFAULT, { ATQ_CMD_TIMEOUT_LONG, 0}, NULL, 0 } }; at_cmd[1].data = ast_malloc(length + 2); if(!at_cmd[1].data) { return -ENOMEM; } at_cmd[1].length = length + 1; memcpy(at_cmd[1].data, pdu, length); at_cmd[1].data[length] = 0x1A; at_cmd[1].data[length + 1] = 0x0; at_cmd[0].length = snprintf(buf, sizeof(buf), "AT+CMGS=%d\r", (int)tpdulen); at_cmd[0].data = ast_strdup(buf); if(!at_cmd[0].data) { ast_free(at_cmd[1].data); return -ENOMEM; } if (at_queue_insert_uid(cpvt, at_cmd, ITEMS_OF(at_cmd), 0, uid) != 0) { chan_dongle_err = E_QUEUE; return -1; } return 0; } /*! * \brief Enqueue a SMS message * \param cpvt -- cpvt structure * \param number -- the destination of the message * \param msg -- utf-8 encoded message */ EXPORT_DEF int at_enqueue_sms(struct cpvt *cpvt, const char *destination, const char *msg, unsigned validity_minutes, int report_req, const char *payload, size_t payload_len) { ssize_t res; pvt_t* pvt = cpvt->pvt; /* set default validity period */ if (validity_minutes <= 0) validity_minutes = 3 * 24 * 60; int msg_len = strlen(msg); uint16_t msg_ucs2[msg_len * 2]; res = utf8_to_ucs2(msg, msg_len, msg_ucs2, sizeof(msg_ucs2)); if (res < 0) { chan_dongle_err = E_PARSE_UTF8; return -1; } char hexbuf[PDU_LENGTH * 2 + 1]; pdu_part_t pdus[255]; int csmsref = smsdb_get_refid(pvt->imsi, destination); if (csmsref < 0) { chan_dongle_err = E_SMSDB; return -1; } res = pdu_build_mult(pdus, "" /* pvt->sms_scenter */, destination, msg_ucs2, res, validity_minutes, !!report_req, csmsref); if (res < 0) { /* pdu_build_mult sets chan_dongle_err */ return -1; } int uid = smsdb_outgoing_add(pvt->imsi, destination, res, validity_minutes * 60, report_req, payload, payload_len); if (uid < 0) { chan_dongle_err = E_SMSDB; return -1; } for (int i = 0; i < res; ++i) { hexify(pdus[i].buffer, pdus[i].length, hexbuf); if (at_enqueue_pdu(cpvt, hexbuf, pdus[i].length * 2, pdus[i].tpdu_length, uid) < 0) { return -1; } } return 0; } /*! * \brief Enqueue AT+CUSD. * \param cpvt -- cpvt structure * \param code the CUSD code to send */ EXPORT_DEF int at_enqueue_ussd(struct cpvt *cpvt, const char *code) { static const char cmd[] = "AT+CUSD=1,\""; static const char cmd_end[] = "\",15\r"; at_queue_cmd_t at_cmd = ATQ_CMD_DECLARE_DYN(CMD_AT_CUSD); ssize_t res; int length; char buf[4096]; memcpy (buf, cmd, STRLEN(cmd)); length = STRLEN(cmd); int code_len = strlen(code); // use 7 bit encoding. 15 is 00001111 in binary and means 'Language using the GSM 7 bit default alphabet; Language unspecified' accodring to GSM 23.038 uint16_t code16[code_len * 2]; uint8_t code_packed[4069]; res = utf8_to_ucs2(code, code_len, code16, sizeof(code16)); if (res < 0) { chan_dongle_err = E_PARSE_UTF8; return -1; } res = gsm7_encode(code16, res, code16); if (res < 0) { chan_dongle_err = E_ENCODE_GSM7; return -1; } res = gsm7_pack(code16, res, (char*)code_packed, sizeof(code_packed), 0); if (res < 0) { chan_dongle_err = E_PACK_GSM7; return -1; } res = (res + 1) / 2; hexify(code_packed, res, buf + STRLEN(cmd)); length += res * 2; memcpy(buf + length, cmd_end, STRLEN(cmd_end)+1); length += STRLEN(cmd_end); at_cmd.length = length; at_cmd.data = ast_strdup (buf); if (!at_cmd.data) { chan_dongle_err = E_UNKNOWN; return -1; } if (at_queue_insert(cpvt, &at_cmd, 1, 0) != 0) { chan_dongle_err = E_QUEUE; return -1; } return 0; } /*! * \brief Enqueue a DTMF command * \param cpvt -- cpvt structure * \param digit -- the dtmf digit to send * \return -2 if digis is invalid, 0 on success */ EXPORT_DEF int at_enqueue_dtmf(struct cpvt *cpvt, char digit) { switch (digit) { /* unsupported, but AT^DTMF=1,22 OK and "2" sent */ case 'a': case 'b': case 'c': case 'd': case 'A': case 'B': case 'C': case 'D': return -1974; // TODO: ??? case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '*': case '#': return at_enqueue_generic(cpvt, CMD_AT_DTMF, 1, "AT^DTMF=%d,%c\r", cpvt->call_idx, digit); } return -1; } /*! * \brief Enqueue the AT+CCWA command (disable call waiting) * \param cpvt -- cpvt structure * \return 0 on success */ EXPORT_DEF int at_enqueue_set_ccwa(struct cpvt *cpvt, unsigned call_waiting) { static const char cmd_ccwa_get[] = "AT+CCWA=1,2,1\r"; static const char cmd_ccwa_set[] = "AT+CCWA=%d,%d,%d\r"; int err; call_waiting_t value; at_queue_cmd_t cmds[] = { /* Set Call-Waiting On/Off */ ATQ_CMD_DECLARE_DYNIT(CMD_AT_CCWA_SET, ATQ_CMD_TIMEOUT_MEDIUM, 0), /* Query CCWA Status for Voice Call */ ATQ_CMD_DECLARE_STIT(CMD_AT_CCWA_STATUS, cmd_ccwa_get, ATQ_CMD_TIMEOUT_MEDIUM, 0), }; at_queue_cmd_t * pcmd = cmds; unsigned count = ITEMS_OF(cmds); if(call_waiting == CALL_WAITING_DISALLOWED || call_waiting == CALL_WAITING_ALLOWED) { value = call_waiting; err = call_waiting == CALL_WAITING_ALLOWED ? 1 : 0; err = at_fill_generic_cmd(&cmds[0], cmd_ccwa_set, err, err, CCWA_CLASS_VOICE); if (err) { chan_dongle_err = E_UNKNOWN; return -1; } } else { value = CALL_WAITING_AUTO; pcmd++; count--; } CONF_SHARED(cpvt->pvt, callwaiting) = value; if (at_queue_insert(cpvt, pcmd, count, 0) != 0) { chan_dongle_err = E_QUEUE; return -1; } return 0; } /*! * \brief Enqueue the device reset command (AT+CFUN Operation Mode Setting) * \param cpvt -- cpvt structure * \return 0 on success */ EXPORT_DEF int at_enqueue_reset(struct cpvt *cpvt) { static const char cmd[] = "AT+CFUN=1,1\r"; static const at_queue_cmd_t at_cmd = ATQ_CMD_DECLARE_ST(CMD_AT_CFUN, cmd); if (at_queue_insert_const(cpvt, &at_cmd, 1, 0) != 0) { chan_dongle_err = E_QUEUE; return -1; } return 0; } /*! * \brief Enqueue a dial commands * \param cpvt -- cpvt structure * \param number -- the called number * \param clir -- value of clir * \return 0 on success */ EXPORT_DEF int at_enqueue_dial(struct cpvt *cpvt, const char *number, int clir) { struct pvt *pvt = cpvt->pvt; int err; int cmdsno = 0; char * tmp = NULL; at_queue_cmd_t cmds[6]; if(PVT_STATE(pvt, chan_count[CALL_STATE_ACTIVE]) > 0 && CPVT_TEST_FLAG(cpvt, CALL_FLAG_HOLD_OTHER)) { ATQ_CMD_INIT_ST(cmds[0], CMD_AT_CHLD_2, cmd_chld2); /* enable this cause response_clcc() see all calls are held and insert 'AT+CHLD=2' ATQ_CMD_INIT_ST(cmds[1], CMD_AT_CLCC, cmd_clcc); */ cmdsno = 1; } if(clir != -1) { err = at_fill_generic_cmd(&cmds[cmdsno], "AT+CLIR=%d\r", clir); if (err) { chan_dongle_err = E_UNKNOWN; return -1; } tmp = cmds[cmdsno].data; ATQ_CMD_INIT_DYNI(cmds[cmdsno], CMD_AT_CLIR); cmdsno++; } err = at_fill_generic_cmd(&cmds[cmdsno], "ATD%s;\r", number); if(err) { ast_free(tmp); chan_dongle_err = E_UNKNOWN; return -1; } ATQ_CMD_INIT_DYNI(cmds[cmdsno], CMD_AT_D); cmdsno++; /* on failed ATD this up held call */ ATQ_CMD_INIT_ST(cmds[cmdsno], CMD_AT_CLCC, cmd_clcc); cmdsno++; if (pvt->has_voice_quectel) { ATQ_CMD_INIT_ST(cmds[cmdsno], CMD_AT_DDSETEX, cmd_qpcmv10); } else { ATQ_CMD_INIT_ST(cmds[cmdsno], CMD_AT_DDSETEX, cmd_ddsetex2); } cmdsno++; if (at_queue_insert(cpvt, cmds, cmdsno, 1) != 0) { chan_dongle_err = E_QUEUE; return -1; } /* set CALL_FLAG_NEED_HANGUP early because ATD may be still in queue while local hangup called */ CPVT_SET_FLAGS(cpvt, CALL_FLAG_NEED_HANGUP); return 0; } /*! * \brief Enqueue a answer commands * \param cpvt -- cpvt structure * \return 0 on success */ EXPORT_DEF int at_enqueue_answer(struct cpvt *cpvt) { pvt_t* pvt = cpvt->pvt; at_queue_cmd_t cmds[2]; unsigned count = 2; /* AT_A + setup-voice */ const char * cmd1; ATQ_CMD_INIT_DYN(cmds[0], CMD_AT_A); if (pvt->has_voice_quectel) { ATQ_CMD_INIT_ST(cmds[1], CMD_AT_DDSETEX, cmd_qpcmv10); } else { ATQ_CMD_INIT_ST(cmds[1], CMD_AT_DDSETEX, cmd_ddsetex2); } if(cpvt->state == CALL_STATE_INCOMING) { /* FIXME: channel number? */ cmd1 = "ATA\r"; } else if(cpvt->state == CALL_STATE_WAITING) { cmds[0].cmd = CMD_AT_CHLD_2x; cmd1 = "AT+CHLD=2%d\r"; /* no need CMD_AT_DDSETEX in this case? */ count--; } else { ast_log (LOG_ERROR, "[%s] Request answer for call idx %d with state '%s'\n", PVT_ID(cpvt->pvt), cpvt->call_idx, call_state2str(cpvt->state)); return -1; } if (at_fill_generic_cmd(&cmds[0], cmd1, cpvt->call_idx) != 0) { chan_dongle_err = E_UNKNOWN; return -1; } if (at_queue_insert(cpvt, cmds, count, 1) != 0) { chan_dongle_err = E_QUEUE; return -1; } return 0; } /*! * \brief Enqueue an activate commands 'Put active calls on hold and activate call x.' * \param cpvt -- cpvt structure * \return 0 on success */ EXPORT_DEF int at_enqueue_activate(struct cpvt *cpvt) { at_queue_cmd_t cmds[] = { ATQ_CMD_DECLARE_DYN(CMD_AT_CHLD_2x), ATQ_CMD_DECLARE_ST(CMD_AT_CLCC, cmd_clcc), }; if (cpvt->state == CALL_STATE_ACTIVE) return 0; if (cpvt->state != CALL_STATE_ONHOLD && cpvt->state != CALL_STATE_WAITING) { ast_log (LOG_ERROR, "[%s] Imposible activate call idx %d from state '%s'\n", PVT_ID(cpvt->pvt), cpvt->call_idx, call_state2str(cpvt->state)); return -1; } if (at_fill_generic_cmd(&cmds[0], "AT+CHLD=2%d\r", cpvt->call_idx) != 0) { chan_dongle_err = E_UNKNOWN; return -1; } if (at_queue_insert(cpvt, cmds, ITEMS_OF(cmds), 1) != 0) { chan_dongle_err = E_QUEUE; return -1; } return 0; } /*! * \brief Enqueue an commands for 'Put active calls on hold and activate the waiting or held call.' * \param pvt -- pvt structure * \return 0 on success */ EXPORT_DEF int at_enqueue_flip_hold(struct cpvt *cpvt) { static const at_queue_cmd_t cmds[] = { ATQ_CMD_DECLARE_ST(CMD_AT_CHLD_2, cmd_chld2), ATQ_CMD_DECLARE_ST(CMD_AT_CLCC, cmd_clcc), }; if (at_queue_insert_const(cpvt, cmds, ITEMS_OF(cmds), 1) != 0) { chan_dongle_err = E_QUEUE; return -1; } return 0; } /*! * \brief Enqueue ping command * \param pvt -- pvt structure * \return 0 on success */ EXPORT_DEF int at_enqueue_ping(struct cpvt *cpvt) { static const at_queue_cmd_t cmds[] = { ATQ_CMD_DECLARE_STIT(CMD_AT, cmd_at, ATQ_CMD_TIMEOUT_SHORT, 0), }; if (at_queue_insert_const(cpvt, cmds, ITEMS_OF(cmds), 1) != 0) { chan_dongle_err = E_QUEUE; return -1; } return 0; } /*! * \brief Enqueue user-specified command * \param cpvt -- cpvt structure * \param input -- user's command * \return 0 on success */ EXPORT_DEF int at_enqueue_user_cmd(struct cpvt *cpvt, const char *input) { if (at_enqueue_generic(cpvt, CMD_USER, 1, "%s\r", input) != 0) { chan_dongle_err = E_QUEUE; return -1; } return 0; } /*! * \brief Start reading next SMS, if any * \param cpvt -- cpvt structure */ EXPORT_DEF void at_retrieve_next_sms(struct cpvt *cpvt, at_cmd_suppress_error_t suppress_error) { pvt_t *pvt = cpvt->pvt; unsigned int i; if (pvt->incoming_sms_index != -1U) { /* clear SMS index */ i = pvt->incoming_sms_index; pvt->incoming_sms_index = -1U; /* clear this message index from inbox */ sms_inbox_clear(pvt, i); } /* get next message to fetch from inbox */ for (i = 0; i != SMS_INDEX_MAX; i++) { if (is_sms_inbox_set(pvt, i)) break; } if (i == SMS_INDEX_MAX || at_enqueue_retrieve_sms(cpvt, i, suppress_error) != 0) { pvt_try_restate(pvt); } } /*! * \brief Enqueue commands for reading SMS * \param cpvt -- cpvt structure * \param index -- index of message in store * \return 0 on success */ EXPORT_DEF int at_enqueue_retrieve_sms(struct cpvt *cpvt, int index, at_cmd_suppress_error_t suppress_error) { pvt_t *pvt = cpvt->pvt; int err; at_queue_cmd_t cmds[] = { ATQ_CMD_DECLARE_DYN2(CMD_AT_CMGR, RES_CMGR), }; unsigned cmdsno = ITEMS_OF(cmds); if (suppress_error == SUPPRESS_ERROR_ENABLED) { cmds[0].flags |= ATQ_CMD_FLAG_SUPPRESS_ERROR; } /* set that we want to receive this message */ if (!sms_inbox_set(pvt, index)) { chan_dongle_err = E_UNKNOWN; return -1; } /* check if message is already being received */ if (pvt->incoming_sms_index != -1U) { ast_debug (4, "[%s] SMS retrieve of [%d] already in progress\n", PVT_ID(pvt), pvt->incoming_sms_index); return 0; } pvt->incoming_sms_index = index; err = at_fill_generic_cmd (&cmds[0], "AT+CMGR=%d\r", index); if (err) goto error; err = at_queue_insert (cpvt, cmds, cmdsno, 0); if (err) goto error; return 0; error: ast_log (LOG_WARNING, "[%s] SMS command error %d\n", PVT_ID(pvt), err); pvt->incoming_sms_index = -1U; chan_dongle_err = E_UNKNOWN; return -1; } /*! * \brief Enqueue commands for deleting SMS * \param cpvt -- cpvt structure * \param index -- index of message in store * \return 0 on success */ EXPORT_DEF int at_enqueue_delete_sms(struct cpvt *cpvt, int index) { int err; at_queue_cmd_t cmds[] = { ATQ_CMD_DECLARE_DYN(CMD_AT_CMGD) }; unsigned cmdsno = ITEMS_OF(cmds); err = at_fill_generic_cmd (&cmds[0], "AT+CMGD=%d\r", index); if (err) { chan_dongle_err = E_UNKNOWN; return err; } if (at_queue_insert(cpvt, cmds, cmdsno, 0) != 0) { chan_dongle_err = E_QUEUE; return -1; } return 0; } /*! * \brief Enqueue AT+CHLD1x or AT+CHUP hangup command * \param cpvt -- channel_pvt structure * \param call_idx -- call id * \return 0 on success */ EXPORT_DEF int at_enqueue_hangup(struct cpvt *cpvt, int call_idx) { /* this try of hangup non-active (held) channel as workaround for HW BUG 2 int err; at_queue_cmd_t cmds[] = { ATQ_CMD_DECLARE_ST(CMD_AT_CHLD_2, cmd_chld2), ATQ_CMD_DECLARE_DYN(CMD_AT_CHLD_1x), }; at_queue_cmd_t * pcmds = cmds; unsigned count = ITEMS_OF(cmds); err = at_fill_generic_cmd(&cmds[1], "AT+CHLD=1%d\r", call_idx); if(err) return err; if(cpvt->state != CALL_STATE_ACTIVE) { pcmds++; count--; } return at_queue_insert(cpvt, pcmds, count, 1); */ /* HW BUG 1: Sequence ATDnum; OK ^ORIG:1,0 AT+CHLD=11 if this command write to modem E1550 before ^CONF: for ATD device no more write responses to any entered command at all ^CONF:1 Workaround a) send AT+CHUP if possible (single call) b) insert fake empty command after ATD expected ^CONF: response if CONF not received yet HW BUG 2: Sequence ATDnum1; OK ^ORIG:1,0 ^CONF:1 ^CONN:1,0 AT+CHLD=2 OK ATDnum2; OK ^ORIG:2,0 ^CONF:2 ^CONN:2,0 AT+CHLD=11 after this command call 1 terminated, but call 2 no voice data and any other new calls created OK ^CEND:1,... same result if active call terminated with AT+CHLD=12 same result if active call terminated by peer side1 Workaround not found yes */ /* static const struct { at_cmd_t cmd; const char *data; } commands[] = { { CMD_AT_CHUP, "AT+CHUP\r" }, { CMD_AT_CHLD_1x, "AT+CHLD=1%d\r" } }; int idx = 0; if(cpvt == &cpvt->pvt->sys_chan || CPVT_TEST_FLAGS(cpvt, CALL_FLAG_CONF_DONE|CALL_FLAG_IDX_VALID)) { if(cpvt->pvt->chansno > 1) idx = 1; } return at_enqueue_generic(cpvt, commands[idx].cmd, 1, commands[idx].data, call_idx); */ static const char cmd_chup[] = "AT+CHUP\r"; struct pvt* pvt = cpvt->pvt; int err; at_queue_cmd_t cmds[] = { ATQ_CMD_DECLARE_ST(CMD_AT_CHUP, cmd_chup), ATQ_CMD_DECLARE_ST(CMD_AT_CLCC, cmd_clcc), }; if(cpvt == &pvt->sys_chan || cpvt->dir == CALL_DIR_INCOMING || (cpvt->state != CALL_STATE_INIT && cpvt->state != CALL_STATE_DIALING)) { /* FIXME: other channels may be in RELEASED or INIT state */ if(PVT_STATE(pvt, chansno) > 1) { cmds[0].cmd = CMD_AT_CHLD_1x; err = at_fill_generic_cmd(&cmds[0], cmd_chld1x, call_idx); if (err) { chan_dongle_err = E_UNKNOWN; return -1; } } } /* early AT+CHUP before ^ORIG for outgoing call may not get ^CEND in future */ if(cpvt->state == CALL_STATE_INIT) pvt->last_dialed_cpvt = 0; if (at_queue_insert(cpvt, cmds, ITEMS_OF(cmds), 1) != 0) { chan_dongle_err = E_QUEUE; return -1; } return 0; } /*! * \brief Enqueue AT+CLVL commands for volume synchronization * \param cpvt -- cpvt structure * \return 0 on success */ EXPORT_DEF int at_enqueue_volsync(struct cpvt *cpvt) { static const char cmd1[] = "AT+CLVL=1\r"; static const char cmd2[] = "AT+CLVL=5\r"; static const at_queue_cmd_t cmds[] = { ATQ_CMD_DECLARE_ST(CMD_AT_CLVL, cmd1), ATQ_CMD_DECLARE_ST(CMD_AT_CLVL, cmd2), }; if (at_queue_insert_const(cpvt, cmds, ITEMS_OF(cmds), 1) != 0) { chan_dongle_err = E_QUEUE; return -1; } return 0; } /*! * \brief Enqueue AT+CLCC command * \param cpvt -- cpvt structure * \return 0 on success */ EXPORT_DEF int at_enqueue_clcc(struct cpvt *cpvt) { static const at_queue_cmd_t at_cmd = ATQ_CMD_DECLARE_ST(CMD_AT_CLCC, cmd_clcc); if (at_queue_insert_const(cpvt, &at_cmd, 1, 1) != 0) { chan_dongle_err = E_QUEUE; return -1; } return 0; } /*! * \brief Enqueue AT+CHLD=3 command * \param cpvt -- cpvt structure * \return 0 on success */ EXPORT_DEF int at_enqueue_conference(struct cpvt *cpvt) { static const char cmd_chld3[] = "AT+CHLD=3\r"; static const at_queue_cmd_t cmds[] = { ATQ_CMD_DECLARE_ST(CMD_AT_CHLD_3, cmd_chld3), ATQ_CMD_DECLARE_ST(CMD_AT_CLCC, cmd_clcc), }; if (at_queue_insert_const(cpvt, cmds, ITEMS_OF(cmds), 1) != 0) { chan_dongle_err = E_QUEUE; return -1; } return 0; } /*! * \brief SEND AT+CHUP command to device IMMEDIALITY * \param cpvt -- cpvt structure */ EXPORT_DEF void at_hangup_immediality(struct cpvt* cpvt) { char buf[20]; int length = snprintf(buf, sizeof(buf), cmd_chld1x, cpvt->call_idx); if(length > 0) at_write(cpvt->pvt, buf, length); }