1.1.1 OTA Framework Integration and Usage

The OTA DFU process generally involves four steps, executed using OTA commands from the image and command groups: Update Request, Start, Complete, and Reset. For detailed information on OTA commands, refer to chapter 2 BLE Document in MPLAB Harmony Wireless BLE.

This section also provides a walk through of the OTA DFU procedure. Below is a step-by-step guide on integrating the OTA framework into an application and using the OTA API.

Download the framework from the following link: MBD_Framework_v2.1.0.

  1. Setting up iOS project:
    1. In the project navigator, open the project settings.
    2. Select the target, and then open the "General" tab.
    3. Navigate to “Frameworks, Libraries, and Embedded Content” settings.
    4. Add MchpBLELib.xcframework.
      Figure 1-4. Import Framework in Project
  2. Conform to OTALibDelegate in ViewController and implement the delegate methods.
    //import the framework in view controller
    import MchpBLELib
     
    class ViewController: UIViewController, OTALibDelegate{
     
        func operationError(errorcode: UInt8, description: String){
            //handle error
        }
     
        func otaProgressUpdate(state: UInt8, value: [UInt8], updateBytes: UInt, otaTime: String){
            //handle update progress and firmware version.
        }
         
        func bleConnecting(bleScanState:Bool, discoveredPeripherals:[Any]){
            //handle scan state and discovered devices
        }
         
        func bleDidConnect(peripheralName:String){
            //handle connected device
        }
  3. Initialize: Initialize the OTAManager.
    var bleOTA: OTAManager?
     
    override func viewDidLoad() {
        super.viewDidLoad()
        if bleOTA == nil {
            bleOTA = OTAManager.sharedInstance()
            bleOTA?.otaDelegate = self
        }
    }
  4. Post initialization. Scan OTA device.
    bleOTA?.bleScan(filter: [.FilterByServiceUUID:ProfileServiceUUID.MCHP_OTA_SERVICE])
    When the OTAManager discovers a peripheral, it calls the bleConnecting(bleScanState:discoveredPeripherals:) method of its delegate object.
    var peripherals = Array<Any>()
    //delegate
    func bleConnecting(bleScanState:Bool, discoveredPeripherals:[Any]) {
            peripherals.removeAll()
            for peripheral in discoveredPeripherals{
                if peripheral is peripheralInfo{ //The peripheralInfo is a tuple. First element is BLE device name and second element is BLE RSSI. It is present inside the framework.
                    let peripheralInfo = peripheral as! peripheralInfo
                    peripherals.append(peripheralInfo)
                }
            }
        }
    Figure 1-5. Scan for Peripheral
  5. Pairing and connect the device. Once the Bluetooth Low Energy is connected, OTAManager calls the bleDidConnect(peripheralName:) method of its delegate object.
    bleOTA?.bleConnect(deviceName: "BLE_UART_D57D")
     
    //delegate
    func bleDidConnect(peripheralName: String) {
        //handle the connection state
    }
    Figure 1-6. Connection Process
  6. Prepare the OTA data for transmission. Once the Image ID is verified, OTA client sends the update request command to the server.

    OTA_Cancel can be called to reload the OTA image data.

    OTA image file format (refer to the 12.3.4 Bluetooth Low Energy OTA DFU Image File Definition section in the PIC32CXBZ2 Application Developer's Guide.)

    Image ID: Confirm whether the updated image is valid before starting DFU procedure, this ID is included in OTAU header of .bin file.

    func loadOTAImage() {
        let bundle = Bundle.main
        let fileUrl = bundle.url(forResource: "ble_uart_OTA_v1.1.1.0_image1", withExtension: "bin")
        if(fileUrl != nil){
            do {
                let file = try FileHandle(forReadingFrom: fileUrl!)
                let dat = file.readDataToEndOfFile()
                let image_version: [UInt8] = [0x01, 0x02, 0x03, 0x04]
                //Verify image id
                let (image_id, image_rev) = bleOTA?.OTAImageHeader(OTAImage: dat)
                if(image_id != nil){
                    bleOTA?.OTA_SetData(format: "BIN", data: dat, version: image_version, completion: {error in
                        if(error == nil){
                            print("Check OTA Image header information. Success")
                        } else {
                            print("Failed to load OTA file.")
                        }
                    })
                }  
            } catch { /* error handling here */
                print("Can't open text file")
                return
            }
        }
    }
    Figure 1-7. Read and Set OTA image file
    Delegate method otaProgressUpdate(state:value:updateBytes:otaTime:) will be called to notify the firmware version.
    func otaProgressUpdate(state: UInt8, value: [UInt8], updateBytes: UInt, otaTime: String) {
        if state == OTA_State.UpdateRequest.rawValue{
            if(value.count == 8){
                print("Device Version : \(String(format: "%d.%d.%d.%d", value[3],value[2],value[1],value[0])")
                print("Update Version : \(String(format: "%d.%d.%d.%d", value[7],value[6],value[5],value[4])")
            }
        }
    }
    Now the connected peripheral is ready for OTA DFU.
  7. OTA DFU Start/Stop:

    The OTA Client sends the fragmented image to the OTA Server and waits for a response. The OTA Server sends a notification to the OTA Client within 3 seconds after receiving the data to avoid a timeout error. Call SetOTADataTimeout to modify the timeout value. The data transfer continues until the total length of transfer data equals the size of the OTA image data.

    The delegate method otaProgressUpdate(state:value:updateBytes:otaTime:) notifies the progress value during the update.
    //start the ota upgrade process
    bleOTA?.OTA_Start()
     
    //delegate
    func otaProgressUpdate(state: UInt8, value: [UInt8], updateBytes: UInt, otaTime: String) {
        if state == OTA_State.UpdateStart.rawValue{
            if(value.count == 1){
                print("ota progress value = \(value[0])")
            }
        }
    }
    Figure 1-8. OTA Update Process
  8. OTA DFU Complete. OTA Server sends update complete command to OTA Client. Delegate method otaProgressUpdate(state:value:updateBytes:otaTime:) will be called to notify that the firmware update has been completed.

    func otaProgressUpdate(state: UInt8, value: [UInt8], updateBytes: UInt, otaTime: String) {
        if state == OTA_State.UpdateComplete.rawValue{
            print("Firmware upgrade: Success")
        }
    }

OTA DFU Error :

OTAManager calls this delegate method when a OTA Error occurs. An error code is generated to indicate the root cause of an error.

/**
 OTA Error code
 
 - OTA_FeatureNotSupport: BLE OTA Service is not found
 - BLE_ConnectionFail: BLE is disconnected
 - BLE_AbnormalDisconnect: BLE connection is abnormally disconnected
 - BleAdapter_PeripheralNotReady: Reserved
 - OTA_Command_InvalidState:  Responsed error code = Invalid State
 - OTA_Command_NotSupported: Responsed error code = Command not supported
 - OTA_Command_OperationFailed: Responsed error code = Operation fail
 - OTA_Command_InvalidParameter: Responsed error code = Invalid parameters
 - OTA_Command_UnspecifiedError: Responsed error code = Unknown error
 - OTA_Data_Result_Error: Data error, Device timeout
 - OTA_Data_Ack_Timeout: ACK timeout in data transmission
*/
public enum OTA_ErrorCode: UInt8 {
    case OTA_FeatureNotSupport = 0
    case BLE_ConnectionFail
    case BLE_AbnormalDisconnect
    case BleAdapter_PeripheralNotReady
    case OTA_Command_InvalidState
    case OTA_Command_NotSupported
    case OTA_Command_OperationFailed
    case OTA_Command_InvalidParameter
    case OTA_Command_UnspecifiedError
    case OTA_Request_NotSupported
    case OTA_Request_OperationFailed
    case OTA_Request_InvalidParameter
    case OTA_Complete_ValidationFail
    case OTA_Data_Result_Error
    case OTA_Data_Ack_Timeout
    case OTA_BT_OFF
}
func operationError(errorcode: UInt8, description: String) {
     switch errorcode {
            case OTA_ErrorCode.BLE_AbnormalDisconnect.rawValue:
                print("BLE disconnect or BT Off")
            case OTA_ErrorCode.OTA_Command_InvalidState.rawValue:
                print("Invalid State")
            case OTA_ErrorCode.OTA_Command_NotSupported.rawValue:
                print("Command not supported")
            case OTA_ErrorCode.OTA_Command_OperationFailed.rawValue:
                print("Command operation failed")
            case OTA_ErrorCode.OTA_Command_InvalidParameter.rawValue:
                print("Invalid parameters")
            case OTA_ErrorCode.OTA_Command_UnspecifiedError.rawValue:
                print("Unspecified Error")
            case OTA_ErrorCode.OTA_Request_NotSupported.rawValue:
                print("Possible reasons:\n\n1.OTA header parameter error\n2.Target device doesn't support\n3.Host doesn't accept the request")
            case OTA_ErrorCode.OTA_Request_OperationFailed.rawValue:
                print("Target device doesn't support OTA DFU")
            case OTA_ErrorCode.OTA_Request_InvalidParameter.rawValue:
                print("Possible reasons:\n\n1.Size is 0 or too large\n2.Size is not 16-byte alignment(internal FW DFU only)")
            case OTA_ErrorCode.OTA_Data_Result_Error.rawValue:
                print("Possible reasons:\n\n1.Responding error or timeout from host MCU(Host OTA)\n2.Host stops DFU")
            case OTA_ErrorCode.OTA_Data_Ack_Timeout.rawValue:
                print("No ACK from target device in transmission")
            case OTA_ErrorCode.OTA_Complete_ValidationFail.rawValue:
                print("OTA image fails to pass validation")
            case OTA_ErrorCode.OTA_BT_OFF.rawValue:
                print("BLE disconnect or BT Off")
            default:
                print("Unknown")
            }
        }
    }