From 268602ef870b1518c8903063697bec34e5a3d597 Mon Sep 17 00:00:00 2001 From: "mila@ringwald.ch" Date: Thu, 26 Feb 2015 16:38:50 +0000 Subject: [PATCH] split hsp ag and hs, under development --- example/embedded/Makefile.inc | 10 +- example/embedded/hsp_ag.c | 606 ++++++++++++++++++++++++++++++++++ example/embedded/hsp_hs.c | 551 +++++++++++++++++++++++++++++++ 3 files changed, 1162 insertions(+), 5 deletions(-) create mode 100644 example/embedded/hsp_ag.c create mode 100644 example/embedded/hsp_hs.c diff --git a/example/embedded/Makefile.inc b/example/embedded/Makefile.inc index 6e2cec3c7..f6304b251 100644 --- a/example/embedded/Makefile.inc +++ b/example/embedded/Makefile.inc @@ -68,11 +68,11 @@ EXAMPLES = \ spp_and_le_counter \ spp_counter \ spp_streamer \ - hfp_test \ - hsp_test \ + hsp_ag \ + hsp_hs \ # requires termios / command line support -EXAMPLES_CLI = ble_peripheral ble_peripheral_sm_minimal l2cap_test ancs_client classic_test bnep_test +EXAMPLES_CLI = ble_peripheral ble_peripheral_sm_minimal l2cap_test ancs_client classic_test bnep_test # .o for .c CORE_OBJ = $(CORE:.c=.o) @@ -158,10 +158,10 @@ ble_peripheral: profile.h ${CORE_OBJ} ${COMMON_OBJ} ${ATT_OBJ} ${GATT_SERVER_OBJ ble_peripheral_sm_minimal: profile.h ${CORE_OBJ} ${COMMON_OBJ} ${ATT_OBJ} ${GATT_SERVER_OBJ} ${SM_MINIMAL_OBJ} ble_peripheral.c ${CC} $(filter-out profile.h,$^) ${CFLAGS} ${LDFLAGS} -o $@ -hfp_test: ${CORE_OBJ} ${COMMON_OBJ} ${SDP_CLIENT} hfp_test.c +hsp_ag: ${CORE_OBJ} ${COMMON_OBJ} ${SDP_CLIENT} hsp_ag.c ${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@ -hsp_test: ${CORE_OBJ} ${COMMON_OBJ} ${SDP_CLIENT} hsp_test.c +hsp_hs: ${CORE_OBJ} ${COMMON_OBJ} ${SDP_CLIENT} hsp_hs.c ${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@ clean: diff --git a/example/embedded/hsp_ag.c b/example/embedded/hsp_ag.c new file mode 100644 index 000000000..7f2560661 --- /dev/null +++ b/example/embedded/hsp_ag.c @@ -0,0 +1,606 @@ +/* + * Copyright (C) 2014 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS + * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +// ***************************************************************************** +// +// Minimal setup for HSP Audio Gateway (!! UNDER DEVELOPMENT !!) +// +// ***************************************************************************** + +#include "btstack-config.h" + +#include +#include +#include +#include + +#include +#include + +#include "hci.h" +#include "btstack_memory.h" +#include "hci_dump.h" +#include "l2cap.h" +#include "sdp_query_rfcomm.h" +#include "sdp.h" +#include "debug.h" + +#define RFCOMM_SERVER_CHANNEL 1 + +#define HSP_HS_BUTTON_PRESS "AT+CKPD=200\r" +#define HSP_HS_AT_CKPD "AT+CKPD\r" +#define HSP_AG_OK "\r\nOK\r\n" +#define HSP_AG_ERROR "\r\nERROR\r\n" +#define HSP_AG_RING "\r\nRING\r\n" +#define HSP_MICROPHONE_GAIN "+VGM" +#define HSP_SPEAKER_GAIN "+VGS" + +#define HSP_HS_MICROPHONE_GAIN "AT+VGM=" +#define HSP_HS_SPEAKER_GAIN "AT+VGS=" + +static const char default_hsp_ag_service_name[] = "Audio Gateway"; + +static bd_addr_t remote = {0x00, 0x21, 0x3C, 0xAC, 0xF7, 0x38}; +static uint8_t channel_nr = 0; + +static uint16_t mtu; +static uint16_t rfcomm_cid = 0; +static uint16_t sco_handle = 0; +static uint16_t rfcomm_handle = 0; + +// static uint8_t connection_state = 0; + +static int ag_microphone_gain = -1; +static int ag_speaker_gain = -1; +static uint8_t ag_ring = 0; +static uint8_t ag_send_ok = 0; +static uint8_t ag_send_error = 0; +static uint8_t ag_in_band_ring_tone = 0; +static uint8_t ag_at_ckpd_received = 0; +static uint8_t ag_button_press_received = 0; + +static uint8_t hsp_service_buffer[150]; + +typedef enum { + HSP_IDLE, + HSP_SDP_QUERY_RFCOMM_CHANNEL, + HSP_W4_SDP_QUERY_COMPLETE, + HSP_W4_RFCOMM_CONNECTED, + HSP_W4_RING_ANSWER, + HSP_W2_CONNECT_SCO, + HSP_W4_SCO_CONNECTED, + HSP_W4_AT_CMD, + HSP_ACTIVE, + HSP_W2_DISCONNECT_SCO, + HSP_W4_SCO_DISCONNECTED, + HSP_W2_DISCONNECT_RFCOMM, + HSP_W4_RFCOMM_DISCONNECTED, + HSP_W4_CONNECTION_ESTABLISHED_TO_SHUTDOWN +} hsp_state_t; + +static hsp_state_t hsp_state = HSP_IDLE; + +static void hsp_run(); + +// remote audio volume control +// AG +VGM=13 [0..15] ; HS AT+VGM=6 | AG OK + +static int send_str_over_rfcomm(uint16_t cid, char * command){ + if (!rfcomm_can_send_packet_now(rfcomm_cid)) return 1; + int err = rfcomm_send_internal(cid, (uint8_t*) command, strlen(command)); + if (err){ + printf("rfcomm_send_internal -> error 0X%02x", err); + } + return err; +} + +void hsp_ag_init(){ + hsp_state = HSP_IDLE; + + rfcomm_cid = 0; + rfcomm_handle = 0; + sco_handle = 0; + + ag_send_ok = 0; + ag_send_error = 0; + ag_ring = 0; + + ag_at_ckpd_received = 0; + ag_button_press_received = 0; + + ag_microphone_gain = -1; + ag_speaker_gain = -1; + ag_in_band_ring_tone = 0; +} + + +void ag_enable_in_band_ring_tone(int enabled){ + // send is not yet implemented + ag_in_band_ring_tone = enabled; +} + +void hsp_ag_connect(bd_addr_t bd_addr){ + if (hsp_state != HSP_IDLE) return; + hsp_state = HSP_SDP_QUERY_RFCOMM_CHANNEL; + memcpy(remote, bd_addr, 6); + hsp_run(); +} + +void hsp_ag_disconnect(){ + switch (hsp_state){ + case HSP_ACTIVE: + hsp_state = HSP_W2_DISCONNECT_SCO; + break; + case HSP_W2_CONNECT_SCO: + hsp_state = HSP_W2_DISCONNECT_RFCOMM; + break; + + case HSP_W4_RFCOMM_CONNECTED: + case HSP_W4_SCO_CONNECTED: + hsp_state = HSP_W4_CONNECTION_ESTABLISHED_TO_SHUTDOWN; + break; + default: + return; + } + hsp_run(); +} + +void hsp_ag_set_microphone_gain(uint8_t gain){ + if (gain < 0 || gain >15) { + printf("Gain must be in interval [0..15], it is given %d\n", gain); + return; + } + ag_microphone_gain = gain; + hsp_run(); +} + +// AG +VGS=5 [0..15] ; HS AT+VGM=6 | AG OK +void hsp_ag_set_speaker_gain(uint8_t gain){ + if (gain < 0 || gain >15) { + printf("Gain must be in interval [0..15], it is given %d\n", gain); + return; + } + ag_speaker_gain = gain; + hsp_run(); +} + + + +static void hsp_run(){ + int err; + + if (ag_send_ok){ + err = send_str_over_rfcomm(rfcomm_cid, HSP_AG_OK); + if (!err) ag_send_ok = 0; + return; + } + + if (ag_send_error){ + err = send_str_over_rfcomm(rfcomm_cid, HSP_AG_ERROR); + if (!err) ag_send_error = 0; + return; + } + if (ag_ring){ + err = send_str_over_rfcomm(rfcomm_cid, HSP_AG_RING); + if (!err) ag_ring = 0; + return; + } + + + + switch (hsp_state){ + case HSP_SDP_QUERY_RFCOMM_CHANNEL: + hsp_state = HSP_W4_SDP_QUERY_COMPLETE; + printf("Start SDP query %s, 0x%02x\n", bd_addr_to_str(remote), SDP_Headset_HS); + sdp_query_rfcomm_channel_and_name_for_uuid(remote, SDP_Headset_HS); + break; + case HSP_W4_RING_ANSWER: + // ring once explicitely after the RFCOMM channel is created + if (ag_ring){ + err = send_str_over_rfcomm(rfcomm_cid, HSP_AG_RING); + if (!err) { + printf("ring once explicitely after the RFCOMM channel is created\n"); + ag_ring = 0; + } + break; + } + + if (ag_in_band_ring_tone){ + log_error("n_band_ring_tone: send is not yet implemented"); + break; + } + + if (!ag_button_press_received){ + printf("keep ringing, until button pressed received \n"); + // keep ringing, until button pressed received + // send_str_over_rfcomm(rfcomm_cid, HSP_AG_RING); + break; + } + ag_button_press_received = 0; + printf("received button press, send OK\n"); + ag_send_ok = 1; + err = send_str_over_rfcomm(rfcomm_cid, HSP_AG_OK); + if (!err) { + hsp_state = HSP_W2_CONNECT_SCO; + ag_send_ok = 0; + } + break; + + case HSP_W4_AT_CMD: + if (ag_at_ckpd_received){ + ag_send_ok = 1; + ag_at_ckpd_received = 0; + } + + if (ag_send_ok){ + err = send_str_over_rfcomm(rfcomm_cid, HSP_AG_OK); + if (!err) ag_send_ok = 0; + hsp_state = HSP_W2_CONNECT_SCO; + } + break; + + case HSP_W2_CONNECT_SCO: + printf("HSP_W2_CONNECT_SCO\n"); + + if (!hci_can_send_command_packet_now()) break; + hsp_state = HSP_W4_SCO_CONNECTED; + hci_send_cmd(&hci_setup_synchronous_connection_command, rfcomm_handle, 8000, 8000, 0xFFFF, 0x0060, 0xFF, 0x003F); + break; + + + case HSP_W2_DISCONNECT_SCO: + hsp_state = HSP_W4_SCO_DISCONNECTED; + gap_disconnect(sco_handle); + break; + + case HSP_W2_DISCONNECT_RFCOMM: + hsp_state = HSP_W4_RFCOMM_DISCONNECTED; + rfcomm_disconnect_internal(rfcomm_cid); + break; + case HSP_ACTIVE: + + if (ag_microphone_gain >= 0){ + char buffer[10]; + sprintf(buffer, "%s=%d\r", HSP_MICROPHONE_GAIN, ag_microphone_gain); + err = send_str_over_rfcomm(rfcomm_cid, buffer); + if (!err) ag_microphone_gain = -1; + break; + } + + if (ag_speaker_gain >= 0){ + char buffer[10]; + sprintf(buffer, "%s=%d\r", HSP_SPEAKER_GAIN, ag_speaker_gain); + err = send_str_over_rfcomm(rfcomm_cid, buffer); + if (!err) ag_speaker_gain = -1; + break; + } + + break; + default: + break; + } +} + + +static void packet_handler (void * connection, uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ + // printf("packet_handler type %u, packet[0] %x\n", packet_type, packet[0]); + if (packet_type == RFCOMM_DATA_PACKET){ + if (strncmp((char *)packet, HSP_HS_BUTTON_PRESS, 11) == 0){ + printf("Received button press %s\n", HSP_HS_BUTTON_PRESS); + // hsp_state: HSP_W4_RING_ANSWER + ag_button_press_received = 1; + + } else if (strncmp((char *)packet, HSP_HS_AT_CKPD, 7) == 0){ + printf("Received AT+CKPD %s\n", HSP_HS_AT_CKPD); + ag_at_ckpd_received = 1; + } else if (strncmp((char *)packet, HSP_HS_MICROPHONE_GAIN, 7) == 0 || + strncmp((char *)packet, HSP_HS_SPEAKER_GAIN, 7) == 0){ + // uint8_t gain = packet[8]; + // TODO: parse gain + printf("Received changed gain info %c\n", packet[8]); + ag_send_ok = 1; + } else if (strncmp((char *)packet, "AT+", 3) == 0){ + ag_send_error = 1; + log_info("Received not yet supported AT command.\n"); + } + hsp_run(); + return; + } + + if (packet_type != HCI_EVENT_PACKET) return; + uint8_t event = packet[0]; + bd_addr_t event_addr; + uint16_t handle; + + switch (event) { + case BTSTACK_EVENT_STATE: + // bt stack activated, get started + if (packet[2] == HCI_STATE_WORKING){ + printf("Try to connect.\n"); + hsp_ag_connect(remote); + } + break; + + case HCI_EVENT_PIN_CODE_REQUEST: + // inform about pin code request + printf("Pin code request - using '0000'\n\r"); + bt_flip_addr(event_addr, &packet[2]); + hci_send_cmd(&hci_pin_code_request_reply, &event_addr, 4, "0000"); + break; + case HCI_EVENT_SYNCHRONOUS_CONNECTION_COMPLETE:{ + int index = 2; + uint8_t status = packet[index++]; + sco_handle = READ_BT_16(packet, index); + index+=2; + bd_addr_t address; + memcpy(address, &packet[index], 6); + index+=6; + uint8_t link_type = packet[index++]; + uint8_t transmission_interval = packet[index++]; // measured in slots + uint8_t retransmission_interval = packet[index++];// measured in slots + uint16_t rx_packet_length = READ_BT_16(packet, index); // measured in bytes + index+=2; + uint16_t tx_packet_length = READ_BT_16(packet, index); // measured in bytes + index+=2; + uint8_t air_mode = packet[index]; + + if (status != 0){ + log_error("(e)SCO Connection is not established, status %u", status); + exit(0); + break; + } + switch (link_type){ + case 0x00: + printf("SCO Connection established. \n"); + if (transmission_interval != 0) log_error("SCO Connection: transmission_interval not zero: %d.", transmission_interval); + if (retransmission_interval != 0) log_error("SCO Connection: retransmission_interval not zero: %d.", retransmission_interval); + if (rx_packet_length != 0) log_error("SCO Connection: rx_packet_length not zero: %d.", rx_packet_length); + if (tx_packet_length != 0) log_error("SCO Connection: tx_packet_length not zero: %d.", tx_packet_length); + break; + case 0x02: + printf("eSCO Connection established. \n"); + break; + default: + log_error("(e)SCO reserved link_type 0x%2x", link_type); + break; + } + log_info("sco_handle 0x%2x, address %s, transmission_interval %u slots, retransmission_interval %u slots, " + " rx_packet_length %u bytes, tx_packet_length %u bytes, air_mode 0x%2x (0x02 == CVSD)", sco_handle, + bd_addr_to_str(address), transmission_interval, retransmission_interval, rx_packet_length, tx_packet_length, air_mode); + + if (hsp_state == HSP_W4_CONNECTION_ESTABLISHED_TO_SHUTDOWN){ + hsp_state = HSP_W2_DISCONNECT_SCO; + break; + } + hsp_state = HSP_ACTIVE; + break; + } + + case RFCOMM_EVENT_INCOMING_CONNECTION: + // data: event (8), len(8), address(48), channel (8), rfcomm_cid (16) + if (hsp_state != HSP_IDLE) return; + + bt_flip_addr(event_addr, &packet[2]); + rfcomm_cid = READ_BT_16(packet, 9); + printf("RFCOMM channel %u requested for %s\n", packet[8], bd_addr_to_str(event_addr)); + rfcomm_accept_connection_internal(rfcomm_cid); + + hsp_state = HSP_W4_AT_CMD; + + break; + + case RFCOMM_EVENT_OPEN_CHANNEL_COMPLETE: + printf("RFCOMM_EVENT_OPEN_CHANNEL_COMPLETE packet_handler type %u, packet[0] %x\n", packet_type, packet[0]); + // data: event(8), len(8), status (8), address (48), handle(16), server channel(8), rfcomm_cid(16), max frame size(16) + if (packet[2]) { + printf("RFCOMM channel open failed, status %u\n", packet[2]); + hsp_state = HSP_IDLE; + } else { + // data: event(8) , len(8), status (8), address (48), handle (16), server channel(8), rfcomm_cid(16), max frame size(16) + rfcomm_handle = READ_BT_16(packet, 9); + rfcomm_cid = READ_BT_16(packet, 12); + mtu = READ_BT_16(packet, 14); + printf("RFCOMM channel open succeeded. New RFCOMM Channel ID %u, max frame size %u, state %d\n", rfcomm_cid, mtu, hsp_state); + + switch (hsp_state){ + case HSP_W4_RFCOMM_CONNECTED: + hsp_state = HSP_W4_RING_ANSWER; + printf("enter HSP_W4_RING_ANSWER\n"); + ag_ring = 1; + break; + case HSP_W4_CONNECTION_ESTABLISHED_TO_SHUTDOWN: + hsp_state = HSP_W2_DISCONNECT_RFCOMM; + break; + default: + printf("no valid state\n"); + break; + } + } + break; + case DAEMON_EVENT_HCI_PACKET_SENT: + case RFCOMM_EVENT_CREDITS: + break; + + case HCI_EVENT_DISCONNECTION_COMPLETE: + if (hsp_state != HSP_W4_SCO_DISCONNECTED){ + log_info("received gap disconnect in wrong hsp state"); + } + handle = READ_BT_16(packet,3); + if (handle == sco_handle){ + sco_handle = 0; + hsp_state = HSP_W2_DISCONNECT_RFCOMM; + break; + } + break; + case RFCOMM_EVENT_CHANNEL_CLOSED: + if (hsp_state != HSP_W4_RFCOMM_DISCONNECTED){ + log_info("received RFCOMM disconnect in wrong hsp state"); + } + printf("RFCOMM channel closed\n"); + hsp_state = HSP_IDLE; + hsp_ag_init(); + break; + default: + break; + } + hsp_run(); +} + +void handle_query_rfcomm_event(sdp_query_event_t * event, void * context){ + sdp_query_rfcomm_service_event_t * ve; + sdp_query_complete_event_t * ce; + + switch (event->type){ + case SDP_QUERY_RFCOMM_SERVICE: + ve = (sdp_query_rfcomm_service_event_t*) event; + channel_nr = ve->channel_nr; + printf("** Service name: '%s', RFCOMM port %u\n", ve->service_name, channel_nr); + break; + case SDP_QUERY_COMPLETE: + ce = (sdp_query_complete_event_t*) event; + + if (channel_nr > 0){ + hsp_state = HSP_W4_RFCOMM_CONNECTED; + printf("RFCOMM create channel. state %d\n", HSP_W4_RFCOMM_CONNECTED); + rfcomm_create_channel_internal(NULL, &remote, channel_nr); + break; + } + hsp_state = HSP_IDLE; + printf("Service not found, status %u.\n", ce->status); + exit(0); + break; + } +} + +void hsp_ag_create_service(uint8_t * service, int rfcomm_channel_nr, const char * name){ + uint8_t* attribute; + de_create_sequence(service); + + // 0x0000 "Service Record Handle" + de_add_number(service, DE_UINT, DE_SIZE_16, SDP_ServiceRecordHandle); + de_add_number(service, DE_UINT, DE_SIZE_32, 0x10001); + + // 0x0001 "Service Class ID List" + de_add_number(service, DE_UINT, DE_SIZE_16, SDP_ServiceClassIDList); + attribute = de_push_sequence(service); + { + // "UUID for PAN Service" + de_add_number(attribute, DE_UUID, DE_SIZE_16, SDP_Headset_AG); + de_add_number(attribute, DE_UUID, DE_SIZE_16, SDP_GenericAudio); + } + de_pop_sequence(service, attribute); + + // 0x0004 "Protocol Descriptor List" + de_add_number(service, DE_UINT, DE_SIZE_16, SDP_ProtocolDescriptorList); + attribute = de_push_sequence(service); + { + uint8_t* l2cpProtocol = de_push_sequence(attribute); + { + de_add_number(l2cpProtocol, DE_UUID, DE_SIZE_16, SDP_L2CAPProtocol); + } + de_pop_sequence(attribute, l2cpProtocol); + + uint8_t* rfcomm = de_push_sequence(attribute); + { + de_add_number(rfcomm, DE_UUID, DE_SIZE_16, SDP_RFCOMMProtocol); // rfcomm_service + de_add_number(rfcomm, DE_UINT, DE_SIZE_8, rfcomm_channel_nr); // rfcomm channel + } + de_pop_sequence(attribute, rfcomm); + } + de_pop_sequence(service, attribute); + + // 0x0005 "Public Browse Group" + de_add_number(service, DE_UINT, DE_SIZE_16, SDP_BrowseGroupList); // public browse group + attribute = de_push_sequence(service); + { + de_add_number(attribute, DE_UUID, DE_SIZE_16, SDP_PublicBrowseGroup); + } + de_pop_sequence(service, attribute); + + // 0x0009 "Bluetooth Profile Descriptor List" + de_add_number(service, DE_UINT, DE_SIZE_16, SDP_BluetoothProfileDescriptorList); + attribute = de_push_sequence(service); + { + uint8_t *sppProfile = de_push_sequence(attribute); + { + de_add_number(sppProfile, DE_UUID, DE_SIZE_16, SDP_HSP); + de_add_number(sppProfile, DE_UINT, DE_SIZE_16, 0x0102); // Verision 1.2 + } + de_pop_sequence(attribute, sppProfile); + } + de_pop_sequence(service, attribute); + + // 0x0100 "Service Name" + de_add_number(service, DE_UINT, DE_SIZE_16, 0x0100); + if (name){ + de_add_data(service, DE_STRING, strlen(name), (uint8_t *) name); + } else { + de_add_data(service, DE_STRING, strlen(default_hsp_ag_service_name), (uint8_t *) default_hsp_ag_service_name); + } +} + +int btstack_main(int argc, const char * argv[]); +int btstack_main(int argc, const char * argv[]){ + + printf("Client HCI init done\r\n"); + + // init L2CAP + l2cap_init(); + l2cap_register_packet_handler(packet_handler); + + rfcomm_init(); + rfcomm_register_packet_handler(packet_handler); + rfcomm_register_service_internal(NULL, RFCOMM_SERVER_CHANNEL, 0xffff); // reserved channel, mtu limited by l2cap + + // init SDP, create record for SPP and register with SDP + sdp_init(); + memset(hsp_service_buffer, 0, sizeof(hsp_service_buffer)); + + hsp_ag_create_service(hsp_service_buffer, RFCOMM_SERVER_CHANNEL, NULL); + hsp_ag_init(); + + sdp_register_service_internal(NULL, hsp_service_buffer); + + sdp_query_rfcomm_register_callback(handle_query_rfcomm_event, NULL); + + // turn on! + hci_power_control(HCI_POWER_ON); + + // go! + run_loop_execute(); + return 0; +} diff --git a/example/embedded/hsp_hs.c b/example/embedded/hsp_hs.c new file mode 100644 index 000000000..085e712c8 --- /dev/null +++ b/example/embedded/hsp_hs.c @@ -0,0 +1,551 @@ +/* + * Copyright (C) 2014 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS + * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +// ***************************************************************************** +// +// Minimal setup for HSP Headset (!! UNDER DEVELOPMENT !!) +// +// ***************************************************************************** + +#include "btstack-config.h" + +#include +#include +#include +#include + +#include +#include + +#include "hci.h" +#include "btstack_memory.h" +#include "hci_dump.h" +#include "l2cap.h" +#include "sdp_query_rfcomm.h" +#include "sdp.h" +#include "debug.h" + +#define RFCOMM_SERVER_CHANNEL 1 + +#define HSP_HS_BUTTON_PRESS "AT+CKPD=200\r" +#define HSP_HS_AT_CKPD "AT+CKPD\r" +#define HSP_AG_OK "\r\nOK\r\n" +#define HSP_AG_ERROR "\r\nERROR\r\n" +#define HSP_AG_RING "\r\nRING\r\n" +#define HSP_MICROPHONE_GAIN "+VGM" +#define HSP_SPEAKER_GAIN "+VGS" + +#define HSP_HS_MICROPHONE_GAIN "AT+VGM=" +#define HSP_HS_SPEAKER_GAIN "AT+VGS=" + +static const char default_hsp_hs_service_name[] = "Headset"; + +static bd_addr_t remote = {0x00, 0x21, 0x3C, 0xAC, 0xF7, 0x38}; +static uint8_t channel_nr = 0; + +static uint16_t mtu; +static uint16_t rfcomm_cid = 0; +static uint16_t sco_handle = 0; +static uint16_t rfcomm_handle = 0; + +// static uint8_t connection_state = 0; + +static int hs_microphone_gain = -1; +static int hs_speaker_gain = -1; + +static uint8_t hs_send_button_press = 0; +static uint8_t hs_send_at_cpkd = 0; +static uint8_t hs_ok_received = 0; +static uint8_t hs_ring_received = 0; + +static uint8_t hsp_service_buffer[150]; + +typedef enum { + HSP_IDLE, + HSP_SDP_QUERY_RFCOMM_CHANNEL, + HSP_W4_RFCOMM_CONNECTED, + HSP_W2_CONNECT_SCO, + HSP_W4_SCO_CONNECTED, + HSP_ACTIVE, + HSP_W2_DISCONNECT_SCO, + HSP_W4_SCO_DISCONNECTED, + HSP_W2_DISCONNECT_RFCOMM, + HSP_W4_RFCOMM_DISCONNECTED, + HSP_W4_CONNECTION_ESTABLISHED_TO_SHUTDOWN +} hsp_state_t; + +static hsp_state_t hsp_state = HSP_IDLE; + +static void hsp_run(); + +// remote audio volume control +// AG +VGM=13 [0..15] ; HS AT+VGM=6 | AG OK + +static int send_str_over_rfcomm(uint16_t cid, char * command){ + if (!rfcomm_can_send_packet_now(rfcomm_cid)) return 1; + int err = rfcomm_send_internal(cid, (uint8_t*) command, strlen(command)); + if (err){ + printf("rfcomm_send_internal -> error 0X%02x", err); + } + return err; +} + +void hsp_hs_init(){ + hsp_state = HSP_IDLE; + + rfcomm_cid = 0; + rfcomm_handle = 0; + sco_handle = 0; + + hs_microphone_gain = -1; + hs_speaker_gain = -1; + + hs_send_button_press = 0; + hs_send_at_cpkd = 0; + + hs_ok_received = 0; + hs_ring_received = 0; + // TODO +} + + +void hsp_hs_connect(bd_addr_t bd_addr){ + if (hsp_state != HSP_IDLE) return; + hsp_state = HSP_SDP_QUERY_RFCOMM_CHANNEL; + memcpy(remote, bd_addr, 6); + hsp_run(); +} + +void hsp_hs_disconnect(bd_addr_t bd_addr){ + switch (hsp_state){ + case HSP_ACTIVE: + hsp_state = HSP_W2_DISCONNECT_SCO; + hs_send_at_cpkd = 1; + break; + + case HSP_W4_RFCOMM_CONNECTED: + hsp_state = HSP_W4_CONNECTION_ESTABLISHED_TO_SHUTDOWN; + break; + default: + return; + } + hsp_run(); +} + + +void hsp_hs_set_microphone_gain(uint8_t gain){ + if (gain < 0 || gain >15) { + printf("Gain must be in interval [0..15], it is given %d\n", gain); + return; + } + hs_microphone_gain = gain; + hsp_run(); +} + +// AG +VGS=5 [0..15] ; HS AT+VGM=6 | AG OK +void hsp_hs_set_speaker_gain(uint8_t gain){ + if (gain < 0 || gain >15) { + printf("Gain must be in interval [0..15], it is given %d\n", gain); + return; + } + hs_speaker_gain = gain; + hsp_run(); +} + + +static void hsp_run(){ + int err; + + if (hs_send_at_cpkd){ + int err = send_str_over_rfcomm(rfcomm_cid, HSP_HS_AT_CKPD); + if (!err) hs_send_at_cpkd = 0; + return; + } + + if (hs_send_button_press){ + int err = send_str_over_rfcomm(rfcomm_cid, HSP_HS_BUTTON_PRESS); + if (!err) hs_send_button_press = 0; + return; + } + + switch (hsp_state){ + case HSP_SDP_QUERY_RFCOMM_CHANNEL: + sdp_query_rfcomm_channel_and_name_for_uuid(remote, SDP_Headset_AG); + break; + + case HSP_W2_CONNECT_SCO: + if (hs_send_button_press){ + int err = send_str_over_rfcomm(rfcomm_cid, HSP_HS_BUTTON_PRESS); + if (!err) hs_send_button_press = 0; + break; + } + + if (hs_send_at_cpkd){ + int err = send_str_over_rfcomm(rfcomm_cid, HSP_HS_AT_CKPD); + if (!err) hs_send_at_cpkd = 0; + break; + } + + if (hs_ok_received){ + hs_ok_received = 0; + hsp_state = HSP_W4_SCO_CONNECTED; + } + break; + + case HSP_W2_DISCONNECT_SCO: + if (hs_send_at_cpkd){ + int err = send_str_over_rfcomm(rfcomm_cid, HSP_HS_AT_CKPD); + if (!err) hs_send_at_cpkd = 0; + break; + } + if (hs_ok_received){ + hs_ok_received = 0; + hsp_state = HSP_W4_SCO_DISCONNECTED; + } + break; + + case HSP_ACTIVE: + + if (hs_microphone_gain >= 0){ + char buffer[10]; + sprintf(buffer, "%s=%d\r", HSP_HS_MICROPHONE_GAIN, hs_microphone_gain); + err = send_str_over_rfcomm(rfcomm_cid, buffer); + if (!err) hs_microphone_gain = -1; + break; + } + + if (hs_speaker_gain >= 0){ + char buffer[10]; + sprintf(buffer, "%s=%d\r", HSP_HS_SPEAKER_GAIN, hs_speaker_gain); + err = send_str_over_rfcomm(rfcomm_cid, buffer); + if (!err) hs_speaker_gain = -1; + break; + } + break; + default: + break; + } +} + + +static void packet_handler (void * connection, uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ + // printf("packet_handler type %u, packet[0] %x\n", packet_type, packet[0]); + if (packet_type == RFCOMM_DATA_PACKET){ + if (strncmp((char *)packet, HSP_AG_RING, strlen(HSP_AG_RING)) == 0){ + printf("Received RING %s\n", HSP_AG_RING); + hs_ring_received = 1; + } if (strncmp((char *)packet, HSP_AG_OK, strlen(HSP_AG_OK)) == 0){ + printf("Received OK %s\n", HSP_AG_OK); + hs_ok_received = 1; + } else if (strncmp((char *)packet, HSP_MICROPHONE_GAIN, strlen(HSP_MICROPHONE_GAIN)) == 0 || + strncmp((char *)packet, HSP_SPEAKER_GAIN, strlen(HSP_SPEAKER_GAIN)) == 0){ + printf("Received changed gain info %c\n", packet[8]); + } else { + log_info("Received not yet supported command.\n"); + } + hsp_run(); + return; + } + + if (packet_type != HCI_EVENT_PACKET) return; + uint8_t event = packet[0]; + bd_addr_t event_addr; + uint16_t handle; + + switch (event) { + case BTSTACK_EVENT_STATE: + // bt stack activated, get started + if (packet[2] == HCI_STATE_WORKING){ + hsp_hs_connect(remote); + } + break; + + case HCI_EVENT_PIN_CODE_REQUEST: + // inform about pin code request + printf("Pin code request - using '0000'\n\r"); + bt_flip_addr(event_addr, &packet[2]); + hci_send_cmd(&hci_pin_code_request_reply, &event_addr, 4, "0000"); + break; + case HCI_EVENT_SYNCHRONOUS_CONNECTION_COMPLETE:{ + int index = 2; + uint8_t status = packet[index++]; + sco_handle = READ_BT_16(packet, index); + index+=2; + bd_addr_t address; + memcpy(address, &packet[index], 6); + index+=6; + uint8_t link_type = packet[index++]; + uint8_t transmission_interval = packet[index++]; // measured in slots + uint8_t retransmission_interval = packet[index++];// measured in slots + uint16_t rx_packet_length = READ_BT_16(packet, index); // measured in bytes + index+=2; + uint16_t tx_packet_length = READ_BT_16(packet, index); // measured in bytes + index+=2; + uint8_t air_mode = packet[index]; + + if (status != 0){ + log_error("(e)SCO Connection is not established, status %u", status); + exit(0); + break; + } + switch (link_type){ + case 0x00: + printf("SCO Connection established. \n"); + if (transmission_interval != 0) log_error("SCO Connection: transmission_interval not zero: %d.", transmission_interval); + if (retransmission_interval != 0) log_error("SCO Connection: retransmission_interval not zero: %d.", retransmission_interval); + if (rx_packet_length != 0) log_error("SCO Connection: rx_packet_length not zero: %d.", rx_packet_length); + if (tx_packet_length != 0) log_error("SCO Connection: tx_packet_length not zero: %d.", tx_packet_length); + break; + case 0x02: + printf("eSCO Connection established. \n"); + break; + default: + log_error("(e)SCO reserved link_type 0x%2x", link_type); + break; + } + log_info("sco_handle 0x%2x, address %s, transmission_interval %u slots, retransmission_interval %u slots, " + " rx_packet_length %u bytes, tx_packet_length %u bytes, air_mode 0x%2x (0x02 == CVSD)", sco_handle, + bd_addr_to_str(address), transmission_interval, retransmission_interval, rx_packet_length, tx_packet_length, air_mode); + + if (hsp_state == HSP_W4_CONNECTION_ESTABLISHED_TO_SHUTDOWN){ + hsp_state = HSP_W2_DISCONNECT_SCO; + break; + } + hsp_state = HSP_ACTIVE; + break; + } + + case RFCOMM_EVENT_INCOMING_CONNECTION: + // data: event (8), len(8), address(48), channel (8), rfcomm_cid (16) + if (hsp_state != HSP_IDLE) return; + + bt_flip_addr(event_addr, &packet[2]); + rfcomm_cid = READ_BT_16(packet, 9); + printf("RFCOMM channel %u requested for %s\n", packet[8], bd_addr_to_str(event_addr)); + rfcomm_accept_connection_internal(rfcomm_cid); + hsp_state = HSP_W2_CONNECT_SCO; + hs_send_button_press = 1; + break; + + case RFCOMM_EVENT_OPEN_CHANNEL_COMPLETE: + printf("RFCOMM_EVENT_OPEN_CHANNEL_COMPLETE packet_handler type %u, packet[0] %x\n", packet_type, packet[0]); + // data: event(8), len(8), status (8), address (48), handle(16), server channel(8), rfcomm_cid(16), max frame size(16) + if (packet[2]) { + printf("RFCOMM channel open failed, status %u\n", packet[2]); + hsp_state = HSP_IDLE; + } else { + // data: event(8) , len(8), status (8), address (48), handle (16), server channel(8), rfcomm_cid(16), max frame size(16) + rfcomm_handle = READ_BT_16(packet, 9); + rfcomm_cid = READ_BT_16(packet, 12); + mtu = READ_BT_16(packet, 14); + printf("RFCOMM channel open succeeded. New RFCOMM Channel ID %u, max frame size %u\n", rfcomm_cid, mtu); + + switch (hsp_state){ + case HSP_W4_RFCOMM_CONNECTED: + hsp_state = HSP_W2_CONNECT_SCO; + hs_send_at_cpkd = 1; + break; + case HSP_W4_CONNECTION_ESTABLISHED_TO_SHUTDOWN: + hsp_state = HSP_W2_DISCONNECT_RFCOMM; + break; + default: + break; + } + } + break; + case DAEMON_EVENT_HCI_PACKET_SENT: + case RFCOMM_EVENT_CREDITS: + if (!rfcomm_cid) { + hsp_state = HSP_IDLE; + hsp_hs_init(); + } + break; + + case HCI_EVENT_DISCONNECTION_COMPLETE: + if (hsp_state != HSP_W4_SCO_DISCONNECTED){ + log_info("received gap disconnect in wrong hsp state"); + } + handle = READ_BT_16(packet,3); + if (handle == sco_handle){ + sco_handle = 0; + hsp_state = HSP_W4_RFCOMM_DISCONNECTED; + break; + } + break; + case RFCOMM_EVENT_CHANNEL_CLOSED: + if (hsp_state != HSP_W4_RFCOMM_DISCONNECTED){ + log_info("received RFCOMM disconnect in wrong hsp state"); + } + printf("RFCOMM channel closed\n"); + hsp_state = HSP_IDLE; + hsp_hs_init(); + break; + default: + break; + } + hsp_run(); +} + +void handle_query_rfcomm_event(sdp_query_event_t * event, void * context){ + sdp_query_rfcomm_service_event_t * ve; + sdp_query_complete_event_t * ce; + + switch (event->type){ + case SDP_QUERY_RFCOMM_SERVICE: + ve = (sdp_query_rfcomm_service_event_t*) event; + channel_nr = ve->channel_nr; + printf("** Service name: '%s', RFCOMM port %u\n", ve->service_name, channel_nr); + break; + case SDP_QUERY_COMPLETE: + ce = (sdp_query_complete_event_t*) event; + + if (channel_nr > 0){ + hsp_state = HSP_W4_RFCOMM_CONNECTED; + printf("RFCOMM create channel.\n"); + rfcomm_create_channel_internal(NULL, &remote, channel_nr); + break; + } + hsp_state = HSP_IDLE; + printf("Service not found, status %u.\n", ce->status); + exit(0); + break; + } +} + + +void hsp_hs_create_service(uint8_t * service, int rfcomm_channel_nr, const char * name, uint8_t have_remote_audio_control){ + uint8_t* attribute; + de_create_sequence(service); + + // 0x0000 "Service Record Handle" + de_add_number(service, DE_UINT, DE_SIZE_16, SDP_ServiceRecordHandle); + de_add_number(service, DE_UINT, DE_SIZE_32, 0x10001); + + // 0x0001 "Service Class ID List" + de_add_number(service, DE_UINT, DE_SIZE_16, SDP_ServiceClassIDList); + attribute = de_push_sequence(service); + { + // "UUID for PAN Service" + de_add_number(attribute, DE_UUID, DE_SIZE_16, SDP_Headset_HS); + de_add_number(attribute, DE_UUID, DE_SIZE_16, SDP_GenericAudio); + } + de_pop_sequence(service, attribute); + + // 0x0004 "Protocol Descriptor List" + de_add_number(service, DE_UINT, DE_SIZE_16, SDP_ProtocolDescriptorList); + attribute = de_push_sequence(service); + { + uint8_t* l2cpProtocol = de_push_sequence(attribute); + { + de_add_number(l2cpProtocol, DE_UUID, DE_SIZE_16, SDP_L2CAPProtocol); + } + de_pop_sequence(attribute, l2cpProtocol); + + uint8_t* rfcomm = de_push_sequence(attribute); + { + de_add_number(rfcomm, DE_UUID, DE_SIZE_16, SDP_RFCOMMProtocol); // rfcomm_service + de_add_number(rfcomm, DE_UINT, DE_SIZE_8, rfcomm_channel_nr); // rfcomm channel + } + de_pop_sequence(attribute, rfcomm); + } + de_pop_sequence(service, attribute); + + // 0x0005 "Public Browse Group" + de_add_number(service, DE_UINT, DE_SIZE_16, SDP_BrowseGroupList); // public browse group + attribute = de_push_sequence(service); + { + de_add_number(attribute, DE_UUID, DE_SIZE_16, SDP_PublicBrowseGroup); + } + de_pop_sequence(service, attribute); + + // 0x0009 "Bluetooth Profile Descriptor List" + de_add_number(service, DE_UINT, DE_SIZE_16, SDP_BluetoothProfileDescriptorList); + attribute = de_push_sequence(service); + { + uint8_t *sppProfile = de_push_sequence(attribute); + { + de_add_number(sppProfile, DE_UUID, DE_SIZE_16, SDP_HSP); + de_add_number(sppProfile, DE_UINT, DE_SIZE_16, 0x0102); // Verision 1.2 + } + de_pop_sequence(attribute, sppProfile); + } + de_pop_sequence(service, attribute); + + // 0x0100 "Service Name" + de_add_number(service, DE_UINT, DE_SIZE_16, 0x0100); + if (name){ + de_add_data(service, DE_STRING, strlen(name), (uint8_t *) name); + } else { + de_add_data(service, DE_STRING, strlen(default_hsp_hs_service_name), (uint8_t *) default_hsp_hs_service_name); + } + + // Remote audio volume control + de_add_number(service, DE_UINT, DE_SIZE_16, 0x030C); + de_add_number(service, DE_BOOL, DE_SIZE_8, have_remote_audio_control); +} + +int btstack_main(int argc, const char * argv[]); +int btstack_main(int argc, const char * argv[]){ + + printf("Client HCI init done\r\n"); + + // init L2CAP + l2cap_init(); + l2cap_register_packet_handler(packet_handler); + + rfcomm_init(); + rfcomm_register_packet_handler(packet_handler); + rfcomm_register_service_internal(NULL, RFCOMM_SERVER_CHANNEL, 0xffff); // reserved channel, mtu limited by l2cap + + // init SDP, create record for SPP and register with SDP + sdp_init(); + memset(hsp_service_buffer, 0, sizeof(hsp_service_buffer)); + + hsp_hs_create_service(hsp_service_buffer, RFCOMM_SERVER_CHANNEL, NULL, 0); + hsp_hs_init(); + + sdp_register_service_internal(NULL, hsp_service_buffer); + + sdp_query_rfcomm_register_callback(handle_query_rfcomm_event, NULL); + + // turn on! + hci_power_control(HCI_POWER_ON); + + // go! + run_loop_execute(); + return 0; +}