1adc3e7d5SMilanka Ringwald /* 2adc3e7d5SMilanka Ringwald * Copyright (C) 2014 BlueKitchen GmbH 3adc3e7d5SMilanka Ringwald * 4adc3e7d5SMilanka Ringwald * Redistribution and use in source and binary forms, with or without 5adc3e7d5SMilanka Ringwald * modification, are permitted provided that the following conditions 6adc3e7d5SMilanka Ringwald * are met: 7adc3e7d5SMilanka Ringwald * 8adc3e7d5SMilanka Ringwald * 1. Redistributions of source code must retain the above copyright 9adc3e7d5SMilanka Ringwald * notice, this list of conditions and the following disclaimer. 10adc3e7d5SMilanka Ringwald * 2. Redistributions in binary form must reproduce the above copyright 11adc3e7d5SMilanka Ringwald * notice, this list of conditions and the following disclaimer in the 12adc3e7d5SMilanka Ringwald * documentation and/or other materials provided with the distribution. 13adc3e7d5SMilanka Ringwald * 3. Neither the name of the copyright holders nor the names of 14adc3e7d5SMilanka Ringwald * contributors may be used to endorse or promote products derived 15adc3e7d5SMilanka Ringwald * from this software without specific prior written permission. 16adc3e7d5SMilanka Ringwald * 4. Any redistribution, use, or modification is done solely for 17adc3e7d5SMilanka Ringwald * personal benefit and not for any commercial purpose or for 18adc3e7d5SMilanka Ringwald * monetary gain. 19adc3e7d5SMilanka Ringwald * 20adc3e7d5SMilanka Ringwald * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS 21adc3e7d5SMilanka Ringwald * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22adc3e7d5SMilanka Ringwald * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 23adc3e7d5SMilanka Ringwald * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS 24adc3e7d5SMilanka Ringwald * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 25adc3e7d5SMilanka Ringwald * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26adc3e7d5SMilanka Ringwald * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 27adc3e7d5SMilanka Ringwald * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 28adc3e7d5SMilanka Ringwald * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29adc3e7d5SMilanka Ringwald * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 30adc3e7d5SMilanka Ringwald * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31adc3e7d5SMilanka Ringwald * SUCH DAMAGE. 32adc3e7d5SMilanka Ringwald * 33adc3e7d5SMilanka Ringwald * Please inquire about commercial licensing options at 34adc3e7d5SMilanka Ringwald * [email protected] 35adc3e7d5SMilanka Ringwald * 36adc3e7d5SMilanka Ringwald */ 37adc3e7d5SMilanka Ringwald 38e501bae0SMatthias Ringwald #define BTSTACK_FILE__ "heart_rate_service_server.c" 39adc3e7d5SMilanka Ringwald 40adc3e7d5SMilanka Ringwald 41adc3e7d5SMilanka Ringwald #include "bluetooth.h" 42adc3e7d5SMilanka Ringwald #include "btstack_defines.h" 43adc3e7d5SMilanka Ringwald #include "ble/att_db.h" 44adc3e7d5SMilanka Ringwald #include "ble/att_server.h" 45adc3e7d5SMilanka Ringwald #include "btstack_util.h" 46adc3e7d5SMilanka Ringwald #include "bluetooth_gatt.h" 475d3bf30aSMilanka Ringwald #include "btstack_debug.h" 4846e18d79SMilanka Ringwald #include "l2cap.h" 49adc3e7d5SMilanka Ringwald 50adc3e7d5SMilanka Ringwald #include "ble/gatt-service/heart_rate_service_server.h" 51adc3e7d5SMilanka Ringwald 5246e18d79SMilanka Ringwald #define HEART_RATE_RESET_ENERGY_EXPENDED 0x01 5346e18d79SMilanka Ringwald #define HEART_RATE_CONTROL_POINT_NOT_SUPPORTED 0x80 5446e18d79SMilanka Ringwald 555d3bf30aSMilanka Ringwald typedef enum { 5646e18d79SMilanka Ringwald HEART_RATE_SERVICE_VALUE_FORMAT = 0, 575d3bf30aSMilanka Ringwald HEART_RATE_SERVICE_SENSOR_CONTACT_STATUS, 5846e18d79SMilanka Ringwald HEART_RATE_SERVICE_ENERGY_EXPENDED_STATUS = 3, 595d3bf30aSMilanka Ringwald HEART_RATE_SERVICE_RR_INTERVAL 605d3bf30aSMilanka Ringwald } heart_rate_service_flag_bit_t; 615d3bf30aSMilanka Ringwald 625d3bf30aSMilanka Ringwald typedef struct { 635d3bf30aSMilanka Ringwald hci_con_handle_t con_handle; 645d3bf30aSMilanka Ringwald 655d3bf30aSMilanka Ringwald // characteristic: Heart Rate Mesurement 665d3bf30aSMilanka Ringwald uint16_t measurement_value_handle; 675d3bf30aSMilanka Ringwald uint16_t measurement_bpm; 6846e18d79SMilanka Ringwald uint8_t energy_expended_supported; 695d3bf30aSMilanka Ringwald uint16_t energy_expended_kJ; // kilo Joules 705d3bf30aSMilanka Ringwald int rr_interval_count; 715d3bf30aSMilanka Ringwald int rr_offset; 725d3bf30aSMilanka Ringwald uint16_t * rr_intervals; 735d3bf30aSMilanka Ringwald heart_rate_service_sensor_contact_status_t sensor_contact; 745d3bf30aSMilanka Ringwald 755d3bf30aSMilanka Ringwald // characteristic descriptor: Client Characteristic Configuration 765d3bf30aSMilanka Ringwald uint16_t measurement_client_configuration_descriptor_handle; 775d3bf30aSMilanka Ringwald uint16_t measurement_client_configuration_descriptor_notify; 785d3bf30aSMilanka Ringwald btstack_context_callback_registration_t measurement_callback; 795d3bf30aSMilanka Ringwald 805d3bf30aSMilanka Ringwald // characteristic: Body Sensor Location 815d3bf30aSMilanka Ringwald uint16_t sensor_location_value_handle; 825d3bf30aSMilanka Ringwald heart_rate_service_body_sensor_location_t sensor_location; 835d3bf30aSMilanka Ringwald 84d5d6b232SMilanka Ringwald // characteristic: Heart Rate Control Point 855d3bf30aSMilanka Ringwald uint16_t control_point_value_handle; 865d3bf30aSMilanka Ringwald } heart_rate_t; 875d3bf30aSMilanka Ringwald 885d3bf30aSMilanka Ringwald static att_service_handler_t heart_rate_service; 895d3bf30aSMilanka Ringwald static heart_rate_t heart_rate; 905d3bf30aSMilanka Ringwald 915d3bf30aSMilanka Ringwald static uint16_t heart_rate_service_read_callback(hci_con_handle_t con_handle, uint16_t attribute_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size){ 925d3bf30aSMilanka Ringwald UNUSED(con_handle); 935d3bf30aSMilanka Ringwald UNUSED(attribute_handle); 945d3bf30aSMilanka Ringwald UNUSED(offset); 955d3bf30aSMilanka Ringwald UNUSED(buffer_size); 965d3bf30aSMilanka Ringwald 975d3bf30aSMilanka Ringwald if (attribute_handle == heart_rate.measurement_client_configuration_descriptor_handle){ 984ea43905SMatthias Ringwald if (buffer && (buffer_size >= 2u)){ 995d3bf30aSMilanka Ringwald little_endian_store_16(buffer, 0, heart_rate.measurement_client_configuration_descriptor_notify); 1005d3bf30aSMilanka Ringwald } 1015d3bf30aSMilanka Ringwald return 2; 1025d3bf30aSMilanka Ringwald } 1035d3bf30aSMilanka Ringwald 1045d3bf30aSMilanka Ringwald if (attribute_handle == heart_rate.sensor_location_value_handle){ 1054ea43905SMatthias Ringwald if (buffer && (buffer_size >= 1u)){ 1065d3bf30aSMilanka Ringwald buffer[0] = heart_rate.sensor_location; 1075d3bf30aSMilanka Ringwald } 1085d3bf30aSMilanka Ringwald return 1; 1095d3bf30aSMilanka Ringwald } 1105d3bf30aSMilanka Ringwald return 0; 1115d3bf30aSMilanka Ringwald } 1125d3bf30aSMilanka Ringwald 1135d3bf30aSMilanka Ringwald static int heart_rate_service_write_callback(hci_con_handle_t con_handle, uint16_t attribute_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size){ 1145d3bf30aSMilanka Ringwald UNUSED(transaction_mode); 1155d3bf30aSMilanka Ringwald UNUSED(offset); 1165d3bf30aSMilanka Ringwald UNUSED(buffer_size); 1175d3bf30aSMilanka Ringwald 1185d3bf30aSMilanka Ringwald if (attribute_handle == heart_rate.measurement_client_configuration_descriptor_handle){ 1194ea43905SMatthias Ringwald if (buffer_size < 2u){ 12046e18d79SMilanka Ringwald return ATT_ERROR_INVALID_OFFSET; 12146e18d79SMilanka Ringwald } 12246e18d79SMilanka Ringwald heart_rate.measurement_client_configuration_descriptor_notify = little_endian_read_16(buffer, 0); 1235d3bf30aSMilanka Ringwald heart_rate.con_handle = con_handle; 1245d3bf30aSMilanka Ringwald return 0; 1255d3bf30aSMilanka Ringwald } 1265d3bf30aSMilanka Ringwald 12746e18d79SMilanka Ringwald if (attribute_handle == heart_rate.control_point_value_handle){ 12846e18d79SMilanka Ringwald uint16_t cmd = little_endian_read_16(buffer, 0); 12946e18d79SMilanka Ringwald switch (cmd){ 13046e18d79SMilanka Ringwald case HEART_RATE_RESET_ENERGY_EXPENDED: 13146e18d79SMilanka Ringwald heart_rate.energy_expended_kJ = 0; 13246e18d79SMilanka Ringwald heart_rate.con_handle = con_handle; 13346e18d79SMilanka Ringwald break; 13446e18d79SMilanka Ringwald default: 13546e18d79SMilanka Ringwald return HEART_RATE_CONTROL_POINT_NOT_SUPPORTED; 13646e18d79SMilanka Ringwald } 13746e18d79SMilanka Ringwald return 0; 13846e18d79SMilanka Ringwald } 13946e18d79SMilanka Ringwald return 0; 14046e18d79SMilanka Ringwald } 14146e18d79SMilanka Ringwald 14246e18d79SMilanka Ringwald 14346e18d79SMilanka Ringwald void heart_rate_service_server_init(heart_rate_service_body_sensor_location_t location, int energy_expended_supported){ 1445d3bf30aSMilanka Ringwald heart_rate_t * instance = &heart_rate; 1455d3bf30aSMilanka Ringwald 1465d3bf30aSMilanka Ringwald instance->sensor_location = location; 14746e18d79SMilanka Ringwald instance->energy_expended_supported = energy_expended_supported; 1485d3bf30aSMilanka Ringwald 1495d3bf30aSMilanka Ringwald // get service handle range 1505d3bf30aSMilanka Ringwald uint16_t start_handle = 0; 1515d3bf30aSMilanka Ringwald uint16_t end_handle = 0xffff; 1525d3bf30aSMilanka Ringwald int service_found = gatt_server_get_get_handle_range_for_service_with_uuid16(ORG_BLUETOOTH_SERVICE_HEART_RATE, &start_handle, &end_handle); 153*a2489f29SMatthias Ringwald btstack_assert(service_found != 0); 154*a2489f29SMatthias Ringwald UNUSED(service_found); 1555d3bf30aSMilanka Ringwald 1565d3bf30aSMilanka Ringwald // get Heart Rate Mesurement characteristic value handle and client configuration handle 1575d3bf30aSMilanka Ringwald instance->measurement_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_HEART_RATE_MEASUREMENT); 1585d3bf30aSMilanka Ringwald instance->measurement_client_configuration_descriptor_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_HEART_RATE_MEASUREMENT); 1595d3bf30aSMilanka Ringwald // get Body Sensor Location characteristic value handle and client configuration handle 16046e18d79SMilanka Ringwald instance->sensor_location_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_BODY_SENSOR_LOCATION); 1615d3bf30aSMilanka Ringwald // get Hear Rate Control Point characteristic value handle and client configuration handle 1625d3bf30aSMilanka Ringwald instance->control_point_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_HEART_RATE_CONTROL_POINT); 1635d3bf30aSMilanka Ringwald 16404f45ea1SMilanka Ringwald log_info("Measurement value handle 0x%02x", instance->measurement_value_handle); 16504f45ea1SMilanka Ringwald log_info("Client Config value handle 0x%02x", instance->measurement_client_configuration_descriptor_handle); 16604f45ea1SMilanka Ringwald log_info("Sensor location value handle 0x%02x", instance->sensor_location_value_handle); 16704f45ea1SMilanka Ringwald log_info("Control Point value handle 0x%02x", instance->control_point_value_handle); 1685d3bf30aSMilanka Ringwald // register service with ATT Server 1695d3bf30aSMilanka Ringwald heart_rate_service.start_handle = start_handle; 1705d3bf30aSMilanka Ringwald heart_rate_service.end_handle = end_handle; 1715d3bf30aSMilanka Ringwald heart_rate_service.read_callback = &heart_rate_service_read_callback; 1725d3bf30aSMilanka Ringwald heart_rate_service.write_callback = &heart_rate_service_write_callback; 1735d3bf30aSMilanka Ringwald 1745d3bf30aSMilanka Ringwald att_server_register_service_handler(&heart_rate_service); 175adc3e7d5SMilanka Ringwald } 176adc3e7d5SMilanka Ringwald 1775d3bf30aSMilanka Ringwald 1785d3bf30aSMilanka Ringwald static void heart_rate_service_can_send_now(void * context){ 1795d3bf30aSMilanka Ringwald heart_rate_t * instance = (heart_rate_t *) context; 18046e18d79SMilanka Ringwald uint8_t flags = (1 << HEART_RATE_SERVICE_VALUE_FORMAT); 18146e18d79SMilanka Ringwald flags |= (instance->sensor_contact << HEART_RATE_SERVICE_SENSOR_CONTACT_STATUS); 18246e18d79SMilanka Ringwald if (instance->energy_expended_supported){ 1834ea43905SMatthias Ringwald flags |= (1u << HEART_RATE_SERVICE_ENERGY_EXPENDED_STATUS); 1845d3bf30aSMilanka Ringwald } 1855d3bf30aSMilanka Ringwald if (instance->rr_interval_count){ 1864ea43905SMatthias Ringwald flags |= (1u << HEART_RATE_SERVICE_RR_INTERVAL); 187adc3e7d5SMilanka Ringwald } 188adc3e7d5SMilanka Ringwald 1895d3bf30aSMilanka Ringwald uint8_t value[100]; 1905d3bf30aSMilanka Ringwald int pos = 0; 191adc3e7d5SMilanka Ringwald 19246e18d79SMilanka Ringwald value[pos++] = flags; 1935d3bf30aSMilanka Ringwald little_endian_store_16(value, pos, instance->measurement_bpm); 1945d3bf30aSMilanka Ringwald pos += 2; 19546e18d79SMilanka Ringwald if (instance->energy_expended_supported){ 1965d3bf30aSMilanka Ringwald little_endian_store_16(value, pos, instance->energy_expended_kJ); 1975d3bf30aSMilanka Ringwald pos += 2; 1985d3bf30aSMilanka Ringwald } 199adc3e7d5SMilanka Ringwald 2005d3bf30aSMilanka Ringwald // TODO: get actual MTU from ATT server 2014ea43905SMatthias Ringwald uint16_t bytes_left = btstack_min(sizeof(value), l2cap_max_mtu() - 3u - pos); 2025d3bf30aSMilanka Ringwald 2034ea43905SMatthias Ringwald while ((bytes_left > 2u) && instance->rr_interval_count){ 2045d3bf30aSMilanka Ringwald little_endian_store_16(value, pos, instance->rr_intervals[0]); 2055d3bf30aSMilanka Ringwald pos +=2; 2064ea43905SMatthias Ringwald bytes_left -= 2u; 2075d3bf30aSMilanka Ringwald instance->rr_intervals++; 2085d3bf30aSMilanka Ringwald instance->rr_interval_count--; 2095d3bf30aSMilanka Ringwald } 2105d3bf30aSMilanka Ringwald 2115d3bf30aSMilanka Ringwald att_server_notify(instance->con_handle, instance->measurement_value_handle, &value[0], pos); 2125d3bf30aSMilanka Ringwald 2135d3bf30aSMilanka Ringwald if (instance->rr_interval_count){ 2145d3bf30aSMilanka Ringwald instance->measurement_callback.callback = &heart_rate_service_can_send_now; 2155d3bf30aSMilanka Ringwald instance->measurement_callback.context = (void*) instance; 2165d3bf30aSMilanka Ringwald att_server_register_can_send_now_callback(&instance->measurement_callback, instance->con_handle); 2175d3bf30aSMilanka Ringwald } 2185d3bf30aSMilanka Ringwald } 2195d3bf30aSMilanka Ringwald 2205d3bf30aSMilanka Ringwald void heart_rate_service_add_energy_expended(uint16_t energy_expended_kJ){ 2215d3bf30aSMilanka Ringwald heart_rate_t * instance = &heart_rate; 2225d3bf30aSMilanka Ringwald // limit energy expended to 0xffff 2234ea43905SMatthias Ringwald if (instance->energy_expended_kJ <= (0xffffu - energy_expended_kJ)){ 2245d3bf30aSMilanka Ringwald instance->energy_expended_kJ += energy_expended_kJ; 2255d3bf30aSMilanka Ringwald } else { 2265d3bf30aSMilanka Ringwald instance->energy_expended_kJ = 0xffff; 2275d3bf30aSMilanka Ringwald } 2285d3bf30aSMilanka Ringwald } 2295d3bf30aSMilanka Ringwald 230d5d6b232SMilanka Ringwald void heart_rate_service_server_update_heart_rate_values(uint16_t heart_rate_bpm, 2315d3bf30aSMilanka Ringwald heart_rate_service_sensor_contact_status_t sensor_contact, int rr_interval_count, uint16_t * rr_intervals){ 2325d3bf30aSMilanka Ringwald heart_rate_t * instance = &heart_rate; 2335d3bf30aSMilanka Ringwald 234d5d6b232SMilanka Ringwald instance->measurement_bpm = heart_rate_bpm; 2355d3bf30aSMilanka Ringwald instance->sensor_contact = sensor_contact; 2365d3bf30aSMilanka Ringwald instance->rr_interval_count = rr_interval_count; 2375d3bf30aSMilanka Ringwald instance->rr_intervals = rr_intervals; 2385d3bf30aSMilanka Ringwald instance->rr_offset = 0; 2395d3bf30aSMilanka Ringwald 2405d3bf30aSMilanka Ringwald if (instance->measurement_client_configuration_descriptor_notify){ 2415d3bf30aSMilanka Ringwald instance->measurement_callback.callback = &heart_rate_service_can_send_now; 2425d3bf30aSMilanka Ringwald instance->measurement_callback.context = (void*) instance; 2435d3bf30aSMilanka Ringwald att_server_register_can_send_now_callback(&instance->measurement_callback, instance->con_handle); 2445d3bf30aSMilanka Ringwald } 245adc3e7d5SMilanka Ringwald }