diff --git a/src/ble/gatt-service/cycling_power_service.gatt b/src/ble/gatt-service/cycling_power_service.gatt new file mode 100644 index 000000000..e3505ff3b --- /dev/null +++ b/src/ble/gatt-service/cycling_power_service.gatt @@ -0,0 +1,14 @@ +// Specification Type org.bluetooth.service.cycling_power +// https://www.bluetooth.com/api/gatt/xmlfile?xmlFileName=org.bluetooth.service.cycling_power.xml + +// Cycling Power 1818 +PRIMARY_SERVICE, ORG_BLUETOOTH_SERVICE_CYCLING_POWER +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_CYCLING_POWER_MEASUREMENT, DYNAMIC | NOTIFY | BROADCAST, +CLIENT_CHARACTERISTIC_CONFIGURATION, READ | WRITE, +SERVER_CHARACTERISTIC_CONFIGURATION, READ | WRITE, +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_CYCLING_POWER_FEATURE, DYNAMIC | READ, +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_SENSOR_LOCATION, DYNAMIC | READ, +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_CYCLING_POWER_VECTOR, DYNAMIC | NOTIFY, +CLIENT_CHARACTERISTIC_CONFIGURATION, READ | WRITE, +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_CYCLING_POWER_CONTROL_POINT, DYNAMIC | WRITE | INDICATE, +CLIENT_CHARACTERISTIC_CONFIGURATION, READ | WRITE, diff --git a/src/ble/gatt-service/cycling_power_service_server.c b/src/ble/gatt-service/cycling_power_service_server.c new file mode 100644 index 000000000..2333c687d --- /dev/null +++ b/src/ble/gatt-service/cycling_power_service_server.c @@ -0,0 +1,1344 @@ +/* + * 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__ "cycling_power_service_server.c" + + +#include "bluetooth.h" +#include "btstack_defines.h" +#include "bluetooth_data_types.h" +#include "btstack_event.h" +#include "ble/att_db.h" +#include "ble/att_server.h" +#include "btstack_util.h" +#include "bluetooth_gatt.h" +#include "btstack_debug.h" +#include "l2cap.h" +#include "hci.h" + +#include "ble/gatt-service/cycling_power_service_server.h" + +// error codes from cps spec +#define CYCLING_POWER_ERROR_CODE_INAPPROPRIATE_CONNECTION_PARAMETERS 0x80 +#define CYCLING_POWER_MAX_BROACAST_MSG_SIZE 31 + +#define CSC_ERROR_CODE_PROCEDURE_ALREADY_IN_PROGRESS 0xFE +#define CSC_ERROR_CODE_CCC_DESCRIPTOR_IMPROPERLY_CONFIGURED 0xFD + +#define CONTROL_POINT_PROCEDURE_TIMEOUT_MS 30 +#define CYCLING_POWER_MEASUREMENT_FLAGS_CLEARED 0xFFFF + +typedef enum { + CP_MASK_BIT_PEDAL_POWER_BALANCE = 0, + CP_MASK_BIT_ACCUMULATED_TORQUE, + CP_MASK_BIT_WHEEL_REVOLUTION_DATA, + CP_MASK_BIT_CRANK_REVOLUTION_DATA, + CP_MASK_BIT_EXTREME_MAGNITUDES, + CP_MASK_BIT_EXTREME_ANGLES, + CP_MASK_BIT_TOP_DEAD_SPOT_ANGLE, + CP_MASK_BIT_BOTTOM_DEAD_SPOT_ANGLE, + CP_MASK_BIT_ACCUMULATED_ENERGY, + CP_MASK_BIT_RESERVED +} cycling_power_mask_bit_t; + +typedef enum { + CP_OPCODE_IDLE = 0, + CP_OPCODE_SET_CUMULATIVE_VALUE, + CP_OPCODE_UPDATE_SENSOR_LOCATION, + CP_OPCODE_REQUEST_SUPPORTED_SENSOR_LOCATIONS, + CP_OPCODE_SET_CRANK_LENGTH, + CP_OPCODE_REQUEST_CRANK_LENGTH, + CP_OPCODE_SET_CHAIN_LENGTH, + CP_OPCODE_REQUEST_CHAIN_LENGTH, + CP_OPCODE_SET_CHAIN_WEIGHT, + CP_OPCODE_REQUEST_CHAIN_WEIGHT, + CP_OPCODE_SET_SPAN_LENGTH, + CP_OPCODE_REQUEST_SPAN_LENGTH, + CP_OPCODE_START_OFFSET_COMPENSATION, + CP_OPCODE_MASK_CYCLING_POWER_MEASUREMENT_CHARACTERISTIC_CONTENT, + CP_OPCODE_REQUEST_SAMPLING_RATE, + CP_OPCODE_REQUEST_FACTORY_CALIBRATION_DATE, + CP_OPCODE_START_ENHANCED_OFFSET_COMPENSATION, + CP_OPCODE_RESPONSE_CODE = 32 +} cycling_power_opcode_t; + +typedef enum { + CP_RESPONSE_VALUE_SUCCESS = 1, + CP_RESPONSE_VALUE_OP_CODE_NOT_SUPPORTED, + CP_RESPONSE_VALUE_INVALID_PARAMETER, + CP_RESPONSE_VALUE_OPERATION_FAILED, + CP_RESPONSE_VALUE_NOT_AVAILABLE, + CP_RESPONSE_VALUE_W4_VALUE_AVAILABLE +} cycling_power_response_value_t; + +typedef enum { + CP_CONNECTION_INTERVAL_STATUS_NONE = 0, + CP_CONNECTION_INTERVAL_STATUS_RECEIVED, + CP_CONNECTION_INTERVAL_STATUS_ACCEPTED, + CP_CONNECTION_INTERVAL_STATUS_W4_L2CAP_RESPONSE, + CP_CONNECTION_INTERVAL_STATUS_W4_UPDATE, + CP_CONNECTION_INTERVAL_STATUS_REJECTED +} cycling_power_con_interval_status_t; + +typedef struct { + hci_con_handle_t con_handle; + // GATT connection management + uint16_t con_interval; + uint16_t con_interval_min; + uint16_t con_interval_max; + cycling_power_con_interval_status_t con_interval_status; + + // Cycling Power Measurement + uint16_t measurement_value_handle; + int16_t instantaneous_power_watt; + + cycling_power_pedal_power_balance_reference_t pedal_power_balance_reference; + uint8_t pedal_power_balance_percentage; // percentage, resolution 1/2, + // If the sensor provides the power balance referenced to the left pedal, + // the power balance is calculated as [LeftPower/(LeftPower + RightPower)]*100 in units of percent + + cycling_power_torque_source_t torque_source; + uint16_t accumulated_torque_m; // meters, resolution 1/32, + // The Accumulated Torque value may decrease + // wheel revolution data: + uint32_t cumulative_wheel_revolutions; // CANNOT roll over + uint16_t last_wheel_event_time_s; // seconds, resolution 1/2048 + // crank revolution data: + uint16_t cumulative_crank_revolutions; + uint16_t last_crank_event_time_s; // seconds, resolution 1/1024 + // extreme force magnitudes + int16_t maximum_force_magnitude_newton; + int16_t minimum_force_magnitude_newton; + int16_t maximum_torque_magnitude_newton_m; // newton meters, resolution 1/32 + int16_t minimum_torque_magnitude_newton_m; // newton meters, resolution 1/32 + // extreme angles + uint16_t maximum_angle_deg; // 12bit, degrees + uint16_t minimum_angle_deg; // 12bit, degrees, concatenated with previous into 3 octets + // i.e. if the Maximum Angle is 0xABC and the Minimum Angle is 0x123, the transmitted value is 0x123ABC. + uint16_t top_dead_spot_angle_deg; + uint16_t bottom_dead_spot_angle_deg; // The Bottom Dead Spot Angle field represents the crank angle when the value of the Instantaneous Power value becomes negative. + uint16_t accumulated_energy_kJ; // kilojoules; CANNOT roll over + + // uint8_t offset_compensation; + + // CP Measurement Notification (Client Characteristic Configuration) + uint16_t measurement_client_configuration_descriptor_handle; + uint16_t measurement_client_configuration_descriptor_notify; + btstack_context_callback_registration_t measurement_notify_callback; + + // CP Measurement Broadcast (Server Characteristic Configuration) + uint16_t measurement_server_configuration_descriptor_handle; + uint16_t measurement_server_configuration_descriptor_broadcast; + btstack_context_callback_registration_t measurement_broadcast_callback; + + // Cycling Power Feature + uint16_t feature_value_handle; + uint32_t feature_flags; // see cycling_power_feature_flag_t + uint16_t masked_measurement_flags; + uint16_t default_measurement_flags; + + // Sensor Location + uint16_t sensor_location_value_handle; + cycling_power_sensor_location_t sensor_location; // see cycling_power_sensor_location_t + cycling_power_sensor_location_t * supported_sensor_locations; + uint16_t num_supported_sensor_locations; + uint16_t crank_length_mm; // resolution 1/2 mm + uint16_t chain_length_mm; // resolution 1 mm + uint16_t chain_weight_g; // resolution 1 gram + uint16_t span_length_mm; // resolution 1 mm + + gatt_date_time_t factory_calibration_date; + + uint8_t sampling_rate_hz; // resolution 1 Herz + + int16_t current_force_magnitude_newton; + int16_t current_torque_magnitude_newton_m; // newton meters, resolution 1/32 + uint16_t manufacturer_company_id; + uint8_t num_manufacturer_specific_data; + uint8_t * manufacturer_specific_data; + + // Cycling Power Vector + uint16_t vector_value_handle; + uint16_t vector_cumulative_crank_revolutions; + uint16_t vector_last_crank_event_time_s; // seconds, resolution 1/1024 + uint16_t vector_first_crank_measurement_angle_deg; + int16_t * vector_instantaneous_force_magnitude_newton_array; // newton + int force_magnitude_count; + int16_t * vector_instantaneous_torque_magnitude_newton_per_m_array; // newton per meter, resolution 1/32 + int torque_magnitude_count; + cycling_power_instantaneous_measurement_direction_t vector_instantaneous_measurement_direction; + + // CP Vector Notification (Client Characteristic Configuration) + uint16_t vector_client_configuration_descriptor_handle; + uint16_t vector_client_configuration_descriptor_notify; + btstack_context_callback_registration_t vector_notify_callback; + + // CP Control Point + uint16_t control_point_value_handle; + // CP Control Point Indication (Client Characteristic Configuration) + uint16_t control_point_client_configuration_descriptor_handle; + uint16_t control_point_client_configuration_descriptor_indicate; + btstack_context_callback_registration_t control_point_indicate_callback; + + cycling_power_opcode_t request_opcode; + cycling_power_response_value_t response_value; + + btstack_packet_handler_t calibration_callback; + uint8_t w4_indication_complete; +} cycling_power_t; + +static att_service_handler_t cycling_power_service; +static cycling_power_t cycling_power; +static btstack_packet_callback_registration_t hci_event_callback_registration; + +static uint16_t cycling_power_service_read_callback(hci_con_handle_t con_handle, uint16_t attribute_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size){ + UNUSED(con_handle); + UNUSED(attribute_handle); + UNUSED(offset); + cycling_power_t * instance = &cycling_power; + + if (attribute_handle == instance->measurement_client_configuration_descriptor_handle){ + if (buffer && buffer_size >= 2){ + little_endian_store_16(buffer, 0, instance->measurement_client_configuration_descriptor_notify); + } + return 2; + } + + if (attribute_handle == instance->measurement_server_configuration_descriptor_handle){ + if (buffer && buffer_size >= 2){ + little_endian_store_16(buffer, 0, instance->measurement_server_configuration_descriptor_broadcast); + } + return 2; + } + + if (attribute_handle == instance->vector_client_configuration_descriptor_handle){ + if (buffer && buffer_size >= 2){ + little_endian_store_16(buffer, 0, instance->vector_client_configuration_descriptor_notify); + } + return 2; + } + + if (attribute_handle == instance->control_point_client_configuration_descriptor_handle){ + if (buffer && buffer_size >= 2){ + little_endian_store_16(buffer, 0, instance->control_point_client_configuration_descriptor_indicate); + } + return 2; + } + + if (attribute_handle == instance->feature_value_handle){ + if (buffer && buffer_size >= 4){ + little_endian_store_32(buffer, 0, instance->feature_flags); + } + return 4; + } + + if (attribute_handle == instance->sensor_location_value_handle){ + if (buffer && buffer_size >= 1){ + buffer[0] = instance->sensor_location; + } + return 1; + } + return 0; +} + +static int has_feature(cycling_power_feature_flag_t feature){ + cycling_power_t * instance = &cycling_power; + return (instance->feature_flags & (1 << feature)) != 0; +} + +static int cycling_power_vector_instantaneous_measurement_direction(void){ + cycling_power_t * instance = &cycling_power; + return instance->vector_instantaneous_measurement_direction; +} + +static uint16_t cycling_power_service_default_measurement_flags(void){ + cycling_power_t * instance = &cycling_power; + uint16_t measurement_flags = 0; + uint8_t flag[] = { + has_feature(CP_FEATURE_FLAG_PEDAL_POWER_BALANCE_SUPPORTED), + has_feature(CP_FEATURE_FLAG_PEDAL_POWER_BALANCE_SUPPORTED) && instance->pedal_power_balance_reference, + has_feature(CP_FEATURE_FLAG_ACCUMULATED_TORQUE_SUPPORTED), + has_feature(CP_FEATURE_FLAG_ACCUMULATED_TORQUE_SUPPORTED) && instance->torque_source, + has_feature(CP_FEATURE_FLAG_WHEEL_REVOLUTION_DATA_SUPPORTED), + has_feature(CP_FEATURE_FLAG_CRANK_REVOLUTION_DATA_SUPPORTED), + has_feature(CP_FEATURE_FLAG_EXTREME_MAGNITUDES_SUPPORTED) && (has_feature(CP_FEATURE_FLAG_SENSOR_MEASUREMENT_CONTEXT) == CP_SENSOR_MEASUREMENT_CONTEXT_FORCE), + has_feature(CP_FEATURE_FLAG_EXTREME_MAGNITUDES_SUPPORTED) && (has_feature(CP_FEATURE_FLAG_SENSOR_MEASUREMENT_CONTEXT) == CP_SENSOR_MEASUREMENT_CONTEXT_TORQUE), + has_feature(CP_FEATURE_FLAG_EXTREME_ANGLES_SUPPORTED), + has_feature(CP_FEATURE_FLAG_TOP_AND_BOTTOM_DEAD_SPOT_ANGLE_SUPPORTED), + has_feature(CP_FEATURE_FLAG_TOP_AND_BOTTOM_DEAD_SPOT_ANGLE_SUPPORTED), + has_feature(CP_FEATURE_FLAG_ACCUMULATED_ENERGY_SUPPORTED), + has_feature(CP_FEATURE_FLAG_OFFSET_COMPENSATION_INDICATOR_SUPPORTED) + }; + + int i; + for (i = CP_MEASUREMENT_FLAG_PEDAL_POWER_BALANCE_PRESENT; i <= CP_MEASUREMENT_FLAG_OFFSET_COMPENSATION_INDICATOR; i++){ + measurement_flags |= flag[i] << i; + } + + printf("default_measurement_flags \n"); + for (i = 0; i < CP_MEASUREMENT_FLAG_RESERVED; i++){ + uint8_t v = (measurement_flags & (1 << i)) != 0; + printf("%2d ", v); + } + printf("\n"); + + return measurement_flags; +} + +static uint16_t cycling_power_service_get_measurement_flags(cycling_power_t * instance){ + if (!instance) return 0; + if (instance->masked_measurement_flags != CYCLING_POWER_MEASUREMENT_FLAGS_CLEARED){ + return instance->masked_measurement_flags; + } + if (instance->default_measurement_flags == CYCLING_POWER_MEASUREMENT_FLAGS_CLEARED){ + instance->default_measurement_flags = cycling_power_service_default_measurement_flags(); + } + return instance->default_measurement_flags; +} + + +uint16_t cycling_power_service_measurement_flags(void){ + cycling_power_t * instance = &cycling_power; + return cycling_power_service_get_measurement_flags(instance); +} + +uint8_t cycling_power_service_vector_flags(void){ + uint8_t vector_flags = 0; + uint8_t flag[] = { + has_feature(CP_FEATURE_FLAG_CRANK_REVOLUTION_DATA_SUPPORTED), + has_feature(CP_FEATURE_FLAG_EXTREME_ANGLES_SUPPORTED), + has_feature(CP_FEATURE_FLAG_EXTREME_MAGNITUDES_SUPPORTED) && (has_feature(CP_FEATURE_FLAG_SENSOR_MEASUREMENT_CONTEXT) == CP_SENSOR_MEASUREMENT_CONTEXT_FORCE), + has_feature(CP_FEATURE_FLAG_EXTREME_MAGNITUDES_SUPPORTED) && (has_feature(CP_FEATURE_FLAG_SENSOR_MEASUREMENT_CONTEXT) == CP_SENSOR_MEASUREMENT_CONTEXT_TORQUE), + has_feature(CP_FEATURE_FLAG_INSTANTANEOUS_MEASUREMENT_DIRECTION_SUPPORTED) && cycling_power_vector_instantaneous_measurement_direction() + }; + + int i; + for (i = CP_VECTOR_FLAG_CRANK_REVOLUTION_DATA_PRESENT; i <= CP_VECTOR_FLAG_INSTANTANEOUS_MEASUREMENT_DIRECTION; i++){ + vector_flags |= flag[i] << i; + } + return vector_flags; +} + +static void cycling_power_service_vector_can_send_now(void * context){ + cycling_power_t * instance = (cycling_power_t *) context; + if (!instance){ + printf("instance is null (cycling_power_service_measurement_can_send_now)\n"); + return; + } + uint8_t value[50]; + uint8_t vector_flags = cycling_power_service_vector_flags(); + int pos = 0; + + value[pos++] = vector_flags; + int i; + printf("vector flags 0x%02x\n", vector_flags); + for (i = CP_VECTOR_FLAG_CRANK_REVOLUTION_DATA_PRESENT; i <= CP_VECTOR_FLAG_INSTANTANEOUS_MEASUREMENT_DIRECTION; i++){ + + if ((vector_flags & (1 << i)) == 0) continue; + switch ((cycling_power_vector_flag_t) i){ + case CP_VECTOR_FLAG_CRANK_REVOLUTION_DATA_PRESENT: + printf("CP_VECTOR_FLAG_CRANK_REVOLUTION_DATA_PRESENT \n"); + little_endian_store_16(value, pos, instance->cumulative_crank_revolutions); + pos += 2; + little_endian_store_16(value, pos, instance->last_crank_event_time_s); + pos += 2; + break; + case CP_VECTOR_FLAG_INSTANTANEOUS_FORCE_MAGNITUDE_ARRAY_PRESENT:{ + // TODO: get actual MTU from ATT server + printf("CP_VECTOR_FLAG_INSTANTANEOUS_FORCE_MAGNITUDE_ARRAY_PRESENT \n"); + uint16_t bytes_left = btstack_min(sizeof(value), l2cap_max_mtu() - 3 - pos); + + while (bytes_left > 2 && instance->force_magnitude_count){ + little_endian_store_16(value, pos, instance->vector_instantaneous_force_magnitude_newton_array[0]); + pos += 2; + bytes_left -= 2; + instance->vector_instantaneous_force_magnitude_newton_array++; + instance->force_magnitude_count--; + } + break; + } + case CP_VECTOR_FLAG_INSTANTANEOUS_TORQUE_MAGNITUDE_ARRAY_PRESENT:{ + // TODO: get actual MTU from ATT server + printf("CP_VECTOR_FLAG_INSTANTANEOUS_TORQUE_MAGNITUDE_ARRAY_PRESENT \n"); + + uint16_t bytes_left = btstack_min(sizeof(value), l2cap_max_mtu() - 3 - pos); + + while (bytes_left > 2 && instance->torque_magnitude_count){ + little_endian_store_16(value, pos, instance->vector_instantaneous_torque_magnitude_newton_per_m_array[0]); + pos += 2; + bytes_left -= 2; + instance->vector_instantaneous_torque_magnitude_newton_per_m_array++; + instance->torque_magnitude_count--; + } + break; + } + case CP_VECTOR_FLAG_FIRST_CRANK_MEASUREMENT_ANGLE_PRESENT: + printf("CP_VECTOR_FLAG_FIRST_CRANK_MEASUREMENT_ANGLE_PRESENT \n"); + little_endian_store_16(value, pos, instance->vector_first_crank_measurement_angle_deg); + pos += 2; + break; + case CP_VECTOR_FLAG_INSTANTANEOUS_MEASUREMENT_DIRECTION: + break; + default: + break; + } + } + + att_server_notify(instance->con_handle, instance->vector_value_handle, &value[0], pos); +} + +static int cycling_power_measurement_flag_value_size(cycling_power_measurement_flag_t flag){ + switch (flag){ + case CP_MEASUREMENT_FLAG_PEDAL_POWER_BALANCE_PRESENT: + return 1; + case CP_MEASUREMENT_FLAG_WHEEL_REVOLUTION_DATA_PRESENT: + return 6; + case CP_MEASUREMENT_FLAG_CRANK_REVOLUTION_DATA_PRESENT: + case CP_MEASUREMENT_FLAG_EXTREME_FORCE_MAGNITUDES_PRESENT: + case CP_MEASUREMENT_FLAG_EXTREME_TORQUE_MAGNITUDES_PRESENT: + return 4; + case CP_MEASUREMENT_FLAG_EXTREME_ANGLES_PRESENT: + return 3; + case CP_MEASUREMENT_FLAG_ACCUMULATED_TORQUE_PRESENT: + case CP_MEASUREMENT_FLAG_TOP_DEAD_SPOT_ANGLE_PRESENT: + case CP_MEASUREMENT_FLAG_BOTTOM_DEAD_SPOT_ANGLE_PRESENT: + case CP_MEASUREMENT_FLAG_ACCUMULATED_ENERGY_PRESENT: + return 2; + default: + return 0; + } +} + +static int cycling_power_store_measurement_flag_value(cycling_power_t * instance, cycling_power_measurement_flag_t flag, uint8_t * value){ + if (!instance) return 0; + + int pos = 0; + switch (flag){ + case CP_MEASUREMENT_FLAG_PEDAL_POWER_BALANCE_PRESENT: + value[pos++] = instance->pedal_power_balance_percentage; + break; + case CP_MEASUREMENT_FLAG_ACCUMULATED_TORQUE_PRESENT: + little_endian_store_16(value, pos, instance->accumulated_torque_m); + pos += 2; + break; + case CP_MEASUREMENT_FLAG_WHEEL_REVOLUTION_DATA_PRESENT: + little_endian_store_32(value, pos, instance->cumulative_wheel_revolutions); + pos += 4; + little_endian_store_16(value, pos, instance->last_wheel_event_time_s); + pos += 2; + break; + case CP_MEASUREMENT_FLAG_CRANK_REVOLUTION_DATA_PRESENT: + little_endian_store_16(value, pos, instance->cumulative_crank_revolutions); + pos += 2; + little_endian_store_16(value, pos, instance->last_crank_event_time_s); + pos += 2; + break; + case CP_MEASUREMENT_FLAG_EXTREME_FORCE_MAGNITUDES_PRESENT: + little_endian_store_16(value, pos, (uint16_t)instance->maximum_force_magnitude_newton); + pos += 2; + little_endian_store_16(value, pos, (uint16_t)instance->minimum_force_magnitude_newton); + pos += 2; + break; + case CP_MEASUREMENT_FLAG_EXTREME_TORQUE_MAGNITUDES_PRESENT: + little_endian_store_16(value, pos, (uint16_t)instance->maximum_torque_magnitude_newton_m); + pos += 2; + little_endian_store_16(value, pos, (uint16_t)instance->minimum_torque_magnitude_newton_m); + pos += 2; + break; + case CP_MEASUREMENT_FLAG_EXTREME_ANGLES_PRESENT: + little_endian_store_24(value, pos, (instance->maximum_angle_deg << 12) | instance->minimum_angle_deg); + pos += 3; + break; + case CP_MEASUREMENT_FLAG_TOP_DEAD_SPOT_ANGLE_PRESENT: + little_endian_store_16(value, pos, (uint16_t)instance->top_dead_spot_angle_deg); + pos += 2; + break; + case CP_MEASUREMENT_FLAG_BOTTOM_DEAD_SPOT_ANGLE_PRESENT: + little_endian_store_16(value, pos, (uint16_t)instance->bottom_dead_spot_angle_deg); + pos += 2; + break; + case CP_MEASUREMENT_FLAG_ACCUMULATED_ENERGY_PRESENT: + little_endian_store_16(value, pos, (uint16_t)instance->accumulated_energy_kJ); + pos += 2; + break; + default: + break; + } + return pos; +} + + +static int cycling_power_store_measurement(cycling_power_t * instance, uint8_t * value, uint16_t max_value_size){ + if (max_value_size < 4) return 0; + if (!instance) return 0; + + uint16_t measurement_flags = cycling_power_service_get_measurement_flags(instance); + int pos = 0; + little_endian_store_16(value, 0, measurement_flags); + pos += 2; + little_endian_store_16(value, 2, instance->instantaneous_power_watt); + pos += 2; + int flag; + uint16_t bytes_left = max_value_size - pos; + + for (flag = 0; flag < CP_MEASUREMENT_FLAG_RESERVED; flag++){ + if ((measurement_flags & (1 << flag)) == 0) continue; + uint16_t value_size = cycling_power_measurement_flag_value_size(flag); + if (value_size > bytes_left ) return pos; + cycling_power_store_measurement_flag_value(instance, flag, &value[pos]); + pos += value_size; + bytes_left -= value_size; + } + return pos; +} + +int cycling_power_get_measurement_adv(uint16_t adv_interval, uint8_t * broadcast_adv, uint16_t max_value_size){ + if (max_value_size < 12) return 0; + cycling_power_t * instance = &cycling_power; + int pos = 0; + // adv flags + broadcast_adv[pos++] = 2; + broadcast_adv[pos++] = BLUETOOTH_DATA_TYPE_FLAGS; + broadcast_adv[pos++] = 0x4; + + // adv interval + broadcast_adv[pos++] = 3; + broadcast_adv[pos++] = BLUETOOTH_DATA_TYPE_ADVERTISING_INTERVAL; + little_endian_store_16(broadcast_adv, pos, adv_interval); + pos += 2; + // + int value_len = cycling_power_store_measurement(instance, &broadcast_adv[pos+4], CYCLING_POWER_MAX_BROACAST_MSG_SIZE - (pos + 4)); + broadcast_adv[pos++] = 3 + value_len; + broadcast_adv[pos++] = BLUETOOTH_DATA_TYPE_SERVICE_DATA_16_BIT_UUID; + little_endian_store_16(broadcast_adv, pos, ORG_BLUETOOTH_SERVICE_CYCLING_POWER); + pos += 2; + // value data already in place cycling_power_get_measurement + pos += value_len; + // set ADV_NONCONN_IND + return pos; +} + +static void cycling_power_service_broadcast_can_send_now(void * context){ + cycling_power_t * instance = (cycling_power_t *) context; + if (!instance){ + printf("instance is null (cycling_power_service_broadcast_can_send_now)\n"); + return; + } + uint8_t value[CYCLING_POWER_MAX_BROACAST_MSG_SIZE]; + int pos = cycling_power_store_measurement(instance, &value[0], sizeof(value)); + att_server_notify(instance->con_handle, instance->measurement_value_handle, &value[0], pos); +} + +static void cycling_power_service_measurement_can_send_now(void * context){ + cycling_power_t * instance = (cycling_power_t *) context; + if (!instance){ + printf("instance is null (cycling_power_service_measurement_can_send_now)\n"); + return; + } + uint8_t value[40]; + int pos = cycling_power_store_measurement(instance, &value[0], sizeof(value)); + att_server_notify(instance->con_handle, instance->measurement_value_handle, &value[0], pos); +} + +static void cycling_power_service_response_can_send_now(void * context){ + cycling_power_t * instance = (cycling_power_t *) context; + if (!instance){ + printf("instance is null (cycling_power_service_response_can_send_now)\n"); + return; + } + + if (instance->response_value == CP_RESPONSE_VALUE_W4_VALUE_AVAILABLE){ + printf("cycling_power_service_response_can_send_now: CP_RESPONSE_VALUE_W4_VALUE_AVAILABLE\n"); + return; + } + + // if (instance->w4_indication_complete){ + // printf("cycling_power_service_response_can_send_now: w4_indication_complete\n"); + // return; + // } + + uint8_t value[3 + btstack_max(CP_SENSOR_LOCATION_RESERVED, CYCLING_POWER_MANUFACTURER_SPECIFIC_DATA_MAX_SIZE + 5)]; + int pos = 0; + value[pos++] = CP_OPCODE_RESPONSE_CODE; + value[pos++] = instance->request_opcode; + value[pos++] = instance->response_value; + if (instance->response_value == CP_RESPONSE_VALUE_SUCCESS){ + switch (instance->request_opcode){ + case CP_OPCODE_REQUEST_SUPPORTED_SENSOR_LOCATIONS:{ + int i; + for (i=0; inum_supported_sensor_locations; i++){ + value[pos++] = instance->supported_sensor_locations[i]; + } + break; + } + case CP_OPCODE_REQUEST_CRANK_LENGTH: + little_endian_store_16(value, pos, instance->crank_length_mm); + pos += 2; + break; + case CP_OPCODE_REQUEST_CHAIN_LENGTH: + little_endian_store_16(value, pos, instance->chain_length_mm); + pos += 2; + break; + case CP_OPCODE_REQUEST_CHAIN_WEIGHT: + little_endian_store_16(value, pos, instance->chain_weight_g); + pos += 2; + break; + case CP_OPCODE_REQUEST_SPAN_LENGTH: + little_endian_store_16(value, pos, instance->span_length_mm); + pos += 2; + break; + case CP_OPCODE_REQUEST_FACTORY_CALIBRATION_DATE: + little_endian_store_16(value, pos, instance->factory_calibration_date.year); + pos += 2; + value[pos++] = instance->factory_calibration_date.month; + value[pos++] = instance->factory_calibration_date.day; + value[pos++] = instance->factory_calibration_date.hours; + value[pos++] = instance->factory_calibration_date.minutes; + value[pos++] = instance->factory_calibration_date.seconds; + break; + case CP_OPCODE_REQUEST_SAMPLING_RATE: + value[pos++] = instance->sampling_rate_hz; + break; + case CP_OPCODE_START_OFFSET_COMPENSATION: + case CP_OPCODE_START_ENHANCED_OFFSET_COMPENSATION:{ + uint16_t calibrated_value = 0xffff; + if (has_feature(CP_FEATURE_FLAG_EXTREME_MAGNITUDES_SUPPORTED)){ + if (has_feature(CP_FEATURE_FLAG_SENSOR_MEASUREMENT_CONTEXT) == CP_SENSOR_MEASUREMENT_CONTEXT_FORCE) { + calibrated_value = instance->current_force_magnitude_newton; + } else if (has_feature(CP_FEATURE_FLAG_SENSOR_MEASUREMENT_CONTEXT) == CP_SENSOR_MEASUREMENT_CONTEXT_TORQUE){ + calibrated_value = instance->current_torque_magnitude_newton_m; + } + } + + if (calibrated_value == CP_CALIBRATION_STATUS_INCORRECT_CALIBRATION_POSITION){ + value[pos++] = calibrated_value; + // do not include manufacturer ID and data + break; + } else if (calibrated_value == CP_CALIBRATION_STATUS_MANUFACTURER_SPECIFIC_ERROR_FOLLOWS){ + value[pos++] = calibrated_value; + } else { + little_endian_store_16(value, pos, calibrated_value); + pos += 2; + + } + + if (instance->request_opcode == CP_OPCODE_START_OFFSET_COMPENSATION) break; + little_endian_store_16(value, pos, instance->manufacturer_company_id); + pos += 2; + int data_len = instance->num_manufacturer_specific_data < CYCLING_POWER_MANUFACTURER_SPECIFIC_DATA_MAX_SIZE ? instance->num_manufacturer_specific_data : (CYCLING_POWER_MANUFACTURER_SPECIFIC_DATA_MAX_SIZE - 1); + value[pos++] = data_len; + memcpy(&value[pos], instance->manufacturer_specific_data, data_len); + pos += data_len; + value[pos++] = 0; + break; + } + case CP_OPCODE_MASK_CYCLING_POWER_MEASUREMENT_CHARACTERISTIC_CONTENT: + break; + default: + break; + } + } + cycling_power_opcode_t temp_request_opcode = instance->request_opcode; + + uint8_t status = att_server_indicate(instance->con_handle, instance->control_point_value_handle, &value[0], pos); + if (status == ERROR_CODE_SUCCESS){ + instance->w4_indication_complete = 1; + printf("cycling_power_service_response_can_send_now: set w4_indication_complete\n"); + printf("can_send_now set opcode to CP_OPCODE_IDLE\n"); + instance->request_opcode = CP_OPCODE_IDLE; + } else { + printf("can_send_now failed 0x%2x\n", status); + } + switch (temp_request_opcode){ + // todo handle notify if needed + default: + break; + } +} + +static int cycling_power_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){ + UNUSED(con_handle); + UNUSED(transaction_mode); + UNUSED(offset); + UNUSED(buffer_size); + cycling_power_t * instance = &cycling_power; + + printf("cycling_power_service_write_callback: attr handle 0x%02x\n", attribute_handle); + if (attribute_handle == instance->measurement_client_configuration_descriptor_handle){ + if (buffer_size < 2){ + return ATT_ERROR_INVALID_OFFSET; + } + instance->measurement_client_configuration_descriptor_notify = little_endian_read_16(buffer, 0); + instance->con_handle = con_handle; + if (instance->measurement_client_configuration_descriptor_notify){ + printf("measurement enable notification\n"); + } + return 0; + } + + if (attribute_handle == instance->measurement_server_configuration_descriptor_handle){ + if (buffer_size < 2){ + return ATT_ERROR_INVALID_OFFSET; + } + instance->measurement_server_configuration_descriptor_broadcast = little_endian_read_16(buffer, 0); + instance->con_handle = con_handle; + uint8_t event[5]; + int index = 0; + event[index++] = HCI_EVENT_GATT_SERVICE_META; + event[index++] = sizeof(event) - 2; + + if (instance->measurement_server_configuration_descriptor_broadcast){ + event[index++] = GATT_SERVICE_SUBEVENT_CYCLING_POWER_BROADCAST_START; + log_info("cycling power: start broadcast"); + } else { + event[index++] = GATT_SERVICE_SUBEVENT_CYCLING_POWER_BROADCAST_STOP; + log_info("cycling power: stop broadcast"); + } + little_endian_store_16(event, index, con_handle); + index += 2; + (*instance->calibration_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event)); + return 0; + } + + if (attribute_handle == instance->vector_client_configuration_descriptor_handle){ + if (buffer_size < 2){ + return ATT_ERROR_INVALID_OFFSET; + } + instance->con_handle = con_handle; + +#ifdef ENABLE_ATT_DELAYED_RESPONSE + switch (instance->con_interval_status){ + case CP_CONNECTION_INTERVAL_STATUS_REJECTED: + return ATT_ERROR_INAPPROPRIATE_CONNECTION_PARAMETERS; + + case CP_CONNECTION_INTERVAL_STATUS_ACCEPTED: + case CP_CONNECTION_INTERVAL_STATUS_RECEIVED: + if (instance->con_interval > instance->con_interval_max || instance->con_interval < instance->con_interval_min){ + printf("send gap_request_connection_parameter_update \n"); + instance->con_interval_status = CP_CONNECTION_INTERVAL_STATUS_W4_L2CAP_RESPONSE; + gap_request_connection_parameter_update(instance->con_handle, instance->con_interval_min, instance->con_interval_max, 4, 100); // 15 ms, 4, 1s + return ATT_ERROR_WRITE_RESPONSE_PENDING; + } + instance->vector_client_configuration_descriptor_notify = little_endian_read_16(buffer, 0); + return 0; + default: + return ATT_ERROR_WRITE_RESPONSE_PENDING; + + } +#endif + } + + if (attribute_handle == instance->control_point_client_configuration_descriptor_handle){ + if (buffer_size < 2){ + return ATT_ERROR_INVALID_OFFSET; + } + instance->control_point_client_configuration_descriptor_indicate = little_endian_read_16(buffer, 0); + instance->con_handle = con_handle; + if (instance->control_point_client_configuration_descriptor_indicate){ + printf("control point enable indication\n"); + } + return 0; + } + + if (attribute_handle == instance->feature_value_handle){ + if (buffer_size < 4){ + return ATT_ERROR_INVALID_OFFSET; + } + instance->feature_flags = little_endian_read_32(buffer, 0); + return 0; + } + + if (attribute_handle == instance->control_point_value_handle){ + if (instance->control_point_client_configuration_descriptor_indicate == 0) return CSC_ERROR_CODE_CCC_DESCRIPTOR_IMPROPERLY_CONFIGURED; + if (instance->w4_indication_complete != 0){ + printf("w4_indication_complete not 0 \n"); + return CSC_ERROR_CODE_PROCEDURE_ALREADY_IN_PROGRESS; + } + printf(" \n"); + printf("cycling_power_service_write_callback: w4_indication_complete %d \n", instance->w4_indication_complete); + int pos = 0; + instance->request_opcode = buffer[pos++]; + instance->response_value = CP_RESPONSE_VALUE_OP_CODE_NOT_SUPPORTED; + + switch (instance->request_opcode){ + case CP_OPCODE_SET_CUMULATIVE_VALUE: + if (!has_feature(CP_FEATURE_FLAG_WHEEL_REVOLUTION_DATA_SUPPORTED)) break; + instance->cumulative_wheel_revolutions = little_endian_read_32(buffer, pos); + instance->response_value = CP_RESPONSE_VALUE_SUCCESS; + break; + + case CP_OPCODE_REQUEST_SUPPORTED_SENSOR_LOCATIONS: + if (!has_feature(CP_FEATURE_FLAG_MULTIPLE_SENSOR_LOCATIONS_SUPPORTED)) break; + instance->response_value = CP_RESPONSE_VALUE_SUCCESS; + break; + + case CP_OPCODE_UPDATE_SENSOR_LOCATION: + if (!has_feature(CP_FEATURE_FLAG_MULTIPLE_SENSOR_LOCATIONS_SUPPORTED)) break; + cycling_power_sensor_location_t location = buffer[pos]; + int i; + instance->response_value = CP_RESPONSE_VALUE_INVALID_PARAMETER; + for (i=0; inum_supported_sensor_locations; i++){ + if (instance->supported_sensor_locations[i] == location){ + instance->sensor_location = location; + instance->response_value = CP_RESPONSE_VALUE_SUCCESS; + break; + } + } + break; + + case CP_OPCODE_REQUEST_CRANK_LENGTH: + if (!has_feature(CP_FEATURE_FLAG_CRANK_LENGTH_ADJUSTMENT_SUPPORTED)) break; + instance->response_value = CP_RESPONSE_VALUE_SUCCESS; + break; + case CP_OPCODE_SET_CRANK_LENGTH: + if (!has_feature(CP_FEATURE_FLAG_CRANK_LENGTH_ADJUSTMENT_SUPPORTED)) break; + instance->crank_length_mm = little_endian_read_16(buffer, pos); + instance->response_value = CP_RESPONSE_VALUE_SUCCESS; + break; + + case CP_OPCODE_REQUEST_CHAIN_LENGTH: + if (!has_feature(CP_FEATURE_FLAG_CHAIN_LENGTH_ADJUSTMENT_SUPPORTED)) break; + instance->response_value = CP_RESPONSE_VALUE_SUCCESS; + break; + case CP_OPCODE_SET_CHAIN_LENGTH: + if (!has_feature(CP_FEATURE_FLAG_CHAIN_LENGTH_ADJUSTMENT_SUPPORTED)) break; + instance->chain_length_mm = little_endian_read_16(buffer, pos); + instance->response_value = CP_RESPONSE_VALUE_SUCCESS; + break; + + case CP_OPCODE_REQUEST_CHAIN_WEIGHT: + if (!has_feature(CP_FEATURE_FLAG_CHAIN_WEIGHT_ADJUSTMENT_SUPPORTED)) break; + instance->response_value = CP_RESPONSE_VALUE_SUCCESS; + break; + case CP_OPCODE_SET_CHAIN_WEIGHT: + if (!has_feature(CP_FEATURE_FLAG_CHAIN_WEIGHT_ADJUSTMENT_SUPPORTED)) break; + instance->chain_weight_g = little_endian_read_16(buffer, pos); + instance->response_value = CP_RESPONSE_VALUE_SUCCESS; + break; + + case CP_OPCODE_REQUEST_SPAN_LENGTH: + if (!has_feature(CP_FEATURE_FLAG_SPAN_LENGTH_ADJUSTMENT_SUPPORTED)) break; + instance->response_value = CP_RESPONSE_VALUE_SUCCESS; + break; + case CP_OPCODE_SET_SPAN_LENGTH: + if (!has_feature(CP_FEATURE_FLAG_SPAN_LENGTH_ADJUSTMENT_SUPPORTED)) break; + instance->span_length_mm = little_endian_read_16(buffer, pos); + instance->response_value = CP_RESPONSE_VALUE_SUCCESS; + break; + + case CP_OPCODE_REQUEST_FACTORY_CALIBRATION_DATE: + if (!has_feature(CP_FEATURE_FLAG_FACTORY_CALIBRATION_DATE_SUPPORTED)) break; + instance->response_value = CP_RESPONSE_VALUE_SUCCESS; + break; + + case CP_OPCODE_REQUEST_SAMPLING_RATE: + if (!instance->vector_value_handle) break; + instance->response_value = CP_RESPONSE_VALUE_SUCCESS; + break; + + case CP_OPCODE_START_OFFSET_COMPENSATION: + case CP_OPCODE_START_ENHANCED_OFFSET_COMPENSATION: + if (!has_feature(CP_FEATURE_FLAG_OFFSET_COMPENSATION_SUPPORTED)){ + instance->response_value = CP_RESPONSE_VALUE_INVALID_PARAMETER; + break; + } + if (has_feature(CP_FEATURE_FLAG_EXTREME_MAGNITUDES_SUPPORTED) && + ((has_feature(CP_FEATURE_FLAG_SENSOR_MEASUREMENT_CONTEXT) == CP_SENSOR_MEASUREMENT_CONTEXT_FORCE) || + (has_feature(CP_FEATURE_FLAG_SENSOR_MEASUREMENT_CONTEXT) == CP_SENSOR_MEASUREMENT_CONTEXT_TORQUE)) + ){ + printf("start offset compensation procedure, enhanced %d\n", (instance->request_opcode == CP_OPCODE_START_ENHANCED_OFFSET_COMPENSATION)); + uint8_t event[7]; + int index = 0; + event[index++] = HCI_EVENT_GATT_SERVICE_META; + event[index++] = sizeof(event) - 2; + event[index++] = GATT_SERVICE_SUBEVENT_CYCLING_POWER_START_CALIBRATION; + little_endian_store_16(event, index, con_handle); + index += 2; + event[index++] = has_feature(CP_FEATURE_FLAG_SENSOR_MEASUREMENT_CONTEXT) == CP_SENSOR_MEASUREMENT_CONTEXT_TORQUE; + event[index++] = (instance->request_opcode == CP_OPCODE_START_ENHANCED_OFFSET_COMPENSATION); + instance->response_value = CP_RESPONSE_VALUE_W4_VALUE_AVAILABLE; + (*instance->calibration_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event)); + return 0; + } + instance->current_force_magnitude_newton = 0xffff; + instance->current_torque_magnitude_newton_m = 0xffff; + break; + + case CP_OPCODE_MASK_CYCLING_POWER_MEASUREMENT_CHARACTERISTIC_CONTENT:{ + if (!has_feature(CP_FEATURE_FLAG_CYCLING_POWER_MEASUREMENT_CHARACTERISTIC_CONTENT_MASKING_SUPPORTED)) break; + uint16_t mask_bitmap = little_endian_read_16(buffer, pos); + uint16_t masked_measurement_flags = instance->default_measurement_flags; + uint16_t index = 0; + + for (i = 0; i < CP_MASK_BIT_RESERVED; i++){ + uint8_t clear_bit = mask_bitmap & (1 << i) ? 1 : 0; + + masked_measurement_flags &= ~(clear_bit << index); + index++; + // following measurement flags have additional flag + switch ((cycling_power_mask_bit_t)i){ + case CP_MASK_BIT_PEDAL_POWER_BALANCE: + case CP_MASK_BIT_ACCUMULATED_TORQUE: + case CP_MASK_BIT_EXTREME_MAGNITUDES: + masked_measurement_flags &= ~(clear_bit << index); + index++; + break; + default: + break; + } + } + // printf("masked : "); + // for (i = 0; i < CP_MEASUREMENT_FLAG_RESERVED; i++){ + // uint8_t v = (instance->masked_measurement_flags & (1 << i)) != 0; + // printf("%2d ", v); + // } + // printf("\n"); + + instance->masked_measurement_flags = masked_measurement_flags; + instance->response_value = CP_RESPONSE_VALUE_SUCCESS; + break; + } + default: + break; + } + + if (instance->control_point_client_configuration_descriptor_indicate){ + instance->control_point_indicate_callback.callback = &cycling_power_service_response_can_send_now; + instance->control_point_indicate_callback.context = (void*) instance; + att_server_register_can_send_now_callback(&instance->control_point_indicate_callback, instance->con_handle); + } + return 0; + } + + printf("write callback, not handeled read on handle 0x%02x\n", attribute_handle); + return 0; +} + +static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ + UNUSED(channel); + UNUSED(size); + cycling_power_t * instance = &cycling_power; + uint8_t event = hci_event_packet_get_type(packet); + uint16_t con_handle; + + if (packet_type != HCI_EVENT_PACKET) return; + switch (event){ + case HCI_EVENT_LE_META: + switch (hci_event_le_meta_get_subevent_code(packet)){ + case HCI_SUBEVENT_LE_CONNECTION_COMPLETE: + instance->con_handle = hci_subevent_le_connection_complete_get_connection_handle(packet); + // print connection parameters (without using float operations) + instance->con_interval = hci_subevent_le_connection_complete_get_conn_interval(packet); + printf("Initial Connection Interval: %u, %u.%02u ms\n", instance->con_interval, instance->con_interval * 125 / 100, 25 * (instance->con_interval & 3)); + printf("Initial Connection Latency: %u\n", hci_subevent_le_connection_complete_get_conn_latency(packet)); + instance->con_interval_status = CP_CONNECTION_INTERVAL_STATUS_RECEIVED; + break; + case HCI_SUBEVENT_LE_CONNECTION_UPDATE_COMPLETE: + if (instance->con_interval_status != CP_CONNECTION_INTERVAL_STATUS_W4_UPDATE) return; + + if (instance->con_interval > instance->con_interval_max || instance->con_interval < instance->con_interval_min){ + instance->con_interval = hci_subevent_le_connection_update_complete_get_conn_interval(packet); + printf("Updated Connection Interval: %u, %u.%02u ms\n", instance->con_interval, instance->con_interval * 125 / 100, 25 * (instance->con_interval & 3)); + printf("Updated Connection Latency: %u\n", hci_subevent_le_connection_update_complete_get_conn_latency(packet)); + instance->con_interval_status = CP_CONNECTION_INTERVAL_STATUS_ACCEPTED; + } else { + instance->con_interval_status = CP_CONNECTION_INTERVAL_STATUS_REJECTED; + } + att_server_response_ready(l2cap_event_connection_parameter_update_response_get_handle(packet)); + break; + default: + break; + } + break; + case L2CAP_EVENT_CONNECTION_PARAMETER_UPDATE_RESPONSE: + if (instance->con_interval_status != CP_CONNECTION_INTERVAL_STATUS_W4_L2CAP_RESPONSE) return; + + printf("L2CAP Connection Parameter Update Complete, response: %x\n", l2cap_event_connection_parameter_update_response_get_result(packet)); + if (l2cap_event_connection_parameter_update_response_get_result(packet) == ERROR_CODE_SUCCESS){ + instance->con_interval_status = CP_CONNECTION_INTERVAL_STATUS_W4_UPDATE; + } else { + instance->con_interval_status = CP_CONNECTION_INTERVAL_STATUS_REJECTED; + att_server_response_ready(l2cap_event_connection_parameter_update_response_get_handle(packet)); + } + break; + + case HCI_EVENT_DISCONNECTION_COMPLETE:{ + // printf("HCI_EVENT_DISCONNECTION_COMPLETE \n"); + + if (!instance) return; + con_handle = hci_event_disconnection_complete_get_connection_handle(packet); + if (con_handle == HCI_CON_HANDLE_INVALID) return; + + instance->masked_measurement_flags = CYCLING_POWER_MEASUREMENT_FLAGS_CLEARED; + instance->w4_indication_complete = 0; + + uint8_t event[5]; + int index = 0; + event[index++] = HCI_EVENT_GATT_SERVICE_META; + event[index++] = sizeof(event) - 2; + + event[index++] = GATT_SERVICE_SUBEVENT_CYCLING_POWER_BROADCAST_STOP; + little_endian_store_16(event, index, con_handle); + index += 2; + (*instance->calibration_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event)); + + break; + } + case ATT_EVENT_HANDLE_VALUE_INDICATION_COMPLETE: + printf("ATT_EVENT_HANDLE_VALUE_INDICATION_COMPLETE status %u\n", packet[2]); + instance->w4_indication_complete = 0; + break; + + default: + break; + } +} + +void cycling_power_service_server_init(uint32_t feature_flags, + cycling_power_pedal_power_balance_reference_t reference, cycling_power_torque_source_t torque_source, + cycling_power_sensor_location_t * supported_sensor_locations, uint16_t num_supported_sensor_locations, + cycling_power_sensor_location_t current_sensor_location){ + + cycling_power_t * instance = &cycling_power; + // TODO: remove hardcoded initialization + instance->con_interval_min = 6; + instance->con_interval_max = 6; + instance->con_interval_status = CP_CONNECTION_INTERVAL_STATUS_NONE; + instance->w4_indication_complete = 0; + hci_event_callback_registration.callback = &packet_handler; + hci_add_event_handler(&hci_event_callback_registration); + l2cap_register_packet_handler(&packet_handler); + + instance->sensor_location = current_sensor_location; + instance->num_supported_sensor_locations = 0; + if (supported_sensor_locations != NULL){ + instance->num_supported_sensor_locations = num_supported_sensor_locations; + instance->supported_sensor_locations = supported_sensor_locations; + } + + instance->feature_flags = feature_flags; + instance->default_measurement_flags = CYCLING_POWER_MEASUREMENT_FLAGS_CLEARED; + instance->masked_measurement_flags = CYCLING_POWER_MEASUREMENT_FLAGS_CLEARED; + instance->pedal_power_balance_reference = reference; + instance->torque_source = torque_source; + printf("init with 0x%04x\n", instance->feature_flags); + + // get service handle range + uint16_t start_handle = 0; + uint16_t end_handle = 0xffff; + int service_found = gatt_server_get_get_handle_range_for_service_with_uuid16(ORG_BLUETOOTH_SERVICE_CYCLING_POWER, &start_handle, &end_handle); + if (!service_found){ + printf("no service found\n"); + return; + } + // get CP Mesurement characteristic value handle and client configuration handle + instance->measurement_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_CYCLING_POWER_MEASUREMENT); + instance->measurement_client_configuration_descriptor_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_CYCLING_POWER_MEASUREMENT); + instance->measurement_server_configuration_descriptor_handle = gatt_server_get_server_configuration_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_CYCLING_POWER_MEASUREMENT); + + // get CP Feature characteristic value handle and client configuration handle + instance->feature_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_CYCLING_POWER_FEATURE); + // get CP Sensor Location characteristic value handle and client configuration handle + instance->sensor_location_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_SENSOR_LOCATION); + + // get CP Vector characteristic value handle and client configuration handle + instance->vector_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_CYCLING_POWER_VECTOR); + instance->vector_client_configuration_descriptor_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_CYCLING_POWER_VECTOR); + + // get Body Sensor Location characteristic value handle and client configuration handle + instance->sensor_location_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_SENSOR_LOCATION); + + // get SP Control Point characteristic value handle and client configuration handle + instance->control_point_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_CYCLING_POWER_CONTROL_POINT); + instance->control_point_client_configuration_descriptor_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_CYCLING_POWER_CONTROL_POINT); + + printf("Measurement value handle 0x%02x\n", instance->measurement_value_handle); + printf("M. Client Cfg value handle 0x%02x\n", instance->measurement_client_configuration_descriptor_handle); + printf("M. Server Cfg value handle 0x%02x\n", instance->measurement_server_configuration_descriptor_handle); + + printf("Feature value handle 0x%02x\n", instance->feature_value_handle); + printf("Sensor location value handle 0x%02x\n", instance->sensor_location_value_handle); + + printf("Vector value handle 0x%02x\n", instance->vector_value_handle); + printf("Vector Cfg. value handle 0x%02x\n", instance->vector_client_configuration_descriptor_handle); + + printf("Control Point value handle 0x%02x\n", instance->control_point_value_handle); + printf("Control P. Cfg. value handle 0x%02x\n", instance->control_point_client_configuration_descriptor_handle); + + cycling_power_service.start_handle = start_handle; + cycling_power_service.end_handle = end_handle; + cycling_power_service.read_callback = &cycling_power_service_read_callback; + cycling_power_service.write_callback = &cycling_power_service_write_callback; + cycling_power_service.packet_handler = &packet_handler; + att_server_register_service_handler(&cycling_power_service); +} + + +void cycling_power_service_server_add_torque(int16_t torque_m){ + cycling_power_t * instance = &cycling_power; + instance->accumulated_torque_m += torque_m; +} + +void cycling_power_service_server_add_wheel_revolution(int32_t wheel_revolution, uint16_t wheel_event_time_s){ + cycling_power_t * instance = &cycling_power; + instance->last_wheel_event_time_s = wheel_event_time_s; + if (wheel_revolution < 0){ + if (instance->cumulative_wheel_revolutions > -wheel_revolution){ + instance->cumulative_wheel_revolutions += wheel_revolution; + } else { + instance->cumulative_wheel_revolutions = 0; + } + } else { + if (instance->cumulative_wheel_revolutions < 0xffffffff - wheel_revolution){ + instance->cumulative_wheel_revolutions += wheel_revolution; + } else { + instance->cumulative_wheel_revolutions = 0xffffffff; + } + } +} + +void cycling_power_service_server_add_crank_revolution(uint16_t crank_revolution, uint16_t crank_event_time_s){ + cycling_power_t * instance = &cycling_power; + instance->last_crank_event_time_s = crank_event_time_s; + instance->cumulative_crank_revolutions += crank_revolution; +} + +void cycling_power_service_add_energy(uint16_t energy_kJ){ + cycling_power_t * instance = &cycling_power; + if (instance->accumulated_energy_kJ <= 0xffff - energy_kJ){ + instance->accumulated_energy_kJ += energy_kJ; + } else { + instance->accumulated_energy_kJ = 0xffff; + } + printf("energy %d\n", instance->accumulated_energy_kJ); +} + +void cycling_power_service_server_set_instantaneous_power(int16_t instantaneous_power_watt){ + cycling_power_t * instance = &cycling_power; + instance->instantaneous_power_watt = instantaneous_power_watt; +} + +void cycling_power_service_server_set_pedal_power_balance(uint8_t pedal_power_balance_percentage){ + cycling_power_t * instance = &cycling_power; + instance->pedal_power_balance_percentage = pedal_power_balance_percentage; +} + +void cycling_power_service_server_set_force_magnitude_values(int force_magnitude_count, int16_t * force_magnitude_newton_array){ + cycling_power_t * instance = &cycling_power; + instance->force_magnitude_count = force_magnitude_count; + instance->vector_instantaneous_force_magnitude_newton_array = force_magnitude_newton_array; +} + +void cycling_power_service_server_set_torque_magnitude_values(int torque_magnitude_count, int16_t * torque_magnitude_newton_array){ + cycling_power_t * instance = &cycling_power; + instance->torque_magnitude_count = torque_magnitude_count; + instance->vector_instantaneous_torque_magnitude_newton_per_m_array = torque_magnitude_newton_array; +} + +void cycling_power_service_server_set_first_crank_measurement_angle(uint16_t first_crank_measurement_angle_deg){ + cycling_power_t * instance = &cycling_power; + instance->vector_first_crank_measurement_angle_deg = first_crank_measurement_angle_deg; +} + +void cycling_power_service_server_set_instantaneous_measurement_direction(cycling_power_instantaneous_measurement_direction_t direction){ + cycling_power_t * instance = &cycling_power; + instance->vector_instantaneous_measurement_direction = direction; +} + +void cycling_power_service_server_set_force_magnitude(int16_t min_force_magnitude_newton, int16_t max_force_magnitude_newton){ + cycling_power_t * instance = &cycling_power; + instance->minimum_force_magnitude_newton = min_force_magnitude_newton; + instance->maximum_force_magnitude_newton = max_force_magnitude_newton; +} + +void cycling_power_service_server_set_torque_magnitude(int16_t min_torque_magnitude_newton, int16_t max_torque_magnitude_newton){ + cycling_power_t * instance = &cycling_power; + instance->minimum_torque_magnitude_newton_m = min_torque_magnitude_newton; + instance->maximum_torque_magnitude_newton_m = max_torque_magnitude_newton; +} + +void cycling_power_service_server_set_angle(uint16_t min_angle_deg, uint16_t max_angle_deg){ + cycling_power_t * instance = &cycling_power; + instance->minimum_angle_deg = min_angle_deg; + instance->maximum_angle_deg = max_angle_deg; +} + +void cycling_power_service_server_set_top_dead_spot_angle(uint16_t top_dead_spot_angle_deg){ + cycling_power_t * instance = &cycling_power; + instance->top_dead_spot_angle_deg = top_dead_spot_angle_deg; +} + +void cycling_power_service_server_set_bottom_dead_spot_angle(uint16_t bottom_dead_spot_angle_deg){ + cycling_power_t * instance = &cycling_power; + instance->bottom_dead_spot_angle_deg = bottom_dead_spot_angle_deg; +} + +static int gatt_date_is_valid(gatt_date_time_t date){ + if (date.year != 0 && (date.year < 1582 || date.year > 9999)) return 0; + if (date.month != 0 && date.month > 12) return 0; + if (date.day != 0 && date.day > 31) return 0; + + if (date.hours > 23) return 0; + if (date.minutes > 59) return 0; + if (date.seconds > 59) return 0; + return 1; +} + +int cycling_power_service_server_set_factory_calibration_date(gatt_date_time_t date){ + if (!gatt_date_is_valid(date)) return 0; + + cycling_power_t * instance = &cycling_power; + instance->factory_calibration_date = date; + return 1; +} + +void cycling_power_service_server_set_sampling_rate(uint8_t sampling_rate_hz){ + cycling_power_t * instance = &cycling_power; + instance->sampling_rate_hz = sampling_rate_hz; +} + + +void cycling_power_service_server_update_values(void){ + cycling_power_t * instance = &cycling_power; + + if (instance->measurement_server_configuration_descriptor_broadcast){ + instance->measurement_broadcast_callback.callback = &cycling_power_service_broadcast_can_send_now; + instance->measurement_broadcast_callback.context = (void*) instance; + att_server_register_can_send_now_callback(&instance->measurement_broadcast_callback, instance->con_handle); + } + + if (instance->measurement_client_configuration_descriptor_notify){ + instance->measurement_notify_callback.callback = &cycling_power_service_measurement_can_send_now; + instance->measurement_notify_callback.context = (void*) instance; + att_server_register_can_send_now_callback(&instance->measurement_notify_callback, instance->con_handle); + } + + if (instance->vector_client_configuration_descriptor_notify){ + instance->vector_notify_callback.callback = &cycling_power_service_vector_can_send_now; + instance->vector_notify_callback.context = (void*) instance; + att_server_register_can_send_now_callback(&instance->vector_notify_callback, instance->con_handle); + } +} + +void cycling_power_service_server_packet_handler(btstack_packet_handler_t callback){ + if (callback == NULL){ + log_error("cycling_power_service_server_packet_handler called with NULL callback"); + return; + } + cycling_power_t * instance = &cycling_power; + instance->calibration_callback = callback; +} + +void cycling_power_server_calibration_done(cycling_power_sensor_measurement_context_t measurement_type, uint16_t calibrated_value){ + cycling_power_t * instance = &cycling_power; + if (instance->response_value != CP_RESPONSE_VALUE_W4_VALUE_AVAILABLE){ + printf("cycling_power_server_calibration_done : CP_RESPONSE_VALUE_W4_VALUE_AVAILABLE con_handle 0x%02x\n", instance->con_handle); + return; + } + instance->response_value = CP_RESPONSE_VALUE_SUCCESS; + + switch (measurement_type){ + case CP_SENSOR_MEASUREMENT_CONTEXT_FORCE: + instance->current_force_magnitude_newton = calibrated_value; + break; + case CP_SENSOR_MEASUREMENT_CONTEXT_TORQUE: + instance->current_torque_magnitude_newton_m = calibrated_value; + break; + default: + instance->response_value = CP_RESPONSE_VALUE_INVALID_PARAMETER; + break; + } + + if (instance->response_value == CP_RESPONSE_VALUE_SUCCESS){ + switch (calibrated_value){ + case CP_CALIBRATION_STATUS_INCORRECT_CALIBRATION_POSITION: + case CP_CALIBRATION_STATUS_MANUFACTURER_SPECIFIC_ERROR_FOLLOWS: + instance->response_value = CP_RESPONSE_VALUE_OPERATION_FAILED; + instance->response_value = CP_RESPONSE_VALUE_OPERATION_FAILED; + break; + default: + break; + } + } + + if (instance->control_point_client_configuration_descriptor_indicate){ + instance->control_point_indicate_callback.callback = &cycling_power_service_response_can_send_now; + instance->control_point_indicate_callback.context = (void*) instance; + att_server_register_can_send_now_callback(&instance->control_point_indicate_callback, instance->con_handle); + } +} + +void cycling_power_server_enhanced_calibration_done(cycling_power_sensor_measurement_context_t measurement_type, + uint16_t calibrated_value, uint16_t manufacturer_company_id, + uint8_t num_manufacturer_specific_data, uint8_t * manufacturer_specific_data){ + cycling_power_t * instance = &cycling_power; + if (instance->response_value != CP_RESPONSE_VALUE_W4_VALUE_AVAILABLE) return; + instance->response_value = CP_RESPONSE_VALUE_SUCCESS; + + switch (measurement_type){ + case CP_SENSOR_MEASUREMENT_CONTEXT_FORCE: + instance->current_force_magnitude_newton = calibrated_value; + break; + case CP_SENSOR_MEASUREMENT_CONTEXT_TORQUE: + instance->current_torque_magnitude_newton_m = calibrated_value; + break; + default: + instance->response_value = CP_RESPONSE_VALUE_INVALID_PARAMETER; + break; + } + + if (instance->response_value == CP_RESPONSE_VALUE_SUCCESS){ + switch (calibrated_value){ + case CP_CALIBRATION_STATUS_INCORRECT_CALIBRATION_POSITION: + case CP_CALIBRATION_STATUS_MANUFACTURER_SPECIFIC_ERROR_FOLLOWS: + instance->response_value = CP_RESPONSE_VALUE_OPERATION_FAILED; + instance->response_value = CP_RESPONSE_VALUE_OPERATION_FAILED; + break; + default: + break; + } + instance->manufacturer_company_id = manufacturer_company_id; + instance->num_manufacturer_specific_data = num_manufacturer_specific_data; + instance->manufacturer_specific_data = manufacturer_specific_data; + } + + if (instance->control_point_client_configuration_descriptor_indicate){ + instance->control_point_indicate_callback.callback = &cycling_power_service_response_can_send_now; + instance->control_point_indicate_callback.context = (void*) instance; + att_server_register_can_send_now_callback(&instance->control_point_indicate_callback, instance->con_handle); + } +} diff --git a/src/ble/gatt-service/cycling_power_service_server.h b/src/ble/gatt-service/cycling_power_service_server.h new file mode 100644 index 000000000..de95a2dc8 --- /dev/null +++ b/src/ble/gatt-service/cycling_power_service_server.h @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2018 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 + * + */ +#ifndef __CYCLING_POWER_SERVICE_SERVER_H +#define __CYCLING_POWER_SERVICE_SERVER_H + +#include + +#if defined __cplusplus +extern "C" { +#endif + +/** + * Implementation of the GATT Cycling Power Service Server + */ + +// ***************************************************************************** +/* GATT_SERVICE_SERVER_START(cycling_power_service_server){Cycling PowerService} + * + */ +// ***************************************************************************** +/* GATT_SERVICE_SERVER_END */ + +/* API_START */ +#define CYCLING_POWER_MANUFACTURER_SPECIFIC_DATA_MAX_SIZE 16 + +typedef enum { + CP_PEDAL_POWER_BALANCE_REFERENCE_UNKNOWN = 0, + CP_PEDAL_POWER_BALANCE_REFERENCE_LEFT, + CP_PEDAL_POWER_BALANCE_REFERENCE_NOT_SUPPORTED +} cycling_power_pedal_power_balance_reference_t; + +typedef enum { + CP_TORQUE_SOURCE_WHEEL = 0, + CP_TORQUE_SOURCE_CRANK, + CP_TORQUE_SOURCE_NOT_SUPPORTED +} cycling_power_torque_source_t; + +typedef enum { + CP_SENSOR_MEASUREMENT_CONTEXT_FORCE = 0, + CP_SENSOR_MEASUREMENT_CONTEXT_TORQUE +} cycling_power_sensor_measurement_context_t; + +typedef enum { + CP_DISTRIBUTED_SYSTEM_UNSPECIFIED = 0, + CP_DISTRIBUTED_SYSTEM_NOT_SUPPORTED, + CP_DISTRIBUTED_SYSTEM_SUPPORTED +} cycling_power_distributed_system_t; + +typedef enum { + CP_MEASUREMENT_FLAG_PEDAL_POWER_BALANCE_PRESENT = 0, + CP_MEASUREMENT_FLAG_PEDAL_POWER_BALANCE_REFERENCE, // 0 - unknown, 1 - left + CP_MEASUREMENT_FLAG_ACCUMULATED_TORQUE_PRESENT, + CP_MEASUREMENT_FLAG_ACCUMULATED_TORQUE_SOURCE, // 0 - wheel based, 1 - crank based + CP_MEASUREMENT_FLAG_WHEEL_REVOLUTION_DATA_PRESENT, + CP_MEASUREMENT_FLAG_CRANK_REVOLUTION_DATA_PRESENT, + CP_MEASUREMENT_FLAG_EXTREME_FORCE_MAGNITUDES_PRESENT, + CP_MEASUREMENT_FLAG_EXTREME_TORQUE_MAGNITUDES_PRESENT, + CP_MEASUREMENT_FLAG_EXTREME_ANGLES_PRESENT, + CP_MEASUREMENT_FLAG_TOP_DEAD_SPOT_ANGLE_PRESENT, + CP_MEASUREMENT_FLAG_BOTTOM_DEAD_SPOT_ANGLE_PRESENT, + CP_MEASUREMENT_FLAG_ACCUMULATED_ENERGY_PRESENT, + CP_MEASUREMENT_FLAG_OFFSET_COMPENSATION_INDICATOR, + CP_MEASUREMENT_FLAG_RESERVED +} cycling_power_measurement_flag_t; + +typedef enum { + CP_INSTANTANEOUS_MEASUREMENT_DIRECTION_UNKNOWN = 0, + CP_INSTANTANEOUS_MEASUREMENT_DIRECTION_TANGENTIAL_COMPONENT, + CP_INSTANTANEOUS_MEASUREMENT_DIRECTION_RADIAL_COMPONENT, + CP_INSTANTANEOUS_MEASUREMENT_DIRECTION_LATERAL_COMPONENT +} cycling_power_instantaneous_measurement_direction_t; + +typedef enum { + CP_VECTOR_FLAG_CRANK_REVOLUTION_DATA_PRESENT = 0, + CP_VECTOR_FLAG_FIRST_CRANK_MEASUREMENT_ANGLE_PRESENT, + CP_VECTOR_FLAG_INSTANTANEOUS_FORCE_MAGNITUDE_ARRAY_PRESENT, + CP_VECTOR_FLAG_INSTANTANEOUS_TORQUE_MAGNITUDE_ARRAY_PRESENT, + CP_VECTOR_FLAG_INSTANTANEOUS_MEASUREMENT_DIRECTION = 4, // 2 bit + CP_VECTOR_FLAG_RESERVED = 6 +} cycling_power_vector_flag_t; + +typedef enum { + CP_SENSOR_LOCATION_OTHER, + CP_SENSOR_LOCATION_TOP_OF_SHOE, + CP_SENSOR_LOCATION_IN_SHOE, + CP_SENSOR_LOCATION_HIP, + CP_SENSOR_LOCATION_FRONT_WHEEL, + CP_SENSOR_LOCATION_LEFT_CRANK, + CP_SENSOR_LOCATION_RIGHT_CRANK, + CP_SENSOR_LOCATION_LEFT_PEDAL, + CP_SENSOR_LOCATION_RIGHT_PEDAL, + CP_SENSOR_LOCATION_FRONT_HUB, + CP_SENSOR_LOCATION_REAR_DROPOUT, + CP_SENSOR_LOCATION_CHAINSTAY, + CP_SENSOR_LOCATION_REAR_WHEEL, + CP_SENSOR_LOCATION_REAR_HUB, + CP_SENSOR_LOCATION_CHEST, + CP_SENSOR_LOCATION_SPIDER, + CP_SENSOR_LOCATION_CHAIN_RING, + CP_SENSOR_LOCATION_RESERVED +} cycling_power_sensor_location_t; + +typedef enum { + CP_FEATURE_FLAG_PEDAL_POWER_BALANCE_SUPPORTED = 0, + CP_FEATURE_FLAG_ACCUMULATED_TORQUE_SUPPORTED, + CP_FEATURE_FLAG_WHEEL_REVOLUTION_DATA_SUPPORTED, + CP_FEATURE_FLAG_CRANK_REVOLUTION_DATA_SUPPORTED, + CP_FEATURE_FLAG_EXTREME_MAGNITUDES_SUPPORTED, + CP_FEATURE_FLAG_EXTREME_ANGLES_SUPPORTED, + CP_FEATURE_FLAG_TOP_AND_BOTTOM_DEAD_SPOT_ANGLE_SUPPORTED, + CP_FEATURE_FLAG_ACCUMULATED_ENERGY_SUPPORTED, + CP_FEATURE_FLAG_OFFSET_COMPENSATION_INDICATOR_SUPPORTED, + CP_FEATURE_FLAG_OFFSET_COMPENSATION_SUPPORTED, + CP_FEATURE_FLAG_CYCLING_POWER_MEASUREMENT_CHARACTERISTIC_CONTENT_MASKING_SUPPORTED, + CP_FEATURE_FLAG_MULTIPLE_SENSOR_LOCATIONS_SUPPORTED, + CP_FEATURE_FLAG_CRANK_LENGTH_ADJUSTMENT_SUPPORTED, + CP_FEATURE_FLAG_CHAIN_LENGTH_ADJUSTMENT_SUPPORTED, + CP_FEATURE_FLAG_CHAIN_WEIGHT_ADJUSTMENT_SUPPORTED, + CP_FEATURE_FLAG_SPAN_LENGTH_ADJUSTMENT_SUPPORTED, + CP_FEATURE_FLAG_SENSOR_MEASUREMENT_CONTEXT, // 0-force based, 1-torque based + CP_FEATURE_FLAG_INSTANTANEOUS_MEASUREMENT_DIRECTION_SUPPORTED, + CP_FEATURE_FLAG_FACTORY_CALIBRATION_DATE_SUPPORTED, + CP_FEATURE_FLAG_ENHANCED_OFFSET_COMPENSATION_SUPPORTED, + CP_FEATURE_FLAG_DISTRIBUTED_SYSTEM_SUPPORT = 20, // 0-unspecified, 1-not for use in distr. system, 2-used in distr. system, 3-reserved + CP_FEATURE_FLAG_RESERVED = 22 +} cycling_power_feature_flag_t; + +typedef enum { + CP_CALIBRATION_STATUS_INCORRECT_CALIBRATION_POSITION = 0x01, + CP_CALIBRATION_STATUS_MANUFACTURER_SPECIFIC_ERROR_FOLLOWS = 0xFF +} cycling_power_calibration_status_t; + + +/** + * @brief Init Server with ATT DB + */ +void cycling_power_service_server_init(uint32_t feature_flags, + cycling_power_pedal_power_balance_reference_t reference, cycling_power_torque_source_t torque_source, + cycling_power_sensor_location_t * supported_sensor_locations, uint16_t num_supported_sensor_locations, + cycling_power_sensor_location_t current_sensor_location); +/** + * @brief Push update + * @note triggers notifications if subscribed + */ +void cycling_power_service_server_update_values(void); + +void cycling_power_server_enhanced_calibration_done(cycling_power_sensor_measurement_context_t measurement_type, + uint16_t calibrated_value, uint16_t manufacturer_company_id, + uint8_t num_manufacturer_specific_data, uint8_t * manufacturer_specific_data); + +int cycling_power_get_measurement_adv(uint16_t adv_interval, uint8_t * value, uint16_t max_value_size); +/** + * @brief Register callback for the calibration. + * @param callback + */ +void cycling_power_service_server_packet_handler(btstack_packet_handler_t callback); + +void cycling_power_server_calibration_done(cycling_power_sensor_measurement_context_t measurement_type, uint16_t calibrated_value); + +int cycling_power_service_server_set_factory_calibration_date(gatt_date_time_t date); +void cycling_power_service_server_set_sampling_rate(uint8_t sampling_rate_hz); + +void cycling_power_service_server_add_torque(int16_t torque_m); +void cycling_power_service_server_add_wheel_revolution(int32_t wheel_revolution, uint16_t wheel_event_time_s); +void cycling_power_service_server_add_crank_revolution(uint16_t crank_revolution, uint16_t crank_event_time_s); +void cycling_power_service_add_energy(uint16_t energy_kJ); + +void cycling_power_service_server_set_instantaneous_power(int16_t instantaneous_power_watt); +void cycling_power_service_server_set_pedal_power_balance(uint8_t pedal_power_balance_percentage); +void cycling_power_service_server_set_force_magnitude(int16_t min_force_magnitude_newton, int16_t max_force_magnitude_newton); +void cycling_power_service_server_set_torque_magnitude(int16_t min_torque_magnitude_newton, int16_t max_torque_magnitude_newton); +void cycling_power_service_server_set_angle(uint16_t min_angle_deg, uint16_t max_angle_deg); +void cycling_power_service_server_set_top_dead_spot_angle(uint16_t top_dead_spot_angle_deg); +void cycling_power_service_server_set_bottom_dead_spot_angle(uint16_t bottom_dead_spot_angle_deg); +void cycling_power_service_server_set_force_magnitude_values(int force_magnitude_count, int16_t * force_magnitude_newton_array); +void cycling_power_service_server_set_torque_magnitude_values(int torque_magnitude_count, int16_t * torque_magnitude_newton_array); +void cycling_power_service_server_set_instantaneous_measurement_direction(cycling_power_instantaneous_measurement_direction_t direction); +// send only in first packet, ignore during continuation +void cycling_power_service_server_set_first_crank_measurement_angle(uint16_t first_crank_measurement_angle_deg); + +uint16_t cycling_power_service_measurement_flags(void); +uint8_t cycling_power_service_vector_flags(void); +/* API_END */ + +#if defined __cplusplus +} +#endif + +#endif + diff --git a/src/ble/gatt-service/cycling_speed_and_cadence_service.gatt b/src/ble/gatt-service/cycling_speed_and_cadence_service.gatt new file mode 100644 index 000000000..8dfcb1e25 --- /dev/null +++ b/src/ble/gatt-service/cycling_speed_and_cadence_service.gatt @@ -0,0 +1,11 @@ +// Specification Type org.bluetooth.service.cycling_speed_and_cadence +// https://www.bluetooth.com/api/gatt/xmlfile?xmlFileName=org.bluetooth.service.cycling_speed_and_cadence.xml + +// Cycling Speed and Cadence 1816 +PRIMARY_SERVICE, ORG_BLUETOOTH_SERVICE_CYCLING_SPEED_AND_CADENCE +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_CSC_MEASUREMENT, DYNAMIC | NOTIFY, +CLIENT_CHARACTERISTIC_CONFIGURATION, READ | WRITE, +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_CSC_FEATURE, DYNAMIC | READ, +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_SENSOR_LOCATION, DYNAMIC | READ, +CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_SC_CONTROL_POINT, DYNAMIC | WRITE | INDICATE, +CLIENT_CHARACTERISTIC_CONFIGURATION, READ | WRITE, diff --git a/src/ble/gatt-service/cycling_speed_and_cadence_service_server.c b/src/ble/gatt-service/cycling_speed_and_cadence_service_server.c new file mode 100644 index 000000000..cfcb8e1b4 --- /dev/null +++ b/src/ble/gatt-service/cycling_speed_and_cadence_service_server.c @@ -0,0 +1,390 @@ +/* + * 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__ "cycling_speed_and_cadence_service_server.c" + + +#include "bluetooth.h" +#include "btstack_defines.h" +#include "ble/att_db.h" +#include "ble/att_server.h" +#include "btstack_util.h" +#include "bluetooth_gatt.h" +#include "btstack_debug.h" +#include "l2cap.h" + +#include "ble/gatt-service/cycling_speed_and_cadence_service_server.h" + +// error codes from cscs spec +#define CSC_ERROR_CODE_PROCEDURE_ALREADY_IN_PROGRESS 0x80 +#define CSC_ERROR_CODE_CCC_DESCRIPTOR_IMPROPERLY_CONFIGURED 0x81 + +typedef enum { + CSC_RESPONSE_VALUE_SUCCESS = 1, + CSC_RESPONSE_VALUE_OP_CODE_NOT_SUPPORTED, + CSC_RESPONSE_VALUE_INVALID_PARAMETER, + CSC_RESPONSE_VALUE_OPERATION_FAILED +} csc_response_value_t; + +typedef struct { + hci_con_handle_t con_handle; + + uint8_t wheel_revolution_data_supported; + uint8_t crank_revolution_data_supported; + uint8_t multiple_sensor_locations_supported; + + // characteristic: CSC Mesurement + uint16_t measurement_value_handle; + uint32_t cumulative_wheel_revolutions; + uint16_t last_wheel_event_time; // Unit has a resolution of 1/1024s + uint16_t cumulative_crank_revolutions; + uint16_t last_crank_event_time; // Unit has a resolution of 1/1024s + + // characteristic descriptor: Client Characteristic Configuration + uint16_t measurement_client_configuration_descriptor_handle; + uint16_t measurement_client_configuration_descriptor_notify; + btstack_context_callback_registration_t measurement_callback; + + // sensor locations bitmap + uint16_t feature_handle; + + // sensor locations + uint16_t sensor_location_value_handle; + cycling_speed_and_cadence_sensor_location_t sensor_location; + uint32_t supported_sensor_locations; + + // characteristic: Heart Rate Control Point + uint16_t control_point_value_handle; + uint16_t control_point_client_configuration_descriptor_handle; + uint16_t control_point_client_configuration_descriptor_indicate; + btstack_context_callback_registration_t control_point_callback; + + csc_opcode_t request_opcode; + csc_response_value_t response_value; +} cycling_speed_and_cadence_t; + +static att_service_handler_t cycling_speed_and_cadence_service; +static cycling_speed_and_cadence_t cycling_speed_and_cadence; + +static uint16_t cycling_speed_and_cadence_service_read_callback(hci_con_handle_t con_handle, uint16_t attribute_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size){ + UNUSED(con_handle); + UNUSED(attribute_handle); + UNUSED(offset); + cycling_speed_and_cadence_t * instance = &cycling_speed_and_cadence; + + if (attribute_handle == instance->measurement_client_configuration_descriptor_handle){ + if (buffer && buffer_size >= 2){ + little_endian_store_16(buffer, 0, instance->measurement_client_configuration_descriptor_notify); + } + return 2; + } + + if (attribute_handle == instance->control_point_client_configuration_descriptor_handle){ + if (buffer && buffer_size >= 2){ + little_endian_store_16(buffer, 0, instance->control_point_client_configuration_descriptor_indicate); + } + return 2; + } + + if (attribute_handle == instance->feature_handle){ + if (buffer && buffer_size >= 2){ + uint16_t feature = (instance->wheel_revolution_data_supported << CSC_FLAG_WHEEL_REVOLUTION_DATA_SUPPORTED); + feature |= (instance->crank_revolution_data_supported << CSC_FLAG_CRANK_REVOLUTION_DATA_SUPPORTED); + feature |= (instance->multiple_sensor_locations_supported << CSC_FLAG_MULTIPLE_SENSOR_LOCATIONS_SUPPORTED); + little_endian_store_16(buffer, 0, feature); + } + return 2; + } + + if (attribute_handle == instance->sensor_location_value_handle){ + if (buffer && buffer_size >= 1){ + buffer[0] = instance->sensor_location; + } + return 1; + } + return 0; +} + +static void cycling_speed_and_cadence_service_csc_measurement_can_send_now(void * context){ + cycling_speed_and_cadence_t * instance = (cycling_speed_and_cadence_t *) context; + if (!instance){ + printf("instance is null (cycling_speed_and_cadence_service_csc_measurement_can_send_now)\n"); + return; + } + uint8_t flags = (instance->wheel_revolution_data_supported << CSC_FLAG_WHEEL_REVOLUTION_DATA_SUPPORTED); + flags |= (instance->crank_revolution_data_supported << CSC_FLAG_CRANK_REVOLUTION_DATA_SUPPORTED); + + uint8_t value[11]; + int pos = 0; + + value[pos++] = flags; + if (instance->wheel_revolution_data_supported){ + little_endian_store_32(value, pos, instance->cumulative_wheel_revolutions); + pos += 4; + little_endian_store_16(value, pos, instance->last_wheel_event_time); + pos += 2; + printf("send cumulative 0x%04x\n", instance->cumulative_wheel_revolutions); + } + + if (instance->crank_revolution_data_supported){ + little_endian_store_16(value, pos, instance->cumulative_crank_revolutions); + pos += 2; + little_endian_store_16(value, pos, instance->last_crank_event_time); + pos += 2; + } + + att_server_notify(instance->con_handle, instance->measurement_value_handle, &value[0], pos); +} + +static void cycling_speed_and_cadence_service_response_can_send_now(void * context){ + cycling_speed_and_cadence_t * instance = (cycling_speed_and_cadence_t *) context; + if (!instance){ + printf("instance is null (cycling_speed_and_cadence_service_response_can_send_now)\n"); + return; + } + + uint8_t value[3 + sizeof(cycling_speed_and_cadence_sensor_location_t)]; + int pos = 0; + value[pos++] = CSC_OPCODE_RESPONSE_CODE; + value[pos++] = instance->request_opcode; + value[pos++] = instance->response_value; + switch (instance->request_opcode){ + case CSC_OPCODE_REQUEST_SUPPORTED_SENSOR_LOCATIONS:{ + int loc; + for (loc = CSC_SERVICE_SENSOR_LOCATION_OTHER; loc < CSC_SERVICE_SENSOR_LOCATION_RESERVED-1; loc++){ + if (instance->supported_sensor_locations & (1 << loc)){ + value[pos++] = loc; + } + } + break; + } + default: + break; + } + csc_opcode_t temp_request_opcode = instance->request_opcode; + instance->request_opcode = CSC_OPCODE_IDLE; + + printf_hexdump(value, pos); + uint8_t status = att_server_indicate(instance->con_handle, instance->control_point_value_handle, &value[0], pos); + printf("att_server_indicate status 0x%02x\n", status); + switch (temp_request_opcode){ + case CSC_OPCODE_SET_CUMULATIVE_VALUE: + if (instance->response_value != CSC_RESPONSE_VALUE_SUCCESS) break; + if (instance->measurement_client_configuration_descriptor_notify){ + instance->measurement_callback.callback = &cycling_speed_and_cadence_service_csc_measurement_can_send_now; + instance->measurement_callback.context = (void*) instance; + att_server_register_can_send_now_callback(&instance->measurement_callback, instance->con_handle); + } + break; + default: + break; + } +} + +static int cycling_speed_and_cadence_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){ + UNUSED(con_handle); + UNUSED(transaction_mode); + UNUSED(offset); + UNUSED(buffer_size); + cycling_speed_and_cadence_t * instance = &cycling_speed_and_cadence; + + // printf("cycling_speed_and_cadence_service_write_callback: attr handle 0x%02x\n", attribute_handle); + if (attribute_handle == instance->measurement_client_configuration_descriptor_handle){ + if (buffer_size < 2){ + return ATT_ERROR_INVALID_OFFSET; + } + instance->measurement_client_configuration_descriptor_notify = little_endian_read_16(buffer, 0); + instance->con_handle = con_handle; + if (instance->measurement_client_configuration_descriptor_notify){ + printf("enable notification\n"); + } + return 0; + } + + if (attribute_handle == instance->control_point_client_configuration_descriptor_handle){ + if (buffer_size < 2){ + return ATT_ERROR_INVALID_OFFSET; + } + instance->control_point_client_configuration_descriptor_indicate = little_endian_read_16(buffer, 0); + instance->con_handle = con_handle; + if (instance->control_point_client_configuration_descriptor_indicate){ + printf("enable indication\n"); + } + return 0; + } + + if (attribute_handle == instance->control_point_value_handle){ + if (instance->control_point_client_configuration_descriptor_indicate == 0) return CSC_ERROR_CODE_CCC_DESCRIPTOR_IMPROPERLY_CONFIGURED; + if (instance->request_opcode != CSC_OPCODE_IDLE) return CSC_ERROR_CODE_PROCEDURE_ALREADY_IN_PROGRESS; + + instance->request_opcode = buffer[0]; + instance->response_value = CSC_RESPONSE_VALUE_SUCCESS; + + switch (instance->request_opcode){ + case CSC_OPCODE_SET_CUMULATIVE_VALUE: + if (instance->wheel_revolution_data_supported){ + instance->cumulative_wheel_revolutions = little_endian_read_32(buffer, 1); + break; + } + instance->response_value = CSC_RESPONSE_VALUE_OPERATION_FAILED; + break; + case CSC_OPCODE_START_SENSOR_CALIBRATION: + break; + case CSC_OPCODE_UPDATE_SENSOR_LOCATION: + if (instance->multiple_sensor_locations_supported){ + cycling_speed_and_cadence_sensor_location_t sensor_location = buffer[1]; + if (sensor_location >= CSC_SERVICE_SENSOR_LOCATION_RESERVED){ + instance->response_value = CSC_RESPONSE_VALUE_INVALID_PARAMETER; + break; + } + instance->sensor_location = sensor_location; + break; + } + instance->response_value = CSC_RESPONSE_VALUE_OPERATION_FAILED; + break; + case CSC_OPCODE_REQUEST_SUPPORTED_SENSOR_LOCATIONS: + break; + default: + instance->response_value = CSC_RESPONSE_VALUE_OP_CODE_NOT_SUPPORTED; + break; + } + printf("control point, opcode %02x, response %02x\n", instance->request_opcode, instance->response_value); + + if (instance->control_point_client_configuration_descriptor_indicate){ + instance->control_point_callback.callback = &cycling_speed_and_cadence_service_response_can_send_now; + instance->control_point_callback.context = (void*) instance; + att_server_register_can_send_now_callback(&instance->control_point_callback, instance->con_handle); + } + return 0; + } + + printf("heart_rate_service_read_callback, not handeled read on handle 0x%02x\n", attribute_handle); + return 0; +} + +void cycling_speed_and_cadence_service_server_init(uint32_t supported_sensor_locations, + uint8_t multiple_sensor_locations_supported, uint8_t wheel_revolution_data_supported, uint8_t crank_revolution_data_supported){ + cycling_speed_and_cadence_t * instance = &cycling_speed_and_cadence; + + instance->wheel_revolution_data_supported = wheel_revolution_data_supported; + instance->crank_revolution_data_supported = crank_revolution_data_supported; + instance->multiple_sensor_locations_supported = multiple_sensor_locations_supported; + instance->supported_sensor_locations = supported_sensor_locations; + + instance->sensor_location = CSC_SERVICE_SENSOR_LOCATION_OTHER; + + // get service handle range + uint16_t start_handle = 0; + uint16_t end_handle = 0xffff; + int service_found = gatt_server_get_get_handle_range_for_service_with_uuid16(ORG_BLUETOOTH_SERVICE_CYCLING_SPEED_AND_CADENCE, &start_handle, &end_handle); + if (!service_found){ + printf("no service found\n"); + return; + } + // // get CSC Mesurement characteristic value handle and client configuration handle + instance->measurement_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_CSC_MEASUREMENT); + instance->measurement_client_configuration_descriptor_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_CSC_MEASUREMENT); + + // get CSC Feature characteristic value handle and client configuration handle + instance->feature_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_CSC_FEATURE); + + // get Body Sensor Location characteristic value handle and client configuration handle + instance->sensor_location_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_SENSOR_LOCATION); + + // get SC Control Point characteristic value handle and client configuration handle + instance->control_point_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_SC_CONTROL_POINT); + instance->control_point_client_configuration_descriptor_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_SC_CONTROL_POINT); + printf("Measurement value handle 0x%02x\n", instance->measurement_value_handle); + printf("Measurement Cfg value handle 0x%02x\n", instance->measurement_client_configuration_descriptor_handle); + printf("Feature value handle 0x%02x\n", instance->feature_handle); + printf("Sensor location value handle 0x%02x\n", instance->sensor_location_value_handle); + printf("Control Point value handle 0x%02x\n", instance->control_point_value_handle); + printf("Control P. Cfg. value handle 0x%02x\n", instance->control_point_client_configuration_descriptor_handle); + + cycling_speed_and_cadence_service.start_handle = start_handle; + cycling_speed_and_cadence_service.end_handle = end_handle; + cycling_speed_and_cadence_service.read_callback = &cycling_speed_and_cadence_service_read_callback; + cycling_speed_and_cadence_service.write_callback = &cycling_speed_and_cadence_service_write_callback; + + att_server_register_service_handler(&cycling_speed_and_cadence_service); +} + +static void cycling_speed_and_cadence_service_calculate_cumulative_wheel_revolutions(int32_t revolutions_change){ + cycling_speed_and_cadence_t * instance = &cycling_speed_and_cadence; + if (revolutions_change < 0){ + if (instance->cumulative_wheel_revolutions > -revolutions_change){ + instance->cumulative_wheel_revolutions += revolutions_change; + } else { + instance->cumulative_wheel_revolutions = 0; + } + } else { + if (instance->cumulative_wheel_revolutions < 0xffffffff - revolutions_change){ + instance->cumulative_wheel_revolutions += revolutions_change; + } else { + instance->cumulative_wheel_revolutions = 0xffffffff; + } + } + printf("cumulative 0x%04x, wheel revolution change %d\n", instance->cumulative_wheel_revolutions, revolutions_change); +} + +static void cycling_speed_and_cadence_service_calculate_cumulative_crank_revolutions(uint16_t revolutions_change){ + cycling_speed_and_cadence_t * instance = &cycling_speed_and_cadence; + + if (instance->cumulative_crank_revolutions <= 0xffff - revolutions_change){ + instance->cumulative_crank_revolutions += revolutions_change; + } else { + instance->cumulative_crank_revolutions = 0xffff; + } +} + +// The Cumulative Wheel Revolutions value may decrement (e.g. If the bicycle is rolled in reverse), but shall not decrease below 0void cycling_speed_and_cadence_service_add_wheel_revolutions(int32_t wheel_revolutions, uint16_t last_wheel_event_time){ +void cycling_speed_and_cadence_service_server_update_values(int32_t wheel_revolutions, uint16_t last_wheel_event_time, uint16_t crank_revolutions, uint16_t last_crank_event_time){ + cycling_speed_and_cadence_t * instance = &cycling_speed_and_cadence; + + cycling_speed_and_cadence_service_calculate_cumulative_wheel_revolutions(wheel_revolutions); + instance->last_wheel_event_time = last_wheel_event_time; + + cycling_speed_and_cadence_service_calculate_cumulative_crank_revolutions(crank_revolutions); + instance->last_wheel_event_time = last_crank_event_time; + + if (instance->measurement_client_configuration_descriptor_notify){ + instance->measurement_callback.callback = &cycling_speed_and_cadence_service_csc_measurement_can_send_now; + instance->measurement_callback.context = (void*) instance; + // printf("cycling_speed_and_cadence_service_server_update_values instance %p, context %p\n", instance, instance->measurement_callback.context); + att_server_register_can_send_now_callback(&instance->measurement_callback, instance->con_handle); + } +} \ No newline at end of file diff --git a/src/ble/gatt-service/cycling_speed_and_cadence_service_server.h b/src/ble/gatt-service/cycling_speed_and_cadence_service_server.h new file mode 100644 index 000000000..38641d04c --- /dev/null +++ b/src/ble/gatt-service/cycling_speed_and_cadence_service_server.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2018 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 + * + */ +#ifndef __CYCLING_SPEED_AND_CADENCE_SERVICE_SERVER_H +#define __CYCLING_SPEED_AND_CADENCE_SERVICE_SERVER_H + +#include + +#if defined __cplusplus +extern "C" { +#endif + +/** + * Implementation of the GATT Cycling Speed and Cadence Service Server + */ + +// ***************************************************************************** +/* GATT_SERVICE_SERVER_START(cycling_speed_and_cadence_service_server){Cycling Speed and Cadence Service} + * + */ +// ***************************************************************************** +/* GATT_SERVICE_SERVER_END */ + +/* API_START */ + +typedef enum { + CSC_SERVICE_SENSOR_LOCATION_OTHER = 0, + CSC_SERVICE_SENSOR_LOCATION_TOP_OF_SHOE, + CSC_SERVICE_SENSOR_LOCATION_IN_SHOE, + CSC_SERVICE_SENSOR_LOCATION_HIP, + CSC_SERVICE_SENSOR_LOCATION_FRONT_WHEEL, + CSC_SERVICE_SENSOR_LOCATION_LEFT_CRANK, + CSC_SERVICE_SENSOR_LOCATION_RIGHT_CRANK, + CSC_SERVICE_SENSOR_LOCATION_LEFT_PEDAL, + CSC_SERVICE_SENSOR_LOCATION_RIGHT_PEDAL, + CSC_SERVICE_SENSOR_LOCATION_FRONT_HUB, + CSC_SERVICE_SENSOR_LOCATION_REAR_DROPOUT, + CSC_SERVICE_SENSOR_LOCATION_CHAINSTAY, + CSC_SERVICE_SENSOR_LOCATION_REAR_WHEEL, + CSC_SERVICE_SENSOR_LOCATION_REAR_HUB, + CSC_SERVICE_SENSOR_LOCATION_CHEST, + CSC_SERVICE_SENSOR_LOCATION_SPIDER, + CSC_SERVICE_SENSOR_LOCATION_CHAIN_RING, + CSC_SERVICE_SENSOR_LOCATION_RESERVED +} cycling_speed_and_cadence_sensor_location_t; + +typedef enum { + CSC_FLAG_WHEEL_REVOLUTION_DATA_SUPPORTED = 0, + CSC_FLAG_CRANK_REVOLUTION_DATA_SUPPORTED, + CSC_FLAG_MULTIPLE_SENSOR_LOCATIONS_SUPPORTED +} csc_feature_flag_bit_t; + +typedef enum { + CSC_OPCODE_IDLE = 0, + CSC_OPCODE_SET_CUMULATIVE_VALUE = 1, + CSC_OPCODE_START_SENSOR_CALIBRATION, + CSC_OPCODE_UPDATE_SENSOR_LOCATION, + CSC_OPCODE_REQUEST_SUPPORTED_SENSOR_LOCATIONS, + CSC_OPCODE_RESPONSE_CODE = 16 +} csc_opcode_t; + +/** + * @brief Init Server with ATT DB + */ +void cycling_speed_and_cadence_service_server_init(uint32_t supported_sensor_locations, + uint8_t multiple_sensor_locations_supported, uint8_t wheel_revolution_data_supported, uint8_t crank_revolution_data_supported); + +/** + * @brief Update heart rate (unit: beats per minute) + * @note triggers notifications if subscribed + */ +void cycling_speed_and_cadence_service_server_update_values(int32_t wheel_revolutions, uint16_t last_wheel_event_time, uint16_t crank_revolutions, uint16_t last_crank_event_time); + +/* API_END */ + +#if defined __cplusplus +} +#endif + +#endif + diff --git a/src/btstack_defines.h b/src/btstack_defines.h index d7437c0e9..32e91473c 100644 --- a/src/btstack_defines.h +++ b/src/btstack_defines.h @@ -1019,6 +1019,7 @@ typedef uint8_t sm_key_t[16]; #define HCI_EVENT_HID_META 0xEF #define HCI_EVENT_A2DP_META 0xF0 #define HCI_EVENT_HIDS_META 0xF1 +#define HCI_EVENT_GATT_SERVICE_META 0xF2 // Potential other meta groups // #define HCI_EVENT_BNEP_META 0xxx @@ -2190,4 +2191,27 @@ typedef uint8_t sm_key_t[16]; * @param con_handle */ #define HIDS_SUBEVENT_EXIT_SUSPEND 0x09 + +/** + * @format 1211 + * @param subevent_code + * @param con_handle + * @param measurement_type // 0 - force magnitude, 1 - torque magnitude, see cycling_power_sensor_measurement_context_t + * @param is_enhanced +*/ +#define GATT_SERVICE_SUBEVENT_CYCLING_POWER_START_CALIBRATION 0x01 + +/** + * @format 12 + * @param subevent_code + * @param con_handle +*/ +#define GATT_SERVICE_SUBEVENT_CYCLING_POWER_BROADCAST_START 0x02 + +/** + * @format 12 + * @param subevent_code + * @param con_handle +*/ +#define GATT_SERVICE_SUBEVENT_CYCLING_POWER_BROADCAST_STOP 0x03 #endif diff --git a/src/btstack_event.h b/src/btstack_event.h index 7f98975ad..73c36f075 100644 --- a/src/btstack_event.h +++ b/src/btstack_event.h @@ -6838,6 +6838,60 @@ static inline uint16_t hids_subevent_exit_suspend_get_con_handle(const uint8_t * return little_endian_read_16(event, 3); } +#ifdef ENABLE_BLE +/** + * @brief Get field con_handle from event GATT_SERVICE_SUBEVENT_CYCLING_POWER_START_CALIBRATION + * @param event packet + * @return con_handle + * @note: btstack_type 2 + */ +static inline uint16_t gatt_service_subevent_cycling_power_start_calibration_get_con_handle(const uint8_t * event){ + return little_endian_read_16(event, 3); +} +/** + * @brief Get field measurement_type from event GATT_SERVICE_SUBEVENT_CYCLING_POWER_START_CALIBRATION + * @param event packet + * @return measurement_type + * @note: btstack_type 1 + */ +static inline uint8_t gatt_service_subevent_cycling_power_start_calibration_get_measurement_type(const uint8_t * event){ + return event[5]; +} +/** + * @brief Get field is_enhanced from event GATT_SERVICE_SUBEVENT_CYCLING_POWER_START_CALIBRATION + * @param event packet + * @return is_enhanced + * @note: btstack_type 1 + */ +static inline uint8_t gatt_service_subevent_cycling_power_start_calibration_get_is_enhanced(const uint8_t * event){ + return event[6]; +} +#endif + +#ifdef ENABLE_BLE +/** + * @brief Get field con_handle from event GATT_SERVICE_SUBEVENT_CYCLING_POWER_BROADCAST_START + * @param event packet + * @return con_handle + * @note: btstack_type 2 + */ +static inline uint16_t gatt_service_subevent_cycling_power_broadcast_start_get_con_handle(const uint8_t * event){ + return little_endian_read_16(event, 3); +} +#endif + +#ifdef ENABLE_BLE +/** + * @brief Get field con_handle from event GATT_SERVICE_SUBEVENT_CYCLING_POWER_BROADCAST_STOP + * @param event packet + * @return con_handle + * @note: btstack_type 2 + */ +static inline uint16_t gatt_service_subevent_cycling_power_broadcast_stop_get_con_handle(const uint8_t * event){ + return little_endian_read_16(event, 3); +} +#endif + /* API_END */ diff --git a/test/pts/csc_client_test.c b/test/pts/csc_client_test.c new file mode 100644 index 000000000..4589728a6 --- /dev/null +++ b/test/pts/csc_client_test.c @@ -0,0 +1,858 @@ +/* + * Copyright (C) 2018 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__ "csc_client_test.c" + +#include +#include +#include +#include +#include + +#include "btstack.h" + +// prototypes +static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size); + +typedef enum { + TC_OFF, + TC_IDLE, + TC_W4_SCAN_RESULT, + TC_W4_CONNECT, + TC_W4_SERVICE_RESULT, + TC_W4_CHARACTERISTIC_RESULT, + TC_W4_CHARACTERISTIC_DESCRIPTOR_RESULT, + TC_W4_HEART_RATE_MEASUREMENT_CHARACTERISTIC, + TC_W4_ENABLE_NOTIFICATIONS_COMPLETE, + TC_W4_ENABLE_INDICATIONS_COMPLETE, + TC_W4_SENSOR_LOCATION_CHARACTERISTIC, + TC_W4_SENSOR_LOCATION, + TC_W4_WRITE_CHARACTERISTIC, + TC_CONNECTED, + TC_W4_DISCONNECT +} gc_state_t; + +static const char * sensor_location_string[] = { + "other", + "top of shoe", + "in shoe", + "hip", + "front wheel", + "left crank", + "right crank", + "left pedal", + "right pedal", + "front hub", + "rear dropout", + "chainstay", + "rear wheel", + "rear hub", + "chest", + "spider", + "chain ring", + "reserved" +}; + +static const char * sensor_loc2str(cycling_speed_and_cadence_sensor_location_t index){ + if (index < CSC_SERVICE_SENSOR_LOCATION_RESERVED){ + return sensor_location_string[index]; + } + return sensor_location_string[CSC_SERVICE_SENSOR_LOCATION_RESERVED]; +} + +#define MAX_NUM_MEASUREMENTS 20 +#ifdef HAVE_BTSTACK_STDIN +// pts: +static const char * device_addr_string = "00:1B:DC:07:32:EF"; +#endif +static bd_addr_t cmdline_addr = { }; +static int cmdline_addr_found = 0; + +// addr and type of device with correct name +static bd_addr_t csc_server_addr; +static bd_addr_type_t csc_server_addr_type; + +static hci_con_handle_t connection_handle; + +static gatt_client_service_t service; +static gatt_client_characteristic_t characteristic; +static gatt_client_characteristic_t measurement_characteristic; +static gatt_client_characteristic_t control_point_characteristic; + +static gatt_client_characteristic_descriptor_t descriptor; + +static gatt_client_notification_t notification_listener; +static int notification_listener_registered; + +static gatt_client_notification_t indication_listener; +static int indication_listener_registered; + +static gc_state_t state = TC_OFF; +static btstack_packet_callback_registration_t hci_event_callback_registration; +static btstack_packet_callback_registration_t sm_event_callback_registration; + +static uint32_t cumulative_wheel_revolutions[MAX_NUM_MEASUREMENTS]; +static uint16_t last_wheel_event_time[MAX_NUM_MEASUREMENTS]; // Unit has a resolution of 1/1024s +static uint16_t cumulative_crank_revolutions[MAX_NUM_MEASUREMENTS]; +static uint16_t last_crank_event_time[MAX_NUM_MEASUREMENTS]; // Unit has a resolution of 1/1024s +static int wm_index = 0; +static int cm_index = 0; + +static btstack_timer_source_t indication_timer; + +static int wheel_circumference_cm = 210; +// returns 1 if name is found in advertisement +static int advertisement_report_contains_uuid16(uint16_t uuid16, uint8_t * advertisement_report){ + // get advertisement from report event + const uint8_t * adv_data = gap_event_advertising_report_get_data(advertisement_report); + uint16_t adv_len = gap_event_advertising_report_get_data_length(advertisement_report); + + // iterate over advertisement data + ad_context_t context; + int found = 0; + for (ad_iterator_init(&context, adv_len, adv_data) ; ad_iterator_has_more(&context) ; ad_iterator_next(&context)){ + uint8_t data_type = ad_iterator_get_data_type(&context); + uint8_t data_size = ad_iterator_get_data_len(&context); + const uint8_t * data = ad_iterator_get_data(&context); + int i; + switch (data_type){ + case BLUETOOTH_DATA_TYPE_INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS: + case BLUETOOTH_DATA_TYPE_COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS: + // compare common prefix + for (i=0; i 0){ + // Speed = (Difference in two successive Cumulative Wheel Revolution values * Wheel Circumference) / (Difference in two successive Last Wheel Event Time values) + printf("instantaneous speed %f km/h\n", csc_client_calculate_instantaneous_speed_km_per_h(&cumulative_wheel_revolutions[wm_index-1], &last_wheel_event_time[wm_index-1])); + } + wm_index++; + } + + if (crank_revolution_data_supported){ + cumulative_crank_revolutions[cm_index] = little_endian_read_16(value, pos); + pos += 2; + last_crank_event_time[cm_index] = little_endian_read_16(value, pos); + pos += 2; + printf("crank_revolution_data (0x%04x) = %d, time (0x%02x) = %f s\n", cumulative_crank_revolutions[cm_index], cumulative_crank_revolutions[cm_index], + last_crank_event_time[cm_index], last_crank_event_time[cm_index]/1024.0); + + if (cm_index > 0){ + printf("instantaneous cadence %f rpm\n", + csc_client_calculate_instantaneous_cadence_rpm(&cumulative_crank_revolutions[cm_index-1], &last_crank_event_time[cm_index-1])); + } + cm_index++; + } + + break; + } + case ORG_BLUETOOTH_CHARACTERISTIC_CSC_FEATURE: + printf("ORG_BLUETOOTH_CHARACTERISTIC_CSC_FEATURE\n"); + break; + case ORG_BLUETOOTH_CHARACTERISTIC_SENSOR_LOCATION:{ + cycling_speed_and_cadence_sensor_location_t sensor_location = value[pos++]; + printf("Sensor location 0x%2x, name %s\n", sensor_location, sensor_loc2str(sensor_location)); + break; + } + default: + printf("uuid 0x%2x\n", characteristic.uuid16); + break; + } + break; + } + case GATT_EVENT_QUERY_COMPLETE: + break; + case GATT_EVENT_CAN_WRITE_WITHOUT_RESPONSE: + break; + default: + break; + } + break; + + default: + break; + } +} + +static void hci_event_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ + UNUSED(channel); + UNUSED(size); + + if (packet_type != HCI_EVENT_PACKET) return; + bd_addr_t addr; + uint16_t conn_interval; + uint8_t event = hci_event_packet_get_type(packet); + switch (event) { + case SM_EVENT_JUST_WORKS_REQUEST: + printf("Just Works requested\n"); + sm_just_works_confirm(sm_event_just_works_request_get_handle(packet)); + break; + case SM_EVENT_NUMERIC_COMPARISON_REQUEST: + printf("Confirming numeric comparison: %"PRIu32"\n", sm_event_numeric_comparison_request_get_passkey(packet)); + sm_numeric_comparison_confirm(sm_event_passkey_display_number_get_handle(packet)); + break; + case SM_EVENT_PASSKEY_DISPLAY_NUMBER: + printf("Display Passkey: %"PRIu32"\n", sm_event_passkey_display_number_get_passkey(packet)); + break; + case SM_EVENT_IDENTITY_CREATED: + sm_event_identity_created_get_identity_address(packet, addr); + printf("Identity created: type %u address %s\n", sm_event_identity_created_get_identity_addr_type(packet), bd_addr_to_str(addr)); + break; + case SM_EVENT_IDENTITY_RESOLVING_SUCCEEDED: + sm_event_identity_resolving_succeeded_get_identity_address(packet, addr); + printf("Identity resolved: type %u address %s\n", sm_event_identity_resolving_succeeded_get_identity_addr_type(packet), bd_addr_to_str(addr)); + break; + case SM_EVENT_IDENTITY_RESOLVING_FAILED: + sm_event_identity_created_get_address(packet, addr); + printf("Identity resolving failed\n"); + break; + case SM_EVENT_PAIRING_COMPLETE: + switch (sm_event_pairing_complete_get_status(packet)){ + case ERROR_CODE_SUCCESS: + printf("Pairing complete, success\n"); + break; + case ERROR_CODE_CONNECTION_TIMEOUT: + printf("Pairing failed, timeout\n"); + break; + case ERROR_CODE_REMOTE_USER_TERMINATED_CONNECTION: + printf("Pairing faileed, disconnected\n"); + break; + case ERROR_CODE_AUTHENTICATION_FAILURE: + printf("Pairing failed, reason = %u\n", sm_event_pairing_complete_get_reason(packet)); + break; + default: + break; + } + break; + case BTSTACK_EVENT_STATE: + // BTstack activated, get started + if (btstack_event_state_get_state(packet) == HCI_STATE_WORKING) { + printf("btstack is powered on\n");; + } else { + state = TC_OFF; + } + break; + case GAP_EVENT_ADVERTISING_REPORT: + if (state != TC_W4_SCAN_RESULT) return; + // check name in advertisement + if (!advertisement_report_contains_uuid16(ORG_BLUETOOTH_SERVICE_HEART_RATE, packet)) return; + // store address and type + gap_event_advertising_report_get_address(packet, csc_server_addr); + csc_server_addr_type = gap_event_advertising_report_get_address_type(packet); + // stop scanning, and connect to the device + state = TC_W4_CONNECT; + gap_stop_scan(); + printf("Stop scan. Connect to device with addr %s.\n", bd_addr_to_str(csc_server_addr)); + gap_connect(csc_server_addr,csc_server_addr_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 (state != TC_W4_CONNECT) return; + connection_handle = hci_subevent_le_connection_complete_get_connection_handle(packet); + // print connection parameters (without using float operations) + conn_interval = hci_subevent_le_connection_complete_get_conn_interval(packet); + printf("Connection Interval: %u.%02u ms\n", conn_interval * 125 / 100, 25 * (conn_interval & 3)); + printf("Connection Latency: %u\n", hci_subevent_le_connection_complete_get_conn_latency(packet)); + state = TC_CONNECTED; + // TODO: find better way to re-register listeners. Re-register notification listener if Characteristic is known + notification_listener_registered = 0; + if (measurement_characteristic.uuid16 != 0){ + gatt_client_listen_for_characteristic_value_updates(¬ification_listener, handle_gatt_client_event, connection_handle, &measurement_characteristic); + notification_listener_registered = 1; + } + + indication_listener_registered = 0; + if (control_point_characteristic.uuid16 != 0){ + gatt_client_listen_for_characteristic_value_updates(&indication_listener, handle_gatt_client_event, connection_handle, &control_point_characteristic); + indication_listener_registered = 1; + } + break; + case HCI_EVENT_DISCONNECTION_COMPLETE: + // unregister listener + connection_handle = HCI_CON_HANDLE_INVALID; + if (notification_listener_registered){ + notification_listener_registered = 0; + gatt_client_stop_listening_for_characteristic_value_updates(¬ification_listener); + } + if (indication_listener_registered){ + indication_listener_registered = 0; + gatt_client_stop_listening_for_characteristic_value_updates(&indication_listener); + } + printf("Disconnected %s\n", bd_addr_to_str(csc_server_addr)); + wm_index = 0; + cm_index = 0; + if (state == TC_OFF) break; + break; + default: + break; + } +} + +#ifdef HAVE_BTSTACK_STDIN +static void usage(const char *name){ + fprintf(stderr, "Usage: %s [-a|--address aa:bb:cc:dd:ee:ff]\n", name); + fprintf(stderr, "If no argument is provided, GATT Heart Rate Client will start scanning and connect to the first device named 'LE Streamer'.\n"); + fprintf(stderr, "To connect to a specific device use argument [-a].\n\n"); +} +#endif + +#ifdef HAVE_BTSTACK_STDIN + +static void show_usage(void){ + bd_addr_t iut_address; + gap_local_bd_addr(iut_address); + printf("\n--- GATT Heart Rate Client Test Console %s ---\n", bd_addr_to_str(iut_address)); + printf("e - create connection to addr %s\n", device_addr_string); + printf("E - disconnect\n"); + printf("b - start pairing\n"); + printf("a - start scanning\n"); + printf("\n"); + printf("P - search all primary services\n"); + printf("C - search all characteristics of last found primary service\n"); + printf("D - search all characteristic descriptors of last found characteristic\n"); + printf("\n"); + printf("H - search CSC service\n"); + printf("1 - search CSC_MEASUREMENT characteristic\n"); + printf("2 - search CSC FEATURE characteristic\n"); + printf("3 - search SENSOR_LOCATION characteristic\n"); + printf("4 - search CONTROL_POINT characteristic\n"); + printf("\n"); + printf("I - search GATT Device Information service\n"); + printf("x - search for 0x2a2a device info characteristic\n"); + printf("y - search for 0x2a25 device info characteristic\n"); + printf("z - search for 0x2a29 device info characteristic\n"); + printf(" \n"); + printf("F - search CSC feature\n"); + printf("f - search for 0x0075 handle\n"); + printf(" \n"); + + printf("r - read request for last found characteristic\n"); + printf("n - register notification for last found characteristic\n"); + printf("i - register indication for last found characteristic\n"); + printf(" \n"); + + printf("j - set cumulative wheel revolutions to 0 \n"); + printf("k - set cumulative wheel revolutions to 0xFFFF \n"); + printf("o - request supported sensor locations\n"); + printf("p - update sensor location to right pedal\n"); + printf(" \n"); + + printf("J - write unsupported opcode \n"); + printf("K - write invalid parameter \n"); + + printf(" \n"); + printf(" \n"); + printf("Ctrl-c - exit\n"); + printf("---\n"); +} + + +static uint8_t csc_client_set_cumulative_wheel_revolutions(uint32_t wheel_revolutions){ + uint8_t event[20]; + int pos = 0; + event[pos++] = CSC_OPCODE_SET_CUMULATIVE_VALUE; // opcode; + little_endian_store_32(event, pos, wheel_revolutions); + pos += 4; + return gatt_client_write_value_of_characteristic(handle_gatt_client_event, connection_handle, control_point_characteristic.value_handle, pos, event); +} + +static uint8_t csc_client_update_sensor_location(cycling_speed_and_cadence_sensor_location_t sensor_location){ + uint8_t event[20]; + int pos = 0; + event[pos++] = CSC_OPCODE_UPDATE_SENSOR_LOCATION; // opcode; + event[pos++] = sensor_location; + return gatt_client_write_value_of_characteristic(handle_gatt_client_event, connection_handle, control_point_characteristic.value_handle, pos, event); +} + +static uint8_t csc_client_request_supported_sensor_locations(){ + uint8_t event[20]; + int pos = 0; + event[pos++] = CSC_OPCODE_REQUEST_SUPPORTED_SENSOR_LOCATIONS; // opcode; + return gatt_client_write_value_of_characteristic(handle_gatt_client_event, connection_handle, control_point_characteristic.value_handle, pos, event); +} + + +static void stdin_process(char cmd){ + uint8_t status = ERROR_CODE_SUCCESS; + switch (cmd){ + case 'e': + printf("Connect to %s\n", device_addr_string); + state = TC_W4_CONNECT; + status = gap_connect(csc_server_addr, 0); + break; + case 'E': + printf("Disconnect from %s\n", device_addr_string); + state = TC_W4_DISCONNECT; + status = gap_disconnect(connection_handle); + break; + case 'b': + printf("Start pairing\n"); + sm_request_pairing(connection_handle); + break; + case 'a': + printf("Start scanning!\n"); + state = TC_W4_SCAN_RESULT; + gap_set_scan_parameters(0,0x0030, 0x0030); + gap_start_scan(); + break; + case 'P': + printf("Search all primary services\n"); + state = TC_W4_SERVICE_RESULT; + status = gatt_client_discover_primary_services(handle_gatt_client_event, connection_handle); + break; + case 'C': + printf("Search all characteristics\n"); + state = TC_W4_CHARACTERISTIC_RESULT; + status = gatt_client_discover_characteristics_for_service(handle_gatt_client_event, connection_handle, &service); + break; + case 'D': + printf("Search all characteristic descriptors\n"); + state = TC_W4_CHARACTERISTIC_DESCRIPTOR_RESULT; + status = gatt_client_discover_characteristic_descriptors(handle_gatt_client_event, connection_handle, &characteristic); + break; + case 'H': + printf("Search CSC service\n"); + state = TC_W4_SERVICE_RESULT; + status = gatt_client_discover_primary_services_by_uuid16(handle_gatt_client_event, connection_handle, ORG_BLUETOOTH_SERVICE_CYCLING_SPEED_AND_CADENCE); + break; + + case '1': + printf("Search for CSC_MEASUREMENT characteristic.\n"); + state = TC_W4_CHARACTERISTIC_RESULT; + status = gatt_client_discover_characteristics_for_service_by_uuid16(handle_gatt_client_event, connection_handle, &service, ORG_BLUETOOTH_CHARACTERISTIC_CSC_MEASUREMENT); + break; + case '2': + printf("Search for CSC_FEATURE characteristic.\n"); + state = TC_W4_CHARACTERISTIC_RESULT; + status = gatt_client_discover_characteristics_for_service_by_uuid16(handle_gatt_client_event, connection_handle, &service, ORG_BLUETOOTH_CHARACTERISTIC_CSC_FEATURE); + break; + case '3': + printf("Search for SENSOR_LOCATION characteristic.\n"); + state = TC_W4_CHARACTERISTIC_RESULT; + status = gatt_client_discover_characteristics_for_service_by_uuid16(handle_gatt_client_event, connection_handle, &service, ORG_BLUETOOTH_CHARACTERISTIC_SENSOR_LOCATION); + break; + case '4': + printf("Search for CONTROL_POINT characteristic.\n"); + state = TC_W4_CHARACTERISTIC_RESULT; + status = gatt_client_discover_characteristics_for_service_by_uuid16(handle_gatt_client_event, connection_handle, &service, ORG_BLUETOOTH_CHARACTERISTIC_SC_CONTROL_POINT); + break; + + case 'I': + printf("Search GATT Device Information service\n"); + state = TC_W4_SERVICE_RESULT; + status = gatt_client_discover_primary_services_by_uuid16(handle_gatt_client_event, connection_handle, ORG_BLUETOOTH_SERVICE_DEVICE_INFORMATION); + break; + case 'x': + printf("Search for 0x2a2a device info characteristic.\n"); + state = TC_W4_CHARACTERISTIC_RESULT; + status = gatt_client_discover_characteristics_for_service_by_uuid16(handle_gatt_client_event, connection_handle, &service, ORG_BLUETOOTH_CHARACTERISTIC_IEEE_11073_20601_REGULATORY_CERTIFICATION_DATA_LIST); + break; + case 'y': + printf("Search for 0x2a25 device info characteristic.\n"); + state = TC_W4_CHARACTERISTIC_RESULT; + status = gatt_client_discover_characteristics_for_service_by_uuid16(handle_gatt_client_event, connection_handle, &service, ORG_BLUETOOTH_CHARACTERISTIC_SERIAL_NUMBER_STRING); + break; + case 'z': + printf("Search for 0x2a29 device info characteristic.\n"); + state = TC_W4_CHARACTERISTIC_RESULT; + status = gatt_client_discover_characteristics_for_service_by_uuid16(handle_gatt_client_event, connection_handle, &service, ORG_BLUETOOTH_CHARACTERISTIC_MANUFACTURER_NAME_STRING); + break; + + // case 'x': + // printf("Search for 0x2a2a device info characteristic.\n"); + // state = TC_W4_CHARACTERISTIC_RESULT; + // status = gatt_client_discover_characteristics_for_service_by_uuid16(handle_gatt_client_event, connection_handle, &service, ORG_BLUETOOTH_CHARACTERISTIC_IEEE_11073_20601_REGULATORY_CERTIFICATION_DATA_LIST); + // break; + + case 'r': + printf("Read request for characteristic 0x%4x, value handle 0x%4x.\n", characteristic.uuid16, characteristic.value_handle); + state = TC_W4_SENSOR_LOCATION; + status = gatt_client_read_value_of_characteristic(handle_gatt_client_event, connection_handle, &characteristic); + break; + case 'n': + printf("Register notification handler for characteristic 0x%4x.\n", characteristic.uuid16); + gatt_client_listen_for_characteristic_value_updates(¬ification_listener, handle_gatt_client_event, connection_handle, &characteristic); + printf("Request Notify on characteristic 0x%4x.\n", characteristic.uuid16); + state = TC_W4_ENABLE_NOTIFICATIONS_COMPLETE; + status = gatt_client_write_client_characteristic_configuration(handle_gatt_client_event, connection_handle, + &characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION); + if (status == ERROR_CODE_SUCCESS){ + notification_listener_registered = 1; + } + break; + + case 'i': + printf("Register indication handler for characteristic 0x%4x.\n", characteristic.uuid16); + gatt_client_listen_for_characteristic_value_updates(&indication_listener, handle_gatt_client_event, connection_handle, &characteristic); + printf("Request Indicate on characteristic 0x%4x.\n", characteristic.uuid16); + state = TC_W4_ENABLE_INDICATIONS_COMPLETE; + status = gatt_client_write_client_characteristic_configuration(handle_gatt_client_event, connection_handle, + &characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_INDICATION); + if (status == ERROR_CODE_SUCCESS){ + indication_listener_registered = 1; + } + break; + + case 'j': + printf("set cumulative value to 0\n"); + state = TC_W4_WRITE_CHARACTERISTIC; + status = csc_client_set_cumulative_wheel_revolutions(0); + break; + case 'k': + printf("set cumulative value to 0xFFFF\n"); + state = TC_W4_WRITE_CHARACTERISTIC; + status = csc_client_set_cumulative_wheel_revolutions(0xFFFF); + break; + case 'o': + printf("request supported sensor locations\n"); + state = TC_W4_WRITE_CHARACTERISTIC; + status = csc_client_request_supported_sensor_locations(); + break; + case 'p': + printf("update sensor location to right pedal\n"); + state = TC_W4_WRITE_CHARACTERISTIC; + status = csc_client_update_sensor_location(CSC_SERVICE_SENSOR_LOCATION_RIGHT_PEDAL); + break; + + case 'J':{ + uint8_t value[] = {10}; + printf("write unsupported opcode\n"); + state = TC_W4_WRITE_CHARACTERISTIC; + status = gatt_client_write_value_of_characteristic(handle_gatt_client_event, connection_handle, control_point_characteristic.value_handle, 1, value); + break; + } + case 'K': + printf("write invalid param\n"); + state = TC_W4_WRITE_CHARACTERISTIC; + status = csc_client_update_sensor_location(CSC_SERVICE_SENSOR_LOCATION_RESERVED); + break; + + case '\n': + case '\r': + break; + default: + show_usage(); + break; + } + if (status != ERROR_CODE_SUCCESS){ + printf("GATT cmd \'%c\' failed, status 0x%02x\n", cmd, status); + state = TC_CONNECTED; + } +} +#endif + +int btstack_main(int argc, const char * argv[]); +int btstack_main(int argc, const char * argv[]){ + +#ifdef HAVE_BTSTACK_STDIN + 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; + } + usage(argv[0]); + return 0; + } +#else + (void)argc; + (void)argv; +#endif + + hci_event_callback_registration.callback = &hci_event_handler; + hci_add_event_handler(&hci_event_callback_registration); + + l2cap_init(); + + gatt_client_init(); + + le_device_db_init(); + + sm_init(); + sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT); + sm_set_authentication_requirements(SM_AUTHREQ_BONDING); + sm_event_callback_registration.callback = &hci_event_handler; + sm_add_event_handler(&sm_event_callback_registration); + +#ifdef HAVE_BTSTACK_STDIN + // parse human readable Bluetooth address + sscanf_bd_addr(device_addr_string, csc_server_addr); + btstack_stdin_setup(stdin_process); +#endif + + // turn on! + hci_power_control(HCI_POWER_ON); + + return 0; +} +/* EXAMPLE_END */ diff --git a/test/pts/csc_server_test.c b/test/pts/csc_server_test.c new file mode 100644 index 000000000..8486f6f8b --- /dev/null +++ b/test/pts/csc_server_test.c @@ -0,0 +1,153 @@ +/* + * 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__ "csc_server_test.c" + +#include +#include +#include +#include + +#include "csc_server_test.h" +#include "btstack.h" + +#define WHEEL_REVOLUTION_DATA_SUPPORTED 1 +#define CRANK_REVOLUTION_DATA_SUPPORTED 1 +#define MULTIPLE_SENSOR_LOCATIONS_SUPPORTED 1 + +// https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.gap.appearance.xml +// cycling / speed and cadence sensor +static const uint16_t appearance = (18 << 6) | 5; + +const uint8_t adv_data[] = { + // Flags general discoverable, BR/EDR not supported + 0x02, 0x01, 0x06, + // Name + 0x0B, 0x09, 'C', 'S', 'C', ' ', 'S', 'e', 'r', 'v', 'e','r', + // 16-bit Service UUIDs + 0x03, BLUETOOTH_DATA_TYPE_COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS, ORG_BLUETOOTH_SERVICE_CYCLING_SPEED_AND_CADENCE & 0xff, ORG_BLUETOOTH_SERVICE_CYCLING_SPEED_AND_CADENCE >> 8, + // Appearance + 3, BLUETOOTH_DATA_TYPE_APPEARANCE, appearance & 0xff, appearance >> 8, +}; + +const uint8_t adv_data_len = sizeof(adv_data); + +static int32_t wheel_revolutions = 0; +static uint16_t last_wheel_event_time = 0; +static uint16_t crank_revolutions = 0; +static uint16_t last_crank_event_time = 0; + +#ifdef HAVE_BTSTACK_STDIN + +static void show_usage(void){ + bd_addr_t iut_address; + gap_local_bd_addr(iut_address); + printf("\n--- Bluetooth CSCS Server Test Console %s ---\n", bd_addr_to_str(iut_address)); + printf("f - update forward wheel revolution\n"); + printf("r - update reverse wheel revolution\n"); + printf("c - update crank revolution\n"); + printf("Ctrl-c - exit\n"); + printf("---\n"); +} + + +static void stdin_process(char cmd){ + last_wheel_event_time += 10; + last_crank_event_time += 10; + + switch (cmd){ + case '\n': + case '\r': + break; + case 'f': + wheel_revolutions = 10; + crank_revolutions = 10; + cycling_speed_and_cadence_service_server_update_values(wheel_revolutions, last_wheel_event_time, crank_revolutions, last_crank_event_time); + break; + case 'r': + wheel_revolutions = -10; + crank_revolutions = 10; + cycling_speed_and_cadence_service_server_update_values(wheel_revolutions, last_wheel_event_time, crank_revolutions, last_crank_event_time); + break; + case 'c': + wheel_revolutions = 0; + crank_revolutions = 0; + cycling_speed_and_cadence_service_server_update_values(wheel_revolutions, last_wheel_event_time, crank_revolutions, last_crank_event_time); + break; + default: + show_usage(); + break; + } +} +#endif + +int btstack_main(void); +int btstack_main(void){ + l2cap_init(); + + // setup le device db + le_device_db_init(); + + // setup SM: Display only + sm_init(); + + // setup ATT server + att_server_init(profile_data, NULL, NULL); + + // setup heart rate service + cycling_speed_and_cadence_service_server_init(CSC_SERVICE_SENSOR_LOCATION_TOP_OF_SHOE, + MULTIPLE_SENSOR_LOCATIONS_SUPPORTED, WHEEL_REVOLUTION_DATA_SUPPORTED, CRANK_REVOLUTION_DATA_SUPPORTED); + + // setup advertisements + uint16_t adv_int_min = 0x0030; + uint16_t adv_int_max = 0x0030; + uint8_t adv_type = 0; + bd_addr_t null_addr; + memset(null_addr, 0, 6); + gap_advertisements_set_params(adv_int_min, adv_int_max, adv_type, 0, null_addr, 0x07, 0x00); + gap_advertisements_set_data(adv_data_len, (uint8_t*) adv_data); + gap_advertisements_enable(1); + +#ifdef HAVE_BTSTACK_STDIN + btstack_stdin_setup(stdin_process); +#endif + // turn on! + hci_power_control(HCI_POWER_ON); + + return 0; +} +/* EXAMPLE_END */ diff --git a/test/pts/csc_server_test.gatt b/test/pts/csc_server_test.gatt new file mode 100644 index 000000000..570f87c5b --- /dev/null +++ b/test/pts/csc_server_test.gatt @@ -0,0 +1,5 @@ +PRIMARY_SERVICE, GAP_SERVICE +CHARACTERISTIC, GAP_DEVICE_NAME, READ, "CSC Server" + +// add Cycling Speed and Cadence Service +#import diff --git a/test/pts/cycling_power_server_test.c b/test/pts/cycling_power_server_test.c new file mode 100644 index 000000000..05d23fc98 --- /dev/null +++ b/test/pts/cycling_power_server_test.c @@ -0,0 +1,501 @@ +/* + * 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__ "cycling_power_server_test.c" + +#include +#include +#include +#include + +#include "cycling_power_server_test.h" +#include "btstack.h" + +// https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.gap.appearance.xml +// cycling / cycling power sensor +static const uint16_t appearance = (18 << 6) | 4; +static uint16_t con_handle; + +static uint16_t event_time_s = 0; +static uint16_t force_magnitude_newton = 0; +static uint16_t torque_magnitude_newton_m = 0; +static uint16_t angle_deg = 0; + +static uint8_t broadcast_adv[31]; +static uint16_t adv_int_min = 0x0030; +static uint16_t adv_int_max = 0x0030; +static btstack_packet_callback_registration_t hci_event_callback_registration; + + +const uint8_t adv_data[] = { + // Flags general discoverable, BR/EDR not supported + 0x02, BLUETOOTH_DATA_TYPE_FLAGS, 0x06, + // Name + 0x0E, BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME, 'C', 'y', 'c', 'l', 'i', 'n', 'g', '_', 'P', 'o', 'w', 'e', 'r', + // 16-bit Service UUIDs + 0x03, BLUETOOTH_DATA_TYPE_COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS, ORG_BLUETOOTH_SERVICE_CYCLING_POWER & 0xff, ORG_BLUETOOTH_SERVICE_CYCLING_POWER >> 8, + // Appearance + 3, BLUETOOTH_DATA_TYPE_APPEARANCE, appearance & 0xff, appearance >> 8 +}; + +const uint8_t adv_data_len = sizeof(adv_data); + +static cycling_power_sensor_location_t supported_sensor_locations[] = { + CP_SENSOR_LOCATION_TOP_OF_SHOE, + CP_SENSOR_LOCATION_IN_SHOE, + CP_SENSOR_LOCATION_HIP, + CP_SENSOR_LOCATION_FRONT_WHEEL, + CP_SENSOR_LOCATION_LEFT_CRANK, + CP_SENSOR_LOCATION_RIGHT_CRANK +}; +static uint16_t num_supported_sensor_locations = 6; +static gatt_date_time_t calibration_date = {2018, 1, 1, 13, 40, 50}; +static cycling_power_sensor_measurement_context_t measurement_type; +static int enhanced_calibration = 0; +static uint16_t manufacturer_company_id = 0xf0f0; // SIG assigned numbers +static uint8_t numb_manufacturer_specific_data = CYCLING_POWER_MANUFACTURER_SPECIFIC_DATA_MAX_SIZE; +static uint8_t manufacturer_specific_data[] = { + 0x11, 0x11, 0x11, 0x11, + 0x22, 0x22, 0x22, 0x22, + 0x33, 0x33, 0x33, 0x33, + 0x44, 0x44, 0x44, 0x44 +}; + +#ifdef HAVE_BTSTACK_STDIN +// static char * measurement_flag_str[] = { +// "1 Pedal Power Balance", +// "2 Pedal Power Balance Reference", // Unknown/Left +// "2 Accumulated Torque", // Wheel Based/Crank +// "2 Accumulated Torque Source", // Wheel Based/Crank +// "4 2 Wheel Revolution Data", +// "2 2 Crank Revolution Data", +// "2 2 Extreme Force Magnitudes", +// "2 2 Extreme Torque Magnitudes", +// "3 Extreme Angles", +// "2 Top Dead Spot Angle", +// "2 Bottom Dead Spot Angle", +// "2 Accumulated Energy", +// "Offset Compensation Indicator" +// }; + +// static char buffer[80]; + +// static char * measurement_flag2str(cycling_power_measurement_flag_t flag, uint8_t value){ +// if (flag >= CP_MEASUREMENT_FLAG_RESERVED) return "Reserved"; + +// strcpy(buffer, measurement_flag_str[flag]); +// int pos = strlen(measurement_flag_str[flag]); +// // printf(" copy %d\n", pos); +// switch (flag){ +// case CP_MEASUREMENT_FLAG_PEDAL_POWER_BALANCE_REFERENCE: +// if (value == 0){ +// strcpy(buffer + pos, ": Unknown"); +// } else { +// strcpy(buffer + pos, ": Left"); +// } +// break; +// case CP_MEASUREMENT_FLAG_ACCUMULATED_TORQUE_PRESENT: +// if (value == 0){ +// strcpy(buffer + pos, ": Wheel Based"); +// } else { +// strcpy(buffer + pos, ": Crank Based"); +// } +// break; +// case CP_MEASUREMENT_FLAG_ACCUMULATED_TORQUE_SOURCE: +// if (value == 0){ +// strcpy(buffer + pos, ": Wheel Based"); +// } else { +// strcpy(buffer + pos, ": Crank Based"); +// } +// break; +// default: +// if (value == 0){ +// strcpy(buffer + pos, ": NOT SUPPORTED"); +// } +// break; +// } +// return &buffer[0]; +// } + +static void dump_feature_flags(uint32_t feature_flags){ + int i; + printf("feature flags: \n"); + for (i = 0; i < CP_FEATURE_FLAG_RESERVED; i++){ + printf("%02d ", i); + } + printf("\n"); + for (i = 0; i < CP_FEATURE_FLAG_RESERVED; i++){ + uint8_t value = (feature_flags & (1 << i)) != 0; + printf("%2d ", value); + } + printf("\n"); +} + +static void dump_measurement_flags(uint16_t measurement_flags){ + int i; + printf("measurement flags: \n"); + // for (i = 0; i < CP_MEASUREMENT_FLAG_RESERVED; i++){ + // printf("%02d ", i); + // } + // printf("\n"); + for (i = 0; i < CP_MEASUREMENT_FLAG_RESERVED; i++){ + uint8_t value = (measurement_flags & (1 << i)) != 0; + printf("%2d ", value); + } + printf("\n"); +} + +static void show_usage(void){ + bd_addr_t iut_address; + gap_local_bd_addr(iut_address); + printf("\n--- Bluetooth Cycling Power Server Test Console %s ---\n", bd_addr_to_str(iut_address)); + printf("u - push update\n"); + printf("\n"); + printf("t - add positive torque\n"); + printf("T - add negative torque\n"); + printf("w - add wheel revolution\n"); + printf("c - add crank revolution\n"); + printf("e - add energy\n"); + printf("\n"); + printf("p - set instantaneous power\n"); + printf("b - set power balance percentage\n"); + printf("m - set force magnitude\n"); + printf("M - set torque magnitude\n"); + printf("a - set angle\n"); + printf("x - set top dead spot angle\n"); + printf("y - set bottom dead spot angle\n"); + printf("R - reset values\n"); + printf("z - stop calibration\n"); + printf("Z - incorrect calibration position\n"); + printf("Y - Invalid param\n"); + printf("X - start calibration\n"); + + printf("\n"); + printf("Ctrl-c - exit\n"); + printf("---\n"); +} + +static void cps_reset_values(void){ + event_time_s = 0; + force_magnitude_newton = 0; + torque_magnitude_newton_m = 0; + angle_deg = 0; +} + + + +static void stdin_process(char cmd){ + switch (cmd){ + case 'C': + printf("reset all values\n"); + cps_reset_values(); + break; + case 'u':{ + printf("push update\n"); + cycling_power_service_server_update_values(); + event_time_s++; + break; + } + case 'r': + printf("add positive torque\n"); + cycling_power_service_server_add_torque(100); + break; + case 'R': + printf("add negative torque\n"); + cycling_power_service_server_add_torque(-100); + break; + case 'w': + printf("add wheel revolution\n"); + cycling_power_service_server_add_wheel_revolution(10, event_time_s); + break; + case 'W': + printf("reverse wheel revolution\n"); + cycling_power_service_server_add_wheel_revolution(-10, event_time_s); + break; + + case 'c': + printf("add crank revolution\n"); + cycling_power_service_server_add_crank_revolution(10, event_time_s); + break; + case 'e': + printf("add energy\n"); + cycling_power_service_add_energy(100); + break; + + case 'p': + printf("set instantaneous power\n"); + cycling_power_service_server_set_instantaneous_power(100); + break; + case 'b': + printf("set power balance percentage\n"); + cycling_power_service_server_set_pedal_power_balance(50); + break; + case 'm': + force_magnitude_newton += 10; + printf("set force magnitude\n"); + cycling_power_service_server_set_force_magnitude(force_magnitude_newton-5, force_magnitude_newton+5); + break; + case 'M': + torque_magnitude_newton_m += 10; + printf("set torque magnitude\n"); + cycling_power_service_server_set_torque_magnitude(torque_magnitude_newton_m-5, torque_magnitude_newton_m+5); + break; + case 'a': + angle_deg += 10; + printf("set angle\n"); + cycling_power_service_server_set_angle(angle_deg-5, angle_deg+5); + break; + case 'x': + printf("set top dead spot angle\n"); + cycling_power_service_server_set_top_dead_spot_angle(180); + break; + case 'y': + printf("set bottom dead spot angle\n"); + cycling_power_service_server_set_bottom_dead_spot_angle(20); + break; + case 'Y': + printf("Invalid parameter\n"); + uint16_t calibrated_value = 0; + if (enhanced_calibration){ + cycling_power_server_enhanced_calibration_done(3, calibrated_value, + manufacturer_company_id, numb_manufacturer_specific_data, manufacturer_specific_data); + } else { + cycling_power_server_calibration_done(2, calibrated_value); + } + break; + case 'z':{ + printf("stop calibration\n"); + switch (measurement_type){ + case CP_SENSOR_MEASUREMENT_CONTEXT_FORCE: + calibrated_value = force_magnitude_newton; + break; + case CP_SENSOR_MEASUREMENT_CONTEXT_TORQUE: + calibrated_value = torque_magnitude_newton_m; + break; + default: + printf("wrong measurement type\n"); + break; + } + + if (enhanced_calibration){ + printf(" enhanced calibration on\n"); + cycling_power_server_enhanced_calibration_done(measurement_type, calibrated_value, + manufacturer_company_id, numb_manufacturer_specific_data, manufacturer_specific_data); + } else { + // printf("cycling_power_server_calibration_done, data %d \n", calibrated_value); + cycling_power_server_calibration_done(measurement_type, calibrated_value); + } + break; + } + case 'Z': + printf("stop calibration, incorrect calibration position\n"); + if (enhanced_calibration){ + printf(" enhanced calibration on\n"); + cycling_power_server_enhanced_calibration_done(measurement_type, CP_CALIBRATION_STATUS_INCORRECT_CALIBRATION_POSITION, + manufacturer_company_id, numb_manufacturer_specific_data, manufacturer_specific_data); + } else { + cycling_power_server_calibration_done(measurement_type, CP_CALIBRATION_STATUS_INCORRECT_CALIBRATION_POSITION); + } + + break; + case 't': + printf("disconnect \n"); + // gap_advertisements_enable(0); + gap_disconnect(con_handle); + break; + case '\n': + case '\r': + break; + default: + show_usage(); + break; + } +} +#endif + + +static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ + UNUSED(channel); + UNUSED(size); + int pos; + bd_addr_t null_addr; + + if (packet_type != HCI_EVENT_PACKET) return; + + switch (hci_event_packet_get_type(packet)){ + case HCI_EVENT_LE_META: + switch (hci_event_le_meta_get_subevent_code(packet)){ + case HCI_SUBEVENT_LE_CONNECTION_COMPLETE: + con_handle = hci_subevent_le_connection_complete_get_connection_handle(packet); + break; + default: + break; + } + break; + + case HCI_EVENT_GATT_SERVICE_META: + switch (packet[2]){ + case GATT_SERVICE_SUBEVENT_CYCLING_POWER_START_CALIBRATION: + measurement_type = gatt_service_subevent_cycling_power_start_calibration_get_measurement_type(packet); + enhanced_calibration = gatt_service_subevent_cycling_power_start_calibration_get_is_enhanced(packet); + break; + + case GATT_SERVICE_SUBEVENT_CYCLING_POWER_BROADCAST_START: + printf("start broadcast\n"); + // set ADV_NONCONN_IND + pos = cycling_power_get_measurement_adv(adv_int_max, &broadcast_adv[0], sizeof(broadcast_adv)); + memset(null_addr, 0, 6); + gap_advertisements_set_params(adv_int_min, adv_int_max, 0x03, 0, null_addr, 0x07, 0x00); + gap_advertisements_set_data(pos, (uint8_t*) broadcast_adv); + gap_advertisements_enable(1); + break; + case GATT_SERVICE_SUBEVENT_CYCLING_POWER_BROADCAST_STOP: + printf("stop broadcast\n"); + gap_advertisements_enable(0); + break; + + default: + break; + } + break; + default: + break; + } + + // uint8_t event = hci_event_packet_get_type(packet); +} + +int btstack_main(void); +int btstack_main(void){ + hci_event_callback_registration.callback = &packet_handler; + hci_add_event_handler(&hci_event_callback_registration); + + l2cap_init(); + + // setup le device db + le_device_db_init(); + + // setup SM: Display only + sm_init(); + + // setup ATT server + att_server_init(profile_data, NULL, NULL); + + // setup heart rate service + // cycling_power_service_server_init(0x2FFFFF, 0x1F, CP_PEDAL_POWER_BALANCE_REFERENCE_LEFT); + + uint32_t feature_flags = 0; + feature_flags |= (1 << CP_FEATURE_FLAG_PEDAL_POWER_BALANCE_SUPPORTED); + feature_flags |= (1 << CP_FEATURE_FLAG_ACCUMULATED_TORQUE_SUPPORTED); + // feature_flags |= (1 << CP_FEATURE_FLAG_WHEEL_REVOLUTION_DATA_SUPPORTED); + // feature_flags |= (1 << CP_FEATURE_FLAG_CRANK_REVOLUTION_DATA_SUPPORTED); + feature_flags |= (1 << CP_FEATURE_FLAG_EXTREME_ANGLES_SUPPORTED); + // feature_flags |= (1 << CP_FEATURE_FLAG_TOP_AND_BOTTOM_DEAD_SPOT_ANGLE_SUPPORTED); + feature_flags |= (1 << CP_FEATURE_FLAG_ACCUMULATED_ENERGY_SUPPORTED); + // feature_flags |= (1 << CP_FEATURE_FLAG_OFFSET_COMPENSATION_INDICATOR_SUPPORTED); + // feature_flags |= (1 << CP_FEATURE_FLAG_OFFSET_COMPENSATION_SUPPORTED); + + feature_flags |= (1 << CP_FEATURE_FLAG_EXTREME_MAGNITUDES_SUPPORTED); + feature_flags |= (CP_SENSOR_MEASUREMENT_CONTEXT_FORCE << CP_FEATURE_FLAG_SENSOR_MEASUREMENT_CONTEXT); + // feature_flags |= (CP_SENSOR_MEASUREMENT_CONTEXT_TORQUE << CP_FEATURE_FLAG_SENSOR_MEASUREMENT_CONTEXT); + + // feature_flags |= (1 << CP_FEATURE_FLAG_INSTANTANEOUS_MEASUREMENT_DIRECTION_SUPPORTED); + feature_flags |= (1 << CP_FEATURE_FLAG_MULTIPLE_SENSOR_LOCATIONS_SUPPORTED); + feature_flags |= (1 << CP_FEATURE_FLAG_CRANK_LENGTH_ADJUSTMENT_SUPPORTED); + feature_flags |= (1 << CP_FEATURE_FLAG_CHAIN_LENGTH_ADJUSTMENT_SUPPORTED); + feature_flags |= (1 << CP_FEATURE_FLAG_CHAIN_WEIGHT_ADJUSTMENT_SUPPORTED); + feature_flags |= (1 << CP_FEATURE_FLAG_SPAN_LENGTH_ADJUSTMENT_SUPPORTED); + feature_flags |= (1 << CP_FEATURE_FLAG_FACTORY_CALIBRATION_DATE_SUPPORTED); + feature_flags |= (1 << CP_FEATURE_FLAG_OFFSET_COMPENSATION_SUPPORTED); + feature_flags |= (1 << CP_FEATURE_FLAG_ENHANCED_OFFSET_COMPENSATION_SUPPORTED); + feature_flags |= (1 << CP_FEATURE_FLAG_CYCLING_POWER_MEASUREMENT_CHARACTERISTIC_CONTENT_MASKING_SUPPORTED); + + + printf(" num_supported_sensor_locations %lu\n", sizeof(num_supported_sensor_locations)); + + cycling_power_service_server_init(feature_flags, CP_PEDAL_POWER_BALANCE_REFERENCE_LEFT, CP_TORQUE_SOURCE_WHEEL, + &supported_sensor_locations[0], num_supported_sensor_locations, supported_sensor_locations[0]); + cycling_power_service_server_packet_handler(packet_handler); + + cycling_power_service_server_set_factory_calibration_date(calibration_date); + + uint16_t measurement_flags = cycling_power_service_measurement_flags(); + dump_feature_flags(feature_flags); + dump_measurement_flags(measurement_flags); + // dump_measurement_flags_as_str(measurement_flags); + + cycling_power_service_server_add_torque(100); + cycling_power_service_server_add_torque(-100); + cycling_power_service_server_add_wheel_revolution(10, event_time_s); + cycling_power_service_server_add_crank_revolution(10, event_time_s); + cycling_power_service_add_energy(100); + + cycling_power_service_server_set_instantaneous_power(100); + cycling_power_service_server_set_pedal_power_balance(50); + force_magnitude_newton += 10; + cycling_power_service_server_set_force_magnitude(force_magnitude_newton-5, force_magnitude_newton+5); + torque_magnitude_newton_m += 10; + cycling_power_service_server_set_torque_magnitude(torque_magnitude_newton_m-5, torque_magnitude_newton_m+5); + angle_deg += 10; + cycling_power_service_server_set_angle(angle_deg-5, angle_deg+5); + cycling_power_service_server_set_top_dead_spot_angle(180); + cycling_power_service_server_set_bottom_dead_spot_angle(20); + cycling_power_service_add_energy(100); + int16_t values[] = {12, -50, 100}; + cycling_power_service_server_set_torque_magnitude_values(3, values); + cycling_power_service_server_set_force_magnitude_values(3, values); + cycling_power_service_server_set_instantaneous_measurement_direction(CP_INSTANTANEOUS_MEASUREMENT_DIRECTION_TANGENTIAL_COMPONENT); + // cycling_power_service_server_set_first_crank_measurement_angle(first_crank_measurement_angle_deg); + + // setup advertisements + + uint8_t adv_type = 0; // AFV_IND + bd_addr_t null_addr; + memset(null_addr, 0, 6); + gap_advertisements_set_params(adv_int_min, adv_int_max, adv_type, 0, null_addr, 0x07, 0x00); + gap_advertisements_set_data(adv_data_len, (uint8_t*) adv_data); + gap_advertisements_enable(1); + +#ifdef HAVE_BTSTACK_STDIN + btstack_stdin_setup(stdin_process); +#endif + // turn on! + hci_power_control(HCI_POWER_ON); + + return 0; +} +/* EXAMPLE_END */ diff --git a/test/pts/cycling_power_server_test.gatt b/test/pts/cycling_power_server_test.gatt new file mode 100644 index 000000000..c90de2040 --- /dev/null +++ b/test/pts/cycling_power_server_test.gatt @@ -0,0 +1,5 @@ +PRIMARY_SERVICE, GAP_SERVICE +CHARACTERISTIC, GAP_DEVICE_NAME, READ, "Cycling Power Server" + +// add Cycling Power Service +#import diff --git a/test/pts/hog_demo_test.h b/test/pts/hog_demo_test.h deleted file mode 100644 index 745390bea..000000000 --- a/test/pts/hog_demo_test.h +++ /dev/null @@ -1,235 +0,0 @@ - -// hog_demo_test.h generated from hog_demo_test.gatt for BTstack -// att db format version 1 - -// binary attribute representation: -// - size in bytes (16), flags(16), handle (16), uuid (16/128), value(...) - -#include - -const uint8_t profile_data[] = -{ - // ATT DB Version - 1, - - // 0x0001 PRIMARY_SERVICE-GAP_SERVICE - 0x0a, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x28, 0x00, 0x18, - // 0x0002 CHARACTERISTIC-GAP_DEVICE_NAME-READ - 0x0d, 0x00, 0x02, 0x00, 0x02, 0x00, 0x03, 0x28, 0x02, 0x03, 0x00, 0x00, 0x2a, - // 0x0003 VALUE-GAP_DEVICE_NAME-READ-'HID Keyboard' - // READ_ANYBODY - 0x14, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0x2a, 0x48, 0x49, 0x44, 0x20, 0x4b, 0x65, 0x79, 0x62, 0x6f, 0x61, 0x72, 0x64, - // add Battery Service - // #import -- BEGIN - // Specification Type org.bluetooth.service.battery_service - // https://www.bluetooth.com/api/gatt/xmlfile?xmlFileName=org.bluetooth.service.battery_service.xml - // Battery Service 180F - - // 0x0004 PRIMARY_SERVICE-ORG_BLUETOOTH_SERVICE_BATTERY_SERVICE - 0x0a, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x28, 0x0f, 0x18, - // 0x0005 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BATTERY_LEVEL-DYNAMIC | READ | NOTIFY - 0x0d, 0x00, 0x02, 0x00, 0x05, 0x00, 0x03, 0x28, 0x12, 0x06, 0x00, 0x19, 0x2a, - // 0x0006 VALUE-ORG_BLUETOOTH_CHARACTERISTIC_BATTERY_LEVEL-DYNAMIC | READ | NOTIFY-'' - // READ_ANYBODY - 0x08, 0x00, 0x02, 0x01, 0x06, 0x00, 0x19, 0x2a, - // 0x0007 CLIENT_CHARACTERISTIC_CONFIGURATION - // READ_ANYBODY, WRITE_ANYBODY - 0x0a, 0x00, 0x0e, 0x01, 0x07, 0x00, 0x02, 0x29, 0x00, 0x00, - // #import -- END - // add Device ID Service - // #import -- BEGIN - // Specification Type org.bluetooth.service.device_information - // https://www.bluetooth.com/api/gatt/xmlfile?xmlFileName=org.bluetooth.service.device_information.xml - // Device Information 180A - - // 0x0008 PRIMARY_SERVICE-ORG_BLUETOOTH_SERVICE_DEVICE_INFORMATION - 0x0a, 0x00, 0x02, 0x00, 0x08, 0x00, 0x00, 0x28, 0x0a, 0x18, - // 0x0009 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_MANUFACTURER_NAME_STRING-DYNAMIC | READ - 0x0d, 0x00, 0x02, 0x00, 0x09, 0x00, 0x03, 0x28, 0x02, 0x0a, 0x00, 0x29, 0x2a, - // 0x000a VALUE-ORG_BLUETOOTH_CHARACTERISTIC_MANUFACTURER_NAME_STRING-DYNAMIC | READ-'' - // READ_ANYBODY - 0x08, 0x00, 0x02, 0x01, 0x0a, 0x00, 0x29, 0x2a, - // 0x000b CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_MODEL_NUMBER_STRING-DYNAMIC | READ - 0x0d, 0x00, 0x02, 0x00, 0x0b, 0x00, 0x03, 0x28, 0x02, 0x0c, 0x00, 0x24, 0x2a, - // 0x000c VALUE-ORG_BLUETOOTH_CHARACTERISTIC_MODEL_NUMBER_STRING-DYNAMIC | READ-'' - // READ_ANYBODY - 0x08, 0x00, 0x02, 0x01, 0x0c, 0x00, 0x24, 0x2a, - // 0x000d CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_SERIAL_NUMBER_STRING-DYNAMIC | READ - 0x0d, 0x00, 0x02, 0x00, 0x0d, 0x00, 0x03, 0x28, 0x02, 0x0e, 0x00, 0x25, 0x2a, - // 0x000e VALUE-ORG_BLUETOOTH_CHARACTERISTIC_SERIAL_NUMBER_STRING-DYNAMIC | READ-'' - // READ_ANYBODY - 0x08, 0x00, 0x02, 0x01, 0x0e, 0x00, 0x25, 0x2a, - // 0x000f CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_HARDWARE_REVISION_STRING-DYNAMIC | READ - 0x0d, 0x00, 0x02, 0x00, 0x0f, 0x00, 0x03, 0x28, 0x02, 0x10, 0x00, 0x27, 0x2a, - // 0x0010 VALUE-ORG_BLUETOOTH_CHARACTERISTIC_HARDWARE_REVISION_STRING-DYNAMIC | READ-'' - // READ_ANYBODY - 0x08, 0x00, 0x02, 0x01, 0x10, 0x00, 0x27, 0x2a, - // 0x0011 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_FIRMWARE_REVISION_STRING-DYNAMIC | READ - 0x0d, 0x00, 0x02, 0x00, 0x11, 0x00, 0x03, 0x28, 0x02, 0x12, 0x00, 0x26, 0x2a, - // 0x0012 VALUE-ORG_BLUETOOTH_CHARACTERISTIC_FIRMWARE_REVISION_STRING-DYNAMIC | READ-'' - // READ_ANYBODY - 0x08, 0x00, 0x02, 0x01, 0x12, 0x00, 0x26, 0x2a, - // 0x0013 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_SOFTWARE_REVISION_STRING-DYNAMIC | READ - 0x0d, 0x00, 0x02, 0x00, 0x13, 0x00, 0x03, 0x28, 0x02, 0x14, 0x00, 0x28, 0x2a, - // 0x0014 VALUE-ORG_BLUETOOTH_CHARACTERISTIC_SOFTWARE_REVISION_STRING-DYNAMIC | READ-'' - // READ_ANYBODY - 0x08, 0x00, 0x02, 0x01, 0x14, 0x00, 0x28, 0x2a, - // 0x0015 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_SYSTEM_ID-DYNAMIC | READ - 0x0d, 0x00, 0x02, 0x00, 0x15, 0x00, 0x03, 0x28, 0x02, 0x16, 0x00, 0x23, 0x2a, - // 0x0016 VALUE-ORG_BLUETOOTH_CHARACTERISTIC_SYSTEM_ID-DYNAMIC | READ-'' - // READ_ANYBODY - 0x08, 0x00, 0x02, 0x01, 0x16, 0x00, 0x23, 0x2a, - // 0x0017 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_IEEE_11073_20601_REGULATORY_CERTIFICATION_DATA_LIST-DYNAMIC | READ - 0x0d, 0x00, 0x02, 0x00, 0x17, 0x00, 0x03, 0x28, 0x02, 0x18, 0x00, 0x2a, 0x2a, - // 0x0018 VALUE-ORG_BLUETOOTH_CHARACTERISTIC_IEEE_11073_20601_REGULATORY_CERTIFICATION_DATA_LIST-DYNAMIC | READ-'' - // READ_ANYBODY - 0x08, 0x00, 0x02, 0x01, 0x18, 0x00, 0x2a, 0x2a, - // 0x0019 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_PNP_ID-DYNAMIC | READ - 0x0d, 0x00, 0x02, 0x00, 0x19, 0x00, 0x03, 0x28, 0x02, 0x1a, 0x00, 0x50, 0x2a, - // 0x001a VALUE-ORG_BLUETOOTH_CHARACTERISTIC_PNP_ID-DYNAMIC | READ-'' - // READ_ANYBODY - 0x08, 0x00, 0x02, 0x01, 0x1a, 0x00, 0x50, 0x2a, - // #import -- END - // add HID Service - // #import -- BEGIN - // Specification Type org.bluetooth.service.human_interface_device - // https://www.bluetooth.com/api/gatt/xmlfile?xmlFileName=org.bluetooth.service.human_interface_device.xml - // Human Interface Device 1812 - - // 0x001b PRIMARY_SERVICE-ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE - 0x0a, 0x00, 0x02, 0x00, 0x1b, 0x00, 0x00, 0x28, 0x12, 0x18, - // 0x001c CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_PROTOCOL_MODE-DYNAMIC | READ | WRITE_WITHOUT_RESPONSE - 0x0d, 0x00, 0x02, 0x00, 0x1c, 0x00, 0x03, 0x28, 0x06, 0x1d, 0x00, 0x4e, 0x2a, - // 0x001d VALUE-ORG_BLUETOOTH_CHARACTERISTIC_PROTOCOL_MODE-DYNAMIC | READ | WRITE_WITHOUT_RESPONSE-'' - // READ_ANYBODY, WRITE_ANYBODY - 0x08, 0x00, 0x06, 0x01, 0x1d, 0x00, 0x4e, 0x2a, - // 0x001e CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT-DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16 - 0x0d, 0x00, 0x02, 0x00, 0x1e, 0x00, 0x03, 0x28, 0x1a, 0x1f, 0x00, 0x4d, 0x2a, - // 0x001f VALUE-ORG_BLUETOOTH_CHARACTERISTIC_REPORT-DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16-'' - // READ_ENCRYPTED, WRITE_ENCRYPTED, ENCRYPTION_KEY_SIZE=16 - 0x08, 0x00, 0x0b, 0xf5, 0x1f, 0x00, 0x4d, 0x2a, - // 0x0020 CLIENT_CHARACTERISTIC_CONFIGURATION - // READ_ANYBODY, WRITE_ENCRYPTED, ENCRYPTION_KEY_SIZE=16 - 0x0a, 0x00, 0x0f, 0xf1, 0x20, 0x00, 0x02, 0x29, 0x00, 0x00, - // fixed report id = 1, type = Input (1) - // 0x0021 REPORT_REFERENCE-READ-1-1 - 0x0a, 0x00, 0x02, 0x00, 0x21, 0x00, 0x08, 0x29, 0x1, 0x1, - // 0x0022 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT-DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16 - 0x0d, 0x00, 0x02, 0x00, 0x22, 0x00, 0x03, 0x28, 0x1a, 0x23, 0x00, 0x4d, 0x2a, - // 0x0023 VALUE-ORG_BLUETOOTH_CHARACTERISTIC_REPORT-DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16-'' - // READ_ENCRYPTED, WRITE_ENCRYPTED, ENCRYPTION_KEY_SIZE=16 - 0x08, 0x00, 0x0b, 0xf5, 0x23, 0x00, 0x4d, 0x2a, - // 0x0024 CLIENT_CHARACTERISTIC_CONFIGURATION - // READ_ANYBODY, WRITE_ENCRYPTED, ENCRYPTION_KEY_SIZE=16 - 0x0a, 0x00, 0x0f, 0xf1, 0x24, 0x00, 0x02, 0x29, 0x00, 0x00, - // fixed report id = 2, type = Output (2) - // 0x0025 REPORT_REFERENCE-READ-2-2 - 0x0a, 0x00, 0x02, 0x00, 0x25, 0x00, 0x08, 0x29, 0x2, 0x2, - // 0x0026 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT-DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16 - 0x0d, 0x00, 0x02, 0x00, 0x26, 0x00, 0x03, 0x28, 0x1a, 0x27, 0x00, 0x4d, 0x2a, - // 0x0027 VALUE-ORG_BLUETOOTH_CHARACTERISTIC_REPORT-DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16-'' - // READ_ENCRYPTED, WRITE_ENCRYPTED, ENCRYPTION_KEY_SIZE=16 - 0x08, 0x00, 0x0b, 0xf5, 0x27, 0x00, 0x4d, 0x2a, - // 0x0028 CLIENT_CHARACTERISTIC_CONFIGURATION - // READ_ANYBODY, WRITE_ENCRYPTED, ENCRYPTION_KEY_SIZE=16 - 0x0a, 0x00, 0x0f, 0xf1, 0x28, 0x00, 0x02, 0x29, 0x00, 0x00, - // fixed report id = 3, type = Feature (3) - // 0x0029 REPORT_REFERENCE-READ-3-3 - 0x0a, 0x00, 0x02, 0x00, 0x29, 0x00, 0x08, 0x29, 0x3, 0x3, - // 0x002a CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP-DYNAMIC | READ - 0x0d, 0x00, 0x02, 0x00, 0x2a, 0x00, 0x03, 0x28, 0x02, 0x2b, 0x00, 0x4b, 0x2a, - // 0x002b VALUE-ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP-DYNAMIC | READ-'' - // READ_ANYBODY - 0x08, 0x00, 0x02, 0x01, 0x2b, 0x00, 0x4b, 0x2a, - // 0x002c CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT-DYNAMIC | READ | WRITE | NOTIFY - 0x0d, 0x00, 0x02, 0x00, 0x2c, 0x00, 0x03, 0x28, 0x1a, 0x2d, 0x00, 0x22, 0x2a, - // 0x002d VALUE-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT-DYNAMIC | READ | WRITE | NOTIFY-'' - // READ_ANYBODY, WRITE_ANYBODY - 0x08, 0x00, 0x0a, 0x01, 0x2d, 0x00, 0x22, 0x2a, - // 0x002e CLIENT_CHARACTERISTIC_CONFIGURATION - // READ_ANYBODY, WRITE_ANYBODY - 0x0a, 0x00, 0x0e, 0x01, 0x2e, 0x00, 0x02, 0x29, 0x00, 0x00, - // 0x002f CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT-DYNAMIC | READ | WRITE | WRITE_WITHOUT_RESPONSE - 0x0d, 0x00, 0x02, 0x00, 0x2f, 0x00, 0x03, 0x28, 0x0e, 0x30, 0x00, 0x32, 0x2a, - // 0x0030 VALUE-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT-DYNAMIC | READ | WRITE | WRITE_WITHOUT_RESPONSE-'' - // READ_ANYBODY, WRITE_ANYBODY - 0x08, 0x00, 0x0e, 0x01, 0x30, 0x00, 0x32, 0x2a, - // 0x0031 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT-DYNAMIC | READ | WRITE | NOTIFY - 0x0d, 0x00, 0x02, 0x00, 0x31, 0x00, 0x03, 0x28, 0x1a, 0x32, 0x00, 0x33, 0x2a, - // 0x0032 VALUE-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT-DYNAMIC | READ | WRITE | NOTIFY-'' - // READ_ANYBODY, WRITE_ANYBODY - 0x08, 0x00, 0x0a, 0x01, 0x32, 0x00, 0x33, 0x2a, - // 0x0033 CLIENT_CHARACTERISTIC_CONFIGURATION - // READ_ANYBODY, WRITE_ANYBODY - 0x0a, 0x00, 0x0e, 0x01, 0x33, 0x00, 0x02, 0x29, 0x00, 0x00, - // bcdHID = 0x101 (v1.0.1), bCountryCode 0, remote wakeable = 0 | normally connectable 2 - // 0x0034 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION-READ - 0x0d, 0x00, 0x02, 0x00, 0x34, 0x00, 0x03, 0x28, 0x02, 0x35, 0x00, 0x4a, 0x2a, - // 0x0035 VALUE-ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION-READ-'01 01 00 02' - // READ_ANYBODY - 0x0c, 0x00, 0x02, 0x00, 0x35, 0x00, 0x4a, 0x2a, 0x01, 0x01, 0x00, 0x02, - // 0x0036 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT-DYNAMIC | WRITE_WITHOUT_RESPONSE - 0x0d, 0x00, 0x02, 0x00, 0x36, 0x00, 0x03, 0x28, 0x04, 0x37, 0x00, 0x4c, 0x2a, - // 0x0037 VALUE-ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT-DYNAMIC | WRITE_WITHOUT_RESPONSE-'' - // WRITE_ANYBODY - 0x08, 0x00, 0x04, 0x01, 0x37, 0x00, 0x4c, 0x2a, - // #import -- END - - // 0x0038 PRIMARY_SERVICE-GATT_SERVICE - 0x0a, 0x00, 0x02, 0x00, 0x38, 0x00, 0x00, 0x28, 0x01, 0x18, - // 0x0039 CHARACTERISTIC-GATT_SERVICE_CHANGED-READ - 0x0d, 0x00, 0x02, 0x00, 0x39, 0x00, 0x03, 0x28, 0x02, 0x3a, 0x00, 0x05, 0x2a, - // 0x003a VALUE-GATT_SERVICE_CHANGED-READ-'' - // READ_ANYBODY - 0x08, 0x00, 0x02, 0x00, 0x3a, 0x00, 0x05, 0x2a, - - // END - 0x00, 0x00, -}; // total size 338 bytes - - -// -// list service handle ranges -// -#define ATT_SERVICE_GAP_SERVICE_START_HANDLE 0x0001 -#define ATT_SERVICE_GAP_SERVICE_END_HANDLE 0x0003 -#define ATT_SERVICE_ORG_BLUETOOTH_SERVICE_BATTERY_SERVICE_START_HANDLE 0x0004 -#define ATT_SERVICE_ORG_BLUETOOTH_SERVICE_BATTERY_SERVICE_END_HANDLE 0x0007 -#define ATT_SERVICE_ORG_BLUETOOTH_SERVICE_DEVICE_INFORMATION_START_HANDLE 0x0008 -#define ATT_SERVICE_ORG_BLUETOOTH_SERVICE_DEVICE_INFORMATION_END_HANDLE 0x001a -#define ATT_SERVICE_ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE_START_HANDLE 0x001b -#define ATT_SERVICE_ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE_END_HANDLE 0x0037 -#define ATT_SERVICE_GATT_SERVICE_START_HANDLE 0x0038 -#define ATT_SERVICE_GATT_SERVICE_END_HANDLE 0x003a - -// -// list mapping between characteristics and handles -// -#define ATT_CHARACTERISTIC_GAP_DEVICE_NAME_01_VALUE_HANDLE 0x0003 -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_BATTERY_LEVEL_01_VALUE_HANDLE 0x0006 -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_BATTERY_LEVEL_01_CLIENT_CONFIGURATION_HANDLE 0x0007 -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_MANUFACTURER_NAME_STRING_01_VALUE_HANDLE 0x000a -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_MODEL_NUMBER_STRING_01_VALUE_HANDLE 0x000c -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_SERIAL_NUMBER_STRING_01_VALUE_HANDLE 0x000e -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_HARDWARE_REVISION_STRING_01_VALUE_HANDLE 0x0010 -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_FIRMWARE_REVISION_STRING_01_VALUE_HANDLE 0x0012 -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_SOFTWARE_REVISION_STRING_01_VALUE_HANDLE 0x0014 -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_SYSTEM_ID_01_VALUE_HANDLE 0x0016 -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_IEEE_11073_20601_REGULATORY_CERTIFICATION_DATA_LIST_01_VALUE_HANDLE 0x0018 -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_PNP_ID_01_VALUE_HANDLE 0x001a -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_PROTOCOL_MODE_01_VALUE_HANDLE 0x001d -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_REPORT_01_VALUE_HANDLE 0x001f -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_REPORT_01_CLIENT_CONFIGURATION_HANDLE 0x0020 -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_REPORT_02_VALUE_HANDLE 0x0023 -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_REPORT_02_CLIENT_CONFIGURATION_HANDLE 0x0024 -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_REPORT_03_VALUE_HANDLE 0x0027 -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_REPORT_03_CLIENT_CONFIGURATION_HANDLE 0x0028 -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP_01_VALUE_HANDLE 0x002b -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT_01_VALUE_HANDLE 0x002d -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT_01_CLIENT_CONFIGURATION_HANDLE 0x002e -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT_01_VALUE_HANDLE 0x0030 -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT_01_VALUE_HANDLE 0x0032 -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT_01_CLIENT_CONFIGURATION_HANDLE 0x0033 -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION_01_VALUE_HANDLE 0x0035 -#define ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT_01_VALUE_HANDLE 0x0037 -#define ATT_CHARACTERISTIC_GATT_SERVICE_CHANGED_01_VALUE_HANDLE 0x003a