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.
Revision 0:4eaf82806f06, committed 2014-08-18
- 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
--- /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()