1361 lines
56 KiB
C
Raw Normal View History

2015-07-09 15:28:06 +02:00
/*
* 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 MATTHIAS
* RINGWALD 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
*
*/
// *****************************************************************************
//
// Minimal setup for HFP Audio Gateway (AG) unit (!! UNDER DEVELOPMENT !!)
//
// *****************************************************************************
2016-01-21 15:41:16 +01:00
#include "btstack_config.h"
2015-07-09 15:28:06 +02:00
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
2015-08-07 21:45:00 +02:00
#include <inttypes.h>
2015-07-09 15:28:06 +02:00
2016-01-21 11:33:17 +01:00
#include "hci_cmd.h"
2016-01-20 15:08:39 +01:00
#include "btstack_run_loop.h"
2015-07-09 15:28:06 +02:00
#include "hci.h"
#include "btstack_memory.h"
#include "hci_dump.h"
#include "l2cap.h"
2015-11-13 15:04:41 +01:00
#include "classic/sdp_query_rfcomm.h"
#include "classic/sdp.h"
2016-01-20 14:52:45 +01:00
#include "btstack_debug.h"
#include "btstack_event.h"
2015-07-09 15:28:06 +02:00
2015-07-17 11:14:34 +02:00
#define HFP_HF_FEATURES_SIZE 10
#define HFP_AG_FEATURES_SIZE 12
2015-08-13 20:55:21 +02:00
2015-07-17 11:14:34 +02:00
static const char * hfp_hf_features[] = {
"EC and/or NR function",
"Three-way calling",
"CLI presentation capability",
"Voice recognition activation",
"Remote volume control",
2015-08-13 20:55:21 +02:00
2015-07-17 11:14:34 +02:00
"Enhanced call status",
"Enhanced call control",
2015-08-13 20:55:21 +02:00
2015-07-17 11:14:34 +02:00
"Codec negotiation",
2015-08-13 20:55:21 +02:00
2015-07-17 11:14:34 +02:00
"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"
};
2015-08-12 14:14:55 +02:00
static int hfp_generic_status_indicators_nr = 0;
static hfp_generic_status_indicator_t hfp_generic_status_indicators[HFP_MAX_NUM_HF_INDICATORS];
2015-08-05 17:05:16 +02:00
2016-01-20 15:38:37 +01:00
static btstack_linked_list_t hfp_connections = NULL;
2015-11-26 22:05:31 +01:00
static void parse_sequence(hfp_connection_t * context);
2015-07-09 17:03:16 +02:00
hfp_generic_status_indicator_t * get_hfp_generic_status_indicators(void){
return (hfp_generic_status_indicator_t *) &hfp_generic_status_indicators;
2015-08-12 12:22:53 +02:00
}
int get_hfp_generic_status_indicators_nr(void){
2015-08-12 14:14:55 +02:00
return hfp_generic_status_indicators_nr;
2015-08-12 12:22:53 +02:00
}
void set_hfp_generic_status_indicators(hfp_generic_status_indicator_t * indicators, int indicator_nr){
2015-08-12 12:22:53 +02:00
if (indicator_nr > HFP_MAX_NUM_HF_INDICATORS) return;
2015-08-12 14:14:55 +02:00
hfp_generic_status_indicators_nr = indicator_nr;
memcpy(hfp_generic_status_indicators, indicators, indicator_nr * sizeof(hfp_generic_status_indicator_t));
2015-08-12 12:22:53 +02:00
}
2015-07-17 11:14:34 +02:00
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, char * command){
if (!rfcomm_can_send_packet_now(cid)) return 1;
2016-01-07 14:09:31 +01:00
log_info("HFP_TX %s", command);
2016-01-21 12:09:19 +01:00
int err = rfcomm_send(cid, (uint8_t*) command, strlen(command));
if (err){
2016-01-21 12:09:19 +01:00
log_error("rfcomm_send -> error 0x%02x \n", err);
2015-08-07 21:45:00 +02:00
}
2015-10-15 13:55:28 +02:00
return 1;
}
#if 0
void hfp_set_codec(hfp_connection_t * context, uint8_t *packet, uint16_t size){
// parse available codecs
int pos = 0;
int i;
for (i=0; i<size; i++){
pos+=8;
if (packet[pos] > context->negotiated_codec){
context->negotiated_codec = packet[pos];
}
}
printf("Negotiated Codec 0x%02x\n", context->negotiated_codec);
}
#endif
// 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;
}
2015-07-30 13:16:53 +02:00
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 += snprintf(buffer+offset, buffer_size-offset, "%d,", values[i]); // puts string into buffer
}
if (i<values_nr){
offset += snprintf(buffer+offset, buffer_size-offset, "%d", values[i]);
2015-07-15 16:22:57 +02:00
}
2015-07-30 13:16:53 +02:00
return offset;
}
2015-08-07 21:45:00 +02:00
int join_bitmap(char * buffer, int buffer_size, uint32_t values, int values_nr){
if (buffer_size < values_nr * 3) return 0;
2015-08-07 21:45:00 +02:00
int i;
int offset = 0;
for (i = 0; i < values_nr-1; i++) {
offset += snprintf(buffer+offset, buffer_size-offset, "%d,", get_bit(values,i)); // puts string into buffer
}
2015-08-07 21:45:00 +02:00
if (i<values_nr){
offset += snprintf(buffer+offset, buffer_size-offset, "%d", get_bit(values,i));
}
return offset;
}
2015-08-05 17:05:16 +02:00
void hfp_emit_event(hfp_callback_t callback, uint8_t event_subtype, uint8_t value){
if (!callback) return;
uint8_t event[4];
event[0] = HCI_EVENT_HFP_META;
event[1] = sizeof(event) - 2;
event[2] = event_subtype;
event[3] = value; // status 0 == OK
(*callback)(event, sizeof(event));
}
2015-11-20 17:29:41 +01:00
void hfp_emit_string_event(hfp_callback_t callback, uint8_t event_subtype, const char * value){
if (!callback) return;
2015-11-25 14:59:08 +01:00
uint8_t event[40];
2015-11-20 17:29:41 +01:00
event[0] = HCI_EVENT_HFP_META;
event[1] = sizeof(event) - 2;
event[2] = event_subtype;
2015-11-20 23:25:54 +01:00
int size = (strlen(value) < sizeof(event) - 4) ? strlen(value) : sizeof(event) - 4;
2015-11-20 17:29:41 +01:00
strncpy((char*)&event[3], value, size);
2015-11-21 21:11:33 +01:00
event[3 + size] = 0;
2015-11-20 17:29:41 +01:00
(*callback)(event, sizeof(event));
}
2015-11-12 23:31:31 +01:00
static void hfp_emit_audio_connection_established_event(hfp_callback_t callback, uint8_t value, uint16_t sco_handle){
2015-11-12 17:25:31 +01:00
if (!callback) return;
uint8_t event[6];
event[0] = HCI_EVENT_HFP_META;
event[1] = sizeof(event) - 2;
event[2] = HFP_SUBEVENT_AUDIO_CONNECTION_ESTABLISHED;
event[3] = value; // status 0 == OK
little_endian_store_16(event, 4, sco_handle);
2015-11-12 17:25:31 +01:00
(*callback)(event, sizeof(event));
}
2016-01-20 15:38:37 +01:00
btstack_linked_list_t * hfp_get_connections(){
return (btstack_linked_list_t *) &hfp_connections;
2015-07-09 17:03:16 +02:00
}
2015-07-09 15:28:06 +02:00
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 * connection = (hfp_connection_t *)btstack_linked_list_iterator_next(&it);
if (connection->rfcomm_cid == cid){
return connection;
}
}
return NULL;
}
2015-08-07 21:45:00 +02:00
hfp_connection_t * get_hfp_connection_context_for_bd_addr(bd_addr_t bd_addr){
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 * connection = (hfp_connection_t *)btstack_linked_list_iterator_next(&it);
2015-07-09 15:28:06 +02:00
if (memcmp(connection->remote_addr, bd_addr, 6) == 0) {
return connection;
}
}
return NULL;
}
2015-11-11 15:45:35 +01:00
hfp_connection_t * get_hfp_connection_context_for_sco_handle(uint16_t handle){
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 * connection = (hfp_connection_t *)btstack_linked_list_iterator_next(&it);
2015-11-11 15:45:35 +01:00
if (connection->sco_handle == handle){
2015-08-05 17:23:23 +02:00
return connection;
}
}
return NULL;
}
2015-08-21 17:29:10 +02:00
void hfp_reset_context_flags(hfp_connection_t * context){
2015-08-27 00:08:33 +02:00
if (!context) return;
2015-11-18 16:34:26 +01:00
context->ok_pending = 0;
2015-08-21 17:29:10 +02:00
context->send_error = 0;
2015-11-26 22:05:31 +01:00
context->keep_byte = 0;
2015-08-21 17:29:10 +02:00
context->change_status_update_for_individual_ag_indicators = 0;
context->operator_name_changed = 0;
context->enable_extended_audio_gateway_error_report = 0;
context->extended_audio_gateway_error = 0;
// establish codecs connection
context->suggested_codec = 0;
2015-10-15 13:55:28 +02:00
context->negotiated_codec = 0;
context->codec_confirmed = 0;
2015-08-21 17:29:10 +02:00
context->establish_audio_connection = 0;
}
2015-07-09 15:28:06 +02:00
static hfp_connection_t * create_hfp_connection_context(){
hfp_connection_t * context = btstack_memory_hfp_connection_get();
if (!context) return NULL;
// init state
2015-08-05 17:05:16 +02:00
memset(context,0, sizeof(hfp_connection_t));
2015-07-09 15:28:06 +02:00
context->state = HFP_IDLE;
2015-11-13 17:13:48 +01:00
context->call_state = HFP_CALL_IDLE;
context->codecs_state = HFP_CODECS_IDLE;
2015-07-30 11:10:50 +02:00
context->parser_state = HFP_PARSER_CMD_HEADER;
context->command = HFP_CMD_NONE;
2015-08-27 00:08:33 +02:00
context->negotiated_codec = 0;
2015-07-30 17:03:11 +02:00
2015-08-07 21:45:00 +02:00
context->enable_status_update_for_ag_indicators = 0xFF;
2015-07-15 16:22:57 +02:00
2015-08-12 14:14:55 +02:00
context->generic_status_indicators_nr = hfp_generic_status_indicators_nr;
memcpy(context->generic_status_indicators, hfp_generic_status_indicators, hfp_generic_status_indicators_nr * sizeof(hfp_generic_status_indicator_t));
2015-08-12 12:22:53 +02:00
btstack_linked_list_add(&hfp_connections, (btstack_linked_item_t*)context);
2015-07-09 15:28:06 +02:00
return context;
}
2015-08-05 17:05:16 +02:00
static void remove_hfp_connection_context(hfp_connection_t * context){
btstack_linked_list_remove(&hfp_connections, (btstack_linked_item_t*)context);
2015-08-05 17:05:16 +02:00
}
2015-07-09 15:28:06 +02:00
static hfp_connection_t * provide_hfp_connection_context_for_bd_addr(bd_addr_t bd_addr){
2015-07-09 15:28:06 +02:00
hfp_connection_t * context = get_hfp_connection_context_for_bd_addr(bd_addr);
if (context) return context;
context = create_hfp_connection_context();
2015-11-12 14:16:20 +01:00
printf("created context for address %s\n", bd_addr_to_str(bd_addr));
2015-07-09 15:28:06 +02:00
memcpy(context->remote_addr, bd_addr, 6);
return context;
}
2015-11-18 16:34:09 +01:00
/* @param network.
* 0 == no ability to reject a call.
* 1 == ability to reject a call.
*/
2015-07-09 15:28:06 +02:00
/* @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){
2015-07-09 15:28:06 +02:00
uint8_t* attribute;
de_create_sequence(service);
// 0x0000 "Service Record Handle"
de_add_number(service, DE_UINT, DE_SIZE_16, SDP_ServiceRecordHandle);
de_add_number(service, DE_UINT, DE_SIZE_32, service_record_handle);
2015-07-09 15:28:06 +02:00
// 0x0001 "Service Class ID List"
de_add_number(service, DE_UINT, DE_SIZE_16, SDP_ServiceClassIDList);
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, SDP_GenericAudio);
}
de_pop_sequence(service, attribute);
// 0x0004 "Protocol Descriptor List"
de_add_number(service, DE_UINT, DE_SIZE_16, SDP_ProtocolDescriptorList);
attribute = de_push_sequence(service);
{
uint8_t* l2cpProtocol = de_push_sequence(attribute);
{
de_add_number(l2cpProtocol, DE_UUID, DE_SIZE_16, SDP_L2CAPProtocol);
}
de_pop_sequence(attribute, l2cpProtocol);
uint8_t* rfcomm = de_push_sequence(attribute);
{
de_add_number(rfcomm, DE_UUID, DE_SIZE_16, SDP_RFCOMMProtocol); // 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, SDP_BrowseGroupList); // public browse group
attribute = de_push_sequence(service);
{
de_add_number(attribute, DE_UUID, DE_SIZE_16, SDP_PublicBrowseGroup);
}
de_pop_sequence(service, attribute);
// 0x0009 "Bluetooth Profile Descriptor List"
de_add_number(service, DE_UINT, DE_SIZE_16, SDP_BluetoothProfileDescriptorList);
attribute = de_push_sequence(service);
{
uint8_t *sppProfile = de_push_sequence(attribute);
{
de_add_number(sppProfile, DE_UUID, DE_SIZE_16, SDP_Handsfree);
de_add_number(sppProfile, DE_UINT, DE_SIZE_16, 0x0107); // Verision 1.7
}
de_pop_sequence(attribute, sppProfile);
}
de_pop_sequence(service, attribute);
// 0x0100 "Service Name"
de_add_number(service, DE_UINT, DE_SIZE_16, 0x0100);
de_add_data(service, DE_STRING, strlen(name), (uint8_t *) name);
}
static hfp_connection_t * connection_doing_sdp_query = NULL;
static void handle_query_rfcomm_event(uint8_t packet_type, uint8_t *packet, uint16_t size, void * context){
2015-07-09 15:28:06 +02:00
hfp_connection_t * connection = connection_doing_sdp_query;
2015-07-09 17:03:16 +02:00
2016-01-30 23:58:36 +01:00
if ( connection->state != HFP_W4_SDP_EVENT_QUERY_COMPLETE) return;
2015-07-09 17:03:16 +02:00
switch (packet[0]){
2016-01-30 23:58:36 +01:00
case SDP_EVENT_QUERY_RFCOMM_SERVICE:
2015-07-09 15:28:06 +02:00
if (!connection) {
log_error("handle_query_rfcomm_event alloc connection for RFCOMM port %u failed", sdp_query_rfcomm_service_event_get_rfcomm_channel(packet));
2015-07-09 15:28:06 +02:00
return;
}
connection->rfcomm_channel_nr = sdp_query_rfcomm_service_event_get_rfcomm_channel(packet);
2015-07-09 15:28:06 +02:00
break;
2016-01-30 23:58:36 +01:00
case SDP_EVENT_QUERY_COMPLETE:
2015-07-09 15:28:06 +02:00
connection_doing_sdp_query = NULL;
if (connection->rfcomm_channel_nr > 0){
connection->state = HFP_W4_RFCOMM_CONNECTED;
2016-01-30 23:58:36 +01:00
log_info("HFP: SDP_EVENT_QUERY_COMPLETE context %p, addr %s, state %d", connection, bd_addr_to_str( connection->remote_addr), connection->state);
rfcomm_create_channel(connection->remote_addr, connection->rfcomm_channel_nr, NULL);
2015-07-09 15:28:06 +02:00
break;
}
log_info("rfcomm service not found, status %u.", sdp_query_complete_event_get_status(packet));
2015-07-09 15:28:06 +02:00
break;
default:
break;
}
}
2015-08-05 17:23:23 +02:00
void hfp_handle_hci_event(hfp_callback_t callback, uint8_t packet_type, uint8_t *packet, uint16_t size){
2015-07-09 17:03:16 +02:00
bd_addr_t event_addr;
2015-08-05 17:23:23 +02:00
uint16_t rfcomm_cid, handle;
2015-07-09 17:03:16 +02:00
hfp_connection_t * context = NULL;
2015-11-12 14:16:20 +01:00
// printf("AG packet_handler type %u, packet[0] %x, size %u\n", packet_type, packet[0], size);
2015-07-10 10:46:45 +02:00
switch (packet[0]) {
2015-07-09 17:03:16 +02:00
case BTSTACK_EVENT_STATE:
// bt stack activated, get started
if (packet[2] == HCI_STATE_WORKING){
printf("BTstack activated, get started .\n");
}
break;
2015-07-09 15:28:06 +02:00
2015-07-09 17:03:16 +02:00
case HCI_EVENT_PIN_CODE_REQUEST:
// inform about pin code request
printf("Pin code request - using '0000'\n\r");
bt_flip_addr(event_addr, &packet[2]);
hci_send_cmd(&hci_pin_code_request_reply, &event_addr, 4, "0000");
break;
case RFCOMM_EVENT_INCOMING_CONNECTION:
// data: event (8), len(8), address(48), channel (8), rfcomm_cid (16)
bt_flip_addr(event_addr, &packet[2]);
context = provide_hfp_connection_context_for_bd_addr(event_addr);
2015-07-09 17:03:16 +02:00
2015-08-05 17:05:16 +02:00
if (!context || context->state != HFP_IDLE) return;
2015-07-09 17:03:16 +02:00
context->rfcomm_cid = little_endian_read_16(packet, 9);
2015-07-09 17:03:16 +02:00
context->state = HFP_W4_RFCOMM_CONNECTED;
printf("RFCOMM channel %u requested for %s\n", context->rfcomm_cid, bd_addr_to_str(context->remote_addr));
2016-01-21 12:09:19 +01:00
rfcomm_accept_connection(context->rfcomm_cid);
2015-07-09 17:03:16 +02:00
break;
case RFCOMM_EVENT_OPEN_CHANNEL_COMPLETE:
// data: event(8), len(8), status (8), address (48), handle(16), server channel(8), rfcomm_cid(16), max frame size(16)
2015-11-12 12:13:43 +01:00
printf("RFCOMM_EVENT_OPEN_CHANNEL_COMPLETE packet_handler type %u, packet[0] %x, size %u\n", packet_type, packet[0], size);
2015-11-11 15:45:35 +01:00
bt_flip_addr(event_addr, &packet[3]);
2015-08-05 17:05:16 +02:00
context = get_hfp_connection_context_for_bd_addr(event_addr);
if (!context || context->state != HFP_W4_RFCOMM_CONNECTED) return;
2015-07-09 17:03:16 +02:00
if (packet[2]) {
2015-08-05 17:23:23 +02:00
hfp_emit_event(callback, HFP_SUBEVENT_SERVICE_LEVEL_CONNECTION_ESTABLISHED, packet[2]);
2015-08-05 17:05:16 +02:00
remove_hfp_connection_context(context);
2015-07-09 17:03:16 +02:00
} else {
context->con_handle = little_endian_read_16(packet, 9);
2015-11-11 15:45:35 +01:00
printf("RFCOMM_EVENT_OPEN_CHANNEL_COMPLETE con_handle 0x%02x\n", context->con_handle);
context->rfcomm_cid = little_endian_read_16(packet, 12);
uint16_t mtu = little_endian_read_16(packet, 14);
printf("RFCOMM channel open succeeded. Context %p, RFCOMM Channel ID 0x%02x, max frame size %u\n", context, context->rfcomm_cid, mtu);
2015-08-05 17:05:16 +02:00
switch (context->state){
case HFP_W4_RFCOMM_CONNECTED:
context->state = HFP_EXCHANGE_SUPPORTED_FEATURES;
break;
case HFP_W4_CONNECTION_ESTABLISHED_TO_SHUTDOWN:
context->state = HFP_W2_DISCONNECT_RFCOMM;
printf("Shutting down RFCOMM.\n");
break;
default:
break;
}
// forward event to app, to learn about con_handle
(*callback)(packet, size);
2015-07-09 17:03:16 +02:00
}
break;
2015-08-05 17:05:16 +02:00
2015-08-21 17:29:10 +02:00
case HCI_EVENT_SYNCHRONOUS_CONNECTION_COMPLETE:{
2015-11-12 12:13:43 +01:00
bt_flip_addr(event_addr, &packet[5]);
2015-08-21 17:29:10 +02:00
int index = 2;
uint8_t status = packet[index++];
2015-11-12 12:13:43 +01:00
if (status != 0){
log_error("(e)SCO Connection failed status %u", status);
// if outgoing && link_setting != d0 && appropriate error
if (status != 0x11 && status != 0x1f) break; // invalid params / unspecified error
context = get_hfp_connection_context_for_bd_addr(event_addr);
if (!context) break;
switch (context->link_setting){
case HFP_LINK_SETTINGS_D0:
return; // no other option left
case HFP_LINK_SETTINGS_D1:
// context->link_setting = HFP_LINK_SETTINGS_D0;
// break;
case HFP_LINK_SETTINGS_S1:
// context->link_setting = HFP_LINK_SETTINGS_D1;
// break;
case HFP_LINK_SETTINGS_S2:
case HFP_LINK_SETTINGS_S3:
case HFP_LINK_SETTINGS_S4:
// context->link_setting = HFP_LINK_SETTINGS_S1;
// break;
case HFP_LINK_SETTINGS_T1:
case HFP_LINK_SETTINGS_T2:
// context->link_setting = HFP_LINK_SETTINGS_S3;
context->link_setting = HFP_LINK_SETTINGS_D0;
break;
}
context->establish_audio_connection = 1;
2015-11-12 12:13:43 +01:00
break;
}
uint16_t sco_handle = little_endian_read_16(packet, index);
2015-08-21 17:29:10 +02:00
index+=2;
2015-11-12 12:13:43 +01:00
bt_flip_addr(event_addr, &packet[index]);
2015-08-21 17:29:10 +02:00
index+=6;
2015-11-12 12:13:43 +01:00
2015-08-21 17:29:10 +02:00
uint8_t link_type = packet[index++];
uint8_t transmission_interval = packet[index++]; // measured in slots
uint8_t retransmission_interval = packet[index++];// measured in slots
uint16_t rx_packet_length = little_endian_read_16(packet, index); // measured in bytes
2015-08-21 17:29:10 +02:00
index+=2;
uint16_t tx_packet_length = little_endian_read_16(packet, index); // measured in bytes
2015-08-21 17:29:10 +02:00
index+=2;
uint8_t air_mode = packet[index];
switch (link_type){
case 0x00:
2015-11-12 14:16:20 +01:00
log_info("SCO Connection established.");
2015-08-21 17:29:10 +02:00
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:
2015-11-12 12:13:43 +01:00
log_info("eSCO Connection established. \n");
2015-08-21 17:29:10 +02:00
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, "
2015-11-12 14:16:20 +01:00
" rx_packet_length %u bytes, tx_packet_length %u bytes, air_mode 0x%2x (0x02 == CVSD)\n", sco_handle,
2015-11-12 12:13:43 +01:00
bd_addr_to_str(event_addr), transmission_interval, retransmission_interval, rx_packet_length, tx_packet_length, air_mode);
2015-08-21 17:29:10 +02:00
2015-11-12 12:13:43 +01:00
context = get_hfp_connection_context_for_bd_addr(event_addr);
if (!context) {
2015-11-12 14:16:20 +01:00
log_error("SCO link created, context for address %s not found.", bd_addr_to_str(event_addr));
2015-11-12 12:13:43 +01:00
break;
}
2015-08-21 17:29:10 +02:00
if (context->state == HFP_W4_CONNECTION_ESTABLISHED_TO_SHUTDOWN){
2015-11-12 14:16:20 +01:00
log_info("SCO about to disconnect: HFP_W4_CONNECTION_ESTABLISHED_TO_SHUTDOWN");
2015-08-21 17:29:10 +02:00
context->state = HFP_W2_DISCONNECT_SCO;
break;
}
context->sco_handle = sco_handle;
2015-11-12 17:25:31 +01:00
context->establish_audio_connection = 0;
2015-08-21 17:29:10 +02:00
context->state = HFP_AUDIO_CONNECTION_ESTABLISHED;
2015-11-12 17:25:31 +01:00
hfp_emit_audio_connection_established_event(callback, packet[2], sco_handle);
2015-08-21 17:29:10 +02:00
break;
}
2015-07-09 17:03:16 +02:00
case RFCOMM_EVENT_CHANNEL_CLOSED:
rfcomm_cid = little_endian_read_16(packet,2);
2015-08-05 17:05:16 +02:00
context = get_hfp_connection_context_for_rfcomm_cid(rfcomm_cid);
if (!context) break;
2015-08-12 23:14:23 +02:00
if (context->state == HFP_W4_RFCOMM_DISCONNECTED_AND_RESTART){
context->state = HFP_IDLE;
hfp_establish_service_level_connection(context->remote_addr, context->service_uuid);
break;
}
2015-08-05 17:23:23 +02:00
hfp_emit_event(callback, HFP_SUBEVENT_SERVICE_LEVEL_CONNECTION_RELEASED, 0);
2015-08-27 00:08:33 +02:00
remove_hfp_connection_context(context);
2015-08-05 17:23:23 +02:00
break;
case HCI_EVENT_DISCONNECTION_COMPLETE:
handle = little_endian_read_16(packet,3);
2015-11-11 15:45:35 +01:00
context = get_hfp_connection_context_for_sco_handle(handle);
2015-08-05 17:23:23 +02:00
if (!context) break;
2015-11-11 15:45:35 +01:00
if (context->state != HFP_W4_SCO_DISCONNECTED){
log_info("Received gap disconnect in wrong hfp state");
}
2015-11-12 12:13:43 +01:00
log_info("Check SCO handle: incoming 0x%02x, context 0x%02x\n", handle,context->sco_handle);
2015-11-11 15:45:35 +01:00
if (handle == context->sco_handle){
2015-11-12 12:13:43 +01:00
log_info("SCO disconnected, w2 disconnect RFCOMM\n");
2015-11-11 15:45:35 +01:00
context->sco_handle = 0;
2015-11-12 12:13:43 +01:00
context->release_audio_connection = 0;
2015-11-19 12:19:43 +01:00
context->state = HFP_SERVICE_LEVEL_CONNECTION_ESTABLISHED;
hfp_emit_event(callback, HFP_SUBEVENT_AUDIO_CONNECTION_RELEASED, 0);
2015-08-12 23:14:23 +02:00
break;
}
2015-07-09 17:03:16 +02:00
break;
2015-08-05 17:05:16 +02:00
2015-11-26 10:40:45 +01:00
case HCI_EVENT_INQUIRY_RESULT:
case HCI_EVENT_INQUIRY_RESULT_WITH_RSSI:
case HCI_EVENT_INQUIRY_COMPLETE:
case BTSTACK_EVENT_REMOTE_NAME_CACHED:
case HCI_EVENT_REMOTE_NAME_REQUEST_COMPLETE:
// forward inquiry events to app - TODO: replace with new event handler architecture
(*callback)(packet, size);
2015-07-09 17:03:16 +02:00
break;
}
}
2015-07-09 15:28:06 +02:00
// translates command string into hfp_command_t CMD
2015-11-18 21:39:08 +01:00
static hfp_command_t parse_command(const char * line_buffer, int isHandsFree){
2015-11-18 17:27:45 +01:00
int offset = isHandsFree ? 0 : 2;
2015-11-25 17:11:04 +01:00
if (strncmp(line_buffer+offset, HFP_LIST_CURRENT_CALLS, strlen(HFP_LIST_CURRENT_CALLS)) == 0){
return HFP_CMD_LIST_CURRENT_CALLS;
}
if (strncmp(line_buffer+offset, HFP_SUBSCRIBER_NUMBER_INFORMATION, strlen(HFP_SUBSCRIBER_NUMBER_INFORMATION)) == 0){
return HFP_CMD_GET_SUBSCRIBER_NUMBER_INFORMATION;
}
if (strncmp(line_buffer+offset, HFP_PHONE_NUMBER_FOR_VOICE_TAG, strlen(HFP_PHONE_NUMBER_FOR_VOICE_TAG)) == 0){
if (isHandsFree) return HFP_CMD_AG_SENT_PHONE_NUMBER;
2015-11-23 16:52:55 +01:00
return HFP_CMD_HF_REQUEST_PHONE_NUMBER;
}
2015-11-23 21:48:19 +01:00
if (strncmp(line_buffer+offset, HFP_TRANSMIT_DTMF_CODES, strlen(HFP_TRANSMIT_DTMF_CODES)) == 0){
2015-11-23 16:52:55 +01:00
return HFP_CMD_TRANSMIT_DTMF_CODES;
}
2015-11-23 21:48:19 +01:00
if (strncmp(line_buffer+offset, HFP_SET_MICROPHONE_GAIN, strlen(HFP_SET_MICROPHONE_GAIN)) == 0){
2015-11-23 16:52:55 +01:00
return HFP_CMD_SET_MICROPHONE_GAIN;
}
2015-11-23 21:48:19 +01:00
if (strncmp(line_buffer+offset, HFP_SET_SPEAKER_GAIN, strlen(HFP_SET_SPEAKER_GAIN)) == 0){
2015-11-23 16:52:55 +01:00
return HFP_CMD_SET_SPEAKER_GAIN;
}
2015-11-23 21:48:19 +01:00
if (strncmp(line_buffer+offset, HFP_ACTIVATE_VOICE_RECOGNITION, strlen(HFP_ACTIVATE_VOICE_RECOGNITION)) == 0){
if (isHandsFree) return HFP_CMD_AG_ACTIVATE_VOICE_RECOGNITION;
return HFP_CMD_HF_ACTIVATE_VOICE_RECOGNITION;
}
2015-11-23 21:48:19 +01:00
if (strncmp(line_buffer+offset, HFP_TURN_OFF_EC_AND_NR, strlen(HFP_TURN_OFF_EC_AND_NR)) == 0){
2015-11-23 15:37:09 +01:00
return HFP_CMD_TURN_OFF_EC_AND_NR;
}
2015-11-19 09:49:38 +01:00
if (strncmp(line_buffer, HFP_CALL_ANSWERED, strlen(HFP_CALL_ANSWERED)) == 0){
2015-11-18 21:39:08 +01:00
return HFP_CMD_CALL_ANSWERED;
}
2015-11-20 23:25:54 +01:00
if (strncmp(line_buffer, HFP_CALL_PHONE_NUMBER, strlen(HFP_CALL_PHONE_NUMBER)) == 0){
2015-11-20 17:29:41 +01:00
return HFP_CMD_CALL_PHONE_NUMBER;
}
if (strncmp(line_buffer+offset, HFP_REDIAL_LAST_NUMBER, strlen(HFP_REDIAL_LAST_NUMBER)) == 0){
2015-11-22 18:48:40 +01:00
return HFP_CMD_REDIAL_LAST_NUMBER;
}
2015-11-19 15:56:06 +01:00
if (strncmp(line_buffer+offset, HFP_CHANGE_IN_BAND_RING_TONE_SETTING, strlen(HFP_CHANGE_IN_BAND_RING_TONE_SETTING)) == 0){
return HFP_CMD_CHANGE_IN_BAND_RING_TONE_SETTING;
}
2015-11-19 12:53:36 +01:00
if (strncmp(line_buffer+offset, HFP_HANG_UP_CALL, strlen(HFP_HANG_UP_CALL)) == 0){
return HFP_CMD_HANG_UP_CALL;
}
2015-11-18 19:39:36 +01:00
if (strncmp(line_buffer+offset, HFP_ERROR, strlen(HFP_ERROR)) == 0){
2015-11-18 21:39:08 +01:00
return HFP_CMD_ERROR;
}
2015-11-27 14:29:41 +01:00
if (strncmp(line_buffer+offset, HFP_RING, strlen(HFP_RING)) == 0){
return HFP_CMD_RING;
}
2015-11-18 19:39:36 +01:00
if (isHandsFree && strncmp(line_buffer+offset, HFP_OK, strlen(HFP_OK)) == 0){
2015-11-18 21:39:08 +01:00
return HFP_CMD_OK;
}
2015-11-18 19:39:36 +01:00
if (strncmp(line_buffer+offset, HFP_SUPPORTED_FEATURES, strlen(HFP_SUPPORTED_FEATURES)) == 0){
2015-11-18 21:39:08 +01:00
return HFP_CMD_SUPPORTED_FEATURES;
}
if (strncmp(line_buffer+offset, HFP_TRANSFER_HF_INDICATOR_STATUS, strlen(HFP_TRANSFER_HF_INDICATOR_STATUS)) == 0){
return HFP_CMD_HF_INDICATOR_STATUS;
}
if (strncmp(line_buffer+offset, HFP_RESPONSE_AND_HOLD, strlen(HFP_RESPONSE_AND_HOLD)) == 0){
if (strncmp(line_buffer+strlen(HFP_RESPONSE_AND_HOLD)+offset, "?", 1) == 0){
return HFP_CMD_RESPONSE_AND_HOLD_QUERY;
}
if (strncmp(line_buffer+strlen(HFP_RESPONSE_AND_HOLD)+offset, "=", 1) == 0){
return HFP_CMD_RESPONSE_AND_HOLD_COMMAND;
}
return HFP_CMD_RESPONSE_AND_HOLD_STATUS;
}
2015-11-18 19:39:36 +01:00
if (strncmp(line_buffer+offset, HFP_INDICATOR, strlen(HFP_INDICATOR)) == 0){
if (strncmp(line_buffer+strlen(HFP_INDICATOR)+offset, "?", 1) == 0){
2015-11-18 21:39:08 +01:00
return HFP_CMD_RETRIEVE_AG_INDICATORS_STATUS;
}
2015-11-18 19:39:36 +01:00
if (strncmp(line_buffer+strlen(HFP_INDICATOR)+offset, "=?", 2) == 0){
2015-11-18 21:39:08 +01:00
return HFP_CMD_RETRIEVE_AG_INDICATORS;
}
}
2015-11-18 19:39:36 +01:00
if (strncmp(line_buffer+offset, HFP_AVAILABLE_CODECS, strlen(HFP_AVAILABLE_CODECS)) == 0){
2015-11-18 21:39:08 +01:00
return HFP_CMD_AVAILABLE_CODECS;
}
2015-11-18 19:39:36 +01:00
if (strncmp(line_buffer+offset, HFP_ENABLE_STATUS_UPDATE_FOR_AG_INDICATORS, strlen(HFP_ENABLE_STATUS_UPDATE_FOR_AG_INDICATORS)) == 0){
2015-11-18 21:39:08 +01:00
return HFP_CMD_ENABLE_INDICATOR_STATUS_UPDATE;
}
if (strncmp(line_buffer+offset, HFP_ENABLE_CLIP, strlen(HFP_ENABLE_CLIP)) == 0){
return HFP_CMD_ENABLE_CLIP;
}
2015-11-22 21:14:19 +01:00
if (strncmp(line_buffer+offset, HFP_ENABLE_CALL_WAITING_NOTIFICATION, strlen(HFP_ENABLE_CALL_WAITING_NOTIFICATION)) == 0){
return HFP_CMD_ENABLE_CALL_WAITING_NOTIFICATION;
}
2015-11-18 19:39:36 +01:00
if (strncmp(line_buffer+offset, HFP_SUPPORT_CALL_HOLD_AND_MULTIPARTY_SERVICES, strlen(HFP_SUPPORT_CALL_HOLD_AND_MULTIPARTY_SERVICES)) == 0){
2015-11-22 22:40:12 +01:00
if (isHandsFree) return HFP_CMD_SUPPORT_CALL_HOLD_AND_MULTIPARTY_SERVICES;
if (strncmp(line_buffer+strlen(HFP_SUPPORT_CALL_HOLD_AND_MULTIPARTY_SERVICES)+offset, "=?", 2) == 0){
return HFP_CMD_SUPPORT_CALL_HOLD_AND_MULTIPARTY_SERVICES;
}
2015-11-28 17:46:37 +01:00
if (strncmp(line_buffer+strlen(HFP_SUPPORT_CALL_HOLD_AND_MULTIPARTY_SERVICES)+offset, "=", 1) == 0){
2015-11-22 22:40:12 +01:00
return HFP_CMD_CALL_HOLD;
}
return HFP_CMD_UNKNOWN;
}
2015-11-18 19:39:36 +01:00
if (strncmp(line_buffer+offset, HFP_GENERIC_STATUS_INDICATOR, strlen(HFP_GENERIC_STATUS_INDICATOR)) == 0){
2015-11-28 17:46:37 +01:00
if (isHandsFree) {
return HFP_CMD_SET_GENERIC_STATUS_INDICATOR_STATUS;
}
2015-11-18 19:39:36 +01:00
if (strncmp(line_buffer+strlen(HFP_GENERIC_STATUS_INDICATOR)+offset, "=?", 2) == 0){
2015-11-18 21:39:08 +01:00
return HFP_CMD_RETRIEVE_GENERIC_STATUS_INDICATORS;
}
if (strncmp(line_buffer+strlen(HFP_GENERIC_STATUS_INDICATOR)+offset, "=", 1) == 0){
return HFP_CMD_LIST_GENERIC_STATUS_INDICATORS;
}
return HFP_CMD_RETRIEVE_GENERIC_STATUS_INDICATORS_STATE;
}
2015-11-18 19:39:36 +01:00
if (strncmp(line_buffer+offset, HFP_UPDATE_ENABLE_STATUS_FOR_INDIVIDUAL_AG_INDICATORS, strlen(HFP_UPDATE_ENABLE_STATUS_FOR_INDIVIDUAL_AG_INDICATORS)) == 0){
2015-11-18 21:39:08 +01:00
return HFP_CMD_ENABLE_INDIVIDUAL_AG_INDICATOR_STATUS_UPDATE;
}
2015-11-18 19:39:36 +01:00
if (strncmp(line_buffer+offset, HFP_QUERY_OPERATOR_SELECTION, strlen(HFP_QUERY_OPERATOR_SELECTION)) == 0){
if (strncmp(line_buffer+strlen(HFP_QUERY_OPERATOR_SELECTION)+offset, "=", 1) == 0){
2015-11-18 21:39:08 +01:00
return HFP_CMD_QUERY_OPERATOR_SELECTION_NAME_FORMAT;
}
2015-11-18 21:39:08 +01:00
return HFP_CMD_QUERY_OPERATOR_SELECTION_NAME;
}
2015-11-18 19:39:36 +01:00
if (strncmp(line_buffer+offset, HFP_TRANSFER_AG_INDICATOR_STATUS, strlen(HFP_TRANSFER_AG_INDICATOR_STATUS)) == 0){
2015-11-18 21:39:08 +01:00
return HFP_CMD_TRANSFER_AG_INDICATOR_STATUS;
}
2015-11-18 19:39:36 +01:00
if (isHandsFree && strncmp(line_buffer+offset, HFP_EXTENDED_AUDIO_GATEWAY_ERROR, strlen(HFP_EXTENDED_AUDIO_GATEWAY_ERROR)) == 0){
2015-11-18 21:39:08 +01:00
return HFP_CMD_EXTENDED_AUDIO_GATEWAY_ERROR;
}
2015-11-18 19:39:36 +01:00
if (!isHandsFree && strncmp(line_buffer+offset, HFP_ENABLE_EXTENDED_AUDIO_GATEWAY_ERROR, strlen(HFP_ENABLE_EXTENDED_AUDIO_GATEWAY_ERROR)) == 0){
2015-11-18 21:39:08 +01:00
return HFP_CMD_ENABLE_EXTENDED_AUDIO_GATEWAY_ERROR;
}
2015-08-21 17:29:10 +02:00
2015-11-18 19:39:36 +01:00
if (strncmp(line_buffer+offset, HFP_TRIGGER_CODEC_CONNECTION_SETUP, strlen(HFP_TRIGGER_CODEC_CONNECTION_SETUP)) == 0){
2015-11-18 21:39:08 +01:00
return HFP_CMD_TRIGGER_CODEC_CONNECTION_SETUP;
2015-08-21 17:29:10 +02:00
}
2015-11-18 19:39:36 +01:00
if (strncmp(line_buffer+offset, HFP_CONFIRM_COMMON_CODEC, strlen(HFP_CONFIRM_COMMON_CODEC)) == 0){
if (isHandsFree){
2015-11-18 21:39:08 +01:00
return HFP_CMD_AG_SUGGESTED_CODEC;
} else {
2015-11-18 21:39:08 +01:00
return HFP_CMD_HF_CONFIRMED_CODEC;
}
2015-08-21 17:29:10 +02:00
}
2015-11-11 12:15:35 +01:00
2015-11-18 19:39:36 +01:00
if (strncmp(line_buffer+offset, "AT+", 3) == 0){
2015-11-28 17:46:37 +01:00
log_info("process unknown HF command %s \n", line_buffer);
2015-11-18 21:39:08 +01:00
return HFP_CMD_UNKNOWN;
2015-11-12 17:25:31 +01:00
}
2015-11-18 21:39:08 +01:00
2015-11-18 19:39:36 +01:00
if (strncmp(line_buffer+offset, "+", 1) == 0){
2015-11-28 17:46:37 +01:00
log_info(" process unknown AG command %s \n", line_buffer);
2015-11-18 21:39:08 +01:00
return HFP_CMD_UNKNOWN;
2015-11-12 17:25:31 +01:00
}
2015-11-18 19:39:36 +01:00
if (strncmp(line_buffer+offset, "NOP", 3) == 0){
2015-11-18 21:39:08 +01:00
return HFP_CMD_NONE;
2015-11-12 17:25:31 +01:00
}
2015-11-18 21:39:08 +01:00
return HFP_CMD_NONE;
}
2015-07-30 17:03:11 +02:00
2015-08-14 14:48:02 +02:00
static void hfp_parser_store_byte(hfp_connection_t * context, uint8_t byte){
// printf("hfp_parser_store_byte %c at pos %u\n", (char) byte, context->line_size);
2015-08-14 14:19:31 +02:00
// TODO: add limit
context->line_buffer[context->line_size++] = byte;
context->line_buffer[context->line_size] = 0;
}
2015-08-14 17:17:04 +02:00
static int hfp_parser_is_buffer_empty(hfp_connection_t * context){
2015-08-14 14:48:02 +02:00
return context->line_size == 0;
}
2015-08-14 14:19:31 +02:00
2015-08-14 17:17:04 +02:00
static int hfp_parser_is_end_of_line(uint8_t byte){
2015-08-14 14:19:31 +02:00
return byte == '\n' || byte == '\r';
}
2015-08-14 17:17:04 +02:00
static int hfp_parser_is_end_of_header(uint8_t byte){
return hfp_parser_is_end_of_line(byte) || byte == ':' || byte == '?';
2015-08-14 14:19:31 +02:00
}
2015-08-14 14:48:02 +02:00
static int hfp_parser_found_separator(hfp_connection_t * context, uint8_t byte){
2015-11-26 22:05:31 +01:00
if (context->keep_byte == 1) return 1;
2015-08-14 17:17:04 +02:00
2015-08-19 17:00:49 +02:00
int found_separator = byte == ',' || byte == '\n'|| byte == '\r'||
2015-08-14 14:48:02 +02:00
byte == ')' || byte == '(' || byte == ':' ||
2015-08-19 17:00:49 +02:00
byte == '-' || byte == '"' || byte == '?'|| byte == '=';
2015-08-14 17:17:04 +02:00
return found_separator;
2015-08-14 14:19:31 +02:00
}
static void hfp_parser_next_state(hfp_connection_t * context, uint8_t byte){
context->line_size = 0;
2015-08-14 17:17:04 +02:00
if (hfp_parser_is_end_of_line(byte)){
2015-08-14 14:19:31 +02:00
context->parser_item_index = 0;
2015-08-19 17:00:49 +02:00
context->parser_state = HFP_PARSER_CMD_HEADER;
2015-08-14 14:19:31 +02:00
return;
}
switch (context->parser_state){
2015-08-14 15:09:57 +02:00
case HFP_PARSER_CMD_HEADER:
2015-08-14 16:35:31 +02:00
context->parser_state = HFP_PARSER_CMD_SEQUENCE;
2015-11-26 22:05:31 +01:00
if (context->keep_byte == 1){
2015-08-14 16:35:31 +02:00
hfp_parser_store_byte(context, byte);
2015-11-26 22:05:31 +01:00
context->keep_byte = 0;
2015-08-14 15:17:03 +02:00
}
2015-08-14 14:48:02 +02:00
break;
2015-08-14 14:19:31 +02:00
case HFP_PARSER_CMD_SEQUENCE:
2015-08-14 15:51:00 +02:00
switch (context->command){
case HFP_CMD_AG_SENT_PHONE_NUMBER:
2015-08-14 15:51:00 +02:00
case HFP_CMD_TRANSFER_AG_INDICATOR_STATUS:
2015-11-18 16:04:27 +01:00
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:
context->parser_state = HFP_PARSER_SECOND_ITEM;
2015-08-14 15:51:00 +02:00
break;
default:
break;
}
2015-08-14 14:19:31 +02:00
break;
2015-08-14 16:35:31 +02:00
case HFP_PARSER_SECOND_ITEM:
context->parser_state = HFP_PARSER_THIRD_ITEM;
break;
case HFP_PARSER_THIRD_ITEM:
if (context->command == HFP_CMD_RETRIEVE_AG_INDICATORS){
2015-08-14 16:35:31 +02:00
context->parser_state = HFP_PARSER_CMD_SEQUENCE;
break;
}
context->parser_state = HFP_PARSER_CMD_HEADER;
break;
2015-08-14 14:19:31 +02:00
}
}
2015-11-18 17:27:45 +01:00
void hfp_parse(hfp_connection_t * context, uint8_t byte, int isHandsFree){
2015-11-20 23:25:54 +01:00
// handle ATD<dial_string>;
if (strncmp((const char*)context->line_buffer, HFP_CALL_PHONE_NUMBER, strlen(HFP_CALL_PHONE_NUMBER)) == 0){
// check for end-of-line or ';'
if (byte == ';' || hfp_parser_is_end_of_line(byte)){
context->line_buffer[context->line_size] = 0;
context->line_size = 0;
context->command = HFP_CMD_CALL_PHONE_NUMBER;
} else {
context->line_buffer[context->line_size++] = byte;
}
return;
}
2015-08-14 17:17:04 +02:00
// TODO: handle space inside word
if (byte == ' ' && context->parser_state > HFP_PARSER_CMD_HEADER) return;
2015-11-26 22:05:31 +01:00
if (byte == ',' && context->command == HFP_CMD_ENABLE_INDIVIDUAL_AG_INDICATOR_STATUS_UPDATE){
if (context->line_size == 0){
context->line_buffer[0] = 0;
context->ignore_value = 1;
parse_sequence(context);
return;
}
}
2015-08-14 17:17:04 +02:00
if (!hfp_parser_found_separator(context, byte)){
hfp_parser_store_byte(context, byte);
return;
2015-11-26 11:55:35 +01:00
}
2015-08-19 17:00:49 +02:00
if (hfp_parser_is_end_of_line(byte)) {
if (hfp_parser_is_buffer_empty(context)){
context->parser_state = HFP_PARSER_CMD_HEADER;
}
}
2015-08-14 17:17:04 +02:00
if (hfp_parser_is_buffer_empty(context)) return;
2015-07-30 17:03:11 +02:00
switch (context->parser_state){
case HFP_PARSER_CMD_HEADER: // header
2015-08-14 16:35:31 +02:00
if (byte == '='){
2015-11-26 22:05:31 +01:00
context->keep_byte = 1;
2015-08-14 16:35:31 +02:00
hfp_parser_store_byte(context, byte);
2015-08-14 17:17:04 +02:00
return;
2015-08-14 16:35:31 +02:00
}
if (byte == '?'){
2015-11-26 22:05:31 +01:00
context->keep_byte = 0;
2015-08-14 16:35:31 +02:00
hfp_parser_store_byte(context, byte);
2015-08-14 17:17:04 +02:00
return;
2015-08-13 20:55:21 +02:00
}
2015-11-26 22:05:31 +01:00
if (byte == ','){
context->resolve_byte = 1;
}
// printf(" parse header 2 %s, keep separator $ %d\n", context->line_buffer, context->keep_byte);
if (hfp_parser_is_end_of_header(byte) || context->keep_byte == 1){
// printf(" parse header 3 %s, keep separator $ %d\n", context->line_buffer, context->keep_byte);
2015-11-18 21:39:08 +01:00
char * line_buffer = (char *)context->line_buffer;
context->command = parse_command(line_buffer, isHandsFree);
/* resolve command name according to context */
if (context->command == HFP_CMD_UNKNOWN){
switch(context->state){
case HFP_W4_LIST_GENERIC_STATUS_INDICATORS:
context->command = HFP_CMD_LIST_GENERIC_STATUS_INDICATORS;
break;
case HFP_W4_RETRIEVE_GENERIC_STATUS_INDICATORS:
context->command = HFP_CMD_RETRIEVE_GENERIC_STATUS_INDICATORS;
break;
case HFP_W4_RETRIEVE_INITITAL_STATE_GENERIC_STATUS_INDICATORS:
context->command = HFP_CMD_RETRIEVE_GENERIC_STATUS_INDICATORS_STATE;
break;
case HFP_W4_RETRIEVE_INDICATORS_STATUS:
context->command = HFP_CMD_RETRIEVE_AG_INDICATORS_STATUS;
break;
case HFP_W4_RETRIEVE_INDICATORS:
context->send_ag_indicators_segment = 0;
2015-11-18 21:39:08 +01:00
context->command = HFP_CMD_RETRIEVE_AG_INDICATORS;
break;
default:
break;
}
}
2015-07-30 17:03:11 +02:00
}
break;
2015-11-26 11:55:35 +01:00
case HFP_PARSER_CMD_SEQUENCE:
2015-11-26 22:05:31 +01:00
parse_sequence(context);
2015-07-30 17:03:11 +02:00
break;
2015-08-14 14:19:31 +02:00
case HFP_PARSER_SECOND_ITEM:
switch (context->command){
2015-11-18 16:04:27 +01:00
case HFP_CMD_QUERY_OPERATOR_SELECTION_NAME:
2015-11-26 11:55:35 +01:00
log_info("format %s, ", context->line_buffer);
2015-11-18 16:04:27 +01:00
context->network_operator.format = atoi((char *)&context->line_buffer[0]);
2015-08-14 14:19:31 +02:00
break;
2015-11-18 16:04:27 +01:00
case HFP_CMD_QUERY_OPERATOR_SELECTION_NAME_FORMAT:
2015-11-26 11:55:35 +01:00
log_info("format %s \n", context->line_buffer);
2015-11-18 16:04:27 +01:00
context->network_operator.format = atoi((char *)&context->line_buffer[0]);
2015-08-14 14:19:31 +02:00
break;
case HFP_CMD_LIST_GENERIC_STATUS_INDICATORS:
case HFP_CMD_RETRIEVE_GENERIC_STATUS_INDICATORS:
case HFP_CMD_RETRIEVE_GENERIC_STATUS_INDICATORS_STATE:
2015-08-14 14:53:43 +02:00
context->generic_status_indicators[context->parser_item_index].state = (uint8_t)atoi((char*)context->line_buffer);
break;
case HFP_CMD_TRANSFER_AG_INDICATOR_STATUS:
context->ag_indicators[context->parser_item_index].status = (uint8_t)atoi((char*)context->line_buffer);
2015-11-26 11:55:35 +01:00
log_info("%d \n", context->ag_indicators[context->parser_item_index].status);
2015-08-14 14:53:43 +02:00
context->ag_indicators[context->parser_item_index].status_changed = 1;
break;
case HFP_CMD_RETRIEVE_AG_INDICATORS:
context->ag_indicators[context->parser_item_index].min_range = atoi((char *)context->line_buffer);
log_info("%s, ", context->line_buffer);
2015-08-14 15:12:31 +02:00
break;
case HFP_CMD_AG_SENT_PHONE_NUMBER:
context->bnip_type = (uint8_t)atoi((char*)context->line_buffer);
break;
2015-08-14 14:19:31 +02:00
default:
break;
}
break;
case HFP_PARSER_THIRD_ITEM:
2015-08-14 17:17:04 +02:00
switch (context->command){
2015-11-18 16:04:27 +01:00
case HFP_CMD_QUERY_OPERATOR_SELECTION_NAME:
strcpy(context->network_operator.name, (char *)context->line_buffer);
log_info("name %s\n", context->line_buffer);
2015-08-14 14:19:31 +02:00
break;
case HFP_CMD_RETRIEVE_AG_INDICATORS:
context->ag_indicators[context->parser_item_index].max_range = atoi((char *)context->line_buffer);
context->parser_item_index++;
context->ag_indicators_nr = context->parser_item_index;
log_info("%s)\n", context->line_buffer);
2015-08-14 15:12:31 +02:00
break;
2015-08-14 14:19:31 +02:00
default:
break;
2015-08-13 15:01:19 +02:00
}
break;
2015-07-30 17:03:11 +02:00
}
2015-08-14 17:17:04 +02:00
hfp_parser_next_state(context, byte);
2015-11-26 22:05:31 +01:00
if (context->resolve_byte && context->command == HFP_CMD_ENABLE_INDIVIDUAL_AG_INDICATOR_STATUS_UPDATE){
context->resolve_byte = 0;
context->ignore_value = 1;
parse_sequence(context);
context->line_buffer[0] = 0;
context->line_size = 0;
}
}
static void parse_sequence(hfp_connection_t * context){
int value;
switch (context->command){
2015-11-28 17:46:37 +01:00
case HFP_CMD_SET_GENERIC_STATUS_INDICATOR_STATUS:
value = atoi((char *)&context->line_buffer[0]);
int i;
switch (context->parser_item_index){
case 0:
for (i=0;i<context->generic_status_indicators_nr;i++){
if (context->generic_status_indicators[i].uuid == value){
context->parser_indicator_index = i;
break;
}
}
break;
case 1:
if (context->parser_indicator_index <0) break;
context->generic_status_indicators[context->parser_indicator_index].state = value;
log_info("HFP_CMD_SET_GENERIC_STATUS_INDICATOR_STATUS set indicator at index %u, to %u\n",
context->parser_item_index, value);
break;
default:
break;
}
context->parser_item_index++;
break;
case HFP_CMD_GET_SUBSCRIBER_NUMBER_INFORMATION:
switch(context->parser_item_index){
case 0:
strncpy(context->bnip_number, (char *)context->line_buffer, sizeof(context->bnip_number));
context->bnip_number[sizeof(context->bnip_number)-1] = 0;
break;
case 1:
value = atoi((char *)&context->line_buffer[0]);
context->bnip_type = value;
break;
default:
break;
}
context->parser_item_index++;
break;
2015-11-28 16:07:40 +01:00
case HFP_CMD_LIST_CURRENT_CALLS:
switch(context->parser_item_index){
case 0:
value = atoi((char *)&context->line_buffer[0]);
context->clcc_idx = value;
break;
case 1:
value = atoi((char *)&context->line_buffer[0]);
context->clcc_dir = value;
break;
case 2:
value = atoi((char *)&context->line_buffer[0]);
context->clcc_status = value;
break;
case 3:
value = atoi((char *)&context->line_buffer[0]);
context->clcc_mpty = value;
break;
case 4:
strncpy(context->bnip_number, (char *)context->line_buffer, sizeof(context->bnip_number));
context->bnip_number[sizeof(context->bnip_number)-1] = 0;
break;
case 5:
value = atoi((char *)&context->line_buffer[0]);
context->bnip_type = value;
break;
default:
break;
}
context->parser_item_index++;
break;
2015-11-26 22:05:31 +01:00
case HFP_CMD_SET_MICROPHONE_GAIN:
value = atoi((char *)&context->line_buffer[0]);
context->microphone_gain = value;
log_info("hfp parse HFP_CMD_SET_MICROPHONE_GAIN %d\n", value);
break;
case HFP_CMD_SET_SPEAKER_GAIN:
value = atoi((char *)&context->line_buffer[0]);
context->speaker_gain = value;
log_info("hfp parse HFP_CMD_SET_SPEAKER_GAIN %d\n", value);
break;
case HFP_CMD_HF_ACTIVATE_VOICE_RECOGNITION:
value = atoi((char *)&context->line_buffer[0]);
context->ag_activate_voice_recognition = value;
log_info("hfp parse HFP_CMD_HF_ACTIVATE_VOICE_RECOGNITION %d\n", value);
break;
case HFP_CMD_TURN_OFF_EC_AND_NR:
value = atoi((char *)&context->line_buffer[0]);
context->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 = atoi((char *)&context->line_buffer[0]);
context->remote_supported_features = store_bit(context->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:
context->codec_confirmed = atoi((char*)context->line_buffer);
log_info("hfp parse HFP_CMD_HF_CONFIRMED_CODEC %d\n", context->codec_confirmed);
break;
case HFP_CMD_AG_SUGGESTED_CODEC:
context->suggested_codec = atoi((char*)context->line_buffer);
log_info("hfp parse HFP_CMD_AG_SUGGESTED_CODEC %d\n", context->suggested_codec);
break;
case HFP_CMD_SUPPORTED_FEATURES:
context->remote_supported_features = atoi((char*)context->line_buffer);
log_info("Parsed supported feature %d\n", context->remote_supported_features);
break;
case HFP_CMD_AVAILABLE_CODECS:
log_info("Parsed codec %s\n", context->line_buffer);
context->remote_codecs[context->parser_item_index] = (uint16_t)atoi((char*)context->line_buffer);
context->parser_item_index++;
context->remote_codecs_nr = context->parser_item_index;
break;
case HFP_CMD_RETRIEVE_AG_INDICATORS:
strcpy((char *)context->ag_indicators[context->parser_item_index].name, (char *)context->line_buffer);
context->ag_indicators[context->parser_item_index].index = context->parser_item_index+1;
log_info("Indicator %d: %s (", context->ag_indicators_nr+1, context->line_buffer);
break;
case HFP_CMD_RETRIEVE_AG_INDICATORS_STATUS:
log_info("Parsed Indicator %d with status: %s\n", context->parser_item_index+1, context->line_buffer);
context->ag_indicators[context->parser_item_index].status = atoi((char *) context->line_buffer);
context->parser_item_index++;
break;
case HFP_CMD_ENABLE_INDICATOR_STATUS_UPDATE:
context->parser_item_index++;
if (context->parser_item_index != 4) break;
log_info("Parsed Enable indicators: %s\n", context->line_buffer);
value = atoi((char *)&context->line_buffer[0]);
context->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", context->line_buffer);
if (context->line_size > 2 ) break;
strcpy((char *)context->remote_call_services[context->remote_call_services_nr].name, (char *)context->line_buffer);
context->remote_call_services_nr++;
break;
case HFP_CMD_LIST_GENERIC_STATUS_INDICATORS:
case HFP_CMD_RETRIEVE_GENERIC_STATUS_INDICATORS:
log_info("Parsed Generic status indicator: %s\n", context->line_buffer);
context->generic_status_indicators[context->parser_item_index].uuid = (uint16_t)atoi((char*)context->line_buffer);
context->parser_item_index++;
context->generic_status_indicators_nr = context->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: ", context->line_buffer);
context->parser_item_index = (uint8_t)atoi((char*)context->line_buffer);
break;
case HFP_CMD_HF_INDICATOR_STATUS:
context->parser_indicator_index = (uint8_t)atoi((char*)context->line_buffer);
log_info("Parsed HF indicator index %u", context->parser_indicator_index);
break;
2015-11-26 22:05:31 +01:00
case HFP_CMD_ENABLE_INDIVIDUAL_AG_INDICATOR_STATUS_UPDATE:
// AG parses new gen. ind. state
if (context->ignore_value){
context->ignore_value = 0;
log_info("Parsed Enable AG indicator pos %u('%s') - unchanged (stays %u)\n", context->parser_item_index,
context->ag_indicators[context->parser_item_index].name, context->ag_indicators[context->parser_item_index].enabled);
2015-11-26 22:05:31 +01:00
}
else if (context->ag_indicators[context->parser_item_index].mandatory){
log_info("Parsed Enable AG indicator pos %u('%s') - ignore (mandatory)\n",
context->parser_item_index, context->ag_indicators[context->parser_item_index].name);
} else {
value = atoi((char *)&context->line_buffer[0]);
context->ag_indicators[context->parser_item_index].enabled = value;
log_info("Parsed Enable AG indicator pos %u('%s'): %u\n", context->parser_item_index,
context->ag_indicators[context->parser_item_index].name, value);
}
context->parser_item_index++;
break;
case HFP_CMD_TRANSFER_AG_INDICATOR_STATUS:
// indicators are indexed starting with 1
context->parser_item_index = atoi((char *)&context->line_buffer[0]) - 1;
log_info("Parsed status of the AG indicator %d, status ", context->parser_item_index);
break;
case HFP_CMD_QUERY_OPERATOR_SELECTION_NAME:
context->network_operator.mode = atoi((char *)&context->line_buffer[0]);
log_info("Parsed network operator mode: %d, ", context->network_operator.mode);
break;
case HFP_CMD_QUERY_OPERATOR_SELECTION_NAME_FORMAT:
if (context->line_buffer[0] == '3'){
log_info("Parsed Set network operator format : %s, ", context->line_buffer);
break;
}
// TODO emit ERROR, wrong format
log_info("ERROR Set network operator format: index %s not supported\n", context->line_buffer);
break;
case HFP_CMD_ERROR:
break;
case HFP_CMD_EXTENDED_AUDIO_GATEWAY_ERROR:
context->extended_audio_gateway_error = (uint8_t)atoi((char*)context->line_buffer);
break;
case HFP_CMD_ENABLE_EXTENDED_AUDIO_GATEWAY_ERROR:
context->enable_extended_audio_gateway_error_report = (uint8_t)atoi((char*)context->line_buffer);
context->ok_pending = 1;
context->extended_audio_gateway_error = 0;
break;
case HFP_CMD_AG_SENT_PHONE_NUMBER:
strncpy(context->bnip_number, (char *)context->line_buffer, sizeof(context->bnip_number));
context->bnip_number[sizeof(context->bnip_number)-1] = 0;
break;
2015-11-26 22:05:31 +01:00
default:
break;
}
2015-07-30 17:03:11 +02:00
}
2015-07-09 15:28:06 +02:00
void hfp_init(uint16_t rfcomm_channel_nr){
rfcomm_register_service(rfcomm_channel_nr, 0xffff);
2015-07-09 15:28:06 +02:00
sdp_query_rfcomm_register_callback(handle_query_rfcomm_event, NULL);
}
void hfp_establish_service_level_connection(bd_addr_t bd_addr, uint16_t service_uuid){
2015-08-05 17:05:16 +02:00
hfp_connection_t * context = provide_hfp_connection_context_for_bd_addr(bd_addr);
log_info("hfp_connect %s, context %p", bd_addr_to_str(bd_addr), context);
2015-08-05 17:05:16 +02:00
if (!context) {
2015-08-07 21:45:00 +02:00
log_error("hfp_establish_service_level_connection for addr %s failed", bd_addr_to_str(bd_addr));
2015-07-09 15:28:06 +02:00
return;
}
2015-11-13 17:13:48 +01:00
2015-08-12 23:14:23 +02:00
switch (context->state){
case HFP_W2_DISCONNECT_RFCOMM:
context->state = HFP_SERVICE_LEVEL_CONNECTION_ESTABLISHED;
return;
case HFP_W4_RFCOMM_DISCONNECTED:
context->state = HFP_W4_RFCOMM_DISCONNECTED_AND_RESTART;
return;
case HFP_IDLE:
memcpy(context->remote_addr, bd_addr, 6);
2016-01-30 23:58:36 +01:00
context->state = HFP_W4_SDP_EVENT_QUERY_COMPLETE;
2015-08-12 23:14:23 +02:00
connection_doing_sdp_query = context;
context->service_uuid = service_uuid;
sdp_query_rfcomm_channel_and_name_for_uuid(context->remote_addr, service_uuid);
break;
default:
break;
}
2015-08-05 17:05:16 +02:00
}
2015-08-07 21:45:00 +02:00
void hfp_release_service_level_connection(hfp_connection_t * context){
2015-08-21 17:29:10 +02:00
if (!context) return;
2015-08-27 00:08:33 +02:00
if (context->state < HFP_W4_RFCOMM_CONNECTED){
context->state = HFP_IDLE;
return;
2015-08-05 17:05:16 +02:00
}
2015-08-27 00:08:33 +02:00
if (context->state == HFP_W4_RFCOMM_CONNECTED){
context->state = HFP_W4_CONNECTION_ESTABLISHED_TO_SHUTDOWN;
return;
}
2015-11-11 15:45:35 +01:00
if (context->state < HFP_W4_SCO_CONNECTED){
2015-08-27 00:08:33 +02:00
context->state = HFP_W2_DISCONNECT_RFCOMM;
return;
}
if (context->state < HFP_W4_SCO_DISCONNECTED){
context->state = HFP_W2_DISCONNECT_SCO;
return;
}
2015-08-07 21:45:00 +02:00
return;
2015-07-09 17:03:16 +02:00
}
2015-11-12 12:13:43 +01:00
void hfp_release_audio_connection(hfp_connection_t * context){
if (!context) return;
if (context->state >= HFP_W2_DISCONNECT_SCO) return;
context->release_audio_connection = 1;
2015-08-21 17:29:10 +02:00
}
2015-07-30 13:16:53 +02:00
static const struct link_settings {
const uint16_t max_latency;
const uint8_t retransmission_effort;
const uint16_t packet_types;
} hfp_link_settings [] = {
{ 0xffff, 0xff, 0x03c1 }, // HFP_LINK_SETTINGS_D0, HV1
{ 0xffff, 0xff, 0x03c4 }, // HFP_LINK_SETTINGS_D1, HV3
{ 0x0007, 0x01, 0x03c8 }, // HFP_LINK_SETTINGS_S1, EV3
{ 0x0007, 0x01, 0x0380 }, // HFP_LINK_SETTINGS_S2, 2-EV3
{ 0x000a, 0x01, 0x0380 }, // HFP_LINK_SETTINGS_S3, 2-EV3
{ 0x000c, 0x02, 0x0380 }, // HFP_LINK_SETTINGS_S4, 2-EV3
{ 0x0008, 0x02, 0x03c8 }, // HFP_LINK_SETTINGS_T1, EV3
{ 0x000d, 0x02, 0x0380 } // HFP_LINK_SETTINGS_T2, 2-EV3
};
void hfp_setup_synchronous_connection(hci_con_handle_t handle, hfp_link_setttings_t setting){
// all packet types, fixed bandwidth
log_info("hfp_setup_synchronous_connection using setting nr %u", setting);
hci_send_cmd(&hci_setup_synchronous_connection, handle, 8000, 8000, hfp_link_settings[setting].max_latency,
hci_get_sco_voice_setting(), hfp_link_settings[setting].retransmission_effort, hfp_link_settings[setting].packet_types); // all types 0x003f, only 2-ev3 0x380
}