Search code examples
iosswiftbluetooth-lowenergycore-bluetooth

BLE: image transfer between 2 ios devices using CoreBluetooth


I have 2 iOS devices: one is Peripheral and the other Central. I want that the data would be image.

I have tried with a string value and it is working fine but with image I get this error:

read_user_chunkIDOT:1221: invalid PNG file: no valid iEnd chunk

Also I can see that the bytes are different (<CBCharacteristic: 0x1c40bde20, UUID = 2A38, properties = 0x2, value = (null), notifying = NO> Optional(526 bytes)), they are larger when I get them.

This is the peripheral:

if let img = UIImage(named: "maiden") {
        let data = UIImagePNGRepresentation(img)
        let base64 = data?.base64EncodedData(options: .lineLength64Characters)
        let char = CBMutableCharacteristic(type: CHAR_UUID, properties: [.read], value: base64!, permissions: [.readable])
        let myRoei = CBMutableService(type: RX_UUID, primary: true)
        
        myRoei.characteristics = [char]
        cameraPeripheralManager.add(myRoei)
        cameraPeripheralManager.startAdvertising([CBAdvertisementDataServiceUUIDsKey:[RX_UUID], CBAdvertisementDataLocalNameKey: advertisementData])
    }

This is the Central inside didUpdateValueFor characteristic:

print(characteristic.value as Any)
    switch characteristic.uuid {
    case CHAR_UUID:
        let image = UIImage(data: Data(base64Encoded: characteristic.value!, options: .ignoreUnknownCharacters)!)

        self.imageView.image = image
        _ = bodyLocation(from: characteristic)
    case RX_UUID: break
       // onHeartRateReceived(bpm)
    default:
        print("Unhandled Characteristic UUID: \(characteristic.uuid)")
    }

I would like to know where I am wrong.


Solution

  • I have managed doing so with this code:

    func sendData() {
        if sendingEOM {
            // send it
            let didSend = cameraPeripheralManager?.updateValue(
                "EOM".data(using: String.Encoding.utf8)!,
                for: char!,
                onSubscribedCentrals: nil
            )
            // Did it send?
            if (didSend == true) {
                // It did, so mark it as sent
                sendingEOM = false
                print("Sent: EOM")
            }
            return
        }
        
        // We're not sending an EOM, so we're sending data
        
        // Is there any left to send?
        guard sendDataIndex! < (dataToSend?.count)! else {
            // No data left.  Do nothing
            return
        }
        
        // There's data left, so send until the callback fails, or we're done.
        var didSend = true
        
        while didSend {
            // Make the next chunk
            
            // Work out how big it should be
            var amountToSend = dataToSend!.count - sendDataIndex!;
            
            // Can't be longer than 20 bytes
            if (amountToSend > NOTIFY_MTU) {
                amountToSend = NOTIFY_MTU;
            }
            
            // Copy out the data we want
            let chunk = dataToSend!.withUnsafeBytes{(body: UnsafePointer<UInt8>) in
                return Data(
                    bytes: body + sendDataIndex!,
                    count: amountToSend
                )
            }
            
            // Send it
            didSend = cameraPeripheralManager!.updateValue(
                chunk as Data,
                for: char!,
                onSubscribedCentrals: nil
            )
            
            // If it didn't work, drop out and wait for the callback
            if (!didSend) {
                return
            }
            
            let stringFromData = NSString(
                data: chunk as Data,
                encoding: String.Encoding.utf8.rawValue
            )
            
            print("Sent: \(String(describing: stringFromData))")
            
            // It did send, so update our index
            sendDataIndex! += amountToSend;
            
            // Was it the last one?
            if (sendDataIndex! >= dataToSend!.count) {
                
                // It was - send an EOM
                
                // Set this so if the send fails, we'll send it next time
                sendingEOM = true
                
                // Send it
                let eomSent = cameraPeripheralManager!.updateValue(
                    "EOM".data(using: String.Encoding.utf8)!,
                    for: char!,
                    onSubscribedCentrals: nil
                )
                
                if (eomSent) {
                    // It sent, we're all done
                    sendingEOM = false
                    print("Sent: EOM")
                }
                
                return
            }
        }
    }
    

    The code below is the client side:

    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        print(characteristic.value as Any)
        switch characteristic.uuid {
        case Constants.CHAR_UUID:
            imageView.image = nil
            print("Char value: \(String(describing: characteristic.value))")
            guard error == nil else {
                print("Error discovering services: \(error!.localizedDescription)")
                return
            }
            
            if  let stringFromData = NSString(data: characteristic.value!, encoding: String.Encoding.utf8.rawValue){
                
                if  (stringFromData.isEqual(to:"EOM")){
                    countNumberOfImages += 1
                    imageView.image = UIImage(data: data as Data)
                   // peripheral.setNotifyValue(false, for: characteristic)
                    print("Image number: \(countNumberOfImages)")
    
                    let end = NSDate()   // <<<<<<<<<<   end time
                    print("Total data: \(data.length)")
                    
                    let start = NSDate() // <<<<<<<<<< Start time
                    
                    data.setData(NSData() as Data)
                    //totalData.setData(NSData() as Data)
                   // centralManager?.cancelPeripheralConnection(peripheral)
                    
                } else {
                    // Otherwise, just add the data on to what we already have
                    data.append(characteristic.value!)
                    totalData.append(characteristic.value!)
                    count += 1
                   // print("Times: +\(count)")
                    // Log it
                    print("Received: \(data.length)")
                }
            } else {
                data.append(characteristic.value!)
                totalData.append(characteristic.value!)
                count += 1
              //  print("Times: +\(count)")
                // Log it
                print("Received: \(data.length)")
            }
        default:
            print("Unhandled Characteristic UUID: \(characteristic.uuid)")
        }
    }
    

    Very useful code that helps you break the data into chunks and deliver the last chunk with a string to notify the central device that the transfer finished.