/** **************************************************************************************** * @addtogroup CPPC * @{ **************************************************************************************** */ /* * INCLUDE FILES **************************************************************************************** */ #include "rwip_config.h" #if (BLE_CP_COLLECTOR) #include "cpp_common.h" #include "cppc.h" #include "cppc_task.h" #include "ke_timer.h" #include "co_utils.h" #include "ke_mem.h" /* * GLOBAL VARIABLES DECLARATION **************************************************************************************** */ /* * LOCAL FUNCTION DEFINITIONS **************************************************************************************** */ /** **************************************************************************************** * @brief Initialization of the CPPC module. * This function performs all the initializations of the Profile module. * - Creation of database (if it's a service) * - Allocation of profile required memory * - Initialization of task descriptor to register application * - Task State array * - Number of tasks * - Default task handler * * @param[out] env Collector or Service allocated environment data. * @param[in|out] start_hdl Service start handle (0 - dynamically allocated), *only applies for services. * @param[in] app_task Application task number. * @param[in] sec_lvl Security level (AUTH, EKS and MI field of @see enum *attm_value_perm_mask) * @param[in] param Configuration parameters of profile collector or *service (32 bits aligned) * * @return status code to know if profile initialization succeed or not. **************************************************************************************** */ static uint8_t cppc_init(struct prf_task_env *env, uint16_t *start_hdl, uint16_t app_task, uint8_t sec_lvl, void *params) { uint8_t idx; //-------------------- allocate memory required for the profile //--------------------- struct cppc_env_tag *cppc_env = (struct cppc_env_tag *)ke_malloc( sizeof(struct cppc_env_tag), KE_MEM_ATT_DB); // allocate CPPC required environment variable env->env = (prf_env_t *)cppc_env; cppc_env->prf_env.app_task = app_task | (PERM_GET(sec_lvl, SVC_MI) ? PERM(PRF_MI, ENABLE) : PERM(PRF_MI, DISABLE)); cppc_env->prf_env.prf_task = env->task | PERM(PRF_MI, ENABLE); // initialize environment variable env->id = TASK_ID_CPPC; cppc_task_init(&(env->desc)); for (idx = 0; idx < CPPC_IDX_MAX; idx++) { cppc_env->env[idx] = NULL; // service is ready, go into an Idle state ke_state_set(KE_BUILD_ID(env->task, idx), CPPC_FREE); } return GAP_ERR_NO_ERROR; } /** **************************************************************************************** * @brief Clean-up connection dedicated environment parameters * This function performs cleanup of ongoing operations * @param[in|out] env Collector or Service allocated environment data. * @param[in] conidx Connection index * @param[in] reason Detach reason **************************************************************************************** */ static void cppc_cleanup(struct prf_task_env *env, uint8_t conidx, uint8_t reason) { struct cppc_env_tag *cppc_env = (struct cppc_env_tag *)env->env; // clean-up environment variable allocated for task instance if (cppc_env->env[conidx] != NULL) { if (cppc_env->env[conidx]->operation != NULL) { ke_free(ke_param2msg(cppc_env->env[conidx]->operation)); } ke_timer_clear(CPPC_TIMEOUT_TIMER_IND, prf_src_task_get(&cppc_env->prf_env, conidx)); ke_free(cppc_env->env[conidx]); cppc_env->env[conidx] = NULL; } /* Put CPP Client in Free state */ ke_state_set(KE_BUILD_ID(env->task, conidx), CPPC_FREE); } /** **************************************************************************************** * @brief Destruction of the CPPC module - due to a reset for instance. * This function clean-up allocated memory (attribute database is destroyed by *another procedure) * * @param[in|out] env Collector or Service allocated environment data. **************************************************************************************** */ static void cppc_destroy(struct prf_task_env *env) { uint8_t idx; struct cppc_env_tag *cppc_env = (struct cppc_env_tag *)env->env; // cleanup environment variable for each task instances for (idx = 0; idx < CPPC_IDX_MAX; idx++) { cppc_cleanup(env, idx, 0); } // free profile environment variables env->env = NULL; ke_free(cppc_env); } /** **************************************************************************************** * @brief Handles Connection creation * * @param[in|out] env Collector or Service allocated environment data. * @param[in] conidx Connection index **************************************************************************************** */ static void cppc_create(struct prf_task_env *env, uint8_t conidx) { /* Put CPP Client in Idle state */ ke_state_set(KE_BUILD_ID(env->task, conidx), CPPC_IDLE); } /// CPPC Task interface required by profile manager const struct prf_task_cbs cppc_itf = { cppc_init, cppc_destroy, cppc_create, cppc_cleanup, }; /* * GLOBAL FUNCTIONS DEFINITIONS **************************************************************************************** */ const struct prf_task_cbs *cppc_prf_itf_get(void) { return &cppc_itf; } void cppc_enable_rsp_send(struct cppc_env_tag *cppc_env, uint8_t conidx, uint8_t status) { // Send to APP the details of the discovered attributes on CPPS struct cppc_enable_rsp *rsp = KE_MSG_ALLOC( CPPC_ENABLE_RSP, prf_dst_task_get(&(cppc_env->prf_env), conidx), prf_src_task_get(&(cppc_env->prf_env), conidx), cppc_enable_rsp); rsp->status = status; if (status == GAP_ERR_NO_ERROR) { rsp->cps = cppc_env->env[conidx]->cps; // Register CPPC task in gatt for indication/notifications prf_register_atthdl2gatt(&(cppc_env->prf_env), conidx, &(cppc_env->env[conidx]->cps.svc)); // Go to connected state ke_state_set(prf_src_task_get(&(cppc_env->prf_env), conidx), CPPC_IDLE); } ke_msg_send(rsp); } void cppc_send_no_conn_cmp_evt(uint8_t src_id, uint8_t dest_id, uint8_t operation) { // Send the message struct cppc_cmp_evt *evt = KE_MSG_ALLOC(CPPC_CMP_EVT, dest_id, src_id, cppc_cmp_evt); evt->operation = operation; evt->status = PRF_ERR_REQ_DISALLOWED; ke_msg_send(evt); } void cppc_send_cmp_evt(struct cppc_env_tag *cppc_env, uint8_t conidx, uint8_t operation, uint8_t status) { // Free the stored operation if needed if (cppc_env->env[conidx]->operation != NULL) { ke_msg_free(ke_param2msg(cppc_env->env[conidx]->operation)); cppc_env->env[conidx]->operation = NULL; } // Go back to the IDLE state if the state is busy if (ke_state_get(prf_src_task_get(&(cppc_env->prf_env), conidx)) == CPPC_BUSY) { ke_state_set(prf_src_task_get(&cppc_env->prf_env, conidx), CPPC_IDLE); } // Send the message struct cppc_cmp_evt *evt = KE_MSG_ALLOC( CPPC_CMP_EVT, prf_dst_task_get(&(cppc_env->prf_env), conidx), prf_src_task_get(&(cppc_env->prf_env), conidx), cppc_cmp_evt); evt->operation = operation; evt->status = status; ke_msg_send(evt); } /* * LOCAL FUNCTIONS DEFINITIONS **************************************************************************************** */ uint16_t cppc_get_read_handle_req(struct cppc_env_tag *cppc_env, uint8_t conidx, struct cpps_read_cmd *param) { // Attribute Handle uint16_t handle = ATT_INVALID_SEARCH_HANDLE; switch (param->read_code) { // Read CP Feature case (CPPC_RD_CP_FEAT): { handle = cppc_env->env[conidx]->cps.chars[CPP_CPS_FEAT_CHAR].val_hdl; } break; // Read Sensor Location case (CPPC_RD_SENSOR_LOC): { handle = cppc_env->env[conidx]->cps.chars[CPP_CPS_SENSOR_LOC_CHAR].val_hdl; } break; // Read CP Measurement Characteristic Client Char. Cfg. Descriptor Value case (CPPC_RD_WR_CP_MEAS_CL_CFG): { handle = cppc_env->env[conidx]->cps.descs[CPPC_DESC_CP_MEAS_CL_CFG].desc_hdl; } break; // Read CP Measurement Characteristic Server Char. Cfg. Descriptor Value case (CPPC_RD_WR_CP_MEAS_SV_CFG): { handle = cppc_env->env[conidx]->cps.descs[CPPC_DESC_CP_MEAS_SV_CFG].desc_hdl; } break; // Read CP Vector Characteristic Server Char. Cfg. Descriptor Value case (CPPC_RD_WR_VECTOR_CFG): { handle = cppc_env->env[conidx]->cps.descs[CPPC_DESC_VECTOR_CL_CFG].desc_hdl; } break; // Read Unread Alert Characteristic Client Char. Cfg. Descriptor Value case (CPPC_RD_WR_CTNL_PT_CFG): { handle = cppc_env->env[conidx]->cps.descs[CPPC_DESC_CTNL_PT_CL_CFG].desc_hdl; } break; default: { handle = ATT_INVALID_SEARCH_HANDLE; } break; } return handle; } uint8_t cppc_get_write_desc_handle_req(uint8_t conidx, struct cppc_cfg_ntfind_cmd *param, struct cppc_env_tag *cppc_env, uint16_t *handle) { // Status uint8_t status = GAP_ERR_NO_ERROR; switch (param->desc_code) { // Write CP Measurement Characteristic Client Char. Cfg. Descriptor Value case (CPPC_RD_WR_CP_MEAS_CL_CFG): { if (param->ntfind_cfg <= PRF_CLI_START_NTF) { *handle = cppc_env->env[conidx]->cps.descs[CPPC_DESC_CP_MEAS_CL_CFG].desc_hdl; // The descriptor is mandatory ASSERT_ERR(*handle != ATT_INVALID_SEARCH_HANDLE); } else { status = PRF_ERR_INVALID_PARAM; } } break; // Write CP Measurement Characteristic Server Char. Cfg. Descriptor Value case (CPPC_RD_WR_CP_MEAS_SV_CFG): { if (param->ntfind_cfg <= PRF_SRV_START_BCST) { *handle = cppc_env->env[conidx]->cps.descs[CPPC_DESC_CP_MEAS_SV_CFG].desc_hdl; if (*handle == ATT_INVALID_SEARCH_HANDLE) { // The descriptor has not been found. status = PRF_ERR_INEXISTENT_HDL; } } else { status = PRF_ERR_INVALID_PARAM; } } break; // Write CP Vector Characteristic Client Char. Cfg. Descriptor Value case (CPPC_RD_WR_VECTOR_CFG): { if (param->ntfind_cfg <= PRF_CLI_START_NTF) { *handle = cppc_env->env[conidx]->cps.descs[CPPC_DESC_VECTOR_CL_CFG].desc_hdl; if (*handle == ATT_INVALID_SEARCH_HANDLE) { // The descriptor has not been found. status = PRF_ERR_INEXISTENT_HDL; } } else { status = PRF_ERR_INVALID_PARAM; } } break; // Write SC Control Point Characteristic Client Char. Cfg. Descriptor Value case (CPPC_RD_WR_CTNL_PT_CFG): { if ((param->ntfind_cfg == PRF_CLI_STOP_NTFIND) || (param->ntfind_cfg == PRF_CLI_START_IND)) { *handle = cppc_env->env[conidx]->cps.descs[CPPC_DESC_CTNL_PT_CL_CFG].desc_hdl; if (*handle == ATT_INVALID_SEARCH_HANDLE) { // The descriptor has not been found. status = PRF_ERR_INEXISTENT_HDL; } } else { status = PRF_ERR_INVALID_PARAM; } } break; default: { status = PRF_ERR_INVALID_PARAM; } break; } return (status); } uint8_t cppc_unpack_meas_ind(uint8_t conidx, struct gattc_event_ind const *param, struct cppc_env_tag *cppc_env) { // CP Measurement value has been received struct cppc_value_meas_ind *ind = KE_MSG_ALLOC( CPPC_VALUE_IND, prf_dst_task_get(&(cppc_env->prf_env), conidx), prf_src_task_get(&(cppc_env->prf_env), conidx), cppc_value_meas_ind); // Offset uint8_t offset = CPP_CP_MEAS_NTF_MIN_LEN; // Attribute code ind->att_code = CPPC_NTF_CP_MEAS; // Flags ind->value.cp_meas.flags = co_read16p(¶m->value[0]); // Instant power ind->value.cp_meas.inst_power = co_read16p(¶m->value[2]); if (ind->value.cp_meas.flags & CPP_MEAS_PEDAL_POWER_BALANCE_PRESENT) { // Unpack Pedal Power Balance info ind->value.cp_meas.pedal_power_balance = param->value[offset]; offset++; } if (ind->value.cp_meas.flags & CPP_MEAS_ACCUM_TORQUE_PRESENT) { // Unpack Accumulated Torque info ind->value.cp_meas.accum_torque = co_read16p(¶m->value[offset]); offset += 2; } if (ind->value.cp_meas.flags & CPP_MEAS_WHEEL_REV_DATA_PRESENT) { // Unpack Wheel Revolution Data (Cumulative Wheel & Last Wheel Event Time) ind->value.cp_meas.cumul_wheel_rev = co_read32p(¶m->value[offset]); offset += 4; ind->value.cp_meas.last_wheel_evt_time = co_read16p(¶m->value[offset]); offset += 2; } if (ind->value.cp_meas.flags & CPP_MEAS_CRANK_REV_DATA_PRESENT) { // Unpack Crank Revolution Data (Cumulative Crank & Last Crank Event Time) ind->value.cp_meas.cumul_crank_rev = co_read16p(¶m->value[offset]); offset += 2; ind->value.cp_meas.last_crank_evt_time = co_read16p(¶m->value[offset]); offset += 2; } if (ind->value.cp_meas.flags & CPP_MEAS_EXTREME_FORCE_MAGNITUDES_PRESENT) { // Unpack Extreme Force Magnitudes (Maximum Force Magnitude & Minimum Force // Magnitude) ind->value.cp_meas.max_force_magnitude = co_read16p(¶m->value[offset]); offset += 2; ind->value.cp_meas.min_force_magnitude = co_read16p(¶m->value[offset]); offset += 2; } else if (ind->value.cp_meas.flags & CPP_MEAS_EXTREME_TORQUE_MAGNITUDES_PRESENT) { // Unpack Extreme Force Magnitudes (Maximum Force Magnitude & Minimum Force // Magnitude) ind->value.cp_meas.max_torque_magnitude = co_read16p(¶m->value[offset]); offset += 2; ind->value.cp_meas.min_torque_magnitude = co_read16p(¶m->value[offset]); offset += 2; } if (ind->value.cp_meas.flags & CPP_MEAS_EXTREME_ANGLES_PRESENT) { // Unpack Extreme Angles (Maximum Angle & Minimum Angle) uint32_t angle = co_read24p(¶m->value[offset]); offset += 3; // Force to 12 bits ind->value.cp_meas.max_angle = (angle & (0x0FFF)); ind->value.cp_meas.min_angle = ((angle >> 12) & 0x0FFF); } if (ind->value.cp_meas.flags & CPP_MEAS_TOP_DEAD_SPOT_ANGLE_PRESENT) { // Unpack Top Dead Spot Angle ind->value.cp_meas.top_dead_spot_angle = co_read16p(¶m->value[offset]); offset += 2; } if (ind->value.cp_meas.flags & CPP_MEAS_BOTTOM_DEAD_SPOT_ANGLE_PRESENT) { // Unpack Bottom Dead Spot Angle ind->value.cp_meas.bot_dead_spot_angle = co_read16p(¶m->value[offset]); offset += 2; } if (ind->value.cp_meas.flags & CPP_MEAS_ACCUM_ENERGY_PRESENT) { // Unpack Accumulated Energy ind->value.cp_meas.accum_energy = co_read16p(¶m->value[offset]); offset += 2; } // Send the message ke_msg_send(ind); return offset; } uint8_t cppc_unpack_vector_ind(uint8_t conidx, struct gattc_event_ind const *param, struct cppc_env_tag *cppc_env) { // CP Measurement value has been received struct cppc_value_ind *ind = KE_MSG_ALLOC_DYN( CPPC_VALUE_IND, prf_dst_task_get(&cppc_env->prf_env, conidx), prf_src_task_get(&cppc_env->prf_env, conidx), cppc_value_ind, param->length); // Offset uint8_t offset = CPP_CP_VECTOR_MIN_LEN; // Attribute code ind->att_code = CPPC_NTF_CP_VECTOR; // Flags ind->value.cp_vector.flags = param->value[0]; if (ind->value.cp_vector.flags & CPP_VECTOR_CRANK_REV_DATA_PRESENT) { // Unpack Crank Revolution Data ind->value.cp_vector.cumul_crank_rev = co_read16p(¶m->value[offset]); offset += 2; // Unpack Last Crank Evt time ind->value.cp_vector.last_crank_evt_time = co_read16p(¶m->value[offset]); offset += 2; } if (ind->value.cp_vector.flags & CPP_VECTOR_FIRST_CRANK_MEAS_ANGLE_PRESENT) { // Unpack First Crank Measurement Angle ind->value.cp_vector.first_crank_meas_angle = co_read16p(¶m->value[offset]); offset += 2; } if (!(ind->value.cp_vector.flags & CPP_VECTOR_INST_FORCE_MAGNITUDE_ARRAY_PRESENT) != !(ind->value.cp_vector.flags & CPP_VECTOR_INST_TORQUE_MAGNITUDE_ARRAY_PRESENT)) { // Unpack Force or Torque magnitude (mutually excluded) ind->value.cp_vector.nb = (param->length - offset) / 2; if (ind->value.cp_vector.nb) { for (int i = 0; i < ind->value.cp_vector.nb; i++) { // Handle the array buffer to extract parameters ind->value.cp_vector.force_torque_magnitude[i] = co_read16p(¶m->value[offset]); offset += 2; } } } // if ((ind->value.cp_vector.flags & CPP_VECTOR_INST_MEAS_DIRECTION_LSB) || // (ind->value.cp_vector.flags & // CPP_VECTOR_INST_MEAS_DIRECTION_MSB)) // { // // } // Send the message ke_msg_send(ind); return offset; } uint8_t cppc_pack_ctnl_pt_req(struct cppc_ctnl_pt_cfg_req *param, uint8_t *req, uint8_t *status) { // Request Length uint8_t req_len = CPP_CP_CNTL_PT_REQ_MIN_LEN; // Set the operation code req[0] = param->ctnl_pt.op_code; // Fulfill the message according to the operation code switch (param->ctnl_pt.op_code) { case (CPP_CTNL_PT_SET_CUMUL_VAL): { // Set the cumulative value co_write32p(&req[req_len], param->ctnl_pt.value.cumul_val); // Update length req_len += 4; } break; case (CPP_CTNL_PT_UPD_SENSOR_LOC): { // Set the sensor location req[req_len] = param->ctnl_pt.value.sensor_loc; // Update length req_len++; } break; case (CPP_CTNL_PT_SET_CRANK_LENGTH): { // Set the crank length co_write16p(&req[req_len], param->ctnl_pt.value.crank_length); // Update length req_len += 2; } break; case (CPP_CTNL_PT_SET_CHAIN_LENGTH): { // Set the chain length co_write16p(&req[req_len], param->ctnl_pt.value.chain_length); // Update length req_len += 2; } break; case (CPP_CTNL_PT_SET_CHAIN_WEIGHT): { // Set the chain weight co_write16p(&req[req_len], param->ctnl_pt.value.chain_weight); // Update length req_len += 2; } break; case (CPP_CTNL_PT_SET_SPAN_LENGTH): { // Set the span length co_write16p(&req[req_len], param->ctnl_pt.value.span_length); // Update length req_len += 2; } break; case (CPP_CTNL_MASK_CP_MEAS_CH_CONTENT): { // Set the Content Mask co_write16p(&req[req_len], param->ctnl_pt.value.mask_content); // Update length req_len += 2; } break; case (CPP_CTNL_PT_REQ_SUPP_SENSOR_LOC): case (CPP_CTNL_PT_REQ_CRANK_LENGTH): case (CPP_CTNL_PT_REQ_CHAIN_LENGTH): case (CPP_CTNL_PT_REQ_CHAIN_WEIGHT): case (CPP_CTNL_PT_REQ_SPAN_LENGTH): case (CPP_CTNL_PT_START_OFFSET_COMP): case (CPP_CTNL_REQ_SAMPLING_RATE): case (CPP_CTNL_REQ_FACTORY_CALIBRATION_DATE): { // Nothing more to do } break; default: { *status = PRF_ERR_INVALID_PARAM; } break; } return req_len; } uint8_t cppc_unpack_ctln_pt_ind(uint8_t conidx, struct gattc_event_ind const *param, struct cppc_env_tag *cppc_env) { // Offset uint8_t offset = CPP_CP_CNTL_PT_RSP_MIN_LEN; // Control Point value has been received struct cppc_ctnl_pt_rsp *ind = KE_MSG_ALLOC( CPPC_CTNL_PT_RSP, prf_dst_task_get(&cppc_env->prf_env, conidx), prf_src_task_get(&cppc_env->prf_env, conidx), cppc_ctnl_pt_rsp); // Requested operation code ind->rsp.req_op_code = param->value[1]; // Response value ind->rsp.resp_value = param->value[2]; if ((ind->rsp.resp_value == CPP_CTNL_PT_RESP_SUCCESS) && (param->length >= 3)) { switch (ind->rsp.req_op_code) { case (CPP_CTNL_PT_REQ_SUPP_SENSOR_LOC): { // Get the number of supported locations that have been received uint8_t nb_supp_loc = (param->length - 3); // Location uint8_t loc; for (uint8_t counter = 0; counter < nb_supp_loc; counter++) { loc = param->value[counter + CPP_CP_CNTL_PT_RSP_MIN_LEN]; // Check if valid if (loc < CPP_LOC_MAX) { ind->rsp.value.supp_loc |= (1 << loc); } offset++; } } break; case (CPP_CTNL_PT_REQ_CRANK_LENGTH): { ind->rsp.value.crank_length = co_read16p(¶m->value[offset]); offset += 2; } break; case (CPP_CTNL_PT_REQ_CHAIN_LENGTH): { ind->rsp.value.chain_length = co_read16p(¶m->value[offset]); offset += 2; } break; case (CPP_CTNL_PT_REQ_CHAIN_WEIGHT): { ind->rsp.value.chain_weight = co_read16p(¶m->value[offset]); offset += 2; } break; case (CPP_CTNL_PT_REQ_SPAN_LENGTH): { ind->rsp.value.span_length = co_read16p(¶m->value[offset]); offset += 2; } break; case (CPP_CTNL_PT_START_OFFSET_COMP): { ind->rsp.value.offset_comp = co_read16p(¶m->value[offset]); offset += 2; } break; case (CPP_CTNL_REQ_SAMPLING_RATE): { ind->rsp.value.sampling_rate = param->value[offset]; offset++; } break; case (CPP_CTNL_REQ_FACTORY_CALIBRATION_DATE): { offset += prf_unpack_date_time((uint8_t *)&(param->value[offset]), &(ind->rsp.value.factory_calibration)); } break; case (CPP_CTNL_PT_SET_CUMUL_VAL): case (CPP_CTNL_PT_UPD_SENSOR_LOC): case (CPP_CTNL_PT_SET_CRANK_LENGTH): case (CPP_CTNL_PT_SET_CHAIN_LENGTH): case (CPP_CTNL_PT_SET_CHAIN_WEIGHT): case (CPP_CTNL_PT_SET_SPAN_LENGTH): case (CPP_CTNL_MASK_CP_MEAS_CH_CONTENT): { // No parameters } break; default: { } break; } } // Send the message ke_msg_send(ind); return offset; } #endif //(BLE_CP_COLLECTOR) /// @} CPP