Part Number: CC2652R
Hello,
I have created two custom Bluetooth applications. One is based on the simple_peripheral example and the other is based on the simple_central example. Right now, I'm having trouble getting the central device to write to a characteristic value on the peripheral device.
I am running these applications simultaneously on two CC2652R1F Launchpads. I am using version 4.20.00.35 of the SimpleLink SDK.
At this point, I believe my peripheral application is working exactly how I intend. It maintains a custom profile/service with two characteristics. One characteristic is setup as read-only with notification functionality and the other is simply write-only. I have tested the peripheral application by connecting to it via an app on my phone and doing reads / subscribing to notifications / doing writes and everything seems to be working as intended.
The issue lies in my central application. I have attached the file which contains the main functionality of the central device for reference.
/******************************************************************************
@file simple_central.c
@brief This file contains the Simple Central sample application for use
with the CC2650 Bluetooth Low Energy Protocol Stack.
Group: WCS, BTS
Target Device: cc13x2_26x2
*****************************************************************************/
/*********************************************************************
* INCLUDES
*/
#include <string.h>
#include <ti/sysbios/knl/Task.h>
#include <ti/sysbios/knl/Clock.h>
#include <ti/sysbios/knl/Event.h>
#include <ti/sysbios/knl/Queue.h>
#include "bcomdef.h"
#include <icall.h>
#include "util.h"
#include "icall_ble_api.h"
#include "osal_list.h"
#include <ti_drivers_config.h>
#include "ti_ble_config.h"
#include "ble_user_config.h"
#include "custom_profile.h"
#include "simple_central.h"
/*********************************************************************
* CONSTANTS
*/
// Application events
#define SC_EVT_SCAN_DISABLED 0x03
#define SC_EVT_SVC_DISC 0x05
#define SC_EVT_INIT_CONN 0x06
#define SC_EVT_START_WRITING 0x07
#define SC_EVT_DEVICE_NOT_FND 0x08
// Simple Central Task Events
#define SC_ICALL_EVT ICALL_MSG_EVENT_ID // Event_Id_31
#define SC_QUEUE_EVT UTIL_QUEUE_EVENT_ID // Event_Id_30
#define SC_ALL_EVENTS (SC_ICALL_EVT | SC_QUEUE_EVT)
// Task configuration
#define SC_TASK_PRIORITY 1
// Task stack size
#define SC_TASK_STACK_SIZE 1024
// Spin if the expression is not true
#define SIMPLECENTRAL_ASSERT(expr) if (!(expr)) SimpleCentral_spin();
/*********************************************************************
* TYPEDEFS
*/
// Discovery states
enum
{
BLE_DISC_STATE_IDLE, // Idle
BLE_DISC_STATE_MTU, // Exchange ATT MTU size
BLE_DISC_STATE_SVC, // Service discovery
BLE_DISC_STATE_CHAR // Characteristic discovery
};
// App event passed from profiles.
typedef struct
{
appEvtHdr_t hdr; // event header
uint8_t *pData; // event data
} scEvt_t;
// Scanned device information record
typedef struct
{
uint8_t addrType; // Peer Device's Address Type
uint8_t addr[B_ADDR_LEN]; // Peer Device Address
} scanRec_t;
// Connected device information
typedef struct
{
uint16_t connHandle; // Connection Handle
uint16_t char1Handle; // Characteristic 1 Handle
uint16_t char2Handle; // Characteristic 2 Handle
uint8_t addr[B_ADDR_LEN]; // Peer Device Address
} connRec_t;
typedef struct
{
osal_list_elem elem;
uint8_t addr[B_ADDR_LEN]; // member's BDADDR
uint8_t addrType; // member's Address Type
uint16_t connHandle; // member's connection handle
uint8_t status; // bitwise status flag
} groupListElem_t;
/*********************************************************************
* LOCAL VARIABLES
*/
// Entity ID globally used to check for source and/or destination of messages
static ICall_EntityID selfEntity;
// Event globally used to post local events and pend on system and
// local events.
static ICall_SyncHandle syncEvent;
// Queue object used for app messages
static Queue_Struct appMsg;
static Queue_Handle appMsgQueue;
// Task configuration
Task_Struct scTask;
#if defined __TI_COMPILER_VERSION__
#pragma DATA_ALIGN(scTaskStack, 8)
#else
#pragma data_alignment=8
#endif
uint8_t scTaskStack[SC_TASK_STACK_SIZE];
// Number of connected devices
static uint8_t numConn = 0;
// List of connections
static connRec_t connList[MAX_NUM_BLE_CONNS];
// Connection handle of current connection
static uint16_t scConnHandle = LINKDB_CONNHANDLE_INVALID;
// Discovery state
static uint8_t discState = BLE_DISC_STATE_IDLE;
// Discovered service start and end handle
static uint16_t svcStartHdl = 0;
static uint16_t svcEndHdl = 0;
// Maximum PDU size (default = 27 octets)
static uint16_t scMaxPduSize;
// Address mode
static GAP_Addr_Modes_t addrMode = DEFAULT_ADDRESS_MODE;
// Index of custom peripheral in the advertisement report, returned once scanning is done.
static uint8_t indexOfPeriphInAdvReport = 0;
// The BDA (Bluetooth Device Address) of the target peripheral to connect to.
// This address is hard coded in the custom peripheral application.
static uint8_t customPeriphBDA[B_ADDR_LEN] = {0x06, 0x05, 0x04, 0x03, 0x02, 0xC1};
// A general pointer to a uint8_t that does not point to any data.
// This pointer is used when we want to trigger things on application events without
// needing to pass any data.
static uint8_t *noData = NULL;
// A debug counter.
static uint16_t debugCntr = 0;
/*********************************************************************
* LOCAL FUNCTIONS
*/
static void SimpleCentral_init(void);
static void SimpleCentral_taskFxn(uintptr_t a0, uintptr_t a1);
static uint8_t SimpleCentral_processStackMsg(ICall_Hdr *pMsg);
static void SimpleCentral_processGapMsg(gapEventHdr_t *pMsg);
static void SimpleCentral_processGATTMsg(gattMsgEvent_t *pMsg);
static void SimpleCentral_processAppMsg(scEvt_t *pMsg);
static void SimpleCentral_processGATTDiscEvent(gattMsgEvent_t *pMsg);
static void SimpleCentral_startSvcDiscovery(void);
static uint8_t SimpleCentral_addConnInfo(uint16_t connHandle, uint8_t *pAddr);
static uint8_t SimpleCentral_getConnIndex(uint16_t connHandle);
static status_t SimpleCentral_enqueueMsg(uint8_t event, uint8_t status, uint8_t *pData);
static void SimpleCentral_scanCb(uint32_t evt, void* msg, uintptr_t arg);
/*********************************************************************
* PUBLIC FUNCTIONS
*/
/*********************************************************************
* @fn SimpleCentral_spin
*
* @brief Spin forever
*
* @param none
*/
static void SimpleCentral_spin(void)
{
volatile uint8_t x;
while(1)
{
x++;
}
}
/*********************************************************************
* @fn SimpleCentral_createTask
*
* @brief Task creation function for the Simple Central.
*
* @param none
*
* @return none
*/
void SimpleCentral_createTask(void)
{
Task_Params taskParams;
// Configure task
Task_Params_init(&taskParams);
taskParams.stack = scTaskStack;
taskParams.stackSize = SC_TASK_STACK_SIZE;
taskParams.priority = SC_TASK_PRIORITY;
Task_construct(&scTask, SimpleCentral_taskFxn, &taskParams, NULL);
}
/*********************************************************************
* @fn SimpleCentral_Init
*
* @brief Initialization function for the Simple Central App Task.
* This is called during initialization and should contain
* any application specific initialization (ie. hardware
* initialization/setup, table initialization, power up
* notification).
*
* @param none
*
* @return none
*/
static void SimpleCentral_init(void)
{
// ******************************************************************
// N0 STACK API CALLS CAN OCCUR BEFORE THIS CALL TO ICall_registerApp
// ******************************************************************
// Register the current thread as an ICall dispatcher application
// so that the application can send and receive messages.
ICall_registerApp(&selfEntity, &syncEvent);
// Create an RTOS queue for message from profile to be sent to app.
appMsgQueue = Util_constructQueue(&appMsg);
// Initialize the connection list.
uint8_t i;
for (i = 0; i < MAX_NUM_BLE_CONNS; i++)
{
connList[i].connHandle = LINKDB_CONNHANDLE_INVALID;
}
// Set the device name.
GGS_SetParameter(GGS_DEVICE_NAME_ATT, GAP_DEVICE_NAME_LEN, (void *)attDeviceName);
// Set default values for rx/tx packet sizes and rx/tx times.
// Extended Data Length Feature is enabled by default in build_config.opt in stack project.
// The data length extension feature allows us to define packet sizes greater than 27 bytes long.
{
#define APP_SUGGESTED_RX_PDU_SIZE 251 // Default is 251 octets (RX).
#define APP_SUGGESTED_RX_TIME 3000 // Default is 17000us (RX).
#define APP_SUGGESTED_TX_PDU_SIZE 251 // Default is 27 octets (TX).
#define APP_SUGGESTED_TX_TIME 3000 // Default is 328us (TX).
// Set the maximum values. Note that setting the maximum values does not actually set the PDU sizes/times.
// The PDU size for a particular connection is set when the
HCI_EXT_SetMaxDataLenCmd(APP_SUGGESTED_TX_PDU_SIZE, APP_SUGGESTED_TX_TIME, APP_SUGGESTED_RX_PDU_SIZE, APP_SUGGESTED_RX_TIME);
}
// Initialize GATT Client
GATT_InitClient();
// Register to receive incoming ATT Indications/Notifications.
GATT_RegisterForInd(selfEntity);
// Initialize GATT attributes.
GGS_AddService(GATT_ALL_SERVICES); // GAP
GATTServApp_AddService(GATT_ALL_SERVICES); // GATT attributes
// Register for GATT local events and ATT Responses pending for transmission
GATT_RegisterForMsgs(selfEntity);
// Reject all parameter update requests
GAP_SetParamValue(GAP_PARAM_LINK_UPDATE_DECISION, GAP_UPDATE_REQ_DENY_ALL);
// Initialize GAP layer for Central role and register to receive GAP events
GAP_DeviceInit(GAP_PROFILE_CENTRAL, selfEntity, addrMode, &pRandomAddress);
}
/*********************************************************************
* @fn SimpleCentral_taskFxn
*
* @brief Application task entry point for the Simple Central.
*
* @param none
*
* @return events not processed
*/
static void SimpleCentral_taskFxn(uintptr_t a0, uintptr_t a1)
{
// Initialize application
SimpleCentral_init();
// Application main loop
for (;;)
{
uint32_t events;
events = Event_pend(syncEvent, Event_Id_NONE, SC_ALL_EVENTS,
ICALL_TIMEOUT_FOREVER);
if (events)
{
ICall_EntityID dest;
ICall_ServiceEnum src;
ICall_HciExtEvt *pMsg = NULL;
if (ICall_fetchServiceMsg(&src, &dest, (void **)&pMsg) == ICALL_ERRNO_SUCCESS)
{
uint8 safeToDealloc = TRUE;
if ((src == ICALL_SERVICE_CLASS_BLE) && (dest == selfEntity))
{
ICall_Stack_Event *pEvt = (ICall_Stack_Event *)pMsg;
// Check for BLE stack events first
if (pEvt->signature != 0xffff)
{
// Process inter-task message
safeToDealloc = SimpleCentral_processStackMsg((ICall_Hdr *)pMsg);
}
}
if (pMsg && safeToDealloc)
{
ICall_freeMsg(pMsg);
}
}
// If RTOS queue is not empty, process app message
if (events & SC_QUEUE_EVT)
{
scEvt_t *pMsg;
while (pMsg = (scEvt_t *)Util_dequeueMsg(appMsgQueue))
{
// Process message
SimpleCentral_processAppMsg(pMsg);
// Free the space from the message
ICall_free(pMsg);
}
}
}
}
}
/*********************************************************************
* @fn SimpleCentral_processStackMsg
*
* @brief Process an incoming task message.
*
* @param pMsg - message to process
*
* @return TRUE if safe to deallocate incoming message, FALSE otherwise.
*/
static uint8_t SimpleCentral_processStackMsg(ICall_Hdr *pMsg)
{
uint8_t safeToDealloc = TRUE;
switch (pMsg->event)
{
case GAP_MSG_EVENT:
SimpleCentral_processGapMsg((gapEventHdr_t*) pMsg);
break;
case GATT_MSG_EVENT:
SimpleCentral_processGATTMsg((gattMsgEvent_t *)pMsg);
break;
default:
break;
}
return (safeToDealloc);
}
/*********************************************************************
* @fn SimpleCentral_processAppMsg
*
* @brief Scanner application event processing function.
*
* @param pMsg - pointer to event structure
*
* @return none
*/
static void SimpleCentral_processAppMsg(scEvt_t *pMsg)
{
bool safeToDealloc = TRUE;
switch (pMsg->hdr.event)
{
// Process all advertising reports. Since we are trying to connect to specifically the
// hw5_custom_peripheral, and we know its hard-coded device address, we must find the
// index of the advertising report which has this address and record it.
case SC_EVT_SCAN_DISABLED:
{
uint8_t numReport;
uint8_t i;
uint8_t j;
bool isAddrEqual;
GapScan_Evt_AdvRpt_t advRpt;
// The total number of advertising reports. Note that the upper bound on this number is
// DEFAULT_MAX_SCAN_RES.
numReport = ((GapScan_Evt_End_t*) (pMsg->pData))->numReport;
// Now loop through all advertising reports until we find the report which has the BDA we
// are looking for i.e. the BDA of the custom peripheral. When we find that, record the
// index.
// This index will be used to connect to that particular device.
for (i = 0; i < numReport; i++)
{
// This method assumes there is exactly one matching address in the advertisement report
// list.
isAddrEqual = true;
GapScan_getAdvReport(i, &advRpt);
for (j = 0; j < B_ADDR_LEN; j++)
{
if (advRpt.addr[j] != customPeriphBDA[j])
{
isAddrEqual = false;
}
}
// If this particular address report contains the address of the custom peripheral, save the
// index and stop examining the advertising reports.
if (isAddrEqual == true)
{
indexOfPeriphInAdvReport = i;
break;
}
}
if (isAddrEqual == false)
{
// The target device was not found. No data to pass with this event.
SimpleCentral_enqueueMsg(SC_EVT_DEVICE_NOT_FND, 0, noData);
}
else
{
// The target device was found. Include a pointer to the index in the message.
uint8_t *pIndexOfPeriphInAdvReport = &indexOfPeriphInAdvReport;
SimpleCentral_enqueueMsg(SC_EVT_INIT_CONN, 0, pIndexOfPeriphInAdvReport);
}
break;
}
// The custom peripheral was not found during scanning, so spin.
case SC_EVT_DEVICE_NOT_FND:
{
while(1);
}
// Initiate the connection process by passing the index of the advertisement
// report corresponding to the target peripheral device.
case SC_EVT_INIT_CONN:
{
SimpleCentral_doConnect(*(pMsg->pData));
break;
}
// Start the service discovery process. This will allow us to do characteristic reads and writes.
case SC_EVT_SVC_DISC:
{
SimpleCentral_startSvcDiscovery();
break;
}
// We have discovered the handle of the characteristic value we want to write to. Now do a write.
case SC_EVT_START_WRITING:
{
SimpleCentral_doGattWrite();
break;
}
default:
break;
}
// Deallocate the data contained in the message. If there is no data in the message, don't deallocate anything.
// Note that deallocation of the event header is done at the task function level.
if ((safeToDealloc == TRUE) && (pMsg->pData != NULL))
{
ICall_free(pMsg->pData);
}
}
/*********************************************************************
* @fn SimpleCentral_processGapMsg
*
* @brief GAP message processing function.
*
* @param pMsg - pointer to event message structure
*
* @return none
*/
static void SimpleCentral_processGapMsg(gapEventHdr_t *pMsg)
{
switch (pMsg->opcode)
{
case GAP_DEVICE_INIT_DONE_EVENT:
{
uint8_t temp8;
uint16_t temp16;
gapDeviceInitDoneEvent_t *pPkt = (gapDeviceInitDoneEvent_t *)pMsg;
// Register callback to process Scanner events.
// When one of the scan events specified via the GapScan_setEventMask happens,
// this callback function will be called.
GapScan_registerCb(SimpleCentral_scanCb, NULL);
// Set Scanner Event Mask.
// The list of GAP events that trigger callbacks to the registered callback function.
GapScan_setEventMask(GAP_EVT_SCAN_DISABLED);
// Set Scan PHY parameters.
// Passive scanning looks for non-scannable advertisements i.e. advertisements belonging
// to peripheral devices which are not set up to be able to do handle scan requests and
// issue scan responses.
GapScan_setPhyParams(DEFAULT_SCAN_PHY, SCAN_TYPE_PASSIVE, DEFAULT_SCAN_INTERVAL, DEFAULT_SCAN_WINDOW);
// Set Advertising report fields to keep.
// We are only interested in the address and address type of the advertising device. The advertising packet
// contains no other data of interest to us.
temp16 = ADV_RPT_FIELDS;
GapScan_setParam(SCAN_PARAM_RPT_FIELDS, &temp16);
// Set Scanning Primary PHY.
temp8 = DEFAULT_SCAN_PHY;
GapScan_setParam(SCAN_PARAM_PRIM_PHYS, &temp8);
// Set LL Duplicate Filter.
// This filter out duplicate packets at the link layer.
temp8 = SCAN_FLT_DUP_ENABLE;
GapScan_setParam(SCAN_PARAM_FLT_DUP, &temp8);
// Set PDU filter.
// Only connectable and complete packets are desired.
// It doesn't matter if received packets are Scannable or Non-Scannable,
// Directed or Undirected, Scan Responses or Advertisements, and
// Legacy or Extended.
// This prevents useless packets from being processed.
temp16 = SCAN_FLT_PDU_CONNECTABLE_ONLY | SCAN_FLT_PDU_COMPLETE_ONLY;
GapScan_setParam(SCAN_PARAM_FLT_PDU_TYPE, &temp16);
// Set PHY parameters.
// The connection interval is the maximum time period that can elapse before a Bluetooth
// connection disconnects. Packets must be exchanged over Bluetooth even when data does
// not necessarily need to shared between client and server.
GapInit_setPhyParam(DEFAULT_INIT_PHY, INIT_PHYPARAM_CONN_INT_MIN, INIT_PHYPARAM_MIN_CONN_INT);
GapInit_setPhyParam(DEFAULT_INIT_PHY, INIT_PHYPARAM_CONN_INT_MAX, INIT_PHYPARAM_MAX_CONN_INT);
// Set the maximum PDU size.
scMaxPduSize = pPkt->dataPktLen;
// Now start scanning for available connections.
// Scanning for DEFAULT_SCAN_DURATION x 10 ms.
// Let the stack record the advertising reports as many as up to DEFAULT_MAX_SCAN_RES.
GapScan_enable(0, DEFAULT_SCAN_DURATION, DEFAULT_MAX_SCAN_RES);
break;
}
case GAP_CONNECTING_CANCELLED_EVENT:
{
// The connection attempt failed, spin.
while(1){}
break;
}
case GAP_LINK_ESTABLISHED_EVENT:
{
uint16_t connHandle = ((gapEstLinkReqEvent_t*) pMsg)->connectionHandle;
uint8_t* pAddr = ((gapEstLinkReqEvent_t*) pMsg)->devAddr;
uint8_t connIndex;
// Update the current connection handle.
// The connection handle is used to identify a particular connection in the connection list.
scConnHandle = connHandle;
// Add this connection info to the list of current connections.
connIndex = SimpleCentral_addConnInfo(connHandle, pAddr);
// connIndex cannot be equal to or greater than MAX_NUM_BLE_CONNS
SIMPLECENTRAL_ASSERT(connIndex < MAX_NUM_BLE_CONNS);
// For this new connection, the handles of the characteristics of interest
// are unknown.
// Once we do service discovery of this connection, these values will be
// updated with the actual handles of the characteristic values of interest.
// Note that the "characteristic handle" refers to the handle of the characteristic
// value of interest.
connList[connIndex].char1Handle = 0;
connList[connIndex].char2Handle = 0;
// Now start service discovery.
SimpleCentral_startSvcDiscovery();
break;
}
default:
break;
}
}
/*********************************************************************
* @fn SimpleCentral_processGATTMsg
*
* @brief Process GATT messages and events.
*
*/
static void SimpleCentral_processGATTMsg(gattMsgEvent_t *pMsg)
{
// Check the discovery state of the device. If the device is not in the idle
// state, pass off processing.
if (discState != BLE_DISC_STATE_IDLE)
{
SimpleCentral_processGATTDiscEvent(pMsg);
}
// Needed only for ATT Protocol messages
GATT_bm_free(&pMsg->msg, pMsg->method);
}
/*********************************************************************
* @fn SimpleCentral_startSvcDiscovery
*
* @brief Start service discovery.
*
*/
static void SimpleCentral_startSvcDiscovery(void)
{
attExchangeMTUReq_t req;
// Update the global discovery state.
discState = BLE_DISC_STATE_MTU;
// Discover GATT Server's Rx MTU size.
req.clientRxMTU = scMaxPduSize - L2CAP_HDR_SIZE;
// ATT MTU size should be set to the minimum of the Client Rx MTU
// and Server Rx MTU values.
// Note that when this function successfully finishes executing, a ATT_EXCHANGE_MTU_RSP
// event is issued. Otherwise an ATT_ERROR_RSP event is issued.
// I believe an ATT_ERROR_RSP is issued only if the set PDU sizes are incompatible i.e.
// the client is trying to transmit more bytes to the server in a single packet than
// it can handle.
GATT_ExchangeMTU(scConnHandle, &req, selfEntity);
}
/*********************************************************************
* @fn SimpleCentral_processGATTDiscEvent
*
* @brief Process GATT discovery event
*
*/
static void SimpleCentral_processGATTDiscEvent(gattMsgEvent_t *pMsg)
{
if (discState == BLE_DISC_STATE_MTU)
{
// MTU size response received, discover the primary service of the peripheral device.
if (pMsg->method == ATT_EXCHANGE_MTU_RSP)
{
uint8_t uuid[ATT_BT_UUID_SIZE] = {LO_UINT16(CUSTOMPROFILE_SERV_UUID), HI_UINT16(CUSTOMPROFILE_SERV_UUID)};
// Update the global discovery state.
discState = BLE_DISC_STATE_SVC;
// Search for the specified service. When found, the stack issues the ATT_FIND_BY_TYPE_VALUE_RSP event.
// If an error occurs on the server, the stack will issue an ATT_ERROR_RSP event.
GATT_DiscPrimaryServiceByUUID(pMsg->connHandle, uuid, ATT_BT_UUID_SIZE, selfEntity);
}
}
else if (discState == BLE_DISC_STATE_SVC)
{
// Service found, store handles.
if (pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP && pMsg->msg.findByTypeValueRsp.numInfo > 0)
{
// Get the starting and ending handle of the primary service.
svcStartHdl = ATT_ATTR_HANDLE(pMsg->msg.findByTypeValueRsp.pHandlesInfo, 0);
svcEndHdl = ATT_GRP_END_HANDLE(pMsg->msg.findByTypeValueRsp.pHandlesInfo, 0);
}
// If the primary service was properly discovered on the server, attempt to discover the characteristics within
// the service.
if ((pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP) && (pMsg->hdr.status == bleProcedureComplete))
{
if (svcStartHdl != 0)
{
attReadByTypeReq_t req;
// Update the global discovery state.
discState = BLE_DISC_STATE_CHAR;
// Attempt to discover CUSTOM_PROFILE_CHAR2 on the server.
req.startHandle = svcStartHdl;
req.endHandle = svcEndHdl;
req.type.len = ATT_BT_UUID_SIZE;
req.type.uuid[0] = LO_UINT16(CUSTOMPROFILE_CHAR2_UUID);
req.type.uuid[1] = HI_UINT16(CUSTOMPROFILE_CHAR2_UUID);
// In order to write to a characteristic value, we need to discover the handle
// of the characteristic value.
// This function issues a ATT_READ_BY_TYPE_RSP event or a ATT_ERROR_RSP event when
// it completes.
uint8_t status = GATT_DiscCharsByUUID(pMsg->connHandle, &req, selfEntity);
}
}
}
else if (discState == BLE_DISC_STATE_CHAR)
{
// Characteristic found, store handle.
if (pMsg->method == ATT_READ_BY_TYPE_RSP)
{
// Get the index of the current connection.
// Note that there is only ever one connection.
uint8_t connIndex = SimpleCentral_getConnIndex(scConnHandle);
// Store the handle of the custom profile characteristic 2 value in the connection list.
connList[connIndex].char2Handle = BUILD_UINT16(pMsg->msg.readByTypeRsp.pDataList[3], pMsg->msg.readByTypeRsp.pDataList[4]);
}
// Characteristic discovery is done.
discState = BLE_DISC_STATE_IDLE;
// Now that we have discovered the characteristic we want to write to, enqueue an application message to do a
// characteristic value write.
if( SimpleCentral_enqueueMsg( SC_EVT_START_WRITING, 0, NULL ) != SUCCESS )
{
ICall_free(pMsg);
}
}
}
/*********************************************************************
* @fn SimpleCentral_addConnInfo
*
* @brief Add a device to the connected device list
*
* @return Index of the connected device list entry where the new connection
* info is put in.
* If there is no room, MAX_NUM_BLE_CONNS will be returned.
*/
static uint8_t SimpleCentral_addConnInfo(uint16_t connHandle, uint8_t *pAddr)
{
uint8_t i;
for (i = 0; i < MAX_NUM_BLE_CONNS; i++)
{
if (connList[i].connHandle == LINKDB_CONNHANDLE_INVALID)
{
// Found available entry to put a new connection info in
connList[i].connHandle = connHandle;
memcpy(connList[i].addr, pAddr, B_ADDR_LEN);
numConn++;
break;
}
}
return i;
}
/*********************************************************************
* @fn SimpleCentral_getConnIndex
*
* @brief Find index in the connected device list by connHandle
*
* @return the index of the entry that has the given connection handle.
* if there is no match, MAX_NUM_BLE_CONNS will be returned.
*/
static uint8_t SimpleCentral_getConnIndex(uint16_t connHandle)
{
uint8_t i;
for (i = 0; i < MAX_NUM_BLE_CONNS; i++)
{
if (connList[i].connHandle == connHandle)
{
break;
}
}
return i;
}
/*********************************************************************
* @fn SimpleCentral_enqueueMsg
*
* @brief Creates a message and puts the message in RTOS queue.
*
* @param event - message event.
* @param state - message state.
* @param pData - message data pointer.
*
* @return TRUE or FALSE
*/
static status_t SimpleCentral_enqueueMsg(uint8_t event, uint8_t state, uint8_t *pData)
{
uint8_t success;
scEvt_t *pMsg = ICall_malloc(sizeof(scEvt_t));
// Create dynamic pointer to message.
if (pMsg)
{
pMsg->hdr.event = event;
pMsg->hdr.state = state;
pMsg->pData = pData;
// Enqueue the message.
success = Util_enqueueMsg(appMsgQueue, syncEvent, (uint8_t *)pMsg);
return (success) ? SUCCESS : FAILURE;
}
return(bleMemAllocError);
}
/*********************************************************************
* @fn SimpleCentral_scanCb
*
* @brief Callback called by GapScan module while scanning is enabled.
* Various events are passed back to this callback function, which
* are then enqueued in the RTOS message queue, for later processing
* by the application.
*
* @param evt - event
* @param msg - message coming with the event
* @param arg - user argument
*
* @return none
*/
void SimpleCentral_scanCb(uint32_t evt, void* pMsg, uintptr_t arg)
{
uint8_t event;
if (evt & GAP_EVT_SCAN_DISABLED)
{
event = SC_EVT_SCAN_DISABLED;
}
else
{
return;
}
if(SimpleCentral_enqueueMsg(event, SUCCESS, pMsg) != SUCCESS)
{
ICall_free(pMsg);
}
}
/*********************************************************************
* @fn SimpleCentral_doConnect
*
* @brief Establish a connection to a peer device.
*
* @param index - The index of the advertisement report corresponding to
* the targeted peripheral device.
*
*/
void SimpleCentral_doConnect(uint8_t index)
{
GapScan_Evt_AdvRpt_t advRpt;
// Get the advertisement report corresponding to the desired peripheral
// connection. We need this in order to connect to the correct peripheral.
GapScan_getAdvReport(index, &advRpt);
// Initiate the connection with the desired peripheral.
// Continue trying to initiate a connection until GapInit_cancelConnect() is called.
// When the connection is successfully formed, a GAP_LINK_ESTABLISHED_EVENT is issued.
GapInit_connect(advRpt.addrType & MASK_ADDRTYPE_ID, advRpt.addr, DEFAULT_INIT_PHY, 0);
}
/*********************************************************************
* @fn SimpleCentral_doGattWrite
*
* @brief GATT Write
*
*/
void SimpleCentral_doGattWrite()
{
status_t status;
// Corresponds to writing "HI" at row 1, column 0
uint8_t charVals[5] = {0x01, 0x00, 0x48, 0x49, 0x00};
attWriteReq_t req;
req.pValue = GATT_bm_alloc(scConnHandle, ATT_WRITE_REQ, 5, NULL);
if ( req.pValue != NULL )
{
// Get the connection index of the current connection.
// Note that there is never more than one connection.
uint8_t connIndex = SimpleCentral_getConnIndex(scConnHandle);
// Fill in the write request.
req.handle = connList[connIndex].char2Handle;
req.len = 5;
req.pValue[0] = charVals[0];
req.pValue[1] = charVals[1];
req.pValue[2] = charVals[2];
req.pValue[3] = charVals[3];
req.pValue[4] = charVals[4];
req.sig = 0;
req.cmd = 0;
status = GATT_WriteCharValue(scConnHandle, &req, selfEntity);
if ( status != SUCCESS )
{
GATT_bm_free((gattMsg_t *)&req, ATT_WRITE_REQ);
}
}
}
/*********************************************************************
*********************************************************************/
Just like the simple_central example, my application starts by scanning (only once!) for advertisement packets. When scanning finishes, it looks through the advertisement report for a Bluetooth device with a specific device address. If the target device is found, a connection is established with it. Once the connection is formed, the service discovery process starts. As a first step, the client and server exchange MTU values. Then the client attempts to discover the service by looking for my custom service UUID in the attribute table (i.e. GATT_DiscPrimaryServiceByUUID). Once the starting handle of the service is retrieved, an attempt is made to discover one of the characteristics within the service. Once the handle of the characteristic value of interest is discovered, a write is attempted from client to server. At this point, things go awry.
Up until line 757 of the attached code, things seem to be working as I would expect. The central device always finds the target peripheral device and successfully connects to it. The starting handle of the discovered custom service matches what I see with BTool, and the handle of the characteristic value that is discovered also matches what I see with BTool. However, once SimpleCentral_enqueueMsg( SC_EVT_START_WRITING, 0, NULL ) is executed and SimpleCentral_doGattWrite() is called (at some point later in time), things stop making sense to me. The actual write within SimpleCentral_doGattWrite() is done via GATT_WriteCharValue(), and the status returned from this function call is always 0x16 - which means blePending. This implies that the client is busy doing something else at the moment the write is attempted, but I cannot figure out what it's doing. Another interesting symptom I've noticed is that at the moment the SC_EVT_START_WRITING application event is processed, the svcStartHdl and svcEndHdl seem to have been overwritten from their originally correct values. Since these values are only written in the service discovery procedure, this seems to imply that service discovery is happening multiple times in error. I tested against this hypothesis by simply counting the number of times the various stages of service discovery happen - and it turns out each only happens once, as expected. I've also tried adding an extended task sleep delay before doing the client-to-server write to give the BLE stack time to process anything that it might need to, but that doesn't seem to resolve the issue either.
Anyone have any idea what might be going wrong here?
Thank you in advance for the help.