/********************************************************************* * @fn Thermometer_sendStoredMeas * * @brief Prepare and send a stored Meas Indication. * * @param none * * @return none */ static void Thermometer_sendStoredMeas(void) { // If previously connected to this peer send any stored measurements. if (thStoreStartIndex != thStoreIndex) { bStatus_t status = FAILURE; attHandleValueInd_t *pStoreInd = &thStoreMeas[thStoreStartIndex]; if (pStoreInd->handle == THERMOMETER_TEMP_VALUE_POS) { // Send Measurement. status = Thermometer_TempIndicate(thermometer_connHandle, pStoreInd, ICall_getEntityId()); } else if (pStoreInd->handle == THERMOMETER_INTERVAL_VALUE_POS) { // Send Interval. status = Thermometer_IntervalIndicate(thermometer_connHandle, pStoreInd, ICall_getEntityId()); } if (status == SUCCESS) { thStoreStartIndex = thStoreStartIndex + 1; // Wrap around buffer. if(thStoreStartIndex > TH_STORE_MAX) { thStoreStartIndex = 0; } // Clear out this Meas indication. memset(pStoreInd, 0, sizeof(attHandleValueInd_t)); } } }
/********************************************************************* * @fn Time_configNext() * * @brief Perform the characteristic configuration read or * write procedure. * * @param state - Configuration state. * * @return New configuration state. */ uint8_t Time_configNext(uint8_t state) { bool read; // Find next non-zero cached handle of interest while (state < TIME_CONFIG_MAX && Time_handleCache[Time_configList[state]] == 0) { state++; } // Return if reached end of list if (state >= TIME_CONFIG_MAX) { return TIME_CONFIG_CMPL; } // Determine what to do with characteristic switch (Time_configList[state]) { // Read these characteristics case HDL_CURR_TIME_CT_TIME_START: read = TRUE; break; // Set notification for these characteristics case HDL_CURR_TIME_CT_TIME_CCCD: read = FALSE; break; default: return state; } if(Time_configDone==TRUE) { return state; } // Do a GATT read or write if (read) { attReadReq_t readReq; readReq.handle = Time_handleCache[Time_configList[state]]; // Send the read request GATT_ReadCharValue(Time_connHandle, &readReq, ICall_getEntityId()); // Only reading time right now Time_configDone = TRUE; } else { attWriteReq_t writeReq; writeReq.pValue = GATT_bm_alloc(Time_connHandle, ATT_WRITE_REQ, 2, NULL); if (writeReq.pValue != NULL) { writeReq.len = 2; writeReq.pValue[0] = LO_UINT16(GATT_CLIENT_CFG_NOTIFY); writeReq.pValue[1] = HI_UINT16(GATT_CLIENT_CFG_NOTIFY); writeReq.sig = 0; writeReq.cmd = 0; writeReq.handle = Time_handleCache[Time_configList[state]]; // Send the read request if (GATT_WriteCharValue(Time_connHandle, &writeReq, ICall_getEntityId()) != SUCCESS) { GATT_bm_free((gattMsg_t *)&writeReq, ATT_WRITE_REQ); } } } return state; }
/********************************************************************* * @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; }
/********************************************************************* * @fn Time_discRefTime() * * @brief Reference time service and characteristic discovery. * * @param state - Discovery state. * @param pMsg - GATT message. * * @return New discovery state. */ static uint8_t Time_discRefTime(uint8_t state, gattMsgEvent_t *pMsg) { uint8_t newState = state; switch (state) { case DISC_REF_TIME_START: { uint8_t uuid[ATT_BT_UUID_SIZE] = { LO_UINT16(REF_TIME_UPDATE_SERV_UUID), HI_UINT16(REF_TIME_UPDATE_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_REF_TIME_SVC; } break; case DISC_REF_TIME_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_REF_TIME_CHAR; } else { // Service not found newState = DISC_FAILED; } } break; case DISC_REF_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_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 UUID is of interest, store handle switch (uuid) { case TIME_UPDATE_CTRL_PT_UUID: Time_handleCache[HDL_REF_TIME_UPD_CTRL] = handle; break; case TIME_UPDATE_STATE_UUID: Time_handleCache[HDL_REF_TIME_UPD_STATE] = 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)) { // If didn't find mandatory characteristics if (Time_handleCache[HDL_REF_TIME_UPD_CTRL] == 0 || Time_handleCache[HDL_REF_TIME_UPD_STATE] == 0) { newState = DISC_FAILED; } else { newState = DISC_IDLE; } } } break; default: break; } return newState; }
/********************************************************************* * @fn Thermometer_handleKeys * * @brief Handles all key events for this device. * * @param shift - true if in shift/alt. * @param keys - bit field for key events. * * @return none */ static void Thermometer_handleKeys(uint8_t shift, uint8_t keys) { bStatus_t status; uint8_t notify_interval; if (keys & KEY_UP) { // Set simulated measurement flag index. thermometerFlagsIdx+=1; if (thermometerFlagsIdx == FLAGS_IDX_MAX) { thermometerFlagsIdx = 0; } } // Read stored interval value. Thermometer_GetParameter(THERMOMETER_INTERVAL, ¬ify_interval); if(notify_interval == 0) { thMeasTimerRunning = FALSE; } if (keys & KEY_RIGHT) { // If device is not in a connection, pressing the right key should toggle // advertising on and off. If timer is running, then will adv when meas is // ready. if((gapProfileState != GAPROLE_CONNECTED) && (thMeasTimerRunning == FALSE)) { uint8_t current_adv_enabled_status; uint8_t new_adv_enabled_status; // Find the current GAP advertisement status. GAPRole_GetParameter(GAPROLE_ADVERT_ENABLED, ¤t_adv_enabled_status); if(current_adv_enabled_status == FALSE) { new_adv_enabled_status = TRUE; } else { new_adv_enabled_status = FALSE; } // Change the GAP advertisement status to opposite of current status. GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8_t), &new_adv_enabled_status); } // Timer is running, so allow simulated changes. else { // Change temperature, remove single precision. if((thermometerCelcius) < 0x000175) { thermometerCelcius += 1; } else { uint16_t thInterval = 30; thermometerCelcius = 0x000173; // Simulate interval change. Thermometer_SetParameter(THERMOMETER_INTERVAL, THERMOMETER_INTERVAL_LEN, &thInterval); if(temperatureIntervalConfig == true) { attHandleValueInd_t intervalInd; intervalInd.pValue = GATT_bm_alloc(thermometer_connHandle, ATT_HANDLE_VALUE_IND, THERMOMETER_INTERVAL_LEN, NULL); if (intervalInd.pValue != NULL) { intervalInd.len = THERMOMETER_INTERVAL_LEN; intervalInd.pValue[0] = LO_UINT16(thInterval); intervalInd.pValue[1] = HI_UINT16(thInterval); intervalInd.handle = THERMOMETER_INTERVAL_VALUE_POS; status = Thermometer_IntervalIndicate(thermometer_connHandle, &intervalInd, ICall_getEntityId()); // We can fail if there was pending meas or not connected. if (status != SUCCESS) { // Queue indication. Thermometer_storeIndications(&intervalInd); } } } } } } }