gatt-service: implement MICS Client

This commit is contained in:
Milanka Ringwald 2021-09-28 16:15:08 +02:00
parent 01b628dca3
commit 35facaddd1
9 changed files with 1077 additions and 0 deletions

View File

@ -88,6 +88,7 @@ GATT_CLIENT += \
device_information_service_client.c \
scan_parameters_service_client.c \
hids_client.c \
microphone_control_service_client.c \
PAN += \
pan.c \

View File

@ -0,0 +1,516 @@
/*
* Copyright (C) 2021 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
*
*/
#define BTSTACK_FILE__ "microphone_control_service_client.c"
#include "btstack_config.h"
#ifdef ENABLE_TESTING_SUPPORT
#include <stdio.h>
#include <unistd.h>
#endif
#include <stdint.h>
#include <string.h>
#include "ble/gatt-service/microphone_control_service_client.h"
#include "btstack_memory.h"
#include "ble/core.h"
#include "ble/gatt_client.h"
#include "bluetooth_gatt.h"
#include "btstack_debug.h"
#include "btstack_event.h"
#include "btstack_run_loop.h"
#include "gap.h"
static microphone_control_service_client_t mic_client;
static uint16_t mic_service_cid_counter = 0;
static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
static uint16_t microphone_control_service_get_next_cid(void){
mic_service_cid_counter = btstack_next_cid_ignoring_zero(mic_service_cid_counter);
return mic_service_cid_counter;
}
static void microphone_control_service_finalize_client(microphone_control_service_client_t * client){
client->cid = 0;
client->con_handle = HCI_CON_HANDLE_INVALID;
client->state = MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_IDLE;
client->need_polling = false;
}
static microphone_control_service_client_t * microphone_control_service_get_client_for_con_handle(hci_con_handle_t con_handle){
if (mic_client.con_handle == con_handle){
return &mic_client;
}
return NULL;
}
static microphone_control_service_client_t * microphone_control_service_get_client_for_cid(uint16_t microphone_control_service_cid){
if (mic_client.cid == microphone_control_service_cid){
return &mic_client;
}
return NULL;
}
static void microphone_control_service_emit_connection_established(microphone_control_service_client_t * client, uint8_t status){
uint8_t event[6];
int pos = 0;
event[pos++] = HCI_EVENT_GATTSERVICE_META;
event[pos++] = sizeof(event) - 2;
event[pos++] = GATTSERVICE_SUBEVENT_MICROPHONE_CONTROL_SERVICE_CONNECTED;
little_endian_store_16(event, pos, client->cid);
pos += 2;
event[pos++] = status;
(*client->client_handler)(HCI_EVENT_PACKET, 0, event, sizeof(event));
}
static void microphone_control_service_emit_mute_state(microphone_control_service_client_t * client, uint16_t value_handle, uint8_t att_status, uint8_t mute_state){
if (value_handle != client->mute_value_handle){
return;
}
uint8_t event[7];
int pos = 0;
event[pos++] = HCI_EVENT_GATTSERVICE_META;
event[pos++] = sizeof(event) - 2;
event[pos++] = GATTSERVICE_SUBEVENT_MICROPHONE_CONTROL_SERVICE_MUTE;
little_endian_store_16(event, pos, client->cid);
pos += 2;
event[pos++] = att_status;
event[pos++] = mute_state;
(*client->client_handler)(HCI_EVENT_PACKET, 0, event, sizeof(event));
}
static bool microphone_control_service_registered_notification(microphone_control_service_client_t * client){
gatt_client_characteristic_t characteristic;
// if there are services without notification, register pool timer,
// othervise register for notifications
characteristic.value_handle = client->mute_value_handle;
characteristic.properties = client->properties;
characteristic.end_handle = client->end_handle;
uint8_t status = gatt_client_write_client_characteristic_configuration(
&handle_gatt_client_event,
client->con_handle,
&characteristic,
GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
// notification supported, register for value updates
if (status == ERROR_CODE_SUCCESS){
gatt_client_listen_for_characteristic_value_updates(
&client->notification_listener,
&handle_gatt_client_event,
client->con_handle, &characteristic);
}
return status;
}
static void microphone_control_service_run_for_client(microphone_control_service_client_t * client){
uint8_t status;
gatt_client_characteristic_t characteristic;
switch (client->state){
case MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_CONNECTED:
if (client->need_polling){
// clear bit of polled service
client->need_polling = false;
// poll value of characteristic
characteristic.value_handle = client->mute_value_handle;
characteristic.properties = client->properties;
characteristic.end_handle = client->end_handle;
gatt_client_read_value_of_characteristic(&handle_gatt_client_event, client->con_handle, &characteristic);
break;
}
break;
case MICROPHONE_CONTROL_SERVICE_CLIENT_W2_WRITE_MUTE:
#ifdef ENABLE_TESTING_SUPPORT
printf(" Write mute [0x%04X, %d]:\n",
client->mute_value_handle, client->requested_mute);
#endif
client->state = MICROPHONE_CONTROL_SERVICE_CLIENT_W4_WRITE_MUTE_RESULT;
// see GATT_EVENT_QUERY_COMPLETE for end of write
status = gatt_client_write_value_of_characteristic(
&handle_gatt_client_event, client->con_handle,
client->mute_value_handle,
1, &client->requested_mute);
UNUSED(status);
break;
case MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W2_QUERY_SERVICE:
client->state = MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W4_SERVICE_RESULT;
status = gatt_client_discover_primary_services_by_uuid16(&handle_gatt_client_event, client->con_handle, ORG_BLUETOOTH_SERVICE_MICROPHONE_CONTROL);
// TODO handle status
break;
case MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTICS:
client->state = MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_RESULT;
gatt_client_discover_characteristics_for_handle_range_by_uuid16(
&handle_gatt_client_event,
client->con_handle,
client->start_handle,
client->end_handle,
ORG_BLUETOOTH_CHARACTERISTIC_BATTERY_LEVEL);
break;
#ifdef ENABLE_TESTING_SUPPORT
case MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTIC_DESCRIPTORS:
client->state = MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_DESCRIPTORS_RESULT;
// if there are services without notification, register pool timer,
// othervise register for notifications
characteristic.value_handle = client->mute_value_handle;
characteristic.properties = client->properties;
characteristic.end_handle = client->end_handle;
(void) gatt_client_discover_characteristic_descriptors(&handle_gatt_client_event, client->con_handle, &characteristic);
break;
case MICROPHONE_CONTROL_SERVICE_CLIENT_W2_READ_CHARACTERISTIC_CONFIGURATION:
printf("Read client characteristic value [handle 0x%04X]:\n", client->ccc_handle);
client->state = MICROPHONE_CONTROL_SERVICE_CLIENT_W4_CHARACTERISTIC_CONFIGURATION_RESULT;
// result in GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT
(void) gatt_client_read_characteristic_descriptor_using_descriptor_handle(
&handle_gatt_client_event,
client->con_handle,
client->ccc_handle);
break;
#endif
case MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W2_REGISTER_NOTIFICATION:
client->state = MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W4_NOTIFICATION_REGISTERED;
status = microphone_control_service_registered_notification(client);
if (status == ERROR_CODE_SUCCESS) return;
#ifdef ENABLE_TESTING_SUPPORT
if (client->ccc_handle != 0){
client->state = MICROPHONE_CONTROL_SERVICE_CLIENT_W2_READ_CHARACTERISTIC_CONFIGURATION;
break;
}
#endif
client->state = MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_CONNECTED;
microphone_control_service_emit_connection_established(client, ERROR_CODE_SUCCESS);
break;
default:
break;
}
}
// @return true if client valid / run function should be called
static bool microphone_control_service_client_handle_query_complete(microphone_control_service_client_t * client, uint8_t status){
switch (client->state){
case MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W4_SERVICE_RESULT:
if (status != ATT_ERROR_SUCCESS){
microphone_control_service_emit_connection_established(client, status);
microphone_control_service_finalize_client(client);
return false;
}
if (client->num_instances == 0){
microphone_control_service_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
microphone_control_service_finalize_client(client);
return false;
}
client->state = MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTICS;
break;
case MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_RESULT:
if (status != ATT_ERROR_SUCCESS){
microphone_control_service_emit_connection_established(client, status);
microphone_control_service_finalize_client(client);
return false;
}
if ((client->num_instances == 0) || (client->mute_value_handle == 0)){
microphone_control_service_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
microphone_control_service_finalize_client(client);
return false;
}
#ifdef ENABLE_TESTING_SUPPORT
client->state = MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTIC_DESCRIPTORS;
#else
// wait for notification registration
// to send connection established event
client->state = MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W2_REGISTER_NOTIFICATION;
#endif
break;
#ifdef ENABLE_TESTING_SUPPORT
case MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_DESCRIPTORS_RESULT:
client->state = MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W2_REGISTER_NOTIFICATION;
break;
case MICROPHONE_CONTROL_SERVICE_CLIENT_W4_CHARACTERISTIC_CONFIGURATION_RESULT:
client->state = MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_CONNECTED;
microphone_control_service_emit_connection_established(client, ERROR_CODE_SUCCESS);
break;
#endif
case MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W4_NOTIFICATION_REGISTERED:
#ifdef ENABLE_TESTING_SUPPORT
printf("read CCC 0x%02x, polling %d \n", client->ccc_handle, client->need_polling ? 1 : 0);
if ( (client->ccc_handle != 0) && !client->need_polling ) {
client->state = MICROPHONE_CONTROL_SERVICE_CLIENT_W2_READ_CHARACTERISTIC_CONFIGURATION;
break;
}
#endif
client->state = MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_CONNECTED;
microphone_control_service_emit_connection_established(client, ERROR_CODE_SUCCESS);
break;
case MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_CONNECTED:
if (status != ATT_ERROR_SUCCESS){
microphone_control_service_emit_mute_state(client, client->mute_value_handle, status, 0);
}
break;
case MICROPHONE_CONTROL_SERVICE_CLIENT_W4_WRITE_MUTE_RESULT:
client->state = MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_CONNECTED;
microphone_control_service_emit_mute_state(client, client->mute_value_handle, status, client->requested_mute);
break;
default:
break;
}
return true;
}
static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
UNUSED(packet_type);
UNUSED(channel);
UNUSED(size);
microphone_control_service_client_t * client = NULL;
gatt_client_service_t service;
gatt_client_characteristic_t characteristic;
bool call_run = true;
switch(hci_event_packet_get_type(packet)){
case GATT_EVENT_SERVICE_QUERY_RESULT:
client = microphone_control_service_get_client_for_con_handle(gatt_event_service_query_result_get_handle(packet));
btstack_assert(client != NULL);
if (client->num_instances < 1){
gatt_event_service_query_result_get_service(packet, &service);
client->start_handle = service.start_group_handle;
client->end_handle = service.end_group_handle;
#ifdef ENABLE_TESTING_SUPPORT
printf("MIC Service: start handle 0x%04X, end handle 0x%04X\n", client->start_handle, client->end_handle);
#endif
client->num_instances++;
} else {
log_info("Found more then one MIC Service instance. ");
}
break;
case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
client = microphone_control_service_get_client_for_con_handle(gatt_event_characteristic_query_result_get_handle(packet));
btstack_assert(client != NULL);
gatt_event_characteristic_query_result_get_characteristic(packet, &characteristic);
btstack_assert(characteristic.uuid16 == ORG_BLUETOOTH_CHARACTERISTIC_MUTE);
client->mute_value_handle = characteristic.value_handle;
client->properties = characteristic.properties;
#ifdef ENABLE_TESTING_SUPPORT
printf("Mute Characteristic:\n Attribute Handle 0x%04X, Properties 0x%02X, Handle 0x%04X, UUID 0x%04X\n",
// hid_characteristic_name(characteristic.uuid16),
characteristic.start_handle,
characteristic.properties,
characteristic.value_handle, characteristic.uuid16);
#endif
break;
case GATT_EVENT_NOTIFICATION:
if (gatt_event_notification_get_value_length(packet) != 1) break;
client = microphone_control_service_get_client_for_con_handle(gatt_event_notification_get_handle(packet));
btstack_assert(client != NULL);
microphone_control_service_emit_mute_state(client,
gatt_event_notification_get_value_handle(packet),
ATT_ERROR_SUCCESS,
gatt_event_notification_get_value(packet)[0]);
break;
case GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT:
client = microphone_control_service_get_client_for_con_handle(gatt_event_characteristic_value_query_result_get_handle(packet));
btstack_assert(client != NULL);
#ifdef ENABLE_TESTING_SUPPORT
if (client->state == MICROPHONE_CONTROL_SERVICE_CLIENT_W4_CHARACTERISTIC_CONFIGURATION_RESULT){
printf(" Received CCC value: ");
printf_hexdump(gatt_event_characteristic_value_query_result_get_value(packet), gatt_event_characteristic_value_query_result_get_value_length(packet));
break;
}
#endif
if (gatt_event_characteristic_value_query_result_get_value_length(packet) != 1) break;
microphone_control_service_emit_mute_state(client,
gatt_event_characteristic_value_query_result_get_value_handle(packet),
ATT_ERROR_SUCCESS,
gatt_event_characteristic_value_query_result_get_value(packet)[0]);
break;
#ifdef ENABLE_TESTING_SUPPORT
case GATT_EVENT_ALL_CHARACTERISTIC_DESCRIPTORS_QUERY_RESULT:{
gatt_client_characteristic_descriptor_t characteristic_descriptor;
client = microphone_control_service_get_client_for_con_handle(gatt_event_all_characteristic_descriptors_query_result_get_handle(packet));
btstack_assert(client != NULL);
gatt_event_all_characteristic_descriptors_query_result_get_characteristic_descriptor(packet, &characteristic_descriptor);
if (characteristic_descriptor.uuid16 == ORG_BLUETOOTH_DESCRIPTOR_GATT_CLIENT_CHARACTERISTIC_CONFIGURATION){
client->ccc_handle = characteristic_descriptor.handle;
printf(" MICS Characteristic Configuration Descriptor: Handle 0x%04X, UUID 0x%04X\n",
characteristic_descriptor.handle,
characteristic_descriptor.uuid16);
}
break;
}
#endif
case GATT_EVENT_QUERY_COMPLETE:
client = microphone_control_service_get_client_for_con_handle(gatt_event_query_complete_get_handle(packet));
btstack_assert(client != NULL);
call_run = microphone_control_service_client_handle_query_complete(client, gatt_event_query_complete_get_att_status(packet));
break;
default:
break;
}
if (call_run && (client != NULL)){
microphone_control_service_run_for_client(client);
}
}
uint8_t microphone_control_service_client_connect(hci_con_handle_t con_handle, btstack_packet_handler_t packet_handler, uint16_t * mics_cid){
btstack_assert(packet_handler != NULL);
microphone_control_service_client_t * client = microphone_control_service_get_client_for_con_handle(con_handle);
if (client != NULL){
return ERROR_CODE_COMMAND_DISALLOWED;
}
uint16_t cid = microphone_control_service_get_next_cid();
if (mics_cid != NULL) {
*mics_cid = cid;
}
client->cid = cid;
client->con_handle = con_handle;
client->state = MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_IDLE;
client->client_handler = packet_handler;
client->state = MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W2_QUERY_SERVICE;
microphone_control_service_run_for_client(client);
return ERROR_CODE_SUCCESS;
}
uint8_t microphone_control_service_client_disconnect(uint16_t mics_cid){
microphone_control_service_client_t * client = microphone_control_service_get_client_for_cid(mics_cid);
if (client == NULL){
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
}
// finalize connections
microphone_control_service_finalize_client(client);
return ERROR_CODE_SUCCESS;
}
uint8_t microphone_control_service_client_read_mute_state(uint16_t mics_cid){
microphone_control_service_client_t * client = microphone_control_service_get_client_for_cid(mics_cid);
if (client == NULL) {
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
}
if (client->state != MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_CONNECTED) {
return GATT_CLIENT_IN_WRONG_STATE;
}
client->need_polling = true;
microphone_control_service_run_for_client(client);
return ERROR_CODE_SUCCESS;
}
static uint8_t microphone_control_service_client_mute_write(uint16_t mics_cid, gatt_microphone_control_mute_t mute_status){
microphone_control_service_client_t * client = microphone_control_service_get_client_for_cid(mics_cid);
if (client == NULL) {
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
}
if (client->state != MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_CONNECTED) {
return GATT_CLIENT_IN_WRONG_STATE;
}
client->requested_mute = (uint8_t)mute_status;
client->state = MICROPHONE_CONTROL_SERVICE_CLIENT_W2_WRITE_MUTE;
microphone_control_service_run_for_client(client);
return ERROR_CODE_SUCCESS;
}
uint8_t microphone_control_service_client_mute_turn_on(uint16_t mics_cid){
return microphone_control_service_client_mute_write(mics_cid, GATT_MICROPHONE_CONTROL_MUTE_ON);
}
uint8_t microphone_control_service_client_mute_turn_off(uint16_t mics_cid){
return microphone_control_service_client_mute_write(mics_cid, GATT_MICROPHONE_CONTROL_MUTE_OFF);
}
void microphone_control_service_client_init(void){}
void microphone_control_service_client_deinit(void){
mic_service_cid_counter = 0;
microphone_control_service_finalize_client(&mic_client);
}

View File

@ -0,0 +1,194 @@
/*
* Copyright (C) 2021 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
*
*/
/**
* @title Microphone Control Service Client
*
*/
#ifndef MICROPHONE_CONTROL_SERVICE_CLIENT_H
#define MICROPHONE_CONTROL_SERVICE_CLIENT_H
#include <stdint.h>
#include "btstack_defines.h"
#include "bluetooth.h"
#include "btstack_linked_list.h"
#include "ble/gatt_client.h"
#if defined __cplusplus
extern "C" {
#endif
/**
* @text The Microphone Control Service Client connects to the Microphone Control Services of a remote device
* and it can query or set mute value if mute value on the remote side is enabled. The Mute updates are received via notifications.
*/
typedef enum {
MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_IDLE,
MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W2_QUERY_SERVICE,
MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W4_SERVICE_RESULT,
MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTICS,
MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_RESULT,
#ifdef ENABLE_TESTING_SUPPORT
MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTIC_DESCRIPTORS,
MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_DESCRIPTORS_RESULT,
#endif
MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W2_REGISTER_NOTIFICATION,
MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_W4_NOTIFICATION_REGISTERED,
MICROPHONE_CONTROL_SERVICE_CLIENT_STATE_CONNECTED,
#ifdef ENABLE_TESTING_SUPPORT
MICROPHONE_CONTROL_SERVICE_CLIENT_W2_READ_CHARACTERISTIC_CONFIGURATION,
MICROPHONE_CONTROL_SERVICE_CLIENT_W4_CHARACTERISTIC_CONFIGURATION_RESULT,
#endif
MICROPHONE_CONTROL_SERVICE_CLIENT_W2_WRITE_MUTE,
MICROPHONE_CONTROL_SERVICE_CLIENT_W4_WRITE_MUTE_RESULT
} microphone_service_client_state_t;
typedef struct {
// service
uint16_t start_handle;
uint16_t end_handle;
// characteristic
uint16_t properties;
uint16_t value_handle;
#ifdef ENABLE_TESTING_SUPPORT
uint16_t ccc_handle;
#endif
gatt_client_notification_t notification_listener;
} microphone_service_t;
typedef struct {
btstack_linked_item_t item;
hci_con_handle_t con_handle;
uint16_t cid;
microphone_service_client_state_t state;
btstack_packet_handler_t client_handler;
// microphone_service_t service;
// service
uint16_t start_handle;
uint16_t end_handle;
// characteristic
uint16_t properties;
uint16_t mute_value_handle;
#ifdef ENABLE_TESTING_SUPPORT
uint16_t ccc_handle;
#endif
bool need_polling;
uint16_t num_instances;
uint8_t requested_mute;
gatt_client_notification_t notification_listener;
} microphone_control_service_client_t;
/* API_START */
/**
* @brief Initialize Microphone Control Service.
*/
void microphone_control_service_client_init(void);
/**
* @brief Connect to Microphone Control Services of remote device. The client will automatically register for notifications.
* The mute state is received via GATTSERVICE_SUBEVENT_MICROPHONE_CONTROL_SERVICE_MUTE event.
* The mute state can be 0 - off, 1 - on, 2 - disabeled and it is valid if the ATTT status is equal to ATT_ERROR_SUCCESS,
* see ATT errors (see bluetooth.h) for other values.
*
* Event GATTSERVICE_SUBEVENT_MICROPHONE_CONTROL_SERVICE_CONNECTED is emitted with status ERROR_CODE_SUCCESS on success, otherwise
* GATT_CLIENT_IN_WRONG_STATE, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE if no microphone control service is found, or ATT errors (see bluetooth.h).
*
* @param con_handle
* @param packet_handler
* @param mics_cid
* @return status ERROR_CODE_SUCCESS on success, otherwise ERROR_CODE_COMMAND_DISALLOWED if there is already a client associated with con_handle, or BTSTACK_MEMORY_ALLOC_FAILED
*/
uint8_t microphone_control_service_client_connect(hci_con_handle_t con_handle, btstack_packet_handler_t packet_handler, uint16_t * mics_cid);
/**
* @brief Read mute state. The mute state is received via GATTSERVICE_SUBEVENT_MICROPHONE_CONTROL_SERVICE_MUTE event.
* @param mics_cid
* @return status
*/
uint8_t microphone_control_service_client_read_mute_state(uint16_t mics_cid);
/**
* @brief Turn on mute.
* @param mics_cid
* @return status
*/
uint8_t microphone_control_service_client_mute_turn_on(uint16_t mics_cid);
/**
* @brief Turn off mute.
* @param mics_cid
* @return status
*/
uint8_t microphone_control_service_client_mute_turn_off(uint16_t mics_cid);
/**
* @brief Disconnect.
* @param mics_cid
* @return status
*/
uint8_t microphone_control_service_client_disconnect(uint16_t mics_cid);
/**
* @brief De-initialize Microphone Control Service.
*/
void microphone_control_service_client_deinit(void);
/* API_END */
#if defined __cplusplus
}
#endif
#endif

View File

@ -93,6 +93,7 @@
#include "ble/gatt-service/heart_rate_service_server.h"
#include "ble/gatt-service/hids_client.h"
#include "ble/gatt-service/hids_device.h"
#include "ble/gatt-service/microphone_control_service_client.h"
#include "ble/gatt-service/microphone_control_service_server.h"
#include "ble/gatt-service/scan_parameters_service_client.h"
#include "ble/gatt-service/scan_parameters_service_server.h"

View File

@ -3468,6 +3468,24 @@ typedef uint8_t sm_key_t[16];
*/
#define GATTSERVICE_SUBEVENT_SCAN_PARAMETERS_SERVICE_SCAN_INTERVAL_UPDATE 0x18
// LE Audio
/**
* @format 121
* @param subevent_code
* @param hids_cid
* @param status
*/
#define GATTSERVICE_SUBEVENT_MICROPHONE_CONTROL_SERVICE_CONNECTED 0x19
/**
* @format 1211
* @param subevent_code
* @param mics_cid
* @param status
* @param mute_value
*/
#define GATTSERVICE_SUBEVENT_MICROPHONE_CONTROL_SERVICE_MUTE 0x1A
// MAP Meta Event Group

View File

@ -10463,6 +10463,53 @@ static inline uint16_t gattservice_subevent_scan_parameters_service_scan_interva
return little_endian_read_16(event, 7);
}
/**
* @brief Get field hids_cid from event GATTSERVICE_SUBEVENT_MICROPHONE_CONTROL_SERVICE_CONNECTED
* @param event packet
* @return hids_cid
* @note: btstack_type 2
*/
static inline uint16_t gattservice_subevent_microphone_control_service_connected_get_hids_cid(const uint8_t * event){
return little_endian_read_16(event, 3);
}
/**
* @brief Get field status from event GATTSERVICE_SUBEVENT_MICROPHONE_CONTROL_SERVICE_CONNECTED
* @param event packet
* @return status
* @note: btstack_type 1
*/
static inline uint8_t gattservice_subevent_microphone_control_service_connected_get_status(const uint8_t * event){
return event[5];
}
/**
* @brief Get field mics_cid from event GATTSERVICE_SUBEVENT_MICROPHONE_CONTROL_SERVICE_MUTE
* @param event packet
* @return mics_cid
* @note: btstack_type 2
*/
static inline uint16_t gattservice_subevent_microphone_control_service_mute_get_mics_cid(const uint8_t * event){
return little_endian_read_16(event, 3);
}
/**
* @brief Get field status from event GATTSERVICE_SUBEVENT_MICROPHONE_CONTROL_SERVICE_MUTE
* @param event packet
* @return status
* @note: btstack_type 1
*/
static inline uint8_t gattservice_subevent_microphone_control_service_mute_get_status(const uint8_t * event){
return event[5];
}
/**
* @brief Get field mute_value from event GATTSERVICE_SUBEVENT_MICROPHONE_CONTROL_SERVICE_MUTE
* @param event packet
* @return mute_value
* @note: btstack_type 1
*/
static inline uint8_t gattservice_subevent_microphone_control_service_mute_get_mute_value(const uint8_t * event){
return event[6];
}
/**
* @brief Get field map_cid from event MAP_SUBEVENT_CONNECTION_OPENED
* @param event packet

View File

@ -82,6 +82,9 @@ AVRCP += \
avrcp_browsing_target.c \
avrcp_browsing_controller.c \
SRC_BLE_GATT_SERVICE_FILES += \
microphone_control_service_server.c \
# include ${BTSTACK_ROOT}/example/Makefile.inc
HXCMOD_PLAYER = \
@ -154,6 +157,7 @@ EXAMPLES += avdtp_source_test avdtp_sink_test avrcp_test gatt_profiles hrp_col_t
EXAMPLES += hog_device_test hog_host_test hid_device_test hid_host_test
EXAMPLES += csc_server_test csc_client_test cycling_power_server_test
EXAMPLES += gap_peripheral_test gap_central_test
EXAMPLES += microphone_control_service_client_test
all: ${EXAMPLES}
@ -262,6 +266,12 @@ gatt_profiles.h: gatt_profiles.gatt
gatt_profiles: gatt_profiles.h ${CORE_OBJ} ${COMMON_OBJ} ${ATT_OBJ} ${GATT_SERVER_OBJ} ${SM_OBJ} ${SRC_BLE_GATT_SERVICE_FILES_OBJ} gatt_client.c gatt_profiles.o
${CC} $(filter-out gatt_profiles.h,$^) ${CFLAGS} ${LDFLAGS} -o $@
microphone_control_service_client_test.h: microphone_control_service_client_test.gatt
python3 ${BTSTACK_ROOT}/tool/compile_gatt.py $< $@
microphone_control_service_client_test: microphone_control_service_client_test.h ${CORE_OBJ} ${COMMON_OBJ} ${ATT_OBJ} ${GATT_CLIENT_OBJ} ${GATT_SERVER_OBJ} ${SM_OBJ} microphone_control_service_client_test.c
${CC} $(filter-out microphone_control_service_client_test.h,$^) ${CFLAGS} ${LDFLAGS} -o $@
myclean:
rm -rf *.pyc *.o $(AVDTP_TESTS) *.dSYM *_test *.wav *.sbc ${BTSTACK_ROOT}/port/libusb/*.o ${BTSTACK_ROOT}/src/*.o ${BTSTACK_ROOT}/src/classic/*.o ${BTSTACK_ROOT}/src/ble/*.o
rm -rf hog_device_test.h
@ -273,6 +283,7 @@ myclean:
rm -rf hrp_server_test.h
rm -f sco_input.msbc
rm -f sco_output.msbc
rm -f microphone_control_service_client_test.h
clean: myclean

View File

@ -0,0 +1,285 @@
/*
* 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
*
*/
#define BTSTACK_FILE__ "microphone_control_service_client_test.c"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "btstack.h"
#include "microphone_control_service_client_test.h"
typedef struct advertising_report {
uint8_t type;
uint8_t event_type;
uint8_t address_type;
bd_addr_t address;
uint8_t rssi;
uint8_t length;
const uint8_t * data;
} advertising_report_t;
static enum {
APP_STATE_IDLE,
APP_STATE_W4_SCAN_RESULT,
APP_STATE_W4_CONNECT,
APP_STATE_CONNECTED
} app_state;
static int blacklist_index = 0;
static bd_addr_t blacklist[20];
static advertising_report_t report;
static hci_con_handle_t connection_handle;
static uint16_t mips_cid;
static bd_addr_t cmdline_addr;
static int cmdline_addr_found = 0;
static btstack_packet_callback_registration_t hci_event_callback_registration;
static void hci_event_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
static void gatt_client_event_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
static void microphone_control_service_client_setup(void){
// Init L2CAP
l2cap_init();
// Setup ATT server - only needed if LE Peripheral does ATT queries on its own, e.g. Android phones
att_server_init(profile_data, NULL, NULL);
// GATT Client setup
gatt_client_init();
microphone_control_service_client_init();
sm_init();
sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT);
hci_event_callback_registration.callback = &hci_event_handler;
hci_add_event_handler(&hci_event_callback_registration);
}
static int blacklist_size(void){
return sizeof(blacklist) / sizeof(bd_addr_t);
}
static int blacklist_contains(bd_addr_t addr){
int i;
for (i=0; i<blacklist_size(); i++){
if (bd_addr_cmp(addr, blacklist[i]) == 0) return 1;
}
return 0;
}
static void add_to_blacklist(bd_addr_t addr){
printf("%s added to blacklist (no battery service found).\n", bd_addr_to_str(addr));
bd_addr_copy(blacklist[blacklist_index], addr);
blacklist_index = (blacklist_index + 1) % blacklist_size();
}
static void dump_advertising_report(uint8_t *packet){
bd_addr_t address;
gap_event_advertising_report_get_address(packet, address);
printf(" * adv. event: evt-type %u, addr-type %u, addr %s, rssi %u, length adv %u, data: ",
gap_event_advertising_report_get_advertising_event_type(packet),
gap_event_advertising_report_get_address_type(packet),
bd_addr_to_str(address),
gap_event_advertising_report_get_rssi(packet),
gap_event_advertising_report_get_data_length(packet));
printf_hexdump(gap_event_advertising_report_get_data(packet), gap_event_advertising_report_get_data_length(packet));
}
static void hci_event_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
UNUSED(channel);
UNUSED(size);
bd_addr_t address;
if (packet_type != HCI_EVENT_PACKET){
return;
}
switch (hci_event_packet_get_type(packet)) {
case BTSTACK_EVENT_STATE:
// BTstack activated, get started
if (btstack_event_state_get_state(packet) != HCI_STATE_WORKING) break;
if (cmdline_addr_found){
printf("Connect to %s\n", bd_addr_to_str(cmdline_addr));
app_state = APP_STATE_W4_CONNECT;
gap_connect(cmdline_addr, 0);
break;
}
printf("Start scanning!\n");
app_state = APP_STATE_W4_SCAN_RESULT;
gap_set_scan_parameters(0,0x0030, 0x0030);
gap_start_scan();
break;
case GAP_EVENT_ADVERTISING_REPORT:
if (app_state != APP_STATE_W4_SCAN_RESULT) return;
gap_event_advertising_report_get_address(packet, address);
if (blacklist_contains(address)) {
break;
}
dump_advertising_report(packet);
// stop scanning, and connect to the device
app_state = APP_STATE_W4_CONNECT;
gap_stop_scan();
printf("Stop scan. Connect to device with addr %s.\n", bd_addr_to_str(report.address));
gap_connect(report.address,report.address_type);
break;
case HCI_EVENT_LE_META:
// Wait for connection complete
if (hci_event_le_meta_get_subevent_code(packet) != HCI_SUBEVENT_LE_CONNECTION_COMPLETE) break;
if (app_state != APP_STATE_W4_CONNECT) return;
// Get connection handle from event
connection_handle = hci_subevent_le_connection_complete_get_connection_handle(packet);
(void) microphone_control_service_client_connect(connection_handle, gatt_client_event_handler, &mips_cid);
app_state = APP_STATE_CONNECTED;
printf("Microphone Control service connected.\n");
break;
case HCI_EVENT_DISCONNECTION_COMPLETE:
connection_handle = HCI_CON_HANDLE_INVALID;
// Disconnect battery service
microphone_control_service_client_disconnect(mips_cid);
if (cmdline_addr_found){
printf("Disconnected %s\n", bd_addr_to_str(cmdline_addr));
return;
}
printf("Disconnected %s\n", bd_addr_to_str(report.address));
printf("Restart scan.\n");
app_state = APP_STATE_W4_SCAN_RESULT;
gap_start_scan();
break;
default:
break;
}
}
static void gatt_client_event_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
UNUSED(packet_type);
UNUSED(channel);
UNUSED(size);
uint8_t status;
uint8_t att_status;
if (hci_event_packet_get_type(packet) != HCI_EVENT_GATTSERVICE_META){
return;
}
switch (hci_event_gattservice_meta_get_subevent_code(packet)){
case GATTSERVICE_SUBEVENT_MICROPHONE_CONTROL_SERVICE_CONNECTED:
status = gattservice_subevent_microphone_control_service_connected_get_status(packet);
switch (status){
case ERROR_CODE_SUCCESS:
printf("Microphone Control service client connected\n");
break;
default:
printf("Microphone Control service client connection failed, err 0x%02x.\n", status);
add_to_blacklist(report.address);
gap_disconnect(connection_handle);
break;
}
break;
case GATTSERVICE_SUBEVENT_MICROPHONE_CONTROL_SERVICE_MUTE:
att_status = gattservice_subevent_microphone_control_service_mute_get_status(packet);
if (att_status != ATT_ERROR_SUCCESS){
printf("Mute status read failed, ATT Error 0x%02x\n", att_status);
} else {
printf("Mute status: %d\n", gattservice_subevent_microphone_control_service_mute_get_mute_value(packet));
}
break;
default:
break;
}
}
int btstack_main(int argc, const char * argv[]);
int btstack_main(int argc, const char * argv[]){
// parse address if command line arguments are provided
int arg = 1;
cmdline_addr_found = 0;
while (arg < argc) {
if(!strcmp(argv[arg], "-a") || !strcmp(argv[arg], "--address")){
arg++;
cmdline_addr_found = sscanf_bd_addr(argv[arg], cmdline_addr);
arg++;
if (!cmdline_addr_found) exit(1);
continue;
}
fprintf(stderr, "\nUsage: %s [-a|--address aa:bb:cc:dd:ee:ff]\n", argv[0]);
fprintf(stderr, "If no argument is provided, GATT browser will start scanning and connect to the first found device.\nTo connect to a specific device use argument [-a].\n\n");
return 0;
}
(void)argv;
microphone_control_service_client_setup();
app_state = APP_STATE_IDLE;
// turn on!
hci_power_control(HCI_POWER_ON);
return 0;
}

View File

@ -0,0 +1,4 @@
PRIMARY_SERVICE, GAP_SERVICE
CHARACTERISTIC, GAP_DEVICE_NAME, READ, "MICS Client"