From 5c562e577119a021e7cdab459b5d85134c36729d Mon Sep 17 00:00:00 2001 From: Milanka Ringwald Date: Tue, 21 Sep 2021 14:07:55 +0200 Subject: [PATCH] gatt_client: support Microphone Control Service Server --- CHANGELOG.md | 3 +- src/ble/gatt-service/Makefile.inc | 1 + .../microphone_control_service.gatt | 4 + .../microphone_control_service_server.c | 151 ++++++++++++++++++ .../microphone_control_service_server.h | 89 +++++++++++ src/bluetooth.h | 1 + test/gatt_service/Makefile | 15 +- .../microphone_control_service_profile.gatt | 2 + .../microphone_control_service_test.c | 118 ++++++++++++++ 9 files changed, 382 insertions(+), 2 deletions(-) create mode 100644 src/ble/gatt-service/microphone_control_service.gatt create mode 100644 src/ble/gatt-service/microphone_control_service_server.c create mode 100644 src/ble/gatt-service/microphone_control_service_server.h create mode 100644 test/gatt_service/microphone_control_service_profile.gatt create mode 100644 test/gatt_service/microphone_control_service_test.c diff --git a/CHANGELOG.md b/CHANGELOG.md index 11bd62b5e..f39a9c140 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,8 +19,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - `a2dp_source_register_media_config_validator` - A2DP Sink: allow accept or reject of stream start on A2DP_SUBEVENT_START_STREAM_REQUESTED when ENABLE_AVDTP_ACCEPTOR_EXPLICIT_START_STREAM_CONFIRMATION is defined - SM: Support Cross-Transport Key-Derivation (CTKD) of LE LTK from BR/EDR SC Link Key -- GATT Service: TX Power Service (TPS 1.0) Server - GATT Service: Bond Management Service (BMS 1.0) Server +- GATT Service: Microphone Control Service (MICS 1.0) Server +- GATT Service: TX Power Service (TPS 1.0) Server ### Fixed - A2DP Source: fix reconfigure diff --git a/src/ble/gatt-service/Makefile.inc b/src/ble/gatt-service/Makefile.inc index 04f19bed1..3b7575218 100644 --- a/src/ble/gatt-service/Makefile.inc +++ b/src/ble/gatt-service/Makefile.inc @@ -14,6 +14,7 @@ SRC_BLE_GATT_SERVICE_FILES = \ hids_device.c \ mesh_provisioning_service_server.c \ mesh_proxy_service_server.c \ + microphone_control_service_server.c \ nordic_spp_service_server.c \ scan_parameters_service_client.c \ scan_parameters_service_server.c \ diff --git a/src/ble/gatt-service/microphone_control_service.gatt b/src/ble/gatt-service/microphone_control_service.gatt new file mode 100644 index 000000000..791f82b74 --- /dev/null +++ b/src/ble/gatt-service/microphone_control_service.gatt @@ -0,0 +1,4 @@ +// Bond Management Service 181E +PRIMARY_SERVICE, ORG_BLUETOOTH_SERVICE_MICROPHONE_CONTROL +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_MUTE, DYNAMIC | READ | WRITE| NOTIFY, +CLIENT_CHARACTERISTIC_CONFIGURATION, READ | WRITE, diff --git a/src/ble/gatt-service/microphone_control_service_server.c b/src/ble/gatt-service/microphone_control_service_server.c new file mode 100644 index 000000000..3cc0d9730 --- /dev/null +++ b/src/ble/gatt-service/microphone_control_service_server.c @@ -0,0 +1,151 @@ +/* + * 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_server.c" + +/** + * Implementation of the GATT Battery Service Server + * To use with your application, add '#import + +#if defined __cplusplus +extern "C" { +#endif + +#define ATT_ERROR_RESPONSE_MICROPHONE_CONTROL_MUTE_DISABLED 0x80 + +typedef enum { + MICROPHONE_CONTROL_MUTE_OFF = 0x00, + MICROPHONE_CONTROL_MUTE_ON, + MICROPHONE_CONTROL_MUTE_DISABLED +} microphone_control_mute_t; + +/** + * @text The Microphone Control Service enables a device to expose the mute control and state of one or more microphones. + * Only server can disable and enable mute. Currently one one client supported. + * + * To use with your application, add `#import ` to your .gatt file. + */ + +/* API_START */ + +/** + * @brief Init Microphone Control Service Server with ATT DB + * @param mute_value + */ +void microphone_control_service_server_init(microphone_control_mute_t mute_value); + +/** + * @brief Set mute value. + * @param mute_value + */ +void microphone_control_service_server_set_mute(microphone_control_mute_t mute_value); + + +/* API_END */ + +#if defined __cplusplus +} +#endif + +#endif + diff --git a/src/bluetooth.h b/src/bluetooth.h index 87cf7c366..fb1524d01 100644 --- a/src/bluetooth.h +++ b/src/bluetooth.h @@ -616,6 +616,7 @@ typedef enum { #define ATT_ERROR_INSUFFICIENT_ENCRYPTION 0x0f #define ATT_ERROR_UNSUPPORTED_GROUP_TYPE 0x10 #define ATT_ERROR_INSUFFICIENT_RESOURCES 0x11 +#define ATT_ERROR_VALUE_NOT_ALLOWED 0x13 // MARK: ATT Error Codes defined by BTstack #define ATT_ERROR_HCI_DISCONNECT_RECEIVED 0x1f diff --git a/test/gatt_service/Makefile b/test/gatt_service/Makefile index cfe04ddce..96adecdd5 100644 --- a/test/gatt_service/Makefile +++ b/test/gatt_service/Makefile @@ -29,12 +29,13 @@ COMMON = \ cycling_power_service_server.c \ cycling_speed_and_cadence_service_server.c \ device_information_service_server.c \ - tx_power_service_server.c \ hci_dump.c \ heart_rate_service_server.c \ hids_device.c \ + microphone_control_service_server.c \ mock_att_server.c \ nordic_spp_service_server.c \ + tx_power_service_server.c \ ublox_spp_service_server.c \ @@ -53,6 +54,7 @@ all: build-coverage/battery_service_test build-asan/battery_service_test \ build-coverage/device_information_service_test build-asan/device_information_service_test \ build-coverage/tx_power_service_test build-asan/tx_power_service_test \ build-coverage/bond_management_service_test build-asan/bond_management_service_test \ + build-coverage/microphone_control_service_test build-asan/microphone_control_service_test \ build-%: mkdir -p $@ @@ -69,6 +71,9 @@ build-%/tx_power_service_profile.h: tx_power_service.gatt| build-% build-%/bond_management_service_profile.h: bond_management_service.gatt| build-% python3 ${BTSTACK_ROOT}/tool/compile_gatt.py $< $@ +build-%/microphone_control_service_profile.h: microphone_control_service.gatt| build-% + python3 ${BTSTACK_ROOT}/tool/compile_gatt.py $< $@ + build-coverage/%.o: %.c | build-coverage ${CC} -c $(CFLAGS_COVERAGE) $< -o $@ @@ -99,11 +104,18 @@ build-coverage/bond_management_service_test: build-coverage/bond_management_serv build-asan/bond_management_service_test: build-asan/bond_management_service_profile.h ${COMMON_OBJ_ASAN} build-asan/bond_management_service_server.o build-asan/bond_management_service_test.o | build-asan ${CC} $(filter-out build-asan/bond_management_service_profile.h,$^) ${LDFLAGS_ASAN} -o $@ +build-coverage/microphone_control_service_test: build-coverage/microphone_control_service_profile.h ${COMMON_OBJ_COVERAGE} build-coverage/microphone_control_service_server.o build-coverage/microphone_control_service_test.o | build-coverage + ${CC} $(filter-out build-coverage/microphone_control_service_profile.h,$^) ${LDFLAGS_COVERAGE} -o $@ + +build-asan/microphone_control_service_test: build-asan/microphone_control_service_profile.h ${COMMON_OBJ_ASAN} build-asan/microphone_control_service_server.o build-asan/microphone_control_service_test.o | build-asan + ${CC} $(filter-out build-asan/microphone_control_service_profile.h,$^) ${LDFLAGS_ASAN} -o $@ + test: all build-asan/battery_service_test build-asan/device_information_service_test build-asan/tx_power_service_test build-asan/bond_management_service_test + build-asan/microphone_control_service_test coverage: all rm -f build-coverage/*.gcda @@ -111,6 +123,7 @@ coverage: all build-coverage/device_information_service_test build-asan/tx_power_service_test build-asan/bond_management_service_test + build-asan/microphone_control_service_test clean: rm -rf build-coverage build-asan diff --git a/test/gatt_service/microphone_control_service_profile.gatt b/test/gatt_service/microphone_control_service_profile.gatt new file mode 100644 index 000000000..9c4c33e6d --- /dev/null +++ b/test/gatt_service/microphone_control_service_profile.gatt @@ -0,0 +1,2 @@ +// add Microphone Control Service +#import \ No newline at end of file diff --git a/test/gatt_service/microphone_control_service_test.c b/test/gatt_service/microphone_control_service_test.c new file mode 100644 index 000000000..942cb0015 --- /dev/null +++ b/test/gatt_service/microphone_control_service_test.c @@ -0,0 +1,118 @@ + +// ***************************************************************************** +// +// test battery service +// +// ***************************************************************************** + + +#include +#include +#include +#include + +#include "CppUTest/TestHarness.h" +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTestExt/MockSupport.h" + +#include "hci.h" +#include "btstack_util.h" +#include "bluetooth.h" +#include "bluetooth_gatt.h" + +#include "ble/gatt-service/microphone_control_service_server.h" +#include "microphone_control_service_profile.h" +#include "mock_att_server.h" + + +TEST_GROUP(MICROPHONE_CONTROL_SERVICE_SERVER){ + att_service_handler_t * service; + uint16_t con_handle; + uint16_t mute_value_handle; + uint16_t mute_value_handle_client_configuration; + + void setup(void){ + // setup database + att_set_db(profile_data); + mute_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(0, 0xffff, ORG_BLUETOOTH_CHARACTERISTIC_MUTE); + mute_value_handle_client_configuration = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(0, 0xffff, ORG_BLUETOOTH_CHARACTERISTIC_MUTE); + + // setup battery service + microphone_control_service_server_init(MICROPHONE_CONTROL_MUTE_OFF); + + service = mock_att_server_get_service(); + con_handle = 0x00; + } + + void teardown(){ + mock_deinit(); + } +}; + +TEST(MICROPHONE_CONTROL_SERVICE_SERVER, set_mute_value_trigger_can_send_now){ + // enable notifications + const uint8_t enable_notify[]= { 0x1, 0x0 }; + mock_att_service_write_callback(con_handle, mute_value_handle_client_configuration, ATT_TRANSACTION_MODE_NONE, 0, enable_notify, sizeof(enable_notify)); + + // set battery to trigger notification + mock().expectOneCall("att_server_register_can_send_now_callback"); + microphone_control_service_server_set_mute(MICROPHONE_CONTROL_MUTE_ON); + mock().checkExpectations(); + mock().expectOneCall("att_server_notify"); + mock_att_service_trigger_can_send_now(); + mock().checkExpectations(); +} + +TEST(MICROPHONE_CONTROL_SERVICE_SERVER, lookup_attribute_handles){ + CHECK(mute_value_handle != 0); + CHECK(mute_value_handle_client_configuration != 0); +} + +TEST(MICROPHONE_CONTROL_SERVICE_SERVER, set_mute_value){ + // mute_value_handle_client_configuration not set + mock().expectNCalls(con_handle, "att_server_register_can_send_now_callback"); + microphone_control_service_server_set_mute(MICROPHONE_CONTROL_MUTE_ON); + mock().checkExpectations(); + + // mute_value_handle_client_configuration set + mock().expectOneCall("att_server_register_can_send_now_callback"); + const uint8_t enable_notify[]= { 0x1, 0x0 }; + mock_att_service_write_callback(con_handle, mute_value_handle_client_configuration, ATT_TRANSACTION_MODE_NONE, 0, enable_notify, sizeof(enable_notify)); + microphone_control_service_server_set_mute(MICROPHONE_CONTROL_MUTE_OFF); + mock().checkExpectations(); +} + +TEST(MICROPHONE_CONTROL_SERVICE_SERVER, set_wrong_handle_mute_value){ + // mute_value_handle_client_configuration not set + mock().expectNCalls(con_handle, "att_server_register_can_send_now_callback"); + microphone_control_service_server_set_mute(MICROPHONE_CONTROL_MUTE_ON); + mock().checkExpectations(); + + // // mute_value_handle_client_configuration set + mock().expectNoCall("att_server_register_can_send_now_callback"); + const uint8_t enable_notify[]= { 0x1, 0x0 }; + mock_att_service_write_callback(con_handle, mute_value_handle_client_configuration + 10, ATT_TRANSACTION_MODE_NONE, 0, enable_notify, sizeof(enable_notify)); + microphone_control_service_server_set_mute(MICROPHONE_CONTROL_MUTE_OFF); + mock().checkExpectations(); +} + + +TEST(MICROPHONE_CONTROL_SERVICE_SERVER, read_mute_value){ + uint8_t response[2]; + uint16_t response_len; + + // invalid attribute handle + response_len = mock_att_service_read_callback(con_handle, 0xffff, 0xffff, response, sizeof(response)); + CHECK_EQUAL(0, response_len); + + response_len = mock_att_service_read_callback(con_handle, mute_value_handle, 0, response, sizeof(response)); + CHECK_EQUAL(1, response_len); + + response_len = mock_att_service_read_callback(con_handle, mute_value_handle_client_configuration, 0, response, sizeof(response)); + CHECK_EQUAL(2, response_len); +} + + +int main (int argc, const char * argv[]){ + return CommandLineTestRunner::RunAllTests(argc, argv); +}