mirror of
https://github.com/bluekitchen/btstack.git
synced 2025-03-26 02:37:41 +00:00
2544 lines
106 KiB
C
2544 lines
106 KiB
C
/*
|
||
* Copyright (C) 2014 BlueKitchen GmbH
|
||
*
|
||
* Redistribution and use in source and binary forms, with or without
|
||
* modification, are permitted provided that the following conditions
|
||
* are met:
|
||
*
|
||
* 1. Redistributions of source code must retain the above copyright
|
||
* notice, this list of conditions and the following disclaimer.
|
||
* 2. Redistributions in binary form must reproduce the above copyright
|
||
* notice, this list of conditions and the following disclaimer in the
|
||
* documentation and/or other materials provided with the distribution.
|
||
* 3. Neither the name of the copyright holders nor the names of
|
||
* contributors may be used to endorse or promote products derived
|
||
* from this software without specific prior written permission.
|
||
* 4. Any redistribution, use, or modification is done solely for
|
||
* personal benefit and not for any commercial purpose or for
|
||
* monetary gain.
|
||
*
|
||
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
|
||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
|
||
* GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||
* SUCH DAMAGE.
|
||
*
|
||
* Please inquire about commercial licensing options at
|
||
* contact@bluekitchen-gmbh.com
|
||
*
|
||
*/
|
||
|
||
#define BTSTACK_FILE__ "hfp.c"
|
||
|
||
|
||
#include "btstack_config.h"
|
||
|
||
#include <stdint.h>
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include <inttypes.h>
|
||
|
||
#include "bluetooth_sdp.h"
|
||
#include "btstack_debug.h"
|
||
#include "btstack_event.h"
|
||
#include "btstack_memory.h"
|
||
#include "btstack_run_loop.h"
|
||
#include "classic/sdp_client_rfcomm.h"
|
||
#include "classic/sdp_server.h"
|
||
#include "classic/sdp_util.h"
|
||
#include "classic/sdp_client.h"
|
||
#include "hci.h"
|
||
#include "hci_cmd.h"
|
||
#include "hci_dump.h"
|
||
|
||
#if defined(ENABLE_CC256X_ASSISTED_HFP) && !defined(ENABLE_SCO_OVER_PCM)
|
||
#error "Assisted HFP is only possible over PCM/I2S. Please add define: ENABLE_SCO_OVER_PCM"
|
||
#endif
|
||
|
||
#if defined(ENABLE_BCM_PCM_WBS) && !defined(ENABLE_SCO_OVER_PCM)
|
||
#error "WBS for PCM is only possible over PCM/I2S. Please add define: ENABLE_SCO_OVER_PCM"
|
||
#endif
|
||
|
||
#define HFP_HF_FEATURES_SIZE 10
|
||
#define HFP_AG_FEATURES_SIZE 12
|
||
|
||
typedef struct {
|
||
hfp_role_t local_role;
|
||
bd_addr_t remote_address;
|
||
} hfp_sdp_query_context_t;
|
||
|
||
// globals
|
||
|
||
static btstack_linked_list_t hfp_connections ;
|
||
|
||
static btstack_packet_handler_t hfp_hf_callback;
|
||
static btstack_packet_handler_t hfp_ag_callback;
|
||
|
||
static btstack_packet_handler_t hfp_hf_rfcomm_packet_handler;
|
||
static btstack_packet_handler_t hfp_ag_rfcomm_packet_handler;
|
||
|
||
static uint8_t hfp_hf_indicators_nr;
|
||
static const uint8_t * hfp_hf_indicators;
|
||
|
||
static uint16_t hfp_allowed_sco_packet_types;
|
||
static hfp_connection_t * hfp_sco_establishment_active;
|
||
|
||
static hfp_sdp_query_context_t hfp_sdp_query_context;
|
||
static btstack_context_callback_registration_t hfp_sdp_query_request;
|
||
|
||
|
||
|
||
|
||
|
||
// custom commands
|
||
static btstack_linked_list_t hfp_custom_commands_ag;
|
||
static btstack_linked_list_t hfp_custom_commands_hf;
|
||
|
||
// prototypes
|
||
static hfp_link_settings_t hfp_next_link_setting_for_connection(hfp_link_settings_t current_setting, hfp_connection_t * hfp_connection, uint8_t eSCO_S4_supported);
|
||
static void parse_sequence(hfp_connection_t * context);
|
||
|
||
|
||
#define CODEC_MASK_CVSD (1 << HFP_CODEC_CVSD)
|
||
#define CODEC_MASK_OTHER ((1 << HFP_CODEC_MSBC) | (1 << HFP_CODEC_LC3_SWB))
|
||
|
||
static const struct {
|
||
const uint16_t max_latency;
|
||
const uint8_t retransmission_effort;
|
||
const uint16_t packet_types;
|
||
const uint8_t codec_mask;
|
||
} hfp_link_settings [] = {
|
||
{0x0004, 0xff, SCO_PACKET_TYPES_HV1, CODEC_MASK_CVSD }, // HFP_LINK_SETTINGS_D0
|
||
{0x0005, 0xff, SCO_PACKET_TYPES_HV3, CODEC_MASK_CVSD }, // HFP_LINK_SETTINGS_D1
|
||
{0x0007, 0x01, SCO_PACKET_TYPES_EV3, CODEC_MASK_CVSD }, // HFP_LINK_SETTINGS_S1
|
||
{0x0007, 0x01, SCO_PACKET_TYPES_2EV3, CODEC_MASK_CVSD }, // HFP_LINK_SETTINGS_S2
|
||
{0x000a, 0x01, SCO_PACKET_TYPES_2EV3, CODEC_MASK_CVSD }, // HFP_LINK_SETTINGS_S3
|
||
{0x000c, 0x02, SCO_PACKET_TYPES_2EV3, CODEC_MASK_CVSD }, // HFP_LINK_SETTINGS_S4
|
||
{0x0008, 0x02, SCO_PACKET_TYPES_EV3, CODEC_MASK_OTHER}, // HFP_LINK_SETTINGS_T1
|
||
{0x000d, 0x02, SCO_PACKET_TYPES_2EV3, CODEC_MASK_OTHER} // HFP_LINK_SETTINGS_T2
|
||
};
|
||
|
||
#ifdef ENABLE_HFP_HF_SAFE_SETTINGS
|
||
// HFP v1.9, table 6.10 'mandatory safe settings' for eSCO + similar entries for SCO
|
||
static const struct hfp_mandatory_safe_setting {
|
||
const uint8_t codec_mask;
|
||
const bool secure_connection_in_use;
|
||
hfp_link_settings_t link_setting;
|
||
} hfp_mandatory_safe_settings[] = {
|
||
{ CODEC_MASK_CVSD, false, HFP_LINK_SETTINGS_D1},
|
||
{ CODEC_MASK_CVSD, true, HFP_LINK_SETTINGS_D1},
|
||
{ CODEC_MASK_CVSD, false, HFP_LINK_SETTINGS_S1},
|
||
{ CODEC_MASK_CVSD, true, HFP_LINK_SETTINGS_S4},
|
||
{ CODEC_MASK_OTHER, false, HFP_LINK_SETTINGS_T1},
|
||
{ CODEC_MASK_OTHER, true, HFP_LINK_SETTINGS_T2},
|
||
};
|
||
#endif
|
||
|
||
static const char * hfp_hf_features[] = {
|
||
"EC and/or NR function",
|
||
"Three-way calling",
|
||
"CLI presentation capability",
|
||
"Voice recognition activation",
|
||
"Remote volume control",
|
||
|
||
"Enhanced call status",
|
||
"Enhanced call control",
|
||
|
||
"Codec negotiation",
|
||
|
||
"HF Indicators",
|
||
"eSCO S4 (and T2) Settings Supported",
|
||
"Reserved for future definition"
|
||
};
|
||
|
||
static const char * hfp_ag_features[] = {
|
||
"Three-way calling",
|
||
"EC and/or NR function",
|
||
"Voice recognition function",
|
||
"In-band ring tone capability",
|
||
"Attach a number to a voice tag",
|
||
"Ability to reject a call",
|
||
"Enhanced call status",
|
||
"Enhanced call control",
|
||
"Extended Error Result Codes",
|
||
"Codec negotiation",
|
||
"HF Indicators",
|
||
"eSCO S4 (and T2) Settings Supported",
|
||
"Reserved for future definition"
|
||
};
|
||
|
||
static const char * hfp_enhanced_call_dir[] = {
|
||
"outgoing",
|
||
"incoming"
|
||
};
|
||
|
||
static const char * hfp_enhanced_call_status[] = {
|
||
"active",
|
||
"held",
|
||
"outgoing dialing",
|
||
"outgoing alerting",
|
||
"incoming",
|
||
"incoming waiting",
|
||
"call held by response and hold"
|
||
};
|
||
|
||
static const char * hfp_enhanced_call_mode[] = {
|
||
"voice",
|
||
"data",
|
||
"fax"
|
||
};
|
||
|
||
static const char * hfp_enhanced_call_mpty[] = {
|
||
"not a conference call",
|
||
"conference call"
|
||
};
|
||
|
||
const char * hfp_enhanced_call_dir2str(uint16_t index){
|
||
if (index <= HFP_ENHANCED_CALL_DIR_INCOMING) return hfp_enhanced_call_dir[index];
|
||
return "not defined";
|
||
}
|
||
|
||
const char * hfp_enhanced_call_status2str(uint16_t index){
|
||
if (index <= HFP_ENHANCED_CALL_STATUS_CALL_HELD_BY_RESPONSE_AND_HOLD) return hfp_enhanced_call_status[index];
|
||
return "not defined";
|
||
}
|
||
|
||
const char * hfp_enhanced_call_mode2str(uint16_t index){
|
||
if (index <= HFP_ENHANCED_CALL_MODE_FAX) return hfp_enhanced_call_mode[index];
|
||
return "not defined";
|
||
}
|
||
|
||
const char * hfp_enhanced_call_mpty2str(uint16_t index){
|
||
if (index <= HFP_ENHANCED_CALL_MPTY_CONFERENCE_CALL) return hfp_enhanced_call_mpty[index];
|
||
return "not defined";
|
||
}
|
||
|
||
static uint16_t hfp_parse_indicator_index(hfp_connection_t * hfp_connection){
|
||
uint16_t index = btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
|
||
if (index > HFP_MAX_NUM_INDICATORS){
|
||
log_info("ignoring invalid indicator index bigger then HFP_MAX_NUM_INDICATORS");
|
||
return HFP_MAX_NUM_INDICATORS - 1;
|
||
}
|
||
|
||
// indicator index enumeration starts with 1, we substract 1 to store in array with starting index 0
|
||
if (index > 0){
|
||
index -= 1;
|
||
} else {
|
||
log_info("ignoring invalid indicator index 0");
|
||
return 0;
|
||
}
|
||
return index;
|
||
}
|
||
|
||
static void hfp_next_indicators_index(hfp_connection_t * hfp_connection){
|
||
if (hfp_connection->parser_item_index < HFP_MAX_NUM_INDICATORS - 1){
|
||
hfp_connection->parser_item_index++;
|
||
} else {
|
||
log_info("Ignoring additional indicator");
|
||
}
|
||
}
|
||
|
||
static void hfp_next_codec_index(hfp_connection_t * hfp_connection){
|
||
if (hfp_connection->parser_item_index < HFP_MAX_NUM_CODECS - 1){
|
||
hfp_connection->parser_item_index++;
|
||
} else {
|
||
log_info("Ignoring additional codec index");
|
||
}
|
||
}
|
||
|
||
static void hfp_next_remote_call_services_index(hfp_connection_t * hfp_connection){
|
||
if (hfp_connection->remote_call_services_index < HFP_MAX_NUM_CALL_SERVICES - 1){
|
||
hfp_connection->remote_call_services_index++;
|
||
} else {
|
||
log_info("Ignoring additional remote_call_services");
|
||
}
|
||
}
|
||
|
||
const char * hfp_hf_feature(int index){
|
||
if (index > HFP_HF_FEATURES_SIZE){
|
||
return hfp_hf_features[HFP_HF_FEATURES_SIZE];
|
||
}
|
||
return hfp_hf_features[index];
|
||
}
|
||
|
||
const char * hfp_ag_feature(int index){
|
||
if (index > HFP_AG_FEATURES_SIZE){
|
||
return hfp_ag_features[HFP_AG_FEATURES_SIZE];
|
||
}
|
||
return hfp_ag_features[index];
|
||
}
|
||
|
||
int send_str_over_rfcomm(uint16_t cid, const char * command){
|
||
if (!rfcomm_can_send_packet_now(cid)) return 1;
|
||
log_info("HFP_TX %s", command);
|
||
int err = rfcomm_send(cid, (uint8_t*) command, (uint16_t) strlen(command));
|
||
if (err){
|
||
log_error("rfcomm_send -> error 0x%02x \n", err);
|
||
}
|
||
#ifdef ENABLE_HFP_AT_MESSAGES
|
||
hfp_connection_t * hfp_connection = get_hfp_connection_context_for_rfcomm_cid(cid);
|
||
hfp_emit_string_event(hfp_connection, HFP_SUBEVENT_AT_MESSAGE_SENT, command);
|
||
#endif
|
||
return 1;
|
||
}
|
||
|
||
int hfp_supports_codec(uint8_t codec, int codecs_nr, uint8_t * codecs){
|
||
|
||
// mSBC requires support for eSCO connections
|
||
if ((codec == HFP_CODEC_MSBC) && !hci_extended_sco_link_supported()) return 0;
|
||
|
||
int i;
|
||
for (i = 0; i < codecs_nr; i++){
|
||
if (codecs[i] != codec) continue;
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
void hfp_hf_drop_mSBC_if_eSCO_not_supported(uint8_t * codecs, uint8_t * codecs_nr){
|
||
if (hci_extended_sco_link_supported()) return;
|
||
uint8_t i;
|
||
int filtered_codec_count = 0;
|
||
for (i=0; i < *codecs_nr; i++){
|
||
if (codecs[i] != HFP_CODEC_MSBC) {
|
||
codecs[filtered_codec_count++] = codecs[i];
|
||
}
|
||
}
|
||
*codecs_nr = filtered_codec_count;
|
||
}
|
||
|
||
// UTILS
|
||
int get_bit(uint16_t bitmap, int position){
|
||
return (bitmap >> position) & 1;
|
||
}
|
||
|
||
int store_bit(uint32_t bitmap, int position, uint8_t value){
|
||
if (value){
|
||
bitmap |= 1 << position;
|
||
} else {
|
||
bitmap &= ~ (1 << position);
|
||
}
|
||
return bitmap;
|
||
}
|
||
|
||
int join(char * buffer, int buffer_size, uint8_t * values, int values_nr){
|
||
if (buffer_size < (values_nr * 3)) return 0;
|
||
int i;
|
||
int offset = 0;
|
||
for (i = 0; i < (values_nr-1); i++) {
|
||
offset += btstack_snprintf_assert_complete(buffer+offset, buffer_size-offset, "%d,", values[i]); // puts string into buffer
|
||
}
|
||
if (i<values_nr){
|
||
offset += btstack_snprintf_assert_complete(buffer+offset, buffer_size-offset, "%d", values[i]);
|
||
}
|
||
return offset;
|
||
}
|
||
|
||
int join_bitmap(char * buffer, int buffer_size, uint32_t values, int values_nr){
|
||
if (buffer_size < (values_nr * 3)) return 0;
|
||
|
||
int i;
|
||
int offset = 0;
|
||
for (i = 0; i < (values_nr-1); i++) {
|
||
offset += btstack_snprintf_assert_complete(buffer+offset, buffer_size-offset, "%d,", get_bit(values,i)); // puts string into buffer
|
||
}
|
||
|
||
if (i<values_nr){
|
||
offset += btstack_snprintf_assert_complete(buffer+offset, buffer_size-offset, "%d", get_bit(values,i));
|
||
}
|
||
return offset;
|
||
}
|
||
static void hfp_emit_event_for_role(hfp_role_t local_role, uint8_t * packet, uint16_t size){
|
||
switch (local_role){
|
||
case HFP_ROLE_HF:
|
||
(*hfp_hf_callback)(HCI_EVENT_PACKET, 0, packet, size);
|
||
break;
|
||
case HFP_ROLE_AG:
|
||
(*hfp_ag_callback)(HCI_EVENT_PACKET, 0, packet, size);
|
||
break;
|
||
default:
|
||
btstack_unreachable();
|
||
break;
|
||
}
|
||
}
|
||
|
||
static void hfp_emit_event_for_context(hfp_connection_t * hfp_connection, uint8_t * packet, uint16_t size){
|
||
if (!hfp_connection) return;
|
||
hfp_emit_event_for_role(hfp_connection->local_role, packet, size);
|
||
}
|
||
|
||
void hfp_emit_simple_event(hfp_connection_t * hfp_connection, uint8_t event_subtype){
|
||
hci_con_handle_t acl_handle = (hfp_connection != NULL) ? hfp_connection->acl_handle : HCI_CON_HANDLE_INVALID;
|
||
uint8_t event[5];
|
||
event[0] = HCI_EVENT_HFP_META;
|
||
event[1] = sizeof(event) - 2;
|
||
event[2] = event_subtype;
|
||
little_endian_store_16(event, 3, acl_handle);
|
||
hfp_emit_event_for_context(hfp_connection, event, sizeof(event));
|
||
}
|
||
|
||
void hfp_emit_event(hfp_connection_t * hfp_connection, uint8_t event_subtype, uint8_t value){
|
||
hci_con_handle_t acl_handle = (hfp_connection != NULL) ? hfp_connection->acl_handle : HCI_CON_HANDLE_INVALID;
|
||
uint8_t event[6];
|
||
event[0] = HCI_EVENT_HFP_META;
|
||
event[1] = sizeof(event) - 2;
|
||
event[2] = event_subtype;
|
||
little_endian_store_16(event, 3, acl_handle);
|
||
event[5] = value; // status 0 == OK
|
||
hfp_emit_event_for_context(hfp_connection, event, sizeof(event));
|
||
}
|
||
|
||
static void hfp_emit_slc_released(hci_con_handle_t acl_handle, hfp_role_t role){
|
||
uint8_t event[6];
|
||
event[0] = HCI_EVENT_HFP_META;
|
||
event[1] = sizeof(event) - 2;
|
||
event[2] = HFP_SUBEVENT_SERVICE_LEVEL_CONNECTION_RELEASED;
|
||
little_endian_store_16(event, 3, acl_handle);
|
||
event[5] = ERROR_CODE_SUCCESS; // status 0 == OK
|
||
hfp_emit_event_for_role(role, event, sizeof(event));
|
||
}
|
||
|
||
void hfp_emit_voice_recognition_enabled(hfp_connection_t * hfp_connection, uint8_t status){
|
||
btstack_assert(hfp_connection != NULL);
|
||
|
||
uint8_t event[7];
|
||
event[0] = HCI_EVENT_HFP_META;
|
||
event[1] = sizeof(event) - 2;
|
||
event[2] = HFP_SUBEVENT_VOICE_RECOGNITION_ACTIVATED;
|
||
|
||
little_endian_store_16(event, 3, hfp_connection->acl_handle);
|
||
event[5] = status; // 0:success
|
||
event[6] = hfp_connection->enhanced_voice_recognition_enabled ? 1 : 0;
|
||
hfp_emit_event_for_context(hfp_connection, event, sizeof(event));
|
||
}
|
||
|
||
void hfp_emit_voice_recognition_disabled(hfp_connection_t * hfp_connection, uint8_t status){
|
||
btstack_assert(hfp_connection != NULL);
|
||
|
||
uint8_t event[6];
|
||
event[0] = HCI_EVENT_HFP_META;
|
||
event[1] = sizeof(event) - 2;
|
||
event[2] = HFP_SUBEVENT_VOICE_RECOGNITION_DEACTIVATED;
|
||
|
||
little_endian_store_16(event, 3, hfp_connection->acl_handle);
|
||
event[5] = status; // 0:success
|
||
hfp_emit_event_for_context(hfp_connection, event, sizeof(event));
|
||
}
|
||
|
||
void hfp_emit_enhanced_voice_recognition_hf_ready_for_audio_event(hfp_connection_t * hfp_connection, uint8_t status){
|
||
hci_con_handle_t acl_handle = (hfp_connection != NULL) ? hfp_connection->acl_handle : HCI_CON_HANDLE_INVALID;
|
||
|
||
uint8_t event[6];
|
||
event[0] = HCI_EVENT_HFP_META;
|
||
event[1] = sizeof(event) - 2;
|
||
event[2] = HFP_SUBEVENT_ENHANCED_VOICE_RECOGNITION_HF_READY_FOR_AUDIO;
|
||
little_endian_store_16(event, 3, acl_handle);
|
||
event[5] = status;
|
||
hfp_emit_event_for_context(hfp_connection, event, sizeof(event));
|
||
}
|
||
|
||
void hfp_emit_enhanced_voice_recognition_state_event(hfp_connection_t * hfp_connection, uint8_t status){
|
||
hci_con_handle_t acl_handle = (hfp_connection != NULL) ? hfp_connection->acl_handle : HCI_CON_HANDLE_INVALID;
|
||
|
||
uint8_t event[6];
|
||
event[0] = HCI_EVENT_HFP_META;
|
||
event[1] = sizeof(event) - 2;
|
||
switch (hfp_connection->ag_vra_state){
|
||
case HFP_VOICE_RECOGNITION_STATE_AG_READY_TO_ACCEPT_AUDIO_INPUT:
|
||
event[2] = HFP_SUBEVENT_ENHANCED_VOICE_RECOGNITION_AG_READY_TO_ACCEPT_AUDIO_INPUT;
|
||
break;
|
||
case HFP_VOICE_RECOGNITION_STATE_AG_IS_STARTING_SOUND:
|
||
event[2] = HFP_SUBEVENT_ENHANCED_VOICE_RECOGNITION_AG_IS_STARTING_SOUND;
|
||
break;
|
||
case HFP_VOICE_RECOGNITION_STATE_AG_IS_PROCESSING_AUDIO_INPUT:
|
||
event[2] = HFP_SUBEVENT_ENHANCED_VOICE_RECOGNITION_AG_IS_PROCESSING_AUDIO_INPUT;
|
||
break;
|
||
default:
|
||
btstack_unreachable();
|
||
break;
|
||
}
|
||
|
||
little_endian_store_16(event, 3, acl_handle);
|
||
event[5] = status;
|
||
hfp_emit_event_for_context(hfp_connection, event, sizeof(event));
|
||
}
|
||
|
||
void hfp_emit_slc_connection_event(hfp_role_t local_role, uint8_t status, hci_con_handle_t con_handle, bd_addr_t addr){
|
||
uint8_t event[12];
|
||
int pos = 0;
|
||
event[pos++] = HCI_EVENT_HFP_META;
|
||
event[pos++] = sizeof(event) - 2;
|
||
event[pos++] = HFP_SUBEVENT_SERVICE_LEVEL_CONNECTION_ESTABLISHED;
|
||
little_endian_store_16(event, pos, con_handle);
|
||
pos += 2;
|
||
event[pos++] = status; // status 0 == OK
|
||
reverse_bd_addr(addr,&event[pos]);
|
||
pos += 6;
|
||
hfp_emit_event_for_role(local_role, event, sizeof(event));
|
||
}
|
||
|
||
static void hfp_emit_audio_connection_released(hfp_connection_t * hfp_connection, hci_con_handle_t sco_handle){
|
||
btstack_assert(hfp_connection != NULL);
|
||
uint8_t event[7];
|
||
int pos = 0;
|
||
event[pos++] = HCI_EVENT_HFP_META;
|
||
event[pos++] = sizeof(event) - 2;
|
||
event[pos++] = HFP_SUBEVENT_AUDIO_CONNECTION_RELEASED;
|
||
little_endian_store_16(event, pos, hfp_connection->acl_handle);
|
||
pos += 2;
|
||
little_endian_store_16(event, pos, sco_handle);
|
||
pos += 2;
|
||
hfp_emit_event_for_context(hfp_connection, event, sizeof(event));
|
||
}
|
||
|
||
void hfp_emit_sco_connection_established(hfp_connection_t *hfp_connection, uint8_t status, uint8_t negotiated_codec,
|
||
uint16_t rx_packet_length, uint16_t tx_packet_length) {
|
||
btstack_assert(hfp_connection != NULL);
|
||
uint8_t event[21];
|
||
int pos = 0;
|
||
event[pos++] = HCI_EVENT_HFP_META;
|
||
event[pos++] = sizeof(event) - 2;
|
||
event[pos++] = HFP_SUBEVENT_AUDIO_CONNECTION_ESTABLISHED;
|
||
little_endian_store_16(event, pos, hfp_connection->acl_handle);
|
||
pos += 2;
|
||
event[pos++] = status; // status 0 == OK
|
||
little_endian_store_16(event, pos, hfp_connection->sco_handle);
|
||
pos += 2;
|
||
reverse_bd_addr(hfp_connection->remote_addr,&event[pos]);
|
||
pos += 6;
|
||
event[pos++] = negotiated_codec;
|
||
little_endian_store_16(event, pos, hfp_connection->packet_types);
|
||
pos += 2;
|
||
little_endian_store_16(event, pos, rx_packet_length);
|
||
pos += 2;
|
||
little_endian_store_16(event, pos, tx_packet_length);
|
||
pos += 2;
|
||
hfp_emit_event_for_context(hfp_connection, event, sizeof(event));
|
||
}
|
||
|
||
void hfp_emit_string_event(hfp_connection_t * hfp_connection, uint8_t event_subtype, const char * value){
|
||
btstack_assert(hfp_connection != NULL);
|
||
#ifdef ENABLE_HFP_AT_MESSAGES
|
||
uint8_t event[256];
|
||
#else
|
||
uint8_t event[40];
|
||
#endif
|
||
uint16_t string_len = btstack_min((uint16_t) strlen(value), sizeof(event) - 6);
|
||
event[0] = HCI_EVENT_HFP_META;
|
||
event[1] = 4 + string_len;
|
||
event[2] = event_subtype;
|
||
little_endian_store_16(event, 3, hfp_connection->acl_handle);
|
||
memcpy((char*)&event[5], value, string_len);
|
||
event[5 + string_len] = 0;
|
||
hfp_emit_event_for_context(hfp_connection, event, 6 + string_len);
|
||
}
|
||
|
||
btstack_linked_list_t * hfp_get_connections(void){
|
||
return (btstack_linked_list_t *) &hfp_connections;
|
||
}
|
||
|
||
hfp_connection_t * get_hfp_connection_context_for_rfcomm_cid(uint16_t cid){
|
||
btstack_linked_list_iterator_t it;
|
||
btstack_linked_list_iterator_init(&it, hfp_get_connections());
|
||
while (btstack_linked_list_iterator_has_next(&it)){
|
||
hfp_connection_t * hfp_connection = (hfp_connection_t *)btstack_linked_list_iterator_next(&it);
|
||
if (hfp_connection->rfcomm_cid == cid){
|
||
return hfp_connection;
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
hfp_connection_t * get_hfp_connection_context_for_bd_addr(bd_addr_t bd_addr, hfp_role_t hfp_role){
|
||
btstack_linked_list_iterator_t it;
|
||
btstack_linked_list_iterator_init(&it, hfp_get_connections());
|
||
while (btstack_linked_list_iterator_has_next(&it)){
|
||
hfp_connection_t * hfp_connection = (hfp_connection_t *)btstack_linked_list_iterator_next(&it);
|
||
if ((memcmp(hfp_connection->remote_addr, bd_addr, 6) == 0) && (hfp_connection->local_role == hfp_role)) {
|
||
return hfp_connection;
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
hfp_connection_t * get_hfp_connection_context_for_sco_handle(uint16_t handle, hfp_role_t hfp_role){
|
||
btstack_linked_list_iterator_t it;
|
||
btstack_linked_list_iterator_init(&it, hfp_get_connections());
|
||
while (btstack_linked_list_iterator_has_next(&it)){
|
||
hfp_connection_t * hfp_connection = (hfp_connection_t *)btstack_linked_list_iterator_next(&it);
|
||
if ((hfp_connection->sco_handle == handle) && (hfp_connection->local_role == hfp_role)){
|
||
return hfp_connection;
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
hfp_connection_t * get_hfp_connection_context_for_acl_handle(uint16_t handle, hfp_role_t hfp_role){
|
||
btstack_linked_list_iterator_t it;
|
||
btstack_linked_list_iterator_init(&it, hfp_get_connections());
|
||
while (btstack_linked_list_iterator_has_next(&it)){
|
||
hfp_connection_t * hfp_connection = (hfp_connection_t *)btstack_linked_list_iterator_next(&it);
|
||
if ((hfp_connection->acl_handle == handle) && (hfp_connection->local_role == hfp_role)){
|
||
return hfp_connection;
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
static void hfp_vra_handle_disconnect(hfp_connection_t * hfp_connection) {
|
||
hfp_voice_recognition_activation_status_t current_vra_state = hfp_connection->vra_state;
|
||
hfp_connection->vra_state = HFP_VRA_VOICE_RECOGNITION_OFF;
|
||
if (current_vra_state != HFP_VRA_VOICE_RECOGNITION_OFF){
|
||
hfp_emit_voice_recognition_disabled(hfp_connection, ERROR_CODE_SUCCESS);
|
||
} else if (hfp_connection->vra_state_requested != HFP_VRA_VOICE_RECOGNITION_OFF){
|
||
hfp_emit_voice_recognition_disabled(hfp_connection, ERROR_CODE_SUCCESS);
|
||
}
|
||
}
|
||
|
||
static void hfp_reset_voice_recognition(hfp_connection_t * hfp_connection){
|
||
hfp_connection->vra_state = HFP_VRA_VOICE_RECOGNITION_OFF;
|
||
hfp_connection->vra_state_requested = HFP_VRA_VOICE_RECOGNITION_OFF;
|
||
hfp_connection->activate_voice_recognition = false;
|
||
hfp_connection->deactivate_voice_recognition = false;
|
||
hfp_connection->enhanced_voice_recognition_enabled = false;
|
||
hfp_connection->ag_vra_status = 0;
|
||
hfp_connection->ag_vra_state = HFP_VOICE_RECOGNITION_STATE_AG_READY;
|
||
}
|
||
|
||
static hfp_connection_t * create_hfp_connection_context(void){
|
||
hfp_connection_t * hfp_connection = btstack_memory_hfp_connection_get();
|
||
if (!hfp_connection) return NULL;
|
||
|
||
hfp_connection->state = HFP_IDLE;
|
||
hfp_connection->call_state = HFP_CALL_IDLE;
|
||
hfp_connection->codecs_state = HFP_CODECS_IDLE;
|
||
hfp_connection->establish_audio_connection = 0;
|
||
|
||
hfp_connection->parser_state = HFP_PARSER_CMD_HEADER;
|
||
|
||
hfp_connection->acl_handle = HCI_CON_HANDLE_INVALID;
|
||
hfp_connection->sco_handle = HCI_CON_HANDLE_INVALID;
|
||
|
||
hfp_connection->ok_pending = 0;
|
||
hfp_connection->command = HFP_CMD_NONE;
|
||
|
||
hfp_connection->extended_audio_gateway_error = 0;
|
||
hfp_connection->enable_status_update_for_ag_indicators = 0xFF;
|
||
|
||
// parser
|
||
hfp_connection->found_equal_sign = false;
|
||
|
||
// HF only
|
||
hfp_connection->hf_call_status = HFP_CALL_STATUS_NO_HELD_OR_ACTIVE_CALLS;
|
||
hfp_connection->hf_callsetup_status = HFP_CALLSETUP_STATUS_NO_CALL_SETUP_IN_PROGRESS;
|
||
hfp_connection->hf_callheld_status = HFP_CALLHELD_STATUS_NO_CALLS_HELD;
|
||
hfp_connection->operator_name_changed = 0;
|
||
hfp_connection->change_status_update_for_individual_ag_indicators = 0;
|
||
|
||
// AG only
|
||
hfp_connection->send_error = 0;
|
||
hfp_connection->enable_extended_audio_gateway_error_report = 0;
|
||
hfp_connection->call_waiting_notification_enabled = 0;
|
||
|
||
// Codec negotiation
|
||
hfp_connection->suggested_codec = 0;
|
||
hfp_connection->negotiated_codec = 0;
|
||
hfp_connection->codec_confirmed = 0;
|
||
|
||
hfp_reset_voice_recognition(hfp_connection);
|
||
|
||
btstack_linked_list_add_tail(&hfp_connections, (btstack_linked_item_t*)hfp_connection);
|
||
return hfp_connection;
|
||
}
|
||
|
||
void hfp_finalize_connection_context(hfp_connection_t * hfp_connection){
|
||
btstack_linked_list_remove(&hfp_connections, (btstack_linked_item_t*) hfp_connection);
|
||
btstack_memory_hfp_connection_free(hfp_connection);
|
||
}
|
||
|
||
static hfp_connection_t * hfp_create_connection(bd_addr_t bd_addr, hfp_role_t local_role){
|
||
hfp_connection_t * hfp_connection = get_hfp_connection_context_for_bd_addr(bd_addr, local_role);
|
||
if (hfp_connection) return hfp_connection;
|
||
hfp_connection = create_hfp_connection_context();
|
||
if (!hfp_connection) {
|
||
return NULL;
|
||
}
|
||
(void)memcpy(hfp_connection->remote_addr, bd_addr, 6);
|
||
hfp_connection->local_role = local_role;
|
||
log_info("Create HFP context %p: role %u, addr %s", hfp_connection, local_role, bd_addr_to_str(bd_addr));
|
||
|
||
#ifdef ENABLE_NXP_PCM_WBS
|
||
hfp_connection->nxp_start_audio_handle = HCI_CON_HANDLE_INVALID;
|
||
hfp_connection->nxp_stop_audio_handle = HCI_CON_HANDLE_INVALID;
|
||
#endif
|
||
|
||
return hfp_connection;
|
||
}
|
||
|
||
/* @param network.
|
||
* 0 == no ability to reject a call.
|
||
* 1 == ability to reject a call.
|
||
*/
|
||
|
||
/* @param suported_features
|
||
* HF bit 0: EC and/or NR function (yes/no, 1 = yes, 0 = no)
|
||
* HF bit 1: Call waiting or three-way calling(yes/no, 1 = yes, 0 = no)
|
||
* HF bit 2: CLI presentation capability (yes/no, 1 = yes, 0 = no)
|
||
* HF bit 3: Voice recognition activation (yes/no, 1= yes, 0 = no)
|
||
* HF bit 4: Remote volume control (yes/no, 1 = yes, 0 = no)
|
||
* HF bit 5: Wide band speech (yes/no, 1 = yes, 0 = no)
|
||
*/
|
||
/* Bit position:
|
||
* AG bit 0: Three-way calling (yes/no, 1 = yes, 0 = no)
|
||
* AG bit 1: EC and/or NR function (yes/no, 1 = yes, 0 = no)
|
||
* AG bit 2: Voice recognition function (yes/no, 1 = yes, 0 = no)
|
||
* AG bit 3: In-band ring tone capability (yes/no, 1 = yes, 0 = no)
|
||
* AG bit 4: Attach a phone number to a voice tag (yes/no, 1 = yes, 0 = no)
|
||
* AG bit 5: Wide band speech (yes/no, 1 = yes, 0 = no)
|
||
*/
|
||
|
||
void hfp_create_sdp_record(uint8_t * service, uint32_t service_record_handle, uint16_t service_uuid, int rfcomm_channel_nr, const char * name){
|
||
uint8_t* attribute;
|
||
de_create_sequence(service);
|
||
|
||
// 0x0000 "Service Record Handle"
|
||
de_add_number(service, DE_UINT, DE_SIZE_16, BLUETOOTH_ATTRIBUTE_SERVICE_RECORD_HANDLE);
|
||
de_add_number(service, DE_UINT, DE_SIZE_32, service_record_handle);
|
||
|
||
// 0x0001 "Service Class ID List"
|
||
de_add_number(service, DE_UINT, DE_SIZE_16, BLUETOOTH_ATTRIBUTE_SERVICE_CLASS_ID_LIST);
|
||
attribute = de_push_sequence(service);
|
||
{
|
||
// "UUID for Service"
|
||
de_add_number(attribute, DE_UUID, DE_SIZE_16, service_uuid);
|
||
de_add_number(attribute, DE_UUID, DE_SIZE_16, BLUETOOTH_SERVICE_CLASS_GENERIC_AUDIO);
|
||
}
|
||
de_pop_sequence(service, attribute);
|
||
|
||
// 0x0004 "Protocol Descriptor List"
|
||
de_add_number(service, DE_UINT, DE_SIZE_16, BLUETOOTH_ATTRIBUTE_PROTOCOL_DESCRIPTOR_LIST);
|
||
attribute = de_push_sequence(service);
|
||
{
|
||
uint8_t* l2cpProtocol = de_push_sequence(attribute);
|
||
{
|
||
de_add_number(l2cpProtocol, DE_UUID, DE_SIZE_16, BLUETOOTH_PROTOCOL_L2CAP);
|
||
}
|
||
de_pop_sequence(attribute, l2cpProtocol);
|
||
|
||
uint8_t* rfcomm = de_push_sequence(attribute);
|
||
{
|
||
de_add_number(rfcomm, DE_UUID, DE_SIZE_16, BLUETOOTH_PROTOCOL_RFCOMM); // rfcomm_service
|
||
de_add_number(rfcomm, DE_UINT, DE_SIZE_8, rfcomm_channel_nr); // rfcomm channel
|
||
}
|
||
de_pop_sequence(attribute, rfcomm);
|
||
}
|
||
de_pop_sequence(service, attribute);
|
||
|
||
|
||
// 0x0005 "Public Browse Group"
|
||
de_add_number(service, DE_UINT, DE_SIZE_16, BLUETOOTH_ATTRIBUTE_BROWSE_GROUP_LIST); // public browse group
|
||
attribute = de_push_sequence(service);
|
||
{
|
||
de_add_number(attribute, DE_UUID, DE_SIZE_16, BLUETOOTH_ATTRIBUTE_PUBLIC_BROWSE_ROOT);
|
||
}
|
||
de_pop_sequence(service, attribute);
|
||
|
||
// 0x0009 "Bluetooth Profile Descriptor List"
|
||
de_add_number(service, DE_UINT, DE_SIZE_16, BLUETOOTH_ATTRIBUTE_BLUETOOTH_PROFILE_DESCRIPTOR_LIST);
|
||
attribute = de_push_sequence(service);
|
||
{
|
||
uint8_t *sppProfile = de_push_sequence(attribute);
|
||
{
|
||
de_add_number(sppProfile, DE_UUID, DE_SIZE_16, BLUETOOTH_SERVICE_CLASS_HANDSFREE);
|
||
de_add_number(sppProfile, DE_UINT, DE_SIZE_16, 0x0109); // Version 1.9
|
||
}
|
||
de_pop_sequence(attribute, sppProfile);
|
||
}
|
||
de_pop_sequence(service, attribute);
|
||
|
||
// 0x0100 "Service Name"
|
||
if (strlen(name) > 0){
|
||
de_add_number(service, DE_UINT, DE_SIZE_16, 0x0100);
|
||
de_add_data(service, DE_STRING, (uint16_t) strlen(name), (uint8_t *) name);
|
||
}
|
||
}
|
||
|
||
static void hfp_handle_slc_setup_error(hfp_connection_t * hfp_connection, uint8_t status){
|
||
// cache fields for event
|
||
hfp_role_t local_role = hfp_connection->local_role;
|
||
bd_addr_t remote_addr;
|
||
// cppcheck-suppress uninitvar ; remote_addr is reported as uninitialized although it's the destination of the memcpy
|
||
(void)memcpy(remote_addr, hfp_connection->remote_addr, 6);
|
||
// finalize connection struct
|
||
hfp_finalize_connection_context(hfp_connection);
|
||
// emit event
|
||
hfp_emit_slc_connection_event(local_role, status, HCI_CON_HANDLE_INVALID, remote_addr);
|
||
}
|
||
|
||
static void handle_query_rfcomm_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
|
||
UNUSED(packet_type); // ok: handling own sdp events
|
||
UNUSED(channel); // ok: no channel
|
||
UNUSED(size); // ok: handling own sdp events
|
||
|
||
hfp_connection_t * hfp_connection = get_hfp_connection_context_for_bd_addr(hfp_sdp_query_context.remote_address, hfp_sdp_query_context.local_role);
|
||
if (hfp_connection == NULL) {
|
||
log_info("connection with %s and local role %d not found", hfp_sdp_query_context.remote_address, hfp_sdp_query_context.local_role);
|
||
return;
|
||
}
|
||
|
||
switch (hci_event_packet_get_type(packet)){
|
||
case SDP_EVENT_QUERY_RFCOMM_SERVICE:
|
||
hfp_connection->rfcomm_channel_nr = sdp_event_query_rfcomm_service_get_rfcomm_channel(packet);
|
||
break;
|
||
case SDP_EVENT_QUERY_COMPLETE:
|
||
if (hfp_connection->rfcomm_channel_nr > 0){
|
||
hfp_connection->state = HFP_W4_RFCOMM_CONNECTED;
|
||
btstack_packet_handler_t packet_handler;
|
||
switch (hfp_connection->local_role){
|
||
case HFP_ROLE_AG:
|
||
packet_handler = hfp_ag_rfcomm_packet_handler;
|
||
break;
|
||
case HFP_ROLE_HF:
|
||
packet_handler = hfp_hf_rfcomm_packet_handler;
|
||
break;
|
||
default:
|
||
btstack_assert(false);
|
||
return;
|
||
}
|
||
|
||
rfcomm_create_channel(packet_handler, hfp_connection->remote_addr, hfp_connection->rfcomm_channel_nr, NULL);
|
||
|
||
} else {
|
||
uint8_t status = sdp_event_query_complete_get_status(packet);
|
||
if (status == ERROR_CODE_SUCCESS){
|
||
// report service not found
|
||
status = SDP_SERVICE_NOT_FOUND;
|
||
}
|
||
hfp_handle_slc_setup_error(hfp_connection, status);
|
||
log_info("rfcomm service not found, status 0x%02x", status);
|
||
}
|
||
|
||
// register the SDP Query request to check if there is another connection waiting for the query
|
||
// ignore ERROR_CODE_COMMAND_DISALLOWED because in that case, we already have requested an SDP callback
|
||
(void) sdp_client_register_query_callback(&hfp_sdp_query_request);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
// returns 0 if unexpected error or no other link options remained, otherwise 1
|
||
static int hfp_handle_failed_sco_connection(uint8_t status){
|
||
|
||
if (hfp_sco_establishment_active->accept_sco != 0){
|
||
log_info("(e)SCO Connection failed but not started by us");
|
||
return 0;
|
||
}
|
||
|
||
log_info("(e)SCO Connection failed 0x%02x", status);
|
||
switch (status){
|
||
case ERROR_CODE_INVALID_LMP_PARAMETERS_INVALID_LL_PARAMETERS:
|
||
case ERROR_CODE_SCO_AIR_MODE_REJECTED:
|
||
case ERROR_CODE_SCO_INTERVAL_REJECTED:
|
||
case ERROR_CODE_SCO_OFFSET_REJECTED:
|
||
case ERROR_CODE_UNSPECIFIED_ERROR:
|
||
case ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE:
|
||
case ERROR_CODE_UNSUPPORTED_LMP_PARAMETER_VALUE_UNSUPPORTED_LL_PARAMETER_VALUE:
|
||
case ERROR_CODE_UNSUPPORTED_REMOTE_FEATURE_UNSUPPORTED_LMP_FEATURE:
|
||
break;
|
||
default:
|
||
return 0;
|
||
}
|
||
|
||
// note: eSCO_S4 supported flag not available, but it's only relevant for highest CVSD link setting (and the current failed)
|
||
hfp_link_settings_t next_setting = hfp_next_link_setting_for_connection(hfp_sco_establishment_active->link_setting, hfp_sco_establishment_active, false);
|
||
|
||
// handle no valid setting found
|
||
if (next_setting == HFP_LINK_SETTINGS_NONE) {
|
||
if (hfp_sco_establishment_active->negotiated_codec == HFP_CODEC_MSBC){
|
||
log_info("T2/T1 failed, fallback to CVSD - D1");
|
||
hfp_sco_establishment_active->negotiated_codec = HFP_CODEC_CVSD;
|
||
hfp_sco_establishment_active->sco_for_msbc_failed = 1;
|
||
hfp_sco_establishment_active->ag_send_common_codec = true;
|
||
hfp_sco_establishment_active->link_setting = HFP_LINK_SETTINGS_D1;
|
||
} else {
|
||
// no other options
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
log_info("e)SCO Connection: try new link_setting %d", next_setting);
|
||
hfp_sco_establishment_active->establish_audio_connection = 1;
|
||
hfp_sco_establishment_active->link_setting = next_setting;
|
||
hfp_sco_establishment_active->accept_sco = 0;
|
||
hfp_sco_establishment_active = NULL;
|
||
return 1;
|
||
}
|
||
|
||
void hfp_handle_hci_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size, hfp_role_t local_role){
|
||
UNUSED(packet_type);
|
||
UNUSED(channel); // ok: no channel
|
||
UNUSED(size);
|
||
|
||
bd_addr_t event_addr;
|
||
hci_con_handle_t handle;
|
||
hfp_connection_t * hfp_connection = NULL;
|
||
uint8_t status;
|
||
|
||
log_debug("HFP HCI event handler type %u, event type %x, size %u", packet_type, hci_event_packet_get_type(packet), size);
|
||
|
||
switch (hci_event_packet_get_type(packet)) {
|
||
|
||
case HCI_EVENT_CONNECTION_REQUEST:
|
||
switch(hci_event_connection_request_get_link_type(packet)){
|
||
case 0: // SCO
|
||
case 2: // eSCO
|
||
hci_event_connection_request_get_bd_addr(packet, event_addr);
|
||
hfp_connection = get_hfp_connection_context_for_bd_addr(event_addr, local_role);
|
||
if (!hfp_connection) break;
|
||
if (hci_event_connection_request_get_link_type(packet) == 2){
|
||
hfp_connection->accept_sco = 2;
|
||
} else {
|
||
hfp_connection->accept_sco = 1;
|
||
}
|
||
// configure SBC coded if needed
|
||
hfp_prepare_for_sco(hfp_connection);
|
||
log_info("accept sco %u\n", hfp_connection->accept_sco);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
break;
|
||
|
||
case HCI_EVENT_COMMAND_STATUS:
|
||
if (hci_event_command_status_get_command_opcode(packet) == hci_setup_synchronous_connection.opcode) {
|
||
if (hfp_sco_establishment_active == NULL) break;
|
||
status = hci_event_command_status_get_status(packet);
|
||
if (status == ERROR_CODE_SUCCESS) break;
|
||
|
||
hfp_connection = hfp_sco_establishment_active;
|
||
if (hfp_handle_failed_sco_connection(status)) break;
|
||
|
||
hfp_connection->accept_sco = 0;
|
||
hfp_connection->establish_audio_connection = 0;
|
||
hfp_connection->state = HFP_SERVICE_LEVEL_CONNECTION_ESTABLISHED;
|
||
hfp_sco_establishment_active = NULL;
|
||
hfp_emit_sco_connection_established(hfp_connection, status,
|
||
hfp_connection->negotiated_codec, 0, 0);
|
||
}
|
||
break;
|
||
|
||
case HCI_EVENT_SYNCHRONOUS_CONNECTION_COMPLETE:{
|
||
if (hfp_sco_establishment_active == NULL) break;
|
||
|
||
hfp_connection = hfp_sco_establishment_active;
|
||
|
||
status = hci_event_synchronous_connection_complete_get_status(packet);
|
||
if (status != ERROR_CODE_SUCCESS){
|
||
if (hfp_handle_failed_sco_connection(status)) break;
|
||
|
||
hfp_connection->accept_sco = 0;
|
||
hfp_connection->establish_audio_connection = 0;
|
||
hfp_connection->state = HFP_SERVICE_LEVEL_CONNECTION_ESTABLISHED;
|
||
hfp_sco_establishment_active = NULL;
|
||
hfp_emit_sco_connection_established(hfp_connection, status,
|
||
hfp_connection->negotiated_codec, 0, 0);
|
||
break;
|
||
}
|
||
|
||
uint16_t sco_handle = hci_event_synchronous_connection_complete_get_handle(packet);
|
||
uint8_t link_type = hci_event_synchronous_connection_complete_get_link_type(packet);
|
||
uint8_t transmission_interval = hci_event_synchronous_connection_complete_get_transmission_interval(packet); // measured in slots
|
||
uint8_t retransmission_interval = hci_event_synchronous_connection_complete_get_retransmission_interval(packet);// measured in slots
|
||
uint16_t rx_packet_length = hci_event_synchronous_connection_complete_get_rx_packet_length(packet); // measured in bytes
|
||
uint16_t tx_packet_length = hci_event_synchronous_connection_complete_get_tx_packet_length(packet); // measured in bytes
|
||
|
||
switch (link_type){
|
||
case 0x00:
|
||
log_info("SCO Connection established.");
|
||
if (transmission_interval != 0) log_error("SCO Connection: transmission_interval not zero: %d.", transmission_interval);
|
||
if (retransmission_interval != 0) log_error("SCO Connection: retransmission_interval not zero: %d.", retransmission_interval);
|
||
if (rx_packet_length != 0) log_error("SCO Connection: rx_packet_length not zero: %d.", rx_packet_length);
|
||
if (tx_packet_length != 0) log_error("SCO Connection: tx_packet_length not zero: %d.", tx_packet_length);
|
||
break;
|
||
case 0x02:
|
||
log_info("eSCO Connection established. \n");
|
||
break;
|
||
default:
|
||
log_error("(e)SCO reserved link_type 0x%2x", link_type);
|
||
break;
|
||
}
|
||
|
||
log_info("sco_handle 0x%2x, address %s, transmission_interval %u slots, retransmission_interval %u slots, "
|
||
" rx_packet_length %u bytes, tx_packet_length %u bytes, air_mode 0x%2x (0x02 == CVSD)\n", sco_handle,
|
||
bd_addr_to_str(event_addr), transmission_interval, retransmission_interval, rx_packet_length, tx_packet_length,
|
||
hci_event_synchronous_connection_complete_get_air_mode(packet));
|
||
|
||
if (hfp_connection->state == HFP_W4_CONNECTION_ESTABLISHED_TO_SHUTDOWN){
|
||
log_info("SCO about to disconnect: HFP_W4_CONNECTION_ESTABLISHED_TO_SHUTDOWN");
|
||
hfp_connection->state = HFP_W2_DISCONNECT_SCO;
|
||
break;
|
||
}
|
||
hfp_connection->sco_handle = sco_handle;
|
||
hfp_connection->accept_sco = 0;
|
||
hfp_connection->establish_audio_connection = 0;
|
||
|
||
hfp_connection->state = HFP_AUDIO_CONNECTION_ESTABLISHED;
|
||
|
||
switch (hfp_connection->vra_state){
|
||
case HFP_VRA_VOICE_RECOGNITION_ACTIVATED:
|
||
hfp_connection->ag_audio_connection_opened_before_vra = false;
|
||
break;
|
||
default:
|
||
hfp_connection->ag_audio_connection_opened_before_vra = true;
|
||
break;
|
||
}
|
||
|
||
hfp_sco_establishment_active = NULL;
|
||
|
||
hfp_emit_sco_connection_established(hfp_connection, status,
|
||
hfp_connection->negotiated_codec, rx_packet_length, tx_packet_length);
|
||
|
||
#ifdef ENABLE_NXP_PCM_WBS
|
||
hfp_connection->nxp_start_audio_handle = hfp_connection->sco_handle;
|
||
#endif
|
||
break;
|
||
}
|
||
|
||
case HCI_EVENT_DISCONNECTION_COMPLETE:
|
||
handle = little_endian_read_16(packet,3);
|
||
hfp_connection = get_hfp_connection_context_for_sco_handle(handle, local_role);
|
||
|
||
if (!hfp_connection) break;
|
||
|
||
#ifdef ENABLE_CC256X_ASSISTED_HFP
|
||
hfp_connection->cc256x_send_wbs_disassociate = true;
|
||
#endif
|
||
#ifdef ENABLE_BCM_PCM_WBS
|
||
hfp_connection->bcm_send_disable_wbs = true;
|
||
#endif
|
||
#ifdef ENABLE_NXP_PCM_WBS
|
||
hfp_connection->nxp_stop_audio_handle = hfp_connection->sco_handle;
|
||
#endif
|
||
|
||
if (hfp_connection->sco_handle == handle){
|
||
hfp_connection->sco_handle = HCI_CON_HANDLE_INVALID;
|
||
hfp_connection->release_audio_connection = 0;
|
||
hfp_connection->state = HFP_SERVICE_LEVEL_CONNECTION_ESTABLISHED;
|
||
hfp_emit_audio_connection_released(hfp_connection, handle);
|
||
|
||
hfp_connection->ag_audio_connection_opened_before_vra = false;
|
||
|
||
if (hfp_connection->acl_handle == HCI_CON_HANDLE_INVALID){
|
||
hfp_vra_handle_disconnect(hfp_connection);
|
||
// cache acl handle and role for event
|
||
hci_con_handle_t acl_handle = hfp_connection->acl_handle;
|
||
hfp_role_t role = hfp_connection->local_role;
|
||
hfp_finalize_connection_context(hfp_connection);
|
||
hfp_emit_slc_released(acl_handle, role);
|
||
break;
|
||
} else if (hfp_connection->release_slc_connection == 1){
|
||
hfp_connection->release_slc_connection = 0;
|
||
hfp_connection->state = HFP_W2_DISCONNECT_RFCOMM;
|
||
rfcomm_disconnect(hfp_connection->acl_handle);
|
||
}
|
||
}
|
||
|
||
if (hfp_connection->state == HFP_W4_SCO_DISCONNECTED_TO_SHUTDOWN){
|
||
// RFCOMM already closed -> remote power off
|
||
#if defined(ENABLE_CC256X_ASSISTED_HFP) || defined (ENABLE_BCM_PCM_WBS)
|
||
hfp_connection->state = HFP_W4_WBS_SHUTDOWN;
|
||
#else
|
||
hfp_finalize_connection_context(hfp_connection);
|
||
#endif
|
||
break;
|
||
}
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
void hfp_handle_rfcomm_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size, hfp_role_t local_role){
|
||
UNUSED(packet_type);
|
||
UNUSED(channel); // ok: no channel
|
||
UNUSED(size);
|
||
|
||
bd_addr_t event_addr;
|
||
uint16_t rfcomm_cid;
|
||
hfp_connection_t * hfp_connection = NULL;
|
||
uint8_t status;
|
||
|
||
log_debug("HFP packet_handler type %u, event type %x, size %u", packet_type, hci_event_packet_get_type(packet), size);
|
||
|
||
switch (hci_event_packet_get_type(packet)) {
|
||
|
||
case RFCOMM_EVENT_INCOMING_CONNECTION:
|
||
// data: event (8), len(8), address(48), channel (8), rfcomm_cid (16)
|
||
rfcomm_event_incoming_connection_get_bd_addr(packet, event_addr);
|
||
hfp_connection = hfp_create_connection(event_addr, local_role);
|
||
if (!hfp_connection){
|
||
log_info("hfp: no memory to accept incoming connection - decline");
|
||
rfcomm_decline_connection(rfcomm_event_incoming_connection_get_rfcomm_cid(packet));
|
||
return;
|
||
}
|
||
if (hfp_connection->state != HFP_IDLE) {
|
||
log_error("hfp: incoming connection but not idle, reject");
|
||
rfcomm_decline_connection(rfcomm_event_incoming_connection_get_rfcomm_cid(packet));
|
||
return;
|
||
}
|
||
|
||
hfp_connection->rfcomm_cid = rfcomm_event_incoming_connection_get_rfcomm_cid(packet);
|
||
hfp_connection->state = HFP_W4_RFCOMM_CONNECTED;
|
||
rfcomm_accept_connection(hfp_connection->rfcomm_cid);
|
||
break;
|
||
|
||
case RFCOMM_EVENT_CHANNEL_OPENED:
|
||
rfcomm_event_channel_opened_get_bd_addr(packet, event_addr);
|
||
|
||
hfp_connection = get_hfp_connection_context_for_bd_addr(event_addr, local_role);
|
||
btstack_assert(hfp_connection != NULL);
|
||
|
||
if (hfp_connection->state != HFP_W4_RFCOMM_CONNECTED){
|
||
break;
|
||
}
|
||
|
||
status = rfcomm_event_channel_opened_get_status(packet);
|
||
if (status != ERROR_CODE_SUCCESS) {
|
||
hfp_handle_slc_setup_error(hfp_connection, status);
|
||
break;
|
||
}
|
||
|
||
hfp_connection->acl_handle = rfcomm_event_channel_opened_get_con_handle(packet);
|
||
hfp_connection->rfcomm_cid = rfcomm_event_channel_opened_get_rfcomm_cid(packet);
|
||
hfp_connection->rfcomm_mtu = rfcomm_event_channel_opened_get_max_frame_size(packet);
|
||
bd_addr_copy(hfp_connection->remote_addr, event_addr);
|
||
hfp_connection->state = HFP_EXCHANGE_SUPPORTED_FEATURES;
|
||
|
||
if (local_role == HFP_ROLE_HF) {
|
||
// setup HF Indicators
|
||
uint8_t i;
|
||
for (i=0; i < hfp_hf_indicators_nr; i++){
|
||
hfp_connection->generic_status_indicators[i].uuid = hfp_hf_indicators[i];
|
||
hfp_connection->generic_status_indicators[i].state = 0;
|
||
}
|
||
}
|
||
|
||
rfcomm_request_can_send_now_event(hfp_connection->rfcomm_cid);
|
||
break;
|
||
|
||
case RFCOMM_EVENT_CHANNEL_CLOSED:
|
||
rfcomm_cid = little_endian_read_16(packet,2);
|
||
hfp_connection = get_hfp_connection_context_for_rfcomm_cid(rfcomm_cid);
|
||
if (!hfp_connection) break;
|
||
switch (hfp_connection->state){
|
||
case HFP_W4_RFCOMM_DISCONNECTED_AND_RESTART:
|
||
hfp_connection->acl_handle = HCI_CON_HANDLE_INVALID;
|
||
hfp_connection->state = HFP_IDLE;
|
||
hfp_establish_service_level_connection(hfp_connection->remote_addr, hfp_connection->service_uuid, local_role);
|
||
break;
|
||
|
||
case HFP_AUDIO_CONNECTION_ESTABLISHED:
|
||
// service connection was released, this implicitly releases audio connection as well
|
||
hfp_connection->release_audio_connection = 0;
|
||
hfp_connection->acl_handle = HCI_CON_HANDLE_INVALID;
|
||
hfp_connection->state = HFP_W4_SCO_DISCONNECTED_TO_SHUTDOWN;
|
||
gap_disconnect(hfp_connection->sco_handle);
|
||
break;
|
||
|
||
default: {
|
||
// regular case
|
||
hfp_vra_handle_disconnect(hfp_connection);
|
||
hci_con_handle_t acl_handle = hfp_connection->acl_handle;
|
||
hfp_role_t role = hfp_connection->local_role;
|
||
hfp_finalize_connection_context(hfp_connection);
|
||
hfp_emit_slc_released(acl_handle, role);
|
||
break;
|
||
}
|
||
}
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
// translates command string into hfp_command_t CMD
|
||
|
||
typedef struct {
|
||
const char * command;
|
||
hfp_command_t command_id;
|
||
} hfp_command_entry_t;
|
||
|
||
static hfp_command_entry_t hfp_ag_command_table[] = {
|
||
{ "AT+BAC=", HFP_CMD_AVAILABLE_CODECS },
|
||
{ "AT+BCC", HFP_CMD_TRIGGER_CODEC_CONNECTION_SETUP },
|
||
{ "AT+BCS=", HFP_CMD_HF_CONFIRMED_CODEC },
|
||
{ "AT+BIA=", HFP_CMD_ENABLE_INDIVIDUAL_AG_INDICATOR_STATUS_UPDATE, }, // +BIA:<enabled>,,<enabled>,,,<enabled>
|
||
{ "AT+BIEV=", HFP_CMD_HF_INDICATOR_STATUS },
|
||
{ "AT+BIND=", HFP_CMD_LIST_GENERIC_STATUS_INDICATORS },
|
||
{ "AT+BIND=?", HFP_CMD_RETRIEVE_GENERIC_STATUS_INDICATORS },
|
||
{ "AT+BIND?", HFP_CMD_RETRIEVE_GENERIC_STATUS_INDICATORS_STATE },
|
||
{ "AT+BINP=", HFP_CMD_HF_REQUEST_PHONE_NUMBER },
|
||
{ "AT+BLDN", HFP_CMD_REDIAL_LAST_NUMBER },
|
||
{ "AT+BRSF=", HFP_CMD_SUPPORTED_FEATURES },
|
||
{ "AT+BTRH=", HFP_CMD_RESPONSE_AND_HOLD_COMMAND },
|
||
{ "AT+BTRH?", HFP_CMD_RESPONSE_AND_HOLD_QUERY },
|
||
{ "AT+BVRA=", HFP_CMD_HF_ACTIVATE_VOICE_RECOGNITION },
|
||
{ "AT+CCWA=", HFP_CMD_ENABLE_CALL_WAITING_NOTIFICATION},
|
||
{ "AT+CHLD=", HFP_CMD_CALL_HOLD },
|
||
{ "AT+CHLD=?", HFP_CMD_SUPPORT_CALL_HOLD_AND_MULTIPARTY_SERVICES },
|
||
{ "AT+CHUP", HFP_CMD_HANG_UP_CALL },
|
||
{ "AT+CIND=?", HFP_CMD_RETRIEVE_AG_INDICATORS },
|
||
{ "AT+CIND?", HFP_CMD_RETRIEVE_AG_INDICATORS_STATUS },
|
||
{ "AT+CLCC", HFP_CMD_LIST_CURRENT_CALLS },
|
||
{ "AT+CLIP=", HFP_CMD_ENABLE_CLIP},
|
||
{ "AT+CMEE=", HFP_CMD_ENABLE_EXTENDED_AUDIO_GATEWAY_ERROR},
|
||
{ "AT+CMER=", HFP_CMD_ENABLE_INDICATOR_STATUS_UPDATE },
|
||
{ "AT+CNUM", HFP_CMD_GET_SUBSCRIBER_NUMBER_INFORMATION },
|
||
{ "AT+COPS=", HFP_CMD_QUERY_OPERATOR_SELECTION_NAME_FORMAT },
|
||
{ "AT+COPS?", HFP_CMD_QUERY_OPERATOR_SELECTION_NAME },
|
||
{ "AT+IPHONEACCEV=", HFP_CMD_APPLE_ACCESSORY_STATE },
|
||
{ "AT+NREC=", HFP_CMD_TURN_OFF_EC_AND_NR, },
|
||
{ "AT+VGM=", HFP_CMD_SET_MICROPHONE_GAIN },
|
||
{ "AT+VGS=", HFP_CMD_SET_SPEAKER_GAIN },
|
||
{ "AT+VTS=", HFP_CMD_TRANSMIT_DTMF_CODES },
|
||
{ "AT+XAPL=", HFP_CMD_APPLE_ACCESSORY_INFORMATION },
|
||
{ "ATA", HFP_CMD_CALL_ANSWERED },
|
||
};
|
||
|
||
static hfp_command_entry_t hfp_hf_command_table[] = {
|
||
{ "+BCS:", HFP_CMD_AG_SUGGESTED_CODEC },
|
||
{ "+BIND:", HFP_CMD_SET_GENERIC_STATUS_INDICATOR_STATUS },
|
||
{ "+BINP:", HFP_CMD_AG_SENT_PHONE_NUMBER },
|
||
{ "+BRSF:", HFP_CMD_SUPPORTED_FEATURES },
|
||
{ "+BSIR:", HFP_CMD_CHANGE_IN_BAND_RING_TONE_SETTING },
|
||
{ "+BTRH:", HFP_CMD_RESPONSE_AND_HOLD_STATUS },
|
||
{ "+BVRA:", HFP_CMD_AG_ACTIVATE_VOICE_RECOGNITION },
|
||
{ "+CCWA:", HFP_CMD_AG_SENT_CALL_WAITING_NOTIFICATION_UPDATE, },
|
||
{ "+CHLD:", HFP_CMD_SUPPORT_CALL_HOLD_AND_MULTIPARTY_SERVICES },
|
||
{ "+CIEV:", HFP_CMD_TRANSFER_AG_INDICATOR_STATUS},
|
||
{ "+CIND:", HFP_CMD_RETRIEVE_AG_INDICATORS_GENERIC },
|
||
{ "+CLCC:", HFP_CMD_LIST_CURRENT_CALLS },
|
||
{ "+CLIP:", HFP_CMD_AG_SENT_CLIP_INFORMATION },
|
||
{ "+CME ERROR:", HFP_CMD_EXTENDED_AUDIO_GATEWAY_ERROR },
|
||
{ "+CNUM:", HFP_CMD_GET_SUBSCRIBER_NUMBER_INFORMATION},
|
||
{ "+COPS:", HFP_CMD_QUERY_OPERATOR_SELECTION_NAME },
|
||
{ "+VGM:", HFP_CMD_SET_MICROPHONE_GAIN },
|
||
{ "+VGM=", HFP_CMD_SET_MICROPHONE_GAIN },
|
||
{ "+VGS:", HFP_CMD_SET_SPEAKER_GAIN},
|
||
{ "+VGS=", HFP_CMD_SET_SPEAKER_GAIN},
|
||
{ "+XAPL=", HFP_CMD_APPLE_DEVICE_INFORMATION },
|
||
{ "ERROR", HFP_CMD_ERROR},
|
||
{ "NOP", HFP_CMD_NONE}, // dummy command used by unit tests
|
||
{ "OK", HFP_CMD_OK },
|
||
{ "RING", HFP_CMD_RING },
|
||
};
|
||
|
||
static const hfp_custom_at_command_t *
|
||
hfp_custom_command_lookup(bool isHandsFree, const char *text) {
|
||
btstack_linked_list_t * custom_commands = isHandsFree ? &hfp_custom_commands_hf : &hfp_custom_commands_ag;
|
||
btstack_linked_list_iterator_t it;
|
||
btstack_linked_list_iterator_init(&it, custom_commands);
|
||
while (btstack_linked_list_iterator_has_next(&it)) {
|
||
hfp_custom_at_command_t *at_command = (hfp_custom_at_command_t *) btstack_linked_list_iterator_next(&it);
|
||
int match = strcmp(text, at_command->command);
|
||
if (match == 0) {
|
||
return at_command;
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
static hfp_command_t parse_command(const char * line_buffer, int isHandsFree){
|
||
|
||
// check for custom commands, AG only
|
||
const hfp_custom_at_command_t * custom_at_command = hfp_custom_command_lookup(isHandsFree, line_buffer);
|
||
if (custom_at_command != NULL){
|
||
return HFP_CMD_CUSTOM_MESSAGE;
|
||
}
|
||
|
||
// table lookup based on role
|
||
uint16_t num_entries;
|
||
hfp_command_entry_t * table;
|
||
if (isHandsFree == 0){
|
||
table = hfp_ag_command_table;
|
||
num_entries = sizeof(hfp_ag_command_table) / sizeof(hfp_command_entry_t);
|
||
} else {
|
||
table = hfp_hf_command_table;
|
||
num_entries = sizeof(hfp_hf_command_table) / sizeof(hfp_command_entry_t);
|
||
}
|
||
// binary search
|
||
uint16_t left = 0;
|
||
uint16_t right = num_entries - 1;
|
||
while (left <= right){
|
||
uint16_t middle = left + (right - left) / 2;
|
||
hfp_command_entry_t *entry = &table[middle];
|
||
int match = strcmp(line_buffer, entry->command);
|
||
if (match < 0){
|
||
// search term is lower than middle element
|
||
if (middle == 0) break;
|
||
right = middle - 1;
|
||
} else if (match == 0){
|
||
return entry->command_id;
|
||
} else {
|
||
// search term is higher than middle element
|
||
left = middle + 1;
|
||
}
|
||
}
|
||
|
||
// note: if parser in CMD_HEADER state would treats digits and maybe '+' as separator, match on "ATD" would work.
|
||
// note: phone number is currently expected in line_buffer[3..]
|
||
// prefix match on 'ATD', AG only
|
||
if ((isHandsFree == 0) && (strncmp(line_buffer, HFP_CALL_PHONE_NUMBER, strlen(HFP_CALL_PHONE_NUMBER)) == 0)){
|
||
return HFP_CMD_CALL_PHONE_NUMBER;
|
||
}
|
||
|
||
// Valid looking, but unknown commands/responses
|
||
if ((isHandsFree == 0) && (strncmp(line_buffer, "AT+", 3) == 0)){
|
||
return HFP_CMD_UNKNOWN;
|
||
}
|
||
|
||
if ((isHandsFree != 0) && (strncmp(line_buffer, "+", 1) == 0)){
|
||
return HFP_CMD_UNKNOWN;
|
||
}
|
||
|
||
return HFP_CMD_NONE;
|
||
}
|
||
|
||
static void hfp_parser_store_byte(hfp_connection_t * hfp_connection, uint8_t byte){
|
||
if ((hfp_connection->line_size + 1) >= HFP_MAX_VR_TEXT_SIZE) return;
|
||
hfp_connection->line_buffer[hfp_connection->line_size++] = byte;
|
||
hfp_connection->line_buffer[hfp_connection->line_size] = 0;
|
||
}
|
||
static int hfp_parser_is_buffer_empty(hfp_connection_t * hfp_connection){
|
||
return hfp_connection->line_size == 0;
|
||
}
|
||
|
||
static int hfp_parser_is_end_of_line(uint8_t byte){
|
||
return (byte == '\n') || (byte == '\r');
|
||
}
|
||
|
||
void hfp_parser_reset_line_buffer(hfp_connection_t *hfp_connection) {
|
||
hfp_connection->line_size = 0;
|
||
// we don't set the first byte to '\0' to allow access to last argument e.g. in hfp_hf_handle_rfcommand
|
||
}
|
||
|
||
static void hfp_parser_store_if_token(hfp_connection_t * hfp_connection, uint8_t byte){
|
||
switch (byte){
|
||
case ',':
|
||
case '-':
|
||
case ';':
|
||
case '(':
|
||
case ')':
|
||
case '\n':
|
||
case '\r':
|
||
break;
|
||
default:
|
||
hfp_parser_store_byte(hfp_connection, byte);
|
||
break;
|
||
}
|
||
}
|
||
|
||
static bool hfp_parser_is_separator( uint8_t byte){
|
||
switch (byte){
|
||
case ',':
|
||
case '-':
|
||
case ';':
|
||
case '\n':
|
||
case '\r':
|
||
return true;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// returns true if received bytes was processed. Otherwise, functions will be called with same byte again
|
||
// this is used to for a one byte lookahead, where an unexpected byte is pushed back by returning false
|
||
static bool hfp_parse_byte(hfp_connection_t * hfp_connection, uint8_t byte, int isHandsFree){
|
||
|
||
#ifdef HFP_DEBUG
|
||
if (byte >= ' '){
|
||
printf("Parse '%c' - state %u, buffer %s\n", byte, hfp_connection->parser_state, hfp_connection->line_buffer);
|
||
} else {
|
||
printf("Parse 0x%02x - state %u, buffer %s\n", byte, hfp_connection->parser_state, hfp_connection->line_buffer);
|
||
}
|
||
#endif
|
||
|
||
// handle doubles quotes
|
||
if (byte == '"'){
|
||
hfp_connection->parser_quoted = !hfp_connection->parser_quoted;
|
||
return true;
|
||
}
|
||
if (hfp_connection->parser_quoted) {
|
||
hfp_parser_store_byte(hfp_connection, byte);
|
||
return true;
|
||
}
|
||
|
||
// ignore spaces outside command or double quotes (required e.g. for '+CME ERROR:..") command
|
||
if ((byte == ' ') && (hfp_connection->parser_state != HFP_PARSER_CMD_HEADER)) return true;
|
||
|
||
bool processed = true;
|
||
|
||
switch (hfp_connection->parser_state) {
|
||
case HFP_PARSER_CMD_HEADER:
|
||
switch (byte) {
|
||
case '\n':
|
||
case '\r':
|
||
case ';':
|
||
// ignore separator
|
||
break;
|
||
case ':':
|
||
case '?':
|
||
// store separator
|
||
hfp_parser_store_byte(hfp_connection, byte);
|
||
break;
|
||
case '=':
|
||
// equal sign: remember and wait for next char to decided between '=?' and '=\?'
|
||
hfp_connection->found_equal_sign = true;
|
||
hfp_parser_store_byte(hfp_connection, byte);
|
||
return true;
|
||
default:
|
||
// store if not lookahead
|
||
if (!hfp_connection->found_equal_sign) {
|
||
hfp_parser_store_byte(hfp_connection, byte);
|
||
return true;
|
||
}
|
||
// mark as lookahead
|
||
processed = false;
|
||
break;
|
||
}
|
||
|
||
// ignore empty tokens
|
||
if (hfp_parser_is_buffer_empty(hfp_connection)) return true;
|
||
|
||
// parse
|
||
hfp_connection->command = parse_command((char *)hfp_connection->line_buffer, isHandsFree);
|
||
|
||
// pick +CIND version based on connection state: descriptions during SLC vs. states later
|
||
if (hfp_connection->command == HFP_CMD_RETRIEVE_AG_INDICATORS_GENERIC){
|
||
switch(hfp_connection->state){
|
||
case HFP_W4_RETRIEVE_INDICATORS_STATUS:
|
||
hfp_connection->command = HFP_CMD_RETRIEVE_AG_INDICATORS_STATUS;
|
||
break;
|
||
case HFP_W4_RETRIEVE_INDICATORS:
|
||
hfp_connection->command = HFP_CMD_RETRIEVE_AG_INDICATORS;
|
||
break;
|
||
default:
|
||
hfp_connection->command = HFP_CMD_UNKNOWN;
|
||
break;
|
||
}
|
||
}
|
||
|
||
log_info("command string '%s', handsfree %u -> cmd id %u", (char *)hfp_connection->line_buffer, isHandsFree, hfp_connection->command);
|
||
|
||
// store command id for custom command and just store rest of line
|
||
if (hfp_connection->command == HFP_CMD_CUSTOM_MESSAGE){
|
||
const hfp_custom_at_command_t * at_command = hfp_custom_command_lookup(isHandsFree, (const char *) hfp_connection->line_buffer);
|
||
hfp_connection->custom_at_command_id = at_command->command_id;
|
||
hfp_connection->parser_state = HFP_PARSER_CUSTOM_COMMAND;
|
||
return processed;
|
||
}
|
||
|
||
// next state
|
||
hfp_parser_reset_line_buffer(hfp_connection);
|
||
hfp_connection->parser_state = HFP_PARSER_CMD_SEQUENCE;
|
||
|
||
return processed;
|
||
|
||
case HFP_PARSER_CMD_SEQUENCE:
|
||
// handle empty fields
|
||
if ((byte == ',' ) && (hfp_connection->line_size == 0)){
|
||
hfp_connection->line_buffer[0] = 0;
|
||
hfp_connection->ignore_value = 1;
|
||
parse_sequence(hfp_connection);
|
||
return true;
|
||
}
|
||
|
||
hfp_parser_store_if_token(hfp_connection, byte);
|
||
if (!hfp_parser_is_separator(byte)) return true;
|
||
|
||
// ignore empty tokens
|
||
switch (hfp_connection->command){
|
||
case HFP_CMD_AG_ACTIVATE_VOICE_RECOGNITION:
|
||
// don't ignore empty string
|
||
break;
|
||
default:
|
||
if (hfp_parser_is_buffer_empty(hfp_connection) && (hfp_connection->ignore_value == 0)) {
|
||
return true;
|
||
}
|
||
break;
|
||
}
|
||
|
||
parse_sequence(hfp_connection);
|
||
|
||
hfp_parser_reset_line_buffer(hfp_connection);
|
||
|
||
switch (hfp_connection->command){
|
||
case HFP_CMD_AG_SENT_PHONE_NUMBER:
|
||
case HFP_CMD_AG_SENT_CALL_WAITING_NOTIFICATION_UPDATE:
|
||
case HFP_CMD_AG_SENT_CLIP_INFORMATION:
|
||
case HFP_CMD_TRANSFER_AG_INDICATOR_STATUS:
|
||
case HFP_CMD_QUERY_OPERATOR_SELECTION_NAME:
|
||
case HFP_CMD_QUERY_OPERATOR_SELECTION_NAME_FORMAT:
|
||
case HFP_CMD_RETRIEVE_AG_INDICATORS:
|
||
case HFP_CMD_RETRIEVE_GENERIC_STATUS_INDICATORS_STATE:
|
||
case HFP_CMD_HF_INDICATOR_STATUS:
|
||
hfp_connection->parser_state = HFP_PARSER_SECOND_ITEM;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
return true;
|
||
|
||
case HFP_PARSER_SECOND_ITEM:
|
||
|
||
hfp_parser_store_if_token(hfp_connection, byte);
|
||
if (!hfp_parser_is_separator(byte)) return true;
|
||
|
||
switch (hfp_connection->command){
|
||
case HFP_CMD_QUERY_OPERATOR_SELECTION_NAME:
|
||
log_info("format %s, ", hfp_connection->line_buffer);
|
||
hfp_connection->network_operator.format = btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
break;
|
||
case HFP_CMD_QUERY_OPERATOR_SELECTION_NAME_FORMAT:
|
||
log_info("format %s \n", hfp_connection->line_buffer);
|
||
hfp_connection->network_operator.format = btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
break;
|
||
case HFP_CMD_LIST_GENERIC_STATUS_INDICATORS:
|
||
case HFP_CMD_RETRIEVE_GENERIC_STATUS_INDICATORS:
|
||
case HFP_CMD_RETRIEVE_GENERIC_STATUS_INDICATORS_STATE:
|
||
hfp_connection->generic_status_indicators[hfp_connection->parser_item_index].state = (uint8_t)btstack_atoi((char*)hfp_connection->line_buffer);
|
||
break;
|
||
case HFP_CMD_TRANSFER_AG_INDICATOR_STATUS:
|
||
hfp_connection->ag_indicators[hfp_connection->parser_item_index].status = (uint8_t)btstack_atoi((char*)hfp_connection->line_buffer);
|
||
log_info("%d \n", hfp_connection->ag_indicators[hfp_connection->parser_item_index].status);
|
||
hfp_connection->ag_indicators[hfp_connection->parser_item_index].status_changed = 1;
|
||
break;
|
||
case HFP_CMD_RETRIEVE_AG_INDICATORS:
|
||
hfp_connection->ag_indicators[hfp_connection->parser_item_index].min_range = btstack_atoi((char *)hfp_connection->line_buffer);
|
||
log_info("%s, ", hfp_connection->line_buffer);
|
||
break;
|
||
case HFP_CMD_AG_SENT_PHONE_NUMBER:
|
||
case HFP_CMD_AG_SENT_CALL_WAITING_NOTIFICATION_UPDATE:
|
||
case HFP_CMD_AG_SENT_CLIP_INFORMATION:
|
||
hfp_connection->bnip_type = (uint8_t)btstack_atoi((char*)hfp_connection->line_buffer);
|
||
break;
|
||
case HFP_CMD_HF_INDICATOR_STATUS:
|
||
hfp_connection->parser_indicator_value = btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
hfp_parser_reset_line_buffer(hfp_connection);
|
||
|
||
hfp_connection->parser_state = HFP_PARSER_THIRD_ITEM;
|
||
|
||
return true;
|
||
|
||
case HFP_PARSER_THIRD_ITEM:
|
||
|
||
hfp_parser_store_if_token(hfp_connection, byte);
|
||
if (!hfp_parser_is_separator(byte)) return true;
|
||
|
||
switch (hfp_connection->command){
|
||
case HFP_CMD_QUERY_OPERATOR_SELECTION_NAME:
|
||
btstack_strcpy(hfp_connection->network_operator.name, HFP_MAX_NETWORK_OPERATOR_NAME_SIZE, (char *)hfp_connection->line_buffer);
|
||
break;
|
||
case HFP_CMD_RETRIEVE_AG_INDICATORS:
|
||
hfp_connection->ag_indicators[hfp_connection->parser_item_index].max_range = btstack_atoi((char *)hfp_connection->line_buffer);
|
||
hfp_next_indicators_index(hfp_connection);
|
||
hfp_connection->ag_indicators_nr = hfp_connection->parser_item_index;
|
||
break;
|
||
case HFP_CMD_AG_SENT_CLIP_INFORMATION:
|
||
case HFP_CMD_AG_SENT_CALL_WAITING_NOTIFICATION_UPDATE:
|
||
// track if last argument exists
|
||
hfp_connection->clip_have_alpha = hfp_connection->line_size != 0;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
hfp_parser_reset_line_buffer(hfp_connection);
|
||
|
||
if (hfp_connection->command == HFP_CMD_RETRIEVE_AG_INDICATORS){
|
||
hfp_connection->parser_state = HFP_PARSER_CMD_SEQUENCE;
|
||
}
|
||
return true;
|
||
|
||
case HFP_PARSER_CUSTOM_COMMAND:
|
||
if (hfp_parser_is_end_of_line(byte) == false){
|
||
hfp_parser_store_byte(hfp_connection, byte);
|
||
}
|
||
return true;
|
||
|
||
default:
|
||
btstack_assert(false);
|
||
return true;
|
||
}
|
||
}
|
||
|
||
void hfp_parse(hfp_connection_t * hfp_connection, uint8_t byte, int isHandsFree){
|
||
bool processed = false;
|
||
while (!processed){
|
||
processed = hfp_parse_byte(hfp_connection, byte, isHandsFree);
|
||
}
|
||
// reset parser state on end-of-line
|
||
if (hfp_parser_is_end_of_line(byte)){
|
||
hfp_connection->found_equal_sign = false;
|
||
hfp_connection->parser_item_index = 0;
|
||
hfp_connection->parser_state = HFP_PARSER_CMD_HEADER;
|
||
}
|
||
}
|
||
|
||
static void parse_sequence(hfp_connection_t * hfp_connection){
|
||
int value;
|
||
switch (hfp_connection->command){
|
||
case HFP_CMD_SET_GENERIC_STATUS_INDICATOR_STATUS:
|
||
value = btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
int i;
|
||
switch (hfp_connection->parser_item_index){
|
||
case 0:
|
||
for (i=0;i<hfp_connection->generic_status_indicators_nr;i++){
|
||
if (hfp_connection->generic_status_indicators[i].uuid == value){
|
||
hfp_connection->parser_indicator_index = i;
|
||
break;
|
||
}
|
||
}
|
||
break;
|
||
case 1:
|
||
if (hfp_connection->parser_indicator_index <0) break;
|
||
hfp_connection->generic_status_indicators[hfp_connection->parser_indicator_index].state = value;
|
||
log_info("HFP_CMD_SET_GENERIC_STATUS_INDICATOR_STATUS set indicator at index %u, to %u\n",
|
||
hfp_connection->parser_item_index, value);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
hfp_next_indicators_index(hfp_connection);
|
||
break;
|
||
|
||
case HFP_CMD_GET_SUBSCRIBER_NUMBER_INFORMATION:
|
||
switch(hfp_connection->parser_item_index){
|
||
case 0:
|
||
// <alpha>: This optional field is not supported, and shall be left blank.
|
||
break;
|
||
case 1:
|
||
// <number>: Quoted string containing the phone number in the format specified by <type>.
|
||
btstack_strcpy(hfp_connection->bnip_number, sizeof(hfp_connection->bnip_number), (char *)hfp_connection->line_buffer);
|
||
break;
|
||
case 2:
|
||
/*
|
||
<type> field specifies the format of the phone number provided, and can be one of the following values:
|
||
- values 128-143: The phone number format may be a national or international format, and may contain prefix and/or escape digits. No changes on the number presentation are required.
|
||
- values 144-159: The phone number format is an international number, including the country code prefix. If the plus sign ("+") is not included as part of the number and shall be added by the AG as needed.
|
||
- values 160-175: National number. No prefix nor escape digits included.
|
||
*/
|
||
value = btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
hfp_connection->bnip_type = value;
|
||
break;
|
||
case 3:
|
||
// <speed>: This optional field is not supported, and shall be left blank.
|
||
break;
|
||
case 4:
|
||
// <service>: Indicates which service this phone number relates to. Shall be either 4 (voice) or 5 (fax).
|
||
default:
|
||
break;
|
||
}
|
||
// index > 2 are ignored in switch above
|
||
hfp_connection->parser_item_index++;
|
||
break;
|
||
case HFP_CMD_LIST_CURRENT_CALLS:
|
||
switch(hfp_connection->parser_item_index){
|
||
case 0:
|
||
value = btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
hfp_connection->clcc_idx = value;
|
||
break;
|
||
case 1:
|
||
value = btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
hfp_connection->clcc_dir = value;
|
||
break;
|
||
case 2:
|
||
value = btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
hfp_connection->clcc_status = value;
|
||
break;
|
||
case 3:
|
||
value = btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
hfp_connection->clcc_mode = value;
|
||
break;
|
||
case 4:
|
||
value = btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
hfp_connection->clcc_mpty = value;
|
||
break;
|
||
case 5:
|
||
btstack_strcpy(hfp_connection->bnip_number, sizeof(hfp_connection->bnip_number), (char *)hfp_connection->line_buffer);
|
||
break;
|
||
case 6:
|
||
value = btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
hfp_connection->bnip_type = value;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
// index > 6 are ignored in switch above
|
||
hfp_connection->parser_item_index++;
|
||
break;
|
||
case HFP_CMD_SET_MICROPHONE_GAIN:
|
||
value = btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
hfp_connection->microphone_gain = value;
|
||
log_info("hfp parse HFP_CMD_SET_MICROPHONE_GAIN %d\n", value);
|
||
break;
|
||
case HFP_CMD_SET_SPEAKER_GAIN:
|
||
value = btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
hfp_connection->speaker_gain = value;
|
||
log_info("hfp parse HFP_CMD_SET_SPEAKER_GAIN %d\n", value);
|
||
break;
|
||
case HFP_CMD_TURN_OFF_EC_AND_NR:
|
||
value = btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
hfp_connection->ag_echo_and_noise_reduction = value;
|
||
log_info("hfp parse HFP_CMD_TURN_OFF_EC_AND_NR %d\n", value);
|
||
break;
|
||
case HFP_CMD_CHANGE_IN_BAND_RING_TONE_SETTING:
|
||
value = btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
hfp_connection->remote_supported_features = store_bit(hfp_connection->remote_supported_features, HFP_AGSF_IN_BAND_RING_TONE, value);
|
||
log_info("hfp parse HFP_CHANGE_IN_BAND_RING_TONE_SETTING %d\n", value);
|
||
break;
|
||
case HFP_CMD_HF_CONFIRMED_CODEC:
|
||
hfp_connection->codec_confirmed = btstack_atoi((char*)hfp_connection->line_buffer);
|
||
log_info("hfp parse HFP_CMD_HF_CONFIRMED_CODEC %d\n", hfp_connection->codec_confirmed);
|
||
break;
|
||
case HFP_CMD_AG_SUGGESTED_CODEC:
|
||
hfp_connection->suggested_codec = btstack_atoi((char*)hfp_connection->line_buffer);
|
||
log_info("hfp parse HFP_CMD_AG_SUGGESTED_CODEC %d\n", hfp_connection->suggested_codec);
|
||
break;
|
||
case HFP_CMD_SUPPORTED_FEATURES:
|
||
hfp_connection->remote_supported_features = btstack_atoi((char*)hfp_connection->line_buffer);
|
||
log_info("Parsed supported feature %d\n", (int) hfp_connection->remote_supported_features);
|
||
break;
|
||
case HFP_CMD_AVAILABLE_CODECS:
|
||
log_info("Parsed codec %s\n", hfp_connection->line_buffer);
|
||
hfp_connection->remote_codecs[hfp_connection->parser_item_index] = (uint8_t)btstack_atoi((char*)hfp_connection->line_buffer);
|
||
hfp_next_codec_index(hfp_connection);
|
||
hfp_connection->remote_codecs_nr = hfp_connection->parser_item_index;
|
||
break;
|
||
case HFP_CMD_RETRIEVE_AG_INDICATORS:
|
||
btstack_strcpy((char *)hfp_connection->ag_indicators[hfp_connection->parser_item_index].name, HFP_MAX_INDICATOR_DESC_SIZE, (char *)hfp_connection->line_buffer);
|
||
hfp_connection->ag_indicators[hfp_connection->parser_item_index].index = hfp_connection->parser_item_index+1;
|
||
log_info("Indicator %d: %s (", hfp_connection->ag_indicators_nr+1, hfp_connection->line_buffer);
|
||
break;
|
||
case HFP_CMD_RETRIEVE_AG_INDICATORS_STATUS:
|
||
log_info("Parsed Indicator %d with status: %s\n", hfp_connection->parser_item_index+1, hfp_connection->line_buffer);
|
||
hfp_connection->ag_indicators[hfp_connection->parser_item_index].status = btstack_atoi((char *) hfp_connection->line_buffer);
|
||
hfp_next_indicators_index(hfp_connection);
|
||
break;
|
||
case HFP_CMD_ENABLE_INDICATOR_STATUS_UPDATE:
|
||
hfp_next_indicators_index(hfp_connection);
|
||
if (hfp_connection->parser_item_index != 4) break;
|
||
log_info("Parsed Enable indicators: %s\n", hfp_connection->line_buffer);
|
||
value = btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
hfp_connection->enable_status_update_for_ag_indicators = (uint8_t) value;
|
||
break;
|
||
case HFP_CMD_SUPPORT_CALL_HOLD_AND_MULTIPARTY_SERVICES:
|
||
log_info("Parsed Support call hold: %s\n", hfp_connection->line_buffer);
|
||
if (hfp_connection->line_size > 2 ) break;
|
||
memcpy((char *)hfp_connection->remote_call_services[hfp_connection->remote_call_services_index].name, (char *)hfp_connection->line_buffer, HFP_CALL_SERVICE_SIZE-1);
|
||
hfp_connection->remote_call_services[hfp_connection->remote_call_services_index].name[HFP_CALL_SERVICE_SIZE - 1] = 0;
|
||
hfp_next_remote_call_services_index(hfp_connection);
|
||
break;
|
||
case HFP_CMD_LIST_GENERIC_STATUS_INDICATORS:
|
||
case HFP_CMD_RETRIEVE_GENERIC_STATUS_INDICATORS:
|
||
log_info("Parsed Generic status indicator: %s\n", hfp_connection->line_buffer);
|
||
hfp_connection->generic_status_indicators[hfp_connection->parser_item_index].uuid = (uint16_t)btstack_atoi((char*)hfp_connection->line_buffer);
|
||
hfp_next_indicators_index(hfp_connection);
|
||
hfp_connection->generic_status_indicators_nr = hfp_connection->parser_item_index;
|
||
break;
|
||
case HFP_CMD_RETRIEVE_GENERIC_STATUS_INDICATORS_STATE:
|
||
// HF parses inital AG gen. ind. state
|
||
log_info("Parsed List generic status indicator %s state: ", hfp_connection->line_buffer);
|
||
hfp_connection->parser_item_index = hfp_parse_indicator_index(hfp_connection);
|
||
break;
|
||
case HFP_CMD_HF_INDICATOR_STATUS:
|
||
hfp_connection->parser_indicator_index = hfp_parse_indicator_index(hfp_connection);
|
||
log_info("Parsed HF indicator index %u", hfp_connection->parser_indicator_index);
|
||
break;
|
||
case HFP_CMD_ENABLE_INDIVIDUAL_AG_INDICATOR_STATUS_UPDATE:
|
||
// AG parses new gen. ind. state
|
||
if (hfp_connection->ignore_value){
|
||
hfp_connection->ignore_value = 0;
|
||
log_info("Parsed Enable AG indicator pos %u('%s') - unchanged (stays %u)\n", hfp_connection->parser_item_index,
|
||
hfp_connection->ag_indicators[hfp_connection->parser_item_index].name, hfp_connection->ag_indicators[hfp_connection->parser_item_index].enabled);
|
||
}
|
||
else if (hfp_connection->ag_indicators[hfp_connection->parser_item_index].mandatory){
|
||
log_info("Parsed Enable AG indicator pos %u('%s') - ignore (mandatory)\n",
|
||
hfp_connection->parser_item_index, hfp_connection->ag_indicators[hfp_connection->parser_item_index].name);
|
||
} else {
|
||
value = btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
hfp_connection->ag_indicators[hfp_connection->parser_item_index].enabled = value;
|
||
log_info("Parsed Enable AG indicator pos %u('%s'): %u\n", hfp_connection->parser_item_index,
|
||
hfp_connection->ag_indicators[hfp_connection->parser_item_index].name, value);
|
||
}
|
||
hfp_next_indicators_index(hfp_connection);
|
||
break;
|
||
case HFP_CMD_TRANSFER_AG_INDICATOR_STATUS:
|
||
// indicators are indexed starting with 1
|
||
hfp_connection->parser_item_index = hfp_parse_indicator_index(hfp_connection);
|
||
log_info("Parsed status of the AG indicator %d, status ", hfp_connection->parser_item_index);
|
||
break;
|
||
case HFP_CMD_QUERY_OPERATOR_SELECTION_NAME:
|
||
hfp_connection->network_operator.mode = btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
log_info("Parsed network operator mode: %d, ", hfp_connection->network_operator.mode);
|
||
break;
|
||
case HFP_CMD_QUERY_OPERATOR_SELECTION_NAME_FORMAT:
|
||
if (hfp_connection->line_buffer[0] == '3'){
|
||
log_info("Parsed Set network operator format : %s, ", hfp_connection->line_buffer);
|
||
break;
|
||
}
|
||
// TODO emit ERROR, wrong format
|
||
log_info("ERROR Set network operator format: index %s not supported\n", hfp_connection->line_buffer);
|
||
break;
|
||
case HFP_CMD_ERROR:
|
||
break;
|
||
case HFP_CMD_EXTENDED_AUDIO_GATEWAY_ERROR:
|
||
hfp_connection->extended_audio_gateway_error = 1;
|
||
hfp_connection->extended_audio_gateway_error_value = (uint8_t)btstack_atoi((char*)hfp_connection->line_buffer);
|
||
break;
|
||
case HFP_CMD_ENABLE_EXTENDED_AUDIO_GATEWAY_ERROR:
|
||
hfp_connection->enable_extended_audio_gateway_error_report = (uint8_t)btstack_atoi((char*)hfp_connection->line_buffer);
|
||
hfp_connection->extended_audio_gateway_error = 0;
|
||
break;
|
||
case HFP_CMD_AG_SENT_PHONE_NUMBER:
|
||
case HFP_CMD_AG_SENT_CALL_WAITING_NOTIFICATION_UPDATE:
|
||
case HFP_CMD_AG_SENT_CLIP_INFORMATION:
|
||
btstack_strcpy((char *)hfp_connection->bnip_number, sizeof(hfp_connection->bnip_number), (char *)hfp_connection->line_buffer);
|
||
break;
|
||
case HFP_CMD_CALL_HOLD:
|
||
hfp_connection->ag_call_hold_action = hfp_connection->line_buffer[0] - '0';
|
||
if (hfp_connection->line_buffer[1] != '\0'){
|
||
hfp_connection->call_index = btstack_atoi((char *)&hfp_connection->line_buffer[1]);
|
||
}
|
||
break;
|
||
case HFP_CMD_RESPONSE_AND_HOLD_COMMAND:
|
||
hfp_connection->ag_response_and_hold_action = btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
break;
|
||
case HFP_CMD_TRANSMIT_DTMF_CODES:
|
||
hfp_connection->ag_dtmf_code = hfp_connection->line_buffer[0];
|
||
break;
|
||
case HFP_CMD_ENABLE_CLIP:
|
||
hfp_connection->clip_enabled = hfp_connection->line_buffer[0] != '0';
|
||
break;
|
||
case HFP_CMD_ENABLE_CALL_WAITING_NOTIFICATION:
|
||
hfp_connection->call_waiting_notification_enabled = hfp_connection->line_buffer[0] != '0';
|
||
break;
|
||
case HFP_CMD_HF_ACTIVATE_VOICE_RECOGNITION:
|
||
value = btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
hfp_connection->ag_activate_voice_recognition_value = value;
|
||
break;
|
||
case HFP_CMD_AG_ACTIVATE_VOICE_RECOGNITION:
|
||
switch(hfp_connection->parser_item_index){
|
||
case 0:
|
||
hfp_connection->ag_vra_status = btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
break;
|
||
case 1:
|
||
hfp_connection->ag_vra_state = (hfp_voice_recognition_state_t) btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
break;
|
||
case 2:
|
||
hfp_connection->ag_msg.text_id = 0;
|
||
for (i = 0 ; i < 4; i++){
|
||
hfp_connection->ag_msg.text_id = (hfp_connection->ag_msg.text_id << 4) | nibble_for_char(hfp_connection->line_buffer[i]);
|
||
}
|
||
break;
|
||
case 3:
|
||
hfp_connection->ag_msg.text_type = (hfp_text_type_t) btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
break;
|
||
case 4:
|
||
hfp_connection->ag_msg.text_operation = (hfp_text_operation_t) btstack_atoi((char *)&hfp_connection->line_buffer[0]);
|
||
break;
|
||
case 5:
|
||
hfp_connection->line_buffer[hfp_connection->line_size] = 0;
|
||
hfp_connection->ag_vra_msg_length = hfp_connection->line_size + 1;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
hfp_connection->parser_item_index++;
|
||
break;
|
||
case HFP_CMD_APPLE_ACCESSORY_INFORMATION:
|
||
switch (hfp_connection->parser_item_index){
|
||
case 0:
|
||
hfp_connection->apple_accessory_vendor_id = 0;
|
||
for (i = 0 ; i < 4; i++){
|
||
hfp_connection->apple_accessory_vendor_id = (hfp_connection->apple_accessory_vendor_id << 4) | nibble_for_char(hfp_connection->line_buffer[i]);
|
||
}
|
||
break;
|
||
case 1:
|
||
hfp_connection->apple_accessory_product_id = 0;
|
||
for (i = 0 ; i < 4; i++){
|
||
hfp_connection->apple_accessory_product_id = (hfp_connection->apple_accessory_product_id << 4) | nibble_for_char(hfp_connection->line_buffer[i]);
|
||
}
|
||
break;
|
||
case 2:
|
||
btstack_strcpy(hfp_connection->apple_accessory_version, sizeof(hfp_connection->apple_accessory_version), (const char *) hfp_connection->line_buffer);
|
||
break;
|
||
case 3:
|
||
hfp_connection->apple_accessory_features = btstack_atoi((char *)hfp_connection->line_buffer);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
hfp_connection->parser_item_index++;
|
||
break;
|
||
case HFP_CMD_APPLE_ACCESSORY_STATE:
|
||
// ignore K/V count
|
||
if (hfp_connection->parser_item_index > 0){
|
||
if ((hfp_connection->parser_item_index & 1) == 1){
|
||
hfp_connection->apple_accessory_key = btstack_atoi((char *)hfp_connection->line_buffer);
|
||
} else {
|
||
switch (hfp_connection->apple_accessory_key){
|
||
case 1:
|
||
hfp_connection->apple_accessory_battery_level = btstack_atoi((char *)hfp_connection->line_buffer);
|
||
break;
|
||
case 2:
|
||
hfp_connection->apple_accessory_docked = btstack_atoi((char *)hfp_connection->line_buffer);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
hfp_connection->parser_item_index++;
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
static void hfp_handle_start_sdp_client_query(void * context){
|
||
UNUSED(context);
|
||
|
||
btstack_linked_list_iterator_t it;
|
||
btstack_linked_list_iterator_init(&it, &hfp_connections);
|
||
while (btstack_linked_list_iterator_has_next(&it)){
|
||
hfp_connection_t * connection = (hfp_connection_t *)btstack_linked_list_iterator_next(&it);
|
||
|
||
if (connection->state != HFP_W2_SEND_SDP_QUERY) continue;
|
||
|
||
connection->state = HFP_W4_SDP_QUERY_COMPLETE;
|
||
hfp_sdp_query_context.local_role = connection->local_role;
|
||
(void)memcpy(hfp_sdp_query_context.remote_address, connection->remote_addr, 6);
|
||
sdp_client_query_rfcomm_channel_and_name_for_service_class_uuid(&handle_query_rfcomm_event, connection->remote_addr, connection->service_uuid);
|
||
return;
|
||
}
|
||
}
|
||
|
||
uint8_t hfp_establish_service_level_connection(bd_addr_t bd_addr, uint16_t service_uuid, hfp_role_t local_role){
|
||
hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(bd_addr, local_role);
|
||
if (connection != NULL){
|
||
// allow to call hfp_establish_service_level_connection after SLC was triggered remotely
|
||
// @note this also allows to call hfp_establish_service_level_connection again before SLC is complete
|
||
if (connection->state < HFP_SERVICE_LEVEL_CONNECTION_ESTABLISHED){
|
||
return ERROR_CODE_SUCCESS;
|
||
} else {
|
||
return ERROR_CODE_COMMAND_DISALLOWED;
|
||
}
|
||
}
|
||
|
||
connection = hfp_create_connection(bd_addr, local_role);
|
||
if (!connection){
|
||
return BTSTACK_MEMORY_ALLOC_FAILED;
|
||
}
|
||
|
||
connection->state = HFP_W2_SEND_SDP_QUERY;
|
||
|
||
bd_addr_copy(connection->remote_addr, bd_addr);
|
||
connection->service_uuid = service_uuid;
|
||
|
||
hfp_sdp_query_request.callback = &hfp_handle_start_sdp_client_query;
|
||
// ignore ERROR_CODE_COMMAND_DISALLOWED because in that case, we already have requested an SDP callback
|
||
(void) sdp_client_register_query_callback(&hfp_sdp_query_request);
|
||
return ERROR_CODE_SUCCESS;
|
||
}
|
||
|
||
void hfp_trigger_release_service_level_connection(hfp_connection_t * hfp_connection){
|
||
// called internally, NULL check already performed
|
||
btstack_assert(hfp_connection != NULL);
|
||
|
||
hfp_trigger_release_audio_connection(hfp_connection);
|
||
if (hfp_connection->state < HFP_W4_RFCOMM_CONNECTED){
|
||
hfp_connection->state = HFP_IDLE;
|
||
return;
|
||
}
|
||
|
||
if (hfp_connection->state == HFP_W4_RFCOMM_CONNECTED){
|
||
hfp_connection->state = HFP_W4_CONNECTION_ESTABLISHED_TO_SHUTDOWN;
|
||
return;
|
||
}
|
||
hfp_connection->release_slc_connection = 1;
|
||
if (hfp_connection->state < HFP_W4_SCO_CONNECTED){
|
||
hfp_connection->state = HFP_W2_DISCONNECT_RFCOMM;
|
||
return;
|
||
}
|
||
|
||
if (hfp_connection->state < HFP_W4_SCO_DISCONNECTED){
|
||
hfp_connection->state = HFP_W2_DISCONNECT_SCO;
|
||
hfp_connection->release_audio_connection = 1;
|
||
return;
|
||
}
|
||
}
|
||
|
||
uint8_t hfp_trigger_release_audio_connection(hfp_connection_t * hfp_connection){
|
||
// called internally, NULL check already performed
|
||
btstack_assert(hfp_connection != NULL);
|
||
if (hfp_connection->state <= HFP_SERVICE_LEVEL_CONNECTION_ESTABLISHED){
|
||
return ERROR_CODE_COMMAND_DISALLOWED;
|
||
}
|
||
switch (hfp_connection->state) {
|
||
case HFP_W2_CONNECT_SCO:
|
||
hfp_connection->state = HFP_SERVICE_LEVEL_CONNECTION_ESTABLISHED;
|
||
break;
|
||
case HFP_W4_SCO_CONNECTED:
|
||
case HFP_AUDIO_CONNECTION_ESTABLISHED:
|
||
hfp_connection->release_audio_connection = 1;
|
||
break;
|
||
default:
|
||
return ERROR_CODE_COMMAND_DISALLOWED;
|
||
}
|
||
return ERROR_CODE_SUCCESS;
|
||
}
|
||
|
||
bool hfp_sco_setup_active(void){
|
||
return hfp_sco_establishment_active != NULL;
|
||
}
|
||
|
||
void hfp_setup_synchronous_connection(hfp_connection_t * hfp_connection){
|
||
|
||
hfp_sco_establishment_active = hfp_connection;
|
||
|
||
// all packet types, fixed bandwidth
|
||
int setting = hfp_connection->link_setting;
|
||
log_info("hfp_setup_synchronous_connection using setting nr %u", setting);
|
||
uint16_t sco_voice_setting = hci_get_sco_voice_setting();
|
||
if (hfp_connection->negotiated_codec != HFP_CODEC_CVSD){
|
||
#ifdef ENABLE_BCM_PCM_WBS
|
||
sco_voice_setting = 0x0063; // Transparent data, 16-bit for BCM controllers
|
||
#else
|
||
sco_voice_setting = 0x0043; // Transparent data, 8-bit otherwise
|
||
#endif
|
||
}
|
||
uint16_t packet_types = hfp_link_settings[setting].packet_types;
|
||
hfp_connection->packet_types = packet_types;
|
||
|
||
// get packet types - bits 6-9 are 'don't allow'
|
||
uint16_t packet_types_flipped = packet_types ^ 0x03c0;
|
||
#if defined(ENABLE_SCO_OVER_PCM) && defined(ENABLE_NXP_PCM_WBS)
|
||
uint8_t radio_coding_format = 3;
|
||
uint32_t host_bandwidth = 0;
|
||
uint8_t coded_data_size = 0;
|
||
switch (hfp_connection->negotiated_codec){
|
||
case HFP_CODEC_CVSD:
|
||
radio_coding_format = 0x02;
|
||
host_bandwidth = 16000;
|
||
coded_data_size = 0x10;
|
||
break;
|
||
case HFP_CODEC_MSBC:
|
||
radio_coding_format = 0x05;
|
||
host_bandwidth = 32000;
|
||
coded_data_size = 0x08;
|
||
break;
|
||
default:
|
||
log_error("Coding format %u not supported by Controller", hfp_connection->negotiated_codec);
|
||
btstack_assert(false);
|
||
break;
|
||
}
|
||
hci_send_cmd(&hci_enhanced_setup_synchronous_connection,
|
||
// ACL Handle
|
||
hfp_connection->acl_handle,
|
||
// Transmit_Bandwidth
|
||
8000,
|
||
// Receive_Bandwidth
|
||
8000,
|
||
// Transmit_Coding_Format: radio_config_format, company, codec
|
||
radio_coding_format, 0x00, 0x00,
|
||
// Receive_Coding_Format: radio_config_format, company, codec
|
||
radio_coding_format, 0x00, 0x00,
|
||
// Transmit_Codec_Frame_Size
|
||
0x3c,
|
||
// Receive_Codec_Frame_Size
|
||
0x3c,
|
||
// Input_Bandwidth
|
||
host_bandwidth,
|
||
// Output_Bandwidth
|
||
host_bandwidth,
|
||
// Input_Coding_Format, 0x04 = Linear PCM, company, codec
|
||
0x04, 0x00, 0x00,
|
||
// Output_Coding_Format, 0x04 = Linear PCM, company, codec
|
||
0x04, 0x00, 0x00,
|
||
// Input_Coded_Data_Size
|
||
coded_data_size,
|
||
// Output_Coded_Data_Size
|
||
coded_data_size,
|
||
// Input_PCM_Data_Format, 0x02 = 2’s complement
|
||
0x02,
|
||
// Output_PCM_Data_Format, 0x02 = 2’s complement
|
||
0x02,
|
||
// Input_PCM_Sample_Payload_MSB_Position
|
||
0x00,
|
||
// Output_PCM_Sample_Payload_MSB_Position
|
||
0x00,
|
||
// Input_Data_Path - vendor specific: NXP - I2S/PCM
|
||
0x01,
|
||
// Output_Data_Path - vendor specific: NXP - I2S/PCM
|
||
0x01,
|
||
// Input_Transport_Unit_Size
|
||
0x10,
|
||
// Output_Transport_Unit_Size
|
||
0x10,
|
||
//
|
||
hfp_link_settings[setting].max_latency,
|
||
packet_types_flipped,
|
||
hfp_link_settings[setting].retransmission_effort);
|
||
#else
|
||
hci_send_cmd(&hci_setup_synchronous_connection, hfp_connection->acl_handle, 8000, 8000, hfp_link_settings[setting].max_latency,
|
||
sco_voice_setting, hfp_link_settings[setting].retransmission_effort, packet_types_flipped);
|
||
#endif
|
||
}
|
||
|
||
#ifdef ENABLE_HFP_HF_SAFE_SETTINGS
|
||
hfp_link_settings_t hfp_safe_settings_for_context(bool use_eSCO, uint8_t negotiated_codec, bool secure_connection_in_use){
|
||
uint8_t i;
|
||
hfp_link_settings_t link_setting = HFP_LINK_SETTINGS_NONE;
|
||
for (i=0 ; i < (sizeof(hfp_mandatory_safe_settings) / sizeof(struct hfp_mandatory_safe_setting)) ; i++){
|
||
uint16_t packet_types = hfp_link_settings[(uint8_t)(hfp_mandatory_safe_settings[i].link_setting)].packet_types;
|
||
bool is_eSCO_setting = (packet_types & SCO_PACKET_TYPES_ESCO) != 0;
|
||
if (is_eSCO_setting != use_eSCO) continue;
|
||
if ((hfp_mandatory_safe_settings[i].codec_mask & (1 << negotiated_codec)) == 0) continue;
|
||
if (hfp_mandatory_safe_settings[i].secure_connection_in_use != secure_connection_in_use) continue;
|
||
link_setting = hfp_mandatory_safe_settings[i].link_setting;
|
||
break;
|
||
}
|
||
return link_setting;
|
||
}
|
||
#endif
|
||
|
||
void hfp_accept_synchronous_connection(hfp_connection_t * hfp_connection, bool use_eSCO){
|
||
|
||
hfp_sco_establishment_active = hfp_connection;
|
||
|
||
// lookup safe settings based on SCO type, SC use and Codec type
|
||
uint16_t max_latency;
|
||
uint16_t packet_types;
|
||
uint16_t retransmission_effort;
|
||
|
||
#ifdef ENABLE_HFP_HF_SAFE_SETTINGS
|
||
hfp_link_settings_t link_setting = HFP_LINK_SETTINGS_NONE;
|
||
// fallback for non-CVSD codec and SCO connection
|
||
if ((hfp_connection->negotiated_codec != HFP_CODEC_CVSD) && (use_eSCO == false)){
|
||
max_latency = 0xffff;
|
||
retransmission_effort = 0xff;
|
||
packet_types = SCO_PACKET_TYPES_HV3 | SCO_PACKET_TYPES_HV1;
|
||
} else {
|
||
// use safe settings from HFP v1.9, table 6.10
|
||
bool secure_connection_in_use = gap_secure_connection(hfp_connection->acl_handle);
|
||
link_setting = hfp_safe_settings_for_context(use_eSCO, hfp_connection->negotiated_codec, secure_connection_in_use);
|
||
max_latency = hfp_link_settings[(uint8_t) link_setting].max_latency;
|
||
retransmission_effort = hfp_link_settings[(uint8_t) link_setting].retransmission_effort;
|
||
packet_types = hfp_link_settings[(uint8_t) link_setting].packet_types;
|
||
}
|
||
#else
|
||
max_latency = 0xffff;
|
||
retransmission_effort = 0xff;
|
||
if (use_eSCO) {
|
||
packet_types = SCO_PACKET_TYPES_EV3 | SCO_PACKET_TYPES_2EV3;
|
||
} else {
|
||
packet_types = SCO_PACKET_TYPES_HV3 | SCO_PACKET_TYPES_HV1;
|
||
}
|
||
#endif
|
||
|
||
// transparent data for non-CVSD connections or if codec provided by Controller
|
||
uint16_t sco_voice_setting = hci_get_sco_voice_setting();
|
||
if (hfp_connection->negotiated_codec != HFP_CODEC_CVSD){
|
||
#ifdef ENABLE_BCM_PCM_WBS
|
||
sco_voice_setting = 0x0063; // Transparent data, 16-bit for BCM controllers
|
||
#else
|
||
sco_voice_setting = 0x0043; // Transparent data, 8-bit otherwise
|
||
#endif
|
||
}
|
||
|
||
// filter packet types
|
||
packet_types &= hfp_get_sco_packet_types();
|
||
|
||
hfp_connection->packet_types = packet_types;
|
||
|
||
// bits 6-9 are 'don't allow'
|
||
uint16_t packet_types_flipped = packet_types ^ 0x3c0;
|
||
|
||
log_info("Sending hci_accept_connection_request: packet types 0x%04x, sco_voice_setting 0x%02x",
|
||
packet_types, sco_voice_setting);
|
||
|
||
#if defined(ENABLE_SCO_OVER_PCM) && defined(ENABLE_NXP_PCM_WBS)
|
||
uint8_t radio_coding_format = 3;
|
||
uint32_t host_bandwidth = 0;
|
||
uint8_t coded_data_size = 0;
|
||
switch (hfp_connection->negotiated_codec){
|
||
case HFP_CODEC_CVSD:
|
||
radio_coding_format = 0x02;
|
||
host_bandwidth = 16000;
|
||
coded_data_size = 0x10;
|
||
break;
|
||
case HFP_CODEC_MSBC:
|
||
radio_coding_format = 0x05;
|
||
host_bandwidth = 32000;
|
||
coded_data_size = 0x08;
|
||
break;
|
||
default:
|
||
log_error("Coding format %u not supported by Controller", hfp_connection->negotiated_codec);
|
||
btstack_assert(false);
|
||
break;
|
||
}
|
||
hci_send_cmd(&hci_enhanced_accept_synchronous_connection,
|
||
// BD_ADDR
|
||
hfp_connection->remote_addr,
|
||
// Transmit_Bandwidth
|
||
8000,
|
||
// Receive_Bandwidth
|
||
8000,
|
||
// Transmit_Coding_Format: radio_config_format, company, codec
|
||
radio_coding_format, 0x00, 0x00,
|
||
// Receive_Coding_Format: radio_config_format, company, codec
|
||
radio_coding_format, 0x00, 0x00,
|
||
// Transmit_Codec_Frame_Size
|
||
0x3c,
|
||
// Receive_Codec_Frame_Size
|
||
0x3c,
|
||
// Input_Bandwidth
|
||
host_bandwidth,
|
||
// Output_Bandwidth
|
||
host_bandwidth,
|
||
// Input_Coding_Format, 0x04 = Linear PCM, company, codec
|
||
0x04, 0x00, 0x00,
|
||
// Output_Coding_Format, 0x04 = Linear PCM, company, codec
|
||
0x04, 0x00, 0x00,
|
||
// Input_Coded_Data_Size
|
||
coded_data_size,
|
||
// Output_Coded_Data_Size
|
||
coded_data_size,
|
||
// Input_PCM_Data_Format, 0x02 = 2’s complement
|
||
0x02,
|
||
// Output_PCM_Data_Format, 0x02 = 2’s complement
|
||
0x02,
|
||
// Input_PCM_Sample_Payload_MSB_Position
|
||
0x00,
|
||
// Output_PCM_Sample_Payload_MSB_Position
|
||
0x00,
|
||
// Input_Data_Path - vendor specific: NXP - I2S/PCM
|
||
0x01,
|
||
// Output_Data_Path - vendor specific: NXP - I2S/PCM
|
||
0x01,
|
||
// Input_Transport_Unit_Size
|
||
0x10,
|
||
// Output_Transport_Unit_Size
|
||
0x10,
|
||
//
|
||
max_latency,
|
||
packet_types_flipped,
|
||
retransmission_effort);
|
||
#else
|
||
hci_send_cmd(&hci_accept_synchronous_connection, hfp_connection->remote_addr, 8000, 8000, max_latency,
|
||
sco_voice_setting, retransmission_effort, packet_types_flipped);
|
||
#endif
|
||
}
|
||
|
||
#ifdef ENABLE_CC256X_ASSISTED_HFP
|
||
void hfp_cc256x_write_codec_config(hfp_connection_t * hfp_connection){
|
||
uint32_t sample_rate_hz;
|
||
uint16_t clock_rate_khz;
|
||
if (hfp_connection->negotiated_codec == HFP_CODEC_MSBC){
|
||
clock_rate_khz = 512;
|
||
sample_rate_hz = 16000;
|
||
} else {
|
||
clock_rate_khz = 256;
|
||
sample_rate_hz = 8000;
|
||
}
|
||
uint8_t clock_direction = 0; // master
|
||
uint16_t frame_sync_duty_cycle = 0; // i2s with 50%
|
||
uint8_t frame_sync_edge = 1; // rising edge
|
||
uint8_t frame_sync_polarity = 0; // active high
|
||
uint8_t reserved = 0;
|
||
uint16_t size = 16;
|
||
uint16_t chan_1_offset = 1;
|
||
uint16_t chan_2_offset = chan_1_offset + size;
|
||
uint8_t out_edge = 1; // rising
|
||
uint8_t in_edge = 0; // falling
|
||
hci_send_cmd(&hci_ti_write_codec_config, clock_rate_khz, clock_direction, sample_rate_hz, frame_sync_duty_cycle,
|
||
frame_sync_edge, frame_sync_polarity, reserved,
|
||
size, chan_1_offset, out_edge, size, chan_1_offset, in_edge, reserved,
|
||
size, chan_2_offset, out_edge, size, chan_2_offset, in_edge, reserved);
|
||
}
|
||
#endif
|
||
|
||
#ifdef ENABLE_BCM_PCM_WBS
|
||
void hfp_bcm_write_i2spcm_interface_param(hfp_connection_t * hfp_connection){
|
||
uint8_t sample_rate = (hfp_connection->negotiated_codec == HFP_CODEC_MSBC) ? 1 : 0;
|
||
// i2s enable, master, 8/16 kHz, 512 kHz
|
||
hci_send_cmd(&hci_bcm_write_i2spcm_interface_param, 1, 1, sample_rate, 2);
|
||
}
|
||
#endif
|
||
|
||
void hfp_prepare_for_sco(hfp_connection_t * hfp_connection){
|
||
UNUSED(hfp_connection);
|
||
#ifdef ENABLE_CC256X_ASSISTED_HFP
|
||
hfp_connection->cc256x_send_write_codec_config = true;
|
||
if (hfp_connection->negotiated_codec == HFP_CODEC_MSBC){
|
||
hfp_connection->cc256x_send_wbs_associate = true;
|
||
}
|
||
#endif
|
||
|
||
#ifdef ENABLE_BCM_PCM_WBS
|
||
#ifndef HAVE_BCM_PCM_NBS_16KHZ
|
||
hfp_connection->bcm_send_write_i2spcm_interface_param = true;
|
||
#endif
|
||
if (hfp_connection->negotiated_codec == HFP_CODEC_MSBC){
|
||
hfp_connection->bcm_send_enable_wbs = true;
|
||
}
|
||
#endif
|
||
|
||
#ifdef ENABLE_RTK_PCM_WBS
|
||
hfp_connection->rtk_send_sco_config = true;
|
||
#endif
|
||
}
|
||
|
||
void hfp_set_hf_callback(btstack_packet_handler_t callback){
|
||
hfp_hf_callback = callback;
|
||
}
|
||
|
||
void hfp_set_ag_callback(btstack_packet_handler_t callback){
|
||
hfp_ag_callback = callback;
|
||
}
|
||
|
||
void hfp_set_ag_rfcomm_packet_handler(btstack_packet_handler_t handler){
|
||
hfp_ag_rfcomm_packet_handler = handler;
|
||
}
|
||
|
||
void hfp_set_hf_rfcomm_packet_handler(btstack_packet_handler_t handler){
|
||
hfp_hf_rfcomm_packet_handler = handler;
|
||
}
|
||
|
||
void hfp_set_hf_indicators(uint8_t indicators_nr, const uint8_t * indicators) {
|
||
hfp_hf_indicators_nr = indicators_nr;
|
||
hfp_hf_indicators = indicators;
|
||
}
|
||
|
||
void hfp_init(void){
|
||
hfp_allowed_sco_packet_types = SCO_PACKET_TYPES_ALL;
|
||
}
|
||
|
||
void hfp_deinit(void){
|
||
hfp_allowed_sco_packet_types = 0;
|
||
hfp_connections = NULL;
|
||
hfp_hf_callback = NULL;
|
||
hfp_ag_callback = NULL;
|
||
hfp_hf_rfcomm_packet_handler = NULL;
|
||
hfp_ag_rfcomm_packet_handler = NULL;
|
||
hfp_sco_establishment_active = NULL;
|
||
hfp_custom_commands_ag = NULL;
|
||
hfp_custom_commands_hf = NULL;
|
||
(void) memset(&hfp_sdp_query_context, 0, sizeof(hfp_sdp_query_context_t));
|
||
(void) memset(&hfp_sdp_query_request, 0, sizeof(btstack_context_callback_registration_t));
|
||
}
|
||
|
||
void hfp_set_sco_packet_types(uint16_t packet_types){
|
||
hfp_allowed_sco_packet_types = packet_types;
|
||
}
|
||
|
||
uint16_t hfp_get_sco_packet_types(void){
|
||
return hfp_allowed_sco_packet_types;
|
||
}
|
||
|
||
hfp_link_settings_t hfp_next_link_setting(hfp_link_settings_t current_setting, uint16_t local_sco_packet_types,
|
||
uint16_t remote_sco_packet_types, bool eSCO_S4_supported,
|
||
uint8_t negotiated_codec) {
|
||
int8_t setting = (int8_t) current_setting;
|
||
while (setting > 0){
|
||
setting--;
|
||
// skip S4 if not supported
|
||
if ((setting == (int8_t) HFP_LINK_SETTINGS_S4) && !eSCO_S4_supported) continue;
|
||
// skip wrong codec
|
||
if ((hfp_link_settings[setting].codec_mask & (1 << negotiated_codec)) == 0) continue;
|
||
// skip disabled or not supported packet types
|
||
uint16_t required_packet_types = hfp_link_settings[setting].packet_types;
|
||
uint16_t allowed_packet_types = hfp_allowed_sco_packet_types & local_sco_packet_types & remote_sco_packet_types;
|
||
if ((required_packet_types & allowed_packet_types) == 0) continue;
|
||
|
||
// found matching setting
|
||
return (hfp_link_settings_t) setting;
|
||
}
|
||
return HFP_LINK_SETTINGS_NONE;
|
||
}
|
||
|
||
static hfp_link_settings_t hfp_next_link_setting_for_connection(hfp_link_settings_t current_setting, hfp_connection_t * hfp_connection, uint8_t eSCO_S4_supported){
|
||
uint8_t negotiated_codec = hfp_connection->negotiated_codec;
|
||
uint16_t local_sco_packet_types = hci_usable_sco_packet_types();
|
||
uint16_t remote_sco_packet_types = hci_remote_sco_packet_types(hfp_connection->acl_handle);
|
||
return hfp_next_link_setting(current_setting, local_sco_packet_types, remote_sco_packet_types, eSCO_S4_supported,
|
||
negotiated_codec);
|
||
}
|
||
|
||
void hfp_init_link_settings(hfp_connection_t * hfp_connection, uint8_t eSCO_S4_supported){
|
||
// get highest possible link setting
|
||
hfp_connection->link_setting = hfp_next_link_setting_for_connection(HFP_LINK_SETTINGS_NONE, hfp_connection, eSCO_S4_supported);
|
||
log_info("hfp_init_link_settings: %u", hfp_connection->link_setting);
|
||
}
|
||
|
||
#define HFP_HF_RX_DEBUG_PRINT_LINE 80
|
||
|
||
void hfp_log_rfcomm_message(const char * tag, uint8_t * packet, uint16_t size){
|
||
#ifdef ENABLE_LOG_INFO
|
||
// encode \n\r
|
||
char printable[HFP_HF_RX_DEBUG_PRINT_LINE+2];
|
||
int i = 0;
|
||
int pos;
|
||
for (pos=0 ; (pos < size) && (i < (HFP_HF_RX_DEBUG_PRINT_LINE - 3)) ; pos++){
|
||
switch (packet[pos]){
|
||
case '\n':
|
||
printable[i++] = '\\';
|
||
printable[i++] = 'n';
|
||
break;
|
||
case '\r':
|
||
printable[i++] = '\\';
|
||
printable[i++] = 'r';
|
||
break;
|
||
default:
|
||
printable[i++] = packet[pos];
|
||
break;
|
||
}
|
||
}
|
||
printable[i] = 0;
|
||
log_info("%s: '%s'", tag, printable);
|
||
#else
|
||
UNUSED(tag);
|
||
UNUSED(packet);
|
||
UNUSED(size);
|
||
#endif
|
||
}
|
||
|
||
void hfp_register_custom_ag_command(hfp_custom_at_command_t * custom_at_command){
|
||
btstack_linked_list_add(&hfp_custom_commands_ag, (btstack_linked_item_t *) custom_at_command);
|
||
}
|
||
|
||
void hfp_register_custom_hf_command(hfp_custom_at_command_t * custom_at_command){
|
||
btstack_linked_list_add(&hfp_custom_commands_hf, (btstack_linked_item_t *) custom_at_command);
|
||
}
|
||
|
||
// HFP H2 Synchronization - might get moved into a hfp_h2.c
|
||
|
||
// find position of h2 sync header, returns -1 if not found, or h2 sync position
|
||
static int16_t hfp_h2_sync_find(const uint8_t * frame_data, uint16_t frame_len){
|
||
uint16_t i;
|
||
for (i=0;i<(frame_len - 1);i++){
|
||
// check: first byte == 1
|
||
uint8_t h2_first_byte = frame_data[i];
|
||
if (h2_first_byte == 0x01) {
|
||
uint8_t h2_second_byte = frame_data[i + 1];
|
||
// check lower nibble of second byte == 0x08
|
||
if ((h2_second_byte & 0x0F) == 8) {
|
||
// check if bits 0+2 == bits 1+3
|
||
uint8_t hn = h2_second_byte >> 4;
|
||
if (((hn >> 1) & 0x05) == (hn & 0x05)) {
|
||
return (int16_t) i;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
static void hfp_h2_sync_reset(hfp_h2_sync_t * hfp_h2_sync){
|
||
hfp_h2_sync->frame_len = 0;
|
||
}
|
||
|
||
void hfp_h2_sync_init(hfp_h2_sync_t * hfp_h2_sync,
|
||
bool (*callback)(bool bad_frame, const uint8_t * frame_data, uint16_t frame_len)){
|
||
hfp_h2_sync->callback = callback;
|
||
hfp_h2_sync->dropped_bytes = 0;
|
||
hfp_h2_sync_reset(hfp_h2_sync);
|
||
}
|
||
|
||
static void hfp_h2_report_bad_frames(hfp_h2_sync_t *hfp_h2_sync){
|
||
// report bad frames
|
||
while (hfp_h2_sync->dropped_bytes >= HFP_H2_SYNC_FRAME_SIZE){
|
||
hfp_h2_sync->dropped_bytes -= HFP_H2_SYNC_FRAME_SIZE;
|
||
(void)(*hfp_h2_sync->callback)(true,NULL, HFP_H2_SYNC_FRAME_SIZE);
|
||
}
|
||
}
|
||
|
||
static void hfp_h2_sync_drop_bytes(hfp_h2_sync_t * hfp_h2_sync, uint16_t bytes_to_drop){
|
||
btstack_assert(bytes_to_drop <= hfp_h2_sync->frame_len);
|
||
memmove(hfp_h2_sync->frame_data, &hfp_h2_sync->frame_data[bytes_to_drop], hfp_h2_sync->frame_len - bytes_to_drop);
|
||
hfp_h2_sync->dropped_bytes += bytes_to_drop;
|
||
hfp_h2_sync->frame_len -= bytes_to_drop;
|
||
hfp_h2_report_bad_frames(hfp_h2_sync);
|
||
}
|
||
|
||
void hfp_h2_sync_process(hfp_h2_sync_t *hfp_h2_sync, bool bad_frame, const uint8_t *frame_data, uint16_t frame_len) {
|
||
|
||
if (bad_frame){
|
||
// drop all data
|
||
hfp_h2_sync->dropped_bytes += hfp_h2_sync->frame_len;
|
||
hfp_h2_sync->frame_len = 0;
|
||
// all new data is bad, too
|
||
hfp_h2_sync->dropped_bytes += frame_len;
|
||
// report frames
|
||
hfp_h2_report_bad_frames(hfp_h2_sync);
|
||
return;
|
||
}
|
||
|
||
while (frame_len > 0){
|
||
// Fill hfp_h2_sync->frame_buffer
|
||
uint16_t bytes_free_in_frame_buffer = HFP_H2_SYNC_FRAME_SIZE - hfp_h2_sync->frame_len;
|
||
uint16_t bytes_to_append = btstack_min(frame_len, bytes_free_in_frame_buffer);
|
||
memcpy(&hfp_h2_sync->frame_data[hfp_h2_sync->frame_len], frame_data, bytes_to_append);
|
||
frame_data += bytes_to_append;
|
||
frame_len -= bytes_to_append;
|
||
hfp_h2_sync->frame_len += bytes_to_append;
|
||
// check complete frame for h2 sync
|
||
if (hfp_h2_sync->frame_len == HFP_H2_SYNC_FRAME_SIZE){
|
||
bool valid_frame = true;
|
||
int16_t h2_pos = hfp_h2_sync_find(hfp_h2_sync->frame_data, hfp_h2_sync->frame_len);
|
||
if (h2_pos < 0){
|
||
// no h2 sync, no valid frame, keep last byte if it is 0x01
|
||
if (hfp_h2_sync->frame_data[HFP_H2_SYNC_FRAME_SIZE-1] == 0x01){
|
||
hfp_h2_sync_drop_bytes(hfp_h2_sync, HFP_H2_SYNC_FRAME_SIZE - 1);
|
||
} else {
|
||
hfp_h2_sync_drop_bytes(hfp_h2_sync, HFP_H2_SYNC_FRAME_SIZE);
|
||
}
|
||
valid_frame = false;
|
||
}
|
||
else if (h2_pos > 0){
|
||
// drop data before h2 sync
|
||
hfp_h2_sync_drop_bytes(hfp_h2_sync, h2_pos);
|
||
valid_frame = false;
|
||
}
|
||
if (valid_frame) {
|
||
// h2 sync at pos 0 and complete frame
|
||
bool codec_ok = (*hfp_h2_sync->callback)(false, hfp_h2_sync->frame_data, hfp_h2_sync->frame_len);
|
||
if (codec_ok){
|
||
hfp_h2_sync_reset(hfp_h2_sync);
|
||
} else {
|
||
// drop first two bytes
|
||
hfp_h2_sync_drop_bytes(hfp_h2_sync, 2);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|