Bluetooth Low Energy OTAP application integration
The Bluetooth Low Energy OTAP demo applications are standalone applications that only run the OTAP Server and the OTAP Client. In practice, however the OTAP Server and OTAP Client are used alongside with other functions. The OTAP functionality is used as a tool along with the main application on a device.
This section contains some guidelines on how to integrate OTAP functionality into other Bluetooth Low Energy applications.
OTAP server
Before any OTAP transactions can be done the application which acts as an OTAP Server must connect to a peer device and perform ATT service and characteristic discovery. Once the handles of the OTAP Service, OTAP Control Point and OTAP Data characteristics and their descriptors are found then OTAP communication can begin.
A good starting point for OTAP transactions for both the OTAP Server and The OTAP client is the moment the Server writes the OTAP Control Point CCCD to receive ATT Indications from the OTAP Client. At that point the Server can send a New Image Notification to the Client if it finds out what kind of device the client is through other means than the OTAP server. How this can be done is entirely application-specific. If the OTAP Server does not know exactly what kind of device is the OTAP Client it can wait for the Client to send a New Image Info Request. Again, the best behavior depends on application requirements.
Once OTAP communication begins then the OTAP Server just has to wait for commands from the OTAP Client and answer them. This behavior is almost completely stateless. An example state diagram for the OTAP Server application is shown in Figure 1.
The OTAP Server waits in an idle state until a valid Image Block Request command is received and then moves to a pseudo-state and starts sending the requested block. The transfer can be interrupted by some commands (Error Notification, Stop Image Transfer, and so on) or other events (disconnection, user interruption, and so on).
The otap_interface.h file contains infrastructure for sending and receiving OTAP Commands and parsing OTAP image files. Packed structure types are defined for all OTAP commands and type enumerations are defined for command parameter values and some configuration values like the data payloads for the different transfer methods.
To receive ATT Indications and ATT Write Confirmations from the OTAP Client the OTAP Server application registers a set of callbacks in the stack. This is done in the BluetoothLEHost_Initialized function.
App_RegisterGattClientProcedureCallback (BleApp_GattClientCallback);
App_RegisterGattClientIndicationCallback (BleApp_GattIndicationCallback);
This BleApp_GattIndicationCallback() function is called when any attribute is indicated so the handle of the indicated attribute must be checked against a list of expected handles. In our case, we are looking for the OTAP Control Point handle that was obtained during the discovery procedure.
The BleApp_GattIndicationCallback() function from the demo calls an application-specific function called BleApp_AttributeIndicated() in which the OTAP Commands are handled.
**static void BleApp\_AttributeIndicated**
(
deviceId_t deviceId,
uint16_t handle,
uint8_t* pValue,
uint16_t length
)
{
**if** (handle == mPeerInformation.customInfo.otapServerConfig.hControlPoint)
{
otapCommandVars.pValueTemp = pValue;
otapCommand_t* pOtaCmd = otapCommandVars.otapCommandTemp;
/* ... Missing code here ... */
/* If the OTAP Server does not have internal storage then all commands must be forwarded
* via the serial interface. */
FsciBleOtap_SendPkt (&(pOtaCmd->cmdId),
(uint8_t*)(&(pOtaCmd->cmd)),
length - gOtap_CmdIdFieldSize_c);
}
**elseif** (handle == otherHandle)
{
/* Handle other attribute indications here */
/* ... Missing code here ... */
}
**else**
{
/*! A GATT Client is trying to GATT Indicate an unknown attribute value.
* This should not happen. Disconnect the link. */
Gap_Disconnect (deviceId);
}
}
OTAP Server demo does not have internal storage, so all commands are forwarded via the serial interface.
To send OTAP Commands to the OTAP Client the application running the OTAP Server calls the OtapServer_SendCommandToOtapClient() function, which performs an ATT Write operation on the OTAP Control Point attribute.
**static void OtapServer\_SendCommandToOtapClient**
(deviceId_t otapClientDevId,
void* pCommand,
uint16_t cmdLength)
{
/* GATT Characteristic to be written - OTAP Client Control Point */
gattCharacteristic_t otapCtrlPointChar;
bleResult_t bleResult;
/* Only the value handle element of this structure is relevant for this operation. */
otapCtrlPointChar.value.handle = mPeerInformation.customInfo.otapServerConfig.hControlPoint;
otapCtrlPointChar.value.valueLength = 0;
otapCtrlPointChar.cNumDescriptors = 0;
otapCtrlPointChar.aDescriptors = NULL;
bleResult = GattClient_SimpleCharacteristicWrite (mPeerInformation.deviceId,
&otapCtrlPointChar,
cmdLength,
pCommand);
**
if** (gBleSuccess_c == bleResult)
{
otapServerData.lastCmdSentToOtapClient = (otapCmdIdt_t)(((otapCommand_t*)pCommand)->cmdId);
}
**else**
{
/*! A Bluetooth Low Energy error has occurred - Disconnect */
(void)Gap_Disconnect (otapClientDevId);
}
}
The ATT Confirmation for the ATT Write is received in the BleApp_GattClientCallback() set up earlier which receives a GATT procedure success message for a gGattProcWriteCharacteristicValue_c procedure type.
**static void BleApp\_GattClientCallback**(
deviceId_t serverDeviceId,
gattProcedureType_t procedureType,
gattProcedureResult_t procedureResult,
bleResult_t error
)
{
** union**
{
uint8_t errorTemp;
attErrorCode_t attErrorCodeTemp;
}attErrorCodeVars;
**if** (procedureResult == gGattProcError_c)
{
attErrorCodeVars.errorTemp = (uint8_t)error & 0xFFU;
attErrorCode_t attError = attErrorCodeVars.attErrorCodeTemp;
if (attError == gAttErrCodeInsufficientEncryption_c ||
attError == gAttErrCodeInsufficientAuthorization_c ||
attError == gAttErrCodeInsufficientAuthentication_c)
{
**\#if **gAppUsePairing_d
/* Start Pairing Procedure */
(void)Gap_Pair (serverDeviceId, &gPairingParameters);
**\#endif**
}
BleApp_StateMachineHandler (serverDeviceId, mAppEvt_GattProcError_c);
}
**else if** (procedureResult == gGattProcSuccess_c)
{
**switch**(procedureType)
{
/* ... Missing code here... */
** case** gGattProcWriteCharacteristicValue_c:
{
BleApp_HandleValueWriteConfirmations (serverDeviceId);
}
**break;**
** default:**
; /* For MISRA compliance */
** break;**
}
BleApp_StateMachineHandler(serverDeviceId, mAppEvt_GattProcComplete_c);
}
**else**
{
; /* For MISRA compliance */
}
}
The BleApp_HandleValueWriteConfirmations() function deals with ATT Write Confirmations based on the requirements of the application.
There are two possible transfer methods for Image Chunks, the ATT transfer method and the L2CAP transfer method. The OTAP server is prepared to handle both, as requested by the OTAP Client.
To be able to use the L2CAP transfer method, the OTAP Server application must register a L2CAP LE PSM and 2 callbacks: a data callback and a control callback. This is done by using the BluetoothLEHost_Initialized() function.
/* Register OTAP L2CAP PSM */
L2ca_RegisterLePsm (gOtap_L2capLePsm_c,
gOtapCmdImageChunkCocLength_c); /*!< The negotiated MTU must be higher than the biggest data chunk that is sent fragmented */
...
App_RegisterLeCbCallbacks(BleApp_L2capPsmDataCallback, BleApp_L2capPsmControlCallback);
The data callback BleApp_L2capPsmDataCallback() is not used by the OTAP Server.
The control callback is used to handle L2CAP LE PSM connection requests from the OTAP Client and other events: PSM disconnections, No peer credits, and so on. The OTAP Client must initiate the L2CAP PSM connection if it wants to use the L2CAP transfer method.
**static** **void** **BleApp\_L2capPsmControlCallback**(l2capControlMessageType_t messageType,
**void*** pMessage)
{
**switch** (messageType)
{
**case** *gL2ca\_LePsmConnectRequest\_c*:
{
l2caLeCbConnectionRequest_t *pConnReq = ( l2caLeCbConnectionRequest_t *)pMessage;
/* Respond to the peer L2CAP CB Connection request - send a connection response. */
L2ca_ConnectLePsm (gOtap_L2capLePsm_c,
pConnReq-> deviceId,
mAppLeCbInitialCredits_c);
**break**;
}
**case** *gL2ca\_LePsmConnectionComplete\_c*:
{
l2caLeCbConnectionComplete_t *pConnComplete = ( l2caLeCbConnectionComplete_t *)pMessage;
**if** (pConnComplete->result == *gSuccessful\_c*)
{
/* Set the application L2CAP PSM Connection flag to TRUE because there is no gL2ca_LePsmConnectionComplete_c
* event on the responder of the PSM connection. */
otapServerData. l2capPsmConnected = TRUE;
otapServerData. l2capPsmChannelId = pConnComplete->cId;
}
**break**;
}
**case** *gL2ca\_LePsmDisconnectNotification\_c*:
{
l2caLeCbDisconnection_t *pCbDisconnect = ( l2caLeCbDisconnection_t *)pMessage;
/* Call App State Machine */
BleApp_StateMachineHandler (pCbDisconnect-> deviceId, *mAppEvt\_CbDisconnected\_c*);
otapServerData. l2capPsmConnected = FALSE;
**break**;
}
**case** *gL2ca\_NoPeerCredits\_c*:
{
l2caLeCbNoPeerCredits_t *pCbNoPeerCredits = ( l2caLeCbNoPeerCredits_t *)pMessage;
L2ca_SendLeCredit (pCbNoPeerCredits-> deviceId,
otapServerData. l2capPsmChannelId,
mAppLeCbInitialCredits_c);
**break**;
}
**case** *gL2ca\_LocalCreditsNotification\_c*:
{
l2caLeCbLocalCreditsNotification_t *pMsg = ( l2caLeCbLocalCreditsNotification_t *)pMessage;
**break**;
}
**default**:
**break**;
}
}
The ATT transfer method is supported by default but the L2CAP transfer method only works if the OTAP Client opens an L2CAP PSM credit-oriented channel.
To send data chunks to the OTAP Client the OTAP Server application calls the OtapServer_SendCImgChunkToOtapClient() function which delivers the chunk via the selected transfer method. For the ATT transfer method the chunk is sent via the GattClient_CharacteristicWriteWithoutResponse() function and for the L2CAP transfer method the chunk is sent via the L2ca_SendLeCbData() function.
**static void OtapServer\_SendCImgChunkToOtapClient** (deviceId_t otapClientDevId,
**void*** pChunk,
uint16_t chunkCmdLength)
{
bleResult_t bleResult = gBleSuccess_c;
**if** (otapServerData.transferMethod == gOtapTransferMethodAtt_c)
{
/* GATT Characteristic to be written without response - OTAP Client Data */
gattCharacteristic_t otapDataChar;
/* Only the value handle element of this structure is relevant for this operation. */
otapDataChar.value.handle = mPeerInformation.customInfo.otapServerConfig.hData;
bleResult = GattClient_CharacteristicWriteWithoutResponse
(mPeerInformation.deviceId,
&otapDataChar,
chunkCmdLength,
pChunk);
}
**else if** (otapServerData.transferMethod == gOtapTransferMethodL2capCoC_c)
{
bleResult = L2ca_SendLeCbData (mPeerInformation.deviceId,
otapServerData.l2capPsmChannelId,
pChunk,
chunkCmdLength);
}
**if** (gBleSuccess_c != bleResult)
{
/*! A Bluetooth Low Energy error has occurred - Disconnect */
Gap_Disconnect (otapClientDevId);
}
}
The OTAP Server demo application relays all commands received from the OTAP Client to a PC through the FSCI type protocol running over a serial interface. It also directly relays all responses from the PC back to the OTAP Client.
Other implementations can bring the image to an external memory through other means of communication and directly respond to the OTAP Client requests.
Parent topic:Bluetooth Low Energy OTAP application integration
OTAP client
An application running an OTAP Client must wait for an OTAP Server to connect and perform service and characteristic discovery before performing any OTAP-related operations. OTAP transactions can begin only after the OTAP Server writes the OTAP Control point CCC Descriptor to receive ATT Notifications. After this is done, bidirectional communication is established between the OTAP Server and Client and OTAP transactions can begin.
The OTAP Client can advertise the OTAP Service via the demo application. Optionally, the OTAP Server may already know the advertising device has an OTAP Service based on application-specific means. In both situations, the OTAP Server must discover the handles of the OTAP Service and its characteristics.
In addition to the OTAP Service instantiated in the GATT Database, the OTAP Client needs to have some storage capabilities for the downloaded image file.
How to put the OTAP Service in the GATT Database is described in The OTAP Service and Characteristics.
The upgrade image storage capabilities in the demo OTAP Client applications are handled by the OtaSupport module from the Framework, which contains support modules and drivers. The OtaSupport module has support for both internal storage (a part of the internal flash memory is reserved for storing the upgrade image) and external storage (a SPI flash memory chip).
The demo applications use internal storage by default. The internal storage is viable only if there is enough space in the internal flash for the upgrade image – the flash in this case should be at least twice the size of the largest application. The OtaSupport module also needs the Eeprom module from the Framework to work correctly.
The OtaSupport module also includes functionality for configuring the OTACFG IFR sections after the image is received in order to enable the ROM bootloader to perform the actual image update.
To use the OtaSupport module several configuration options must be set up in both the source files and the linker options of the toolchain.
To use internal storage, set up the gUseInternalStorageLink_d=1
symbol in the linker configuration window (Linker->Config tab in the IAR project properties) and set the gAppOtaExternalStorage_c value
to (0
) in the app_preinclude.h
file:
/*! Define as 1 to place OTA storage in external flash */
#define gAppOtaExternalStorage_c (0)
The OTAP demo applications for the IAR EW IDE have some settings in the Linker options tab which must be configured to use OtaSupport and the OTAP Bootloader. In the Project Target Options->Linker->Config tab, 3 symbols must be correctly defined. To use NVM storage, thegUseNVMLink_d
symbol must be set to 1
. The gUseInternalStorageLink_d
symbol must be set to 0
when OTAP external storage is used and to 1
when the internal storage is used. The gEraseNVMLink_d
must be set to 0.
An example linker configuration window for IAR is shown in Figure 1.
Note: The gEraseNVMLink_d=1 IAR linker flag places some dummy bytes into the NVM region to invalidate the data and force the application to erase the entire NVM region. When generating an image for the OTA upgrade, this flag must be set to 0. This results in a smaller image size being transferred and lower power consumption. If the NVM region must be erased after the upgrade process, the “Preserve NVM” checkbox (from the Over The Air programming tool) should be unchecked.
For MCUXpresso IDE, the linker settings required for OTAP applications can be set up from the “SDK Import Wizard” or from the “Project Properties ->MCU settings”. Refer to Figure 2.
The demo applications use internal storage by default. To enable external storage support for MCUX, set the gAppOtaExternalStorage_c
value to (1
) in the app_preinclude.h
file. Also remove the INT_STORAGE
section (from Project Properties-> MCU settings) and extend the PROGRAM_FLASH
section as shown in the Figure 3.
Once the application starts and bidirectional OTAP communication is established via the OTAP Service, then the OTAP Client must determine if the connected OTAP Server has a newer image than the one currently present on the device. This can be done in two ways:
The OTAP Server knows by some application-specific means that it has a newer image and sends a New Image Notification to the OTAP Client or
The OTAP Client sends a New Image Info Request to the OTAP Server and waits for a response. This example application uses the second method.
The New Image Info Request contains enough information about the currently running image to allow the OTAP Server to determine if it has a newer image for the requesting device. The New Image Info Response contains enough information for the OTAP Client to determine if the “deadvertised” image is newer and it wants to download it. The best method is entirely dependent on application requirements.
An example function that checks if an ImageVerison field from a New Image Notification or a New Image Info Response corresponds to a newer image (based on the suggested format of this field) is provided in the OTAP Client demo applications. The function is called OtapClient_IsRemoteImageNewer().
The OTAP Client application is a little more complicated than the OTAP Server application because more state information needs to be handled (current image position, current chunk sequence number, image file parsing information, and so on). An example state diagram for the OTAP Client is shown below. The Figure 4 briefly lists the steps of the image download process. Note that some of the states may not be explicitly present in the demo applications.
After the OTAP Client determines that the peer OTAP Server has a suitable upgrade image available, it can start the download process. This is done by sending multiple Image Block Request messages and waiting for the Image Chunks via the selected transfer method.
While receiving the image file blocks, the OTAP Client application parses the image file. In case any parameter of an image file sub-element is invalid or the image file format is invalid, it sends an Error Notification to the OTAP Server and tries to restart the download process from the beginning or a known good position.
When an Image Chunk is received, its sequence number is checked and its content is parsed in the context of the image file format. If the sequence number is not as expected, then the block transfer is restarted from the last known good position. When all chunks of an Image Block are received, the next block is requested, if there are more blocks to download. When the last Image Block in an image file is received, then the image integrity is checked (the received CRC from the Image File CRC sub-element is compared to the computed CRC).
The computed image integrity initialization and intermediary value must be reset to ‘0
’ before starting or restarting an image download. If the image integrity check fails then the image download process is restarted from the beginning. If the image integrity check is successful, then the Image Download Complete
message is sent to the OTAP Server, the OTACFG IFR is updated and the MCU is restarted. After the restart, the ROM bootloader kicks in and writes the new image to the flash memory, afterwards giving CPU control to the newly installed application.
If at any time during the download process, a Link Layer disconnection occurs, then the image download process is restarted from the last known good position when the link is re-established.
As noted earlier, the OTAP Client application needs to handle a lot of state information. In the demo application, all this information is held in the otapClientData structure of the otapClientAppData_t type. The type is defined and the structure is initialized in the otap_client.c file of the application. This structure is defined and initialized differently for the OTAP Client ATT and L2CAP example applications. Mainly, the transferMethod member of the structure is constant and has different values for the two example applications and the L2CAP application structure has an extra member.
To receive write notifications when the OTAP Server writes the OTAP Control Point attribute and ATT Confirmations when it indicates the OTAP Control Point attribute, the OTAP Client application must register a GATT Server callback and enable write notifications for the OTAP Control Point attribute. This is done in the BluetoothLEHost_Initialized() function in the otap_client_att.c/otap_client_l2cap_credit.c file.
**static void BluetoothLEHost\_Initialized**(void)
{
/* ... Missing code here ... */
/* Register stack callbacks */
(void)App_RegisterGattServerCallback (BleApp_GattServerCallback);
/* ... Missing code here ... */
}
The BleApp_GattServerCallback() function handles all incoming communication from the OTAP Server.
**static void BleApp\_GattServerCallback **(deviceId_t deviceId, gattServerEvent_t* pServerEvent)
{
**switch** (pServerEvent->eventType)
{
/* ... Missing code here ... */
**case** gEvtCharacteristicCccdWritten_c:
{
OtapClient_CccdWritten (deviceId,
pServerEvent->eventData.charCccdWrittenEvent.handle,
pServerEvent->eventData.charCccdWrittenEvent.newCccd);
}
**break;**
** case** gEvtAttributeWritten_c:
{
OtapClient_AttributeWritten (deviceId,
pServerEvent->eventData.attributeWrittenEvent.handle,
pServerEvent->eventData.attributeWrittenEvent.cValueLength,
pServerEvent->eventData.attributeWrittenEvent.aValue);
}
** break;**
**case** gEvtAttributeWrittenWithoutResponse_c:
{
OtapClient_AttributeWrittenWithoutResponse (deviceId,
pServerEvent->eventData.attributeWrittenEvent.handle,
pServerEvent->eventData.attributeWrittenEvent.cValueLength,
pServerEvent->eventData.attributeWrittenEvent.aValue);
}
**break;**
**case **gEvtHandleValueConfirmation_c:
{
OtapClient_HandleValueConfirmation (deviceId);
}
** break;**
/* ... Missing code here ... */
**default:**
; /* For MISRA compliance */
**break;**
}
}
When the OTAP Server Writes a CCCD the BleApp_GattServerCallback() function calls the OtapClient_CccdWritten() function which sends a New Image Info Request when the OTAP Control Point CCCD is written it – this is the starting point of OTAP transactions in the demo applications.
When an ATT Write Request is made by the OTAP Server the the BleApp_GattServerCallback() function calls the OtapClient_AttributeWritten() function which handles the data as an OTAP command. Only writes to the OTAP Control Point are handled as OTAP commands. For each command received from the OTAP Server there is a separate handler function which performs required OTAP operations. These are:
OtapClient_HandleNewImageNotification()
OtapClient_HandleNewImageInfoResponse()
OtapClient_HandleErrorNotification()
When an ATT Write Command (GATT Write Without Response) is sent by the OTAP Server the BleApp_GattServerCallback() function calls the OtapClient_AttributeWrittenWithoutResponse() function which handles Data Chunks if the selected transfer method is ATT and returns an error if any problems are encountered. Data chunks are handled by the OtapClient_HandleDataChunk() function.
**static void BleApp\_AttributeWrittenWithoutResponse** (deviceId_t deviceId,
uint16_t handle,
uint16_t length,
uint8_t* pValue)
{
/* ... Missing code here ... */
**if** (handle == value_otap_data)
{
/* ... Missing code here ... */
**if** (otapClientData.transferMethod == gOtapTransferMethodAtt_c)
{
**if** (((otapCommand_t*)pValue)->cmdId == gOtapCmdIdImageChunk_c)
{
OtapClient_HandleDataChunk (deviceId,
length,
pValue);
}
}
/* ... Missing code here ... */
}
/* ... Missing code here ... */
}
Finally, when an ATT Confirmation is received for a previously sent ATT Indication the BleApp_GattServerCallback() function calls the OtapClient_HandleValueConfirmation() function, which performs the necessary OTAP operations based on the last sent command to the OTAP Server. This is done using separate confirmation handling functions for each command that is sent to the OTAP Server. These functions are:
OtapClient_HandleNewImageInfoRequestConfirmation()
OtapClient_HandleImageBlockRequestConfirmation()
OtapClient_HandleImageTransferCompleteConfirmation()
OtapClient_HandleErrorNotificationConfirmation()
OtapClient_HandleStopImageTransferConfirmation()
Outgoing communication from the OTAP Client to the OTAP Server is done using the OtapCS_SendCommandToOtapServer() function. This function writes the value to be indicated to the OTAP Control Point attribute in the GATT database and then calls the *OtapCS_SendControlPointIndication()*which checks if indications are enabled for the target device and sends the actual ATT Indication. Both functions are implemented in the otap_service.c file.
bleResult_t **OtapCS\_SendCommandToOtapServer** (uint16_t serviceHandle,
void* pCommand,
uint16_t cmdLength)
{
**union**
{
uint8_t* uuid_char_otap_control_pointTemp;
bleUuid_t* bleUuidTemp;
}bleUuidVars;
uint16_t handle;
bleResult_t result;
bleUuidVars.uuid_char_otap_control_pointTemp = uuid_char_otap_control_point;
bleUuid_t* pUuid = bleUuidVars.bleUuidTemp;
/* Get handle of OTAP Control Point characteristic */
result = GattDb_FindCharValueHandleInService(serviceHandle,
gBleUuidType128_c, pUuid, &handle);
**if** (result == gBleSuccess_c)
{
/* Write characteristic value */
result = GattDb_WriteAttribute(handle,
cmdLength,
(uint8_t*)pCommand);
** if** (result == gBleSuccess_c)
{
/* Send Command to the OTAP Server via ATT Indication */
result = OtapCS_SendControlPointIndication (handle);
}
}
** return** result;
}
**static** bleResult_t **OtapCS\_SendControlPointIndication** (uint16_t handle)
{
uint16_t hCccd;
bool_t isIndicationActive;
/* Get handle of CCCD */
GattDb_FindCccdHandleForCharValueHandle (handle, &hCccd);
Gap_CheckIndicationStatus (...);
**return** GattServer_SendIndication (...);
}
The otap_interface.h file contains all the necessary information for parsing and building OTAP commands (packed command structures type definitions, command parameters enumerations, and so on).
For the two possible image transfer methods (ATT and L2CAP) there are two separate demo applications. To be able to use the L2CAP transfer method the OATP Client application must register a L2CAP LE PSM and 2 callbacks: a data callback and a control callback. This is done in the OtapClient_Config() function.
/* Register OTAP L2CAP PSM */
L2ca_RegisterLePsm (gOtap_L2capLePsm_c,
gOtapCmdImageChunkCocLength_c); /*!< The negotiated MTU must be higher than the biggest data chunk that is sent fragmented */
...
App_RegisterLeCbCallbacks(BleApp_L2capPsmDataCallback, BleApp_L2capPsmControlCallback);
The control callback is used to handle L2CAP LE PSM-related events: PSM disconnections, PSM Connection Complete, No peer credits, and so on.
**static void BleApp\_L2capPsmControlCallback**
(l2capControlMessageType_t messageType,
**void*** pMessage)
{
**switch** (messageType)
{
**case** **gL2ca\_LePsmConnectRequest\_c:**
{
l2caLeCbConnectionRequest_t *pConnReq =
(l2caLeCbConnectionRequest_t *)pMessage;
/* This message is unexpected on the OTAP Client, the OTAP Client sends L2CAP
* PSM connection requests and expects L2CAP PSM connection responses.
* Disconnect the peer. */
Gap_Disconnect (pConnReq->deviceId);
**break**;
}
**case** **gL2ca\_LePsmConnectionComplete\_c:**
{
l2caLeCbConnectionComplete_t *pConnComplete =
(l2caLeCbConnectionComplete_t *)pMessage;
/* Call the application PSM connection complete handler. */
OtapClient_HandlePsmConnectionComplete (pConnComplete);
**break**;
}
**case** **gL2ca\_LePsmDisconnectNotification\_c:**
{
l2caLeCbDisconnection_t *pCbDisconnect = (l2caLeCbDisconnection_t *)pMessage;
/* Call the application PSM disconnection handler. */
OtapClient_HandlePsmDisconnection (pCbDisconnect);
**break**;
}
**case** **gL2ca\_NoPeerCredits\_c:**
{
l2caLeCbNoPeerCredits_t *pCbNoPeerCredits =
(l2caLeCbNoPeerCredits_t *)pMessage;
L2ca_SendLeCredit (pCbNoPeerCredits->deviceId,
otapClientData.l2capPsmChannelId,
mAppLeCbInitialCredits_c);
**break**;
}
/* ... Missing code here ... */
**case** **gL2ca\_Error\_c:**
{
/* Handle error */
break;
}
**default:**
; /* For MISRA compliance */
** break;**
}
The OTAP Client must initiate the L2CAP PSM connection if it wants to use the L2CAP transfer method; this can be done using the L2ca_ConnectLePsm() function. The L2ca_ConnectLePsm() function is called by the OtapClient_ContinueImageDownload() if the transfer method is L2CAP and the PSM is found to be disconnected.
**static void OtapClient\_ContinueImageDownload** (deviceId_t deviceId)
{
/* ... Missing code here ... */
/* Check if the L2CAP OTAP PSM is connected and if not try to connect and exit immediately. */
**if** ((otapClientData.l2capPsmConnected == FALSE) &&
(otapClientData.state != mOtapClientStateImageDownloadComplete_c))
{
L2ca_ConnectLePsm (gOtap_L2capLePsm_c,
deviceId,
mAppLeCbInitialCredits_c);
**bValidState = FALSE;**;
}
/* ... Missing code here ... */
}
The PSM data callback BleApp_L2capPsmDataCallback() is used by the OTAP Client to handle incoming image file parts from the OTAP Server.
**static void BleApp\_L2capPsmDataCallback** (deviceId_t deviceId,
uint8_t* pPacket,
uint16_t uint16_t lePsm,
uint16_t packetLengt
{
OtapClient_HandleDataChunk (deviceId,
packetLength,
pPacket);
}
All data chunks regardless of their source (ATT or L2CAP) are handled by the OtapClient_HandleDataChunk() function. This function checks the validity of Image Chunk messages, parses the image file, requests the continuation or restart of the image download and triggers the bootloader when the image download is complete.
**static void OtapClient\_HandleDataChunk** (deviceId_t deviceId, uint16_t length, uint8_t* pData);
The Image File CRC Value is computed on the fly as the image chunks are received using the OTA_CrcCompute() function from the OtaSupport module which is called by the OtapClient_HandleDataChunk() function. The OTA_CrcCompute() function has a parameter for the intermediary CRC value which must be initialized to 0 every time a new image download is started.
The actual write of the received image parts to the storage medium is also done in the OtapClient_HandleDataChunk() function using the OtaSupport module. This is achieved using the following functions:
OTA_StartImage() – called before the start of writing a new image to the storage medium.
OTA_CancelImage() – called whenever an error occurs and the image download process needs to be stopped/restarted from the beginning.
OTA_PushImageChunk() – called to write a received image chunk to the storage medium. Note that only the Upgrade Image Sub-element of the image file is actually written to the storage medium.
OTA_CommitImage() - called to set up what parts of the downloaded image are written to flash and other information for the bootloader. The Value field of the Sector Bitmap Sub-element of the Image File is given as a parameter to this function.
OTA_SetNewImageFlag() - called to configure the OTACFG IFR when a new image has been successfully received. When the MCU is reset, the ROM bootloader transfers the new image from the storage medium to the program flash.
To continue the image download process after a block is transferred or to restart it after an error has occurred the OtapClient_ContinueImageDownload() function is called. This function is used in multiple situations during the image download process.
To summarize, an outline of the steps required to perform the image download process is shown below:
Wait for a connection from an OTAP Server
Wait for the OTAP Server to write the OTAP Control Point CCCD
Ask or wait for image information from the server
If a new image is available on the server, start the download process using the OtapClient_ContinueImageDownload() function.
If the transfer method is L2CAP CoC, then initiate a PSM connection to the OTAP Server
Repeat while image download is not complete.
Wait for image chunks.
Call the OtapClient_HandleDataChunk() function for all received image chunks regardless of the selected transfer method.
Check image file header integrity using the OtapClient_IsImageFileHeaderValid() function.
Write the Upgrade Image Sub-element to the storage medium using OtaSupport module functions.
When the download is complete, check image integrity.
If the integrity check is successful, commit the image using the Sector Bitmap Sub-element and trigger the bootloader
If integrity check fails, restart the image download from the beginning
If the download is not complete, ask for a new image chunk.
If any error occurs during the processing of the image chunk, restart the download from the last known good position.
If an image was successfully downloaded and transferred to the storage medium and the bootloader triggered, then reset the MCU to start the flashing process of the new image.
Parent topic:Bluetooth Low Energy OTAP application integration
Parent topic:Over the Air Programming (OTAP)