SureshJoshi.com ▼

BLE113 OTA (Over-The-Air) Programming


2015-03-09

This one has been a long time coming… Ever since the BLE113 came out, the functionality I’ve been waiting for the most was the over-the-air programming. I’ve had the BLE113 OTA functionality working for a few months, but I’m only now getting around to blogging about it!

\<Sidebar> A lot of my other posts refer to BitBucket repos for examples. I’m in the process of switching the open-source code over to Github, and using BitBucket only for my private repos. My Github accounts are: https://github.com/sureshjoshi and https://github.com/RobotPajamas/ \</Sidebar>

In order to keep this post even reasonably sized, I’ll be handwaving a lot of the OTA functionality and will try to keep it relatively simple, and I will also refer to my Github code a fair amount.

Requirements

.OTA files

Before a recent version of the BLE SDK, a lot of people posted hacks on how to turn .hex files into the correct format to transfer them to the BLE113 device. However, now the correct way to do it is to simply use BGBuild, and add this to the .bgproj

    <device type="ble113-m256k" />
    <boot fw="bootota" />
    <ota out="RobotPajamas.ota" firmware="false" />
    <!-- firmware=false only updates the BGScript and GATT, =true updates the BT Stack and BGAPI, but is MUCH bigger -->
    <!-- <ota out="RobotPajamas.ota" firmware="true" /> -->

Please note the ‘firmware’ attribute on the ota tag. Not many people realize that you can set that to false and when doing so, the BGScript SDK is not compiled. As a result, OTA files go from ~80kB to 4kB, which speeds up OTA programming significantly.

Adding endpoints

The standard OTA endpoints (one for data, one for control) are those used in Bluegiga’s example code. They’ve baked them into BLEGUI as well, so that if your device’s GATT has these endpoints, the BLEGUI DFU OTA panel automatically recognizes them.

While there’s nothing intrinsically wrong with using them, they use the ‘write_no_response’ profile when sending data, and I’m pretty uncomfortable with that. What can happen (and I’ve seen it happen MANY times) is that a packet isn’t sent correctly, there is no notification to the sender, and the whole OTA process craps out and you need to reset the device.

To that end, I’ve created a new endpoint which uses the ‘write’ profile, however, to keep backwards compatibility, I left in the default OTA endpoints. This works like a gem, because on senders who can use write’s with responses, we get a robust OTA - and to keep compatibility with BLEGUI (which hardcodes using write_no_response into the software) - we use the old endpoints!

    <service uuid="1d14d6ee-fd63-4fa1-bfa4-8f47b42119f0">
        <description>OTA Service</description>

        <!-- Standard OTA endpoints for BLEGUI to work -->
        <characteristic uuid="f7bf3564-fb6d-4e53-88a4-5e37e0326063" id="ota_control">
            <properties write="true" />
            <value length="1" type="user" />
        </characteristic>
        <characteristic uuid="984227f3-34fc-4045-a5d0-2c581f81a153" id="ota_data">
            <properties write_no_response="true" />
            <value length="20" />
        </characteristic>

        <!-- More robust OTA endpoints for Android and iOS (BLEGUI won't work with these) -->
        <characteristic uuid="00737572-6573-686a-6f73-68692e636f6d" id="ota_data_w_response">
            <properties write="true" />
            <value length="20" />
        </characteristic>
            <characteristic uuid="01737572-6573-686a-6f73-68692e636f6d" id="ota_control_wo_response">
            <properties write_no_response="true" />
            <value length="1" type="user" />
        </characteristic>

    </service>

Doing the work

The code which actually does the writing of data to flash, erasing flash pages, and reseting to DFU - I’ve just pilfered all of that from the Bluegiga sample code. To recap…

For safety, I erase all the flash pages every time the device reboots - so I’m always starting with a clean slate:

event system_boot(major, minor, patch, build, ll_version, protocol_version, hw)
    # Erase internal flash dedicated for OTA
    # For this to work, the script timeout has to be increased from the default
    # value in application configuration file (config.xml)
    erase_page = 0
    while erase_page < max_erase_page
        call flash_erase_page(erase_page)
        erase_page = erase_page + 1
    end while

For this to work, make sure you have the following in your config.xml:

    <user_data size="0x20000" />
    <!-- Increase script timeout from default to allow flash erase in loop -->
    <script_timeout value="10000" />

And now the business:

# Handles OTA Control Point Attribute (commands) and OTA Data Attribute (firmware update) writes
# and performs the necessary actions
procedure handle_ota_control(connection, offset, value_len, value_data())
    # Check if OTA control point attribute is written by the remote device and execute the command
        # Command 0 : Erase flash block 0 (0x0-0x1FFFF)
        # Command 1 : Erase flash block 1 (0x10000-0x3FFFF)
        # Command 2 : Reset DFU data pointer
        # Command 3 : Boot to DFU mode
        # Command 4 : Power up external flash
    # In case of errors application error code 0x80 is returned to the remote device
    # In case the flash comms fails error code 0x90 is returned to the remote device

    # Attribute is user attribute, reason is always write_request_user
    if value_len > 1 || offset > 0 then
        # Not a valid command -> report application error code : 0x80
        call attributes_user_write_response(connection, $80)
    else
        command = value_data(0:1)

        if command > 4 then # Unknown command -> report application error code : 0x80
            call attributes_user_write_response(connection, $80)
        else
            if command = 3 then # Command 3 received -> Boot to DFU mode
                call system_reset(1)
            else
                # Other commands are not used, but still accepted in order
                # to be compatible with the external flash OTA
                # implementation
                call attributes_user_write_response(connection, $0)
            end if
        end if
    end if
end


# Incoming data event listener
event attributes_value(connection, reason, handle, offset, value_len, value_data)

    # Both ota_control endpoints run the same code, however, the wo_response just ignores most of this
    if handle = ota_control || handle = ota_control_wo_response then
        call handle_ota_control(connection, offset, value_len, value_data(0:value_len))
    end if

    # Check if OTA data attribute is written which carries the firmware update
    # and store the data to the internal flash
    if handle = ota_data || handle = ota_data_w_response then
        call flash_write_data(dfu_pointer, value_len, value_data(0:value_len))
        dfu_pointer = dfu_pointer + value_len
    end if
end

The only changes that I made to the above are that I have both endpoints checked during attribute event handlers.

OTA upload clients

There are theoretically a lot of options here, but I’ll stick with the main 3: BLEGUI, iOS, and Android

BLEGUI:

I’m not going to go into detail on how to upload your firmware from BLEGUI, as BlueGiga (or, I guess kinda Silicon Labs now) has gone into depth on that. However, if you still need a primer, check out Dr Kroll’s website.

iOS:

I’ve written a super-simple sample application on how to update BLE113 firmware from an iOS device using CoreBluetooth (in my case, I added a block-based abstraction layer to the mix to make my life easier).

https://github.com/RobotPajamas/ble113-ota-ios

The pertinent information is this… OTA files are built to be multiples of 16, so I created a data slicer which would progressively send along 16-byte chunks of data to be sent across to the BLE113. I could have technically sent 20-byte packets (increasing my throughput), however, everything needs to be 256-byte aligned and I frankly was too lazy to deal with remainders from a modulo   :)

The highlight of the app (and really the only important section of code dealing with OTA) is this little guy:

- (void)updateFirmware:(NSData *)firmwareBytes
              progress:(RPDeviceFirmwareUpdateCallback)progressCallback
            completion:(RPDeviceFirmwareUpdateCallback)completionCallback
{
    // Create a filechunker to split up the incoming file into sendable chunks
    RPJDataChunker *chunker = [[RPJDataChunker alloc] initWithData:firmwareBytes chunkLength:16];

    // Create block for recursive send - need weak reference to avoid retain cycle
    __block __weak LGCharacteristicWriteCallback weakFirmwareCallback;
    LGCharacteristicWriteCallback firmwareCallback;
    weakFirmwareCallback = firmwareCallback = ^(NSError *error){
        if ([chunker hasNext])
        {
            NSData *nextFrame = [chunker next];
            progressCallback([chunker currentChunk], [chunker totalChunks]);
            [self writeFirmwareFrameWithCompletion:nextFrame
                                        completion:weakFirmwareCallback];
        }
        else
        {
            // Reset the chunker, just in case
            [chunker reset];
            [self sendResetToDFU];
            completionCallback([chunker totalChunks], [chunker totalChunks]);
        }
    };

    // Recursively send chunks to wristband and notify callback
    NSData *initialFrame = [chunker next];
    [self writeFirmwareFrameWithCompletion:initialFrame
                                completion:firmwareCallback];
}

The hard part about that was using weak references to not cause cyclical leak issues.

And that’s about it. The Github repo includes all firmware and a sample app (the entire app is there to support the above lines of code). When uploading, a progress bar will show up informing the user how far along the upload is (and in this case, I used the write_with_response functionality).

Android:

Before I ever wrote that iOS app, I had a working Android app doing the OTA uploads. I don’t particularly like the Android BLE API, and the code I wrote to get OTA working was horrendous. Services, activities, localbroadcastmanagers… [Shudder]…

Anyways, I’m working on putting my Android BLE113 OTA code online alongside the iOS BLE113 OTA code, but for the sake of my sanity and pride, I would like to clean up the code.

To that end, I’m in the process of writing a thin wrapper around the Android BLE API, in order to make handling Bluetooth a little bit simpler (for simple applications only - nothing too complex). With very little creativity, I’m calling my new library… Blueteeth  :)

https://github.com/RobotPajamas/Blueteeth

There is probably nothing there yet, but within the week, I hope to have the first rumblings of a library working.

You can see the initial workings of this library at:

https://github.com/RobotPajamas/ble113-ota-android

I’d like to point out right here and now that, as of today (March 9th), that Android OTA in that repo DOES NOT WORK! I haven’t replaced all the BLE APIs yet, nor have I extracted a lot of that functionality into a SampleDevice (like in the iOS app).

Maybe in a few days or a couple of weeks… Maybe…

Update: May 30, 2016

I’ve finally gotten around to updating the BLE113-OTA app, and in it, I’ve cleaned up a lot of the code (not perfect, but definitely miles better). I’ve also incorporated v0.2.0 of Blueteeth!

Feature Photo credit: mauren veras / Foter / CC BY