Download a stream of data to a peripheral over BLE.

Dependencies:   BLE_API mbed nRF51822

A simple demonstration of downloading a stream onto a peripheral over BLE. There's a corresponding Python script to driver the client.

Files at this revision

API Documentation at this revision

Comitter:
rgrover1
Date:
Mon Aug 18 16:03:22 2014 +0000
Child:
1:d623a5792ce5
Commit message:
An initial commit for the StreamDownloader. We still have the slowdown behaviour.

Changed in this revision

BLE_API.lib Show annotated file Show diff for this revision Revisions of this file
Configuration.h Show annotated file Show diff for this revision Revisions of this file
Logger.h Show annotated file Show diff for this revision Revisions of this file
TransferService.cpp Show annotated file Show diff for this revision Revisions of this file
TransferService.h Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
mbed.bld Show annotated file Show diff for this revision Revisions of this file
nRF51822.lib Show annotated file Show diff for this revision Revisions of this file
sendStream.py Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/BLE_API.lib	Mon Aug 18 16:03:22 2014 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/teams/Bluetooth-Low-Energy/code/BLE_API/#189ff241dae1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Configuration.h	Mon Aug 18 16:03:22 2014 +0000
@@ -0,0 +1,26 @@
+#ifndef _H_CONFIGURATION_H
+#define _H_CONFIGURATION_H
+
+/*
+ * Configuration.h
+ * Prashant Vaibhav, TobyRich GmbH
+ *
+ * This file defines default configuration parameters like device name, connection parameters etc.
+ */
+
+namespace Config
+{
+const uint8_t deviceName[] = "Transfer PRO";
+
+const int advertisingInterval = 160;   // (0.625 ms units)
+
+// default connection parameters conforming to Apple recommendations
+const int minConnectionInterval = 16; // (1.25 ms units)
+const int maxConnectionInterval = 40; // (1.25 ms units)
+const int slaveLatency          = 0;
+const int supervisionTimeout    = 500; // (10 ms units)
+
+const int blockSize = 16; // in bytes
+}
+
+#endif //_H_CONFIGURATION_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Logger.h	Mon Aug 18 16:03:22 2014 +0000
@@ -0,0 +1,13 @@
+#ifndef _H_LOGGER_H
+#define _H_LOGGER_H
+
+#define NEED_CONSOLE_OUTPUT 1 /* Set this if you need debug messages on the console;
+                               * it will have an impact on code-size and power consumption. */
+
+#if NEED_CONSOLE_OUTPUT
+#define DEBUG(...) { printf(__VA_ARGS__); }
+#else
+#define DEBUG(...) /* nothing */
+#endif /* #if NEED_CONSOLE_OUTPUT */
+
+#endif //_H_LOGGER_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TransferService.cpp	Mon Aug 18 16:03:22 2014 +0000
@@ -0,0 +1,172 @@
+#include "TransferService.h"
+#include "Logger.h"
+#include "Configuration.h"
+
+namespace Transfer {
+// Transfer base UUID: ADC710C2-xxxx-4BF5-8244-3CEAAA0F87F5
+#define transfer_UUID(x)   {0xAD, 0xC7, 0x10, 0xC2, (((x) & 0xFF00) >> 8), ((x) & 0xFF), 0x4B, 0xF5, 0x82, 0x44, 0x3C, 0xEA, 0xAA, 0x0F, 0x87, 0xF5}
+
+// UUID byte arrays
+static const uint8_t transferServiceUUID[]     = transfer_UUID(0xACDC);
+static const uint8_t transferFileInfoUUID[]    = transfer_UUID(0xACDF);
+static const uint8_t transferFileBlockUUID[16] = transfer_UUID(0xACE0);
+
+// Storage for the value of the characteristics
+struct fileInfo_t {
+    uint16_t length;
+    uint16_t crc16;
+};
+static struct fileInfo_t            fileInfo;
+struct fileBlock_t {
+    uint16_t blockNumber;
+    uint8_t data[16];
+};
+static struct fileBlock_t           fileBlock;
+
+// Other things needed for operation
+static BLEDevice*                   ble;
+static Timer                        downloadTimer;
+
+static uint16_t expectingBlock = 0;
+
+static bool acceptFile         = true; // additional condition whether to accept a file upload or not, currently always accept
+static bool downloadInProgress = false; // indicates if we are downloading a file from the phone
+
+static GattCharacteristic transferFileInfo(transferFileInfoUUID,
+                                           (uint8_t *)&fileInfo,
+                                           sizeof(fileInfo),
+                                           sizeof(fileInfo),
+                                           GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY);
+
+static GattCharacteristic transferFileBlock(transferFileBlockUUID,
+                                            (uint8_t *)&fileBlock,
+                                            sizeof(fileBlock_t),
+                                            sizeof(fileBlock_t),
+                                            GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE |
+                                            GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE);
+
+static GattCharacteristic *allChars[] = {&transferFileInfo, &transferFileBlock};
+static GattService transferService(transferServiceUUID, allChars, sizeof(allChars) / sizeof(GattCharacteristic *));
+
+void init(BLEDevice &bleDevice)
+{
+    downloadInProgress = false;
+    ble                = &bleDevice;
+    ble->addService(transferService);
+    DEBUG("Added transfer service\r\n");
+}
+
+void reset()
+{
+    // reset internal state on new connection
+    downloadInProgress = false;
+}
+
+const uint8_t *getServiceUUIDp()
+{
+    return transferServiceUUID;
+}
+
+void requestBlock(uint16_t); // prototype declaration
+void sendFileInfo();
+void refuseFile();
+void sendFileDownloadedSuccessfully();
+
+void handleDataWritten(uint16_t handle)
+{
+    if (!ble) {
+        return;
+    }
+
+    if (handle == transferFileInfo.getHandle()) {
+        uint16_t len = sizeof(fileInfo);
+        ble->readCharacteristicValue(handle, (uint8_t *) &fileInfo, &len);
+
+        if ((fileInfo.length == 0) && (fileInfo.crc16 == 0)) {
+            // signal to cancel pending upload
+            downloadInProgress = false;
+            downloadTimer.reset();
+            expectingBlock = 0;
+            DEBUG("Download RESET\r\n");
+            return;
+        }
+
+        DEBUG("Offered file len=%d, crc=0x%04x, acceptFile=%d, downloadInProgress=%d\r\n", fileInfo.length, fileInfo.crc16, acceptFile, downloadInProgress);
+
+        // Now we must decide whether to accept it or not
+        if (acceptFile && !downloadInProgress) {
+            downloadTimer.reset();
+            downloadTimer.start();
+            downloadInProgress = true;
+            requestBlock(0);
+        } else {
+            refuseFile();
+        }
+    } else if (handle == transferFileBlock.getHandle()) {
+        uint16_t len = sizeof(fileBlock);
+        ble->readCharacteristicValue(handle, (uint8_t *) &fileBlock, &len);
+
+        // DEBUG("received blk %u (total %u): ", fileBlock.blockNumber, fileInfo.length / Config::blockSize);
+        // uint8_t byte;
+        // for (int i = 2; i < len; i++) {
+        //     byte = *(((uint8_t*) &fileBlock) + i);
+        //     DEBUG("%02x ", byte);
+        // }
+        // DEBUG("\r\n");
+
+        if (fileBlock.blockNumber != expectingBlock) {
+            DEBUG("Expected blk %d, not %d!\r\n", expectingBlock, fileBlock.blockNumber);
+            requestBlock(expectingBlock);
+            return;
+        } else {
+            // DEBUG("."); // one dot = one successfully received packet
+        }
+
+        if (fileBlock.blockNumber > (fileInfo.length / Config::blockSize)) {
+            DEBUG("Error: block %d is out of range\r\n", fileBlock.blockNumber);
+            return;
+        }
+
+        // "processing" step disabled
+        //uint16_t offset = fileBlock.blockNumber * Config::blockSize;
+        //memcpy(downloadLocation, fileBlock[0].data, Config::blockSize);
+
+        // request next block if needed
+        uint16_t nextBlock = fileBlock.blockNumber + 1;
+        if (nextBlock <= fileInfo.length / Config::blockSize) {
+            requestBlock(nextBlock);
+        } else {
+            sendFileDownloadedSuccessfully();
+        }
+    } else {
+        DEBUG("Got data on unexpected characteristic handle %u!\r\n", handle);
+    }
+}
+
+void requestBlock(uint16_t blockNumber)
+{
+    // Requesting a block by sending notification is disabled for speed
+    //ble->updateCharacteristicValue(transferFileBlocks[0].getHandle(), (uint8_t*) &blockNumber, sizeof(blockNumber), false);
+    //DEBUG("BlockReq %d --> PHONE\r\n", blockNumber);
+    expectingBlock = blockNumber;
+}
+
+void sendFileInfo(uint32_t value)
+{
+    // refusal is indicated by sending a fileInfo with all zeros
+    ble->updateCharacteristicValue(transferFileInfo.getHandle(), (uint8_t *) &value, sizeof(value), false);
+}
+
+void refuseFile()
+{
+    sendFileInfo(0);
+}
+
+void sendFileDownloadedSuccessfully()
+{
+    sendFileInfo(1);
+    downloadInProgress = false;
+    downloadTimer.stop();
+    DEBUG("File transfer took %0.1f sec\r\n", downloadTimer.read());
+}
+} // namespace transfer
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TransferService.h	Mon Aug 18 16:03:22 2014 +0000
@@ -0,0 +1,13 @@
+#ifndef _H_TRANSFERSERVICE_H
+#define _H_TRANSFERSERVICE_H
+
+#include "BLEDevice.h"
+
+namespace Transfer {
+    void init(BLEDevice &ble);
+    void reset();
+    void handleDataWritten(uint16_t handle);
+    const uint8_t* getServiceUUIDp();
+}
+
+#endif //_H_TRANSFERSERVICE_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Mon Aug 18 16:03:22 2014 +0000
@@ -0,0 +1,116 @@
+/* mbed Microcontroller Library
+ * Copyright (c) 2006-2013 ARM Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mbed.h"
+#include "Logger.h"
+#include "Configuration.h"
+#include "BLEDevice.h"
+#include "TransferService.h"
+
+BLEDevice ble;
+
+static Gap::ConnectionParams_t connectionParams;
+
+static const uint8_t *uuidlist = Transfer::getServiceUUIDp();
+static uint8_t uuidlistrev[16];
+static const char *DEVICE_NAME = "SD";
+
+void bluetoothInit();
+
+void disconnectionCallback(Gap::Handle_t handle)
+{
+    DEBUG("Disconnected\n\r");
+    ble.startAdvertising();
+    DEBUG("Advertising...\r\n");
+}
+
+void onConnectionCallback(Gap::Handle_t handle)
+{
+    DEBUG("____[ Connected ]______________________________________\r\n");
+
+    connectionParams.minConnectionInterval        = Config::minConnectionInterval;
+    connectionParams.maxConnectionInterval        = Config::maxConnectionInterval;
+    connectionParams.slaveLatency                 = Config::slaveLatency;
+    connectionParams.connectionSupervisionTimeout = Config::supervisionTimeout;
+    if (ble.updateConnectionParams(handle, &connectionParams) != BLE_ERROR_NONE) {
+        DEBUG("failed to update connection paramter\r\n");
+    } else {
+    }
+
+    Transfer::reset();
+}
+
+void onUpdatesEnabled(Gap::Handle_t handle)
+{
+    DEBUG("Notifications enabled for %d\r\n", handle);
+}
+
+void onDataWritten(Gap::Handle_t handle)
+{
+    // bubble up to services, they will emit callbacks if handle matches
+    Transfer::handleDataWritten(handle);
+}
+
+void bluetoothInit()
+{
+    DEBUG("Bluetooth initialising...\r\n");
+    ble.init();
+    ble.setDeviceName(Config::deviceName);
+    ble.onDisconnection(disconnectionCallback);
+    ble.onConnection(onConnectionCallback);
+    ble.onDataWritten(onDataWritten);
+    ble.onUpdatesEnabled(onUpdatesEnabled);
+
+    // Make sure we use our preferred conn. parameters
+    connectionParams.minConnectionInterval        = Config::minConnectionInterval;
+    connectionParams.maxConnectionInterval        = Config::maxConnectionInterval;
+    connectionParams.slaveLatency                 = Config::slaveLatency;
+    connectionParams.connectionSupervisionTimeout = Config::supervisionTimeout;
+    ble.setPreferredConnectionParams(&connectionParams);
+    ble.getPreferredConnectionParams(&connectionParams);
+    DEBUG("Conn. params => min=%d, max=%d, slave=%d, supervision=%d\r\n",
+          connectionParams.minConnectionInterval,
+          connectionParams.maxConnectionInterval,
+          connectionParams.slaveLatency,
+          connectionParams.connectionSupervisionTimeout);
+
+    // Initialise transfer service
+    Transfer::init(ble);
+
+    /* setup advertising */
+    ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
+    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS, uuidlistrev, 16);
+    ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
+    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
+    ble.setAdvertisingInterval(Config::advertisingInterval);
+    ble.startAdvertising();
+    DEBUG("Ready. Advertising.\r\n");
+}
+
+int main(void)
+{
+    DEBUG("Initialising TRANSFER PRO | Built %s %s\n\r", __DATE__, __TIME__);
+
+    for (int i = 0; i < 16; i++) {
+        uuidlistrev[15 - i] = uuidlist[i];
+    }
+
+    bluetoothInit();
+
+    while (true) {
+        ble.waitForEvent();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed.bld	Mon Aug 18 16:03:22 2014 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/users/mbed_official/code/mbed/builds/9327015d4013
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nRF51822.lib	Mon Aug 18 16:03:22 2014 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/teams/Nordic-Semiconductor/code/nRF51822/#1e5c300cec7f
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sendStream.py	Mon Aug 18 16:03:22 2014 +0000
@@ -0,0 +1,91 @@
+#!/usr/bin/python
+#
+# This is a very simple test driver for demonstrating the StreamDownloader.
+
+import pexpect
+import sys
+import time
+import select
+import re
+import struct
+
+class StreamDownloaderClient(object):
+    """docstring for StreamDownloaderClient"""
+    def __init__(self, bluetoothAddr, length):
+        super(StreamDownloaderClient, self).__init__()
+        self.bluetoothAddr = bluetoothAddr
+
+        self.con = pexpect.spawn('gatttool -b ' + bluetoothAddr + ' --interactive -t random')
+        self.con.expect('\[LE\]>', timeout=600)
+
+        self.con.sendline('connect')
+        self.con.expect('\[CON\]\[.+\]\[LE\]>')
+        print('connected')
+
+        # ensure that we've got the correct primary service available
+        self.con.sendline('primary')
+        self.con.expect('\[CON\]\[.+\]\[LE\]>')
+        self.con.expect('\[CON\]\[.+\]\[LE\]>')
+
+        emptyString = re.compile('^\s*$')
+        for line in self.con.before.decode('utf-8').split('\r\n'):
+            m = re.match('^attr handle: (0x[\dabcdef]{4}).*adc710c2-acdc-4bf5-8244-3ceaaa0f87f5.*', line)
+            if m:
+                break
+        if not m:
+            self.disconnect()
+
+        self.serviceHandle = m.group(1)
+        print('discovered service handle as ' + self.serviceHandle)
+
+        self.discoverCharHandles()
+        self.sendFileInfoBlock(length)
+        for blocknum in range(64):
+            self.sendFileDataBlock(blocknum)
+
+        self.disconnect()
+
+    def discoverCharHandles(self):
+        self.con.sendline('characteristics ' + self.serviceHandle)
+        self.con.expect('\[CON\]\[.+\]\[LE\]>')
+        self.con.expect('\[CON\]\[.+\]\[LE\]>')
+        for line in self.con.before.decode('utf-8').split('\r\n'):
+            uuidRe = re.compile('.* char value handle: (0x[\dabcdef]{4}).*uuid: adc710c2\-(\w{4}).*')
+            m = uuidRe.match(line)
+            if m:
+                if m.group(2) == 'acdf':
+                    self.transferFileInfoHandle = m.group(1)
+                    print("transferFileInfoHandle: " + self.transferFileInfoHandle)
+                elif m.group(2) == 'ace0':
+                    self.transferFileBlockHandle = m.group(1)
+                    print("transferFileBlockHandle: " + self.transferFileBlockHandle)
+
+    def sendFileInfoBlock(self, length):
+        print("setup fileInfoBlock for transferring {} bytes".format(length))
+
+        writeCommand = 'char-write-req ' + self.transferFileInfoHandle + ' '
+        for c in struct.pack('<HH', length, 0):
+            writeCommand = writeCommand + '{:02x}'.format(c)
+        self.con.sendline(writeCommand)
+        self.con.expect('\[CON\]\[.+\]\[LE\]> Characteristic value was written successfully', timeout=200)
+
+    def sendFileDataBlock(self, blocknum):
+        print("will send command for block {}".format(blocknum))
+
+        writeCommand = 'char-write-req ' + self.transferFileBlockHandle + ' '
+        for c in struct.pack('<HBBBBBBBBBBBBBBBB', blocknum, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15):
+            writeCommand = writeCommand + '{:02x}'.format(c)
+        self.con.sendline(writeCommand)
+        self.con.expect('\[CON\]\[.+\]\[LE\]> Characteristic value was written successfully', timeout=200)
+
+    def disconnect(self):
+        self.con.sendline('disconnect')
+        self.con.expect('\[\s+\]\[.+\]\[LE\]>')
+
+def main():
+    bluetoothAddr = "CC:59:FD:D8:3B:A9" # update as necessary
+
+    target = StreamDownloaderClient(bluetoothAddr, 1024)
+
+if __name__ == "__main__":
+    main()