btstack_defines: add Cycling Power service events

This commit is contained in:
Milanka Ringwald 2018-11-01 09:30:01 +01:00
parent 0b8dd114aa
commit 9fe70df8e9
14 changed files with 3697 additions and 235 deletions

View File

@ -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,

File diff suppressed because it is too large Load Diff

View File

@ -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 <stdint.h>
#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

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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 <stdint.h>
#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

View File

@ -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

View File

@ -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 */

858
test/pts/csc_client_test.c Normal file
View File

@ -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 <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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<data_size;i+=2){
if (little_endian_read_16(data, i) == uuid16) {
found = 1;
break;
}
}
break;
default:
break;
}
}
return found;
}
#define INDICATION_TIMEOUT_MS 30000
static void csc_client_indication_timeout_handler(btstack_timer_source_t * timer){
UNUSED(timer);
// avdtp_connection_t * connection = (avdtp_connection_t *) btstack_run_loop_get_timer_context(timer);
printf("Indication not received\n");
}
static void csc_client_indication_timer_start(void){
btstack_run_loop_remove_timer(&indication_timer);
btstack_run_loop_set_timer_handler(&indication_timer, csc_client_indication_timeout_handler);
// btstack_run_loop_set_timer_context(&indication_timer, connection);
btstack_run_loop_set_timer(&indication_timer, INDICATION_TIMEOUT_MS);
btstack_run_loop_add_timer(&indication_timer);
}
static void csc_client_indication_timer_stop(void){
btstack_run_loop_remove_timer(&indication_timer);
}
static float csc_client_calculate_instantaneous_speed_km_per_h(uint32_t * wheel_rotation, uint16_t * time){
if (time[1] == time[0]) return 0;
int16_t delta_time = time[1] - time[0];
int32_t delta_wheel_rotation = wheel_rotation[1] - wheel_rotation[0];
return delta_wheel_rotation * wheel_circumference_cm / (float)delta_time * 1024 * 3600 / 100000;
}
static float csc_client_calculate_instantaneous_cadence_rpm(uint16_t * crank_revolution, uint16_t * time){
if (time[1] == time[0]) return 0;
int16_t delta_time = time[1] - time[0];
int16_t delta_crank_revolution = crank_revolution[1] - crank_revolution[0];
return delta_crank_revolution / (float)delta_time * 60 * 1024;
}
static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
UNUSED(packet_type);
UNUSED(channel);
UNUSED(size);
switch(state){
case TC_W4_SERVICE_RESULT:
switch(hci_event_packet_get_type(packet)){
case GATT_EVENT_SERVICE_QUERY_RESULT:
// store service (we expect only one)
gatt_event_service_query_result_get_service(packet, &service);
printf("GATT Service:\n start handle 0x%02x, end handle 0x%02x\n", service.start_group_handle, service.end_group_handle);
break;
case GATT_EVENT_QUERY_COMPLETE:
if (packet[4] != 0){
printf("SERVICE_QUERY_RESULT - Error status %x.\n", packet[4]);
gap_disconnect(connection_handle);
break;
}
break;
default:
break;
}
break;
case TC_W4_CHARACTERISTIC_RESULT:
switch(hci_event_packet_get_type(packet)){
case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
gatt_event_characteristic_query_result_get_characteristic(packet, &characteristic);
printf("GATT characteristic:\n attribute handle 0x%02x, properties 0x%02x, handle 0x%02x, uuid 0x%02x\n",
characteristic.start_handle, characteristic.properties, characteristic.value_handle, characteristic.uuid16);
switch(characteristic.uuid16){
case ORG_BLUETOOTH_CHARACTERISTIC_CSC_MEASUREMENT:
measurement_characteristic = characteristic;
break;
case ORG_BLUETOOTH_CHARACTERISTIC_SC_CONTROL_POINT:
control_point_characteristic = characteristic;
break;
default:
break;
}
break;
case GATT_EVENT_QUERY_COMPLETE:
if (packet[4] != 0){
printf("CHARACTERISTIC_QUERY_RESULT - Error status %x.\n", packet[4]);
gap_disconnect(connection_handle);
break;
}
break;
default:
break;
}
break;
case TC_W4_CHARACTERISTIC_DESCRIPTOR_RESULT:
switch(hci_event_packet_get_type(packet)){
case GATT_EVENT_ALL_CHARACTERISTIC_DESCRIPTORS_QUERY_RESULT:
gatt_event_all_characteristic_descriptors_query_result_get_characteristic_descriptor(packet, &descriptor);
printf("GATT descriptors:\n handle 0x%02x, uuid 0x%02x\n",
descriptor.handle, descriptor.uuid16);
break;
case GATT_EVENT_QUERY_COMPLETE:
if (packet[4] != 0){
printf("CHARACTERISTIC_QUERY_RESULT - Error status %x.\n", packet[4]);
gap_disconnect(connection_handle);
break;
}
break;
default:
break;
}
break;
case TC_W4_SENSOR_LOCATION:
switch(hci_event_packet_get_type(packet)){
case GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT:{
int value_len = gatt_event_characteristic_value_query_result_get_value_length(packet);
const uint8_t * value = gatt_event_characteristic_value_query_result_get_value(packet);
printf_hexdump(value, value_len);
switch (characteristic.uuid16){
case ORG_BLUETOOTH_CHARACTERISTIC_HEART_RATE_MEASUREMENT:{
break;
}
case ORG_BLUETOOTH_CHARACTERISTIC_SENSOR_LOCATION:
// https://www.bluetooth.com/specifications/gatt/services
// https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.body_sensor_location.xml
printf("Body sensor location 0x%02x, name %s\n", value[0], sensor_loc2str(value[0]));
break;
case ORG_BLUETOOTH_CHARACTERISTIC_IEEE_11073_20601_REGULATORY_CERTIFICATION_DATA_LIST:
break;
case ORG_BLUETOOTH_CHARACTERISTIC_SERIAL_NUMBER_STRING:
break;
case ORG_BLUETOOTH_CHARACTERISTIC_MANUFACTURER_NAME_STRING:
break;
default:
break;
}
state = TC_CONNECTED;
break;
}
default:
break;
}
break;
case TC_W4_ENABLE_NOTIFICATIONS_COMPLETE:
switch(hci_event_packet_get_type(packet)){
case GATT_EVENT_QUERY_COMPLETE:
printf("Notifications enabled, status %02x\n", gatt_event_query_complete_get_status(packet));
state = TC_CONNECTED;
break;
default:
break;
}
break;
case TC_W4_ENABLE_INDICATIONS_COMPLETE:
switch(hci_event_packet_get_type(packet)){
case GATT_EVENT_QUERY_COMPLETE:
printf("Indications enabled, status %02x\n", gatt_event_query_complete_get_status(packet));
state = TC_CONNECTED;
break;
default:
break;
}
break;
case TC_W4_WRITE_CHARACTERISTIC:
switch(hci_event_packet_get_type(packet)){
case GATT_EVENT_QUERY_COMPLETE:
printf("Command done, status %02x\n", gatt_event_query_complete_get_status(packet));
if (gatt_event_query_complete_get_status(packet) == ERROR_CODE_SUCCESS){
csc_client_indication_timer_start();
}
state = TC_CONNECTED;
break;
default:
break;
}
break;
case TC_CONNECTED:
switch(hci_event_packet_get_type(packet)){
case GATT_EVENT_INDICATION:{
uint16_t value_length = gatt_event_notification_get_value_length(packet);
const uint8_t * value = gatt_event_notification_get_value(packet);
printf("Indication value len %d, value: ", value_length);
printf_hexdump(value, value_length);
csc_client_indication_timer_stop();
break;
}
case GATT_EVENT_NOTIFICATION:{
// uint16_t value_handle = gatt_event_notification_get_value_handle(packet);
// uint16_t value_length = gatt_event_notification_get_value_length(packet);
const uint8_t * value = gatt_event_notification_get_value(packet);
// printf("Notification handle 0x%04x, value len %d, value: ", value_handle, value_length);
// printf_hexdump(value, value_length);
int pos = 0;
uint8_t wheel_revolution_data_supported = 0;
uint8_t crank_revolution_data_supported = 0;
switch (characteristic.uuid16){
case ORG_BLUETOOTH_CHARACTERISTIC_CSC_MEASUREMENT:{
uint8_t flags = value[pos++];
printf("flags 0x%02x\n", flags);
if (flags & (1 << CSC_FLAG_WHEEL_REVOLUTION_DATA_SUPPORTED)){
wheel_revolution_data_supported = 1;
}
if (flags & (1 << CSC_FLAG_CRANK_REVOLUTION_DATA_SUPPORTED)){
crank_revolution_data_supported = 1;
}
if (wheel_revolution_data_supported){
cumulative_wheel_revolutions[wm_index] = little_endian_read_32(value, pos);
pos += 4;
last_wheel_event_time[wm_index] = little_endian_read_16(value, pos);
pos += 2;
printf("wheel_revolution_data (0x%04x) = %d, time (0x%02x) = %f s\n", cumulative_wheel_revolutions[wm_index], cumulative_wheel_revolutions[wm_index],
last_wheel_event_time[wm_index], last_wheel_event_time[wm_index]/1024.0);
if (wm_index > 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(&notification_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(&notification_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(&notification_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 */

153
test/pts/csc_server_test.c Normal file
View File

@ -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 <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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 */

View File

@ -0,0 +1,5 @@
PRIMARY_SERVICE, GAP_SERVICE
CHARACTERISTIC, GAP_DEVICE_NAME, READ, "CSC Server"
// add Cycling Speed and Cadence Service
#import <cycling_speed_and_cadence_service.gatt>

View File

@ -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 <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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 */

View File

@ -0,0 +1,5 @@
PRIMARY_SERVICE, GAP_SERVICE
CHARACTERISTIC, GAP_DEVICE_NAME, READ, "Cycling Power Server"
// add Cycling Power Service
#import <cycling_power_service.gatt>

View File

@ -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 <stdint.h>
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 <battery_service.gatt> -- 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 <battery_service.gatt> -- END
// add Device ID Service
// #import <device_information_service.gatt> -- 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 <device_information_service.gatt> -- END
// add HID Service
// #import <hids.gatt> -- 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 <hids.gatt> -- 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