Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to transfer images via Bluetooth (LE) to a desktop application

We are currently trying to implement the transmission of images from a mobile device (in this case an IPhone) to a desktop application. We tried already the Bluetooth Serial plugin which works fine for Android but does not list any devices when scanning for our desktop application.

To cover iOS support (AFAIK iOS only supports BluetoothLE), we reimplemented our desktop application to use BluetoothLE and behave like a peripheral. Also we altered our Ionic application to use BLE plugin.

Now BluetoothLE only supports the transmission of packages with the size of 20 Byte whilst our image is about 500kb big. So we could obviously split our image into chunks and transmit it with the following function (taken from this gist):

function writeLargeData(buffer) {
    console.log('writeLargeData', buffer.byteLength, 'bytes in',MAX_DATA_SEND_SIZE, 'byte chunks.');
    var chunkCount = Math.ceil(buffer.byteLength / MAX_DATA_SEND_SIZE);
    var chunkTotal = chunkCount;
    var index = 0;
    var startTime = new Date();

    var transferComplete = function () {
        console.log("Transfer Complete");
    }

    var sendChunk = function () {
        if (!chunkCount) {
            transferComplete();
            return; // so we don't send an empty buffer
        }

        console.log('Sending data chunk', chunkCount + '.');

        var chunk = buffer.slice(index, index + MAX_DATA_SEND_SIZE);
        index += MAX_DATA_SEND_SIZE;
        chunkCount--;

        ble.write(
            device_id, 
            service_uuid, 
            characteristic_uuid, 
            chunk, 
            sendChunk,         // success callback - call sendChunk() (recursive)
            function(reason) { // error callback
                console.log('Write failed ' + reason);
            }
        )
    }
    // send the first chunk
    sendChunk();
}

Still this would mean for us that we would have to launch about 25k transmissions which I assume will take a long time to complete. Now I wonder why is that the data transmission via Bluetooth is that handicapped.

like image 398
Entertain Avatar asked May 29 '19 22:05

Entertain


People also ask

How do I send files via Bluetooth to my computer?

In Bluetooth & other devices settings, scroll down to Related Settings, select Send or receive files via Bluetooth. In Bluetooth File Transfer, select Send files and choose the phone you want to share to then hit Next. Select Browse to find the file or files to share, then select Open > Next to send it, then Finish.

How do I transfer files over Bluetooth?

Only one file at a time can be transferred over Bluetooth. Select Send To and choose Bluetooth. Select Next and follow the prompts to rename the file, choose the Bluetooth device, and send the file. After several seconds, a notification appears on the receiving device.

How to transfer photos from iPhone to PC via Bluetooth?

Here’s how you can quickly transfer photos from your iPhone to your PC via bluetooth within seconds: Step 1: First, launch the Settings app on your iPhone, go to Bluetooth settings and turn on Bluetooth. Step 2: Next, on your PC, click on the Windows icon > Settings > Devices > Add Bluetooth & other devices.

How do I connect a Bluetooth device to my computer?

This creates a secure connection between your PC and the Bluetooth capable device, allowing you to send and receive files. To pair a device on Windows, open the Bluetooth settings menu ( Settings > Devices > Bluetooth) and press the Add Bluetooth or other device button at the top. In the Add a device menu, click Bluetooth.

How to send a file to a Bluetooth device in Android?

For sending a file to a Bluetooth device write the following code in the Send file_Click event to send the file. The above code will start only the thread for sending a file but you have to write the method which will do the actual work of sending the file. Write the method sendfile as below.


1 Answers

If you want to try out L2CAP your could modify your Central desktop app somehow like this:

private let characteristicUUID = CBUUID(string: CBUUIDL2CAPPSMCharacteristicString)
...

Then advertize and publish a L2CAP channel:

let service = CBMutableService(type: peripheralUUID, primary: true)

let properties: CBCharacteristicProperties = [.read, .indicate]
let permissions: CBAttributePermissions = [.readable]

let characteristic = CBMutableCharacteristic(type: characteristicUUID, properties: properties, value: nil, permissions: permissions)
self.characteristic = characteristic
service.characteristics = [characteristic]

self.manager.add(service)
self.manager.publishL2CAPChannel(withEncryption: false)
let data = [CBAdvertisementDataLocalNameKey : "Peripherial-42", CBAdvertisementDataServiceUUIDsKey: [peripheralUUID]] as [String : Any]
self.manager.startAdvertising(data)

In your

func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) {

respective your

func peripheralManager(_ peripheral: CBPeripheralManager, didPublishL2CAPChannel PSM: CBL2CAPPSM, error: Error?) {

offer the PSM value (= kind of socket handle (UInt16), for Bluetooth stream connections):

let data = withUnsafeBytes(of: PSM) { Data($0) }
if let characteristic = self.characteristic {
    characteristic.value = data
    self.manager.updateValue(data, for: characteristic, onSubscribedCentrals: self.subscribedCentrals)
}

finally in

func peripheralManager(_ peripheral: CBPeripheralManager, didOpen channel: CBL2CAPChannel?, error: Error?) 

open an input stream:

channel.inputStream.delegate = self
channel.inputStream.schedule(in: RunLoop.current, forMode: .default)
channel.inputStream.open()

where the delegate could look something like this:

func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
    switch eventCode {
    case Stream.Event.hasBytesAvailable:
        if let stream = aStream as? InputStream {
             ...
            //buffer is some UnsafeMutablePointer<UInt8>
            let read = stream.read(buffer, maxLength: capacity)
            print("\(read) bytes read")
        }
    case ...
}

iOS app with Central Role

Assuming you have something like that in your iOS code:

func sendImage(imageData: Data) {
    self.manager = CBCentralManager(delegate: self, queue: nil)
    self.imageData = imageData
    self.bytesToWrite = imageData.count
    NSLog("start")
}

then you can modify your peripheral on your iOS client to work with the L2Cap channel like this:

func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
...
    if let characteristicValue = characteristic.value {
        let psm = characteristicValue.withUnsafeBytes {
            $0.load(as: UInt16.self)
        }
        print("using psm \(psm) for l2cap channel!")
        peripheral.openL2CAPChannel(psm)
    }   
}

and as soon as you are notified of the opened channel, open the output stream on it:

func peripheral(_ peripheral: CBPeripheral, didOpen channel: CBL2CAPChannel?, error: Error?) 
 ...
    channel.outputStream.delegate = self.streamDelegate
    channel.outputStream.schedule(in: RunLoop.current, forMode: .default)
    channel.outputStream.open()

Your supplied stream delegate might look like this:

func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
    switch eventCode {
    case Stream.Event.hasSpaceAvailable:
        if let stream = aStream as? OutputStream, let imageData = self.imageData {
            if self.bytesToWrite > 0 {
                let bytesWritten = imageData.withUnsafeBytes {
                    stream.write(
                        $0.advanced(by: totalBytes),
                        maxLength: self.bytesToWrite
                    )
                }
                self.bytesToWrite -= bytesWritten
                self.totalBytes += bytesWritten
                print("\(bytesWritten) bytes written, \(bytesToWrite) remain")
            } else {
                NSLog("finished")
            }
        }
    case ...

There is a cool WWDC video from 2017, What's New in Core Bluetooth, see here https://developer.apple.com/videos/play/wwdc2017/712/

At around 14:45 it starts to discuss how L2Cap channels are working.

At 28:47, the Get the Most out of Core Bluetooth topic starts, in which performance-related things are discussed in detail. That's probably exactly what you're interested in.

Finally, at 37:59 you will see various possible throughputs in kbps. Based on the data shown on the slide, the maximum possible speed with L2CAP + EDL (Extended Data Length) + 15ms interval is 394 kbps.

like image 150
Stephan Schlecht Avatar answered Nov 25 '22 15:11

Stephan Schlecht