/********************************** (C) COPYRIGHT ******************************* * File Name : runningsensor.c * Author : WCH * Version : V1.0 * Date : 2018/12/12 * Description : Running sensor application, initialize the broadcast * connection parameters, and then broadcast until the * host is connected, and upload the speed regularly ********************************************************************************* * 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 "battservice.h" #include "devinfoservice.h" #include "runningservice.h" #include "runningsensor.h" /********************************************************************* * MACROS */ #define DISTANCE_TRAVELED(speed) ((speed) * ((DEFAULT_RSC_PERIOD) / 1000)) /********************************************************************* * CONSTANTS */ // Fast advertising interval in 625us units #define DEFAULT_FAST_ADV_INTERVAL 48 // 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 #define DEFAULT_SLOW_ADV_INTERVAL 1600 // 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_RSC_PERIOD 2000 // 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 160 // Slave latency to use if parameter update request #define DEFAULT_DESIRED_SLAVE_LATENCY 0 // 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 TRUE // For simulated measurements #define FLAGS_IDX_MAX 4 // Values for simulated measurements. units in revolutions in centimeters #define STRIDE_LENGTH_RUNNING 500 #define STRIDE_LENGTH_WALKING 350 // Cadence = footfalls/minute #define RUNNING_CADENCE 180 #define WALKING_CADENCE 85 // Speed = centimeters/second. #define RUNNING_SPEED 180 #define WALKING_SPEED 50 // Motion that the runner is making #define STANDING_STILL 0 #define WALKING_MOTION 1 #define RUNNING_MOTION 2 #define MOTION_IDX_MAX 3 // Length of running measurement value #define RSC_MEAS_LEN 10 /********************************************************************* * 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[] = { 0x0B, // length of this data GAP_ADTYPE_LOCAL_NAME_COMPLETE, 'R', 'S', 'C', ' ', 'S', 'e', 'n', 's', 'o', 'r', }; 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(RSC_SERV_UUID), HI_UINT16(RSC_SERV_UUID), }; // Device name attribute value static uint8_t attDeviceName[GAP_DEVICE_NAME_LEN] = "RSC Sensor"; // GAP connection handle static uint16_t gapConnHandle; // Running measurement value stored in this structure static attHandleValueNoti_t sensorMeas; // Flags for simulated measurements static const uint8_t sensorFlags[FLAGS_IDX_MAX] = { RSC_FLAGS_ALL, RSC_FLAGS_AT_REST, RSC_FLAGS_STRIDE, RSC_FLAGS_DIST }; // Flag index static uint8_t sensorFlagsIdx = 0; // Advertising user-cancelled state static BOOL sensorAdvCancelled = FALSE; // RSC parameters static uint32_t totalDistance = 0; static uint16_t instSpeed = 0; static uint8_t instCadence = 0; static uint16_t instStrideLength = 0; static uint8_t sensorLocation = RSC_NO_SENSOR_LOC; static uint8_t motion = STANDING_STILL; /********************************************************************* * 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 bStatus_t SensorCB(uint8_t event, uint32_t *pNewCummVal); /********************************************************************* * PROFILE CALLBACKS */ // GAP Role Callbacks static gapRolesCBs_t runningPeripheralCB = { SensorGapStateCB, // Profile State Change Callbacks NULL, // When a valid RSSI is read from controller NULL }; // Bond Manager Callbacks static gapBondCBs_t runningBondCB = { NULL, // Passcode callback NULL, // Pairing state callback NULL // oob callback }; /********************************************************************* * PUBLIC FUNCTIONS */ /********************************************************************* * @fn RunningSensor_Init * * @brief Initialization function for the Running 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 RunningSensor_Init() { sensor_TaskID = TMOS_ProcessEventRegister(RunningSensor_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 Running_AddService(GATT_ALL_SERVICES); DevInfo_AddService(); // Set the GAP Characteristics GGS_SetParameter(GGS_DEVICE_NAME_ATT, sizeof(attDeviceName), attDeviceName); // Register for running service callback Running_Register(SensorCB); // Setup RSC profile data { uint16_t features = RSC_FULL_SUPPORT; uint8_t sensorLocationCurrent = RSC_SENSOR_LOC_0; uint8_t sensorLocation1 = RSC_SENSOR_LOC_0; uint8_t sensorLocation2 = RSC_SENSOR_LOC_1; uint8_t sensorLocation3 = RSC_SENSOR_LOC_2; uint8_t sensorLocation4 = RSC_SENSOR_LOC_3; // Add available sensor locations Running_SetParameter(RSC_AVAIL_SENS_LOCS, sizeof(uint8_t), &sensorLocation1); Running_SetParameter(RSC_AVAIL_SENS_LOCS, sizeof(uint8_t), &sensorLocation2); Running_SetParameter(RSC_AVAIL_SENS_LOCS, sizeof(uint8_t), &sensorLocation3); Running_SetParameter(RSC_AVAIL_SENS_LOCS, sizeof(uint8_t), &sensorLocation4); // Set sensor location Running_SetParameter(RSC_SENS_LOC, sizeof(uint8_t), &sensorLocationCurrent); // Support all features Running_SetParameter(RSC_FEATURE, sizeof(uint16_t), &features); } // Setup a delayed profile startup tmos_set_event(sensor_TaskID, START_DEVICE_EVT); } /********************************************************************* * @fn RunningSensor_ProcessEvent * * @brief Running 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 RunningSensor_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, &runningBondCB, &runningPeripheralCB); return (events ^ START_DEVICE_EVT); } if(events & RSC_PERIODIC_EVT) { // Perform periodic sensor's periodic task sensorPeriodicTask(); return (events ^ RSC_PERIODIC_EVT); } if(events & RSC_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, RSC_NEGLECT_TIMEOUT_EVT, NEGLECT_TIMEOUT_DELAY); } return (events ^ RSC_CONN_PARAM_UPDATE_EVT); } if(events & RSC_NEGLECT_TIMEOUT_EVT) { // No user input, terminate connection. GAPRole_TerminateLink(gapConnHandle); return (events ^ RSC_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 RSC measurement notification * * @return none */ static void sensorMeasNotify(void) { static uint16_t centimeters = 0; sensorMeas.pValue = GATT_bm_alloc(gapConnHandle, ATT_HANDLE_VALUE_NOTI, RSC_MEAS_LEN, NULL, 0); if(sensorMeas.pValue != NULL) { uint8_t *p = sensorMeas.pValue; uint8_t flags = sensorFlags[sensorFlagsIdx]; switch(motion) { case STANDING_STILL: instSpeed = instCadence = instStrideLength = STANDING_STILL; // 0 for walking bit flags = flags & 0xFB; //0b1111 1011 break; case WALKING_MOTION: instStrideLength = STRIDE_LENGTH_WALKING; instCadence = WALKING_CADENCE; instSpeed = WALKING_SPEED; // 0 for walking bit flags = flags & 0xFB; break; case RUNNING_MOTION: instStrideLength = STRIDE_LENGTH_RUNNING; instCadence = RUNNING_CADENCE; instSpeed = RUNNING_SPEED; // set in 1 for walking bit. flags = flags | 0x04; break; default: // Do nothing break; } // Add distance centimeters += (uint16_t)DISTANCE_TRAVELED(instSpeed); // If traveled at least a meter if(centimeters >= 100) { // add distance, truncated to meters totalDistance += (centimeters / 100); // and continue to store the remaining centimeters centimeters %= 100; } // Build RSC measurement structure from simulated values // Flags simulate the isPresent bits. *p++ = flags; //Included regardless of flags. *p++ = LO_UINT16(instSpeed); *p++ = HI_UINT16(instSpeed); *p++ = instCadence; if(flags & RSC_FLAGS_STRIDE) { *p++ = LO_UINT16(instStrideLength); *p++ = HI_UINT16(instStrideLength); } if(flags & RSC_FLAGS_DIST) { *p++ = BREAK_UINT32(totalDistance, 0); *p++ = BREAK_UINT32(totalDistance, 1); *p++ = BREAK_UINT32(totalDistance, 2); *p++ = BREAK_UINT32(totalDistance, 3); } // Get length sensorMeas.len = (uint8_t)(p - sensorMeas.pValue); // set simulated measurement flag index if(++sensorFlagsIdx == FLAGS_IDX_MAX) { sensorFlagsIdx = 0; } // Send to service to send the notification if(Running_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, RSC_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, RSC_PERIODIC_EVT); // reset client characteristic configuration descriptors Running_HandleConnStatusCB(gapConnHandle, LINKDB_STATUS_UPDATE_REMOVED); //Batt_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 RSC service. * * @param event - service event * @param pNewCummVal - pointer to new total distanace data * if specified by event. NULL otherwise. * * @return SUCCESS if operation successful. FAILURE, otherwise. */ static bStatus_t SensorCB(uint8_t event, uint32_t *pNewCummVal) { bStatus_t status = SUCCESS; switch(event) { case RSC_CMD_SET_CUMM_VAL: // Cancel neglect timer if(USING_NEGLECT_TIMEOUT) { tmos_stop_task(sensor_TaskID, RSC_NEGLECT_TIMEOUT_EVT); } // Update total distance totalDistance = *pNewCummVal; // Start neglect timer if(USING_NEGLECT_TIMEOUT) { tmos_start_task(sensor_TaskID, RSC_NEGLECT_TIMEOUT_EVT, NEGLECT_TIMEOUT_DELAY); } break; case RSC_CMD_START_SENS_CALIB: if(sensorFlags[sensorFlagsIdx] != RSC_FLAGS_AT_REST) { // Induce a calibration error for conformance testing status = FAILURE; } break; case RSC_CMD_UPDATE_SENS_LOC: // Cancel neglect timer if(USING_NEGLECT_TIMEOUT) { tmos_stop_task(sensor_TaskID, RSC_NEGLECT_TIMEOUT_EVT); } // Get updated sensor location Running_GetParameter(RSC_SENS_LOC, &sensorLocation); // Start neglect timer if(USING_NEGLECT_TIMEOUT) { tmos_start_task(sensor_TaskID, RSC_NEGLECT_TIMEOUT_EVT, NEGLECT_TIMEOUT_DELAY); } break; case RSC_MEAS_NOTI_ENABLED: // Cancel neglect timer if(USING_NEGLECT_TIMEOUT) { tmos_stop_task(sensor_TaskID, RSC_NEGLECT_TIMEOUT_EVT); } // if connected start periodic measurement if(gapProfileState == GAPROLE_CONNECTED) { tmos_start_task(sensor_TaskID, RSC_PERIODIC_EVT, DEFAULT_RSC_PERIOD); } break; case RSC_MEAS_NOTI_DISABLED: // stop periodic measurement tmos_stop_task(sensor_TaskID, RSC_PERIODIC_EVT); // Start neglect timer if(USING_NEGLECT_TIMEOUT) { tmos_start_task(sensor_TaskID, RSC_NEGLECT_TIMEOUT_EVT, NEGLECT_TIMEOUT_DELAY); } break; case RSC_READ_ATTR: case RSC_WRITE_ATTR: if(USING_NEGLECT_TIMEOUT) { // Cancel neglect timer tmos_stop_task(sensor_TaskID, RSC_NEGLECT_TIMEOUT_EVT); // Start neglect timer tmos_start_task(sensor_TaskID, RSC_NEGLECT_TIMEOUT_EVT, NEGLECT_TIMEOUT_DELAY); } break; default: break; } return (status); } /********************************************************************* * @fn sensorPeriodicTask * * @brief Perform a periodic running 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, RSC_PERIODIC_EVT, DEFAULT_RSC_PERIOD); } } /********************************************************************* *********************************************************************/