/********************************** (C) COPYRIGHT ******************************* * File Name : cyclingsensor.c * Author : WCH * Version : V1.0 * Date : 2020/08/06 * Description : Cycling sensor application, initialize the broadcast parameters, * and then broadcast until the host is connected, the time upload speed and step frequency ********************************************************************************* * Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd. * Attention: This software (modified or not) and binary are used for * microcontroller manufactured by Nanjing Qinheng Microelectronics. *******************************************************************************/ /********************************************************************* * INCLUDES */ #include "CONFIG.h" #include "devinfoservice.h" #include "cyclingservice.h" #include "cyclingsensor.h" /********************************************************************* * MACROS */ /********************************************************************* * CONSTANTS */ // Fast advertising interval in 625us units. 625*32 = 20ms (recommended) #define DEFAULT_FAST_ADV_INTERVAL 32 // Duration of fast advertising duration in 625us #define DEFAULT_FAST_ADV_DURATION 20000 // Duration of advertising to white list members only after link termination #define DEFAULT_WHITE_LIST_ADV_DURATION 10000 // Slow advertising interval in 625us units. 625*1704 = 1065ms (recommended) #define DEFAULT_SLOW_ADV_INTERVAL 1704 // Duration of slow advertising duration in 625us (set to 0 for continuous advertising) #define DEFAULT_SLOW_ADV_DURATION 20000 // How often to perform sensor's periodic event (625us) #define DEFAULT_CSC_PERIOD 1000 // Minimum connection interval (units of 1.25ms) #define DEFAULT_DESIRED_MIN_CONN_INTERVAL 20 // Maximum connection interval (units of 1.25ms) #define DEFAULT_DESIRED_MAX_CONN_INTERVAL 200 // Slave latency to use if parameter update request #define DEFAULT_DESIRED_SLAVE_LATENCY 20 // Supervision timeout value (units of 10ms) #define DEFAULT_DESIRED_CONN_TIMEOUT 1000 // Sensor sends a slave security request. #define DEFAULT_PAIRING_PARAMETER GAPBOND_PAIRING_MODE_WAIT_FOR_REQ // Bonded devices' addresses are stored in white list. #define USING_WHITE_LIST FALSE // Request bonding. #define REQ_BONDING TRUE // Time alloted for service discovery before requesting more energy efficient connection parameters #define SVC_DISC_DELAY 5000 // After 15 seconds of no user input with notifications off, terminate connection #define NEGLECT_TIMEOUT_DELAY 24000 // Setting this to true lets this device disconnect after a period of no use. #define USING_NEGLECT_TIMEOUT FALSE // delay for reset of device's bonds, connections, alerts #define CSC_RESET_DELAY 3000 // in (625us) // For simulated measurements #define FLAGS_IDX_MAX 4 // values for simulated measurements. units in revolutions #define CRANK_REV_INCREMENT 1 #define WHEEL_REV_INCREMENT 10 // values for simulated measurements. units 1/1024 seconds. 1024 * (1/1024) = 1 seconds #define WHEEL_EVT_INCREMENT 1024 #define CRANK_EVT_INCREMENT 1024 /********************************************************************* * TYPEDEFS */ /********************************************************************* * GLOBAL VARIABLES */ /********************************************************************* * EXTERNAL VARIABLES */ /********************************************************************* * EXTERNAL FUNCTIONS */ /********************************************************************* * LOCAL VARIABLES */ static uint8_t sensor_TaskID; // Task ID for internal task/event processing static gapRole_States_t gapProfileState = GAPROLE_INIT; static uint8_t sensorUsingWhiteList = FALSE; // GAP Profile - Name attribute for SCAN RSP data static uint8_t scanRspData[] = { // Tx power level 0x02, // length of this data GAP_ADTYPE_POWER_LEVEL, 0 // 0dBm, default Tx power level }; static uint8_t advertData[] = { // flags 0x02, GAP_ADTYPE_FLAGS, GAP_ADTYPE_FLAGS_GENERAL | GAP_ADTYPE_FLAGS_BREDR_NOT_SUPPORTED, // service UUIDs 0x03, GAP_ADTYPE_16BIT_MORE, LO_UINT16(CSC_SERV_UUID), HI_UINT16(CSC_SERV_UUID), 0x0B, // length of this data GAP_ADTYPE_LOCAL_NAME_COMPLETE, 'C', 'S', 'C', ' ', 'S', 'e', 'n', 's', 'o', 'r', }; // Device name attribute value static uint8_t attDeviceName[GAP_DEVICE_NAME_LEN] = "CSC Sensor"; // GAP connection handle static uint16_t gapConnHandle; // Cycling measurement value stored in this structure static attHandleValueNoti_t sensorMeas; // Flags for simulated measurements static const uint8_t sensorFlags[FLAGS_IDX_MAX] = { CSC_FLAGS_SPEED_CADENCE, CSC_FLAGS_AT_REST, CSC_FLAGS_SPEED, CSC_FLAGS_CADENCE }; // Flag index static uint8_t sensorFlagsIdx = 0; // Advertising user-cancelled state static BOOL sensorAdvCancelled = FALSE; // CSC parameters uint32_t cummWheelRevs = 0; uint16_t cummCrankRevs = 0; uint16_t lastWheelEvtTime = 0; uint16_t lastCrankEvtTime = 0; uint16_t mutativeCount = 0; uint8_t sensorLocation = CSC_SENSOR_LOC_OTHER; /********************************************************************* * LOCAL FUNCTIONS */ static void sensor_ProcessTMOSMsg(tmos_event_hdr_t *pMsg); static void SensorGapStateCB(gapRole_States_t newState, gapRoleEvent_t *pEvent); static void sensorPeriodicTask(void); static void sensorMeasNotify(void); static void SensorCB(uint8_t event, uint32_t *pNewCummVal); /********************************************************************* * PROFILE CALLBACKS */ // GAP Role Callbacks static gapRolesCBs_t cyclingPeripheralCB = { SensorGapStateCB, // Profile State Change Callbacks NULL, // When a valid RSSI is read from controller NULL }; // Bond Manager Callbacks static gapBondCBs_t cyclingBondCB = { NULL, // Passcode callback NULL, // Pairing state callback NULL // oob callback }; /********************************************************************* * PUBLIC FUNCTIONS */ /********************************************************************* * @fn CyclingSensor_Init * * @brief Initialization function for the Cycling App Task. * This is called during initialization and should contain * any application specific initialization (ie. hardware * initialization/setup, table initialization, power up * notificaiton ... ). * * @param task_id - the ID assigned by TMOS. This ID should be * used to send messages and set timers. * * @return none */ void CyclingSensor_Init() { sensor_TaskID = TMOS_ProcessEventRegister(CyclingSensor_ProcessEvent); // Setup the GAP Peripheral Role Profile { uint8_t initial_advertising_enable = TRUE; // Set the GAP Role Parameters GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8_t), &initial_advertising_enable); GAPRole_SetParameter(GAPROLE_SCAN_RSP_DATA, sizeof(scanRspData), scanRspData); GAPRole_SetParameter(GAPROLE_ADVERT_DATA, sizeof(advertData), advertData); } // Setup the GAP Bond Manager { uint32_t passkey = 0; // passkey "000000" uint8_t pairMode = DEFAULT_PAIRING_PARAMETER; uint8_t mitm = FALSE; uint8_t ioCap = GAPBOND_IO_CAP_NO_INPUT_NO_OUTPUT; uint8_t bonding = REQ_BONDING; uint8_t autoSync = USING_WHITE_LIST; GAPBondMgr_SetParameter(GAPBOND_PERI_DEFAULT_PASSCODE, sizeof(uint32_t), &passkey); GAPBondMgr_SetParameter(GAPBOND_PERI_PAIRING_MODE, sizeof(uint8_t), &pairMode); GAPBondMgr_SetParameter(GAPBOND_PERI_MITM_PROTECTION, sizeof(uint8_t), &mitm); GAPBondMgr_SetParameter(GAPBOND_PERI_IO_CAPABILITIES, sizeof(uint8_t), &ioCap); GAPBondMgr_SetParameter(GAPBOND_PERI_BONDING_ENABLED, sizeof(uint8_t), &bonding); GAPBondMgr_SetParameter(GAPBOND_AUTO_SYNC_WL, sizeof(uint8_t), &autoSync); } // Initialize GATT attributes GGS_AddService(GATT_ALL_SERVICES); // GAP GATTServApp_AddService(GATT_ALL_SERVICES); // GATT attributes Cycling_AddService(GATT_ALL_SERVICES); DevInfo_AddService(); // Set the GAP Characteristics GGS_SetParameter(GGS_DEVICE_NAME_ATT, sizeof(attDeviceName), attDeviceName); // Register for cycling service callback Cycling_Register(SensorCB); // Setup CSC profile data { uint8_t features = CSC_FULL_SUPPORT; uint8_t sensorLocationCurrent = CSC_SENSOR_LOC_REAR_DROPOUT; uint8_t sensorLocation1 = CSC_SENSOR_LOC_REAR_DROPOUT; uint8_t sensorLocation2 = CSC_SENSOR_LOC_LEFT_CRANK; uint8_t sensorLocation3 = CSC_SENSOR_LOC_REAR_WHEEL; uint8_t sensorLocation4 = CSC_SENSOR_LOC_HIP; // Add available sensor locations Cycling_SetParameter(CSC_AVAIL_SENS_LOCS, 1, &sensorLocation1); Cycling_SetParameter(CSC_AVAIL_SENS_LOCS, 1, &sensorLocation2); Cycling_SetParameter(CSC_AVAIL_SENS_LOCS, 1, &sensorLocation3); Cycling_SetParameter(CSC_AVAIL_SENS_LOCS, 1, &sensorLocation4); // Set sensor location Cycling_SetParameter(CSC_SENS_LOC, 1, &sensorLocationCurrent); // Support all features Cycling_SetParameter(CSC_FEATURE, 1, &features); } // Setup a delayed profile startup tmos_set_event(sensor_TaskID, START_DEVICE_EVT); } /********************************************************************* * @fn CyclingSensor_ProcessEvent * * @brief Cycling Application Task event processor. This function * is called to process all events for the task. Events * include timers, messages and any other user defined events. * * @param task_id - The TMOS assigned task ID. * @param events - events to process. This is a bit map and can * contain more than one event. * * @return events not processed */ uint16_t CyclingSensor_ProcessEvent(uint8_t task_id, uint16_t events) { if(events & SYS_EVENT_MSG) { uint8_t *pMsg; if((pMsg = tmos_msg_receive(sensor_TaskID)) != NULL) { sensor_ProcessTMOSMsg((tmos_event_hdr_t *)pMsg); // Release the TMOS message tmos_msg_deallocate(pMsg); } // return unprocessed events return (events ^ SYS_EVENT_MSG); } if(events & START_DEVICE_EVT) { // Start the Device GAPRole_PeripheralStartDevice(sensor_TaskID, &cyclingBondCB, &cyclingPeripheralCB); return (events ^ START_DEVICE_EVT); } if(events & CSC_PERIODIC_EVT) { // Perform sensor's periodic task sensorPeriodicTask(); return (events ^ CSC_PERIODIC_EVT); } if(events & CSC_CONN_PARAM_UPDATE_EVT) { // Send param update. GAPRole_PeripheralConnParamUpdateReq(gapConnHandle, DEFAULT_DESIRED_MIN_CONN_INTERVAL, DEFAULT_DESIRED_MAX_CONN_INTERVAL, DEFAULT_DESIRED_SLAVE_LATENCY, DEFAULT_DESIRED_CONN_TIMEOUT, sensor_TaskID); // Assuming service discovery complete, start neglect timer if(USING_NEGLECT_TIMEOUT) { tmos_start_task(sensor_TaskID, CSC_NEGLECT_TIMEOUT_EVT, NEGLECT_TIMEOUT_DELAY); } return (events ^ CSC_CONN_PARAM_UPDATE_EVT); } if(events & CSC_NEGLECT_TIMEOUT_EVT) { // No user input, terminate connection. GAPRole_TerminateLink(gapConnHandle); return (events ^ CSC_NEGLECT_TIMEOUT_EVT); } // Discard unknown events return 0; } /********************************************************************* * @fn sensor_ProcessTMOSMsg * * @brief Process an incoming task message. * * @param pMsg - message to process * * @return none */ static void sensor_ProcessTMOSMsg(tmos_event_hdr_t *pMsg) { switch(pMsg->event) { default: break; } } /********************************************************************* * @fn sensorMeasNotify * * @brief Prepare and send a CSC measurement notification * * @return none */ static void sensorMeasNotify(void) { sensorMeas.pValue = GATT_bm_alloc(gapConnHandle, ATT_HANDLE_VALUE_NOTI, CSC_MEAS_LEN, NULL, 0); if(sensorMeas.pValue != NULL) { uint8_t *p = sensorMeas.pValue; uint8_t flags = sensorFlags[sensorFlagsIdx]; // Build CSC measurement structure from simulated values // Flags simulate the isPresent bits. *p++ = flags; // If flag is present, add speed data into measurement. if(flags & CSC_FLAGS_SPEED) { *p++ = BREAK_UINT32(cummWheelRevs, 0); *p++ = BREAK_UINT32(cummWheelRevs, 1); *p++ = BREAK_UINT32(cummWheelRevs, 2); *p++ = BREAK_UINT32(cummWheelRevs, 3); *p++ = LO_UINT16(lastWheelEvtTime); *p++ = HI_UINT16(lastWheelEvtTime); // Update simulated values (simulate in the reverse direction). // Don't allow revolutions to roll over. if(mutativeCount < 10) { mutativeCount++; } else { mutativeCount--; } cummWheelRevs += mutativeCount; lastWheelEvtTime += WHEEL_EVT_INCREMENT; } // If present, add cadence data into measurement. if(flags & CSC_FLAGS_CADENCE) { *p++ = LO_UINT16(cummCrankRevs); *p++ = HI_UINT16(cummCrankRevs); *p++ = LO_UINT16(lastCrankEvtTime); *p++ = HI_UINT16(lastCrankEvtTime); // Update simulated values. cummCrankRevs += CRANK_REV_INCREMENT; lastCrankEvtTime += CRANK_EVT_INCREMENT; } // Get length. sensorMeas.len = (uint8_t)(p - sensorMeas.pValue); // Send to service to send the notification. if(Cycling_MeasNotify(gapConnHandle, &sensorMeas) != SUCCESS) { GATT_bm_free((gattMsg_t *)&sensorMeas, ATT_HANDLE_VALUE_NOTI); } } } /********************************************************************* * @fn SensorGapStateCB * * @brief Notification from the profile of a state change. * * @param newState - new state * * @return none */ static void SensorGapStateCB(gapRole_States_t newState, gapRoleEvent_t *pEvent) { // If connected if(newState == GAPROLE_CONNECTED) { if(pEvent->gap.opcode == GAP_LINK_ESTABLISHED_EVENT) { // Get connection handle gapConnHandle = pEvent->linkCmpl.connectionHandle; // Set timer to update connection parameters // 5 seconds should allow enough time for Service Discovery by the collector to finish tmos_start_task(sensor_TaskID, CSC_CONN_PARAM_UPDATE_EVT, SVC_DISC_DELAY); } } // If disconnected else if(gapProfileState == GAPROLE_CONNECTED && newState != GAPROLE_CONNECTED) { uint8_t advState = TRUE; uint8_t bondCount = 0; // Stop periodic measurement tmos_stop_task(sensor_TaskID, CSC_PERIODIC_EVT); // Reset client characteristic configuration descriptors Cycling_HandleConnStatusCB(gapConnHandle, LINKDB_STATUS_UPDATE_REMOVED); // If not already using white list, begin to do so. GAPBondMgr_GetParameter(GAPBOND_BOND_COUNT, &bondCount); if(USING_WHITE_LIST && sensorUsingWhiteList == FALSE && bondCount > 0) { uint8_t value = GAP_FILTER_POLICY_WHITE; GAPRole_SetParameter(GAPROLE_ADV_FILTER_POLICY, sizeof(uint8_t), &value); sensorUsingWhiteList = TRUE; } // link loss -- use fast advertising GAP_SetParamValue(TGAP_DISC_ADV_INT_MIN, DEFAULT_FAST_ADV_INTERVAL); GAP_SetParamValue(TGAP_DISC_ADV_INT_MAX, DEFAULT_FAST_ADV_INTERVAL); GAP_SetParamValue(TGAP_GEN_DISC_ADV_MIN, DEFAULT_WHITE_LIST_ADV_DURATION); // Enable advertising GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8_t), &advState); } // if advertising stopped else if(gapProfileState == GAPROLE_ADVERTISING && newState == GAPROLE_WAITING) { uint8_t whiteListUsed = FALSE; // if white list is in use, disable to allow general access if(sensorUsingWhiteList == TRUE) { uint8_t value = GAP_FILTER_POLICY_ALL; GAPRole_SetParameter(GAPROLE_ADV_FILTER_POLICY, sizeof(uint8_t), &value); whiteListUsed = TRUE; sensorUsingWhiteList = FALSE; } // if advertising stopped by user if(sensorAdvCancelled) { sensorAdvCancelled = FALSE; } // if fast advertising interrupted to cancel white list else if(((!USING_WHITE_LIST) || whiteListUsed) && (GAP_GetParamValue(TGAP_DISC_ADV_INT_MIN) == DEFAULT_FAST_ADV_INTERVAL)) { uint8_t advState = TRUE; GAP_SetParamValue(TGAP_DISC_ADV_INT_MIN, DEFAULT_FAST_ADV_INTERVAL); GAP_SetParamValue(TGAP_DISC_ADV_INT_MAX, DEFAULT_FAST_ADV_INTERVAL); GAP_SetParamValue(TGAP_GEN_DISC_ADV_MIN, DEFAULT_FAST_ADV_DURATION); GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8_t), &advState); } // if fast advertising switch to slow or if was already slow but using white list. else if(((!USING_WHITE_LIST) || whiteListUsed) || (GAP_GetParamValue(TGAP_DISC_ADV_INT_MIN) == DEFAULT_FAST_ADV_INTERVAL)) { uint8_t advState = TRUE; GAP_SetParamValue(TGAP_DISC_ADV_INT_MIN, DEFAULT_SLOW_ADV_INTERVAL); GAP_SetParamValue(TGAP_DISC_ADV_INT_MAX, DEFAULT_SLOW_ADV_INTERVAL); GAP_SetParamValue(TGAP_GEN_DISC_ADV_MIN, DEFAULT_SLOW_ADV_DURATION); GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8_t), &advState); } } // if started else if(newState == GAPROLE_STARTED) { // Set the system ID from the bd addr uint8_t systemId[DEVINFO_SYSTEM_ID_LEN]; GAPRole_GetParameter(GAPROLE_BD_ADDR, systemId); // shift three bytes up systemId[7] = systemId[5]; systemId[6] = systemId[4]; systemId[5] = systemId[3]; // set middle bytes to zero systemId[4] = 0; systemId[3] = 0; DevInfo_SetParameter(DEVINFO_SYSTEM_ID, DEVINFO_SYSTEM_ID_LEN, systemId); } gapProfileState = newState; } /********************************************************************* * @fn SensorCB * * @brief Callback function for CSC service. * * @param event - service event * @param pNewCummVal - pointer to new wheel revolution data * if specified by event. NULL otherwise. * @return none */ static void SensorCB(uint8_t event, uint32_t *pNewCummVal) { switch(event) { case CSC_CMD_SET_CUMM_VAL: // Cancel neglect timer if(USING_NEGLECT_TIMEOUT) { tmos_stop_task(sensor_TaskID, CSC_NEGLECT_TIMEOUT_EVT); } cummWheelRevs = *pNewCummVal; // Start neglect timer if(USING_NEGLECT_TIMEOUT) { tmos_start_task(sensor_TaskID, CSC_NEGLECT_TIMEOUT_EVT, NEGLECT_TIMEOUT_DELAY); } break; case CSC_CMD_UPDATE_SENS_LOC: // Cancel neglect timer if(USING_NEGLECT_TIMEOUT) { tmos_stop_task(sensor_TaskID, CSC_NEGLECT_TIMEOUT_EVT); } // Get updated sensor location Cycling_GetParameter(CSC_SENS_LOC, &sensorLocation); // Start neglect timer if(USING_NEGLECT_TIMEOUT) { tmos_start_task(sensor_TaskID, CSC_NEGLECT_TIMEOUT_EVT, NEGLECT_TIMEOUT_DELAY); } break; case CSC_MEAS_NOTI_ENABLED: // Cancel neglect timer if(USING_NEGLECT_TIMEOUT) { tmos_stop_task(sensor_TaskID, CSC_NEGLECT_TIMEOUT_EVT); } // If connected start periodic measurement if(gapProfileState == GAPROLE_CONNECTED) { tmos_start_task(sensor_TaskID, CSC_PERIODIC_EVT, DEFAULT_CSC_PERIOD); } break; case CSC_MEAS_NOTI_DISABLED: // Stop periodic measurement tmos_stop_task(sensor_TaskID, CSC_PERIODIC_EVT); // Start neglect timer if(USING_NEGLECT_TIMEOUT) { tmos_start_task(sensor_TaskID, CSC_NEGLECT_TIMEOUT_EVT, NEGLECT_TIMEOUT_DELAY); } break; case CSC_READ_ATTR: case CSC_WRITE_ATTR: if(USING_NEGLECT_TIMEOUT) { // Cancel neglect timer tmos_stop_task(sensor_TaskID, CSC_NEGLECT_TIMEOUT_EVT); // Start neglect timer tmos_start_task(sensor_TaskID, CSC_NEGLECT_TIMEOUT_EVT, NEGLECT_TIMEOUT_DELAY); } break; default: break; } } /********************************************************************* * @fn sensorPeriodicTask * * @brief Perform a periodic cycling application task. * * @param none * * @return none */ static void sensorPeriodicTask(void) { if(gapProfileState == GAPROLE_CONNECTED) { // Send speed and cadence measurement notification sensorMeasNotify(); // Restart timer tmos_start_task(sensor_TaskID, CSC_PERIODIC_EVT, DEFAULT_CSC_PERIOD); } } /********************************************************************* *********************************************************************/