コード例 #1
0
/*********************************************************************
 * @fn      oadManagerCCCDiscovery
 *
 * @brief   OAD Client Characteristic Configuration discovery.
 *
 * @return  none
 */
static void oadManagerCCCDiscovery(void)
{
  // Initialize CCCD discovery variable
  oadManagerCCCIdx = 0;

  // Discover characteristic descriptors
  if (GATT_DiscAllCharDescs(oadManagerConnHandle, oadSvcStartHdl,
                            oadSvcEndHdl, oadManagerTaskId) != SUCCESS)
  {
    (void)osal_set_event(oadManagerTaskId, CCC_DISCOVERY_EVT);
  }
}
コード例 #2
0
/*********************************************************************
 * @fn      SimpleServiceDiscovery_discoverService
 *
 * @brief   Perform discovery of the given simpleService_t structure.
 *          This function is to be called inside the GATT discovery event
 *          callback to process the service discovery.
 *
 * @param   connHandle    - connection handle
 *          entity        - ICall entity of the calling task
 *          *service      - pointer to the service struct
 *          *pMsg         - pointer to the received gattMsgEvent_t
 *
 * @return  SIMPLE_DISCOVERY_SUCCESSFUL, SIMPLE_DISCOVERY_FINDING_SERVICE,
 *          SIMPLE_DISCOVERY_FINDING_CHAR or SIMPLE_DISCOVERY_UNSUCCESSFUL.
 */
uint32_t SimpleServiceDiscovery_discoverService(uint16_t connHandle, ICall_EntityID entity,
                                                simpleService_t *service, gattMsgEvent_t *pMsg)
{
    uint32_t retVal = 0;

    switch (discoveryState) {
    case BLE_DISC_STATE_IDLE:
    {
        discoveryState = BLE_DISC_STATE_SVC;

        // Discovery the service
        GATT_DiscPrimaryServiceByUUID(connHandle, service->uuid.uuid, service->uuid.len,
                                      entity);

        retVal = SIMPLE_DISCOVERY_FINDING_SERVICE;
        break;
    }
    case BLE_DISC_STATE_SVC:
    {
        // Service found, store handles
        if (pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP &&
            pMsg->msg.findByTypeValueRsp.numInfo > 0)
        {
          service->startHandle = ATT_ATTR_HANDLE(pMsg->msg.findByTypeValueRsp.pHandlesInfo, 0);
          service->endHandle = ATT_GRP_END_HANDLE(pMsg->msg.findByTypeValueRsp.pHandlesInfo, 0);
        }

        // If procedure complete
        if (((pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP) &&
             (pMsg->hdr.status == bleProcedureComplete))  ||
            (pMsg->method == ATT_ERROR_RSP))
        {
          if (service->startHandle != 0)
          {
            discoveryState = BLE_DISC_STATE_CHAR;
            GATT_DiscAllCharDescs(connHandle, service->startHandle, service->endHandle, entity);
            retVal = SIMPLE_DISCOVERY_FINDING_CHAR;
          }
          else
          {
            discoveryState = BLE_DISC_STATE_IDLE;
            retVal = SIMPLE_DISCOVERY_UNSUCCESSFUL;
          }
        }
        break;
    }
    case BLE_DISC_STATE_CHAR:
    {
        // Characteristic found, store handle
        if (pMsg->method == ATT_FIND_INFO_RSP)
        {

            if (pMsg->msg.findInfoRsp.numInfo > 0)
            {
              SimpleServiceDiscovery_processFindInfoRsp(pMsg->msg.findInfoRsp, service);
            }

            if (pMsg->hdr.status == bleProcedureComplete)
            {
              discoveryState = BLE_DISC_STATE_IDLE;
              retVal = SIMPLE_DISCOVERY_SUCCESSFUL;
            }
        }
        break;
    }
    default:
        break;
    }

    return retVal;
}
コード例 #3
0
ファイル: glucose_discovery.c プロジェクト: victor-zheng/BLE
/*********************************************************************
 * @fn      GlucoseDisc
 *
 * @brief   Current glucose service and characteristic discovery. 
 *
 * @param   state - Discovery state.
 * @param   pMsg - GATT message.
 *
 * @return  New discovery state.
 */
static uint8_t GlucoseDisc(uint8_t state, gattMsgEvent_t *pMsg)
{
  uint8_t newState = state;
  
  switch (state)
  {
    case DISC_GLUCOSE_START:  
      {
        uint8_t uuid[ATT_BT_UUID_SIZE] = { LO_UINT16(GLUCOSE_SERV_UUID),
                                           HI_UINT16(GLUCOSE_SERV_UUID) };

        // Initialize service discovery variables
        glucoseSvcStartHdl = glucoseSvcEndHdl = 0;
        glucoseEndHdlIdx = 0;
        
        // Discover service by UUID
        GATT_DiscPrimaryServiceByUUID(glucCollConnHandle, uuid,
                                      ATT_BT_UUID_SIZE, glucCollTaskId);      

        newState = DISC_GLUCOSE_SVC;
      } 
      break;

    case DISC_GLUCOSE_SVC:
      // Service found, store handles
      if (pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP &&
          pMsg->msg.findByTypeValueRsp.numInfo > 0)
      {
        glucoseSvcStartHdl = 
          ATT_ATTR_HANDLE(pMsg->msg.findByTypeValueRsp.pHandlesInfo, 0);
        glucoseSvcEndHdl = 
          ATT_GRP_END_HANDLE(pMsg->msg.findByTypeValueRsp.pHandlesInfo, 0);
      }
      
      // If procedure complete
      if ((pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP  && 
           pMsg->hdr.status == bleProcedureComplete) ||
          (pMsg->method == ATT_ERROR_RSP))
      {
        // If service found
        if (glucoseSvcStartHdl != 0)
        {
          // Discover all characteristics
          GATT_DiscAllChars(glucCollConnHandle, glucoseSvcStartHdl,
                            glucoseSvcEndHdl, glucCollTaskId);
          
          newState = DISC_GLUCOSE_CHAR;
        }
        else
        {
          // Service not found
          newState = DISC_FAILED;
        }
      }
      break;

    case DISC_GLUCOSE_CHAR:
      {
        uint8_t   i;
        uint8_t   *p;
        uint16_t  handle;
        uint16_t  uuid;

        // Characteristics found
        if (pMsg->method == ATT_READ_BY_TYPE_RSP &&
            pMsg->msg.readByTypeRsp.numPairs > 0 && 
            pMsg->msg.readByTypeRsp.len == CHAR_DESC_HDL_UUID16_LEN)
        {
          // For each characteristic declaration
          p = pMsg->msg.readByTypeRsp.pDataList;

          for (i = pMsg->msg.readByTypeRsp.numPairs; i > 0; i--)
          {
            // Parse characteristic declaration
            handle = BUILD_UINT16(p[3], p[4]);
            uuid = BUILD_UINT16(p[5], p[6]);

            // If looking for end handle
            if (glucoseEndHdlIdx != 0)
            {
              // End handle is one less than handle of characteristic declaration
              glucoseHdlCache[glucoseEndHdlIdx] = BUILD_UINT16(p[0], p[1]) - 1;
              glucoseEndHdlIdx = 0;
            }

            // If UUID is of interest, store handle
            switch (uuid)
            {
              case GLUCOSE_MEAS_UUID:
                glucoseHdlCache[HDL_GLUCOSE_START] = handle;
                glucoseEndHdlIdx = HDL_GLUCOSE_END;
                break;

              case GLUCOSE_CONTEXT_UUID:
                glucoseHdlCache[HDL_GLUCOSE_CONTEXT_START] = handle;
                glucoseEndHdlIdx = HDL_GLUCOSE_CONTEXT_END;
                break;

              case RECORD_CTRL_PT_UUID:
                glucoseHdlCache[HDL_GLUCOSE_CTL_PNT_START] = handle;
                glucoseEndHdlIdx = HDL_GLUCOSE_CTL_PNT_END;
                break;                
                
              case GLUCOSE_FEATURE_UUID:
                glucoseHdlCache[HDL_GLUCOSE_FEATURE] = handle;
                break;
                
              default:
                break;
            }
            
            p += CHAR_DESC_HDL_UUID16_LEN;
          }         
        }
          
        // If procedure complete
        if ((pMsg->method == ATT_READ_BY_TYPE_RSP  && 
             pMsg->hdr.status == bleProcedureComplete) ||
            (pMsg->method == ATT_ERROR_RSP))
        {
          // Special case of end handle at end of service
          if (glucoseEndHdlIdx != 0)
          {
            glucoseHdlCache[glucoseEndHdlIdx] = glucoseSvcEndHdl;
            glucoseEndHdlIdx = 0;
          }
          
          // If didn't find glucose characteristic
          if (glucoseHdlCache[HDL_GLUCOSE_START] == 0)
          {
            newState = DISC_FAILED;
          }
          else if (glucoseHdlCache[HDL_GLUCOSE_START] <
                   glucoseHdlCache[HDL_GLUCOSE_END])
          {
            // Discover characteristic descriptors
            GATT_DiscAllCharDescs(glucCollConnHandle,
                                  glucoseHdlCache[HDL_GLUCOSE_START] + 1,
                                  glucoseHdlCache[HDL_GLUCOSE_END],
                                  glucCollTaskId);
                                        
            newState = DISC_GLUCOSE_CCCD;
          }
          else
          {
            newState = DISC_IDLE;
          }
        }
      }      
      break;

    case DISC_GLUCOSE_CCCD:
      {
        uint8_t i;
        
        // Characteristic descriptors found
        if (pMsg->method == ATT_FIND_INFO_RSP &&
            pMsg->msg.findInfoRsp.numInfo > 0 && 
            pMsg->msg.findInfoRsp.format == ATT_HANDLE_BT_UUID_TYPE)
        {
          attFindInfoRsp_t *pRsp = &(pMsg->msg.findInfoRsp);
                      
          // For each handle/uuid pair
          for (i = 0; i < pRsp->numInfo; i++)
          {           
            // Look for CCCD
            if ( ATT_BT_PAIR_UUID( pRsp->pInfo, i ) == GATT_CLIENT_CHAR_CFG_UUID )
            {
              // CCCD found
              glucoseHdlCache[HDL_GLUCOSE_MEAS_CCCD] = 
                ATT_BT_PAIR_HANDLE( pRsp->pInfo, i );
              
              break;
            }
          }
        }
        
        // If procedure complete
        if ((pMsg->method == ATT_FIND_INFO_RSP  && 
             pMsg->hdr.status == bleProcedureComplete) ||
            (pMsg->method == ATT_ERROR_RSP))
        {
          // If CCCD found
          if (glucoseHdlCache[HDL_GLUCOSE_MEAS_CCCD] != 0)
          {
            // Should we look for unread category status CCCD
            if (glucoseHdlCache[HDL_GLUCOSE_CTL_PNT_START] <
                glucoseHdlCache[HDL_GLUCOSE_CTL_PNT_END])
            {
              // Discover unread category status characteristic descriptors
              GATT_DiscAllCharDescs(glucCollConnHandle,
                                    glucoseHdlCache[HDL_GLUCOSE_CTL_PNT_START] + 1,
                                    glucoseHdlCache[HDL_GLUCOSE_CTL_PNT_END],
                                    glucCollTaskId);
                                          
              newState = DISC_GLUCOSE_CTL_PNT_CCCD;
            }
            else
            {
              // Missing required characteristic
              newState = DISC_FAILED;
            }
          }
          else
          {
            // Missing required characteristic descriptor
            glucoseHdlCache[HDL_GLUCOSE_MEAS_CCCD] = 0;
            newState = DISC_FAILED;
          }          
        }
      }
      break;
         
   case DISC_GLUCOSE_CTL_PNT_CCCD:
      {
        uint8_t i;
        
        // Characteristic descriptors found
        if (pMsg->method == ATT_FIND_INFO_RSP &&
            pMsg->msg.findInfoRsp.numInfo > 0 && 
            pMsg->msg.findInfoRsp.format == ATT_HANDLE_BT_UUID_TYPE)
        {
          attFindInfoRsp_t *pRsp = &(pMsg->msg.findInfoRsp);
          
          // For each handle/uuid pair
          for (i = 0; i < pRsp->numInfo; i++)
          {
            // Look for CCCD
            if ( ATT_BT_PAIR_UUID( pRsp->pInfo, i ) == GATT_CLIENT_CHAR_CFG_UUID )
            {
              // CCCD found
              glucoseHdlCache[HDL_GLUCOSE_CTL_PNT_CCCD] =
                ATT_BT_PAIR_HANDLE( pRsp->pInfo, i );
              
              break;
            }
          }
        }
        
        // If procedure complete
        if ((pMsg->method == ATT_FIND_INFO_RSP  && 
             pMsg->hdr.status == bleProcedureComplete) ||
            (pMsg->method == ATT_ERROR_RSP))
        {
          // If CCCD found
          if (glucoseHdlCache[HDL_GLUCOSE_CTL_PNT_CCCD] != 0)
          {
            // Should we look for unread category status CCCD
            if (glucoseHdlCache[HDL_GLUCOSE_CONTEXT_START] <
                glucoseHdlCache[HDL_GLUCOSE_CONTEXT_END])
            {
              // Discover unread category status characteristic descriptors
              GATT_DiscAllCharDescs(glucCollConnHandle,
                                    glucoseHdlCache[HDL_GLUCOSE_CONTEXT_START] + 1,
                                    glucoseHdlCache[HDL_GLUCOSE_CONTEXT_END],
                                    glucCollTaskId);
                                          
              newState = DISC_GLUCOSE_CONTEXT_CCCD;
            }
            else
            {
              // Done
              newState = DISC_IDLE;
            }
          }
          else
          {
            // Missing required characteristic descriptor
            glucoseHdlCache[HDL_GLUCOSE_CTL_PNT_CCCD] = 0;
            newState = DISC_FAILED;
          }          
        }
      }
      break;

   case DISC_GLUCOSE_CONTEXT_CCCD:
      {
        uint8_t i;
        
        // Characteristic descriptors found
        if (pMsg->method == ATT_FIND_INFO_RSP &&
            pMsg->msg.findInfoRsp.numInfo > 0 && 
            pMsg->msg.findInfoRsp.format == ATT_HANDLE_BT_UUID_TYPE)
        {
          attFindInfoRsp_t *pRsp = &(pMsg->msg.findInfoRsp);
          
          // For each handle/uuid pair
          for (i = 0; i < pRsp->numInfo; i++)
          {
            // Look for CCCD
            if ( ATT_BT_PAIR_UUID( pRsp->pInfo, i ) == GATT_CLIENT_CHAR_CFG_UUID )
            {
              // CCCD found
              glucoseHdlCache[HDL_GLUCOSE_CONTEXT_CCCD] =
                ATT_BT_PAIR_HANDLE( pRsp->pInfo, i );
              
              break;
            }
          }
        }
        
        // If procedure complete
        if ((pMsg->method == ATT_FIND_INFO_RSP  && 
             pMsg->hdr.status == bleProcedureComplete) ||
            (pMsg->method == ATT_ERROR_RSP))
        {
          newState = DISC_IDLE;
        }
      }
      break;
      
    default:
      break;
  }
  
  return newState;
}
コード例 #4
0
/*********************************************************************
 * @fn      TimeAppDiscCurrTime()
 *
 * @brief   Current time service and characteristic discovery. 
 *
 * @param   state - Discovery state.
 * @param   pMsg - GATT message.
 *
 * @return  New discovery state.
 */
static uint8 TimeAppDiscCurrTime( uint8 state, gattMsgEvent_t *pMsg )
{
  uint8 newState = state;
  
  switch ( state )
  {
    case DISC_CURR_TIME_START:  
      {
        uint8 uuid[ATT_BT_UUID_SIZE] = { LO_UINT16(CURRENT_TIME_SVC_UUID),
                                         HI_UINT16(CURRENT_TIME_SVC_UUID) };

        // Initialize service discovery variables
        timeAppSvcStartHdl = timeAppSvcEndHdl = 0;
        timeAppEndHdlIdx = 0;
        
        // Discover service by UUID
        GATT_DiscPrimaryServiceByUUID( gapConnHandle, uuid,
                                       ATT_BT_UUID_SIZE, bloodPressureTaskId );      

        newState = DISC_CURR_TIME_SVC;
      } 
      break;

    case DISC_CURR_TIME_SVC:
      // Service found, store handles
      if ( pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP &&
           pMsg->msg.findByTypeValueRsp.numInfo > 0 )
      {
        timeAppSvcStartHdl = pMsg->msg.findByTypeValueRsp.handlesInfo[0].handle;
        timeAppSvcEndHdl = pMsg->msg.findByTypeValueRsp.handlesInfo[0].grpEndHandle;
      }
      
      // If procedure complete
      if ( ( pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP  && 
             pMsg->hdr.status == bleProcedureComplete ) ||
           ( pMsg->method == ATT_ERROR_RSP ) )
      {
        // If service found
        if ( timeAppSvcStartHdl != 0 )
        {
          // Discover all characteristics
          GATT_DiscAllChars( gapConnHandle, timeAppSvcStartHdl,
                             timeAppSvcEndHdl, bloodPressureTaskId );
          
          newState = DISC_CURR_TIME_CHAR;
        }
        else
        {
          // Service not found
          newState = DISC_FAILED;
        }
      }    
      break;

    case DISC_CURR_TIME_CHAR:
      {
         // Characteristics found
        if ( pMsg->method == ATT_READ_BY_TYPE_RSP &&
             pMsg->msg.readByTypeRsp.numPairs > 0 && 
             pMsg->msg.readByTypeRsp.len == CHAR_DESC_HDL_UUID16_LEN)
        {
          uint8   i;
          uint8   *p;
          uint16  handle;
          uint16  uuid;          
          // For each characteristic declaration
          p = pMsg->msg.readByTypeRsp.dataList;
          for ( i = pMsg->msg.readByTypeRsp.numPairs; i > 0; i-- )
          {
            // Parse characteristic declaration
            handle = BUILD_UINT16(p[3], p[4]);
            uuid = BUILD_UINT16(p[5], p[6]);
                   
            // If looking for end handle
            if ( timeAppEndHdlIdx != 0 )
            {
              // End handle is one less than handle of characteristic declaration
              timeAppHdlCache[timeAppEndHdlIdx] = BUILD_UINT16(p[0], p[1]) - 1;
              
              timeAppEndHdlIdx = 0;
            }

            // If UUID is of interest, store handle
            switch ( uuid )
            {
              case CT_TIME_UUID:
                timeAppHdlCache[HDL_CURR_TIME_CT_TIME_START] = handle;
                timeAppEndHdlIdx = HDL_CURR_TIME_CT_TIME_END;
                break;

              default:
                break;
            }
            
            p += CHAR_DESC_HDL_UUID16_LEN;
          }
          
        }
          
        // If procedure complete
        if ( ( pMsg->method == ATT_READ_BY_TYPE_RSP  && 
               pMsg->hdr.status == bleProcedureComplete ) ||
             ( pMsg->method == ATT_ERROR_RSP ) )
        {
          // Special case of end handle at end of service
          if ( timeAppEndHdlIdx != 0 )
          {
            timeAppHdlCache[timeAppEndHdlIdx] = timeAppSvcEndHdl;
            timeAppEndHdlIdx = 0;
          }

          // If didn't find time characteristic
          if ( timeAppHdlCache[HDL_CURR_TIME_CT_TIME_START] == 0 )
          {
            newState = DISC_FAILED;
          }

          else if ( timeAppHdlCache[HDL_CURR_TIME_CT_TIME_START] <timeAppHdlCache[HDL_CURR_TIME_CT_TIME_END] )
          {
            // Discover characteristic descriptors
            GATT_DiscAllCharDescs( gapConnHandle,
                                   timeAppHdlCache[HDL_CURR_TIME_CT_TIME_START] + 1,
                                   timeAppHdlCache[HDL_CURR_TIME_CT_TIME_END],
                                   bloodPressureTaskId );
                                        
            newState = DISC_CURR_TIME_CT_TIME_CCCD;
          }
          else
          {
            newState = DISC_IDLE;
          }
        }
      }      
      break;

    case DISC_CURR_TIME_CT_TIME_CCCD:
      {
        // Characteristic descriptors found
        if ( pMsg->method == ATT_FIND_INFO_RSP &&
             pMsg->msg.findInfoRsp.numInfo > 0 && 
             pMsg->msg.findInfoRsp.format == ATT_HANDLE_BT_UUID_TYPE )
        {
          uint8 i;
          // For each handle/uuid pair
          for ( i = 0; i < pMsg->msg.findInfoRsp.numInfo; i++ )
          {
            // Look for CCCD
            if ( (pMsg->msg.findInfoRsp.info.btPair[i].uuid[0] ==
                  LO_UINT16(GATT_CLIENT_CHAR_CFG_UUID)) &&
                 (pMsg->msg.findInfoRsp.info.btPair[i].uuid[1] ==
                  HI_UINT16(GATT_CLIENT_CHAR_CFG_UUID)) )
            {
              // CCCD found
              timeAppHdlCache[HDL_CURR_TIME_CT_TIME_CCCD] =
                pMsg->msg.findInfoRsp.info.btPair[i].handle;
              
              break;
            }
          }
        }
        
        // If procedure complete
        if ( ( pMsg->method == ATT_FIND_INFO_RSP  && 
               pMsg->hdr.status == bleProcedureComplete ) ||
             ( pMsg->method == ATT_ERROR_RSP ) )
        {
          newState = DISC_IDLE;
        }
      }
      break;

    default:
      break;
  }
  
  return newState;
}
コード例 #5
0
/*********************************************************************
 * @fn      Time_discAlertNtf()
 *
 * @brief   Alert notification service and characteristic discovery. 
 *
 * @param   state - Discovery state.
 * @param   pMsg  - GATT message.
 *
 * @return  New discovery state.
 */
static uint8_t Time_discAlertNtf(uint8_t state, gattMsgEvent_t *pMsg)
{
  uint8_t newState = state;
  
  switch (state)
  {
    case DISC_ALERT_NTF_START:  
      {
        uint8_t uuid[ATT_BT_UUID_SIZE] = { LO_UINT16(ALERT_NOTIF_SERV_UUID),
                                           HI_UINT16(ALERT_NOTIF_SERV_UUID) };

        // Initialize service discovery variables
        Time_svcStartHdl = Time_svcEndHdl = 0;
        Time_endHdlIdx = 0;
        
        // Discover service by UUID
        GATT_DiscPrimaryServiceByUUID(Time_connHandle, uuid,
                                      ATT_BT_UUID_SIZE, ICall_getEntityId());      

        newState = DISC_ALERT_NTF_SVC;
      }
      break;

    case DISC_ALERT_NTF_SVC:
      // Service found, store handles
      if (pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP &&
          pMsg->msg.findByTypeValueRsp.numInfo > 0)
      {
        Time_svcStartHdl = 
          ATT_ATTR_HANDLE(pMsg->msg.findByTypeValueRsp.pHandlesInfo, 0);
        Time_svcEndHdl = 
          ATT_GRP_END_HANDLE(pMsg->msg.findByTypeValueRsp.pHandlesInfo, 0);
      }
      
      // If procedure complete
      if ((pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP  && 
           pMsg->hdr.status == bleProcedureComplete) ||
          (pMsg->method == ATT_ERROR_RSP))
      {
        // If service found
        if (Time_svcStartHdl != 0)
        {
          // Discover all characteristics
          GATT_DiscAllChars(Time_connHandle, Time_svcStartHdl,
                            Time_svcEndHdl, ICall_getEntityId());
          
          newState = DISC_ALERT_NTF_CHAR;
        }
        else
        {
          // Service not found
          newState = DISC_FAILED;
        }
      }    
      break;

    case DISC_ALERT_NTF_CHAR:
      {
        // Characteristics found
        if (pMsg->method == ATT_READ_BY_TYPE_RSP &&
            pMsg->msg.readByTypeRsp.numPairs > 0 && 
            pMsg->msg.readByTypeRsp.len == CHAR_DESC_HDL_UUID16_LEN)
        {
          uint8_t   i;
          uint8_t   *p;
          uint16_t  handle;
          uint16_t  uuid;
          
          p = pMsg->msg.readByTypeRsp.pDataList;
          
          // For each characteristic declaration
          for (i = pMsg->msg.readByTypeRsp.numPairs; i > 0; i--)
          {
            // Parse characteristic declaration
            handle = BUILD_UINT16(p[3], p[4]);
            uuid = BUILD_UINT16(p[5], p[6]);
                   
            // If looking for end handle
            if (Time_endHdlIdx != 0)
            {
              // End handle is one less than handle of characteristic declaration
              Time_handleCache[Time_endHdlIdx] = BUILD_UINT16(p[0], p[1]) - 1;
              
              Time_endHdlIdx = 0;
            }

            // If UUID is of interest, store handle
            switch (uuid)
            {
              case ALERT_NOTIF_CTRL_PT_UUID:
                Time_handleCache[HDL_ALERT_NTF_CTRL] = handle;
                break;

              case UNREAD_ALERT_STATUS_UUID:
                Time_handleCache[HDL_ALERT_NTF_UNREAD_START] = handle;
                Time_endHdlIdx = HDL_ALERT_NTF_UNREAD_END;
                break;

              case NEW_ALERT_UUID:
                Time_handleCache[HDL_ALERT_NTF_NEW_START] = handle;
                Time_endHdlIdx = HDL_ALERT_NTF_NEW_END;
                break;

              case SUP_NEW_ALERT_CAT_UUID:
                Time_handleCache[HDL_ALERT_NTF_NEW_CAT] = handle;
                break;

              case SUP_UNREAD_ALERT_CAT_UUID:
                Time_handleCache[HDL_ALERT_NTF_UNREAD_CAT] = handle;
                break;

              default:
                break;
            }
            
            p += CHAR_DESC_HDL_UUID16_LEN;
          }
        }
          
        // If procedure complete
        if ((pMsg->method == ATT_READ_BY_TYPE_RSP  && 
             pMsg->hdr.status == bleProcedureComplete) ||
            (pMsg->method == ATT_ERROR_RSP))
        {
          // Special case of end handle at end of service
          if (Time_endHdlIdx != 0)
          {
            Time_handleCache[Time_endHdlIdx] = Time_svcEndHdl;
            Time_endHdlIdx = 0;
          }
          
          // If didn't find new alert characteristic
          if (Time_handleCache[HDL_ALERT_NTF_NEW_START] == 0)
          {
            newState = DISC_FAILED;
          }
          else if (Time_handleCache[HDL_ALERT_NTF_NEW_START] <
                   Time_handleCache[HDL_ALERT_NTF_NEW_END])
          {
            // Discover incoming alert characteristic descriptors
            GATT_DiscAllCharDescs(Time_connHandle,
                                  Time_handleCache[HDL_ALERT_NTF_NEW_START] + 1,
                                  Time_handleCache[HDL_ALERT_NTF_NEW_END],
                                  ICall_getEntityId());
                                        
            newState = DISC_ALERT_NTF_NEW_CCCD;
          }
          else
          {
            // Missing required characteristic descriptor
            Time_handleCache[HDL_ALERT_NTF_NEW_START] = 0;
            newState = DISC_FAILED;
          }
        }
      }      
      break;

    case DISC_ALERT_NTF_NEW_CCCD:
      {
        // Characteristic descriptors found
        if (pMsg->method == ATT_FIND_INFO_RSP &&
            pMsg->msg.findInfoRsp.numInfo > 0 && 
            pMsg->msg.findInfoRsp.format == ATT_HANDLE_BT_UUID_TYPE)
        {
          uint8_t i;
          
          // For each handle/uuid pair
          for (i = 0; i < pMsg->msg.findInfoRsp.numInfo; i++)
          {
            // Look for CCCD
            if (ATT_BT_PAIR_UUID(pMsg->msg.findInfoRsp.pInfo, i) ==
                GATT_CLIENT_CHAR_CFG_UUID)
            {
              // CCCD found
              Time_handleCache[HDL_ALERT_NTF_NEW_CCCD] =
                ATT_BT_PAIR_HANDLE(pMsg->msg.findInfoRsp.pInfo, i);
              
              break;
            }
          }
        }
        
        // If procedure complete
        if ((pMsg->method == ATT_FIND_INFO_RSP  && 
             pMsg->hdr.status == bleProcedureComplete) ||
            (pMsg->method == ATT_ERROR_RSP))
        {
          // If CCCD found
          if (Time_handleCache[HDL_ALERT_NTF_NEW_CCCD] != 0)
          {
            // Should we look for unread category status CCCD
            if (Time_handleCache[HDL_ALERT_NTF_UNREAD_START] <
                Time_handleCache[HDL_ALERT_NTF_UNREAD_END])
            {
              // Discover unread category status characteristic descriptors
              GATT_DiscAllCharDescs(Time_connHandle,
                                    Time_handleCache[HDL_ALERT_NTF_UNREAD_START] + 1,
                                    Time_handleCache[HDL_ALERT_NTF_UNREAD_END],
                                    ICall_getEntityId());
                                          
              newState = DISC_ALERT_NTF_UNREAD_CCCD;
            }
            else
            {
              // Done
              newState = DISC_IDLE;
            }
          }
          else
          {
            // Missing required characteristic descriptor
            Time_handleCache[HDL_ALERT_NTF_NEW_START] = 0;
            newState = DISC_FAILED;
          }          
        }
      }
      break;

    case DISC_ALERT_NTF_UNREAD_CCCD:
      {
        // Characteristic descriptors found
        if (pMsg->method == ATT_FIND_INFO_RSP &&
            pMsg->msg.findInfoRsp.numInfo > 0 && 
            pMsg->msg.findInfoRsp.format == ATT_HANDLE_BT_UUID_TYPE)
        {
          uint8_t i;
          
          // For each handle/uuid pair
          for (i = 0; i < pMsg->msg.findInfoRsp.numInfo; i++)
          {
            // Look for CCCD
            if (ATT_BT_PAIR_UUID(pMsg->msg.findInfoRsp.pInfo, i) ==
                GATT_CLIENT_CHAR_CFG_UUID)
            {
              // CCCD found
              Time_handleCache[HDL_ALERT_NTF_UNREAD_CCCD] =
                ATT_BT_PAIR_HANDLE(pMsg->msg.findInfoRsp.pInfo, i);
              
              break;
            }
          }
        }
        
        // If procedure complete
        if ((pMsg->method == ATT_FIND_INFO_RSP  && 
             pMsg->hdr.status == bleProcedureComplete) ||
            (pMsg->method == ATT_ERROR_RSP))
        {
          newState = DISC_IDLE;
        }
      }
      break;

    default:
      break;
  }
  
  return newState;
}