19fe70df8SMilanka Ringwald /*
29fe70df8SMilanka Ringwald * Copyright (C) 2014 BlueKitchen GmbH
39fe70df8SMilanka Ringwald *
49fe70df8SMilanka Ringwald * Redistribution and use in source and binary forms, with or without
59fe70df8SMilanka Ringwald * modification, are permitted provided that the following conditions
69fe70df8SMilanka Ringwald * are met:
79fe70df8SMilanka Ringwald *
89fe70df8SMilanka Ringwald * 1. Redistributions of source code must retain the above copyright
99fe70df8SMilanka Ringwald * notice, this list of conditions and the following disclaimer.
109fe70df8SMilanka Ringwald * 2. Redistributions in binary form must reproduce the above copyright
119fe70df8SMilanka Ringwald * notice, this list of conditions and the following disclaimer in the
129fe70df8SMilanka Ringwald * documentation and/or other materials provided with the distribution.
139fe70df8SMilanka Ringwald * 3. Neither the name of the copyright holders nor the names of
149fe70df8SMilanka Ringwald * contributors may be used to endorse or promote products derived
159fe70df8SMilanka Ringwald * from this software without specific prior written permission.
169fe70df8SMilanka Ringwald * 4. Any redistribution, use, or modification is done solely for
179fe70df8SMilanka Ringwald * personal benefit and not for any commercial purpose or for
189fe70df8SMilanka Ringwald * monetary gain.
199fe70df8SMilanka Ringwald *
209fe70df8SMilanka Ringwald * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
219fe70df8SMilanka Ringwald * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
229fe70df8SMilanka Ringwald * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
232fca4dadSMilanka Ringwald * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
242fca4dadSMilanka Ringwald * GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
259fe70df8SMilanka Ringwald * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
269fe70df8SMilanka Ringwald * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
279fe70df8SMilanka Ringwald * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
289fe70df8SMilanka Ringwald * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
299fe70df8SMilanka Ringwald * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
309fe70df8SMilanka Ringwald * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
319fe70df8SMilanka Ringwald * SUCH DAMAGE.
329fe70df8SMilanka Ringwald *
339fe70df8SMilanka Ringwald * Please inquire about commercial licensing options at
349fe70df8SMilanka Ringwald * [email protected]
359fe70df8SMilanka Ringwald *
369fe70df8SMilanka Ringwald */
379fe70df8SMilanka Ringwald
38e501bae0SMatthias Ringwald #define BTSTACK_FILE__ "cycling_power_service_server.c"
399fe70df8SMilanka Ringwald
409fe70df8SMilanka Ringwald
419fe70df8SMilanka Ringwald #include "bluetooth.h"
429fe70df8SMilanka Ringwald #include "btstack_defines.h"
439fe70df8SMilanka Ringwald #include "bluetooth_data_types.h"
449fe70df8SMilanka Ringwald #include "btstack_event.h"
459fe70df8SMilanka Ringwald #include "ble/att_db.h"
469fe70df8SMilanka Ringwald #include "ble/att_server.h"
479fe70df8SMilanka Ringwald #include "btstack_util.h"
489fe70df8SMilanka Ringwald #include "bluetooth_gatt.h"
499fe70df8SMilanka Ringwald #include "btstack_debug.h"
509fe70df8SMilanka Ringwald #include "l2cap.h"
519fe70df8SMilanka Ringwald #include "hci.h"
529fe70df8SMilanka Ringwald
539fe70df8SMilanka Ringwald #include "ble/gatt-service/cycling_power_service_server.h"
549fe70df8SMilanka Ringwald
559fe70df8SMilanka Ringwald #define CYCLING_POWER_MAX_BROACAST_MSG_SIZE 31
569fe70df8SMilanka Ringwald #define CONTROL_POINT_PROCEDURE_TIMEOUT_MS 30
579fe70df8SMilanka Ringwald #define CYCLING_POWER_MEASUREMENT_FLAGS_CLEARED 0xFFFF
589fe70df8SMilanka Ringwald
599fe70df8SMilanka Ringwald typedef enum {
609fe70df8SMilanka Ringwald CP_MASK_BIT_PEDAL_POWER_BALANCE = 0,
619fe70df8SMilanka Ringwald CP_MASK_BIT_ACCUMULATED_TORQUE,
629fe70df8SMilanka Ringwald CP_MASK_BIT_WHEEL_REVOLUTION_DATA,
639fe70df8SMilanka Ringwald CP_MASK_BIT_CRANK_REVOLUTION_DATA,
649fe70df8SMilanka Ringwald CP_MASK_BIT_EXTREME_MAGNITUDES,
659fe70df8SMilanka Ringwald CP_MASK_BIT_EXTREME_ANGLES,
669fe70df8SMilanka Ringwald CP_MASK_BIT_TOP_DEAD_SPOT_ANGLE,
679fe70df8SMilanka Ringwald CP_MASK_BIT_BOTTOM_DEAD_SPOT_ANGLE,
689fe70df8SMilanka Ringwald CP_MASK_BIT_ACCUMULATED_ENERGY,
699fe70df8SMilanka Ringwald CP_MASK_BIT_RESERVED
709fe70df8SMilanka Ringwald } cycling_power_mask_bit_t;
719fe70df8SMilanka Ringwald
729fe70df8SMilanka Ringwald typedef enum {
739fe70df8SMilanka Ringwald CP_OPCODE_IDLE = 0,
749fe70df8SMilanka Ringwald CP_OPCODE_SET_CUMULATIVE_VALUE,
759fe70df8SMilanka Ringwald CP_OPCODE_UPDATE_SENSOR_LOCATION,
769fe70df8SMilanka Ringwald CP_OPCODE_REQUEST_SUPPORTED_SENSOR_LOCATIONS,
779fe70df8SMilanka Ringwald CP_OPCODE_SET_CRANK_LENGTH,
789fe70df8SMilanka Ringwald CP_OPCODE_REQUEST_CRANK_LENGTH,
799fe70df8SMilanka Ringwald CP_OPCODE_SET_CHAIN_LENGTH,
809fe70df8SMilanka Ringwald CP_OPCODE_REQUEST_CHAIN_LENGTH,
819fe70df8SMilanka Ringwald CP_OPCODE_SET_CHAIN_WEIGHT,
829fe70df8SMilanka Ringwald CP_OPCODE_REQUEST_CHAIN_WEIGHT,
839fe70df8SMilanka Ringwald CP_OPCODE_SET_SPAN_LENGTH,
849fe70df8SMilanka Ringwald CP_OPCODE_REQUEST_SPAN_LENGTH,
859fe70df8SMilanka Ringwald CP_OPCODE_START_OFFSET_COMPENSATION,
869fe70df8SMilanka Ringwald CP_OPCODE_MASK_CYCLING_POWER_MEASUREMENT_CHARACTERISTIC_CONTENT,
879fe70df8SMilanka Ringwald CP_OPCODE_REQUEST_SAMPLING_RATE,
889fe70df8SMilanka Ringwald CP_OPCODE_REQUEST_FACTORY_CALIBRATION_DATE,
899fe70df8SMilanka Ringwald CP_OPCODE_START_ENHANCED_OFFSET_COMPENSATION,
909fe70df8SMilanka Ringwald CP_OPCODE_RESPONSE_CODE = 32
919fe70df8SMilanka Ringwald } cycling_power_opcode_t;
929fe70df8SMilanka Ringwald
939fe70df8SMilanka Ringwald typedef enum {
949fe70df8SMilanka Ringwald CP_RESPONSE_VALUE_SUCCESS = 1,
959fe70df8SMilanka Ringwald CP_RESPONSE_VALUE_OP_CODE_NOT_SUPPORTED,
969fe70df8SMilanka Ringwald CP_RESPONSE_VALUE_INVALID_PARAMETER,
979fe70df8SMilanka Ringwald CP_RESPONSE_VALUE_OPERATION_FAILED,
989fe70df8SMilanka Ringwald CP_RESPONSE_VALUE_NOT_AVAILABLE,
999fe70df8SMilanka Ringwald CP_RESPONSE_VALUE_W4_VALUE_AVAILABLE
1009fe70df8SMilanka Ringwald } cycling_power_response_value_t;
1019fe70df8SMilanka Ringwald
1029fe70df8SMilanka Ringwald typedef enum {
1039fe70df8SMilanka Ringwald CP_CONNECTION_INTERVAL_STATUS_NONE = 0,
1049fe70df8SMilanka Ringwald CP_CONNECTION_INTERVAL_STATUS_RECEIVED,
1059fe70df8SMilanka Ringwald CP_CONNECTION_INTERVAL_STATUS_ACCEPTED,
1069fe70df8SMilanka Ringwald CP_CONNECTION_INTERVAL_STATUS_W4_L2CAP_RESPONSE,
1079fe70df8SMilanka Ringwald CP_CONNECTION_INTERVAL_STATUS_W4_UPDATE,
1089fe70df8SMilanka Ringwald CP_CONNECTION_INTERVAL_STATUS_REJECTED
1099fe70df8SMilanka Ringwald } cycling_power_con_interval_status_t;
1109fe70df8SMilanka Ringwald
1119fe70df8SMilanka Ringwald typedef struct {
1129fe70df8SMilanka Ringwald hci_con_handle_t con_handle;
1139fe70df8SMilanka Ringwald // GATT connection management
1149fe70df8SMilanka Ringwald uint16_t con_interval;
1159fe70df8SMilanka Ringwald uint16_t con_interval_min;
1169fe70df8SMilanka Ringwald uint16_t con_interval_max;
1179fe70df8SMilanka Ringwald cycling_power_con_interval_status_t con_interval_status;
1189fe70df8SMilanka Ringwald
1199fe70df8SMilanka Ringwald // Cycling Power Measurement
1209fe70df8SMilanka Ringwald uint16_t measurement_value_handle;
1218d927742SMilanka Ringwald int16_t instantaneous_power_W;
1229fe70df8SMilanka Ringwald
1239fe70df8SMilanka Ringwald cycling_power_pedal_power_balance_reference_t pedal_power_balance_reference;
1249fe70df8SMilanka Ringwald uint8_t pedal_power_balance_percentage; // percentage, resolution 1/2,
1259fe70df8SMilanka Ringwald // If the sensor provides the power balance referenced to the left pedal,
1269fe70df8SMilanka Ringwald // the power balance is calculated as [LeftPower/(LeftPower + RightPower)]*100 in units of percent
1279fe70df8SMilanka Ringwald
1289fe70df8SMilanka Ringwald cycling_power_torque_source_t torque_source;
1298d927742SMilanka Ringwald uint16_t accumulated_torque_Nm; // newton-meters, resolution 1/32,
1309fe70df8SMilanka Ringwald // The Accumulated Torque value may decrease
1319fe70df8SMilanka Ringwald // wheel revolution data:
1329fe70df8SMilanka Ringwald uint32_t cumulative_wheel_revolutions; // CANNOT roll over
1339fe70df8SMilanka Ringwald uint16_t last_wheel_event_time_s; // seconds, resolution 1/2048
1349fe70df8SMilanka Ringwald // crank revolution data:
1359fe70df8SMilanka Ringwald uint16_t cumulative_crank_revolutions;
1369fe70df8SMilanka Ringwald uint16_t last_crank_event_time_s; // seconds, resolution 1/1024
1379fe70df8SMilanka Ringwald // extreme force magnitudes
1388d927742SMilanka Ringwald int16_t maximum_force_magnitude_N;
1398d927742SMilanka Ringwald int16_t minimum_force_magnitude_N;
1408d927742SMilanka Ringwald int16_t maximum_torque_magnitude_Nm; // newton-meters, resolution 1/32
1418d927742SMilanka Ringwald int16_t minimum_torque_magnitude_Nm; // newton-meters, resolution 1/32
1429fe70df8SMilanka Ringwald // extreme angles
1438d927742SMilanka Ringwald uint16_t maximum_angle_degree; // 12bit, degrees
1448d927742SMilanka Ringwald uint16_t minimum_angle_degree; // 12bit, degrees, concatenated with previous into 3 octets
1459fe70df8SMilanka Ringwald // i.e. if the Maximum Angle is 0xABC and the Minimum Angle is 0x123, the transmitted value is 0x123ABC.
1468d927742SMilanka Ringwald uint16_t top_dead_spot_angle_degree;
1478d927742SMilanka Ringwald uint16_t bottom_dead_spot_angle_degree; // The Bottom Dead Spot Angle field represents the crank angle when the value of the Instantaneous Power value becomes negative.
1489fe70df8SMilanka Ringwald uint16_t accumulated_energy_kJ; // kilojoules; CANNOT roll over
1499fe70df8SMilanka Ringwald
1509fe70df8SMilanka Ringwald // uint8_t offset_compensation;
1519fe70df8SMilanka Ringwald
1529fe70df8SMilanka Ringwald // CP Measurement Notification (Client Characteristic Configuration)
1539fe70df8SMilanka Ringwald uint16_t measurement_client_configuration_descriptor_handle;
1549fe70df8SMilanka Ringwald uint16_t measurement_client_configuration_descriptor_notify;
1559fe70df8SMilanka Ringwald btstack_context_callback_registration_t measurement_notify_callback;
1569fe70df8SMilanka Ringwald
1579fe70df8SMilanka Ringwald // CP Measurement Broadcast (Server Characteristic Configuration)
1589fe70df8SMilanka Ringwald uint16_t measurement_server_configuration_descriptor_handle;
1599fe70df8SMilanka Ringwald uint16_t measurement_server_configuration_descriptor_broadcast;
1609fe70df8SMilanka Ringwald btstack_context_callback_registration_t measurement_broadcast_callback;
1619fe70df8SMilanka Ringwald
1629fe70df8SMilanka Ringwald // Cycling Power Feature
1639fe70df8SMilanka Ringwald uint16_t feature_value_handle;
1649fe70df8SMilanka Ringwald uint32_t feature_flags; // see cycling_power_feature_flag_t
1659fe70df8SMilanka Ringwald uint16_t masked_measurement_flags;
1669fe70df8SMilanka Ringwald uint16_t default_measurement_flags;
1679fe70df8SMilanka Ringwald
1689fe70df8SMilanka Ringwald // Sensor Location
1699fe70df8SMilanka Ringwald uint16_t sensor_location_value_handle;
1709fe70df8SMilanka Ringwald cycling_power_sensor_location_t sensor_location; // see cycling_power_sensor_location_t
1719fe70df8SMilanka Ringwald cycling_power_sensor_location_t * supported_sensor_locations;
1729fe70df8SMilanka Ringwald uint16_t num_supported_sensor_locations;
1739fe70df8SMilanka Ringwald uint16_t crank_length_mm; // resolution 1/2 mm
1749fe70df8SMilanka Ringwald uint16_t chain_length_mm; // resolution 1 mm
1759fe70df8SMilanka Ringwald uint16_t chain_weight_g; // resolution 1 gram
1769fe70df8SMilanka Ringwald uint16_t span_length_mm; // resolution 1 mm
1779fe70df8SMilanka Ringwald
1789fe70df8SMilanka Ringwald gatt_date_time_t factory_calibration_date;
1799fe70df8SMilanka Ringwald
1808d927742SMilanka Ringwald uint8_t sampling_rate_Hz; // resolution 1 Herz
1819fe70df8SMilanka Ringwald
182*52c964beSMatthias Ringwald uint16_t current_force_magnitude_N;
183*52c964beSMatthias Ringwald uint16_t current_torque_magnitude_Nm; // newton-meters, resolution 1/32
1849fe70df8SMilanka Ringwald uint16_t manufacturer_company_id;
1859fe70df8SMilanka Ringwald uint8_t num_manufacturer_specific_data;
1869fe70df8SMilanka Ringwald uint8_t * manufacturer_specific_data;
1879fe70df8SMilanka Ringwald
1889fe70df8SMilanka Ringwald // Cycling Power Vector
1899fe70df8SMilanka Ringwald uint16_t vector_value_handle;
1909fe70df8SMilanka Ringwald uint16_t vector_cumulative_crank_revolutions;
1919fe70df8SMilanka Ringwald uint16_t vector_last_crank_event_time_s; // seconds, resolution 1/1024
1928d927742SMilanka Ringwald uint16_t vector_first_crank_measurement_angle_degree;
1938d927742SMilanka Ringwald int16_t * vector_instantaneous_force_magnitude_N_array; // newton
194*52c964beSMatthias Ringwald uint16_t force_magnitude_count;
1958d927742SMilanka Ringwald int16_t * vector_instantaneous_torque_magnitude_Nm_array; // newton-meter, resolution 1/32
196*52c964beSMatthias Ringwald uint16_t torque_magnitude_count;
1979fe70df8SMilanka Ringwald cycling_power_instantaneous_measurement_direction_t vector_instantaneous_measurement_direction;
1989fe70df8SMilanka Ringwald
1999fe70df8SMilanka Ringwald // CP Vector Notification (Client Characteristic Configuration)
2009fe70df8SMilanka Ringwald uint16_t vector_client_configuration_descriptor_handle;
2019fe70df8SMilanka Ringwald uint16_t vector_client_configuration_descriptor_notify;
2029fe70df8SMilanka Ringwald btstack_context_callback_registration_t vector_notify_callback;
2039fe70df8SMilanka Ringwald
2049fe70df8SMilanka Ringwald // CP Control Point
2059fe70df8SMilanka Ringwald uint16_t control_point_value_handle;
2069fe70df8SMilanka Ringwald // CP Control Point Indication (Client Characteristic Configuration)
2079fe70df8SMilanka Ringwald uint16_t control_point_client_configuration_descriptor_handle;
2089fe70df8SMilanka Ringwald uint16_t control_point_client_configuration_descriptor_indicate;
2099fe70df8SMilanka Ringwald btstack_context_callback_registration_t control_point_indicate_callback;
2109fe70df8SMilanka Ringwald
2119fe70df8SMilanka Ringwald cycling_power_opcode_t request_opcode;
2129fe70df8SMilanka Ringwald cycling_power_response_value_t response_value;
2139fe70df8SMilanka Ringwald
2149fe70df8SMilanka Ringwald btstack_packet_handler_t calibration_callback;
2159fe70df8SMilanka Ringwald uint8_t w4_indication_complete;
2169fe70df8SMilanka Ringwald } cycling_power_t;
2179fe70df8SMilanka Ringwald
2189fe70df8SMilanka Ringwald static att_service_handler_t cycling_power_service;
2199fe70df8SMilanka Ringwald static cycling_power_t cycling_power;
2209fe70df8SMilanka Ringwald static btstack_packet_callback_registration_t hci_event_callback_registration;
2210b81d2a5SMatthias Ringwald static btstack_packet_callback_registration_t l2cap_event_callback_registration;
2229fe70df8SMilanka Ringwald
cycling_power_service_read_callback(hci_con_handle_t con_handle,uint16_t attribute_handle,uint16_t offset,uint8_t * buffer,uint16_t buffer_size)2239fe70df8SMilanka Ringwald static uint16_t cycling_power_service_read_callback(hci_con_handle_t con_handle, uint16_t attribute_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size){
2249fe70df8SMilanka Ringwald UNUSED(con_handle);
2259fe70df8SMilanka Ringwald UNUSED(attribute_handle);
2269fe70df8SMilanka Ringwald UNUSED(offset);
2279fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
2289fe70df8SMilanka Ringwald
2299fe70df8SMilanka Ringwald if (attribute_handle == instance->measurement_client_configuration_descriptor_handle){
2304ea43905SMatthias Ringwald if (buffer && (buffer_size >= 2u)){
2319fe70df8SMilanka Ringwald little_endian_store_16(buffer, 0, instance->measurement_client_configuration_descriptor_notify);
2329fe70df8SMilanka Ringwald }
2339fe70df8SMilanka Ringwald return 2;
2349fe70df8SMilanka Ringwald }
2359fe70df8SMilanka Ringwald
2369fe70df8SMilanka Ringwald if (attribute_handle == instance->measurement_server_configuration_descriptor_handle){
2374ea43905SMatthias Ringwald if (buffer && (buffer_size >= 2u)){
2389fe70df8SMilanka Ringwald little_endian_store_16(buffer, 0, instance->measurement_server_configuration_descriptor_broadcast);
2399fe70df8SMilanka Ringwald }
2409fe70df8SMilanka Ringwald return 2;
2419fe70df8SMilanka Ringwald }
2429fe70df8SMilanka Ringwald
2439fe70df8SMilanka Ringwald if (attribute_handle == instance->vector_client_configuration_descriptor_handle){
2444ea43905SMatthias Ringwald if (buffer && (buffer_size >= 2u)){
2459fe70df8SMilanka Ringwald little_endian_store_16(buffer, 0, instance->vector_client_configuration_descriptor_notify);
2469fe70df8SMilanka Ringwald }
2479fe70df8SMilanka Ringwald return 2;
2489fe70df8SMilanka Ringwald }
2499fe70df8SMilanka Ringwald
2509fe70df8SMilanka Ringwald if (attribute_handle == instance->control_point_client_configuration_descriptor_handle){
2514ea43905SMatthias Ringwald if (buffer && (buffer_size >= 2u)){
2529fe70df8SMilanka Ringwald little_endian_store_16(buffer, 0, instance->control_point_client_configuration_descriptor_indicate);
2539fe70df8SMilanka Ringwald }
2549fe70df8SMilanka Ringwald return 2;
2559fe70df8SMilanka Ringwald }
2569fe70df8SMilanka Ringwald
2579fe70df8SMilanka Ringwald if (attribute_handle == instance->feature_value_handle){
2584ea43905SMatthias Ringwald if (buffer && (buffer_size >= 4u)){
2599fe70df8SMilanka Ringwald little_endian_store_32(buffer, 0, instance->feature_flags);
2609fe70df8SMilanka Ringwald }
2619fe70df8SMilanka Ringwald return 4;
2629fe70df8SMilanka Ringwald }
2639fe70df8SMilanka Ringwald
2649fe70df8SMilanka Ringwald if (attribute_handle == instance->sensor_location_value_handle){
2654ea43905SMatthias Ringwald if (buffer && (buffer_size >= 1u)){
2669fe70df8SMilanka Ringwald buffer[0] = instance->sensor_location;
2679fe70df8SMilanka Ringwald }
2689fe70df8SMilanka Ringwald return 1;
2699fe70df8SMilanka Ringwald }
2709fe70df8SMilanka Ringwald return 0;
2719fe70df8SMilanka Ringwald }
2729fe70df8SMilanka Ringwald
has_feature(cycling_power_feature_flag_t feature)2739fe70df8SMilanka Ringwald static int has_feature(cycling_power_feature_flag_t feature){
2749fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
2754ea43905SMatthias Ringwald return (instance->feature_flags & (1u << feature)) != 0u;
2769fe70df8SMilanka Ringwald }
2779fe70df8SMilanka Ringwald
cycling_power_vector_instantaneous_measurement_direction(void)2789fe70df8SMilanka Ringwald static int cycling_power_vector_instantaneous_measurement_direction(void){
2799fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
2809fe70df8SMilanka Ringwald return instance->vector_instantaneous_measurement_direction;
2819fe70df8SMilanka Ringwald }
2829fe70df8SMilanka Ringwald
cycling_power_service_default_measurement_flags(void)2839fe70df8SMilanka Ringwald static uint16_t cycling_power_service_default_measurement_flags(void){
2849fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
2859fe70df8SMilanka Ringwald uint16_t measurement_flags = 0;
2869fe70df8SMilanka Ringwald uint8_t flag[] = {
2871a406ba4SMatthias Ringwald (uint8_t) has_feature(CP_FEATURE_FLAG_PEDAL_POWER_BALANCE_SUPPORTED),
2881a406ba4SMatthias Ringwald (uint8_t) has_feature(CP_FEATURE_FLAG_PEDAL_POWER_BALANCE_SUPPORTED) && instance->pedal_power_balance_reference,
2891a406ba4SMatthias Ringwald (uint8_t) has_feature(CP_FEATURE_FLAG_ACCUMULATED_TORQUE_SUPPORTED),
2901a406ba4SMatthias Ringwald (uint8_t) has_feature(CP_FEATURE_FLAG_ACCUMULATED_TORQUE_SUPPORTED) && instance->torque_source,
2911a406ba4SMatthias Ringwald (uint8_t) has_feature(CP_FEATURE_FLAG_WHEEL_REVOLUTION_DATA_SUPPORTED),
2921a406ba4SMatthias Ringwald (uint8_t) has_feature(CP_FEATURE_FLAG_CRANK_REVOLUTION_DATA_SUPPORTED),
2931a406ba4SMatthias Ringwald (uint8_t) has_feature(CP_FEATURE_FLAG_EXTREME_MAGNITUDES_SUPPORTED) && (has_feature(CP_FEATURE_FLAG_SENSOR_MEASUREMENT_CONTEXT) == CP_SENSOR_MEASUREMENT_CONTEXT_FORCE),
2941a406ba4SMatthias Ringwald (uint8_t) has_feature(CP_FEATURE_FLAG_EXTREME_MAGNITUDES_SUPPORTED) && (has_feature(CP_FEATURE_FLAG_SENSOR_MEASUREMENT_CONTEXT) == CP_SENSOR_MEASUREMENT_CONTEXT_TORQUE),
2951a406ba4SMatthias Ringwald (uint8_t) has_feature(CP_FEATURE_FLAG_EXTREME_ANGLES_SUPPORTED),
2961a406ba4SMatthias Ringwald (uint8_t) has_feature(CP_FEATURE_FLAG_TOP_AND_BOTTOM_DEAD_SPOT_ANGLE_SUPPORTED),
2971a406ba4SMatthias Ringwald (uint8_t) has_feature(CP_FEATURE_FLAG_TOP_AND_BOTTOM_DEAD_SPOT_ANGLE_SUPPORTED),
2981a406ba4SMatthias Ringwald (uint8_t) has_feature(CP_FEATURE_FLAG_ACCUMULATED_ENERGY_SUPPORTED),
2991a406ba4SMatthias Ringwald (uint8_t) has_feature(CP_FEATURE_FLAG_OFFSET_COMPENSATION_INDICATOR_SUPPORTED)
3009fe70df8SMilanka Ringwald };
3019fe70df8SMilanka Ringwald
3029fe70df8SMilanka Ringwald int i;
3039fe70df8SMilanka Ringwald for (i = CP_MEASUREMENT_FLAG_PEDAL_POWER_BALANCE_PRESENT; i <= CP_MEASUREMENT_FLAG_OFFSET_COMPENSATION_INDICATOR; i++){
3049fe70df8SMilanka Ringwald measurement_flags |= flag[i] << i;
3059fe70df8SMilanka Ringwald }
3069fe70df8SMilanka Ringwald
3079fe70df8SMilanka Ringwald return measurement_flags;
3089fe70df8SMilanka Ringwald }
3099fe70df8SMilanka Ringwald
cycling_power_service_get_measurement_flags(cycling_power_t * instance)3109fe70df8SMilanka Ringwald static uint16_t cycling_power_service_get_measurement_flags(cycling_power_t * instance){
3119fe70df8SMilanka Ringwald if (!instance) return 0;
3129fe70df8SMilanka Ringwald if (instance->masked_measurement_flags != CYCLING_POWER_MEASUREMENT_FLAGS_CLEARED){
3139fe70df8SMilanka Ringwald return instance->masked_measurement_flags;
3149fe70df8SMilanka Ringwald }
3159fe70df8SMilanka Ringwald if (instance->default_measurement_flags == CYCLING_POWER_MEASUREMENT_FLAGS_CLEARED){
3169fe70df8SMilanka Ringwald instance->default_measurement_flags = cycling_power_service_default_measurement_flags();
3179fe70df8SMilanka Ringwald }
3189fe70df8SMilanka Ringwald return instance->default_measurement_flags;
3199fe70df8SMilanka Ringwald }
3209fe70df8SMilanka Ringwald
3219fe70df8SMilanka Ringwald
cycling_power_service_measurement_flags(void)3229fe70df8SMilanka Ringwald uint16_t cycling_power_service_measurement_flags(void){
3239fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
3249fe70df8SMilanka Ringwald return cycling_power_service_get_measurement_flags(instance);
3259fe70df8SMilanka Ringwald }
3269fe70df8SMilanka Ringwald
cycling_power_service_vector_flags(void)3279fe70df8SMilanka Ringwald uint8_t cycling_power_service_vector_flags(void){
3289fe70df8SMilanka Ringwald uint8_t vector_flags = 0;
3299fe70df8SMilanka Ringwald uint8_t flag[] = {
3301a406ba4SMatthias Ringwald (uint8_t )has_feature(CP_FEATURE_FLAG_CRANK_REVOLUTION_DATA_SUPPORTED),
3311a406ba4SMatthias Ringwald (uint8_t )has_feature(CP_FEATURE_FLAG_EXTREME_ANGLES_SUPPORTED),
3321a406ba4SMatthias Ringwald (uint8_t )has_feature(CP_FEATURE_FLAG_EXTREME_MAGNITUDES_SUPPORTED) && (has_feature(CP_FEATURE_FLAG_SENSOR_MEASUREMENT_CONTEXT) == CP_SENSOR_MEASUREMENT_CONTEXT_FORCE),
3331a406ba4SMatthias Ringwald (uint8_t )has_feature(CP_FEATURE_FLAG_EXTREME_MAGNITUDES_SUPPORTED) && (has_feature(CP_FEATURE_FLAG_SENSOR_MEASUREMENT_CONTEXT) == CP_SENSOR_MEASUREMENT_CONTEXT_TORQUE),
3341a406ba4SMatthias Ringwald (uint8_t )has_feature(CP_FEATURE_FLAG_INSTANTANEOUS_MEASUREMENT_DIRECTION_SUPPORTED) && cycling_power_vector_instantaneous_measurement_direction()
3359fe70df8SMilanka Ringwald };
3369fe70df8SMilanka Ringwald
3379fe70df8SMilanka Ringwald int i;
3389fe70df8SMilanka Ringwald for (i = CP_VECTOR_FLAG_CRANK_REVOLUTION_DATA_PRESENT; i <= CP_VECTOR_FLAG_INSTANTANEOUS_MEASUREMENT_DIRECTION; i++){
3399fe70df8SMilanka Ringwald vector_flags |= flag[i] << i;
3409fe70df8SMilanka Ringwald }
3419fe70df8SMilanka Ringwald return vector_flags;
3429fe70df8SMilanka Ringwald }
3439fe70df8SMilanka Ringwald
cycling_power_service_vector_can_send_now(void * context)3449fe70df8SMilanka Ringwald static void cycling_power_service_vector_can_send_now(void * context){
3459fe70df8SMilanka Ringwald cycling_power_t * instance = (cycling_power_t *) context;
3469fe70df8SMilanka Ringwald if (!instance){
347900fc9c6SMilanka Ringwald log_error("cycling_power_service_measurement_can_send_now: instance is null");
3489fe70df8SMilanka Ringwald return;
3499fe70df8SMilanka Ringwald }
3509fe70df8SMilanka Ringwald uint8_t value[50];
3519fe70df8SMilanka Ringwald uint8_t vector_flags = cycling_power_service_vector_flags();
3529fe70df8SMilanka Ringwald int pos = 0;
3539fe70df8SMilanka Ringwald
3549fe70df8SMilanka Ringwald value[pos++] = vector_flags;
3559fe70df8SMilanka Ringwald int i;
3569fe70df8SMilanka Ringwald for (i = CP_VECTOR_FLAG_CRANK_REVOLUTION_DATA_PRESENT; i <= CP_VECTOR_FLAG_INSTANTANEOUS_MEASUREMENT_DIRECTION; i++){
3574ea43905SMatthias Ringwald if ((vector_flags & (1u << i)) == 0u) continue;
3589fe70df8SMilanka Ringwald switch ((cycling_power_vector_flag_t) i){
3599fe70df8SMilanka Ringwald case CP_VECTOR_FLAG_CRANK_REVOLUTION_DATA_PRESENT:
3609fe70df8SMilanka Ringwald little_endian_store_16(value, pos, instance->cumulative_crank_revolutions);
3619fe70df8SMilanka Ringwald pos += 2;
3629fe70df8SMilanka Ringwald little_endian_store_16(value, pos, instance->last_crank_event_time_s);
3639fe70df8SMilanka Ringwald pos += 2;
3649fe70df8SMilanka Ringwald break;
3659fe70df8SMilanka Ringwald case CP_VECTOR_FLAG_INSTANTANEOUS_FORCE_MAGNITUDE_ARRAY_PRESENT:{
366900fc9c6SMilanka Ringwald uint16_t att_mtu = att_server_get_mtu(instance->con_handle);
367900fc9c6SMilanka Ringwald uint16_t bytes_left = 0;
3684ea43905SMatthias Ringwald if (att_mtu > (pos + 3u)){
3694ea43905SMatthias Ringwald bytes_left = btstack_min(sizeof(value), att_mtu - 3u - pos);
370900fc9c6SMilanka Ringwald }
3714ea43905SMatthias Ringwald while ((bytes_left > 2u) && instance->force_magnitude_count){
3728d927742SMilanka Ringwald little_endian_store_16(value, pos, instance->vector_instantaneous_force_magnitude_N_array[0]);
3739fe70df8SMilanka Ringwald pos += 2;
3744ea43905SMatthias Ringwald bytes_left -= 2u;
3758d927742SMilanka Ringwald instance->vector_instantaneous_force_magnitude_N_array++;
3769fe70df8SMilanka Ringwald instance->force_magnitude_count--;
3779fe70df8SMilanka Ringwald }
3789fe70df8SMilanka Ringwald break;
3799fe70df8SMilanka Ringwald }
3809fe70df8SMilanka Ringwald case CP_VECTOR_FLAG_INSTANTANEOUS_TORQUE_MAGNITUDE_ARRAY_PRESENT:{
381900fc9c6SMilanka Ringwald uint16_t att_mtu = att_server_get_mtu(instance->con_handle);
382900fc9c6SMilanka Ringwald uint16_t bytes_left = 0;
3834ea43905SMatthias Ringwald if (att_mtu > (pos + 3u)){
3844ea43905SMatthias Ringwald bytes_left = btstack_min(sizeof(value), att_mtu - 3u - pos);
385900fc9c6SMilanka Ringwald }
3869fe70df8SMilanka Ringwald
3874ea43905SMatthias Ringwald while ((bytes_left > 2u) && instance->torque_magnitude_count){
3888d927742SMilanka Ringwald little_endian_store_16(value, pos, instance->vector_instantaneous_torque_magnitude_Nm_array[0]);
3899fe70df8SMilanka Ringwald pos += 2;
3904ea43905SMatthias Ringwald bytes_left -= 2u;
3918d927742SMilanka Ringwald instance->vector_instantaneous_torque_magnitude_Nm_array++;
3929fe70df8SMilanka Ringwald instance->torque_magnitude_count--;
3939fe70df8SMilanka Ringwald }
3949fe70df8SMilanka Ringwald break;
3959fe70df8SMilanka Ringwald }
3969fe70df8SMilanka Ringwald case CP_VECTOR_FLAG_FIRST_CRANK_MEASUREMENT_ANGLE_PRESENT:
3978d927742SMilanka Ringwald little_endian_store_16(value, pos, instance->vector_first_crank_measurement_angle_degree);
3989fe70df8SMilanka Ringwald pos += 2;
3999fe70df8SMilanka Ringwald break;
4009fe70df8SMilanka Ringwald case CP_VECTOR_FLAG_INSTANTANEOUS_MEASUREMENT_DIRECTION:
4019fe70df8SMilanka Ringwald break;
4029fe70df8SMilanka Ringwald default:
4039fe70df8SMilanka Ringwald break;
4049fe70df8SMilanka Ringwald }
4059fe70df8SMilanka Ringwald }
4069fe70df8SMilanka Ringwald
4079fe70df8SMilanka Ringwald att_server_notify(instance->con_handle, instance->vector_value_handle, &value[0], pos);
4089fe70df8SMilanka Ringwald }
4099fe70df8SMilanka Ringwald
cycling_power_measurement_flag_value_size(cycling_power_measurement_flag_t flag)4109fe70df8SMilanka Ringwald static int cycling_power_measurement_flag_value_size(cycling_power_measurement_flag_t flag){
4119fe70df8SMilanka Ringwald switch (flag){
4129fe70df8SMilanka Ringwald case CP_MEASUREMENT_FLAG_PEDAL_POWER_BALANCE_PRESENT:
4139fe70df8SMilanka Ringwald return 1;
4149fe70df8SMilanka Ringwald case CP_MEASUREMENT_FLAG_WHEEL_REVOLUTION_DATA_PRESENT:
4159fe70df8SMilanka Ringwald return 6;
4169fe70df8SMilanka Ringwald case CP_MEASUREMENT_FLAG_CRANK_REVOLUTION_DATA_PRESENT:
4179fe70df8SMilanka Ringwald case CP_MEASUREMENT_FLAG_EXTREME_FORCE_MAGNITUDES_PRESENT:
4189fe70df8SMilanka Ringwald case CP_MEASUREMENT_FLAG_EXTREME_TORQUE_MAGNITUDES_PRESENT:
4199fe70df8SMilanka Ringwald return 4;
4209fe70df8SMilanka Ringwald case CP_MEASUREMENT_FLAG_EXTREME_ANGLES_PRESENT:
4219fe70df8SMilanka Ringwald return 3;
4229fe70df8SMilanka Ringwald case CP_MEASUREMENT_FLAG_ACCUMULATED_TORQUE_PRESENT:
4239fe70df8SMilanka Ringwald case CP_MEASUREMENT_FLAG_TOP_DEAD_SPOT_ANGLE_PRESENT:
4249fe70df8SMilanka Ringwald case CP_MEASUREMENT_FLAG_BOTTOM_DEAD_SPOT_ANGLE_PRESENT:
4259fe70df8SMilanka Ringwald case CP_MEASUREMENT_FLAG_ACCUMULATED_ENERGY_PRESENT:
4269fe70df8SMilanka Ringwald return 2;
4279fe70df8SMilanka Ringwald default:
4289fe70df8SMilanka Ringwald return 0;
4299fe70df8SMilanka Ringwald }
4309fe70df8SMilanka Ringwald }
4319fe70df8SMilanka Ringwald
cycling_power_store_measurement_flag_value(cycling_power_t * instance,cycling_power_measurement_flag_t flag,uint8_t * value)4329fe70df8SMilanka Ringwald static int cycling_power_store_measurement_flag_value(cycling_power_t * instance, cycling_power_measurement_flag_t flag, uint8_t * value){
4339fe70df8SMilanka Ringwald if (!instance) return 0;
4349fe70df8SMilanka Ringwald
4359fe70df8SMilanka Ringwald int pos = 0;
4369fe70df8SMilanka Ringwald switch (flag){
4379fe70df8SMilanka Ringwald case CP_MEASUREMENT_FLAG_PEDAL_POWER_BALANCE_PRESENT:
4389fe70df8SMilanka Ringwald value[pos++] = instance->pedal_power_balance_percentage;
4399fe70df8SMilanka Ringwald break;
4409fe70df8SMilanka Ringwald case CP_MEASUREMENT_FLAG_ACCUMULATED_TORQUE_PRESENT:
4418d927742SMilanka Ringwald little_endian_store_16(value, pos, instance->accumulated_torque_Nm);
4429fe70df8SMilanka Ringwald pos += 2;
4439fe70df8SMilanka Ringwald break;
4449fe70df8SMilanka Ringwald case CP_MEASUREMENT_FLAG_WHEEL_REVOLUTION_DATA_PRESENT:
4459fe70df8SMilanka Ringwald little_endian_store_32(value, pos, instance->cumulative_wheel_revolutions);
4469fe70df8SMilanka Ringwald pos += 4;
4479fe70df8SMilanka Ringwald little_endian_store_16(value, pos, instance->last_wheel_event_time_s);
4489fe70df8SMilanka Ringwald pos += 2;
4499fe70df8SMilanka Ringwald break;
4509fe70df8SMilanka Ringwald case CP_MEASUREMENT_FLAG_CRANK_REVOLUTION_DATA_PRESENT:
4519fe70df8SMilanka Ringwald little_endian_store_16(value, pos, instance->cumulative_crank_revolutions);
4529fe70df8SMilanka Ringwald pos += 2;
4539fe70df8SMilanka Ringwald little_endian_store_16(value, pos, instance->last_crank_event_time_s);
4549fe70df8SMilanka Ringwald pos += 2;
4559fe70df8SMilanka Ringwald break;
4569fe70df8SMilanka Ringwald case CP_MEASUREMENT_FLAG_EXTREME_FORCE_MAGNITUDES_PRESENT:
4578d927742SMilanka Ringwald little_endian_store_16(value, pos, (uint16_t)instance->maximum_force_magnitude_N);
4589fe70df8SMilanka Ringwald pos += 2;
4598d927742SMilanka Ringwald little_endian_store_16(value, pos, (uint16_t)instance->minimum_force_magnitude_N);
4609fe70df8SMilanka Ringwald pos += 2;
4619fe70df8SMilanka Ringwald break;
4629fe70df8SMilanka Ringwald case CP_MEASUREMENT_FLAG_EXTREME_TORQUE_MAGNITUDES_PRESENT:
4638d927742SMilanka Ringwald little_endian_store_16(value, pos, (uint16_t)instance->maximum_torque_magnitude_Nm);
4649fe70df8SMilanka Ringwald pos += 2;
4658d927742SMilanka Ringwald little_endian_store_16(value, pos, (uint16_t)instance->minimum_torque_magnitude_Nm);
4669fe70df8SMilanka Ringwald pos += 2;
4679fe70df8SMilanka Ringwald break;
4689fe70df8SMilanka Ringwald case CP_MEASUREMENT_FLAG_EXTREME_ANGLES_PRESENT:
4698d927742SMilanka Ringwald little_endian_store_24(value, pos, (instance->maximum_angle_degree << 12) | instance->minimum_angle_degree);
4709fe70df8SMilanka Ringwald pos += 3;
4719fe70df8SMilanka Ringwald break;
4729fe70df8SMilanka Ringwald case CP_MEASUREMENT_FLAG_TOP_DEAD_SPOT_ANGLE_PRESENT:
4738d927742SMilanka Ringwald little_endian_store_16(value, pos, (uint16_t)instance->top_dead_spot_angle_degree);
4749fe70df8SMilanka Ringwald pos += 2;
4759fe70df8SMilanka Ringwald break;
4769fe70df8SMilanka Ringwald case CP_MEASUREMENT_FLAG_BOTTOM_DEAD_SPOT_ANGLE_PRESENT:
4778d927742SMilanka Ringwald little_endian_store_16(value, pos, (uint16_t)instance->bottom_dead_spot_angle_degree);
4789fe70df8SMilanka Ringwald pos += 2;
4799fe70df8SMilanka Ringwald break;
4809fe70df8SMilanka Ringwald case CP_MEASUREMENT_FLAG_ACCUMULATED_ENERGY_PRESENT:
4819fe70df8SMilanka Ringwald little_endian_store_16(value, pos, (uint16_t)instance->accumulated_energy_kJ);
4829fe70df8SMilanka Ringwald pos += 2;
4839fe70df8SMilanka Ringwald break;
4849fe70df8SMilanka Ringwald default:
4859fe70df8SMilanka Ringwald break;
4869fe70df8SMilanka Ringwald }
4879fe70df8SMilanka Ringwald return pos;
4889fe70df8SMilanka Ringwald }
4899fe70df8SMilanka Ringwald
4909fe70df8SMilanka Ringwald
cycling_power_store_measurement(cycling_power_t * instance,uint8_t * value,uint16_t max_value_size)4919fe70df8SMilanka Ringwald static int cycling_power_store_measurement(cycling_power_t * instance, uint8_t * value, uint16_t max_value_size){
4924ea43905SMatthias Ringwald if (max_value_size < 4u) return 0u;
4939fe70df8SMilanka Ringwald if (!instance) return 0;
4949fe70df8SMilanka Ringwald
4959fe70df8SMilanka Ringwald uint16_t measurement_flags = cycling_power_service_get_measurement_flags(instance);
4969fe70df8SMilanka Ringwald int pos = 0;
4979fe70df8SMilanka Ringwald little_endian_store_16(value, 0, measurement_flags);
4989fe70df8SMilanka Ringwald pos += 2;
4998d927742SMilanka Ringwald little_endian_store_16(value, 2, instance->instantaneous_power_W);
5009fe70df8SMilanka Ringwald pos += 2;
5011a406ba4SMatthias Ringwald int flag_index;
5029fe70df8SMilanka Ringwald uint16_t bytes_left = max_value_size - pos;
5031a406ba4SMatthias Ringwald for (flag_index = 0; flag_index < CP_MEASUREMENT_FLAG_RESERVED; flag_index++){
5044ea43905SMatthias Ringwald if ((measurement_flags & (1u << flag_index)) == 0u) continue;
5051a406ba4SMatthias Ringwald cycling_power_measurement_flag_t flag = (cycling_power_measurement_flag_t) flag_index;
5069fe70df8SMilanka Ringwald uint16_t value_size = cycling_power_measurement_flag_value_size(flag);
5079fe70df8SMilanka Ringwald if (value_size > bytes_left ) return pos;
5089fe70df8SMilanka Ringwald cycling_power_store_measurement_flag_value(instance, flag, &value[pos]);
5099fe70df8SMilanka Ringwald pos += value_size;
5109fe70df8SMilanka Ringwald bytes_left -= value_size;
5119fe70df8SMilanka Ringwald }
5129fe70df8SMilanka Ringwald return pos;
5139fe70df8SMilanka Ringwald }
5149fe70df8SMilanka Ringwald
cycling_power_get_measurement_adv(uint16_t adv_interval,uint8_t * adv_buffer,uint16_t adv_size)5158d927742SMilanka Ringwald int cycling_power_get_measurement_adv(uint16_t adv_interval, uint8_t * adv_buffer, uint16_t adv_size){
5168d927742SMilanka Ringwald if (adv_size < 12u) return 0u;
5179fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
5189fe70df8SMilanka Ringwald int pos = 0;
5199fe70df8SMilanka Ringwald // adv flags
5208d927742SMilanka Ringwald adv_buffer[pos++] = 2;
5218d927742SMilanka Ringwald adv_buffer[pos++] = BLUETOOTH_DATA_TYPE_FLAGS;
5228d927742SMilanka Ringwald adv_buffer[pos++] = 0x4;
5239fe70df8SMilanka Ringwald
5249fe70df8SMilanka Ringwald // adv interval
5258d927742SMilanka Ringwald adv_buffer[pos++] = 3;
5268d927742SMilanka Ringwald adv_buffer[pos++] = BLUETOOTH_DATA_TYPE_ADVERTISING_INTERVAL;
5278d927742SMilanka Ringwald little_endian_store_16(adv_buffer, pos, adv_interval);
5289fe70df8SMilanka Ringwald pos += 2;
5299fe70df8SMilanka Ringwald //
5308d927742SMilanka Ringwald int value_len = cycling_power_store_measurement(instance, &adv_buffer[pos + 4], CYCLING_POWER_MAX_BROACAST_MSG_SIZE - (pos + 4));
5318d927742SMilanka Ringwald adv_buffer[pos++] = 3 + value_len;
5328d927742SMilanka Ringwald adv_buffer[pos++] = BLUETOOTH_DATA_TYPE_SERVICE_DATA_16_BIT_UUID;
5338d927742SMilanka Ringwald little_endian_store_16(adv_buffer, pos, ORG_BLUETOOTH_SERVICE_CYCLING_POWER);
5349fe70df8SMilanka Ringwald pos += 2;
5359fe70df8SMilanka Ringwald // value data already in place cycling_power_get_measurement
5369fe70df8SMilanka Ringwald pos += value_len;
5379fe70df8SMilanka Ringwald // set ADV_NONCONN_IND
5389fe70df8SMilanka Ringwald return pos;
5399fe70df8SMilanka Ringwald }
5409fe70df8SMilanka Ringwald
cycling_power_service_broadcast_can_send_now(void * context)5419fe70df8SMilanka Ringwald static void cycling_power_service_broadcast_can_send_now(void * context){
5429fe70df8SMilanka Ringwald cycling_power_t * instance = (cycling_power_t *) context;
5439fe70df8SMilanka Ringwald if (!instance){
544900fc9c6SMilanka Ringwald log_error("cycling_power_service_broadcast_can_send_now: instance is null");
5459fe70df8SMilanka Ringwald return;
5469fe70df8SMilanka Ringwald }
5479fe70df8SMilanka Ringwald uint8_t value[CYCLING_POWER_MAX_BROACAST_MSG_SIZE];
5489fe70df8SMilanka Ringwald int pos = cycling_power_store_measurement(instance, &value[0], sizeof(value));
5499fe70df8SMilanka Ringwald att_server_notify(instance->con_handle, instance->measurement_value_handle, &value[0], pos);
5509fe70df8SMilanka Ringwald }
5519fe70df8SMilanka Ringwald
cycling_power_service_measurement_can_send_now(void * context)5529fe70df8SMilanka Ringwald static void cycling_power_service_measurement_can_send_now(void * context){
5539fe70df8SMilanka Ringwald cycling_power_t * instance = (cycling_power_t *) context;
5549fe70df8SMilanka Ringwald if (!instance){
555900fc9c6SMilanka Ringwald log_error("cycling_power_service_measurement_can_send_now: instance is null");
5569fe70df8SMilanka Ringwald return;
5579fe70df8SMilanka Ringwald }
5589fe70df8SMilanka Ringwald uint8_t value[40];
5599fe70df8SMilanka Ringwald int pos = cycling_power_store_measurement(instance, &value[0], sizeof(value));
5609fe70df8SMilanka Ringwald att_server_notify(instance->con_handle, instance->measurement_value_handle, &value[0], pos);
5619fe70df8SMilanka Ringwald }
5629fe70df8SMilanka Ringwald
cycling_power_service_response_can_send_now(void * context)5639fe70df8SMilanka Ringwald static void cycling_power_service_response_can_send_now(void * context){
5649fe70df8SMilanka Ringwald cycling_power_t * instance = (cycling_power_t *) context;
5659fe70df8SMilanka Ringwald if (!instance){
566900fc9c6SMilanka Ringwald log_error("cycling_power_service_response_can_send_now: instance is null");
5679fe70df8SMilanka Ringwald return;
5689fe70df8SMilanka Ringwald }
5699fe70df8SMilanka Ringwald
5709fe70df8SMilanka Ringwald if (instance->response_value == CP_RESPONSE_VALUE_W4_VALUE_AVAILABLE){
571900fc9c6SMilanka Ringwald log_error("cycling_power_service_response_can_send_now: CP_RESPONSE_VALUE_W4_VALUE_AVAILABLE");
5729fe70df8SMilanka Ringwald return;
5739fe70df8SMilanka Ringwald }
5749fe70df8SMilanka Ringwald
5753f78d90eSMatthias Ringwald // use preprocessor instead of btstack_max to get compile-time constant
5763f78d90eSMatthias Ringwald #if (CP_SENSOR_LOCATION_RESERVED > (CYCLING_POWER_MANUFACTURER_SPECIFIC_DATA_MAX_SIZE + 5))
5773f78d90eSMatthias Ringwald #define MAX_RESPONSE_PAYLOAD CP_SENSOR_LOCATION_RESERVED
5783f78d90eSMatthias Ringwald #else
5793f78d90eSMatthias Ringwald #define MAX_RESPONSE_PAYLOAD (CYCLING_POWER_MANUFACTURER_SPECIFIC_DATA_MAX_SIZE + 5)
5803f78d90eSMatthias Ringwald #endif
5813f78d90eSMatthias Ringwald
5823f78d90eSMatthias Ringwald uint8_t value[3 + MAX_RESPONSE_PAYLOAD];
5839fe70df8SMilanka Ringwald int pos = 0;
5849fe70df8SMilanka Ringwald value[pos++] = CP_OPCODE_RESPONSE_CODE;
5859fe70df8SMilanka Ringwald value[pos++] = instance->request_opcode;
5869fe70df8SMilanka Ringwald value[pos++] = instance->response_value;
5879fe70df8SMilanka Ringwald if (instance->response_value == CP_RESPONSE_VALUE_SUCCESS){
5889fe70df8SMilanka Ringwald switch (instance->request_opcode){
5899fe70df8SMilanka Ringwald case CP_OPCODE_REQUEST_SUPPORTED_SENSOR_LOCATIONS:{
5909fe70df8SMilanka Ringwald int i;
5919fe70df8SMilanka Ringwald for (i=0; i<instance->num_supported_sensor_locations; i++){
5929fe70df8SMilanka Ringwald value[pos++] = instance->supported_sensor_locations[i];
5939fe70df8SMilanka Ringwald }
5949fe70df8SMilanka Ringwald break;
5959fe70df8SMilanka Ringwald }
5969fe70df8SMilanka Ringwald case CP_OPCODE_REQUEST_CRANK_LENGTH:
5979fe70df8SMilanka Ringwald little_endian_store_16(value, pos, instance->crank_length_mm);
5989fe70df8SMilanka Ringwald pos += 2;
5999fe70df8SMilanka Ringwald break;
6009fe70df8SMilanka Ringwald case CP_OPCODE_REQUEST_CHAIN_LENGTH:
6019fe70df8SMilanka Ringwald little_endian_store_16(value, pos, instance->chain_length_mm);
6029fe70df8SMilanka Ringwald pos += 2;
6039fe70df8SMilanka Ringwald break;
6049fe70df8SMilanka Ringwald case CP_OPCODE_REQUEST_CHAIN_WEIGHT:
6059fe70df8SMilanka Ringwald little_endian_store_16(value, pos, instance->chain_weight_g);
6069fe70df8SMilanka Ringwald pos += 2;
6079fe70df8SMilanka Ringwald break;
6089fe70df8SMilanka Ringwald case CP_OPCODE_REQUEST_SPAN_LENGTH:
6099fe70df8SMilanka Ringwald little_endian_store_16(value, pos, instance->span_length_mm);
6109fe70df8SMilanka Ringwald pos += 2;
6119fe70df8SMilanka Ringwald break;
6129fe70df8SMilanka Ringwald case CP_OPCODE_REQUEST_FACTORY_CALIBRATION_DATE:
6139fe70df8SMilanka Ringwald little_endian_store_16(value, pos, instance->factory_calibration_date.year);
6149fe70df8SMilanka Ringwald pos += 2;
6159fe70df8SMilanka Ringwald value[pos++] = instance->factory_calibration_date.month;
6169fe70df8SMilanka Ringwald value[pos++] = instance->factory_calibration_date.day;
6179fe70df8SMilanka Ringwald value[pos++] = instance->factory_calibration_date.hours;
6189fe70df8SMilanka Ringwald value[pos++] = instance->factory_calibration_date.minutes;
6199fe70df8SMilanka Ringwald value[pos++] = instance->factory_calibration_date.seconds;
6209fe70df8SMilanka Ringwald break;
6219fe70df8SMilanka Ringwald case CP_OPCODE_REQUEST_SAMPLING_RATE:
6228d927742SMilanka Ringwald value[pos++] = instance->sampling_rate_Hz;
6239fe70df8SMilanka Ringwald break;
6249fe70df8SMilanka Ringwald case CP_OPCODE_START_OFFSET_COMPENSATION:
6259fe70df8SMilanka Ringwald case CP_OPCODE_START_ENHANCED_OFFSET_COMPENSATION:{
6269fe70df8SMilanka Ringwald uint16_t calibrated_value = 0xffff;
6279fe70df8SMilanka Ringwald if (has_feature(CP_FEATURE_FLAG_EXTREME_MAGNITUDES_SUPPORTED)){
6289fe70df8SMilanka Ringwald if (has_feature(CP_FEATURE_FLAG_SENSOR_MEASUREMENT_CONTEXT) == CP_SENSOR_MEASUREMENT_CONTEXT_FORCE) {
6298d927742SMilanka Ringwald calibrated_value = instance->current_force_magnitude_N;
6309fe70df8SMilanka Ringwald } else if (has_feature(CP_FEATURE_FLAG_SENSOR_MEASUREMENT_CONTEXT) == CP_SENSOR_MEASUREMENT_CONTEXT_TORQUE){
6318d927742SMilanka Ringwald calibrated_value = instance->current_torque_magnitude_Nm;
6329fe70df8SMilanka Ringwald }
6339fe70df8SMilanka Ringwald }
6349fe70df8SMilanka Ringwald
6359fe70df8SMilanka Ringwald if (calibrated_value == CP_CALIBRATION_STATUS_INCORRECT_CALIBRATION_POSITION){
636c1dbba9dSMatthias Ringwald value[pos++] = (uint8_t) calibrated_value;
6379fe70df8SMilanka Ringwald // do not include manufacturer ID and data
6389fe70df8SMilanka Ringwald break;
6399fe70df8SMilanka Ringwald } else if (calibrated_value == CP_CALIBRATION_STATUS_MANUFACTURER_SPECIFIC_ERROR_FOLLOWS){
640c1dbba9dSMatthias Ringwald value[pos++] = (uint8_t) calibrated_value;
6419fe70df8SMilanka Ringwald } else {
6429fe70df8SMilanka Ringwald little_endian_store_16(value, pos, calibrated_value);
6439fe70df8SMilanka Ringwald pos += 2;
6449fe70df8SMilanka Ringwald }
6459fe70df8SMilanka Ringwald
6469fe70df8SMilanka Ringwald if (instance->request_opcode == CP_OPCODE_START_OFFSET_COMPENSATION) break;
6479fe70df8SMilanka Ringwald little_endian_store_16(value, pos, instance->manufacturer_company_id);
6489fe70df8SMilanka Ringwald pos += 2;
649c1ab6cc1SMatthias Ringwald int data_len = (instance->num_manufacturer_specific_data < CYCLING_POWER_MANUFACTURER_SPECIFIC_DATA_MAX_SIZE) ? instance->num_manufacturer_specific_data : (CYCLING_POWER_MANUFACTURER_SPECIFIC_DATA_MAX_SIZE - 1);
6509fe70df8SMilanka Ringwald value[pos++] = data_len;
6516535961aSMatthias Ringwald (void)memcpy(&value[pos],
6526535961aSMatthias Ringwald instance->manufacturer_specific_data, data_len);
6539fe70df8SMilanka Ringwald pos += data_len;
6549fe70df8SMilanka Ringwald value[pos++] = 0;
6559fe70df8SMilanka Ringwald break;
6569fe70df8SMilanka Ringwald }
6579fe70df8SMilanka Ringwald case CP_OPCODE_MASK_CYCLING_POWER_MEASUREMENT_CHARACTERISTIC_CONTENT:
6589fe70df8SMilanka Ringwald break;
6599fe70df8SMilanka Ringwald default:
6609fe70df8SMilanka Ringwald break;
6619fe70df8SMilanka Ringwald }
6629fe70df8SMilanka Ringwald }
6639fe70df8SMilanka Ringwald uint8_t status = att_server_indicate(instance->con_handle, instance->control_point_value_handle, &value[0], pos);
6649fe70df8SMilanka Ringwald if (status == ERROR_CODE_SUCCESS){
6659fe70df8SMilanka Ringwald instance->w4_indication_complete = 1;
6669fe70df8SMilanka Ringwald instance->request_opcode = CP_OPCODE_IDLE;
6679fe70df8SMilanka Ringwald } else {
668900fc9c6SMilanka Ringwald log_error("can_send_now failed 0x%2x", status);
6699fe70df8SMilanka Ringwald }
6709fe70df8SMilanka Ringwald }
6719fe70df8SMilanka Ringwald
cycling_power_service_server_emit_start_calibration(const cycling_power_t * instance,bool enhanced)672ee5fda23SMatthias Ringwald static void cycling_power_service_server_emit_start_calibration(const cycling_power_t *instance, bool enhanced) {
673ee5fda23SMatthias Ringwald
674ee5fda23SMatthias Ringwald cycling_power_sensor_measurement_context_t measurement_type =
675ee5fda23SMatthias Ringwald has_feature(CP_FEATURE_FLAG_SENSOR_MEASUREMENT_CONTEXT) ? CP_SENSOR_MEASUREMENT_CONTEXT_TORQUE : CP_SENSOR_MEASUREMENT_CONTEXT_FORCE;
676ee5fda23SMatthias Ringwald
677c24127c4SMatthias Ringwald uint8_t event[7];
678c24127c4SMatthias Ringwald int index = 0;
679c24127c4SMatthias Ringwald event[index++] = HCI_EVENT_GATTSERVICE_META;
680c24127c4SMatthias Ringwald event[index++] = sizeof(event) - 2u;
681c24127c4SMatthias Ringwald event[index++] = GATTSERVICE_SUBEVENT_CYCLING_POWER_START_CALIBRATION;
682c24127c4SMatthias Ringwald little_endian_store_16(event, index, instance->con_handle);
683c24127c4SMatthias Ringwald index += 2;
684c24127c4SMatthias Ringwald event[index++] = (uint8_t) measurement_type;
685c24127c4SMatthias Ringwald event[index++] = enhanced ? 1 : 0;
686c24127c4SMatthias Ringwald (*instance->calibration_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event));
687c24127c4SMatthias Ringwald }
688c24127c4SMatthias Ringwald
cycling_power_service_write_callback(hci_con_handle_t con_handle,uint16_t attribute_handle,uint16_t transaction_mode,uint16_t offset,uint8_t * buffer,uint16_t buffer_size)6899fe70df8SMilanka Ringwald static int cycling_power_service_write_callback(hci_con_handle_t con_handle, uint16_t attribute_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size){
6909fe70df8SMilanka Ringwald UNUSED(con_handle);
6919fe70df8SMilanka Ringwald UNUSED(offset);
6929fe70df8SMilanka Ringwald UNUSED(buffer_size);
6931a406ba4SMatthias Ringwald int i;
6941a406ba4SMatthias Ringwald cycling_power_sensor_location_t location;
6959fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
6969fe70df8SMilanka Ringwald
697689e7fb3SMatthias Ringwald if (transaction_mode != ATT_TRANSACTION_MODE_NONE){
698689e7fb3SMatthias Ringwald return 0;
699689e7fb3SMatthias Ringwald }
700689e7fb3SMatthias Ringwald
7019fe70df8SMilanka Ringwald if (attribute_handle == instance->measurement_client_configuration_descriptor_handle){
7024ea43905SMatthias Ringwald if (buffer_size < 2u){
7039fe70df8SMilanka Ringwald return ATT_ERROR_INVALID_OFFSET;
7049fe70df8SMilanka Ringwald }
7059fe70df8SMilanka Ringwald instance->measurement_client_configuration_descriptor_notify = little_endian_read_16(buffer, 0);
7069fe70df8SMilanka Ringwald instance->con_handle = con_handle;
707900fc9c6SMilanka Ringwald log_info("cycling_power_service_write_callback: measurement enabled %d", instance->measurement_client_configuration_descriptor_notify);
7089fe70df8SMilanka Ringwald return 0;
7099fe70df8SMilanka Ringwald }
7109fe70df8SMilanka Ringwald
7119fe70df8SMilanka Ringwald if (attribute_handle == instance->measurement_server_configuration_descriptor_handle){
7124ea43905SMatthias Ringwald if (buffer_size < 2u){
7139fe70df8SMilanka Ringwald return ATT_ERROR_INVALID_OFFSET;
7149fe70df8SMilanka Ringwald }
7159fe70df8SMilanka Ringwald instance->measurement_server_configuration_descriptor_broadcast = little_endian_read_16(buffer, 0);
7169fe70df8SMilanka Ringwald instance->con_handle = con_handle;
7179fe70df8SMilanka Ringwald uint8_t event[5];
7189fe70df8SMilanka Ringwald int index = 0;
71928e59789SMilanka Ringwald event[index++] = HCI_EVENT_GATTSERVICE_META;
7204ea43905SMatthias Ringwald event[index++] = sizeof(event) - 2u;
7219fe70df8SMilanka Ringwald
7229fe70df8SMilanka Ringwald if (instance->measurement_server_configuration_descriptor_broadcast){
72328e59789SMilanka Ringwald event[index++] = GATTSERVICE_SUBEVENT_CYCLING_POWER_BROADCAST_START;
724900fc9c6SMilanka Ringwald log_info("cycling_power_service_write_callback: start broadcast");
7259fe70df8SMilanka Ringwald } else {
72628e59789SMilanka Ringwald event[index++] = GATTSERVICE_SUBEVENT_CYCLING_POWER_BROADCAST_STOP;
727900fc9c6SMilanka Ringwald log_info("cycling_power_service_write_callback: stop broadcast");
7289fe70df8SMilanka Ringwald }
7299fe70df8SMilanka Ringwald little_endian_store_16(event, index, con_handle);
7309fe70df8SMilanka Ringwald index += 2;
7319fe70df8SMilanka Ringwald (*instance->calibration_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event));
7329fe70df8SMilanka Ringwald return 0;
7339fe70df8SMilanka Ringwald }
7349fe70df8SMilanka Ringwald
7359fe70df8SMilanka Ringwald if (attribute_handle == instance->vector_client_configuration_descriptor_handle){
7364ea43905SMatthias Ringwald if (buffer_size < 2u){
7379fe70df8SMilanka Ringwald return ATT_ERROR_INVALID_OFFSET;
7389fe70df8SMilanka Ringwald }
7399fe70df8SMilanka Ringwald instance->con_handle = con_handle;
7409fe70df8SMilanka Ringwald
7419fe70df8SMilanka Ringwald #ifdef ENABLE_ATT_DELAYED_RESPONSE
7429fe70df8SMilanka Ringwald switch (instance->con_interval_status){
7439fe70df8SMilanka Ringwald case CP_CONNECTION_INTERVAL_STATUS_REJECTED:
744f15c31b5SMilanka Ringwald return CYCLING_POWER_ERROR_CODE_INAPPROPRIATE_CONNECTION_PARAMETERS;
7459fe70df8SMilanka Ringwald
7469fe70df8SMilanka Ringwald case CP_CONNECTION_INTERVAL_STATUS_ACCEPTED:
7479fe70df8SMilanka Ringwald case CP_CONNECTION_INTERVAL_STATUS_RECEIVED:
748c1ab6cc1SMatthias Ringwald if ((instance->con_interval > instance->con_interval_max) || (instance->con_interval < instance->con_interval_min)){
7499fe70df8SMilanka Ringwald instance->con_interval_status = CP_CONNECTION_INTERVAL_STATUS_W4_L2CAP_RESPONSE;
7509fe70df8SMilanka Ringwald gap_request_connection_parameter_update(instance->con_handle, instance->con_interval_min, instance->con_interval_max, 4, 100); // 15 ms, 4, 1s
7519fe70df8SMilanka Ringwald return ATT_ERROR_WRITE_RESPONSE_PENDING;
7529fe70df8SMilanka Ringwald }
7539fe70df8SMilanka Ringwald instance->vector_client_configuration_descriptor_notify = little_endian_read_16(buffer, 0);
7549fe70df8SMilanka Ringwald return 0;
7559fe70df8SMilanka Ringwald default:
7569fe70df8SMilanka Ringwald return ATT_ERROR_WRITE_RESPONSE_PENDING;
7579fe70df8SMilanka Ringwald
7589fe70df8SMilanka Ringwald }
7599fe70df8SMilanka Ringwald #endif
7609fe70df8SMilanka Ringwald }
7619fe70df8SMilanka Ringwald
7629fe70df8SMilanka Ringwald if (attribute_handle == instance->control_point_client_configuration_descriptor_handle){
7634ea43905SMatthias Ringwald if (buffer_size < 2u){
7649fe70df8SMilanka Ringwald return ATT_ERROR_INVALID_OFFSET;
7659fe70df8SMilanka Ringwald }
7669fe70df8SMilanka Ringwald instance->control_point_client_configuration_descriptor_indicate = little_endian_read_16(buffer, 0);
7679fe70df8SMilanka Ringwald instance->con_handle = con_handle;
768900fc9c6SMilanka Ringwald log_info("cycling_power_service_write_callback: indication enabled %d", instance->control_point_client_configuration_descriptor_indicate);
7699fe70df8SMilanka Ringwald return 0;
7709fe70df8SMilanka Ringwald }
7719fe70df8SMilanka Ringwald
7729fe70df8SMilanka Ringwald if (attribute_handle == instance->feature_value_handle){
7734ea43905SMatthias Ringwald if (buffer_size < 4u){
7749fe70df8SMilanka Ringwald return ATT_ERROR_INVALID_OFFSET;
7759fe70df8SMilanka Ringwald }
7769fe70df8SMilanka Ringwald instance->feature_flags = little_endian_read_32(buffer, 0);
7779fe70df8SMilanka Ringwald return 0;
7789fe70df8SMilanka Ringwald }
7799fe70df8SMilanka Ringwald
7809fe70df8SMilanka Ringwald if (attribute_handle == instance->control_point_value_handle){
7814ea43905SMatthias Ringwald if (instance->control_point_client_configuration_descriptor_indicate == 0u) return CYCLING_POWER_ERROR_CODE_CCC_DESCRIPTOR_IMPROPERLY_CONFIGURED;
7824ea43905SMatthias Ringwald if (instance->w4_indication_complete != 0u){
783f15c31b5SMilanka Ringwald return CYCLING_POWER_ERROR_CODE_PROCEDURE_ALREADY_IN_PROGRESS;
7849fe70df8SMilanka Ringwald }
7859fe70df8SMilanka Ringwald int pos = 0;
7861a406ba4SMatthias Ringwald instance->request_opcode = (cycling_power_opcode_t) buffer[pos++];
7879fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_OP_CODE_NOT_SUPPORTED;
7889fe70df8SMilanka Ringwald
7899fe70df8SMilanka Ringwald switch (instance->request_opcode){
7909fe70df8SMilanka Ringwald case CP_OPCODE_SET_CUMULATIVE_VALUE:
7919fe70df8SMilanka Ringwald if (!has_feature(CP_FEATURE_FLAG_WHEEL_REVOLUTION_DATA_SUPPORTED)) break;
7929fe70df8SMilanka Ringwald instance->cumulative_wheel_revolutions = little_endian_read_32(buffer, pos);
7939fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_SUCCESS;
7949fe70df8SMilanka Ringwald break;
7959fe70df8SMilanka Ringwald
7969fe70df8SMilanka Ringwald case CP_OPCODE_REQUEST_SUPPORTED_SENSOR_LOCATIONS:
7979fe70df8SMilanka Ringwald if (!has_feature(CP_FEATURE_FLAG_MULTIPLE_SENSOR_LOCATIONS_SUPPORTED)) break;
7989fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_SUCCESS;
7999fe70df8SMilanka Ringwald break;
8009fe70df8SMilanka Ringwald
8019fe70df8SMilanka Ringwald case CP_OPCODE_UPDATE_SENSOR_LOCATION:
8029fe70df8SMilanka Ringwald if (!has_feature(CP_FEATURE_FLAG_MULTIPLE_SENSOR_LOCATIONS_SUPPORTED)) break;
8031a406ba4SMatthias Ringwald location = (cycling_power_sensor_location_t) buffer[pos];
8049fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_INVALID_PARAMETER;
8059fe70df8SMilanka Ringwald for (i=0; i<instance->num_supported_sensor_locations; i++){
8069fe70df8SMilanka Ringwald if (instance->supported_sensor_locations[i] == location){
8079fe70df8SMilanka Ringwald instance->sensor_location = location;
8089fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_SUCCESS;
8099fe70df8SMilanka Ringwald break;
8109fe70df8SMilanka Ringwald }
8119fe70df8SMilanka Ringwald }
8129fe70df8SMilanka Ringwald break;
8139fe70df8SMilanka Ringwald
8149fe70df8SMilanka Ringwald case CP_OPCODE_REQUEST_CRANK_LENGTH:
8159fe70df8SMilanka Ringwald if (!has_feature(CP_FEATURE_FLAG_CRANK_LENGTH_ADJUSTMENT_SUPPORTED)) break;
8169fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_SUCCESS;
8179fe70df8SMilanka Ringwald break;
8189fe70df8SMilanka Ringwald case CP_OPCODE_SET_CRANK_LENGTH:
8199fe70df8SMilanka Ringwald if (!has_feature(CP_FEATURE_FLAG_CRANK_LENGTH_ADJUSTMENT_SUPPORTED)) break;
8209fe70df8SMilanka Ringwald instance->crank_length_mm = little_endian_read_16(buffer, pos);
8219fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_SUCCESS;
8229fe70df8SMilanka Ringwald break;
8239fe70df8SMilanka Ringwald
8249fe70df8SMilanka Ringwald case CP_OPCODE_REQUEST_CHAIN_LENGTH:
8259fe70df8SMilanka Ringwald if (!has_feature(CP_FEATURE_FLAG_CHAIN_LENGTH_ADJUSTMENT_SUPPORTED)) break;
8269fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_SUCCESS;
8279fe70df8SMilanka Ringwald break;
8289fe70df8SMilanka Ringwald case CP_OPCODE_SET_CHAIN_LENGTH:
8299fe70df8SMilanka Ringwald if (!has_feature(CP_FEATURE_FLAG_CHAIN_LENGTH_ADJUSTMENT_SUPPORTED)) break;
8309fe70df8SMilanka Ringwald instance->chain_length_mm = little_endian_read_16(buffer, pos);
8319fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_SUCCESS;
8329fe70df8SMilanka Ringwald break;
8339fe70df8SMilanka Ringwald
8349fe70df8SMilanka Ringwald case CP_OPCODE_REQUEST_CHAIN_WEIGHT:
8359fe70df8SMilanka Ringwald if (!has_feature(CP_FEATURE_FLAG_CHAIN_WEIGHT_ADJUSTMENT_SUPPORTED)) break;
8369fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_SUCCESS;
8379fe70df8SMilanka Ringwald break;
8389fe70df8SMilanka Ringwald case CP_OPCODE_SET_CHAIN_WEIGHT:
8399fe70df8SMilanka Ringwald if (!has_feature(CP_FEATURE_FLAG_CHAIN_WEIGHT_ADJUSTMENT_SUPPORTED)) break;
8409fe70df8SMilanka Ringwald instance->chain_weight_g = little_endian_read_16(buffer, pos);
8419fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_SUCCESS;
8429fe70df8SMilanka Ringwald break;
8439fe70df8SMilanka Ringwald
8449fe70df8SMilanka Ringwald case CP_OPCODE_REQUEST_SPAN_LENGTH:
8459fe70df8SMilanka Ringwald if (!has_feature(CP_FEATURE_FLAG_SPAN_LENGTH_ADJUSTMENT_SUPPORTED)) break;
8469fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_SUCCESS;
8479fe70df8SMilanka Ringwald break;
8489fe70df8SMilanka Ringwald case CP_OPCODE_SET_SPAN_LENGTH:
8499fe70df8SMilanka Ringwald if (!has_feature(CP_FEATURE_FLAG_SPAN_LENGTH_ADJUSTMENT_SUPPORTED)) break;
8509fe70df8SMilanka Ringwald instance->span_length_mm = little_endian_read_16(buffer, pos);
8519fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_SUCCESS;
8529fe70df8SMilanka Ringwald break;
8539fe70df8SMilanka Ringwald
8549fe70df8SMilanka Ringwald case CP_OPCODE_REQUEST_FACTORY_CALIBRATION_DATE:
8559fe70df8SMilanka Ringwald if (!has_feature(CP_FEATURE_FLAG_FACTORY_CALIBRATION_DATE_SUPPORTED)) break;
8569fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_SUCCESS;
8579fe70df8SMilanka Ringwald break;
8589fe70df8SMilanka Ringwald
8599fe70df8SMilanka Ringwald case CP_OPCODE_REQUEST_SAMPLING_RATE:
8609fe70df8SMilanka Ringwald if (!instance->vector_value_handle) break;
8619fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_SUCCESS;
8629fe70df8SMilanka Ringwald break;
8639fe70df8SMilanka Ringwald
8649fe70df8SMilanka Ringwald case CP_OPCODE_START_OFFSET_COMPENSATION:
865ef34fc14SMatthias Ringwald if (has_feature(CP_FEATURE_FLAG_OFFSET_COMPENSATION_SUPPORTED)){
8669fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_W4_VALUE_AVAILABLE;
86789d9762fSMatthias Ringwald cycling_power_service_server_emit_start_calibration(instance, false);
86889d9762fSMatthias Ringwald } else {
86989d9762fSMatthias Ringwald instance->response_value = CP_RESPONSE_VALUE_INVALID_PARAMETER;
87089d9762fSMatthias Ringwald }
87189d9762fSMatthias Ringwald break;
87289d9762fSMatthias Ringwald
87389d9762fSMatthias Ringwald case CP_OPCODE_START_ENHANCED_OFFSET_COMPENSATION:
87489d9762fSMatthias Ringwald if (has_feature(CP_FEATURE_FLAG_ENHANCED_OFFSET_COMPENSATION_SUPPORTED)){
87589d9762fSMatthias Ringwald instance->response_value = CP_RESPONSE_VALUE_W4_VALUE_AVAILABLE;
87689d9762fSMatthias Ringwald cycling_power_service_server_emit_start_calibration(instance, true);
877ba458d3bSMatthias Ringwald } else {
878ef34fc14SMatthias Ringwald instance->response_value = CP_RESPONSE_VALUE_INVALID_PARAMETER;
879ba458d3bSMatthias Ringwald }
8809fe70df8SMilanka Ringwald break;
8819fe70df8SMilanka Ringwald
8829fe70df8SMilanka Ringwald case CP_OPCODE_MASK_CYCLING_POWER_MEASUREMENT_CHARACTERISTIC_CONTENT:{
8839fe70df8SMilanka Ringwald if (!has_feature(CP_FEATURE_FLAG_CYCLING_POWER_MEASUREMENT_CHARACTERISTIC_CONTENT_MASKING_SUPPORTED)) break;
8849fe70df8SMilanka Ringwald uint16_t mask_bitmap = little_endian_read_16(buffer, pos);
8859fe70df8SMilanka Ringwald uint16_t masked_measurement_flags = instance->default_measurement_flags;
8869fe70df8SMilanka Ringwald uint16_t index = 0;
8879fe70df8SMilanka Ringwald
8889fe70df8SMilanka Ringwald for (i = 0; i < CP_MASK_BIT_RESERVED; i++){
8894ea43905SMatthias Ringwald uint8_t clear_bit = (mask_bitmap & (1u << i)) ? 1u : 0u;
8909fe70df8SMilanka Ringwald
8919fe70df8SMilanka Ringwald masked_measurement_flags &= ~(clear_bit << index);
8929fe70df8SMilanka Ringwald index++;
8939fe70df8SMilanka Ringwald // following measurement flags have additional flag
8949fe70df8SMilanka Ringwald switch ((cycling_power_mask_bit_t)i){
8959fe70df8SMilanka Ringwald case CP_MASK_BIT_PEDAL_POWER_BALANCE:
8969fe70df8SMilanka Ringwald case CP_MASK_BIT_ACCUMULATED_TORQUE:
8979fe70df8SMilanka Ringwald case CP_MASK_BIT_EXTREME_MAGNITUDES:
8989fe70df8SMilanka Ringwald masked_measurement_flags &= ~(clear_bit << index);
8999fe70df8SMilanka Ringwald index++;
9009fe70df8SMilanka Ringwald break;
9019fe70df8SMilanka Ringwald default:
9029fe70df8SMilanka Ringwald break;
9039fe70df8SMilanka Ringwald }
9049fe70df8SMilanka Ringwald }
9059fe70df8SMilanka Ringwald instance->masked_measurement_flags = masked_measurement_flags;
9069fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_SUCCESS;
9079fe70df8SMilanka Ringwald break;
9089fe70df8SMilanka Ringwald }
9099fe70df8SMilanka Ringwald default:
9109fe70df8SMilanka Ringwald break;
9119fe70df8SMilanka Ringwald }
9129fe70df8SMilanka Ringwald
9139fe70df8SMilanka Ringwald if (instance->control_point_client_configuration_descriptor_indicate){
9149fe70df8SMilanka Ringwald instance->control_point_indicate_callback.callback = &cycling_power_service_response_can_send_now;
9159fe70df8SMilanka Ringwald instance->control_point_indicate_callback.context = (void*) instance;
9169fe70df8SMilanka Ringwald att_server_register_can_send_now_callback(&instance->control_point_indicate_callback, instance->con_handle);
9179fe70df8SMilanka Ringwald }
9189fe70df8SMilanka Ringwald return 0;
9199fe70df8SMilanka Ringwald }
9209fe70df8SMilanka Ringwald return 0;
9219fe70df8SMilanka Ringwald }
9229fe70df8SMilanka Ringwald
packet_handler(uint8_t packet_type,uint16_t channel,uint8_t * packet,uint16_t size)9239fe70df8SMilanka Ringwald static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
9249fe70df8SMilanka Ringwald UNUSED(channel);
9259fe70df8SMilanka Ringwald UNUSED(size);
9269fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
92762545c23SMilanka Ringwald uint8_t event_type = hci_event_packet_get_type(packet);
9289fe70df8SMilanka Ringwald uint16_t con_handle;
9299fe70df8SMilanka Ringwald
9309fe70df8SMilanka Ringwald if (packet_type != HCI_EVENT_PACKET) return;
93162545c23SMilanka Ringwald switch (event_type){
9329d1eff91SMatthias Ringwald case HCI_EVENT_META_GAP:
9339d1eff91SMatthias Ringwald switch (hci_event_gap_meta_get_subevent_code(packet)) {
9349d1eff91SMatthias Ringwald case GAP_SUBEVENT_LE_CONNECTION_COMPLETE:
9359d1eff91SMatthias Ringwald instance->con_handle = gap_subevent_le_connection_complete_get_connection_handle(packet);
9369fe70df8SMilanka Ringwald // print connection parameters (without using float operations)
9379d1eff91SMatthias Ringwald instance->con_interval = gap_subevent_le_connection_complete_get_conn_interval(packet);
9389fe70df8SMilanka Ringwald instance->con_interval_status = CP_CONNECTION_INTERVAL_STATUS_RECEIVED;
9399fe70df8SMilanka Ringwald break;
9409d1eff91SMatthias Ringwald default:
9419d1eff91SMatthias Ringwald break;
9429d1eff91SMatthias Ringwald }
9439d1eff91SMatthias Ringwald break;
9449d1eff91SMatthias Ringwald case HCI_EVENT_LE_META:
9459d1eff91SMatthias Ringwald switch (hci_event_le_meta_get_subevent_code(packet)){
9469b64c21dSMatthias Ringwald #ifdef ENABLE_ATT_DELAYED_RESPONSE
9479fe70df8SMilanka Ringwald case HCI_SUBEVENT_LE_CONNECTION_UPDATE_COMPLETE:
9489fe70df8SMilanka Ringwald if (instance->con_interval_status != CP_CONNECTION_INTERVAL_STATUS_W4_UPDATE) return;
9499fe70df8SMilanka Ringwald
950c1ab6cc1SMatthias Ringwald if ((instance->con_interval > instance->con_interval_max) || (instance->con_interval < instance->con_interval_min)){
9519fe70df8SMilanka Ringwald instance->con_interval = hci_subevent_le_connection_update_complete_get_conn_interval(packet);
9529fe70df8SMilanka Ringwald instance->con_interval_status = CP_CONNECTION_INTERVAL_STATUS_ACCEPTED;
9539fe70df8SMilanka Ringwald } else {
9549fe70df8SMilanka Ringwald instance->con_interval_status = CP_CONNECTION_INTERVAL_STATUS_REJECTED;
9559fe70df8SMilanka Ringwald }
9569fe70df8SMilanka Ringwald att_server_response_ready(l2cap_event_connection_parameter_update_response_get_handle(packet));
9579fe70df8SMilanka Ringwald break;
9589b64c21dSMatthias Ringwald #endif
9599fe70df8SMilanka Ringwald default:
9609fe70df8SMilanka Ringwald break;
9619fe70df8SMilanka Ringwald }
9629fe70df8SMilanka Ringwald break;
9639b64c21dSMatthias Ringwald
9649b64c21dSMatthias Ringwald #ifdef ENABLE_ATT_DELAYED_RESPONSE
9659fe70df8SMilanka Ringwald case L2CAP_EVENT_CONNECTION_PARAMETER_UPDATE_RESPONSE:
9669fe70df8SMilanka Ringwald if (instance->con_interval_status != CP_CONNECTION_INTERVAL_STATUS_W4_L2CAP_RESPONSE) return;
9679fe70df8SMilanka Ringwald
9689fe70df8SMilanka Ringwald if (l2cap_event_connection_parameter_update_response_get_result(packet) == ERROR_CODE_SUCCESS){
9699fe70df8SMilanka Ringwald instance->con_interval_status = CP_CONNECTION_INTERVAL_STATUS_W4_UPDATE;
9709fe70df8SMilanka Ringwald } else {
9719fe70df8SMilanka Ringwald instance->con_interval_status = CP_CONNECTION_INTERVAL_STATUS_REJECTED;
9729fe70df8SMilanka Ringwald att_server_response_ready(l2cap_event_connection_parameter_update_response_get_handle(packet));
9739fe70df8SMilanka Ringwald }
9749fe70df8SMilanka Ringwald break;
9759b64c21dSMatthias Ringwald #endif
9769fe70df8SMilanka Ringwald
9779fe70df8SMilanka Ringwald case HCI_EVENT_DISCONNECTION_COMPLETE:{
9789fe70df8SMilanka Ringwald if (!instance) return;
9799fe70df8SMilanka Ringwald con_handle = hci_event_disconnection_complete_get_connection_handle(packet);
9809fe70df8SMilanka Ringwald if (con_handle == HCI_CON_HANDLE_INVALID) return;
9819fe70df8SMilanka Ringwald
9829fe70df8SMilanka Ringwald instance->masked_measurement_flags = CYCLING_POWER_MEASUREMENT_FLAGS_CLEARED;
9839fe70df8SMilanka Ringwald instance->w4_indication_complete = 0;
9849fe70df8SMilanka Ringwald
9859fe70df8SMilanka Ringwald uint8_t event[5];
9869fe70df8SMilanka Ringwald int index = 0;
98728e59789SMilanka Ringwald event[index++] = HCI_EVENT_GATTSERVICE_META;
9884ea43905SMatthias Ringwald event[index++] = sizeof(event) - 2u;
9899fe70df8SMilanka Ringwald
99028e59789SMilanka Ringwald event[index++] = GATTSERVICE_SUBEVENT_CYCLING_POWER_BROADCAST_STOP;
9919fe70df8SMilanka Ringwald little_endian_store_16(event, index, con_handle);
9929fe70df8SMilanka Ringwald index += 2;
9939fe70df8SMilanka Ringwald (*instance->calibration_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event));
9949fe70df8SMilanka Ringwald
9959fe70df8SMilanka Ringwald break;
9969fe70df8SMilanka Ringwald }
9979fe70df8SMilanka Ringwald case ATT_EVENT_HANDLE_VALUE_INDICATION_COMPLETE:
9989fe70df8SMilanka Ringwald instance->w4_indication_complete = 0;
9999fe70df8SMilanka Ringwald break;
10009fe70df8SMilanka Ringwald default:
10019fe70df8SMilanka Ringwald break;
10029fe70df8SMilanka Ringwald }
10039fe70df8SMilanka Ringwald }
10049fe70df8SMilanka Ringwald
cycling_power_service_server_init(uint32_t feature_flags,cycling_power_pedal_power_balance_reference_t pedal_power_balance_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)10059fe70df8SMilanka Ringwald void cycling_power_service_server_init(uint32_t feature_flags,
10068d927742SMilanka Ringwald cycling_power_pedal_power_balance_reference_t pedal_power_balance_reference, cycling_power_torque_source_t torque_source,
10079fe70df8SMilanka Ringwald cycling_power_sensor_location_t * supported_sensor_locations, uint16_t num_supported_sensor_locations,
10089fe70df8SMilanka Ringwald cycling_power_sensor_location_t current_sensor_location){
10099fe70df8SMilanka Ringwald
10109fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
10119fe70df8SMilanka Ringwald // TODO: remove hardcoded initialization
10129fe70df8SMilanka Ringwald instance->con_interval_min = 6;
10139fe70df8SMilanka Ringwald instance->con_interval_max = 6;
10149fe70df8SMilanka Ringwald instance->con_interval_status = CP_CONNECTION_INTERVAL_STATUS_NONE;
10159fe70df8SMilanka Ringwald instance->w4_indication_complete = 0;
10160b81d2a5SMatthias Ringwald
10179fe70df8SMilanka Ringwald hci_event_callback_registration.callback = &packet_handler;
10189fe70df8SMilanka Ringwald hci_add_event_handler(&hci_event_callback_registration);
10190b81d2a5SMatthias Ringwald
10200b81d2a5SMatthias Ringwald l2cap_event_callback_registration.callback = &packet_handler;
10210b81d2a5SMatthias Ringwald l2cap_add_event_handler(&l2cap_event_callback_registration);
10229fe70df8SMilanka Ringwald
10239fe70df8SMilanka Ringwald instance->sensor_location = current_sensor_location;
10249fe70df8SMilanka Ringwald instance->num_supported_sensor_locations = 0;
10259fe70df8SMilanka Ringwald if (supported_sensor_locations != NULL){
10269fe70df8SMilanka Ringwald instance->num_supported_sensor_locations = num_supported_sensor_locations;
10279fe70df8SMilanka Ringwald instance->supported_sensor_locations = supported_sensor_locations;
10289fe70df8SMilanka Ringwald }
10299fe70df8SMilanka Ringwald
10309fe70df8SMilanka Ringwald instance->feature_flags = feature_flags;
10319fe70df8SMilanka Ringwald instance->default_measurement_flags = CYCLING_POWER_MEASUREMENT_FLAGS_CLEARED;
10329fe70df8SMilanka Ringwald instance->masked_measurement_flags = CYCLING_POWER_MEASUREMENT_FLAGS_CLEARED;
10338d927742SMilanka Ringwald instance->pedal_power_balance_reference = pedal_power_balance_reference;
10349fe70df8SMilanka Ringwald instance->torque_source = torque_source;
10359fe70df8SMilanka Ringwald
10369fe70df8SMilanka Ringwald // get service handle range
10379fe70df8SMilanka Ringwald uint16_t start_handle = 0;
10389fe70df8SMilanka Ringwald uint16_t end_handle = 0xffff;
1039c436b760SMilanka Ringwald int service_found = gatt_server_get_handle_range_for_service_with_uuid16(ORG_BLUETOOTH_SERVICE_CYCLING_POWER, &start_handle, &end_handle);
1040a2489f29SMatthias Ringwald btstack_assert(service_found != 0);
1041a2489f29SMatthias Ringwald UNUSED(service_found);
1042a2489f29SMatthias Ringwald
10439fe70df8SMilanka Ringwald // get CP Mesurement characteristic value handle and client configuration handle
10449fe70df8SMilanka Ringwald instance->measurement_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_CYCLING_POWER_MEASUREMENT);
10459fe70df8SMilanka Ringwald instance->measurement_client_configuration_descriptor_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_CYCLING_POWER_MEASUREMENT);
10469fe70df8SMilanka Ringwald instance->measurement_server_configuration_descriptor_handle = gatt_server_get_server_configuration_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_CYCLING_POWER_MEASUREMENT);
10479fe70df8SMilanka Ringwald
10489fe70df8SMilanka Ringwald // get CP Feature characteristic value handle and client configuration handle
10499fe70df8SMilanka Ringwald instance->feature_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_CYCLING_POWER_FEATURE);
10509fe70df8SMilanka Ringwald // get CP Sensor Location characteristic value handle and client configuration handle
10519fe70df8SMilanka Ringwald instance->sensor_location_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_SENSOR_LOCATION);
10529fe70df8SMilanka Ringwald
10539fe70df8SMilanka Ringwald // get CP Vector characteristic value handle and client configuration handle
10549fe70df8SMilanka Ringwald instance->vector_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_CYCLING_POWER_VECTOR);
10559fe70df8SMilanka Ringwald instance->vector_client_configuration_descriptor_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_CYCLING_POWER_VECTOR);
10569fe70df8SMilanka Ringwald
10579fe70df8SMilanka Ringwald // get Body Sensor Location characteristic value handle and client configuration handle
10589fe70df8SMilanka Ringwald instance->sensor_location_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_SENSOR_LOCATION);
10599fe70df8SMilanka Ringwald
10609fe70df8SMilanka Ringwald // get SP Control Point characteristic value handle and client configuration handle
10619fe70df8SMilanka Ringwald instance->control_point_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_CYCLING_POWER_CONTROL_POINT);
10629fe70df8SMilanka Ringwald instance->control_point_client_configuration_descriptor_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_CYCLING_POWER_CONTROL_POINT);
10639fe70df8SMilanka Ringwald
1064900fc9c6SMilanka Ringwald log_info("Measurement value handle 0x%02x", instance->measurement_value_handle);
1065900fc9c6SMilanka Ringwald log_info("M. Client Cfg value handle 0x%02x", instance->measurement_client_configuration_descriptor_handle);
1066900fc9c6SMilanka Ringwald log_info("M. Server Cfg value handle 0x%02x", instance->measurement_server_configuration_descriptor_handle);
10679fe70df8SMilanka Ringwald
1068900fc9c6SMilanka Ringwald log_info("Feature value handle 0x%02x", instance->feature_value_handle);
1069900fc9c6SMilanka Ringwald log_info("Sensor location value handle 0x%02x", instance->sensor_location_value_handle);
10709fe70df8SMilanka Ringwald
1071900fc9c6SMilanka Ringwald log_info("Vector value handle 0x%02x", instance->vector_value_handle);
1072900fc9c6SMilanka Ringwald log_info("Vector Cfg. value handle 0x%02x", instance->vector_client_configuration_descriptor_handle);
10739fe70df8SMilanka Ringwald
1074900fc9c6SMilanka Ringwald log_info("Control Point value handle 0x%02x", instance->control_point_value_handle);
1075900fc9c6SMilanka Ringwald log_info("Control P. Cfg. value handle 0x%02x", instance->control_point_client_configuration_descriptor_handle);
10769fe70df8SMilanka Ringwald
10779fe70df8SMilanka Ringwald cycling_power_service.start_handle = start_handle;
10789fe70df8SMilanka Ringwald cycling_power_service.end_handle = end_handle;
10799fe70df8SMilanka Ringwald cycling_power_service.read_callback = &cycling_power_service_read_callback;
10809fe70df8SMilanka Ringwald cycling_power_service.write_callback = &cycling_power_service_write_callback;
10819fe70df8SMilanka Ringwald cycling_power_service.packet_handler = &packet_handler;
10829fe70df8SMilanka Ringwald att_server_register_service_handler(&cycling_power_service);
10839fe70df8SMilanka Ringwald }
10849fe70df8SMilanka Ringwald
10859fe70df8SMilanka Ringwald
cycling_power_service_server_add_torque(int16_t torque_Nm)10868d927742SMilanka Ringwald void cycling_power_service_server_add_torque(int16_t torque_Nm){
10879fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
10888d927742SMilanka Ringwald instance->accumulated_torque_Nm += torque_Nm;
10899fe70df8SMilanka Ringwald }
10909fe70df8SMilanka Ringwald
cycling_power_service_server_add_wheel_revolution(int32_t wheel_revolution,uint16_t wheel_event_time_s)10919fe70df8SMilanka Ringwald void cycling_power_service_server_add_wheel_revolution(int32_t wheel_revolution, uint16_t wheel_event_time_s){
10929fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
10939fe70df8SMilanka Ringwald instance->last_wheel_event_time_s = wheel_event_time_s;
10949fe70df8SMilanka Ringwald if (wheel_revolution < 0){
1095c1dbba9dSMatthias Ringwald uint32_t wheel_revolution_to_subtract = (uint32_t) (-wheel_revolution);
1096c1dbba9dSMatthias Ringwald if (instance->cumulative_wheel_revolutions > wheel_revolution_to_subtract){
1097c1dbba9dSMatthias Ringwald instance->cumulative_wheel_revolutions -= wheel_revolution_to_subtract;
10989fe70df8SMilanka Ringwald } else {
10999fe70df8SMilanka Ringwald instance->cumulative_wheel_revolutions = 0;
11009fe70df8SMilanka Ringwald }
11019fe70df8SMilanka Ringwald } else {
11020e588213SMatthias Ringwald if (instance->cumulative_wheel_revolutions < (0xffffffff - wheel_revolution)){
11039fe70df8SMilanka Ringwald instance->cumulative_wheel_revolutions += wheel_revolution;
11049fe70df8SMilanka Ringwald } else {
11059fe70df8SMilanka Ringwald instance->cumulative_wheel_revolutions = 0xffffffff;
11069fe70df8SMilanka Ringwald }
11079fe70df8SMilanka Ringwald }
11089fe70df8SMilanka Ringwald }
11099fe70df8SMilanka Ringwald
cycling_power_service_server_add_crank_revolution(uint16_t crank_revolution,uint16_t crank_event_time_s)11109fe70df8SMilanka Ringwald void cycling_power_service_server_add_crank_revolution(uint16_t crank_revolution, uint16_t crank_event_time_s){
11119fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
11129fe70df8SMilanka Ringwald instance->last_crank_event_time_s = crank_event_time_s;
11139fe70df8SMilanka Ringwald instance->cumulative_crank_revolutions += crank_revolution;
11149fe70df8SMilanka Ringwald }
11159fe70df8SMilanka Ringwald
cycling_power_service_add_energy(uint16_t energy_kJ)11169fe70df8SMilanka Ringwald void cycling_power_service_add_energy(uint16_t energy_kJ){
11179fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
11184ea43905SMatthias Ringwald if (instance->accumulated_energy_kJ <= (0xffffu - energy_kJ)){
11199fe70df8SMilanka Ringwald instance->accumulated_energy_kJ += energy_kJ;
11209fe70df8SMilanka Ringwald } else {
11219fe70df8SMilanka Ringwald instance->accumulated_energy_kJ = 0xffff;
11229fe70df8SMilanka Ringwald }
11239fe70df8SMilanka Ringwald }
11249fe70df8SMilanka Ringwald
cycling_power_service_server_set_instantaneous_power(int16_t instantaneous_power_W)11258d927742SMilanka Ringwald void cycling_power_service_server_set_instantaneous_power(int16_t instantaneous_power_W){
11269fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
11278d927742SMilanka Ringwald instance->instantaneous_power_W = instantaneous_power_W;
11289fe70df8SMilanka Ringwald }
11299fe70df8SMilanka Ringwald
cycling_power_service_server_set_pedal_power_balance(uint8_t pedal_power_balance_percentage)11309fe70df8SMilanka Ringwald void cycling_power_service_server_set_pedal_power_balance(uint8_t pedal_power_balance_percentage){
11319fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
11329fe70df8SMilanka Ringwald instance->pedal_power_balance_percentage = pedal_power_balance_percentage;
11339fe70df8SMilanka Ringwald }
11349fe70df8SMilanka Ringwald
cycling_power_service_server_set_force_magnitude_values(int force_magnitude_count,int16_t * force_magnitude_N_array)11358d927742SMilanka Ringwald void cycling_power_service_server_set_force_magnitude_values(int force_magnitude_count, int16_t * force_magnitude_N_array){
11369fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
11379fe70df8SMilanka Ringwald instance->force_magnitude_count = force_magnitude_count;
11388d927742SMilanka Ringwald instance->vector_instantaneous_force_magnitude_N_array = force_magnitude_N_array;
11399fe70df8SMilanka Ringwald }
11409fe70df8SMilanka Ringwald
cycling_power_service_server_set_torque_magnitude_values(int torque_magnitude_count,int16_t * torque_magnitude_Nm_array)11418d927742SMilanka Ringwald void cycling_power_service_server_set_torque_magnitude_values(int torque_magnitude_count, int16_t * torque_magnitude_Nm_array){
11429fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
11439fe70df8SMilanka Ringwald instance->torque_magnitude_count = torque_magnitude_count;
11448d927742SMilanka Ringwald instance->vector_instantaneous_torque_magnitude_Nm_array = torque_magnitude_Nm_array;
11459fe70df8SMilanka Ringwald }
11469fe70df8SMilanka Ringwald
cycling_power_service_server_set_first_crank_measurement_angle(uint16_t first_crank_measurement_angle_degree)11478d927742SMilanka Ringwald void cycling_power_service_server_set_first_crank_measurement_angle(uint16_t first_crank_measurement_angle_degree){
11489fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
11498d927742SMilanka Ringwald instance->vector_first_crank_measurement_angle_degree = first_crank_measurement_angle_degree;
11509fe70df8SMilanka Ringwald }
11519fe70df8SMilanka Ringwald
cycling_power_service_server_set_instantaneous_measurement_direction(cycling_power_instantaneous_measurement_direction_t direction)11529fe70df8SMilanka Ringwald void cycling_power_service_server_set_instantaneous_measurement_direction(cycling_power_instantaneous_measurement_direction_t direction){
11539fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
11549fe70df8SMilanka Ringwald instance->vector_instantaneous_measurement_direction = direction;
11559fe70df8SMilanka Ringwald }
11569fe70df8SMilanka Ringwald
cycling_power_service_server_set_force_magnitude(int16_t min_force_magnitude_N,int16_t max_force_magnitude_N)11578d927742SMilanka Ringwald void cycling_power_service_server_set_force_magnitude(int16_t min_force_magnitude_N, int16_t max_force_magnitude_N){
11589fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
11598d927742SMilanka Ringwald instance->minimum_force_magnitude_N = min_force_magnitude_N;
11608d927742SMilanka Ringwald instance->maximum_force_magnitude_N = max_force_magnitude_N;
11619fe70df8SMilanka Ringwald }
11629fe70df8SMilanka Ringwald
cycling_power_service_server_set_torque_magnitude(int16_t min_torque_magnitude_Nm,int16_t max_torque_magnitude_Nm)11638d927742SMilanka Ringwald void cycling_power_service_server_set_torque_magnitude(int16_t min_torque_magnitude_Nm, int16_t max_torque_magnitude_Nm){
11649fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
11658d927742SMilanka Ringwald instance->minimum_torque_magnitude_Nm = min_torque_magnitude_Nm;
11668d927742SMilanka Ringwald instance->maximum_torque_magnitude_Nm = max_torque_magnitude_Nm;
11679fe70df8SMilanka Ringwald }
11689fe70df8SMilanka Ringwald
cycling_power_service_server_set_angle(uint16_t min_angle_degree,uint16_t max_angle_degree)11698d927742SMilanka Ringwald void cycling_power_service_server_set_angle(uint16_t min_angle_degree, uint16_t max_angle_degree){
11709fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
11718d927742SMilanka Ringwald instance->minimum_angle_degree = min_angle_degree;
11728d927742SMilanka Ringwald instance->maximum_angle_degree = max_angle_degree;
11739fe70df8SMilanka Ringwald }
11749fe70df8SMilanka Ringwald
cycling_power_service_server_set_top_dead_spot_angle(uint16_t top_dead_spot_angle_degree)11758d927742SMilanka Ringwald void cycling_power_service_server_set_top_dead_spot_angle(uint16_t top_dead_spot_angle_degree){
11769fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
11778d927742SMilanka Ringwald instance->top_dead_spot_angle_degree = top_dead_spot_angle_degree;
11789fe70df8SMilanka Ringwald }
11799fe70df8SMilanka Ringwald
cycling_power_service_server_set_bottom_dead_spot_angle(uint16_t bottom_dead_spot_angle_degree)11808d927742SMilanka Ringwald void cycling_power_service_server_set_bottom_dead_spot_angle(uint16_t bottom_dead_spot_angle_degree){
11819fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
11828d927742SMilanka Ringwald instance->bottom_dead_spot_angle_degree = bottom_dead_spot_angle_degree;
11839fe70df8SMilanka Ringwald }
11849fe70df8SMilanka Ringwald
gatt_date_is_valid(gatt_date_time_t date)11859fe70df8SMilanka Ringwald static int gatt_date_is_valid(gatt_date_time_t date){
11864ea43905SMatthias Ringwald if ((date.year != 0u) && ((date.year < 1582u) || (date.year > 9999u))) return 0u;
11874ea43905SMatthias Ringwald if ((date.month != 0u) && (date.month > 12u)) return 0u;
11884ea43905SMatthias Ringwald if ((date.day != 0u) && (date.day > 31u)) return 0u;
11899fe70df8SMilanka Ringwald
11904ea43905SMatthias Ringwald if (date.hours > 23u) return 0u;
11914ea43905SMatthias Ringwald if (date.minutes > 59u) return 0u;
11924ea43905SMatthias Ringwald if (date.seconds > 59u) return 0u;
11939fe70df8SMilanka Ringwald return 1;
11949fe70df8SMilanka Ringwald }
11959fe70df8SMilanka Ringwald
cycling_power_service_server_set_factory_calibration_date(gatt_date_time_t date)11969fe70df8SMilanka Ringwald int cycling_power_service_server_set_factory_calibration_date(gatt_date_time_t date){
11979fe70df8SMilanka Ringwald if (!gatt_date_is_valid(date)) return 0;
11989fe70df8SMilanka Ringwald
11999fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
12009fe70df8SMilanka Ringwald instance->factory_calibration_date = date;
12019fe70df8SMilanka Ringwald return 1;
12029fe70df8SMilanka Ringwald }
12039fe70df8SMilanka Ringwald
cycling_power_service_server_set_sampling_rate(uint8_t sampling_rate_Hz)12048d927742SMilanka Ringwald void cycling_power_service_server_set_sampling_rate(uint8_t sampling_rate_Hz){
12059fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
12068d927742SMilanka Ringwald instance->sampling_rate_Hz = sampling_rate_Hz;
12079fe70df8SMilanka Ringwald }
12089fe70df8SMilanka Ringwald
12099fe70df8SMilanka Ringwald
cycling_power_service_server_update_values(void)12109fe70df8SMilanka Ringwald void cycling_power_service_server_update_values(void){
12119fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
12129fe70df8SMilanka Ringwald
12139fe70df8SMilanka Ringwald if (instance->measurement_server_configuration_descriptor_broadcast){
12149fe70df8SMilanka Ringwald instance->measurement_broadcast_callback.callback = &cycling_power_service_broadcast_can_send_now;
12159fe70df8SMilanka Ringwald instance->measurement_broadcast_callback.context = (void*) instance;
12169fe70df8SMilanka Ringwald att_server_register_can_send_now_callback(&instance->measurement_broadcast_callback, instance->con_handle);
12179fe70df8SMilanka Ringwald }
12189fe70df8SMilanka Ringwald
12199fe70df8SMilanka Ringwald if (instance->measurement_client_configuration_descriptor_notify){
12209fe70df8SMilanka Ringwald instance->measurement_notify_callback.callback = &cycling_power_service_measurement_can_send_now;
12219fe70df8SMilanka Ringwald instance->measurement_notify_callback.context = (void*) instance;
12229fe70df8SMilanka Ringwald att_server_register_can_send_now_callback(&instance->measurement_notify_callback, instance->con_handle);
12239fe70df8SMilanka Ringwald }
12249fe70df8SMilanka Ringwald
12259fe70df8SMilanka Ringwald if (instance->vector_client_configuration_descriptor_notify){
12269fe70df8SMilanka Ringwald instance->vector_notify_callback.callback = &cycling_power_service_vector_can_send_now;
12279fe70df8SMilanka Ringwald instance->vector_notify_callback.context = (void*) instance;
12289fe70df8SMilanka Ringwald att_server_register_can_send_now_callback(&instance->vector_notify_callback, instance->con_handle);
12299fe70df8SMilanka Ringwald }
12309fe70df8SMilanka Ringwald }
12319fe70df8SMilanka Ringwald
cycling_power_service_server_packet_handler(btstack_packet_handler_t callback)12329fe70df8SMilanka Ringwald void cycling_power_service_server_packet_handler(btstack_packet_handler_t callback){
12339fe70df8SMilanka Ringwald if (callback == NULL){
12349fe70df8SMilanka Ringwald log_error("cycling_power_service_server_packet_handler called with NULL callback");
12359fe70df8SMilanka Ringwald return;
12369fe70df8SMilanka Ringwald }
12379fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
12389fe70df8SMilanka Ringwald instance->calibration_callback = callback;
12399fe70df8SMilanka Ringwald }
12409fe70df8SMilanka Ringwald
cycling_power_server_calibration_done(cycling_power_sensor_measurement_context_t measurement_type,uint16_t calibrated_value)12419fe70df8SMilanka Ringwald void cycling_power_server_calibration_done(cycling_power_sensor_measurement_context_t measurement_type, uint16_t calibrated_value){
12429fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
12439fe70df8SMilanka Ringwald if (instance->response_value != CP_RESPONSE_VALUE_W4_VALUE_AVAILABLE){
12449fe70df8SMilanka Ringwald return;
12459fe70df8SMilanka Ringwald }
12469fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_SUCCESS;
12479fe70df8SMilanka Ringwald
12489fe70df8SMilanka Ringwald switch (measurement_type){
12499fe70df8SMilanka Ringwald case CP_SENSOR_MEASUREMENT_CONTEXT_FORCE:
12508d927742SMilanka Ringwald instance->current_force_magnitude_N = calibrated_value;
12519fe70df8SMilanka Ringwald break;
12529fe70df8SMilanka Ringwald case CP_SENSOR_MEASUREMENT_CONTEXT_TORQUE:
12538d927742SMilanka Ringwald instance->current_torque_magnitude_Nm = calibrated_value;
12549fe70df8SMilanka Ringwald break;
12559fe70df8SMilanka Ringwald default:
12569fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_INVALID_PARAMETER;
12579fe70df8SMilanka Ringwald break;
12589fe70df8SMilanka Ringwald }
12599fe70df8SMilanka Ringwald
12609fe70df8SMilanka Ringwald if (instance->response_value == CP_RESPONSE_VALUE_SUCCESS){
12619fe70df8SMilanka Ringwald switch (calibrated_value){
12629fe70df8SMilanka Ringwald case CP_CALIBRATION_STATUS_INCORRECT_CALIBRATION_POSITION:
12639fe70df8SMilanka Ringwald case CP_CALIBRATION_STATUS_MANUFACTURER_SPECIFIC_ERROR_FOLLOWS:
12649fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_OPERATION_FAILED;
12659fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_OPERATION_FAILED;
12669fe70df8SMilanka Ringwald break;
12679fe70df8SMilanka Ringwald default:
12689fe70df8SMilanka Ringwald break;
12699fe70df8SMilanka Ringwald }
12709fe70df8SMilanka Ringwald }
12719fe70df8SMilanka Ringwald
12729fe70df8SMilanka Ringwald if (instance->control_point_client_configuration_descriptor_indicate){
12739fe70df8SMilanka Ringwald instance->control_point_indicate_callback.callback = &cycling_power_service_response_can_send_now;
12749fe70df8SMilanka Ringwald instance->control_point_indicate_callback.context = (void*) instance;
12759fe70df8SMilanka Ringwald att_server_register_can_send_now_callback(&instance->control_point_indicate_callback, instance->con_handle);
12769fe70df8SMilanka Ringwald }
12779fe70df8SMilanka Ringwald }
12789fe70df8SMilanka Ringwald
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)12799fe70df8SMilanka Ringwald void cycling_power_server_enhanced_calibration_done(cycling_power_sensor_measurement_context_t measurement_type,
12809fe70df8SMilanka Ringwald uint16_t calibrated_value, uint16_t manufacturer_company_id,
12819fe70df8SMilanka Ringwald uint8_t num_manufacturer_specific_data, uint8_t * manufacturer_specific_data){
12829fe70df8SMilanka Ringwald cycling_power_t * instance = &cycling_power;
12839fe70df8SMilanka Ringwald if (instance->response_value != CP_RESPONSE_VALUE_W4_VALUE_AVAILABLE) return;
12849fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_SUCCESS;
12859fe70df8SMilanka Ringwald
12869fe70df8SMilanka Ringwald switch (measurement_type){
12879fe70df8SMilanka Ringwald case CP_SENSOR_MEASUREMENT_CONTEXT_FORCE:
12888d927742SMilanka Ringwald instance->current_force_magnitude_N = calibrated_value;
12899fe70df8SMilanka Ringwald break;
12909fe70df8SMilanka Ringwald case CP_SENSOR_MEASUREMENT_CONTEXT_TORQUE:
12918d927742SMilanka Ringwald instance->current_torque_magnitude_Nm = calibrated_value;
12929fe70df8SMilanka Ringwald break;
12939fe70df8SMilanka Ringwald default:
12949fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_INVALID_PARAMETER;
12959fe70df8SMilanka Ringwald break;
12969fe70df8SMilanka Ringwald }
12979fe70df8SMilanka Ringwald
12989fe70df8SMilanka Ringwald if (instance->response_value == CP_RESPONSE_VALUE_SUCCESS){
12999fe70df8SMilanka Ringwald switch (calibrated_value){
13009fe70df8SMilanka Ringwald case CP_CALIBRATION_STATUS_INCORRECT_CALIBRATION_POSITION:
13019fe70df8SMilanka Ringwald case CP_CALIBRATION_STATUS_MANUFACTURER_SPECIFIC_ERROR_FOLLOWS:
13029fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_OPERATION_FAILED;
13039fe70df8SMilanka Ringwald instance->response_value = CP_RESPONSE_VALUE_OPERATION_FAILED;
13049fe70df8SMilanka Ringwald break;
13059fe70df8SMilanka Ringwald default:
13069fe70df8SMilanka Ringwald break;
13079fe70df8SMilanka Ringwald }
13089fe70df8SMilanka Ringwald instance->manufacturer_company_id = manufacturer_company_id;
13099fe70df8SMilanka Ringwald instance->num_manufacturer_specific_data = num_manufacturer_specific_data;
13109fe70df8SMilanka Ringwald instance->manufacturer_specific_data = manufacturer_specific_data;
13119fe70df8SMilanka Ringwald }
13129fe70df8SMilanka Ringwald
13139fe70df8SMilanka Ringwald if (instance->control_point_client_configuration_descriptor_indicate){
13149fe70df8SMilanka Ringwald instance->control_point_indicate_callback.callback = &cycling_power_service_response_can_send_now;
13159fe70df8SMilanka Ringwald instance->control_point_indicate_callback.context = (void*) instance;
13169fe70df8SMilanka Ringwald att_server_register_can_send_now_callback(&instance->control_point_indicate_callback, instance->con_handle);
13179fe70df8SMilanka Ringwald }
13189fe70df8SMilanka Ringwald }
1319