blob: f607528bfa5f7e77ab5b8110a8aa3e974612443c [file] [log] [blame]
/******************************************************************************
*
* Copyright (c) 2014 The Android Open Source Project
* Copyright (C) 2003-2012 Broadcom Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
#define LOG_TAG "bt_hf_client"
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "bta_hf_client_api.h"
#include "bta_hf_client_int.h"
#include "osi/include/log.h"
#include "osi/include/osi.h"
#include "port_api.h"
/* Uncomment to enable AT traffic dumping */
/* #define BTA_HF_CLIENT_AT_DUMP 1 */
/* minimum length of AT event */
#define BTA_HF_CLIENT_AT_EVENT_MIN_LEN 3
/* timeout (in milliseconds) for AT response */
#define BTA_HF_CLIENT_AT_TIMEOUT 29989
/* timeout (in milliseconds) for AT hold timer */
#define BTA_HF_CLIENT_AT_HOLD_TIMEOUT 41
/******************************************************************************
*
* DATA TYPES AND CONTAINERS
*
******************************************************************************/
/* BRSF: store received values here */
extern tBTA_HF_CLIENT_CB bta_hf_client_cb;
extern fixed_queue_t* btu_bta_alarm_queue;
/******************************************************************************
* SUPPORTED EVENT MESSAGES
******************************************************************************/
/* CIND: supported indicator names */
#define BTA_HF_CLIENT_INDICATOR_BATTERYCHG "battchg"
#define BTA_HF_CLIENT_INDICATOR_SIGNAL "signal"
#define BTA_HF_CLIENT_INDICATOR_SERVICE "service"
#define BTA_HF_CLIENT_INDICATOR_CALL "call"
#define BTA_HF_CLIENT_INDICATOR_ROAM "roam"
#define BTA_HF_CLIENT_INDICATOR_CALLSETUP "callsetup"
#define BTA_HF_CLIENT_INDICATOR_CALLHELD "callheld"
#define MIN(a, b) \
({ \
__typeof__(a) _a = (a); \
__typeof__(b) _b = (b); \
(_a < _b) ? _a : _b; \
})
/* CIND: represents each indicators boundaries */
typedef struct {
const char* name;
uint8_t min;
uint8_t max;
uint8_t namelen;
} tBTA_HF_CLIENT_INDICATOR;
#define BTA_HF_CLIENT_AT_SUPPORTED_INDICATOR_COUNT 7
/* CIND: storage room for indicators value range and their statuses */
static const tBTA_HF_CLIENT_INDICATOR
bta_hf_client_indicators[BTA_HF_CLIENT_AT_SUPPORTED_INDICATOR_COUNT] = {
/* name | min | max | name length -
used by parser */
{BTA_HF_CLIENT_INDICATOR_BATTERYCHG, 0, 5,
sizeof(BTA_HF_CLIENT_INDICATOR_BATTERYCHG)},
{BTA_HF_CLIENT_INDICATOR_SIGNAL, 0, 5,
sizeof(BTA_HF_CLIENT_INDICATOR_SIGNAL)},
{BTA_HF_CLIENT_INDICATOR_SERVICE, 0, 1,
sizeof(BTA_HF_CLIENT_INDICATOR_SERVICE)},
{BTA_HF_CLIENT_INDICATOR_CALL, 0, 1,
sizeof(BTA_HF_CLIENT_INDICATOR_CALL)},
{BTA_HF_CLIENT_INDICATOR_ROAM, 0, 1,
sizeof(BTA_HF_CLIENT_INDICATOR_ROAM)},
{BTA_HF_CLIENT_INDICATOR_CALLSETUP, 0, 3,
sizeof(BTA_HF_CLIENT_INDICATOR_CALLSETUP)},
{BTA_HF_CLIENT_INDICATOR_CALLHELD, 0, 2,
sizeof(BTA_HF_CLIENT_INDICATOR_CALLHELD)}};
/* +VGM/+VGS - gain min/max values */
#define BTA_HF_CLIENT_VGS_MIN 0
#define BTA_HF_CLIENT_VGS_MAX 15
#define BTA_HF_CLIENT_VGM_MIN 0
#define BTA_HF_CLIENT_VGM_MAX 15
uint32_t service_index = 0;
bool service_availability = true;
/* helper functions for handling AT commands queueing */
static void bta_hf_client_handle_ok();
static void bta_hf_client_clear_queued_at(void) {
tBTA_HF_CLIENT_AT_QCMD* cur = bta_hf_client_cb.scb.at_cb.queued_cmd;
tBTA_HF_CLIENT_AT_QCMD* next;
while (cur != NULL) {
next = cur->next;
osi_free(cur);
cur = next;
}
bta_hf_client_cb.scb.at_cb.queued_cmd = NULL;
}
static void bta_hf_client_queue_at(tBTA_HF_CLIENT_AT_CMD cmd, const char* buf,
uint16_t buf_len) {
tBTA_HF_CLIENT_AT_QCMD* new_cmd =
(tBTA_HF_CLIENT_AT_QCMD*)osi_malloc(sizeof(tBTA_HF_CLIENT_AT_QCMD));
APPL_TRACE_DEBUG("%s", __func__);
new_cmd->cmd = cmd;
new_cmd->buf_len = buf_len;
new_cmd->next = NULL;
memcpy(new_cmd->buf, buf, buf_len);
if (bta_hf_client_cb.scb.at_cb.queued_cmd != NULL) {
tBTA_HF_CLIENT_AT_QCMD* qcmd = bta_hf_client_cb.scb.at_cb.queued_cmd;
while (qcmd->next != NULL) qcmd = qcmd->next;
qcmd->next = new_cmd;
} else {
bta_hf_client_cb.scb.at_cb.queued_cmd = new_cmd;
}
}
static void bta_hf_client_at_resp_timer_cback(UNUSED_ATTR void* data) {
if (bta_hf_client_cb.scb.at_cb.current_cmd == BTA_HF_CLIENT_AT_CNUM) {
LOG_INFO(LOG_TAG, "%s timed out waiting for AT+CNUM response; spoofing OK.",
__func__);
bta_hf_client_handle_ok();
} else {
APPL_TRACE_ERROR("HFPClient: AT response timeout, disconnecting");
bta_hf_client_sm_execute(BTA_HF_CLIENT_API_CLOSE_EVT, NULL);
}
}
static void bta_hf_client_start_at_resp_timer(void) {
alarm_set_on_queue(
bta_hf_client_cb.scb.at_cb.resp_timer, BTA_HF_CLIENT_AT_TIMEOUT,
bta_hf_client_at_resp_timer_cback, NULL, btu_bta_alarm_queue);
}
static void bta_hf_client_stop_at_resp_timer(void) {
alarm_cancel(bta_hf_client_cb.scb.at_cb.resp_timer);
}
static void bta_hf_client_send_at(tBTA_HF_CLIENT_AT_CMD cmd, const char* buf,
uint16_t buf_len) {
if ((bta_hf_client_cb.scb.at_cb.current_cmd == BTA_HF_CLIENT_AT_NONE ||
bta_hf_client_cb.scb.svc_conn == false) &&
!alarm_is_scheduled(bta_hf_client_cb.scb.at_cb.hold_timer)) {
uint16_t len;
#ifdef BTA_HF_CLIENT_AT_DUMP
APPL_TRACE_DEBUG("%s %.*s", __func__, buf_len - 1, buf);
#endif
bta_hf_client_cb.scb.at_cb.current_cmd = cmd;
/* Generate fake responses for these because they won't reliably work */
if (!service_availability &&
(cmd == BTA_HF_CLIENT_AT_CNUM || cmd == BTA_HF_CLIENT_AT_COPS)) {
APPL_TRACE_WARNING("%s: No service, skipping %d command", __func__, cmd);
bta_hf_client_handle_ok();
return;
}
PORT_WriteData(bta_hf_client_cb.scb.conn_handle, buf, buf_len, &len);
bta_hf_client_start_at_resp_timer();
return;
}
bta_hf_client_queue_at(cmd, buf, buf_len);
}
static void bta_hf_client_send_queued_at(void) {
tBTA_HF_CLIENT_AT_QCMD* cur = bta_hf_client_cb.scb.at_cb.queued_cmd;
APPL_TRACE_DEBUG("%s", __func__);
if (cur != NULL) {
bta_hf_client_cb.scb.at_cb.queued_cmd = cur->next;
bta_hf_client_send_at(cur->cmd, cur->buf, cur->buf_len);
osi_free(cur);
}
}
static void bta_hf_client_at_hold_timer_cback(UNUSED_ATTR void* data) {
APPL_TRACE_DEBUG("%s", __func__);
bta_hf_client_send_queued_at();
}
static void bta_hf_client_stop_at_hold_timer(void) {
APPL_TRACE_DEBUG("%s", __func__);
alarm_cancel(bta_hf_client_cb.scb.at_cb.hold_timer);
}
static void bta_hf_client_start_at_hold_timer(void) {
APPL_TRACE_DEBUG("%s", __func__);
alarm_set_on_queue(
bta_hf_client_cb.scb.at_cb.hold_timer, BTA_HF_CLIENT_AT_HOLD_TIMEOUT,
bta_hf_client_at_hold_timer_cback, NULL, btu_bta_alarm_queue);
}
/******************************************************************************
*
* COMMON AT EVENT HANDLING funcS
*
* Receives data (strings, ints, etc.) from the parser and processes this
* data. No buffer parsing is being done here.
******************************************************************************/
static void bta_hf_client_handle_ok() {
APPL_TRACE_DEBUG("%s", __func__);
bta_hf_client_stop_at_resp_timer();
if (!bta_hf_client_cb.scb.svc_conn) {
bta_hf_client_slc_seq(false);
return;
}
switch (bta_hf_client_cb.scb.at_cb.current_cmd) {
case BTA_HF_CLIENT_AT_BIA:
case BTA_HF_CLIENT_AT_BCC:
break;
case BTA_HF_CLIENT_AT_BCS:
bta_hf_client_start_at_hold_timer();
bta_hf_client_cb.scb.at_cb.current_cmd = BTA_HF_CLIENT_AT_NONE;
return;
case BTA_HF_CLIENT_AT_CLIP: // last cmd is post slc seq
if (bta_hf_client_cb.scb.send_at_reply == false) {
bta_hf_client_cb.scb.send_at_reply = true;
}
break;
case BTA_HF_CLIENT_AT_NONE:
bta_hf_client_stop_at_hold_timer();
break;
default:
if (bta_hf_client_cb.scb.send_at_reply) {
bta_hf_client_at_result(BTA_HF_CLIENT_AT_RESULT_OK, 0);
}
break;
}
bta_hf_client_cb.scb.at_cb.current_cmd = BTA_HF_CLIENT_AT_NONE;
bta_hf_client_send_queued_at();
}
static void bta_hf_client_handle_error(tBTA_HF_CLIENT_AT_RESULT_TYPE type,
uint16_t cme) {
APPL_TRACE_DEBUG("%s %u %u", __func__, type, cme);
bta_hf_client_stop_at_resp_timer();
if (!bta_hf_client_cb.scb.svc_conn) {
bta_hf_client_slc_seq(true);
return;
}
switch (bta_hf_client_cb.scb.at_cb.current_cmd) {
case BTA_HF_CLIENT_AT_BIA:
break;
case BTA_HF_CLIENT_AT_BCC:
case BTA_HF_CLIENT_AT_BCS:
bta_hf_client_cback_sco(BTA_HF_CLIENT_AUDIO_CLOSE_EVT);
break;
case BTA_HF_CLIENT_AT_CLIP: // last cmd is post slc seq
if (bta_hf_client_cb.scb.send_at_reply == false) {
bta_hf_client_cb.scb.send_at_reply = true;
}
break;
default:
if (bta_hf_client_cb.scb.send_at_reply) {
bta_hf_client_at_result(type, cme);
}
break;
}
bta_hf_client_cb.scb.at_cb.current_cmd = BTA_HF_CLIENT_AT_NONE;
bta_hf_client_send_queued_at();
}
static void bta_hf_client_handle_ring() {
APPL_TRACE_DEBUG("%s", __func__);
bta_hf_client_evt_val(BTA_HF_CLIENT_RING_INDICATION, 0);
}
static void bta_hf_client_handle_brsf(uint32_t value) {
APPL_TRACE_DEBUG("%s 0x%x", __func__, value);
bta_hf_client_cb.scb.peer_features = value;
}
/* handles a single indicator descriptor - registers it for value changing
* events */
static void bta_hf_client_handle_cind_list_item(char* name, uint32_t min,
uint32_t max, uint32_t index) {
uint8_t i = 0;
APPL_TRACE_DEBUG("%s %lu.%s <%lu:%lu>", __func__, index, name, min, max);
/* look for a matching indicator on list of supported ones */
for (i = 0; i < BTA_HF_CLIENT_AT_SUPPORTED_INDICATOR_COUNT; i++) {
if (strcmp(name, BTA_HF_CLIENT_INDICATOR_SERVICE) == 0) {
service_index = index;
}
/* look for a match - search one sign further than indicators name to check
* for string end */
/* It will distinguish 'callheld' which could be matched by strncmp as
* 'call'. */
if (strncmp(name, bta_hf_client_indicators[i].name,
bta_hf_client_indicators[i].namelen) != 0)
continue;
/* index - enumerates value position in the incoming sequence */
/* if name matches one of the known indicators, add its incoming position */
/* to lookup table for easy value->indicator matching later, when only
* values come */
bta_hf_client_cb.scb.at_cb.indicator_lookup[index] = i;
return;
}
}
static void bta_hf_client_handle_cind_value(uint32_t index, uint32_t value) {
APPL_TRACE_DEBUG("%s index: %u value: %u", __func__, index, value);
if (index >= BTA_HF_CLIENT_AT_INDICATOR_COUNT) {
return;
}
if (service_index == index) {
if (value == 0) {
service_availability = false;
} else {
service_availability = true;
}
}
if (bta_hf_client_cb.scb.at_cb.indicator_lookup[index] == -1) {
return;
}
/* get the real array index from lookup table */
index = bta_hf_client_cb.scb.at_cb.indicator_lookup[index];
/* Ignore out of range values */
if (value > bta_hf_client_indicators[index].max ||
value < bta_hf_client_indicators[index].min) {
return;
}
/* tBTA_HF_CLIENT_IND_TYPE match index in bta_hf_client_indicators */
bta_hf_client_ind(index, value);
}
static void bta_hf_client_handle_chld(uint32_t mask) {
APPL_TRACE_DEBUG("%s 0x%x", __func__, mask);
bta_hf_client_cb.scb.chld_features |= mask;
}
static void bta_hf_client_handle_ciev(uint32_t index, uint32_t value) {
int8_t realind = -1;
APPL_TRACE_DEBUG("%s index: %u value: %u", __func__, index, value);
if (index == 0 || index > BTA_HF_CLIENT_AT_INDICATOR_COUNT) {
return;
}
if (service_index == index - 1) {
service_availability = value == 0 ? false : true;
}
realind = bta_hf_client_cb.scb.at_cb.indicator_lookup[index - 1];
if (realind >= 0 && realind < BTA_HF_CLIENT_AT_SUPPORTED_INDICATOR_COUNT) {
/* get the real in-array index from lookup table by index it comes at */
/* if there is no bug it should automatically be correctly calculated */
if (value > bta_hf_client_indicators[realind].max ||
value < bta_hf_client_indicators[realind].min) {
return;
}
/* update service availability on +ciev from AG. */
if (service_index == (index - 1)) {
if (value == 1) {
service_availability = true;
} else {
service_availability = false;
}
}
/* tBTA_HF_CLIENT_IND_TYPE match index in bta_hf_client_indicators */
bta_hf_client_ind(realind, value);
}
}
static void bta_hf_client_handle_bcs(uint32_t codec) {
APPL_TRACE_DEBUG("%s %u", __func__, codec);
if (codec == BTM_SCO_CODEC_CVSD ||
(codec == BTM_SCO_CODEC_MSBC && bta_hf_client_cb.msbc_enabled == true)) {
bta_hf_client_cb.scb.negotiated_codec = codec;
bta_hf_client_send_at_bcs(codec);
} else {
bta_hf_client_cb.scb.negotiated_codec = BTM_SCO_CODEC_CVSD;
bta_hf_client_send_at_bac();
}
}
static void bta_hf_client_handle_bsir(uint32_t provided) {
APPL_TRACE_DEBUG("%s %u", __func__, provided);
bta_hf_client_evt_val(BTA_HF_CLIENT_BSIR_EVT, provided);
}
static void bta_hf_client_handle_cmeerror(uint32_t code) {
bta_hf_client_handle_error(BTA_HF_CLIENT_AT_RESULT_CME, code);
}
static void bta_hf_client_handle_vgm(uint32_t value) {
APPL_TRACE_DEBUG("%s %lu", __func__, value);
if (value <= BTA_HF_CLIENT_VGM_MAX) {
bta_hf_client_evt_val(BTA_HF_CLIENT_MIC_EVT, value);
}
}
static void bta_hf_client_handle_vgs(uint32_t value) {
APPL_TRACE_DEBUG("%s %lu", __func__, value);
if (value <= BTA_HF_CLIENT_VGS_MAX) {
bta_hf_client_evt_val(BTA_HF_CLIENT_SPK_EVT, value);
}
}
static void bta_hf_client_handle_bvra(uint32_t value) {
APPL_TRACE_DEBUG("%s %lu", __func__, value);
if (value > 1) {
return;
}
bta_hf_client_evt_val(BTA_HF_CLIENT_VOICE_REC_EVT, value);
}
static void bta_hf_client_handle_clip(char* numstr, uint32_t type) {
APPL_TRACE_DEBUG("%s %u %s", __func__, type, numstr);
bta_hf_client_clip(numstr);
}
static void bta_hf_client_handle_ccwa(char* numstr, uint32_t type) {
APPL_TRACE_DEBUG("%s %u %s", __func__, type, numstr);
bta_hf_client_ccwa(numstr);
}
static void bta_hf_client_handle_cops(char* opstr, uint32_t mode) {
APPL_TRACE_DEBUG("%s %u %s", __func__, mode, opstr);
bta_hf_client_operator_name(opstr);
}
static void bta_hf_client_handle_binp(char* numstr) {
APPL_TRACE_DEBUG("%s %s", __func__, numstr);
bta_hf_client_binp(numstr);
}
static void bta_hf_client_handle_clcc(uint16_t idx, uint16_t dir,
uint16_t status, uint16_t mode,
uint16_t mpty, char* numstr,
uint16_t type) {
APPL_TRACE_DEBUG("%s idx: %u dir: %u status: %u mode: %u mpty: %u", __func__,
idx, dir, status, mode, mpty);
if (numstr) {
APPL_TRACE_DEBUG("%s number: %s type: %u", __func__, numstr, type);
}
bta_hf_client_clcc(idx, dir, status, mpty, numstr);
}
static void bta_hf_client_handle_cnum(char* numstr, uint16_t type,
uint16_t service) {
APPL_TRACE_DEBUG("%s number: %s type: %u service: %u", __func__, numstr, type,
service);
/* TODO: should number be modified according to type? */
bta_hf_client_cnum(numstr, service);
}
static void bta_hf_client_handle_btrh(uint16_t code) {
APPL_TRACE_DEBUG("%s %lu", __func__, code);
bta_hf_client_evt_val(BTA_HF_CLIENT_BTRH_EVT, code);
}
/******************************************************************************
*
* COMMON AT EVENTS PARSING FUNCTIONS
*
******************************************************************************/
/* Check if prefix match and skip spaces if any */
#define AT_CHECK_EVENT(buf, event) \
do { \
if (strncmp("\r\n" event, buf, sizeof("\r\n" event) - 1) != 0) return buf; \
(buf) += sizeof("\r\n" event) - 1; \
while (*(buf) == ' ') (buf)++; \
} while (0)
/* check for <cr><lf> and forward buffer if match */
#define AT_CHECK_RN(buf) \
do { \
if (strncmp("\r\n", buf, sizeof("\r\n") - 1) != 0) { \
APPL_TRACE_DEBUG("%s missing end <cr><lf>", __func__); \
return NULL; \
} \
(buf) += sizeof("\r\n") - 1; \
} while (0)
/* skip rest of AT string up to <cr> */
#define AT_SKIP_REST(buf) \
do { \
while (*(buf) != '\r') (buf)++; \
} while (0)
static char* bta_hf_client_parse_ok(char* buffer) {
AT_CHECK_EVENT(buffer, "OK");
AT_CHECK_RN(buffer);
bta_hf_client_handle_ok();
return buffer;
}
static char* bta_hf_client_parse_error(char* buffer) {
AT_CHECK_EVENT(buffer, "ERROR");
AT_CHECK_RN(buffer);
bta_hf_client_handle_error(BTA_HF_CLIENT_AT_RESULT_ERROR, 0);
return buffer;
}
static char* bta_hf_client_parse_ring(char* buffer) {
AT_CHECK_EVENT(buffer, "RING");
AT_CHECK_RN(buffer);
bta_hf_client_handle_ring();
return buffer;
}
/* generic uint32 parser */
static char* bta_hf_client_parse_uint32(char* buffer,
void (*handler_callback)(uint32_t)) {
uint32_t value;
int res;
int offset;
res = sscanf(buffer, "%u%n", &value, &offset);
if (res < 1) {
return NULL;
}
buffer += offset;
AT_CHECK_RN(buffer);
handler_callback(value);
return buffer;
}
static char* bta_hf_client_parse_brsf(char* buffer) {
AT_CHECK_EVENT(buffer, "+BRSF:");
return bta_hf_client_parse_uint32(buffer, bta_hf_client_handle_brsf);
}
static char* bta_hf_client_parse_cind_values(char* buffer) {
/* value and its position */
uint16_t index = 0;
uint32_t value = 0;
int offset;
int res;
while ((res = sscanf(buffer, "%u%n", &value, &offset)) > 0) {
/* decides if its valid index and value, if yes stores it */
bta_hf_client_handle_cind_value(index, value);
buffer += offset;
/* check if more values are present */
if (*buffer != ',') {
break;
}
index++;
buffer++;
}
if (res > 0) {
AT_CHECK_RN(buffer);
return buffer;
}
return NULL;
}
static char* bta_hf_client_parse_cind_list(char* buffer) {
int offset = 0;
char name[129];
uint32_t min, max;
uint32_t index = 0;
int res;
while ((res = sscanf(buffer, "(\"%128[^\"]\",(%u%*[-,]%u))%n", name, &min,
&max, &offset)) > 2) {
bta_hf_client_handle_cind_list_item(name, min, max, index);
if (offset == 0) {
APPL_TRACE_ERROR("%s: Format Error %s", __func__, buffer);
return NULL;
}
buffer += offset;
index++;
if (*buffer != ',') {
break;
}
buffer++;
}
if (res > 2) {
AT_CHECK_RN(buffer);
return buffer;
}
return NULL;
}
static char* bta_hf_client_parse_cind(char* buffer) {
AT_CHECK_EVENT(buffer, "+CIND:");
if (*buffer == '(') return bta_hf_client_parse_cind_list(buffer);
return bta_hf_client_parse_cind_values(buffer);
}
static char* bta_hf_client_parse_chld(char* buffer) {
AT_CHECK_EVENT(buffer, "+CHLD:");
if (*buffer != '(') {
return NULL;
}
buffer++;
while (*buffer != '\0') {
if (strncmp("0", buffer, 1) == 0) {
bta_hf_client_handle_chld(BTA_HF_CLIENT_CHLD_REL);
buffer++;
} else if (strncmp("1x", buffer, 2) == 0) {
bta_hf_client_handle_chld(BTA_HF_CLIENT_CHLD_REL_X);
buffer += 2;
} else if (strncmp("1", buffer, 1) == 0) {
bta_hf_client_handle_chld(BTA_HF_CLIENT_CHLD_REL_ACC);
buffer++;
} else if (strncmp("2x", buffer, 2) == 0) {
bta_hf_client_handle_chld(BTA_HF_CLIENT_CHLD_PRIV_X);
buffer += 2;
} else if (strncmp("2", buffer, 1) == 0) {
bta_hf_client_handle_chld(BTA_HF_CLIENT_CHLD_HOLD_ACC);
buffer++;
} else if (strncmp("3", buffer, 1) == 0) {
bta_hf_client_handle_chld(BTA_HF_CLIENT_CHLD_MERGE);
buffer++;
} else if (strncmp("4", buffer, 1) == 0) {
bta_hf_client_handle_chld(BTA_HF_CLIENT_CHLD_MERGE_DETACH);
buffer++;
} else {
return NULL;
}
if (*buffer == ',') {
buffer++;
continue;
}
if (*buffer == ')') {
buffer++;
break;
}
return NULL;
}
AT_CHECK_RN(buffer);
return buffer;
}
static char* bta_hf_client_parse_ciev(char* buffer) {
uint32_t index, value;
int res;
int offset = 0;
AT_CHECK_EVENT(buffer, "+CIEV:");
res = sscanf(buffer, "%u,%u%n", &index, &value, &offset);
if (res < 2) {
return NULL;
}
if (offset == 0) {
APPL_TRACE_ERROR("%s: Format Error %s", __func__, buffer);
return NULL;
}
buffer += offset;
AT_CHECK_RN(buffer);
bta_hf_client_handle_ciev(index, value);
return buffer;
}
static char* bta_hf_client_parse_bcs(char* buffer) {
AT_CHECK_EVENT(buffer, "+BCS:");
return bta_hf_client_parse_uint32(buffer, bta_hf_client_handle_bcs);
}
static char* bta_hf_client_parse_bsir(char* buffer) {
AT_CHECK_EVENT(buffer, "+BSIR:");
return bta_hf_client_parse_uint32(buffer, bta_hf_client_handle_bsir);
}
static char* bta_hf_client_parse_cmeerror(char* buffer) {
AT_CHECK_EVENT(buffer, "+CME ERROR:");
return bta_hf_client_parse_uint32(buffer, bta_hf_client_handle_cmeerror);
}
static char* bta_hf_client_parse_vgm(char* buffer) {
AT_CHECK_EVENT(buffer, "+VGM:");
return bta_hf_client_parse_uint32(buffer, bta_hf_client_handle_vgm);
}
static char* bta_hf_client_parse_vgme(char* buffer) {
AT_CHECK_EVENT(buffer, "+VGM=");
return bta_hf_client_parse_uint32(buffer, bta_hf_client_handle_vgm);
}
static char* bta_hf_client_parse_vgs(char* buffer) {
AT_CHECK_EVENT(buffer, "+VGS:");
return bta_hf_client_parse_uint32(buffer, bta_hf_client_handle_vgs);
}
static char* bta_hf_client_parse_vgse(char* buffer) {
AT_CHECK_EVENT(buffer, "+VGS=");
return bta_hf_client_parse_uint32(buffer, bta_hf_client_handle_vgs);
}
static char* bta_hf_client_parse_bvra(char* buffer) {
AT_CHECK_EVENT(buffer, "+BVRA:");
return bta_hf_client_parse_uint32(buffer, bta_hf_client_handle_bvra);
}
static char* bta_hf_client_parse_clip(char* buffer) {
/* spec forces 32 chars, plus \0 here */
char number[33];
uint32_t type = 0;
int res;
int offset = 0;
AT_CHECK_EVENT(buffer, "+CLIP:");
/* there might be something more after %lu but HFP doesn't care */
res = sscanf(buffer, "\"%32[^\"]\",%u%n", number, &type, &offset);
if (res < 2) {
return NULL;
}
if (offset == 0) {
APPL_TRACE_ERROR("%s: Format Error %s", __func__, buffer);
return NULL;
}
buffer += offset;
AT_SKIP_REST(buffer);
AT_CHECK_RN(buffer);
bta_hf_client_handle_clip(number, type);
return buffer;
}
/* in HFP context there is no difference between ccwa and clip */
static char* bta_hf_client_parse_ccwa(char* buffer) {
/* ac to spec 32 chars max, plus \0 here */
char number[33];
uint32_t type = 0;
int res;
int offset = 0;
AT_CHECK_EVENT(buffer, "+CCWA:");
/* there might be something more after %lu but HFP doesn't care */
res = sscanf(buffer, "\"%32[^\"]\",%u%n", number, &type, &offset);
if (res < 2) {
return NULL;
}
if (offset == 0) {
APPL_TRACE_ERROR("%s: Format Error %s", __func__, buffer);
return NULL;
}
buffer += offset;
AT_SKIP_REST(buffer);
AT_CHECK_RN(buffer);
bta_hf_client_handle_ccwa(number, type);
return buffer;
}
static char* bta_hf_client_parse_cops(char* buffer) {
uint8_t mode;
/* spec forces 16 chars max, plus \0 here */
char opstr[17];
int res;
int offset = 0;
AT_CHECK_EVENT(buffer, "+COPS:");
/* TODO: Not sure if operator string actually can contain escaped " char
* inside */
res = sscanf(buffer, "%hhi,0,\"%16[^\"]\"%n", &mode, opstr, &offset);
if (res < 2) {
return NULL;
}
/* Abort in case offset not set because of format error */
if (offset == 0) {
APPL_TRACE_ERROR("%s: Format Error %s", __func__, buffer);
return NULL;
}
buffer += offset;
AT_SKIP_REST(buffer);
AT_CHECK_RN(buffer);
bta_hf_client_handle_cops(opstr, mode);
// check for OK Response in end
AT_CHECK_EVENT(buffer, "OK");
AT_CHECK_RN(buffer);
bta_hf_client_handle_ok();
return buffer;
}
static char* bta_hf_client_parse_binp(char* buffer) {
/* HFP only supports phone number as BINP data */
/* phone number is 32 chars plus one for \0*/
char numstr[33];
int res;
int offset = 0;
AT_CHECK_EVENT(buffer, "+BINP:");
res = sscanf(buffer, "\"%32[^\"]\"\r\n%n", numstr, &offset);
if (res < 1) {
return NULL;
}
/* Abort in case offset not set because of format error */
if (offset == 0) {
APPL_TRACE_ERROR("%s: Format Error %s", __func__, buffer);
return NULL;
}
buffer += offset;
/* some phones might sent type as well, just skip it */
AT_SKIP_REST(buffer);
AT_CHECK_RN(buffer);
bta_hf_client_handle_binp(numstr);
// check for OK response in end
AT_CHECK_EVENT(buffer, "OK");
AT_CHECK_RN(buffer);
bta_hf_client_handle_ok();
return buffer;
}
static char* bta_hf_client_parse_clcc(char* buffer) {
uint16_t idx, dir, status, mode, mpty;
char numstr[33]; /* spec forces 32 chars, plus one for \0*/
uint16_t type;
int res;
int offset = 0;
AT_CHECK_EVENT(buffer, "+CLCC:");
res = sscanf(buffer, "%hu,%hu,%hu,%hu,%hu%n", &idx, &dir, &status, &mode,
&mpty, &offset);
if (res < 5) {
return NULL;
}
/* Abort in case offset not set because of format error */
if (offset == 0) {
APPL_TRACE_ERROR("%s: Format Error %s", __func__, buffer);
return NULL;
}
buffer += offset;
offset = 0;
/* check optional part */
if (*buffer == ',') {
int res2 = sscanf(buffer, ",\"%32[^\"]\",%hu%n", numstr, &type, &offset);
if (res2 < 0) return NULL;
if (res2 == 0) {
res2 = sscanf(buffer, ",\"\",%hu%n", &type, &offset);
if (res < 0) return NULL;
/* numstr is not matched in second attempt, correct this */
res2++;
numstr[0] = '\0';
}
if (res2 >= 2) {
res += res2;
/* Abort in case offset not set because of format error */
if (offset == 0) {
APPL_TRACE_ERROR("%s: Format Error %s", __func__, buffer);
return NULL;
}
buffer += offset;
}
}
/* Skip any remaing param,as they are not defined by BT HFP spec */
AT_SKIP_REST(buffer);
AT_CHECK_RN(buffer);
if (res > 6) {
/* we also have last two optional parameters */
bta_hf_client_handle_clcc(idx, dir, status, mode, mpty, numstr, type);
} else {
/* we didn't get the last two parameters */
bta_hf_client_handle_clcc(idx, dir, status, mode, mpty, NULL, 0);
}
// check for OK response in end
AT_CHECK_EVENT(buffer, "OK");
AT_CHECK_RN(buffer);
bta_hf_client_handle_ok();
return buffer;
}
static char* bta_hf_client_parse_cnum(char* buffer) {
char numstr[33]; /* spec forces 32 chars, plus one for \0*/
uint16_t type;
uint16_t service =
0; /* 0 in case this optional parameter is not being sent */
int res;
int offset = 0;
AT_CHECK_EVENT(buffer, "+CNUM:");
res = sscanf(buffer, ",\"%32[^\"]\",%hu,,%hu%n", numstr, &type, &service,
&offset);
if (res < 0) {
return NULL;
}
if (res == 0) {
res = sscanf(buffer, ",\"\",%hu,,%hu%n", &type, &service, &offset);
if (res < 0) {
return NULL;
}
/* numstr is not matched in second attempt, correct this */
res++;
numstr[0] = '\0';
}
if (res < 3) {
return NULL;
}
/* Abort in case offset not set because of format error */
if (offset == 0) {
APPL_TRACE_ERROR("%s: Format Error %s", __func__, buffer);
return NULL;
}
buffer += offset;
AT_CHECK_RN(buffer);
/* service is optional */
if (res == 2) {
bta_hf_client_handle_cnum(numstr, type, service);
return buffer;
}
if (service != 4 && service != 5) {
return NULL;
}
bta_hf_client_handle_cnum(numstr, type, service);
// check for OK response in end
AT_CHECK_EVENT(buffer, "OK");
AT_CHECK_RN(buffer);
bta_hf_client_handle_ok();
return buffer;
}
static char* bta_hf_client_parse_btrh(char* buffer) {
uint16_t code = 0;
int res;
int offset;
AT_CHECK_EVENT(buffer, "+BTRH:");
res = sscanf(buffer, "%hu%n", &code, &offset);
if (res < 1) {
return NULL;
}
buffer += offset;
AT_CHECK_RN(buffer);
bta_hf_client_handle_btrh(code);
return buffer;
}
static char* bta_hf_client_parse_busy(char* buffer) {
AT_CHECK_EVENT(buffer, "BUSY");
AT_CHECK_RN(buffer);
bta_hf_client_handle_error(BTA_HF_CLIENT_AT_RESULT_BUSY, 0);
return buffer;
}
static char* bta_hf_client_parse_delayed(char* buffer) {
AT_CHECK_EVENT(buffer, "DELAYED");
AT_CHECK_RN(buffer);
bta_hf_client_handle_error(BTA_HF_CLIENT_AT_RESULT_DELAY, 0);
return buffer;
}
static char* bta_hf_client_parse_no_carrier(char* buffer) {
AT_CHECK_EVENT(buffer, "NO CARRIER");
AT_CHECK_RN(buffer);
bta_hf_client_handle_error(BTA_HF_CLIENT_AT_RESULT_NO_CARRIER, 0);
return buffer;
}
static char* bta_hf_client_parse_no_answer(char* buffer) {
AT_CHECK_EVENT(buffer, "NO ANSWER");
AT_CHECK_RN(buffer);
bta_hf_client_handle_error(BTA_HF_CLIENT_AT_RESULT_NO_ANSWER, 0);
return buffer;
}
static char* bta_hf_client_parse_blacklisted(char* buffer) {
AT_CHECK_EVENT(buffer, "BLACKLISTED");
AT_CHECK_RN(buffer);
bta_hf_client_handle_error(BTA_HF_CLIENT_AT_RESULT_BLACKLISTED, 0);
return buffer;
}
static char* bta_hf_client_skip_unknown(char* buffer) {
char* start;
char* tmp;
tmp = strstr(buffer, "\r\n");
if (tmp == NULL) {
return NULL;
}
buffer += 2;
start = buffer;
tmp = strstr(buffer, "\r\n");
if (tmp == NULL) {
return NULL;
}
buffer = tmp + 2;
APPL_TRACE_DEBUG("%s %.*s", __func__, buffer - start - 2, start);
return buffer;
}
/******************************************************************************
* SUPPORTED EVENT MESSAGES
******************************************************************************/
/* returned values are as follow:
* != NULL && != buf : match and parsed ok
* == NULL : match but parse failed
* != NULL && == buf : no match
*/
typedef char* (*tBTA_HF_CLIENT_PARSER_CALLBACK)(char*);
static const tBTA_HF_CLIENT_PARSER_CALLBACK bta_hf_client_parser_cb[] = {
bta_hf_client_parse_ok, bta_hf_client_parse_error,
bta_hf_client_parse_ring, bta_hf_client_parse_brsf,
bta_hf_client_parse_cind, bta_hf_client_parse_ciev,
bta_hf_client_parse_chld, bta_hf_client_parse_bcs,
bta_hf_client_parse_bsir, bta_hf_client_parse_cmeerror,
bta_hf_client_parse_vgm, bta_hf_client_parse_vgme,
bta_hf_client_parse_vgs, bta_hf_client_parse_vgse,
bta_hf_client_parse_bvra, bta_hf_client_parse_clip,
bta_hf_client_parse_ccwa, bta_hf_client_parse_cops,
bta_hf_client_parse_binp, bta_hf_client_parse_clcc,
bta_hf_client_parse_cnum, bta_hf_client_parse_btrh,
bta_hf_client_parse_busy, bta_hf_client_parse_delayed,
bta_hf_client_parse_no_carrier, bta_hf_client_parse_no_answer,
bta_hf_client_parse_blacklisted, bta_hf_client_skip_unknown};
/* calculate supported event list length */
static const uint16_t bta_hf_client_psraser_cb_count =
sizeof(bta_hf_client_parser_cb) / sizeof(bta_hf_client_parser_cb[0]);
#ifdef BTA_HF_CLIENT_AT_DUMP
static void bta_hf_client_dump_at(void) {
char dump[(4 * BTA_HF_CLIENT_AT_PARSER_MAX_LEN) + 1];
char *p1, *p2;
p1 = bta_hf_client_cb.scb.at_cb.buf;
p2 = dump;
while (*p1 != '\0') {
if (*p1 == '\r') {
strlcpy(p2, "<cr>", 4);
p2 += 4;
} else if (*p1 == '\n') {
strlcpy(p2, "<lf>", 4);
p2 += 4;
} else {
*p2 = *p1;
p2++;
}
p1++;
}
*p2 = '\0';
APPL_TRACE_DEBUG("%s %s", __func__, dump);
}
#endif
static void bta_hf_client_at_parse_start(void) {
char* buf = bta_hf_client_cb.scb.at_cb.buf;
APPL_TRACE_DEBUG("%s", __func__);
#ifdef BTA_HF_CLIENT_AT_DUMP
bta_hf_client_dump_at();
#endif
while (*buf != '\0') {
int i;
char* tmp = NULL;
for (i = 0; i < bta_hf_client_psraser_cb_count; i++) {
tmp = bta_hf_client_parser_cb[i](buf);
if (tmp == NULL) {
APPL_TRACE_ERROR("HFPCient: AT event/reply parsing failed, skipping");
tmp = bta_hf_client_skip_unknown(buf);
break;
}
/* matched or unknown skipped, if unknown failed tmp is NULL so
this is also handled */
if (tmp != buf) {
buf = tmp;
break;
}
}
/* could not skip unknown (received garbage?)... disconnect */
if (tmp == NULL) {
APPL_TRACE_ERROR(
"HFPCient: could not skip unknown AT event, disconnecting");
bta_hf_client_at_reset();
bta_hf_client_sm_execute(BTA_HF_CLIENT_API_CLOSE_EVT, NULL);
return;
}
buf = tmp;
}
}
static bool bta_hf_client_check_at_complete(void) {
bool ret = false;
tBTA_HF_CLIENT_AT_CB* at_cb = &bta_hf_client_cb.scb.at_cb;
if (at_cb->offset >= BTA_HF_CLIENT_AT_EVENT_MIN_LEN) {
if (at_cb->buf[at_cb->offset - 2] == '\r' &&
at_cb->buf[at_cb->offset - 1] == '\n') {
ret = true;
}
}
APPL_TRACE_DEBUG("%s %d", __func__, ret);
return ret;
}
static void bta_hf_client_at_clear_buf(void) {
memset(bta_hf_client_cb.scb.at_cb.buf, 0,
sizeof(bta_hf_client_cb.scb.at_cb.buf));
bta_hf_client_cb.scb.at_cb.offset = 0;
}
/******************************************************************************
*
* MAIN PARSING FUNCTION
*
*
******************************************************************************/
void bta_hf_client_at_parse(char* buf, unsigned int len) {
APPL_TRACE_DEBUG("%s offset: %u len: %u", __func__,
bta_hf_client_cb.scb.at_cb.offset, len);
if (len + bta_hf_client_cb.scb.at_cb.offset >
BTA_HF_CLIENT_AT_PARSER_MAX_LEN) {
char tmp_buff[BTA_HF_CLIENT_AT_PARSER_MAX_LEN];
unsigned int tmp = bta_hf_client_cb.scb.at_cb.offset;
unsigned int space_left =
BTA_HF_CLIENT_AT_PARSER_MAX_LEN - bta_hf_client_cb.scb.at_cb.offset;
APPL_TRACE_DEBUG("%s overrun, trying to recover", __func__);
/* fill up parser buffer */
memcpy(bta_hf_client_cb.scb.at_cb.buf + bta_hf_client_cb.scb.at_cb.offset,
buf, space_left);
len -= space_left;
buf += space_left;
bta_hf_client_cb.scb.at_cb.offset += space_left;
/* find end of last complete command before proceeding */
while (bta_hf_client_check_at_complete() == false) {
if (bta_hf_client_cb.scb.at_cb.offset == 0) {
APPL_TRACE_ERROR("HFPClient: AT parser buffer overrun, disconnecting");
bta_hf_client_at_reset();
bta_hf_client_sm_execute(BTA_HF_CLIENT_API_CLOSE_EVT, NULL);
return;
}
bta_hf_client_cb.scb.at_cb.offset--;
}
/* cut buffer to complete AT event and keep cut data */
tmp += space_left - bta_hf_client_cb.scb.at_cb.offset;
memcpy(tmp_buff,
bta_hf_client_cb.scb.at_cb.buf + bta_hf_client_cb.scb.at_cb.offset,
tmp);
bta_hf_client_cb.scb.at_cb.buf[bta_hf_client_cb.scb.at_cb.offset] = '\0';
/* parse */
bta_hf_client_at_parse_start();
bta_hf_client_at_clear_buf();
/* recover cut data */
memcpy(bta_hf_client_cb.scb.at_cb.buf, tmp_buff, tmp);
bta_hf_client_cb.scb.at_cb.offset += tmp;
}
memcpy(bta_hf_client_cb.scb.at_cb.buf + bta_hf_client_cb.scb.at_cb.offset,
buf, len);
bta_hf_client_cb.scb.at_cb.offset += len;
/* If last event is complete, parsing can be started */
if (bta_hf_client_check_at_complete() == true) {
bta_hf_client_at_parse_start();
bta_hf_client_at_clear_buf();
}
}
void bta_hf_client_send_at_brsf(void) {
char buf[BTA_HF_CLIENT_AT_MAX_LEN];
int at_len;
APPL_TRACE_DEBUG("%s", __func__);
at_len =
snprintf(buf, sizeof(buf), "AT+BRSF=%u\r", bta_hf_client_cb.scb.features);
if (at_len < 0) {
APPL_TRACE_ERROR("%s: AT command Framing error", __func__);
return;
}
bta_hf_client_send_at(BTA_HF_CLIENT_AT_BRSF, buf, at_len);
}
void bta_hf_client_send_at_bac(void) {
const char* buf;
APPL_TRACE_DEBUG("%s", __func__);
if (bta_hf_client_cb.msbc_enabled) {
buf = "AT+BAC=1,2\r";
} else {
buf = "AT+BAC=1\r";
}
bta_hf_client_send_at(BTA_HF_CLIENT_AT_BAC, buf, strlen(buf));
}
void bta_hf_client_send_at_bcs(uint32_t codec) {
char buf[BTA_HF_CLIENT_AT_MAX_LEN];
int at_len;
APPL_TRACE_DEBUG("%s", __func__);
at_len = snprintf(buf, sizeof(buf), "AT+BCS=%u\r", codec);
if (at_len < 0) {
APPL_TRACE_ERROR("%s: AT command Framing error", __func__);
return;
}
bta_hf_client_send_at(BTA_HF_CLIENT_AT_BCS, buf, at_len);
}
void bta_hf_client_send_at_cind(bool status) {
const char* buf;
tBTA_HF_CLIENT_AT_CMD cmd;
APPL_TRACE_DEBUG("%s", __func__);
if (status) {
buf = "AT+CIND?\r";
cmd = BTA_HF_CLIENT_AT_CIND_STATUS;
} else {
buf = "AT+CIND=?\r";
cmd = BTA_HF_CLIENT_AT_CIND;
}
bta_hf_client_send_at(cmd, buf, strlen(buf));
}
void bta_hf_client_send_at_cmer(bool activate) {
const char* buf;
APPL_TRACE_DEBUG("%s", __func__);
if (activate)
buf = "AT+CMER=3,0,0,1\r";
else
buf = "AT+CMER=3,0,0,0\r";
bta_hf_client_send_at(BTA_HF_CLIENT_AT_CMER, buf, strlen(buf));
}
void bta_hf_client_send_at_chld(char cmd, uint32_t idx) {
char buf[BTA_HF_CLIENT_AT_MAX_LEN];
int at_len;
APPL_TRACE_DEBUG("%s", __func__);
if (idx > 0)
at_len = snprintf(buf, sizeof(buf), "AT+CHLD=%c%u\r", cmd, idx);
else
at_len = snprintf(buf, sizeof(buf), "AT+CHLD=%c\r", cmd);
if (at_len < 0) {
APPL_TRACE_ERROR("%s: AT command Framing error", __func__);
return;
}
bta_hf_client_send_at(BTA_HF_CLIENT_AT_CHLD, buf, at_len);
}
void bta_hf_client_send_at_clip(bool activate) {
const char* buf;
APPL_TRACE_DEBUG("%s", __func__);
if (activate)
buf = "AT+CLIP=1\r";
else
buf = "AT+CLIP=0\r";
bta_hf_client_send_at(BTA_HF_CLIENT_AT_CLIP, buf, strlen(buf));
}
void bta_hf_client_send_at_ccwa(bool activate) {
const char* buf;
APPL_TRACE_DEBUG("%s", __func__);
if (activate)
buf = "AT+CCWA=1\r";
else
buf = "AT+CCWA=0\r";
bta_hf_client_send_at(BTA_HF_CLIENT_AT_CCWA, buf, strlen(buf));
}
void bta_hf_client_send_at_cmee(bool activate) {
const char* buf;
APPL_TRACE_DEBUG("%s", __func__);
if (activate)
buf = "AT+CMEE=1\r";
else
buf = "AT+CMEE=0\r";
bta_hf_client_send_at(BTA_HF_CLIENT_AT_CMEE, buf, strlen(buf));
}
void bta_hf_client_send_at_cops(bool query) {
const char* buf;
APPL_TRACE_DEBUG("%s", __func__);
if (query)
buf = "AT+COPS?\r";
else
buf = "AT+COPS=3,0\r";
bta_hf_client_send_at(BTA_HF_CLIENT_AT_COPS, buf, strlen(buf));
}
void bta_hf_client_send_at_clcc(void) {
const char* buf;
APPL_TRACE_DEBUG("%s", __func__);
buf = "AT+CLCC\r";
bta_hf_client_send_at(BTA_HF_CLIENT_AT_CLCC, buf, strlen(buf));
}
void bta_hf_client_send_at_bvra(bool enable) {
const char* buf;
APPL_TRACE_DEBUG("%s", __func__);
if (enable)
buf = "AT+BVRA=1\r";
else
buf = "AT+BVRA=0\r";
bta_hf_client_send_at(BTA_HF_CLIENT_AT_BVRA, buf, strlen(buf));
}
void bta_hf_client_send_at_vgs(uint32_t volume) {
char buf[BTA_HF_CLIENT_AT_MAX_LEN];
int at_len;
APPL_TRACE_DEBUG("%s", __func__);
at_len = snprintf(buf, sizeof(buf), "AT+VGS=%u\r", volume);
if (at_len < 0) {
APPL_TRACE_ERROR("%s: AT command Framing error", __func__);
return;
}
bta_hf_client_send_at(BTA_HF_CLIENT_AT_VGS, buf, at_len);
}
void bta_hf_client_send_at_vgm(uint32_t volume) {
char buf[BTA_HF_CLIENT_AT_MAX_LEN];
int at_len;
APPL_TRACE_DEBUG("%s", __func__);
at_len = snprintf(buf, sizeof(buf), "AT+VGM=%u\r", volume);
if (at_len < 0) {
APPL_TRACE_ERROR("%s: AT command Framing error", __func__);
return;
}
bta_hf_client_send_at(BTA_HF_CLIENT_AT_VGM, buf, at_len);
}
void bta_hf_client_send_at_atd(char* number, uint32_t memory) {
char buf[BTA_HF_CLIENT_AT_MAX_LEN];
int at_len;
APPL_TRACE_DEBUG("%s", __func__);
if (number[0] != '\0') {
at_len = snprintf(buf, sizeof(buf), "ATD%s;\r", number);
} else {
at_len = snprintf(buf, sizeof(buf), "ATD>%u;\r", memory);
}
if (at_len < 0) {
APPL_TRACE_ERROR("%s: error preparing ATD command", __func__);
return;
}
at_len = MIN((size_t)at_len, sizeof(buf));
if (at_len < 0) {
APPL_TRACE_ERROR("%s: AT command Framing error", __func__);
return;
}
bta_hf_client_send_at(BTA_HF_CLIENT_AT_ATD, buf, at_len);
}
void bta_hf_client_send_at_bldn(void) {
const char* buf;
APPL_TRACE_DEBUG("%s", __func__);
buf = "AT+BLDN\r";
bta_hf_client_send_at(BTA_HF_CLIENT_AT_BLDN, buf, strlen(buf));
}
void bta_hf_client_send_at_ata(void) {
const char* buf;
APPL_TRACE_DEBUG("%s", __func__);
buf = "ATA\r";
bta_hf_client_send_at(BTA_HF_CLIENT_AT_ATA, buf, strlen(buf));
}
void bta_hf_client_send_at_chup(void) {
const char* buf;
APPL_TRACE_DEBUG("%s", __func__);
buf = "AT+CHUP\r";
bta_hf_client_send_at(BTA_HF_CLIENT_AT_CHUP, buf, strlen(buf));
}
void bta_hf_client_send_at_btrh(bool query, uint32_t val) {
char buf[BTA_HF_CLIENT_AT_MAX_LEN];
int at_len;
APPL_TRACE_DEBUG("%s", __func__);
if (query == true) {
at_len = snprintf(buf, sizeof(buf), "AT+BTRH?\r");
} else {
at_len = snprintf(buf, sizeof(buf), "AT+BTRH=%u\r", val);
}
if (at_len < 0) {
APPL_TRACE_ERROR("%s: AT command Framing error", __func__);
return;
}
bta_hf_client_send_at(BTA_HF_CLIENT_AT_BTRH, buf, at_len);
}
void bta_hf_client_send_at_vts(char code) {
char buf[BTA_HF_CLIENT_AT_MAX_LEN];
int at_len;
APPL_TRACE_DEBUG("%s", __func__);
at_len = snprintf(buf, sizeof(buf), "AT+VTS=%c\r", code);
if (at_len < 0) {
APPL_TRACE_ERROR("%s: AT command Framing error", __func__);
return;
}
bta_hf_client_send_at(BTA_HF_CLIENT_AT_VTS, buf, at_len);
}
void bta_hf_client_send_at_bcc(void) {
const char* buf;
APPL_TRACE_DEBUG("%s", __func__);
buf = "AT+BCC\r";
bta_hf_client_send_at(BTA_HF_CLIENT_AT_BCC, buf, strlen(buf));
}
void bta_hf_client_send_at_cnum(void) {
const char* buf;
APPL_TRACE_DEBUG("%s", __func__);
buf = "AT+CNUM\r";
bta_hf_client_send_at(BTA_HF_CLIENT_AT_CNUM, buf, strlen(buf));
}
void bta_hf_client_send_at_nrec(void) {
const char* buf;
APPL_TRACE_DEBUG("%s", __func__);
if (!(bta_hf_client_cb.scb.peer_features & BTA_HF_CLIENT_PEER_FEAT_ECNR)) {
APPL_TRACE_ERROR("%s: Remote does not support NREC.", __func__);
return;
}
buf = "AT+NREC=0\r";
bta_hf_client_send_at(BTA_HF_CLIENT_AT_NREC, buf, strlen(buf));
}
void bta_hf_client_send_at_binp(uint32_t action) {
char buf[BTA_HF_CLIENT_AT_MAX_LEN];
int at_len;
APPL_TRACE_DEBUG("%s", __func__);
at_len = snprintf(buf, sizeof(buf), "AT+BINP=%u\r", action);
if (at_len < 0) {
APPL_TRACE_ERROR("%s: AT command Framing error", __func__);
return;
}
bta_hf_client_send_at(BTA_HF_CLIENT_AT_BINP, buf, at_len);
}
void bta_hf_client_send_at_bia(void) {
char buf[BTA_HF_CLIENT_AT_MAX_LEN];
int at_len;
int i;
APPL_TRACE_DEBUG("%s", __func__);
if (bta_hf_client_cb.scb.peer_version < HFP_VERSION_1_6) {
APPL_TRACE_DEBUG("Remote does not Support AT+BIA");
return;
}
at_len = snprintf(buf, sizeof(buf), "AT+BIA=");
for (i = 0; i < BTA_HF_CLIENT_AT_INDICATOR_COUNT; i++) {
int sup = bta_hf_client_cb.scb.at_cb.indicator_lookup[i] == -1 ? 0 : 1;
at_len += snprintf(buf + at_len, sizeof(buf) - at_len, "%u,", sup);
}
buf[at_len - 1] = '\r';
if (at_len < 0) {
APPL_TRACE_ERROR("%s: AT command Framing error", __func__);
return;
}
bta_hf_client_send_at(BTA_HF_CLIENT_AT_BIA, buf, at_len);
}
void bta_hf_client_at_init(void) {
alarm_free(bta_hf_client_cb.scb.at_cb.resp_timer);
alarm_free(bta_hf_client_cb.scb.at_cb.hold_timer);
memset(&bta_hf_client_cb.scb.at_cb, 0, sizeof(tBTA_HF_CLIENT_AT_CB));
bta_hf_client_cb.scb.at_cb.resp_timer =
alarm_new("bta_hf_client.scb_at_resp_timer");
bta_hf_client_cb.scb.at_cb.hold_timer =
alarm_new("bta_hf_client.scb_at_hold_timer");
bta_hf_client_at_reset();
}
void bta_hf_client_at_reset(void) {
int i;
bta_hf_client_stop_at_resp_timer();
bta_hf_client_stop_at_hold_timer();
bta_hf_client_clear_queued_at();
bta_hf_client_at_clear_buf();
for (i = 0; i < BTA_HF_CLIENT_AT_INDICATOR_COUNT; i++) {
bta_hf_client_cb.scb.at_cb.indicator_lookup[i] = -1;
}
bta_hf_client_cb.scb.at_cb.current_cmd = BTA_HF_CLIENT_AT_NONE;
}