diff --git a/src/btstack_defines.h b/src/btstack_defines.h index f36d9c95c..672e48999 100644 --- a/src/btstack_defines.h +++ b/src/btstack_defines.h @@ -689,7 +689,14 @@ #define HFP_SUBEVENT_PLACE_CALL_WITH_NUMBER 0x0D #define HFP_SUBEVENT_REDIAL_LAST_NUMBER 0x0E #define HFP_SUBEVENT_ATTACH_NUMBER_TO_VOICE_TAG 0x0F -#define HFP_SUBEVENT_TRANSMIT_DTMF_CODES 0x10 +#define HFP_SUBEVENT_NUMBER_FOR_VOICE_TAG 0x10 +#define HFP_SUBEVENT_TRANSMIT_DTMF_CODES 0x11 +#define HFP_SUBEVENT_TRANSMIT_STATUS_OF_CURRENT_CALL 0x12 +#define HFP_SUBEVENT_CALL_ANSWERED 0x13 +#define HFP_SUBEVENT_CONFERENCE_CALL 0x14 +#define HFP_SUBEVENT_RING 0x15 +#define HFP_SUBEVENT_SPEAKER_VOLUME 0x16 +#define HFP_SUBEVENT_MICROPHONE_VOLUME 0x17 // ANCS Client #define ANCS_CLIENT_CONNECTED 0xF0 diff --git a/src/classic/hfp.c b/src/classic/hfp.c index e4425a983..e51e75731 100644 --- a/src/classic/hfp.c +++ b/src/classic/hfp.c @@ -101,6 +101,7 @@ static int hfp_generic_status_indicators_nr = 0; static hfp_generic_status_indicator_t hfp_generic_status_indicators[HFP_MAX_NUM_HF_INDICATORS]; static linked_list_t hfp_connections = NULL; +static void parse_sequence(hfp_connection_t * context); hfp_generic_status_indicator_t * get_hfp_generic_status_indicators(void){ return (hfp_generic_status_indicator_t *) &hfp_generic_status_indicators; @@ -272,7 +273,7 @@ void hfp_reset_context_flags(hfp_connection_t * context){ context->ok_pending = 0; context->send_error = 0; - context->keep_separator = 0; + context->keep_byte = 0; context->change_status_update_for_individual_ag_indicators = 0; context->operator_name_changed = 0; @@ -515,12 +516,38 @@ void hfp_handle_hci_event(hfp_callback_t callback, uint8_t packet_type, uint8_t break; case HCI_EVENT_SYNCHRONOUS_CONNECTION_COMPLETE:{ + bt_flip_addr(event_addr, &packet[5]); int index = 2; uint8_t status = packet[index++]; if (status != 0){ - log_error("(e)SCO Connection is not established, status %u", status); + 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; break; } @@ -612,7 +639,13 @@ void hfp_handle_hci_event(hfp_callback_t callback, uint8_t packet_type, uint8_t } break; - default: + 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); break; } } @@ -621,8 +654,16 @@ void hfp_handle_hci_event(hfp_callback_t callback, uint8_t packet_type, uint8_t static hfp_command_t parse_command(const char * line_buffer, int isHandsFree){ int offset = isHandsFree ? 0 : 2; + 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_SEND_PHONE_NUMBER; + if (isHandsFree) return HFP_CMD_AG_SENT_PHONE_NUMBER; return HFP_CMD_HF_REQUEST_PHONE_NUMBER; } @@ -655,7 +696,7 @@ static hfp_command_t parse_command(const char * line_buffer, int isHandsFree){ return HFP_CMD_CALL_PHONE_NUMBER; } - if (strncmp(line_buffer, HFP_REDIAL_LAST_NUMBER, strlen(HFP_REDIAL_LAST_NUMBER)) == 0){ + if (strncmp(line_buffer+offset, HFP_REDIAL_LAST_NUMBER, strlen(HFP_REDIAL_LAST_NUMBER)) == 0){ return HFP_CMD_REDIAL_LAST_NUMBER; } @@ -671,6 +712,10 @@ static hfp_command_t parse_command(const char * line_buffer, int isHandsFree){ return HFP_CMD_ERROR; } + if (strncmp(line_buffer+offset, HFP_RING, strlen(HFP_RING)) == 0){ + return HFP_CMD_RING; + } + if (isHandsFree && strncmp(line_buffer+offset, HFP_OK, strlen(HFP_OK)) == 0){ return HFP_CMD_OK; } @@ -679,6 +724,20 @@ static hfp_command_t parse_command(const char * line_buffer, int isHandsFree){ 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; + } + } + if (strncmp(line_buffer+offset, HFP_INDICATOR, strlen(HFP_INDICATOR)) == 0){ if (strncmp(line_buffer+strlen(HFP_INDICATOR)+offset, "?", 1) == 0){ return HFP_CMD_RETRIEVE_AG_INDICATORS_STATUS; @@ -784,13 +843,8 @@ static hfp_command_t parse_command(const char * line_buffer, int isHandsFree){ return HFP_CMD_NONE; } -#if 0 -uint32_t fromBinary(char *s) { - return (uint32_t) strtol(s, NULL, 2); -} -#endif - 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); // TODO: add limit context->line_buffer[context->line_size++] = byte; context->line_buffer[context->line_size] = 0; @@ -808,7 +862,7 @@ static int hfp_parser_is_end_of_header(uint8_t byte){ } static int hfp_parser_found_separator(hfp_connection_t * context, uint8_t byte){ - if (context->keep_separator == 1) return 1; + if (context->keep_byte == 1) return 1; int found_separator = byte == ',' || byte == '\n'|| byte == '\r'|| byte == ')' || byte == '(' || byte == ':' || @@ -826,22 +880,20 @@ static void hfp_parser_next_state(hfp_connection_t * context, uint8_t byte){ switch (context->parser_state){ case HFP_PARSER_CMD_HEADER: context->parser_state = HFP_PARSER_CMD_SEQUENCE; - if (context->keep_separator == 1){ + if (context->keep_byte == 1){ hfp_parser_store_byte(context, byte); - context->keep_separator = 0; + context->keep_byte = 0; } break; case HFP_PARSER_CMD_SEQUENCE: switch (context->command){ + case HFP_CMD_AG_SENT_PHONE_NUMBER: case HFP_CMD_TRANSFER_AG_INDICATOR_STATUS: case HFP_CMD_QUERY_OPERATOR_SELECTION_NAME: case HFP_CMD_QUERY_OPERATOR_SELECTION_NAME_FORMAT: - context->parser_state = HFP_PARSER_SECOND_ITEM; - break; case HFP_CMD_RETRIEVE_AG_INDICATORS: - context->parser_state = HFP_PARSER_SECOND_ITEM; - break; case HFP_CMD_RETRIEVE_GENERIC_STATUS_INDICATORS_STATE: + case HFP_CMD_HF_INDICATOR_STATUS: context->parser_state = HFP_PARSER_SECOND_ITEM; break; default: @@ -862,8 +914,6 @@ static void hfp_parser_next_state(hfp_connection_t * context, uint8_t byte){ } void hfp_parse(hfp_connection_t * context, uint8_t byte, int isHandsFree){ - int value; - // handle ATD; if (strncmp((const char*)context->line_buffer, HFP_CALL_PHONE_NUMBER, strlen(HFP_CALL_PHONE_NUMBER)) == 0){ // check for end-of-line or ';' @@ -879,11 +929,21 @@ void hfp_parse(hfp_connection_t * context, uint8_t byte, int isHandsFree){ // TODO: handle space inside word if (byte == ' ' && context->parser_state > HFP_PARSER_CMD_HEADER) return; + + 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; + } + } if (!hfp_parser_found_separator(context, byte)){ hfp_parser_store_byte(context, byte); return; - } + } + if (hfp_parser_is_end_of_line(byte)) { if (hfp_parser_is_buffer_empty(context)){ context->parser_state = HFP_PARSER_CMD_HEADER; @@ -891,23 +951,27 @@ void hfp_parse(hfp_connection_t * context, uint8_t byte, int isHandsFree){ } if (hfp_parser_is_buffer_empty(context)) return; - switch (context->parser_state){ case HFP_PARSER_CMD_HEADER: // header if (byte == '='){ - context->keep_separator = 1; + context->keep_byte = 1; hfp_parser_store_byte(context, byte); return; } if (byte == '?'){ - context->keep_separator = 0; + context->keep_byte = 0; hfp_parser_store_byte(context, byte); return; } - // printf(" parse header 2 %s, keep separator $ %d\n", context->line_buffer, context->keep_separator); - if (hfp_parser_is_end_of_header(byte) || context->keep_separator == 1){ - // printf(" parse header 3 %s, keep separator $ %d\n", context->line_buffer, context->keep_separator); + + 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); char * line_buffer = (char *)context->line_buffer; context->command = parse_command(line_buffer, isHandsFree); @@ -936,135 +1000,17 @@ void hfp_parse(hfp_connection_t * context, uint8_t byte, int isHandsFree){ } break; - case HFP_PARSER_CMD_SEQUENCE: // parse comma separated sequence, ignore breacktes - switch (context->command){ - 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_ENABLE_INDIVIDUAL_AG_INDICATOR_STATUS_UPDATE: - // AG parses new gen. ind. state - log_info("Parsed Enable ag indicator state: %s\n", context->line_buffer); - value = atoi((char *)&context->line_buffer[0]); - if (!context->ag_indicators[context->parser_item_index].mandatory){ - context->ag_indicators[context->parser_item_index].enabled = 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; - default: - break; - } + case HFP_PARSER_CMD_SEQUENCE: + parse_sequence(context); break; - case HFP_PARSER_SECOND_ITEM: switch (context->command){ case HFP_CMD_QUERY_OPERATOR_SELECTION_NAME: - printf("format %s, ", context->line_buffer); + log_info("format %s, ", context->line_buffer); context->network_operator.format = atoi((char *)&context->line_buffer[0]); break; case HFP_CMD_QUERY_OPERATOR_SELECTION_NAME_FORMAT: - printf("format %s \n", context->line_buffer); + log_info("format %s \n", context->line_buffer); context->network_operator.format = atoi((char *)&context->line_buffer[0]); break; case HFP_CMD_LIST_GENERIC_STATUS_INDICATORS: @@ -1074,13 +1020,16 @@ void hfp_parse(hfp_connection_t * context, uint8_t byte, int isHandsFree){ break; case HFP_CMD_TRANSFER_AG_INDICATOR_STATUS: context->ag_indicators[context->parser_item_index].status = (uint8_t)atoi((char*)context->line_buffer); - printf("%d \n", context->ag_indicators[context->parser_item_index].status); + log_info("%d \n", context->ag_indicators[context->parser_item_index].status); 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); break; + case HFP_CMD_AG_SENT_PHONE_NUMBER: + context->bnip_type = (uint8_t)atoi((char*)context->line_buffer); + break; default: break; } @@ -1104,6 +1053,153 @@ void hfp_parse(hfp_connection_t * context, uint8_t byte, int isHandsFree){ break; } hfp_parser_next_state(context, byte); + + 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){ + 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; + 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); + } + 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; + default: + break; + } } void hfp_init(uint16_t rfcomm_channel_nr){ @@ -1171,4 +1267,24 @@ void hfp_release_audio_connection(hfp_connection_t * context){ context->release_audio_connection = 1; } +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 +} diff --git a/src/classic/hfp.h b/src/classic/hfp.h index 981895834..7034c3cf9 100644 --- a/src/classic/hfp.h +++ b/src/classic/hfp.h @@ -71,7 +71,7 @@ extern "C" { #define HFP_HFSF_VOICE_RECOGNITION_FUNCTION 3 #define HFP_HFSF_CODEC_NEGOTIATION 7 #define HFP_HFSF_HF_INDICATORS 8 -#define HFP_HFSF_ESCO 9 +#define HFP_HFSF_ESCO_S4 9 /* AG Supported Features: 0: Three-way calling @@ -94,7 +94,7 @@ extern "C" { #define HFP_AGSF_IN_BAND_RING_TONE 3 #define HFP_AGSF_CODEC_NEGOTIATION 9 #define HFP_AGSF_HF_INDICATORS 10 -#define HFP_AGSF_ESCO 11 +#define HFP_AGSF_ESCO_S4 11 #define HFP_DEFAULT_HF_SUPPORTED_FEATURES 0x0000 #define HFP_DEFAULT_AG_SUPPORTED_FEATURES 0x0009 @@ -114,6 +114,7 @@ extern "C" { #define HFP_SUPPORT_CALL_HOLD_AND_MULTIPARTY_SERVICES "+CHLD" #define HFP_GENERIC_STATUS_INDICATOR "+BIND" #define HFP_TRANSFER_AG_INDICATOR_STATUS "+CIEV" // +CIEV: , +#define HFP_TRANSFER_HF_INDICATOR_STATUS "+BIEV" // +BIEC: , #define HFP_QUERY_OPERATOR_SELECTION "+COPS" // +COPS: ,0, #define HFP_ENABLE_EXTENDED_AUDIO_GATEWAY_ERROR "+CMEE" #define HFP_EXTENDED_AUDIO_GATEWAY_ERROR "+CME ERROR" @@ -123,18 +124,20 @@ extern "C" { #define HFP_HANG_UP_CALL "+CHUP" #define HFP_CHANGE_IN_BAND_RING_TONE_SETTING "+BSIR" #define HFP_CALL_PHONE_NUMBER "ATD" -#define HFP_REDIAL_LAST_NUMBER "AT+BLDN" +#define HFP_REDIAL_LAST_NUMBER "+BLDN" #define HFP_TURN_OFF_EC_AND_NR "+NREC" // EC (Echo CAnceling), NR (Noise Reduction) #define HFP_ACTIVATE_VOICE_RECOGNITION "+BVRA" // EC (Echo CAnceling), NR (Noise Reduction) #define HFP_SET_MICROPHONE_GAIN "+VGM" #define HFP_SET_SPEAKER_GAIN "+VGS" - #define HFP_PHONE_NUMBER_FOR_VOICE_TAG "+BINP" #define HFP_TRANSMIT_DTMF_CODES "+VTS" - +#define HFP_SUBSCRIBER_NUMBER_INFORMATION "+CNUM" +#define HFP_LIST_CURRENT_CALLS "+CLCC" +#define HFP_RESPONSE_AND_HOLD "+BTRH" #define HFP_OK "OK" #define HFP_ERROR "ERROR" +#define HFP_RING "RING" // Codecs #define HFP_CODEC_CVSD 0x01 @@ -145,6 +148,7 @@ typedef enum { HFP_CMD_ERROR, HFP_CMD_UNKNOWN, HFP_CMD_OK, + HFP_CMD_RING, HFP_CMD_SUPPORTED_FEATURES, HFP_CMD_AVAILABLE_CODECS, @@ -183,10 +187,15 @@ typedef enum { HFP_CMD_AG_ACTIVATE_VOICE_RECOGNITION, HFP_CMD_HF_ACTIVATE_VOICE_RECOGNITION, HFP_CMD_HF_REQUEST_PHONE_NUMBER, - HFP_CMD_AG_SEND_PHONE_NUMBER, + HFP_CMD_AG_SENT_PHONE_NUMBER, HFP_CMD_TRANSMIT_DTMF_CODES, HFP_CMD_SET_MICROPHONE_GAIN, - HFP_CMD_SET_SPEAKER_GAIN + HFP_CMD_SET_SPEAKER_GAIN, + HFP_CMD_GET_SUBSCRIBER_NUMBER_INFORMATION, + HFP_CMD_LIST_CURRENT_CALLS, + HFP_CMD_RESPONSE_AND_HOLD_QUERY, + HFP_CMD_RESPONSE_AND_HOLD_COMMAND, + HFP_CMD_HF_INDICATOR_STATUS } hfp_command_t; @@ -255,9 +264,16 @@ typedef enum { HFP_AG_OUTGOING_CALL_RINGING, HFP_AG_OUTGOING_CALL_ESTABLISHED, HFP_AG_OUTGOING_REDIAL_INITIATED, + HFP_AG_HELD_CALL_JOINED_BY_AG, HFP_AG_TERMINATE_CALL_BY_AG, HFP_AG_TERMINATE_CALL_BY_HF, HFP_AG_CALL_DROPPED, + HFP_AG_RESPONSE_AND_HOLD_ACCEPT_INCOMING_CALL_BY_AG, + HFP_AG_RESPONSE_AND_HOLD_ACCEPT_HELD_CALL_BY_AG, + HFP_AG_RESPONSE_AND_HOLD_REJECT_HELD_CALL_BY_AG, + HFP_AG_RESPONSE_AND_HOLD_ACCEPT_INCOMING_CALL_BY_HF, + HFP_AG_RESPONSE_AND_HOLD_ACCEPT_HELD_CALL_BY_HF, + HFP_AG_RESPONSE_AND_HOLD_REJECT_HELD_CALL_BY_HF, } hfp_ag_call_event_t; @@ -325,6 +341,7 @@ typedef enum { HFP_CODECS_W4_AG_COMMON_CODEC, HFP_CODECS_AG_SENT_COMMON_CODEC, HFP_CODECS_AG_RESEND_COMMON_CODEC, + HFP_CODECS_HF_CONFIRMED_CODEC, HFP_CODECS_EXCHANGED, HFP_CODECS_ERROR } hfp_codecs_state_t; @@ -343,6 +360,58 @@ typedef enum { HFP_CALL_OUTGOING_RINGING } hfp_call_state_t; +typedef enum{ + HFP_ENHANCED_CALL_DIR_OUTGOING, + HFP_ENHANCED_CALL_DIR_INCOMING +} hfp_enhanced_call_dir_t; + +typedef enum{ + HFP_ENHANCED_CALL_STATUS_ACTIVE, + HFP_ENHANCED_CALL_STATUS_HELD, + HFP_ENHANCED_CALL_STATUS_OUTGOING_DIALING, + HFP_ENHANCED_CALL_STATUS_OUTGOING_ALERTING, + HFP_ENHANCED_CALL_STATUS_INCOMING, + HFP_ENHANCED_CALL_STATUS_INCOMING_WAITING, + HFP_ENHANCED_CALL_STATUS_CALL_HELD_BY_RESPONSE_AND_HOLD +} hfp_enhanced_call_status_t; + +typedef enum{ + HFP_ENHANCED_CALL_MODE_VOICE, + HFP_ENHANCED_CALL_MODE_DATA, + HFP_ENHANCED_CALL_MODE_FAX +} hfp_enhanced_call_mode_t; + +typedef enum{ + HFP_ENHANCED_CALL_MPTY_NOT_A_CONFERENCE_CALL, + HFP_ENHANCED_CALL_MPTY_CONFERENCE_CALL +} hfp_enhanced_call_mpty_t; + +typedef enum { + HFP_RESPONSE_AND_HOLD_INCOMING_ON_HOLD = 0, + HFP_RESPONSE_AND_HOLD_HELD_INCOMING_ACCEPTED, + HFP_RESPONSE_AND_HOLD_HELD_INCOMING_REJECTED +} hfp_response_and_hold_state_t; + +typedef enum { + HFP_HF_QUERY_OPERATOR_FORMAT_NOT_SET = 0, + HFP_HF_QUERY_OPERATOR_SET_FORMAT, + HFP_HF_QUERY_OPERATOR_W4_SET_FORMAT_OK, + HFP_HF_QUERY_OPERATOR_FORMAT_SET, + HFP_HF_QUERY_OPERATOR_SEND_QUERY, + HPF_HF_QUERY_OPERATOR_W4_RESULT +} hfp_hf_query_operator_state_t; + +typedef enum { + HFP_LINK_SETTINGS_D0 = 0, + HFP_LINK_SETTINGS_D1, + HFP_LINK_SETTINGS_S1, + HFP_LINK_SETTINGS_S2, + HFP_LINK_SETTINGS_S3, + HFP_LINK_SETTINGS_S4, + HFP_LINK_SETTINGS_T1, + HFP_LINK_SETTINGS_T2 +} hfp_link_setttings_t; + typedef enum{ HFP_NONE_SM, HFP_SLC_SM, @@ -403,6 +472,7 @@ typedef struct hfp_connection { hfp_command_t command; hfp_parser_state_t parser_state; int parser_item_index; + int parser_indicator_index; uint8_t line_buffer[HFP_MAX_INDICATOR_DESC_SIZE]; int line_size; @@ -436,7 +506,9 @@ typedef struct hfp_connection { // uint8_t send_ok; uint8_t send_error; - uint8_t keep_separator; + uint8_t keep_byte; + uint8_t ignore_value; + uint8_t resolve_byte; uint8_t change_status_update_for_individual_ag_indicators; uint8_t operator_name_changed; @@ -448,14 +520,12 @@ typedef struct hfp_connection { uint8_t suggested_codec; uint8_t codec_confirmed; + hfp_link_setttings_t link_setting; + uint8_t establish_audio_connection; uint8_t release_audio_connection; - uint8_t change_in_band_ring_tone_setting; - uint8_t ag_ring; - uint8_t ag_send_clip; - uint8_t ag_echo_and_noise_reduction; - uint8_t ag_activate_voice_recognition; + timer_source_t hfp_timeout; uint8_t microphone_gain; uint8_t send_microphone_gain; @@ -464,7 +534,50 @@ typedef struct hfp_connection { uint8_t send_speaker_gain; uint8_t send_phone_number_for_voice_tag; - timer_source_t hfp_timeout; + uint8_t send_ag_status_indicators; + uint8_t send_response_and_hold_active; + uint8_t send_response_and_hold_status; + + // AG only + uint8_t change_in_band_ring_tone_setting; + uint8_t ag_ring; + uint8_t ag_send_clip; + uint8_t ag_echo_and_noise_reduction; + uint8_t ag_activate_voice_recognition; + uint8_t send_subscriber_number; + uint8_t next_subscriber_number_to_send; + + int send_status_of_current_calls; + + // HF only + hfp_hf_query_operator_state_t hf_query_operator_state; + uint8_t hf_answer_incoming_call; + uint8_t hf_initiate_outgoing_call; + uint8_t hf_initiate_memory_dialing; + uint8_t hf_initiate_redial_last_number; + + uint8_t hf_send_clip_enable; + uint8_t hf_send_chup; + uint8_t hf_send_chld_0; + uint8_t hf_send_chld_1; + uint8_t hf_send_chld_2; + uint8_t hf_send_chld_3; + uint8_t hf_send_chld_4; + char hf_send_dtmf_code; + uint8_t hf_send_binp; + uint8_t hf_activate_call_waiting_notification; + uint8_t hf_deactivate_call_waiting_notification; + + uint8_t hf_activate_calling_line_notification; + uint8_t hf_deactivate_calling_line_notification; + uint8_t hf_activate_echo_canceling_and_noise_reduction; + uint8_t hf_deactivate_echo_canceling_and_noise_reduction; + uint8_t hf_activate_voice_recognition_notification; + uint8_t hf_deactivate_voice_recognition_notification; + + uint8_t bnip_type; // 0 == not set + char bnip_number[25]; // + } hfp_connection_t; // UTILS_START : TODO move to utils @@ -498,6 +611,8 @@ void hfp_reset_context_flags(hfp_connection_t * context); void hfp_release_audio_connection(hfp_connection_t * context); +void hfp_setup_synchronous_connection(hci_con_handle_t handle, hfp_link_setttings_t link_settings); + const char * hfp_hf_feature(int index); const char * hfp_ag_feature(int index); diff --git a/src/classic/hfp_ag.c b/src/classic/hfp_ag.c index 4d91db8fd..79e8d286d 100644 --- a/src/classic/hfp_ag.c +++ b/src/classic/hfp_ag.c @@ -79,13 +79,21 @@ static hfp_callback_t hfp_callback; static hfp_call_status_t hfp_ag_call_state; static hfp_callsetup_status_t hfp_ag_callsetup_state; static hfp_callheld_status_t hfp_ag_callheld_state; +static hfp_response_and_hold_state_t hfp_ag_response_and_hold_state; +static int hfp_ag_response_and_hold_active = 0; // CLIP feature static uint8_t clip_type; // 0 == not set static char clip_number[25]; // +// Subcriber information entries +static hfp_phone_number_t * subscriber_numbers = NULL; +static int subscriber_numbers_count = 0; + +static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size); static void hfp_run_for_context(hfp_connection_t *context); static void hfp_ag_setup_audio_connection(hfp_connection_t * connection); +static void hfp_ag_hf_start_ringing(hfp_connection_t * context); hfp_generic_status_indicator_t * get_hfp_generic_status_indicators(); int get_hfp_generic_status_indicators_nr(); @@ -94,7 +102,6 @@ void set_hfp_ag_indicators(hfp_ag_indicator_t * indicators, int indicator_nr); int get_hfp_ag_indicators_nr(hfp_connection_t * context); hfp_ag_indicator_t * get_hfp_ag_indicators(hfp_connection_t * context); - hfp_ag_indicator_t * get_hfp_ag_indicators(hfp_connection_t * context){ // TODO: save only value, and value changed in the context? if (context->ag_indicators_nr != hfp_ag_indicators_nr){ @@ -216,12 +223,17 @@ static int hfp_ag_send_clip(uint16_t cid){ return send_str_over_rfcomm(cid, buffer); } -static int hfp_ag_send_phone_number_for_voice_tag_cmd(uint16_t cid){ +static int hfp_send_subscriber_number_cmd(uint16_t cid, uint8_t type, const char * number){ char buffer[50]; - sprintf(buffer, "\r\n%s:%s\r\n", HFP_PHONE_NUMBER_FOR_VOICE_TAG, clip_number); + sprintf(buffer, "\r\n%s: ,\"%s\",%u, , \r\n", HFP_SUBSCRIBER_NUMBER_INFORMATION, number, type); + return send_str_over_rfcomm(cid, buffer); +} + +static int hfp_ag_send_phone_number_for_voice_tag_cmd(uint16_t cid){ + char buffer[50]; + sprintf(buffer, "\r\n%s: %s\r\n", HFP_PHONE_NUMBER_FOR_VOICE_TAG, clip_number); return send_str_over_rfcomm(cid, buffer); } - static int hfp_ag_send_call_waiting_notification(uint16_t cid){ if (!clip_type){ @@ -422,6 +434,12 @@ static int hfp_ag_set_microphone_gain_cmd(uint16_t cid, uint8_t gain){ return send_str_over_rfcomm(cid, buffer); } +static int hfp_ag_set_response_and_hold(uint16_t cid, int state){ + char buffer[30]; + sprintf(buffer, "\r\n%s: %d\r\n", HFP_RESPONSE_AND_HOLD, state); + return send_str_over_rfcomm(cid, buffer); +} + static uint8_t hfp_ag_suggest_codec(hfp_connection_t *context){ int i,j; @@ -510,14 +528,33 @@ static int codecs_exchange_state_machine(hfp_connection_t * context){ return 0; } +static void hfp_init_link_settings(hfp_connection_t * context){ + // determine highest possible link setting + context->link_setting = HFP_LINK_SETTINGS_D1; + if (hci_remote_eSCO_supported(context->con_handle)){ + context->link_setting = HFP_LINK_SETTINGS_S3; + if ((context->remote_supported_features & (1<link_setting = HFP_LINK_SETTINGS_S4; + } + } +} + static void hfp_ag_slc_established(hfp_connection_t * context){ context->state = HFP_SERVICE_LEVEL_CONNECTION_ESTABLISHED; hfp_emit_event(hfp_callback, HFP_SUBEVENT_SERVICE_LEVEL_CONNECTION_ESTABLISHED, 0); + hfp_init_link_settings(context); + // if active call exist, set per-connection state active, too (when audio is on) if (hfp_ag_call_state == HFP_CALL_STATUS_ACTIVE_OR_HELD_CALL_IS_PRESENT){ context->call_state = HFP_CALL_W4_AUDIO_CONNECTION_FOR_ACTIVE; } + // if AG is ringing, also start ringing on the HF + if (hfp_ag_call_state == HFP_CALL_STATUS_NO_HELD_OR_ACTIVE_CALLS && + hfp_ag_callsetup_state == HFP_CALLSETUP_STATUS_INCOMING_CALL_SETUP_IN_PROGRESS){ + hfp_ag_hf_start_ringing(context); + } } static int hfp_ag_run_for_context_service_level_connection(hfp_connection_t * context){ @@ -576,10 +613,11 @@ static int hfp_ag_run_for_context_service_level_connection(hfp_connection_t * co if (context->state != HFP_W4_RETRIEVE_CAN_HOLD_CALL) break; if (has_hf_indicators_feature(context)){ context->state = HFP_W4_LIST_GENERIC_STATUS_INDICATORS; - } else { - hfp_ag_slc_established(context); } hfp_ag_retrieve_can_hold_call_cmd(context->rfcomm_cid); + if (!has_hf_indicators_feature(context)){ + hfp_ag_slc_established(context); + } return 1; case HFP_CMD_LIST_GENERIC_STATUS_INDICATORS: @@ -649,15 +687,14 @@ static int hfp_ag_run_for_context_service_level_connection_queries(hfp_connectio return 1; } case HFP_CMD_ENABLE_INDICATOR_STATUS_UPDATE: - printf("TODO\n"); - break; + hfp_ag_ok(context->rfcomm_cid); + return 1; default: break; } return 0; } - static int hfp_ag_run_for_audio_connection(hfp_connection_t * context){ if (context->state < HFP_SERVICE_LEVEL_CONNECTION_ESTABLISHED || context->state > HFP_W2_DISCONNECT_SCO) return 0; @@ -681,9 +718,8 @@ static int hfp_ag_run_for_audio_connection(hfp_connection_t * context){ if (context->establish_audio_connection){ context->state = HFP_W4_SCO_CONNECTED; context->establish_audio_connection = 0; - hci_send_cmd(&hci_setup_synchronous_connection, context->con_handle, 8000, 8000, 0xFFFF, hci_get_sco_voice_setting(), 0xFF, 0x003F); + hfp_setup_synchronous_connection(context->con_handle, context->link_setting); return 1; - } return 0; } @@ -732,13 +768,13 @@ static void hfp_timeout_stop(hfp_connection_t * context){ // static void hfp_ag_hf_start_ringing(hfp_connection_t * context){ - hfp_timeout_start(context); - context->ag_ring = 1; - context->ag_send_clip = clip_type && context->clip_enabled; if (use_in_band_tone()){ context->call_state = HFP_CALL_W4_AUDIO_CONNECTION_FOR_IN_BAND_RING; hfp_ag_establish_audio_connection(context->remote_addr); } else { + hfp_timeout_start(context); + context->ag_ring = 1; + context->ag_send_clip = clip_type && context->clip_enabled; context->call_state = HFP_CALL_RINGING; hfp_emit_event(hfp_callback, HFP_SUBEVENT_START_RINGINIG, 0); } @@ -894,6 +930,7 @@ static void hfp_ag_trigger_terminate_call(void){ if (connection->call_state == HFP_CALL_IDLE) continue; connection->call_state = HFP_CALL_IDLE; connection->ag_indicators_status_update_bitmap = store_bit(connection->ag_indicators_status_update_bitmap, call_indicator_index, 1); + connection->release_audio_connection = 1; hfp_run_for_context(connection); } hfp_emit_event(hfp_callback, HFP_SUBEVENT_CALL_TERMINATED, 0); @@ -947,12 +984,25 @@ static hfp_connection_t * hfp_ag_connection_for_call_state(hfp_call_state_t call return NULL; } +static void hfp_ag_send_response_and_hold_state(void){ + linked_list_iterator_t it; + linked_list_iterator_init(&it, hfp_get_connections()); + while (linked_list_iterator_has_next(&it)){ + hfp_connection_t * connection = (hfp_connection_t *)linked_list_iterator_next(&it); + connection->send_response_and_hold_status = 1; + } +} + static int call_setup_state_machine(hfp_connection_t * connection){ int indicator_index; switch (connection->call_state){ case HFP_CALL_W4_AUDIO_CONNECTION_FOR_IN_BAND_RING: if (connection->state != HFP_AUDIO_CONNECTION_ESTABLISHED) return 0; // we got event: audio connection established + hfp_timeout_start(connection); + connection->ag_ring = 1; + connection->ag_send_clip = clip_type && connection->clip_enabled; + connection->call_state = HFP_CALL_RINGING; connection->call_state = HFP_CALL_RINGING; hfp_emit_event(hfp_callback, HFP_SUBEVENT_START_RINGINIG, 0); break; @@ -1024,10 +1074,8 @@ static void hfp_ag_call_sm(hfp_ag_call_event_t event, hfp_connection_t * connect printf("AG: current call is placed on hold, incoming call gets active\n"); hfp_ag_set_callsetup_state(HFP_CALLSETUP_STATUS_NO_CALL_SETUP_IN_PROGRESS); hfp_ag_set_callheld_state(HFP_CALLHELD_STATUS_CALL_ON_HOLD_OR_SWAPPED); - // TODO: update AG indicators for all connections - // context->ag_indicators_status_update_bitmap = store_bit(context->ag_indicators_status_update_bitmap, callsetup_indicator_index, 1); - // context->ag_indicators_status_update_bitmap = store_bit(context->ag_indicators_status_update_bitmap, callheld_indicator_index, 1); - // context->call_state = HFP_CALL_ACTIVE; + hfp_ag_transfer_callsetup_state(); + hfp_ag_transfer_callheld_state(); break; default: break; @@ -1035,6 +1083,25 @@ static void hfp_ag_call_sm(hfp_ag_call_event_t event, hfp_connection_t * connect break; } break; + + case HFP_AG_HELD_CALL_JOINED_BY_AG: + switch (hfp_ag_call_state){ + case HFP_CALL_STATUS_ACTIVE_OR_HELD_CALL_IS_PRESENT: + switch (hfp_ag_callheld_state){ + case HFP_CALLHELD_STATUS_CALL_ON_HOLD_OR_SWAPPED: + printf("AG: joining held call with active call\n"); + hfp_ag_set_callheld_state(HFP_CALLHELD_STATUS_NO_CALLS_HELD); + hfp_ag_transfer_callheld_state(); + break; + default: + break; + } + break; + default: + break; + } + break; + case HFP_AG_INCOMING_CALL_ACCEPTED_BY_HF: // clear CLIP clip_type = 0; @@ -1046,6 +1113,7 @@ static void hfp_ag_call_sm(hfp_ag_call_event_t event, hfp_connection_t * connect hfp_ag_set_call_state(HFP_CALL_STATUS_ACTIVE_OR_HELD_CALL_IS_PRESENT); hfp_ag_hf_accept_call(connection); printf("HF answers call, accept call by GSM\n"); + hfp_emit_event(hfp_callback, HFP_CMD_CALL_ANSWERED, 0); break; default: break; @@ -1056,6 +1124,76 @@ static void hfp_ag_call_sm(hfp_ag_call_event_t event, hfp_connection_t * connect } break; + case HFP_AG_RESPONSE_AND_HOLD_ACCEPT_INCOMING_CALL_BY_AG: + // clear CLIP + clip_type = 0; + switch (hfp_ag_call_state){ + case HFP_CALL_STATUS_NO_HELD_OR_ACTIVE_CALLS: + switch (hfp_ag_callsetup_state){ + case HFP_CALLSETUP_STATUS_INCOMING_CALL_SETUP_IN_PROGRESS: + hfp_ag_response_and_hold_active = 1; + hfp_ag_response_and_hold_state = HFP_RESPONSE_AND_HOLD_INCOMING_ON_HOLD; + hfp_ag_send_response_and_hold_state(); + // as with regualr call + hfp_ag_set_call_state(HFP_CALL_STATUS_ACTIVE_OR_HELD_CALL_IS_PRESENT); + hfp_ag_set_callsetup_state(HFP_CALLSETUP_STATUS_NO_CALL_SETUP_IN_PROGRESS); + hfp_ag_ag_accept_call(); + printf("AG response and hold - hold by AG\n"); + break; + default: + break; + } + break; + default: + break; + } + break; + + case HFP_AG_RESPONSE_AND_HOLD_ACCEPT_INCOMING_CALL_BY_HF: + // clear CLIP + clip_type = 0; + switch (hfp_ag_call_state){ + case HFP_CALL_STATUS_NO_HELD_OR_ACTIVE_CALLS: + switch (hfp_ag_callsetup_state){ + case HFP_CALLSETUP_STATUS_INCOMING_CALL_SETUP_IN_PROGRESS: + hfp_ag_response_and_hold_active = 1; + hfp_ag_response_and_hold_state = HFP_RESPONSE_AND_HOLD_INCOMING_ON_HOLD; + hfp_ag_send_response_and_hold_state(); + // as with regualr call + hfp_ag_set_call_state(HFP_CALL_STATUS_ACTIVE_OR_HELD_CALL_IS_PRESENT); + hfp_ag_set_callsetup_state(HFP_CALLSETUP_STATUS_NO_CALL_SETUP_IN_PROGRESS); + hfp_ag_hf_accept_call(connection); + printf("AG response and hold - hold by HF\n"); + break; + default: + break; + } + break; + default: + break; + } + break; + + case HFP_AG_RESPONSE_AND_HOLD_ACCEPT_HELD_CALL_BY_AG: + case HFP_AG_RESPONSE_AND_HOLD_ACCEPT_HELD_CALL_BY_HF: + if (!hfp_ag_response_and_hold_active) break; + if (hfp_ag_response_and_hold_state != HFP_RESPONSE_AND_HOLD_INCOMING_ON_HOLD) break; + hfp_ag_response_and_hold_state = HFP_RESPONSE_AND_HOLD_HELD_INCOMING_ACCEPTED; + hfp_ag_send_response_and_hold_state(); + printf("Held Call accepted and active\n"); + break; + + case HFP_AG_RESPONSE_AND_HOLD_REJECT_HELD_CALL_BY_AG: + case HFP_AG_RESPONSE_AND_HOLD_REJECT_HELD_CALL_BY_HF: + if (!hfp_ag_response_and_hold_active) break; + if (hfp_ag_response_and_hold_state != HFP_RESPONSE_AND_HOLD_INCOMING_ON_HOLD) break; + hfp_ag_response_and_hold_state = HFP_RESPONSE_AND_HOLD_HELD_INCOMING_REJECTED; + hfp_ag_send_response_and_hold_state(); + // from terminate by ag + hfp_ag_set_call_state(HFP_CALL_STATUS_NO_HELD_OR_ACTIVE_CALLS); + hfp_ag_trigger_terminate_call(); + break; + case HFP_AG_TERMINATE_CALL_BY_HF: // clear CLIP clip_type = 0; @@ -1131,6 +1269,10 @@ static void hfp_ag_call_sm(hfp_ag_call_event_t event, hfp_connection_t * connect hfp_ag_transfer_callsetup_state(); break; case HFP_CALL_STATUS_ACTIVE_OR_HELD_CALL_IS_PRESENT: + if (hfp_ag_response_and_hold_active) { + hfp_ag_response_and_hold_state = HFP_RESPONSE_AND_HOLD_HELD_INCOMING_REJECTED; + hfp_ag_send_response_and_hold_state(); + } hfp_ag_set_callsetup_state(HFP_CALLSETUP_STATUS_NO_CALL_SETUP_IN_PROGRESS); hfp_ag_set_call_state(HFP_CALL_STATUS_NO_HELD_OR_ACTIVE_CALLS); hfp_ag_trigger_terminate_call(); @@ -1220,6 +1362,7 @@ static void hfp_ag_call_sm(hfp_ag_call_event_t event, hfp_connection_t * connect hfp_ag_transfer_callheld_state(); } break; + default: break; } @@ -1229,6 +1372,11 @@ static void hfp_run_for_context(hfp_connection_t *context){ if (!context) return; if (!rfcomm_can_send_packet_now(context->rfcomm_cid)) return; + if (context->send_status_of_current_calls){ + hfp_emit_event(hfp_callback, HFP_SUBEVENT_TRANSMIT_STATUS_OF_CURRENT_CALL, 0); + return; + } + if (context->command == HFP_CMD_UNKNOWN){ context->ok_pending = 0; context->send_error = 0; @@ -1237,13 +1385,6 @@ static void hfp_run_for_context(hfp_connection_t *context){ return; } - if (context->ok_pending){ - context->ok_pending = 0; - context->command = HFP_CMD_NONE; - hfp_ag_ok(context->rfcomm_cid); - return; - } - if (context->send_error){ context->send_error = 0; context->command = HFP_CMD_NONE; @@ -1251,12 +1392,36 @@ static void hfp_run_for_context(hfp_connection_t *context){ return; } + // note: before update AG indicators and ok_pending + if (context->send_response_and_hold_status){ + context->send_response_and_hold_status = 0; + hfp_ag_set_response_and_hold(context->rfcomm_cid, hfp_ag_response_and_hold_state); + return; + } + + if (context->send_response_and_hold_active){ + context->send_response_and_hold_active = 0; + hfp_ag_set_response_and_hold(context->rfcomm_cid, 0); + return; + } + + if (context->ok_pending){ + context->ok_pending = 0; + context->command = HFP_CMD_NONE; + hfp_ag_ok(context->rfcomm_cid); + return; + } + // update AG indicators if (context->ag_indicators_status_update_bitmap){ int i; for (i=0;iag_indicators_nr;i++){ if (get_bit(context->ag_indicators_status_update_bitmap, i)){ context->ag_indicators_status_update_bitmap = store_bit(context->ag_indicators_status_update_bitmap, i, 0); + if (!context->enable_status_update_for_ag_indicators) { + log_info("+CMER:3,0,0,0 - not sending update for '%s'", hfp_ag_indicators[i].name); + break; + } hfp_ag_transfer_ag_indicators_status_cmd(context->rfcomm_cid, &hfp_ag_indicators[i]); return; } @@ -1285,6 +1450,18 @@ static void hfp_run_for_context(hfp_connection_t *context){ return; } + if (context->send_subscriber_number){ + if (context->next_subscriber_number_to_send < subscriber_numbers_count){ + hfp_phone_number_t phone = subscriber_numbers[context->next_subscriber_number_to_send++]; + hfp_send_subscriber_number_cmd(context->rfcomm_cid, phone.type, phone.number); + } else { + context->send_subscriber_number = 0; + context->next_subscriber_number_to_send = 0; + hfp_ag_ok(context->rfcomm_cid); + } + context->command = HFP_CMD_NONE; + } + if (context->send_microphone_gain){ context->send_microphone_gain = 0; context->command = HFP_CMD_NONE; @@ -1299,6 +1476,12 @@ static void hfp_run_for_context(hfp_connection_t *context){ return; } + if (context->send_ag_status_indicators){ + context->send_ag_status_indicators = 0; + hfp_ag_retrieve_indicators_cmd(context->rfcomm_cid, context); + return; + } + int done = hfp_ag_run_for_context_service_level_connection(context); if (!done){ done = hfp_ag_run_for_context_service_level_connection_queries(context); @@ -1327,6 +1510,16 @@ static void hfp_run_for_context(hfp_connection_t *context){ context->command = HFP_CMD_NONE; } } +static hfp_generic_status_indicator_t *get_hf_indicator_by_number(int number){ + int i; + for (i=0;i< get_hfp_generic_status_indicators_nr();i++){ + hfp_generic_status_indicator_t * indicator = &get_hfp_generic_status_indicators()[i]; + if (indicator->uuid == number){ + return indicator; + } + } + return NULL; +} static void hfp_handle_rfcomm_data(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ hfp_connection_t * context = get_hfp_connection_context_for_rfcomm_cid(channel); @@ -1335,7 +1528,81 @@ static void hfp_handle_rfcomm_data(uint8_t packet_type, uint16_t channel, uint8_ for (pos = 0; pos < size ; pos++){ hfp_parse(context, packet[pos], 0); } + hfp_generic_status_indicator_t * indicator; + int value; switch(context->command){ + case HFP_CMD_RESPONSE_AND_HOLD_QUERY: + if (hfp_ag_response_and_hold_active){ + context->send_response_and_hold_active = 1; + } + context->ok_pending = 1; + break; + case HFP_CMD_RESPONSE_AND_HOLD_COMMAND: + value = atoi((char *)&context->line_buffer[0]); + printf("HF Response and Hold: %u\n", value); + switch(value){ + case HFP_RESPONSE_AND_HOLD_INCOMING_ON_HOLD: + hfp_ag_call_sm(HFP_AG_RESPONSE_AND_HOLD_ACCEPT_INCOMING_CALL_BY_HF, context); + break; + case HFP_RESPONSE_AND_HOLD_HELD_INCOMING_ACCEPTED: + hfp_ag_call_sm(HFP_AG_RESPONSE_AND_HOLD_ACCEPT_HELD_CALL_BY_HF, context); + break; + case HFP_RESPONSE_AND_HOLD_HELD_INCOMING_REJECTED: + hfp_ag_call_sm(HFP_AG_RESPONSE_AND_HOLD_REJECT_HELD_CALL_BY_HF, context); + break; + default: + break; + } + context->ok_pending = 1; + break; + case HFP_CMD_HF_INDICATOR_STATUS: + context->command = HFP_CMD_NONE; + // find indicator by assigned number + indicator = get_hf_indicator_by_number(context->parser_indicator_index); + if (!indicator){ + context->send_error = 1; + break; + } + value = atoi((char *)&context->line_buffer[0]); + switch (indicator->uuid){ + case 1: // enhanced security + if (value > 1) { + context->send_error = 1; + return; + } + printf("HF Indicator 'enhanced security' set to %u\n", value); + break; + case 2: // battery level + if (value > 100){ + context->send_error = 1; + return; + } + printf("HF Indicator 'battery' set to %u\n", value); + break; + default: + printf("HF Indicator unknown set to %u\n", value); + break; + } + context->ok_pending = 1; + break; + case HFP_CMD_RETRIEVE_AG_INDICATORS_STATUS: + // expected by SLC state machine + if (context->state < HFP_SERVICE_LEVEL_CONNECTION_ESTABLISHED) break; + context->send_ag_status_indicators = 1; + break; + case HFP_CMD_LIST_CURRENT_CALLS: + context->command = HFP_CMD_NONE; + context->send_status_of_current_calls = 1; + hfp_emit_event(hfp_callback, HFP_SUBEVENT_TRANSMIT_STATUS_OF_CURRENT_CALL, 0); + break; + case HFP_CMD_GET_SUBSCRIBER_NUMBER_INFORMATION: + if (subscriber_numbers_count == 0){ + hfp_ag_ok(context->rfcomm_cid); + break; + } + context->next_subscriber_number_to_send = 0; + context->send_subscriber_number = 1; + break; case HFP_CMD_TRANSMIT_DTMF_CODES: context->command = HFP_CMD_NONE; hfp_emit_string_event(hfp_callback, HFP_SUBEVENT_TRANSMIT_DTMF_CODES, (const char *) &context->line_buffer[0]); @@ -1420,6 +1687,7 @@ static void hfp_handle_rfcomm_data(uint8_t packet_type, uint16_t channel, uint8_ printf("AG: Join 3-way-call\n"); hfp_ag_set_callheld_state(HFP_CALLHELD_STATUS_NO_CALLS_HELD); context->ag_indicators_status_update_bitmap = store_bit(context->ag_indicators_status_update_bitmap, callheld_indicator_index, 1); + hfp_emit_event(hfp_callback, HFP_SUBEVENT_CONFERENCE_CALL, 0); } context->call_state = HFP_CALL_ACTIVE; break; @@ -1641,6 +1909,10 @@ void hfp_ag_answer_incoming_call(void){ hfp_ag_call_sm(HFP_AG_INCOMING_CALL_ACCEPTED_BY_AG, NULL); } +void hfp_ag_join_held_call(void){ + hfp_ag_call_sm(HFP_AG_HELD_CALL_JOINED_BY_AG, NULL); +} + void hfp_ag_terminate_call(void){ hfp_ag_call_sm(HFP_AG_TERMINATE_CALL_BY_AG, NULL); } @@ -1660,15 +1932,34 @@ void hfp_ag_outgoing_call_rejected(void){ void hfp_ag_outgoing_call_accepted(void){ hfp_ag_call_sm(HFP_AG_OUTGOING_CALL_ACCEPTED, NULL); } + +void hfp_ag_hold_incoming_call(void){ + hfp_ag_call_sm(HFP_AG_RESPONSE_AND_HOLD_ACCEPT_INCOMING_CALL_BY_AG, NULL); +} + +void hfp_ag_accept_held_incoming_call(void) { + hfp_ag_call_sm(HFP_AG_RESPONSE_AND_HOLD_ACCEPT_HELD_CALL_BY_AG, NULL); +} + +void hfp_ag_reject_held_incoming_call(void){ + hfp_ag_call_sm(HFP_AG_RESPONSE_AND_HOLD_REJECT_HELD_CALL_BY_AG, NULL); +} + static void hfp_ag_set_ag_indicator(const char * name, int value){ int indicator_index = get_ag_indicator_index_for_name(name); if (indicator_index < 0) return; hfp_ag_indicators[indicator_index].status = value; + linked_list_iterator_t it; linked_list_iterator_init(&it, hfp_get_connections()); while (linked_list_iterator_has_next(&it)){ hfp_connection_t * connection = (hfp_connection_t *)linked_list_iterator_next(&it); + if (!connection->ag_indicators[indicator_index].enabled) { + log_info("AG indicator '%s' changed to %u but not enabled", hfp_ag_indicators[indicator_index].name, value); + continue; + } + log_info("AG indicator '%s' changed to %u, request transfer statur", hfp_ag_indicators[indicator_index].name, value); connection->ag_indicators_status_update_bitmap = store_bit(connection->ag_indicators_status_update_bitmap, indicator_index, 1); hfp_run_for_context(connection); } @@ -1733,6 +2024,7 @@ void hfp_ag_set_microphone_gain(bd_addr_t bd_addr, int gain){ connection->microphone_gain = gain; connection->send_microphone_gain = 1; } + hfp_run_for_context(connection); } /* @@ -1744,6 +2036,7 @@ void hfp_ag_set_speaker_gain(bd_addr_t bd_addr, int gain){ connection->speaker_gain = gain; connection->send_speaker_gain = 1; } + hfp_run_for_context(connection); } /* @@ -1765,3 +2058,30 @@ void hfp_ag_send_dtmf_code_done(bd_addr_t bd_addr){ connection->ok_pending = 1; } +void hfp_ag_set_subcriber_number_information(hfp_phone_number_t * numbers, int numbers_count){ + subscriber_numbers = numbers; + subscriber_numbers_count = numbers_count; +} + +void hfp_ag_send_current_call_status(bd_addr_t bd_addr, int idx, hfp_enhanced_call_dir_t dir, + hfp_enhanced_call_status_t status, hfp_enhanced_call_mode_t mode, + hfp_enhanced_call_mpty_t mpty, uint8_t type, const char * number){ + + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(bd_addr); + + char buffer[100]; + int offset = snprintf(buffer, sizeof(buffer), "\r\n%s: %d,%d,%d,%d,%d", HFP_LIST_CURRENT_CALLS, idx, dir, status, mode, mpty); + if (number){ + offset += snprintf(buffer+offset, sizeof(buffer)-offset, ", \"%s\",%u", number, type); + } + snprintf(buffer+offset, sizeof(buffer)-offset, "\r\n"); + send_str_over_rfcomm(connection->rfcomm_cid, buffer); +} + + +void hfp_ag_send_current_call_status_done(bd_addr_t bd_addr){ + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(bd_addr); + connection->ok_pending = 1; + connection->send_status_of_current_calls = 0; +} + diff --git a/src/classic/hfp_ag.h b/src/classic/hfp_ag.h index 9c150b136..cecb1c54c 100644 --- a/src/classic/hfp_ag.h +++ b/src/classic/hfp_ag.h @@ -54,6 +54,10 @@ extern "C" { #endif /* API_START */ +typedef struct { + uint8_t type; + const char * number; +} hfp_phone_number_t; /** * @brief Create HFP Audio Gateway (AG) SDP service record. @@ -208,7 +212,12 @@ void hfp_ag_call_dropped(void); /** * @brief */ - void hfp_ag_answer_incoming_call(void); +void hfp_ag_answer_incoming_call(void); + +/** + * @brief + */ +void hfp_ag_join_held_call(void); /** * @brief @@ -266,6 +275,38 @@ void hfp_ag_reject_phone_number_for_voice_tag(bd_addr_t bd_addr); */ void hfp_ag_send_dtmf_code_done(bd_addr_t bd_addr); +/* + * @brief + */ +void hfp_ag_set_subcriber_number_information(hfp_phone_number_t * numbers, int numbers_count); + +/* + * @brief + */ +void hfp_ag_send_current_call_status(bd_addr_t bd_addr, int idx, hfp_enhanced_call_dir_t dir, + hfp_enhanced_call_status_t status, hfp_enhanced_call_mode_t mode, + hfp_enhanced_call_mpty_t mpty, uint8_t type, const char * number); + +/* + * @brief + */ +void hfp_ag_send_current_call_status_done(bd_addr_t bd_addr); + +/* + * @brief + */ +void hfp_ag_hold_incoming_call(void); + +/* + * @brief + */ +void hfp_ag_accept_held_incoming_call(void); + +/* + * @brief + */ +void hfp_ag_reject_held_incoming_call(void); + /* API_END */ #if defined __cplusplus diff --git a/src/classic/hfp_hf.c b/src/classic/hfp_hf.c index 29229b2e2..1ba58ddc3 100644 --- a/src/classic/hfp_hf.c +++ b/src/classic/hfp_hf.c @@ -73,6 +73,12 @@ static uint8_t hfp_indicators_status; static hfp_callback_t hfp_callback; +static hfp_call_status_t hfp_call_status; +static hfp_callsetup_status_t hfp_callsetup_status; +static hfp_callheld_status_t hfp_callheld_status; + +static char phone_number[25]; + void hfp_hf_register_packet_handler(hfp_callback_t callback){ hfp_callback = callback; if (callback == NULL){ @@ -87,7 +93,7 @@ static int hfp_hf_supports_codec(uint8_t codec){ for (i = 0; i < hfp_codecs_nr; i++){ if (hfp_codecs[i] == codec) return 1; } - return 0; + return HFP_CODEC_CVSD; } static int has_codec_negotiation_feature(hfp_connection_t * connection){ int hf = get_bit(hfp_supported_features, HFP_HFSF_CODEC_NEGOTIATION); @@ -226,15 +232,101 @@ static int hfp_hf_cmd_confirm_codec(uint16_t cid, uint8_t codec){ return send_str_over_rfcomm(cid, buffer); } +static int hfp_hf_cmd_ata(uint16_t cid){ + char buffer[10]; + sprintf(buffer, "%s\r\n", HFP_CALL_ANSWERED); + return send_str_over_rfcomm(cid, buffer); +} + +static int hfp_hf_set_microphone_gain_cmd(uint16_t cid, int gain){ + char buffer[40]; + sprintf(buffer, "AT%s=%d\r\n", HFP_SET_MICROPHONE_GAIN, gain); + return send_str_over_rfcomm(cid, buffer); +} + +static int hfp_hf_set_speaker_gain_cmd(uint16_t cid, int gain){ + char buffer[40]; + sprintf(buffer, "AT%s=%d\r\n", HFP_SET_SPEAKER_GAIN, gain); + return send_str_over_rfcomm(cid, buffer); +} + +static int hfp_hf_set_calling_line_notification_cmd(uint16_t cid, uint8_t activate){ + char buffer[40]; + sprintf(buffer, "AT%s=%d\r\n", HFP_ENABLE_CLIP, activate); + return send_str_over_rfcomm(cid, buffer); +} + +static int hfp_hf_set_echo_canceling_and_noise_reduction_cmd(uint16_t cid, uint8_t activate){ + char buffer[40]; + sprintf(buffer, "AT%s=%d\r\n", HFP_TURN_OFF_EC_AND_NR, activate); + return send_str_over_rfcomm(cid, buffer); +} + +static int hfp_hf_set_voice_recognition_notification_cmd(uint16_t cid, uint8_t activate){ + char buffer[40]; + sprintf(buffer, "AT%s=%d\r\n", HFP_ACTIVATE_VOICE_RECOGNITION, activate); + return send_str_over_rfcomm(cid, buffer); +} + +static int hfp_hf_set_call_waiting_notification_cmd(uint16_t cid, uint8_t activate){ + char buffer[40]; + sprintf(buffer, "AT%s=%d\r\n", HFP_ENABLE_CALL_WAITING_NOTIFICATION, activate); + return send_str_over_rfcomm(cid, buffer); +} + +static int hfp_hf_initiate_outgoing_call_cmd(uint16_t cid){ + char buffer[40]; + sprintf(buffer, "%s%s;\r\n", HFP_CALL_PHONE_NUMBER, phone_number); + return send_str_over_rfcomm(cid, buffer); +} + +static int hfp_hf_send_memory_dial_cmd(uint16_t cid){ + char buffer[40]; + sprintf(buffer, "%s>%s;\r\n", HFP_CALL_PHONE_NUMBER, phone_number); + return send_str_over_rfcomm(cid, buffer); +} + +static int hfp_hf_send_redial_last_number_cmd(uint16_t cid){ + char buffer[20]; + sprintf(buffer, "AT%s\r\n", HFP_REDIAL_LAST_NUMBER); + return send_str_over_rfcomm(cid, buffer); +} + +static int hfp_hf_send_chup(uint16_t cid){ + char buffer[20]; + sprintf(buffer, "AT%s\r\n", HFP_HANG_UP_CALL); + return send_str_over_rfcomm(cid, buffer); +} + +static int hfp_hf_send_chld(uint16_t cid, int number){ + char buffer[20]; + sprintf(buffer, "AT%s=%u\r\n", HFP_SUPPORT_CALL_HOLD_AND_MULTIPARTY_SERVICES, number); + return send_str_over_rfcomm(cid, buffer); +} + +static int hfp_hf_send_dtmf(uint16_t cid, char code){ + char buffer[20]; + sprintf(buffer, "AT%s=%c\r\n", HFP_TRANSMIT_DTMF_CODES, code); + return send_str_over_rfcomm(cid, buffer); +} + +static int hfp_hf_send_binp(uint16_t cid){ + char buffer[20]; + sprintf(buffer, "AT%s=1\r\n", HFP_PHONE_NUMBER_FOR_VOICE_TAG); + return send_str_over_rfcomm(cid, buffer); +} + static void hfp_emit_ag_indicator_event(hfp_callback_t callback, int status, hfp_ag_indicator_t indicator){ if (!callback) return; - uint8_t event[6]; + uint8_t event[6+HFP_MAX_INDICATOR_DESC_SIZE+1]; event[0] = HCI_EVENT_HFP_META; event[1] = sizeof(event) - 2; event[2] = HFP_SUBEVENT_AG_INDICATOR_STATUS_CHANGED; event[3] = status; event[4] = indicator.index; - event[5] = indicator.status; + event[5] = indicator.status; + strncpy((char*)&event[6], indicator.name, HFP_MAX_INDICATOR_DESC_SIZE); + event[6+HFP_MAX_INDICATOR_DESC_SIZE] = 0; (*callback)(event, sizeof(event)); } @@ -300,8 +392,339 @@ static int hfp_hf_run_for_context_service_level_connection(hfp_connection_t * co return done; } -static void hfp_hf_handle_ok_service_level_connection_establishment(hfp_connection_t *context){ - if (context->state >= HFP_SERVICE_LEVEL_CONNECTION_ESTABLISHED) return; + +static int hfp_hf_run_for_context_service_level_connection_queries(hfp_connection_t * context){ + if (context->state != HFP_SERVICE_LEVEL_CONNECTION_ESTABLISHED) return 0; + if (context->ok_pending) return 0; + + int done = 0; + if (context->enable_status_update_for_ag_indicators != 0xFF){ + context->ok_pending = 1; + done = 1; + hfp_hf_cmd_activate_status_update_for_all_ag_indicators(context->rfcomm_cid, context->enable_status_update_for_ag_indicators); + return done; + }; + if (context->change_status_update_for_individual_ag_indicators){ + context->ok_pending = 1; + done = 1; + hfp_hf_cmd_activate_status_update_for_ag_indicator(context->rfcomm_cid, + context->ag_indicators_status_update_bitmap, + context->ag_indicators_nr); + return done; + } + + switch (context->hf_query_operator_state){ + case HFP_HF_QUERY_OPERATOR_SET_FORMAT: + context->hf_query_operator_state = HFP_HF_QUERY_OPERATOR_W4_SET_FORMAT_OK; + context->ok_pending = 1; + hfp_hf_cmd_query_operator_name_format(context->rfcomm_cid); + return 1; + case HFP_HF_QUERY_OPERATOR_SEND_QUERY: + context->hf_query_operator_state = HPF_HF_QUERY_OPERATOR_W4_RESULT; + context->ok_pending = 1; + hfp_hf_cmd_query_operator_name(context->rfcomm_cid); + return 1; + default: + break; + } + + if (context->enable_extended_audio_gateway_error_report){ + context->ok_pending = 1; + done = 1; + hfp_hf_cmd_enable_extended_audio_gateway_error_report(context->rfcomm_cid, context->enable_extended_audio_gateway_error_report); + return done; + } + + return done; +} + +static int codecs_exchange_state_machine(hfp_connection_t * context){ + /* events ( == commands): + HFP_CMD_AVAILABLE_CODECS == received AT+BAC with list of codecs + HFP_CMD_TRIGGER_CODEC_CONNECTION_SETUP: + hf_trigger_codec_connection_setup == received BCC + ag_trigger_codec_connection_setup == received from AG to send BCS + HFP_CMD_HF_CONFIRMED_CODEC == received AT+BCS + */ + + if (context->ok_pending) return 0; + + switch (context->command){ + case HFP_CMD_AVAILABLE_CODECS: + if (context->codecs_state == HFP_CODECS_W4_AG_COMMON_CODEC) return 0; + + context->codecs_state = HFP_CODECS_W4_AG_COMMON_CODEC; + context->ok_pending = 1; + hfp_hf_cmd_notify_on_codecs(context->rfcomm_cid); + return 1; + case HFP_CMD_TRIGGER_CODEC_CONNECTION_SETUP: + context->codec_confirmed = 0; + context->suggested_codec = 0; + context->negotiated_codec = 0; + + context->codecs_state = HFP_CODECS_RECEIVED_TRIGGER_CODEC_EXCHANGE; + context->ok_pending = 1; + hfp_hf_cmd_trigger_codec_connection_setup(context->rfcomm_cid); + break; + + case HFP_CMD_AG_SUGGESTED_CODEC: + if (hfp_hf_supports_codec(context->suggested_codec)){ + context->codec_confirmed = context->suggested_codec; + context->ok_pending = 1; + context->codecs_state = HFP_CODECS_HF_CONFIRMED_CODEC; + hfp_hf_cmd_confirm_codec(context->rfcomm_cid, context->suggested_codec); + } else { + context->codec_confirmed = 0; + context->suggested_codec = 0; + context->negotiated_codec = 0; + context->codecs_state = HFP_CODECS_W4_AG_COMMON_CODEC; + context->ok_pending = 1; + hfp_hf_cmd_notify_on_codecs(context->rfcomm_cid); + + } + break; + + default: + break; + } + return 0; +} + +static int hfp_hf_run_for_audio_connection(hfp_connection_t * context){ + if (context->state < HFP_SERVICE_LEVEL_CONNECTION_ESTABLISHED || + context->state > HFP_W2_DISCONNECT_SCO) return 0; + + + if (context->state == HFP_AUDIO_CONNECTION_ESTABLISHED && context->release_audio_connection){ + context->state = HFP_W4_SCO_DISCONNECTED; + context->release_audio_connection = 0; + gap_disconnect(context->sco_handle); + return 1; + } + + if (context->state == HFP_AUDIO_CONNECTION_ESTABLISHED) return 0; + + // run codecs exchange + int done = codecs_exchange_state_machine(context); + if (done) return 1; + + if (context->establish_audio_connection){ + context->state = HFP_W4_SCO_CONNECTED; + context->establish_audio_connection = 0; + hfp_setup_synchronous_connection(context->con_handle, context->link_setting); + return 1; + } + + return 0; +} + +static int call_setup_state_machine(hfp_connection_t * context){ + if (context->hf_answer_incoming_call){ + hfp_hf_cmd_ata(context->rfcomm_cid); + context->hf_answer_incoming_call = 0; + return 1; + } + return 0; +} + +static void hfp_run_for_context(hfp_connection_t * context){ + if (!context) return; + if (!rfcomm_can_send_packet_now(context->rfcomm_cid)) return; + + int done = hfp_hf_run_for_context_service_level_connection(context); + if (!done){ + done = hfp_hf_run_for_context_service_level_connection_queries(context); + } + if (!done){ + done = hfp_hf_run_for_audio_connection(context); + } + if (!done){ + done = call_setup_state_machine(context); + } + + if (context->send_microphone_gain){ + context->send_microphone_gain = 0; + context->ok_pending = 1; + hfp_hf_set_microphone_gain_cmd(context->rfcomm_cid, context->microphone_gain); + return; + } + + if (context->send_speaker_gain){ + context->send_speaker_gain = 0; + context->ok_pending = 1; + hfp_hf_set_speaker_gain_cmd(context->rfcomm_cid, context->speaker_gain); + return; + } + + if (context->hf_deactivate_calling_line_notification){ + context->hf_deactivate_calling_line_notification = 0; + context->ok_pending = 1; + hfp_hf_set_calling_line_notification_cmd(context->rfcomm_cid, 0); + return; + } + + if (context->hf_activate_calling_line_notification){ + context->hf_activate_calling_line_notification = 0; + context->ok_pending = 1; + hfp_hf_set_calling_line_notification_cmd(context->rfcomm_cid, 1); + return; + } + + if (context->hf_deactivate_echo_canceling_and_noise_reduction){ + context->hf_deactivate_echo_canceling_and_noise_reduction = 0; + context->ok_pending = 1; + hfp_hf_set_echo_canceling_and_noise_reduction_cmd(context->rfcomm_cid, 0); + return; + } + + if (context->hf_activate_echo_canceling_and_noise_reduction){ + context->hf_activate_echo_canceling_and_noise_reduction = 0; + context->ok_pending = 1; + hfp_hf_set_echo_canceling_and_noise_reduction_cmd(context->rfcomm_cid, 1); + return; + } + + if (context->hf_deactivate_voice_recognition_notification){ + context->hf_deactivate_voice_recognition_notification = 0; + context->ok_pending = 1; + hfp_hf_set_voice_recognition_notification_cmd(context->rfcomm_cid, 0); + return; + } + + if (context->hf_activate_voice_recognition_notification){ + context->hf_activate_voice_recognition_notification = 0; + context->ok_pending = 1; + hfp_hf_set_voice_recognition_notification_cmd(context->rfcomm_cid, 1); + return; + } + + + if (context->hf_deactivate_call_waiting_notification){ + context->hf_deactivate_call_waiting_notification = 0; + context->ok_pending = 1; + hfp_hf_set_call_waiting_notification_cmd(context->rfcomm_cid, 0); + return; + } + + if (context->hf_activate_call_waiting_notification){ + context->hf_activate_call_waiting_notification = 0; + context->ok_pending = 1; + hfp_hf_set_call_waiting_notification_cmd(context->rfcomm_cid, 1); + return; + } + + if (context->hf_initiate_outgoing_call){ + context->hf_initiate_outgoing_call = 0; + context->ok_pending = 1; + hfp_hf_initiate_outgoing_call_cmd(context->rfcomm_cid); + return; + } + + if (context->hf_initiate_memory_dialing){ + context->hf_initiate_memory_dialing = 0; + context->ok_pending = 1; + hfp_hf_send_memory_dial_cmd(context->rfcomm_cid); + return; + } + + if (context->hf_initiate_redial_last_number){ + context->hf_initiate_redial_last_number = 0; + context->ok_pending = 1; + hfp_hf_send_redial_last_number_cmd(context->rfcomm_cid); + return; + } + + if (context->hf_send_chup){ + context->hf_send_chup = 0; + context->ok_pending = 1; + hfp_hf_send_chup(context->rfcomm_cid); + return; + } + + if (context->hf_send_chld_0){ + context->hf_send_chld_0 = 0; + context->ok_pending = 1; + hfp_hf_send_chld(context->rfcomm_cid, 0); + return; + } + + if (context->hf_send_chld_1){ + context->hf_send_chld_1 = 0; + context->ok_pending = 1; + hfp_hf_send_chld(context->rfcomm_cid, 1); + return; + } + + if (context->hf_send_chld_2){ + context->hf_send_chld_2 = 0; + context->ok_pending = 1; + hfp_hf_send_chld(context->rfcomm_cid, 2); + return; + } + + if (context->hf_send_chld_3){ + context->hf_send_chld_3 = 0; + context->ok_pending = 1; + hfp_hf_send_chld(context->rfcomm_cid, 3); + return; + } + + if (context->hf_send_chld_4){ + context->hf_send_chld_4 = 0; + context->ok_pending = 1; + hfp_hf_send_chld(context->rfcomm_cid, 4); + return; + } + + if (context->hf_send_dtmf_code){ + char code = context->hf_send_dtmf_code; + context->hf_send_dtmf_code = 0; + context->ok_pending = 1; + hfp_hf_send_dtmf(context->rfcomm_cid, code); + return; + } + + if (context->hf_send_binp){ + context->hf_send_binp = 0; + context->ok_pending = 1; + hfp_hf_send_binp(context->rfcomm_cid); + return; + } + + if (done) return; + // deal with disconnect + switch (context->state){ + case HFP_W2_DISCONNECT_RFCOMM: + context->state = HFP_W4_RFCOMM_DISCONNECTED; + rfcomm_disconnect_internal(context->rfcomm_cid); + break; + + default: + break; + } +} + +static void hfp_init_link_settings(hfp_connection_t * context){ + // determine highest possible link setting + context->link_setting = HFP_LINK_SETTINGS_D1; + if (hci_remote_eSCO_supported(context->con_handle)){ + context->link_setting = HFP_LINK_SETTINGS_S3; + if ((hfp_supported_features & (1<remote_supported_features & (1<link_setting = HFP_LINK_SETTINGS_S4; + } + } +} + +static void hfp_ag_slc_established(hfp_connection_t * context){ + context->state = HFP_SERVICE_LEVEL_CONNECTION_ESTABLISHED; + hfp_emit_event(hfp_callback, HFP_SUBEVENT_SERVICE_LEVEL_CONNECTION_ESTABLISHED, 0); + hfp_init_link_settings(context); +} + +static void hfp_hf_switch_on_ok(hfp_connection_t *context){ + context->ok_pending = 0; + int done = 1; switch (context->state){ case HFP_W4_EXCHANGE_SUPPORTED_FEATURES: if (has_codec_negotiation_feature(context)){ @@ -332,8 +755,7 @@ static void hfp_hf_handle_ok_service_level_connection_establishment(hfp_connecti context->state = HFP_LIST_GENERIC_STATUS_INDICATORS; break; } - context->state = HFP_SERVICE_LEVEL_CONNECTION_ESTABLISHED; - hfp_emit_event(hfp_callback, HFP_SUBEVENT_SERVICE_LEVEL_CONNECTION_ESTABLISHED, 0); + hfp_ag_slc_established(context); break; case HFP_W4_RETRIEVE_CAN_HOLD_CALL: @@ -341,9 +763,8 @@ static void hfp_hf_handle_ok_service_level_connection_establishment(hfp_connecti context->state = HFP_LIST_GENERIC_STATUS_INDICATORS; break; } - context->state = HFP_SERVICE_LEVEL_CONNECTION_ESTABLISHED; - hfp_emit_event(hfp_callback, HFP_SUBEVENT_SERVICE_LEVEL_CONNECTION_ESTABLISHED, 0); - break; + hfp_ag_slc_established(context); + break; case HFP_W4_LIST_GENERIC_STATUS_INDICATORS: context->state = HFP_RETRIEVE_GENERIC_STATUS_INDICATORS; @@ -354,177 +775,57 @@ static void hfp_hf_handle_ok_service_level_connection_establishment(hfp_connecti break; case HFP_W4_RETRIEVE_INITITAL_STATE_GENERIC_STATUS_INDICATORS: - context->state = HFP_SERVICE_LEVEL_CONNECTION_ESTABLISHED; - hfp_emit_event(hfp_callback, HFP_SUBEVENT_SERVICE_LEVEL_CONNECTION_ESTABLISHED, 0); + hfp_ag_slc_established(context); break; - default: - break; - } -} + case HFP_SERVICE_LEVEL_CONNECTION_ESTABLISHED: + if (context->enable_status_update_for_ag_indicators != 0xFF){ + context->enable_status_update_for_ag_indicators = 0xFF; + hfp_emit_event(hfp_callback, HFP_SUBEVENT_COMPLETE, 0); + break; + } -static int hfp_hf_run_for_context_service_level_connection_queries(hfp_connection_t * context){ - if (context->state != HFP_SERVICE_LEVEL_CONNECTION_ESTABLISHED) return 0; - if (context->ok_pending) return 0; - - int done = 0; - if (context->enable_status_update_for_ag_indicators != 0xFF){ - context->ok_pending = 1; - done = 1; - hfp_hf_cmd_activate_status_update_for_all_ag_indicators(context->rfcomm_cid, context->enable_status_update_for_ag_indicators); - return done; - }; - if (context->change_status_update_for_individual_ag_indicators){ - context->ok_pending = 1; - done = 1; - hfp_hf_cmd_activate_status_update_for_ag_indicator(context->rfcomm_cid, - context->ag_indicators_status_update_bitmap, - context->ag_indicators_nr); - return done; - } + if (context->change_status_update_for_individual_ag_indicators == 1){ + context->change_status_update_for_individual_ag_indicators = 0; + hfp_emit_event(hfp_callback, HFP_SUBEVENT_COMPLETE, 0); + break; + } - if (context->command == HFP_CMD_QUERY_OPERATOR_SELECTION_NAME_FORMAT){ - context->ok_pending = 1; - done = 1; - hfp_hf_cmd_query_operator_name_format(context->rfcomm_cid); - return done; - } - if (context->command == HFP_CMD_QUERY_OPERATOR_SELECTION_NAME){ - context->ok_pending = 1; - done = 1; - hfp_hf_cmd_query_operator_name(context->rfcomm_cid); - return done; - } - - if (context->enable_extended_audio_gateway_error_report){ - context->ok_pending = 1; - done = 1; - hfp_hf_cmd_enable_extended_audio_gateway_error_report(context->rfcomm_cid, context->enable_extended_audio_gateway_error_report); - return done; - } - - return done; -} - -static void hfp_hf_handle_ok_service_level_connection_queries(hfp_connection_t * context){ - if (context->state != HFP_SERVICE_LEVEL_CONNECTION_ESTABLISHED) return; - - if (context->enable_status_update_for_ag_indicators != 0xFF){ - context->enable_status_update_for_ag_indicators = 0xFF; - hfp_emit_event(hfp_callback, HFP_SUBEVENT_COMPLETE, 0); - return; - }; - - if (context->change_status_update_for_individual_ag_indicators == 1){ - context->change_status_update_for_individual_ag_indicators = 0; - hfp_emit_event(hfp_callback, HFP_SUBEVENT_COMPLETE, 0); - return; - } - - if (context->command == HFP_CMD_QUERY_OPERATOR_SELECTION_NAME_FORMAT){ - context->command = HFP_CMD_QUERY_OPERATOR_SELECTION_NAME; - return; - } - - if (context->command == HFP_CMD_QUERY_OPERATOR_SELECTION_NAME){ - hfp_emit_network_operator_event(hfp_callback, 0, context->network_operator); - return; - } - if (context->enable_extended_audio_gateway_error_report){ - context->enable_extended_audio_gateway_error_report = 0; - return; - } -} - -static int codecs_exchange_state_machine(hfp_connection_t * context){ - if (context->ok_pending) return 0; - int done = 1; - - switch(context->command){ - case HFP_CMD_AVAILABLE_CODECS: - switch (context->codecs_state){ - case HFP_CODECS_W4_AG_COMMON_CODEC: - context->codec_confirmed = 0; - context->suggested_codec = 0; - context->negotiated_codec = 0; + switch (context->hf_query_operator_state){ + case HFP_HF_QUERY_OPERATOR_W4_SET_FORMAT_OK: + printf("Format set, querying name\n"); + context->hf_query_operator_state = HFP_HF_QUERY_OPERATOR_SEND_QUERY; break; - case HFP_CODECS_EXCHANGED: - context->negotiated_codec = 0; - context->codecs_state = HFP_CODECS_W4_AG_COMMON_CODEC; + case HPF_HF_QUERY_OPERATOR_W4_RESULT: + context->hf_query_operator_state = HFP_HF_QUERY_OPERATOR_FORMAT_SET; + hfp_emit_network_operator_event(hfp_callback, 0, context->network_operator); break; default: break; } - hfp_hf_cmd_notify_on_codecs(context->rfcomm_cid); - break; - case HFP_CMD_TRIGGER_CODEC_CONNECTION_SETUP: - context->codecs_state = HFP_CODECS_RECEIVED_TRIGGER_CODEC_EXCHANGE; - hfp_hf_cmd_trigger_codec_connection_setup(context->rfcomm_cid); - break; - case HFP_CMD_AG_SUGGESTED_CODEC: - if (hfp_hf_supports_codec(context->suggested_codec)){ - context->codec_confirmed = context->suggested_codec; - hfp_hf_cmd_confirm_codec(context->rfcomm_cid, context->suggested_codec); - } else { - context->codec_confirmed = 0; - context->suggested_codec = 0; - context->negotiated_codec = 0; - hfp_hf_cmd_notify_on_codecs(context->rfcomm_cid); + if (context->enable_extended_audio_gateway_error_report){ + context->enable_extended_audio_gateway_error_report = 0; + break; + } + + switch (context->codecs_state){ + case HFP_CODECS_RECEIVED_TRIGGER_CODEC_EXCHANGE: + context->codecs_state = HFP_CODECS_W4_AG_COMMON_CODEC; + break; + case HFP_CODECS_HF_CONFIRMED_CODEC: + context->codecs_state = HFP_CODECS_EXCHANGED; + hfp_emit_event(hfp_callback, HFP_SUBEVENT_CODECS_CONNECTION_COMPLETE, 0); + break; + default: + done = 0; + break; } break; default: - return 0; - } - - if (done){ - context->ok_pending = 1; - } - return done; -} - -static void hfp_hf_handle_ok_codecs_connection(hfp_connection_t * context){ - // handle audio connection setup - switch (context->codecs_state){ - case HFP_CODECS_RECEIVED_TRIGGER_CODEC_EXCHANGE: - context->codecs_state = HFP_CODECS_W4_AG_COMMON_CODEC; - break; - default: + done = 0; break; } -} -static void hfp_run_for_context(hfp_connection_t * context){ - if (!context) return; - if (!rfcomm_can_send_packet_now(context->rfcomm_cid)) return; - - int done = hfp_hf_run_for_context_service_level_connection(context); - if (!done){ - done = hfp_hf_run_for_context_service_level_connection_queries(context); - } - if (!done){ - done = codecs_exchange_state_machine(context); - } - - if (done) return; - // deal with disconnect - switch (context->state){ - case HFP_W2_DISCONNECT_RFCOMM: - context->state = HFP_W4_RFCOMM_DISCONNECTED; - rfcomm_disconnect_internal(context->rfcomm_cid); - break; - - default: - break; - } -} - -static void hfp_hf_switch_on_ok(hfp_connection_t *context){ - // printf("switch on ok\n"); - context->ok_pending = 0; - - hfp_hf_handle_ok_service_level_connection_establishment(context); - hfp_hf_handle_ok_service_level_connection_queries(context); - hfp_hf_handle_ok_codecs_connection(context); // done context->command = HFP_CMD_NONE; } @@ -539,32 +840,59 @@ static void hfp_handle_rfcomm_event(uint8_t packet_type, uint16_t channel, uint8 //printf("\nHF received: %s", packet+2); for (pos = 0; pos < size ; pos++){ hfp_parse(context, packet[pos], 1); - - // emit indicators status changed - for (i = 0; i < context->ag_indicators_nr; i++){ - if (context->ag_indicators[i].status_changed) { - context->ag_indicators[i].status_changed = 0; - hfp_emit_ag_indicator_event(hfp_callback, 0, context->ag_indicators[i]); - break; - } - } + } - if (context->command == HFP_CMD_ERROR){ - context->ok_pending = 0; - hfp_reset_context_flags(context); - hfp_emit_event(hfp_callback, HFP_SUBEVENT_COMPLETE, 1); - return; - } - if (context->command == HFP_CMD_EXTENDED_AUDIO_GATEWAY_ERROR){ + switch (context->command){ + case HFP_CMD_SET_SPEAKER_GAIN: + context->command = HFP_CMD_NONE; + hfp_emit_event(hfp_callback, HFP_SUBEVENT_SPEAKER_VOLUME, atoi((char*)context->line_buffer)); + break; + case HFP_CMD_SET_MICROPHONE_GAIN: + context->command = HFP_CMD_NONE; + hfp_emit_event(hfp_callback, HFP_SUBEVENT_MICROPHONE_VOLUME, atoi((char*)context->line_buffer)); + break; + case HFP_CMD_AG_SENT_PHONE_NUMBER: + context->command = HFP_CMD_NONE; + hfp_emit_string_event(hfp_callback, HFP_SUBEVENT_NUMBER_FOR_VOICE_TAG, context->bnip_number); + break; + case HFP_CMD_EXTENDED_AUDIO_GATEWAY_ERROR: context->ok_pending = 0; context->extended_audio_gateway_error = 0; + context->command = HFP_CMD_NONE; hfp_emit_event(hfp_callback, HFP_SUBEVENT_EXTENDED_AUDIO_GATEWAY_ERROR, context->extended_audio_gateway_error); - return; - } - - if (context->command != HFP_CMD_OK) continue; - hfp_hf_switch_on_ok(context); + break; + case HFP_CMD_ERROR: + context->ok_pending = 0; + hfp_reset_context_flags(context); + context->command = HFP_CMD_NONE; + hfp_emit_event(hfp_callback, HFP_SUBEVENT_COMPLETE, 1); + break; + case HFP_CMD_OK: + hfp_hf_switch_on_ok(context); + break; + case HFP_CMD_RING: + hfp_emit_event(hfp_callback, HFP_SUBEVENT_RING, 0); + break; + case HFP_CMD_TRANSFER_AG_INDICATOR_STATUS: + for (i = 0; i < context->ag_indicators_nr; i++){ + if (context->ag_indicators[i].status_changed) { + if (strcmp(context->ag_indicators[i].name, "callsetup") == 0){ + hfp_callsetup_status = (hfp_callsetup_status_t) context->ag_indicators[i].status; + } else if (strcmp(context->ag_indicators[i].name, "callheld") == 0){ + hfp_callheld_status = (hfp_callheld_status_t) context->ag_indicators[i].status; + } else if (strcmp(context->ag_indicators[i].name, "call") == 0){ + hfp_call_status = (hfp_call_status_t) context->ag_indicators[i].status; + } + context->ag_indicators[i].status_changed = 0; + hfp_emit_ag_indicator_event(hfp_callback, 0, context->ag_indicators[i]); + break; + } + } + break; + default: + break; } + hfp_run_for_context(context); } static void hfp_run(){ @@ -642,7 +970,7 @@ void hfp_hf_release_service_level_connection(bd_addr_t bd_addr){ hfp_run_for_context(connection); } -void hfp_hf_enable_status_update_for_all_ag_indicators(bd_addr_t bd_addr, uint8_t enable){ +static void hfp_hf_set_status_update_for_all_ag_indicators(bd_addr_t bd_addr, uint8_t enable){ hfp_hf_establish_service_level_connection(bd_addr); hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(bd_addr); if (!connection){ @@ -653,8 +981,16 @@ void hfp_hf_enable_status_update_for_all_ag_indicators(bd_addr_t bd_addr, uint8_ hfp_run_for_context(connection); } +void hfp_hf_enable_status_update_for_all_ag_indicators(bd_addr_t bd_addr){ + hfp_hf_set_status_update_for_all_ag_indicators(bd_addr, 1); +} + +void hfp_hf_disable_status_update_for_all_ag_indicators(bd_addr_t bd_addr){ + hfp_hf_set_status_update_for_all_ag_indicators(bd_addr, 0); +} + // TODO: returned ERROR - wrong format -void hfp_hf_enable_status_update_for_individual_ag_indicators(bd_addr_t bd_addr, uint32_t indicators_status_bitmap){ +void hfp_hf_set_status_update_for_individual_ag_indicators(bd_addr_t bd_addr, uint32_t indicators_status_bitmap){ hfp_hf_establish_service_level_connection(bd_addr); hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(bd_addr); if (!connection){ @@ -673,11 +1009,20 @@ void hfp_hf_query_operator_selection(bd_addr_t bd_addr){ log_error("HFP HF: connection doesn't exist."); return; } - connection->command = HFP_CMD_QUERY_OPERATOR_SELECTION_NAME_FORMAT; + switch (connection->hf_query_operator_state){ + case HFP_HF_QUERY_OPERATOR_FORMAT_NOT_SET: + connection->hf_query_operator_state = HFP_HF_QUERY_OPERATOR_SET_FORMAT; + break; + case HFP_HF_QUERY_OPERATOR_FORMAT_SET: + connection->hf_query_operator_state = HFP_HF_QUERY_OPERATOR_SEND_QUERY; + break; + default: + break; + } hfp_run_for_context(connection); } -void hfp_hf_enable_report_extended_audio_gateway_error_result_code(bd_addr_t bd_addr, uint8_t enable){ +static void hfp_hf_set_report_extended_audio_gateway_error_result_code(bd_addr_t bd_addr, uint8_t enable){ hfp_hf_establish_service_level_connection(bd_addr); hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(bd_addr); if (!connection){ @@ -688,22 +1033,38 @@ void hfp_hf_enable_report_extended_audio_gateway_error_result_code(bd_addr_t bd_ hfp_run_for_context(connection); } + +void hfp_hf_enable_report_extended_audio_gateway_error_result_code(bd_addr_t bd_addr){ + hfp_hf_set_report_extended_audio_gateway_error_result_code(bd_addr, 1); +} + +void hfp_hf_disable_report_extended_audio_gateway_error_result_code(bd_addr_t bd_addr){ + hfp_hf_set_report_extended_audio_gateway_error_result_code(bd_addr, 0); +} + + void hfp_hf_establish_audio_connection(bd_addr_t bd_addr){ hfp_hf_establish_service_level_connection(bd_addr); hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(bd_addr); - if (!has_codec_negotiation_feature(connection)) return; - connection->establish_audio_connection = 0; + connection->establish_audio_connection = 0; + if (connection->state == HFP_AUDIO_CONNECTION_ESTABLISHED) return; if (connection->state >= HFP_W2_DISCONNECT_SCO) return; - - connection->establish_audio_connection = 1; - switch (connection->codecs_state){ - case HFP_CODECS_W4_AG_COMMON_CODEC: - break; - default: - connection->command = HFP_CMD_TRIGGER_CODEC_CONNECTION_SETUP; - break; - } + + if (!has_codec_negotiation_feature(connection)){ + log_info("hfp_ag_establish_audio_connection - no codec negotiation feature, using defaults"); + connection->codecs_state = HFP_CODECS_EXCHANGED; + connection->establish_audio_connection = 1; + } else { + switch (connection->codecs_state){ + case HFP_CODECS_W4_AG_COMMON_CODEC: + break; + default: + connection->command = HFP_CMD_TRIGGER_CODEC_CONNECTION_SETUP; + break; + } + } + hfp_run_for_context(connection); } @@ -713,4 +1074,250 @@ void hfp_hf_release_audio_connection(bd_addr_t bd_addr){ hfp_run_for_context(connection); } +void hfp_hf_answer_incoming_call(bd_addr_t bd_addr){ + hfp_hf_establish_service_level_connection(bd_addr); + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(bd_addr); + + if (hfp_callsetup_status == HFP_CALLSETUP_STATUS_INCOMING_CALL_SETUP_IN_PROGRESS){ + connection->hf_answer_incoming_call = 1; + hfp_run_for_context(connection); + } else { + log_error("HFP HF: answering incoming call with wrong callsetup status %u", hfp_callsetup_status); + } +} + +void hfp_hf_terminate_call(bd_addr_t bd_addr){ + hfp_hf_establish_service_level_connection(bd_addr); + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(bd_addr); + + // if (hfp_call_status == HFP_CALL_STATUS_ACTIVE_OR_HELD_CALL_IS_PRESENT){ + connection->hf_send_chup = 1; + hfp_run_for_context(connection); + // } else { + // log_error("HFP HF: terminating incoming call with wrong call status %u", hfp_call_status); + // } +} + +void hfp_hf_reject_call(bd_addr_t bd_addr){ + hfp_hf_establish_service_level_connection(bd_addr); + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(bd_addr); + + if (hfp_callsetup_status == HFP_CALLSETUP_STATUS_INCOMING_CALL_SETUP_IN_PROGRESS){ + connection->hf_send_chup = 1; + hfp_run_for_context(connection); + } +} + +void hfp_hf_user_busy(bd_addr_t addr){ + hfp_hf_establish_service_level_connection(addr); + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(addr); + + if (hfp_callsetup_status == HFP_CALLSETUP_STATUS_INCOMING_CALL_SETUP_IN_PROGRESS){ + connection->hf_send_chld_0 = 1; + hfp_run_for_context(connection); + } +} + +void hfp_hf_end_active_and_accept_other(bd_addr_t addr){ + hfp_hf_establish_service_level_connection(addr); + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(addr); + + if (hfp_callsetup_status == HFP_CALLSETUP_STATUS_INCOMING_CALL_SETUP_IN_PROGRESS || + hfp_call_status == HFP_CALL_STATUS_ACTIVE_OR_HELD_CALL_IS_PRESENT){ + connection->hf_send_chld_1 = 1; + hfp_run_for_context(connection); + } +} + +void hfp_hf_swap_calls(bd_addr_t addr){ + hfp_hf_establish_service_level_connection(addr); + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(addr); + + if (hfp_callsetup_status == HFP_CALLSETUP_STATUS_INCOMING_CALL_SETUP_IN_PROGRESS || + hfp_call_status == HFP_CALL_STATUS_ACTIVE_OR_HELD_CALL_IS_PRESENT){ + connection->hf_send_chld_2 = 1; + hfp_run_for_context(connection); + } +} + +void hfp_hf_join_held_call(bd_addr_t addr){ + hfp_hf_establish_service_level_connection(addr); + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(addr); + + if (hfp_callsetup_status == HFP_CALLSETUP_STATUS_INCOMING_CALL_SETUP_IN_PROGRESS || + hfp_call_status == HFP_CALL_STATUS_ACTIVE_OR_HELD_CALL_IS_PRESENT){ + connection->hf_send_chld_3 = 1; + hfp_run_for_context(connection); + } +} + +void hfp_hf_connect_calls(bd_addr_t addr){ + hfp_hf_establish_service_level_connection(addr); + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(addr); + + if (hfp_callsetup_status == HFP_CALLSETUP_STATUS_INCOMING_CALL_SETUP_IN_PROGRESS || + hfp_call_status == HFP_CALL_STATUS_ACTIVE_OR_HELD_CALL_IS_PRESENT){ + connection->hf_send_chld_4 = 1; + hfp_run_for_context(connection); + } +} + +/** + * @brief + */ +void hfp_hf_connect_calls(bd_addr_t addr); + +void hfp_hf_dial_number(bd_addr_t bd_addr, char * number){ + hfp_hf_establish_service_level_connection(bd_addr); + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(bd_addr); + + connection->hf_initiate_outgoing_call = 1; + snprintf(phone_number, sizeof(phone_number), "%s", number); + hfp_run_for_context(connection); +} + +void hfp_hf_dial_memory(bd_addr_t bd_addr, char * number){ + hfp_hf_establish_service_level_connection(bd_addr); + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(bd_addr); + + connection->hf_initiate_memory_dialing = 1; + snprintf(phone_number, sizeof(phone_number), "%s", number); + hfp_run_for_context(connection); +} + +void hfp_hf_redial_last_number(bd_addr_t bd_addr){ + hfp_hf_establish_service_level_connection(bd_addr); + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(bd_addr); + + connection->hf_initiate_redial_last_number = 1; + hfp_run_for_context(connection); +} + +void hfp_hf_activate_call_waiting_notification(bd_addr_t bd_addr){ + hfp_hf_establish_service_level_connection(bd_addr); + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(bd_addr); + + connection->hf_activate_call_waiting_notification = 1; + hfp_run_for_context(connection); +} + + +void hfp_hf_deactivate_call_waiting_notification(bd_addr_t bd_addr){ + hfp_hf_establish_service_level_connection(bd_addr); + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(bd_addr); + + connection->hf_deactivate_call_waiting_notification = 1; + hfp_run_for_context(connection); +} + + +void hfp_hf_activate_calling_line_notification(bd_addr_t bd_addr){ + hfp_hf_establish_service_level_connection(bd_addr); + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(bd_addr); + + connection->hf_deactivate_calling_line_notification = 1; + hfp_run_for_context(connection); +} + +/* + * @brief + */ +void hfp_hf_deactivate_calling_line_notification(bd_addr_t bd_addr){ + hfp_hf_establish_service_level_connection(bd_addr); + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(bd_addr); + + connection->hf_deactivate_calling_line_notification = 1; + hfp_run_for_context(connection); +} + + +/* + * @brief + */ +void hfp_hf_activate_echo_canceling_and_noise_reduction(bd_addr_t bd_addr){ + hfp_hf_establish_service_level_connection(bd_addr); + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(bd_addr); + + connection->hf_deactivate_echo_canceling_and_noise_reduction = 1; + hfp_run_for_context(connection); +} + +/* + * @brief + */ +void hfp_hf_deactivate_echo_canceling_and_noise_reduction(bd_addr_t bd_addr){ + hfp_hf_establish_service_level_connection(bd_addr); + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(bd_addr); + + connection->hf_deactivate_echo_canceling_and_noise_reduction = 1; + hfp_run_for_context(connection); +} + +/* + * @brief + */ +void hfp_hf_activate_voice_recognition_notification(bd_addr_t bd_addr){ + hfp_hf_establish_service_level_connection(bd_addr); + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(bd_addr); + + connection->hf_activate_voice_recognition_notification = 1; + hfp_run_for_context(connection); +} + +/* + * @brief + */ +void hfp_hf_deactivate_voice_recognition_notification(bd_addr_t bd_addr){ + hfp_hf_establish_service_level_connection(bd_addr); + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(bd_addr); + + connection->hf_deactivate_voice_recognition_notification = 1; + hfp_run_for_context(connection); +} + +/* + * @brief + */ +void hfp_hf_set_microphone_gain(bd_addr_t bd_addr, int gain){ + hfp_hf_establish_service_level_connection(bd_addr); + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(bd_addr); + if (connection->microphone_gain == gain) return; + + connection->microphone_gain = gain; + connection->send_microphone_gain = 1; + hfp_run_for_context(connection); +} + +/* + * @brief + */ +void hfp_hf_set_speaker_gain(bd_addr_t bd_addr, int gain){ + hfp_hf_establish_service_level_connection(bd_addr); + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(bd_addr); + if (connection->speaker_gain == gain) return; + + connection->speaker_gain = gain; + connection->send_speaker_gain = 1; + hfp_run_for_context(connection); +} + +/* + * @brief + */ +void hfp_hf_send_dtmf_code(bd_addr_t addr, char code){ + hfp_hf_establish_service_level_connection(addr); + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(addr); + connection->hf_send_dtmf_code = code; + hfp_run_for_context(connection); +} + +/* + * @brief + */ +void hfp_hf_request_phone_number_for_voice_tag(bd_addr_t addr){ + hfp_hf_establish_service_level_connection(addr); + hfp_connection_t * connection = get_hfp_connection_context_for_bd_addr(addr); + connection->hf_send_binp = 1; + hfp_run_for_context(connection); +} diff --git a/src/classic/hfp_hf.h b/src/classic/hfp_hf.h index de68c844b..3caa66b96 100644 --- a/src/classic/hfp_hf.h +++ b/src/classic/hfp_hf.h @@ -84,7 +84,6 @@ void hfp_hf_register_packet_handler(hfp_callback_t callback); */ void hfp_hf_establish_service_level_connection(bd_addr_t bd_addr); - /** * @brief Release the RFCOMM channel and the audio connection between the HF and the AG. * TODO: trigger release of the audio connection @@ -94,12 +93,13 @@ void hfp_hf_release_service_level_connection(bd_addr_t bd_addr); /** * @brief Deactivate/reactivate status update for all indicators in the AG. */ -void hfp_hf_enable_status_update_for_all_ag_indicators(bd_addr_t bd_addr, uint8_t enable); +void hfp_hf_enable_status_update_for_all_ag_indicators(bd_addr_t bd_addr); +void hfp_hf_disable_status_update_for_all_ag_indicators(bd_addr_t bd_addr); /** * @brief Deactivate/reactivate status update for the individual indicators in the AG using bitmap. */ -void hfp_hf_enable_status_update_for_individual_ag_indicators(bd_addr_t bd_addr, uint32_t indicators_status_bitmap); +void hfp_hf_set_status_update_for_individual_ag_indicators(bd_addr_t bd_addr, uint32_t indicators_status_bitmap); /** @@ -136,7 +136,8 @@ void hfp_hf_query_operator_selection(bd_addr_t bd_addr); * - +CME ERROR: 31 - network Timeout. * - +CME ERROR: 32 - network not allowed – Emergency calls only */ -void hfp_hf_enable_report_extended_audio_gateway_error_result_code(bd_addr_t bd_addr, uint8_t enable); +void hfp_hf_enable_report_extended_audio_gateway_error_result_code(bd_addr_t bd_addr); +void hfp_hf_disable_report_extended_audio_gateway_error_result_code(bd_addr_t bd_addr); /** * @brief @@ -148,6 +149,121 @@ void hfp_hf_establish_audio_connection(bd_addr_t bd_addr); */ void hfp_hf_release_audio_connection(bd_addr_t bd_addr); +/** + * @brief + */ +void hfp_hf_answer_incoming_call(bd_addr_t bd_addr); + +/** + * @brief + */ +void hfp_hf_reject_call(bd_addr_t bd_addr); + +/** + * @brief + */ +void hfp_hf_user_busy(bd_addr_t addr); + +/** + * @brief + */ +void hfp_hf_end_active_and_accept_other(bd_addr_t addr); + +/** + * @brief + */ +void hfp_hf_swap_calls(bd_addr_t addr); + +/** + * @brief + */ +void hfp_hf_join_held_call(bd_addr_t addr); + +/** + * @brief + */ +void hfp_hf_connect_calls(bd_addr_t addr); + +/** + * @brief + */ +void hfp_hf_terminate_call(bd_addr_t bd_addr); + +/** + * @brief + */ +void hfp_hf_dial_number(bd_addr_t bd_addr, char * number); + +/** + * @brief + */ +void hfp_hf_dial_memory(bd_addr_t bd_addr, char * number); + +/** + * @brief + */ +void hfp_hf_redial_last_number(bd_addr_t bd_addr); + +/* + * @brief + */ +void hfp_hf_activate_call_waiting_notification(bd_addr_t bd_addr); + +/* + * @brief + */ +void hfp_hf_deactivate_call_waiting_notification(bd_addr_t bd_addr); + +/* + * @brief + */ +void hfp_hf_activate_calling_line_notification(bd_addr_t bd_addr); + +/* + * @brief + */ +void hfp_hf_deactivate_calling_line_notification(bd_addr_t bd_addr); + + +/* + * @brief + */ +void hfp_hf_activate_echo_canceling_and_noise_reduction(bd_addr_t bd_addr); + +/* + * @brief + */ +void hfp_hf_deactivate_echo_canceling_and_noise_reduction(bd_addr_t bd_addr); + +/* + * @brief + */ +void hfp_hf_activate_voice_recognition_notification(bd_addr_t bd_addr); + +/* + * @brief + */ +void hfp_hf_deactivate_voice_recognition_notification(bd_addr_t bd_addr); + +/* + * @brief + */ +void hfp_hf_set_microphone_gain(bd_addr_t bd_addr, int gain); + +/* + * @brief + */ +void hfp_hf_set_speaker_gain(bd_addr_t bd_addr, int gain); + +/* + * @brief + */ +void hfp_hf_send_dtmf_code(bd_addr_t bd_addr, char code); + +/* + * @brief + */ +void hfp_hf_request_phone_number_for_voice_tag(bd_addr_t addr); /* API_END */ diff --git a/src/hci.c b/src/hci.c index ad67139b8..0d94a5cd5 100644 --- a/src/hci.c +++ b/src/hci.c @@ -1330,6 +1330,10 @@ static void event_handler(uint8_t *packet, int size){ } conn->role = HCI_ROLE_SLAVE; conn->state = RECEIVED_CONNECTION_REQUEST; + // store info about eSCO + if (link_type == 0x02){ + conn->remote_supported_feature_eSCO = 1; + } hci_run(); break; @@ -1403,9 +1407,12 @@ static void event_handler(uint8_t *packet, int size){ if (features[6] & (1 << 3)){ conn->bonding_flags |= BONDING_REMOTE_SUPPORTS_SSP; } + if (features[3] & (1<<7)){ + conn->remote_supported_feature_eSCO = 1; + } } conn->bonding_flags |= BONDING_RECEIVED_REMOTE_FEATURES; - log_info("HCI_EVENT_READ_REMOTE_SUPPORTED_FEATURES_COMPLETE, bonding flags %x", conn->bonding_flags); + log_info("HCI_EVENT_READ_REMOTE_SUPPORTED_FEATURES_COMPLETE, bonding flags %x, eSCO %u", conn->bonding_flags, conn->remote_supported_feature_eSCO); if (conn->bonding_flags & BONDING_DEDICATED){ conn->bonding_flags |= BONDING_SEND_AUTHENTICATE_REQUEST; } @@ -2319,13 +2326,20 @@ void hci_run(void){ return; case RECEIVED_CONNECTION_REQUEST: - log_info("sending hci_accept_connection_request"); + log_info("sending hci_accept_connection_request, remote eSCO %u", connection->remote_supported_feature_eSCO); connection->state = ACCEPTED_CONNECTION_REQUEST; connection->role = HCI_ROLE_SLAVE; if (connection->address_type == BD_ADDR_TYPE_CLASSIC){ hci_send_cmd(&hci_accept_connection_request, connection->address, 1); } else { - hci_send_cmd(&hci_accept_synchronous_connection, connection->address, 8000, 8000, 0xFFFF, hci_stack->sco_voice_setting, 0xFF, 0x003F); + // remote supported feature eSCO is set if link type is eSCO + if (connection->remote_supported_feature_eSCO){ + // eSCO: S4 - max latency == transmission interval = 0x000c == 12 ms, + hci_send_cmd(&hci_accept_synchronous_connection, connection->address, 8000, 8000, 0x000c, hci_stack->sco_voice_setting, 0x02, 0x388); + } else { + // SCO: max latency, retransmission interval: N/A. any packet type + hci_send_cmd(&hci_accept_synchronous_connection, connection->address, 8000, 8000, 0xffff, hci_stack->sco_voice_setting, 0xff, 0x003f); + } } return; @@ -2889,6 +2903,13 @@ void hci_emit_dedicated_bonding_result(bd_addr_t address, uint8_t status){ hci_stack->packet_handler(HCI_EVENT_PACKET, event, sizeof(event)); } +// query if remote side supports eSCO +int hci_remote_eSCO_supported(hci_con_handle_t con_handle){ + hci_connection_t * connection = hci_connection_for_handle(con_handle); + if (!connection) return 0; + return connection->remote_supported_feature_eSCO; +} + // query if remote side supports SSP int hci_remote_ssp_supported(hci_con_handle_t con_handle){ hci_connection_t * connection = hci_connection_for_handle(con_handle); diff --git a/src/hci.h b/src/hci.h index 1dc021a69..11c8f1146 100644 --- a/src/hci.h +++ b/src/hci.h @@ -373,6 +373,9 @@ typedef struct { // link_key_type_t link_key_type; + // remote supported features + uint8_t remote_supported_feature_eSCO; + // errands uint32_t authentication_flags; @@ -710,6 +713,8 @@ void hci_disconnect_security_block(hci_con_handle_t con_handle); // send complete CMD packet int hci_send_cmd_packet(uint8_t *packet, int size); +// query if remote side supports eSCO +int hci_remote_eSCO_supported(hci_con_handle_t con_handle); /* API_START */ diff --git a/test/hfp/hfp_ag_parser_test.c b/test/hfp/hfp_ag_parser_test.c index 4265fee3d..5333ad009 100644 --- a/test/hfp/hfp_ag_parser_test.c +++ b/test/hfp/hfp_ag_parser_test.c @@ -91,7 +91,7 @@ TEST_GROUP(HFPParser){ TEST(HFPParser, HFP_AG_SUPPORTED_FEATURES){ sprintf(packet, "\r\nAT%s=159\r\n", HFP_SUPPORTED_FEATURES); - context.keep_separator = 0; + //context.keep_separator = 0; for (pos = 0; pos < strlen(packet); pos++){ hfp_parse(&context, packet[pos], 0); } @@ -167,19 +167,65 @@ TEST(HFPParser, HFP_AG_ENABLE_INDIVIDUAL_INDICATOR_STATUS_UPDATE){ CHECK_EQUAL(context.ag_indicators[pos].enabled, 0); } } +} - // sprintf(packet, "\r\nAT%s=1,,,1,1,1,\r\n", - // HFP_UPDATE_ENABLE_STATUS_FOR_INDIVIDUAL_AG_INDICATORS); - // for (pos = 0; pos < strlen(packet); pos++){ - // hfp_parse(&context, packet[pos], 0); - // } +TEST(HFPParser, HFP_AG_ENABLE_INDIVIDUAL_INDICATOR_STATUS_UPDATE_OPT_VALUES3){ + set_hfp_ag_indicators((hfp_ag_indicator_t *)&hfp_ag_indicators, hfp_ag_indicators_nr); + context.ag_indicators_nr = hfp_ag_indicators_nr; + memcpy(context.ag_indicators, hfp_ag_indicators, hfp_ag_indicators_nr * sizeof(hfp_ag_indicator_t)); - // CHECK_EQUAL(HFP_CMD_ENABLE_INDIVIDUAL_AG_INDICATOR_STATUS_UPDATE, context.command); + sprintf(packet, "\r\nAT%s=,1,,,,,1\r\n", + HFP_UPDATE_ENABLE_STATUS_FOR_INDIVIDUAL_AG_INDICATORS); + for (pos = 0; pos < strlen(packet); pos++){ + hfp_parse(&context, packet[pos], 0); + } - // for (pos = 0; pos < hfp_ag_indicators_nr; pos++){ - // CHECK_EQUAL(get_hfp_ag_indicators(&context)[pos].enabled, 1); - // CHECK_EQUAL(context.ag_indicators[pos].enabled, 1); - // } + CHECK_EQUAL(HFP_CMD_ENABLE_INDIVIDUAL_AG_INDICATOR_STATUS_UPDATE, context.command); + + for (pos = 0; pos < hfp_ag_indicators_nr; pos++){ + CHECK_EQUAL(get_hfp_ag_indicators(&context)[pos].index, hfp_ag_indicators[pos].index); + CHECK_EQUAL(get_hfp_ag_indicators(&context)[pos].enabled, hfp_ag_indicators[pos].enabled); + CHECK_EQUAL(context.ag_indicators[pos].index, hfp_ag_indicators[pos].index); + CHECK_EQUAL(context.ag_indicators[pos].enabled, hfp_ag_indicators[pos].enabled); + } +} + +TEST(HFPParser, HFP_AG_ENABLE_INDIVIDUAL_INDICATOR_STATUS_UPDATE_OPT_VALUES2){ + set_hfp_ag_indicators((hfp_ag_indicator_t *)&hfp_ag_indicators, hfp_ag_indicators_nr); + context.ag_indicators_nr = hfp_ag_indicators_nr; + memcpy(context.ag_indicators, hfp_ag_indicators, hfp_ag_indicators_nr * sizeof(hfp_ag_indicator_t)); + + sprintf(packet, "\r\nAT%s=1,,,1,1,1,\r\n", + HFP_UPDATE_ENABLE_STATUS_FOR_INDIVIDUAL_AG_INDICATORS); + for (pos = 0; pos < strlen(packet); pos++){ + hfp_parse(&context, packet[pos], 0); + } + + CHECK_EQUAL(HFP_CMD_ENABLE_INDIVIDUAL_AG_INDICATOR_STATUS_UPDATE, context.command); + + for (pos = 0; pos < hfp_ag_indicators_nr; pos++){ + CHECK_EQUAL(get_hfp_ag_indicators(&context)[pos].enabled, 1); + CHECK_EQUAL(context.ag_indicators[pos].enabled, 1); + } +} + +TEST(HFPParser, HFP_AG_ENABLE_INDIVIDUAL_INDICATOR_STATUS_UPDATE_OPT_VALUES1){ + set_hfp_ag_indicators((hfp_ag_indicator_t *)&hfp_ag_indicators, hfp_ag_indicators_nr); + context.ag_indicators_nr = hfp_ag_indicators_nr; + memcpy(context.ag_indicators, hfp_ag_indicators, hfp_ag_indicators_nr * sizeof(hfp_ag_indicator_t)); + + sprintf(packet, "\r\nAT%s=1,,,1,1,1,\r\n", + HFP_UPDATE_ENABLE_STATUS_FOR_INDIVIDUAL_AG_INDICATORS); + for (pos = 0; pos < strlen(packet); pos++){ + hfp_parse(&context, packet[pos], 0); + } + + CHECK_EQUAL(HFP_CMD_ENABLE_INDIVIDUAL_AG_INDICATOR_STATUS_UPDATE, context.command); + + for (pos = 0; pos < hfp_ag_indicators_nr; pos++){ + CHECK_EQUAL(get_hfp_ag_indicators(&context)[pos].enabled, 1); + CHECK_EQUAL(context.ag_indicators[pos].enabled, 1); + } } TEST(HFPParser, HFP_AG_HF_QUERY_OPERATOR_SELECTION){ diff --git a/test/hfp/hfp_hf_client_test.c b/test/hfp/hfp_hf_client_test.c index 9759f78c1..2cce51bab 100644 --- a/test/hfp/hfp_hf_client_test.c +++ b/test/hfp/hfp_hf_client_test.c @@ -71,10 +71,18 @@ const uint8_t rfcomm_channel_nr = 1; static bd_addr_t device_addr = {0xD8,0xBb,0x2C,0xDf,0xF1,0x08}; static uint8_t codecs[2] = {1,2}; +static uint8_t default_codecs[2] = {1}; static uint16_t indicators[1] = {0x01}; static uint8_t service_level_connection_established = 0; static uint8_t codecs_connection_established = 0; +static uint8_t audio_connection_established = 0; +static uint8_t start_ringing = 0; +static uint8_t stop_ringing = 0; +static uint8_t call_termiated = 0; + +static int supported_features_with_codec_negotiation = 438; // 0001 1011 0110 +static int supported_features_without_codec_negotiation = 310; // 0001 0011 0110 int expected_rfcomm_command(const char * cmd){ char * ag_cmd = (char *)get_rfcomm_payload(); @@ -143,28 +151,37 @@ void packet_handler(uint8_t * event, uint16_t event_size){ case HFP_SUBEVENT_SERVICE_LEVEL_CONNECTION_ESTABLISHED: printf("\n** SLC established **\n\n"); service_level_connection_established = 1; + codecs_connection_established = 0; + audio_connection_established = 0; break; case HFP_SUBEVENT_CODECS_CONNECTION_COMPLETE: printf("\n** CC established **\n\n"); codecs_connection_established = 1; + audio_connection_established = 0; break; - case HFP_SUBEVENT_SERVICE_LEVEL_CONNECTION_RELEASED: + printf("\n** SLC released **\n\n"); service_level_connection_established = 0; break; - - case HFP_SUBEVENT_COMPLETE: - printf("HFP_SUBEVENT_COMPLETE.\n\n"); + case HFP_SUBEVENT_AUDIO_CONNECTION_ESTABLISHED: + printf("\n** AC established **\n\n"); + audio_connection_established = 1; break; - case HFP_SUBEVENT_AG_INDICATOR_STATUS_CHANGED: - printf("AG_INDICATOR_STATUS_CHANGED, AG indicator index: %d, status: %d\n", event[4], event[5]); + case HFP_SUBEVENT_AUDIO_CONNECTION_RELEASED: + printf("\n** AC released **\n\n"); + audio_connection_established = 0; break; - case HFP_SUBEVENT_NETWORK_OPERATOR_CHANGED: - printf("NETWORK_OPERATOR_CHANGED, operator mode: %d, format: %d, name: %s\n", event[4], event[5], (char *) &event[6]); + case HFP_SUBEVENT_START_RINGINIG: + printf("\n** Start ringing **\n\n"); + start_ringing = 1; break; - case HFP_SUBEVENT_EXTENDED_AUDIO_GATEWAY_ERROR: - if (event[4]) - printf("EXTENDED_AUDIO_GATEWAY_ERROR_REPORT, status : %d\n", event[3]); + case HFP_SUBEVENT_STOP_RINGINIG: + printf("\n** Stop ringing **\n\n"); + stop_ringing = 1; + start_ringing = 0; + break; + case HFP_SUBEVENT_CALL_TERMINATED: + call_termiated = 1; break; default: printf("event not handled %u\n", event[2]); @@ -179,14 +196,22 @@ TEST_GROUP(HFPClient){ void setup(void){ service_level_connection_established = 0; codecs_connection_established = 0; + audio_connection_established = 0; + start_ringing = 0; + stop_ringing = 0; + call_termiated = 0; + + hfp_hf_init(rfcomm_channel_nr, supported_features_with_codec_negotiation, indicators, sizeof(indicators)/sizeof(uint16_t), 1); + hfp_hf_set_codecs(codecs, sizeof(codecs)); } void teardown(void){ - if (service_level_connection_established){ - hfp_hf_release_service_level_connection(device_addr); - CHECK_EQUAL(service_level_connection_established, 0); - } + hfp_hf_release_audio_connection(device_addr); + hfp_hf_release_service_level_connection(device_addr); + + service_level_connection_established = 0; codecs_connection_established = 0; + audio_connection_established = 0; } void setup_hfp_service_level_connection(char ** test_steps, int nr_test_steps){ @@ -202,6 +227,20 @@ TEST_GROUP(HFPClient){ }; +TEST(HFPClient, HFAudioConnectionEstablishedWithoutCodecNegotiation){ + hfp_hf_init(rfcomm_channel_nr, supported_features_without_codec_negotiation, indicators, sizeof(indicators)/sizeof(uint16_t), 1); + hfp_hf_set_codecs(default_codecs, 1); + + setup_hfp_service_level_connection(hfp_slc_tests()[1].test, hfp_slc_tests()[1].len); + CHECK_EQUAL(service_level_connection_established, 1); + + setup_hfp_codecs_connection(default_cc_setup(), default_cc_setup_size()); + CHECK_EQUAL(codecs_connection_established, 1); + + hfp_hf_establish_audio_connection(device_addr); + hfp_hf_release_audio_connection(device_addr); + CHECK_EQUAL(audio_connection_established, 0); +} TEST(HFPClient, HFCodecsConnectionEstablished){ for (int i = 0; i < cc_tests_size(); i++){ @@ -231,9 +270,6 @@ TEST(HFPClient, HFServiceLevelConnectionEstablished){ int main (int argc, const char * argv[]){ - hfp_hf_init(rfcomm_channel_nr, 438, indicators, sizeof(indicators)/sizeof(uint16_t), 1); - hfp_hf_set_codecs(codecs, sizeof(codecs)); hfp_hf_register_packet_handler(packet_handler); - return CommandLineTestRunner::RunAllTests(argc, argv); } diff --git a/test/pts/hfp_ag_test.c b/test/pts/hfp_ag_test.c index 1785fa43d..57b64abbf 100644 --- a/test/pts/hfp_ag_test.c +++ b/test/pts/hfp_ag_test.c @@ -79,6 +79,15 @@ static uint16_t handle = -1; static int memory_1_enabled = 1; static int last_number_exists = 1; +static int current_call_index = 0; +static hfp_enhanced_call_dir_t current_call_dir; +static int current_call_exists_a = 0; +static int current_call_exists_b = 0; +static hfp_enhanced_call_status_t current_call_status_a; +static hfp_enhanced_call_status_t current_call_status_b; +static hfp_enhanced_call_mpty_t current_call_mpty = HFP_ENHANCED_CALL_MPTY_NOT_A_CONFERENCE_CALL; + + static int ag_indicators_nr = 7; static hfp_ag_indicator_t ag_indicators[] = { // index, name, min range, max range, status, mandatory, enabled, status changed @@ -86,8 +95,8 @@ static hfp_ag_indicator_t ag_indicators[] = { {2, "call", 0, 1, 0, 1, 1, 0}, {3, "callsetup", 0, 3, 0, 1, 1, 0}, {4, "battchg", 0, 5, 3, 0, 0, 0}, - {5, "signal", 0, 5, 5, 0, 0, 0}, - {6, "roam", 0, 1, 0, 0, 0, 0}, + {5, "signal", 0, 5, 5, 0, 1, 0}, + {6, "roam", 0, 1, 0, 0, 1, 0}, {7, "callheld", 0, 2, 0, 1, 1, 0} }; @@ -104,6 +113,168 @@ char cmd; // prototypes static void show_usage(); +// GAP INQUIRY + +#define MAX_DEVICES 10 +enum DEVICE_STATE { REMOTE_NAME_REQUEST, REMOTE_NAME_INQUIRED, REMOTE_NAME_FETCHED }; +struct device { + bd_addr_t address; + uint16_t clockOffset; + uint32_t classOfDevice; + uint8_t pageScanRepetitionMode; + uint8_t rssi; + enum DEVICE_STATE state; +}; + +#define INQUIRY_INTERVAL 5 +struct device devices[MAX_DEVICES]; +int deviceCount = 0; + + +enum STATE {INIT, W4_INQUIRY_MODE_COMPLETE, ACTIVE} ; +enum STATE state = INIT; + + +static int getDeviceIndexForAddress( bd_addr_t addr){ + int j; + for (j=0; j< deviceCount; j++){ + if (BD_ADDR_CMP(addr, devices[j].address) == 0){ + return j; + } + } + return -1; +} + +static void start_scan(void){ + printf("Starting inquiry scan..\n"); + hci_send_cmd(&hci_inquiry, HCI_INQUIRY_LAP, INQUIRY_INTERVAL, 0); +} + +static int has_more_remote_name_requests(void){ + int i; + for (i=0;i= 0) continue; // already in our list + memcpy(devices[deviceCount].address, addr, 6); + + devices[deviceCount].pageScanRepetitionMode = packet[offset]; + offset += 1; + + if (event == HCI_EVENT_INQUIRY_RESULT){ + offset += 2; // Reserved + Reserved + devices[deviceCount].classOfDevice = READ_BT_24(packet, offset); + offset += 3; + devices[deviceCount].clockOffset = READ_BT_16(packet, offset) & 0x7fff; + offset += 2; + devices[deviceCount].rssi = 0; + } else { + offset += 1; // Reserved + devices[deviceCount].classOfDevice = READ_BT_24(packet, offset); + offset += 3; + devices[deviceCount].clockOffset = READ_BT_16(packet, offset) & 0x7fff; + offset += 2; + devices[deviceCount].rssi = packet[offset]; + offset += 1; + } + devices[deviceCount].state = REMOTE_NAME_REQUEST; + printf("Device #%u found: %s with COD: 0x%06x, pageScan %d, clock offset 0x%04x, rssi 0x%02x\n", + deviceCount, bd_addr_to_str(addr), + devices[deviceCount].classOfDevice, devices[deviceCount].pageScanRepetitionMode, + devices[deviceCount].clockOffset, devices[deviceCount].rssi); + deviceCount++; + } + + break; + } + case HCI_EVENT_INQUIRY_COMPLETE: + for (i=0;i= 0) { + if (packet[2] == 0) { + printf("Name: '%s'\n", &packet[9]); + devices[index].state = REMOTE_NAME_FETCHED; + } else { + printf("Failed to get name: page timeout\n"); + } + } + continue_remote_names(); + break; + + default: + break; + } +} +// GAP INQUIRY END + // Testig User Interface static void show_usage(void){ printf("\n--- Bluetooth HFP Hands-Free (HF) unit Test Console ---\n"); @@ -155,7 +326,22 @@ static void show_usage(void){ printf("n - Disable Voice Regocnition\n"); printf("N - Enable Voice Recognition\n"); + printf("o - Set speaker volume to 0 (minimum)\n"); + printf("O - Set speaker volume to 9 (default)\n"); + printf("p - Set speaker volume to 12 (higher)\n"); + printf("P - Set speaker volume to 15 (maximum)\n"); + + printf("q - Set microphone gain to 0 (minimum)\n"); + printf("Q - Set microphone gain to 9 (default)\n"); + printf("s - Set microphone gain to 12 (higher)\n"); + printf("S - Set microphone gain to 15 (maximum)\n"); + printf("t - terminate connection\n"); + printf("u - join held call\n"); + printf("v - discover nearby HF units\n"); + printf("w - put incoming call on hold (Response and Hold)\n"); + printf("x - accept held incoming call (Response and Hold)\n"); + printf("X - reject held incoming call (Response and Hold)\n"); printf("---\n"); printf("Ctrl-c - exit\n"); @@ -193,11 +379,17 @@ static int stdin_process(struct data_source *ds){ break; case 'c': printf("Simulate incoming call from 1234567\n"); + current_call_exists_a = 1; + current_call_status_a = HFP_ENHANCED_CALL_STATUS_INCOMING; + current_call_dir = HFP_ENHANCED_CALL_DIR_INCOMING; hfp_ag_set_clip(129, "1234567"); hfp_ag_incoming_call(); break; case 'm': printf("Simulate incoming call from 7654321\n"); + current_call_exists_b = 1; + current_call_status_b = HFP_ENHANCED_CALL_STATUS_INCOMING; + current_call_dir = HFP_ENHANCED_CALL_DIR_INCOMING; hfp_ag_set_clip(129, "7654321"); hfp_ag_incoming_call(); break; @@ -211,6 +403,12 @@ static int stdin_process(struct data_source *ds){ break; case 'e': printf("Answer call on AG\n"); + if (current_call_status_a == HFP_ENHANCED_CALL_STATUS_INCOMING){ + current_call_status_a = HFP_ENHANCED_CALL_STATUS_ACTIVE; + } + if (current_call_status_b == HFP_ENHANCED_CALL_STATUS_INCOMING){ + current_call_status_b = HFP_ENHANCED_CALL_STATUS_ACTIVE; + } hfp_ag_answer_incoming_call(); break; case 'E': @@ -281,6 +479,38 @@ static int stdin_process(struct data_source *ds){ printf("Enable Voice Recognition\n"); hfp_ag_activate_voice_recognition(device_addr, 1); break; + case 'o': + printf("Set speaker gain to 0 (minimum)\n"); + hfp_ag_set_speaker_gain(device_addr, 0); + break; + case 'O': + printf("Set speaker gain to 9 (default)\n"); + hfp_ag_set_speaker_gain(device_addr, 9); + break; + case 'p': + printf("Set speaker gain to 12 (higher)\n"); + hfp_ag_set_speaker_gain(device_addr, 12); + break; + case 'P': + printf("Set speaker gain to 15 (maximum)\n"); + hfp_ag_set_speaker_gain(device_addr, 15); + break; + case 'q': + printf("Set microphone gain to 0\n"); + hfp_ag_set_microphone_gain(device_addr, 0); + break; + case 'Q': + printf("Set microphone gain to 9\n"); + hfp_ag_set_microphone_gain(device_addr, 9); + break; + case 's': + printf("Set microphone gain to 12\n"); + hfp_ag_set_microphone_gain(device_addr, 12); + break; + case 'S': + printf("Set microphone gain to 15\n"); + hfp_ag_set_microphone_gain(device_addr, 15); + break; case 'R': printf("Enable in-band ring tone\n"); hfp_ag_set_use_in_band_ring_tone(1); @@ -288,6 +518,27 @@ static int stdin_process(struct data_source *ds){ case 't': printf("Terminate HCI connection.\n"); gap_disconnect(handle); + break; + case 'u': + printf("Join held call\n"); + current_call_mpty = HFP_ENHANCED_CALL_MPTY_CONFERENCE_CALL; + hfp_ag_join_held_call(); + break; + case 'v': + start_scan(); + break; + case 'w': + printf("AG: Put incoming call on hold (Response and Hold)\n"); + hfp_ag_hold_incoming_call(); + break; + case 'x': + printf("AG: Accept held incoming call (Response and Hold)\n"); + hfp_ag_accept_held_incoming_call(); + break; + case 'X': + printf("AG: Reject held incoming call (Response and Hold)\n"); + hfp_ag_reject_held_incoming_call(); + break; default: show_usage(); break; @@ -305,8 +556,31 @@ static void packet_handler(uint8_t * event, uint16_t event_size){ return; } + switch (event[0]){ + case RFCOMM_EVENT_OPEN_CHANNEL_COMPLETE: + handle = READ_BT_16(event, 9); + printf("RFCOMM_EVENT_OPEN_CHANNEL_COMPLETE received for handle 0x%04x\n", handle); + return; + + case HCI_EVENT_INQUIRY_RESULT: + case HCI_EVENT_INQUIRY_RESULT_WITH_RSSI: + case HCI_EVENT_INQUIRY_COMPLETE: + case HCI_EVENT_REMOTE_NAME_REQUEST_COMPLETE: + inquiry_packet_handler(HCI_EVENT_PACKET, event, event_size); + break; + + default: + break; + } + + if (event[0] != HCI_EVENT_HFP_META) return; - if (event[3] && event[2] != HFP_SUBEVENT_PLACE_CALL_WITH_NUMBER){ + + if (event[3] + && event[2] != HFP_SUBEVENT_PLACE_CALL_WITH_NUMBER + && event[2] != HFP_SUBEVENT_ATTACH_NUMBER_TO_VOICE_TAG + && event[2] != HFP_SUBEVENT_TRANSMIT_DTMF_CODES + && event[2] != HFP_SUBEVENT_TRANSMIT_STATUS_OF_CURRENT_CALL){ printf("ERROR, status: %u\n", event[3]); return; } @@ -357,24 +631,64 @@ static void packet_handler(uint8_t * event, uint16_t event_size){ hfp_ag_outgoing_call_rejected(); } break; - + case HFP_SUBEVENT_ATTACH_NUMBER_TO_VOICE_TAG: + printf("\n** Attach number to voice tag. Sending '1234567\n"); + hfp_ag_send_phone_number_for_voice_tag(device_addr, "1234567"); + break; + case HFP_SUBEVENT_TRANSMIT_DTMF_CODES: + printf("\n** Send DTMF Codes: '%s'\n", &event[3]); + hfp_ag_send_dtmf_code_done(device_addr); + break; + case HFP_SUBEVENT_TRANSMIT_STATUS_OF_CURRENT_CALL: + if (current_call_index == 0 && current_call_exists_a){ + hfp_ag_send_current_call_status(device_addr, 1, current_call_dir, current_call_status_a, + HFP_ENHANCED_CALL_MODE_VOICE, current_call_mpty, 129, "1234567"); + current_call_index = 1; + break; + } + if (current_call_index == 1 && current_call_exists_b){ + hfp_ag_send_current_call_status(device_addr, 2, current_call_dir, current_call_status_b, + HFP_ENHANCED_CALL_MODE_VOICE, current_call_mpty, 129, "7654321"); + current_call_index = 2; + break; + } + hfp_ag_send_current_call_status_done(device_addr); + break; + case HFP_CMD_CALL_ANSWERED: + printf("Call answered by HF\n"); + if (current_call_status_a == HFP_ENHANCED_CALL_STATUS_INCOMING){ + current_call_status_a = HFP_ENHANCED_CALL_STATUS_ACTIVE; + } + if (current_call_status_b == HFP_ENHANCED_CALL_STATUS_INCOMING){ + current_call_status_b = HFP_ENHANCED_CALL_STATUS_ACTIVE; + } + break; + case HFP_SUBEVENT_CONFERENCE_CALL: + current_call_mpty = HFP_ENHANCED_CALL_MPTY_CONFERENCE_CALL; + current_call_status_a = HFP_ENHANCED_CALL_STATUS_ACTIVE; + current_call_status_b = HFP_ENHANCED_CALL_STATUS_ACTIVE; + break; default: - // printf("event not handled %u\n", event[2]); + printf("Event not handled %u\n", event[2]); break; } } +static hfp_phone_number_t subscriber_number = { + 129, "225577" +}; + int btstack_main(int argc, const char * argv[]); int btstack_main(int argc, const char * argv[]){ // init L2CAP l2cap_init(); rfcomm_init(); - hfp_ag_init(rfcomm_channel_nr, 0x3ef | (1<fd, &cmd, 1); + + if (cmd >= '0' && cmd <= '9'){ + printf("DTMF Code: %c\n", cmd); + hfp_hf_send_dtmf_code(device_addr, cmd); + return 0; + } + switch (cmd){ + case '#': + case '-': + case '+': + case '*': + printf("DTMF Code: %c\n", cmd); + hfp_hf_send_dtmf_code(device_addr, cmd); + break; case 'a': - printf("Establish Audio connection to device with Bluetooth address %s...\n", bd_addr_to_str(device_addr)); - hfp_hf_establish_audio_connection(device_addr); - break; - case 'A': - printf("Release Audio service level connection.\n"); - hfp_hf_release_audio_connection(device_addr); - break; - case 'h': - printf("Establish HFP service level connection to device with Bluetooth address %s...\n", bd_addr_to_str(device_addr)); + printf("Establish Service level connection to device with Bluetooth address %s...\n", bd_addr_to_str(device_addr)); hfp_hf_establish_service_level_connection(device_addr); break; - case 'H': - printf("Release HFP service level connection.\n"); + case 'A': + printf("Release Service level connection.\n"); hfp_hf_release_service_level_connection(device_addr); break; case 'b': - printf("Establish Audio connection %s...\n", bd_addr_to_str(device_addr)); + printf("Establish Audio connection to device with Bluetooth address %s...\n", bd_addr_to_str(device_addr)); hfp_hf_establish_audio_connection(device_addr); break; case 'B': - printf("Release Audio connection.\n"); + printf("Release Audio service level connection.\n"); hfp_hf_release_audio_connection(device_addr); break; - case 'd': - printf("Enable HFP AG registration status update.\n"); - hfp_hf_enable_status_update_for_all_ag_indicators(device_addr, 1); + case 'C': + printf("Enable registration status update for all AG indicators.\n"); + hfp_hf_enable_status_update_for_all_ag_indicators(device_addr); + case 'c': + printf("Disable registration status update for all AG indicators.\n"); + hfp_hf_disable_status_update_for_all_ag_indicators(device_addr); + break; case 'D': - printf("Disable HFP AG registration status update.\n"); - hfp_hf_enable_status_update_for_all_ag_indicators(device_addr, 0); + printf("Set HFP AG registration status update for individual indicators (0111111).\n"); + hfp_hf_set_status_update_for_individual_ag_indicators(device_addr, 63); break; - case 'e': - printf("Enable HFP AG registration status update for individual indicators.\n"); - hfp_hf_enable_status_update_for_individual_ag_indicators(device_addr, 63); - break; - case 'f': + case 'd': printf("Query network operator.\n"); hfp_hf_query_operator_selection(device_addr); break; - case 'g': + case 'E': printf("Enable reporting of the extended AG error result code.\n"); - hfp_hf_enable_report_extended_audio_gateway_error_result_code(device_addr, 1); + hfp_hf_enable_report_extended_audio_gateway_error_result_code(device_addr); + break; + case 'e': + printf("Disable reporting of the extended AG error result code.\n"); + hfp_hf_disable_report_extended_audio_gateway_error_result_code(device_addr); + break; + case 'f': + printf("Answer incoming call.\n"); + hfp_hf_answer_incoming_call(device_addr); + break; + case 'F': + printf("Hangup call.\n"); + hfp_hf_terminate_call(device_addr); break; case 'G': - printf("Disable reporting of the extended AG error result code.\n"); - hfp_hf_enable_report_extended_audio_gateway_error_result_code(device_addr, 0); + printf("Reject call.\n"); + hfp_hf_reject_call(device_addr); + break; + case 'g': + printf("Query operator.\n"); + hfp_hf_query_operator_selection(device_addr); + break; + case 't': + printf("Terminate HCI connection.\n"); + gap_disconnect(handle); break; - case 'y': memcpy(device_addr, phone_addr, 6); printf("Use iPhone %s as Audiogateway.\n", bd_addr_to_str(device_addr)); break; - case 'z': - memcpy(device_addr, pts_addr, 6); - printf("Use PTS module %s as Audiogateway.\n", bd_addr_to_str(device_addr)); + case 'i': + printf("Dial 1234567\n"); + hfp_hf_dial_number(device_addr, "1234567"); + break; + case 'I': + printf("Dial 7654321\n"); + hfp_hf_dial_number(device_addr, "7654321"); + break; + case 'j': + printf("Dial #1\n"); + hfp_hf_dial_memory(device_addr,"1"); + break; + case 'J': + printf("Dial #99\n"); + hfp_hf_dial_memory(device_addr,"99"); + break; + case 'k': + printf("Deactivate call waiting notification\n"); + hfp_hf_deactivate_call_waiting_notification(device_addr); + break; + case 'K': + printf("Activate call waiting notification\n"); + hfp_hf_activate_call_waiting_notification(device_addr); + break; + case 'l': + printf("Deactivate calling line notification\n"); + hfp_hf_deactivate_calling_line_notification(device_addr); + break; + case 'L': + printf("Activate calling line notification\n"); + hfp_hf_activate_calling_line_notification(device_addr); + break; + case 'm': + printf("Deactivate echo canceling and noise reduction\n"); + hfp_hf_deactivate_echo_canceling_and_noise_reduction(device_addr); + break; + case 'M': + printf("Activate echo canceling and noise reduction\n"); + hfp_hf_activate_echo_canceling_and_noise_reduction(device_addr); + break; + case 'n': + printf("Deactivate voice recognition\n"); + hfp_hf_deactivate_voice_recognition_notification(device_addr); + break; + case 'N': + printf("Activate voice recognition\n"); + hfp_hf_activate_voice_recognition_notification(device_addr); + break; + case 'o': + printf("Set speaker gain to 0 (minimum)\n"); + hfp_hf_set_speaker_gain(device_addr, 0); + break; + case 'O': + printf("Set speaker gain to 9 (default)\n"); + hfp_hf_set_speaker_gain(device_addr, 9); + break; + case 'p': + printf("Set speaker gain to 12 (higher)\n"); + hfp_hf_set_speaker_gain(device_addr, 12); + break; + case 'P': + printf("Set speaker gain to 15 (maximum)\n"); + hfp_hf_set_speaker_gain(device_addr, 15); + break; + case 'q': + printf("Set microphone gain to 0\n"); + hfp_hf_set_microphone_gain(device_addr, 0); + break; + case 'Q': + printf("Set microphone gain to 9\n"); + hfp_hf_set_microphone_gain(device_addr, 9); + break; + case 'u': + printf("Send 'user busy' (Three-Way Call 0)\n"); + hfp_hf_user_busy(device_addr); + break; + case 'U': + printf("End active call and accept waiting/held call (Three-Way Call 1)\n"); + hfp_hf_end_active_and_accept_other(device_addr); + break; + case 'v': + printf("Swap active call and hold/waiting call (Three-Way Call 2)\n"); + hfp_hf_swap_calls(device_addr); + break; + case 'V': + printf("Join hold call (Three-Way Call 3)\n"); + hfp_hf_join_held_call(device_addr); + break; + case 'w': + printf("Connect calls (Three-Way Call 4)\n"); + hfp_hf_connect_calls(device_addr); + break; + case 'W': + printf("Redial\n"); + hfp_hf_redial_last_number(device_addr); + break; + case 'x': + printf("Request phone number for voice tag\n"); + hfp_hf_request_phone_number_for_voice_tag(device_addr); break; - default: show_usage(); break; @@ -185,8 +349,17 @@ static int stdin_process(struct data_source *ds){ static void packet_handler(uint8_t * event, uint16_t event_size){ + if (event[0] == RFCOMM_EVENT_OPEN_CHANNEL_COMPLETE){ + handle = READ_BT_16(event, 9); + printf("RFCOMM_EVENT_OPEN_CHANNEL_COMPLETE received for handle 0x%04x\n", handle); + return; + } if (event[0] != HCI_EVENT_HFP_META) return; - if (event[3] && event[2] != HFP_SUBEVENT_EXTENDED_AUDIO_GATEWAY_ERROR){ + if (event[3] + && event[2] != HFP_SUBEVENT_EXTENDED_AUDIO_GATEWAY_ERROR + && event[2] != HFP_SUBEVENT_NUMBER_FOR_VOICE_TAG + && event[2] != HFP_SUBEVENT_SPEAKER_VOLUME + && event[2] != HFP_SUBEVENT_MICROPHONE_VOLUME){ printf("ERROR, status: %u\n", event[3]); return; } @@ -197,6 +370,12 @@ static void packet_handler(uint8_t * event, uint16_t event_size){ case HFP_SUBEVENT_SERVICE_LEVEL_CONNECTION_RELEASED: printf("Service level connection released.\n\n"); break; + case HFP_SUBEVENT_AUDIO_CONNECTION_ESTABLISHED: + printf("\n** Audio connection established **\n"); + break; + case HFP_SUBEVENT_AUDIO_CONNECTION_RELEASED: + printf("\n** Audio connection released **\n"); + break; case HFP_SUBEVENT_COMPLETE: switch (cmd){ case 'd': @@ -209,7 +388,7 @@ static void packet_handler(uint8_t * event, uint16_t event_size){ } break; case HFP_SUBEVENT_AG_INDICATOR_STATUS_CHANGED: - printf("AG_INDICATOR_STATUS_CHANGED, AG indicator index: %d, status: %d\n", event[4], event[5]); + printf("AG_INDICATOR_STATUS_CHANGED, AG indicator '%s' (index: %d) to: %d\n", (const char*) &event[6], event[4], event[5]); break; case HFP_SUBEVENT_NETWORK_OPERATOR_CHANGED: printf("NETWORK_OPERATOR_CHANGED, operator mode: %d, format: %d, name: %s\n", event[4], event[5], (char *) &event[6]); @@ -218,6 +397,18 @@ static void packet_handler(uint8_t * event, uint16_t event_size){ if (event[4]) printf("EXTENDED_AUDIO_GATEWAY_ERROR_REPORT, status : %d\n", event[3]); break; + case HFP_SUBEVENT_RING: + printf("** Ring **\n"); + break; + case HFP_SUBEVENT_NUMBER_FOR_VOICE_TAG: + printf("Phone number for voice tag: %s\n", (const char *) &event[3]); + break; + case HFP_SUBEVENT_SPEAKER_VOLUME: + printf("Speaker volume: %u\n", event[3]); + break; + case HFP_SUBEVENT_MICROPHONE_VOLUME: + printf("Microphone volume: %u\n", event[3]); + break; default: printf("event not handled %u\n", event[2]); break; @@ -231,7 +422,7 @@ int btstack_main(int argc, const char * argv[]){ rfcomm_init(); // hfp_hf_init(rfcomm_channel_nr, HFP_DEFAULT_HF_SUPPORTED_FEATURES, codecs, sizeof(codecs), indicators, sizeof(indicators)/sizeof(uint16_t), 1); - hfp_hf_init(rfcomm_channel_nr, 438, indicators, sizeof(indicators)/sizeof(uint16_t), 1); + hfp_hf_init(rfcomm_channel_nr, 438 | (1<