0
votes

I have a program running on a Windows PC which sends/receives data over a COM port. The data is transmitted over Bluetooth via a HM10 Bluetooth module.

I'm able to discover the peripheral, connect to it, discover its services and characteristics all successfully. However my issue is with sending data. The central is my iPhone. The PC acts as the peripheral.

First my code.

import CoreBluetooth
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var peripheralNameLabel: UILabel!
    @IBOutlet weak var noOfServicesLabel: UILabel!
    @IBOutlet weak var sendBytesButton: UIButton!
    @IBOutlet weak var sendStringButton: UIButton!
    @IBOutlet weak var responseTextView: UITextView!

    private let serviceUUID = CBUUID(string: "FFE0")
    private var characteristicUUID = CBUUID(string: "FFE1")

    private var manager: CBCentralManager!
    private var peripheral: CBPeripheral!
    private var characteristic: CBCharacteristic!

    override func viewDidLoad() {
        super.viewDidLoad()

        manager = CBCentralManager(delegate: self, queue: nil)
    }

    @IBAction func didTapSendBytesButton(sender: UIButton) {
        let bytes: [UInt8] = [0x35, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
        let data = NSData(bytes: bytes, length: bytes.count)
        peripheral.writeValue(data, forCharacteristic: characteristic, type: .WithResponse)
    }

    @IBAction func didTapSendStringButton(sender: UIButton) {
        let string = "5100000000"
        if let data = string.dataUsingEncoding(NSUTF8StringEncoding) {
            peripheral.writeValue(data, forCharacteristic: characteristic, type: .WithResponse)
        } else {
            sendStringButton.enabled = false
            sendStringButton.setTitle("????", forState: .Normal)
        }
    }

}

extension ViewController: CBCentralManagerDelegate {

    func centralManagerDidUpdateState(central: CBCentralManager) {
        print(#function)

        switch central.state {
        case .Unsupported:
            print("Unsupported")
        case .Unauthorized:
            print("Unauthorized")
        case .PoweredOn:
            print("Powered On")
            navigationItem.title = "Connecting..."
            central.scanForPeripheralsWithServices([serviceUUID], options: nil)
        case .Resetting:
            print("Resetting")
        case .PoweredOff:
            print("Powered Off")
        case .Unknown:
            print("Unknown")
        }
    }

    func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber) {
        print(#function)

        print("Discovered \(peripheral.name) at \(RSSI)")
        peripheralNameLabel.text = peripheral.name

        if peripheral.name == nil || peripheral.name == "" {
            return
        }

        if self.peripheral == nil || self.peripheral.state == .Disconnected {
            self.peripheral = peripheral
            central.connectPeripheral(peripheral, options: nil)

            central.stopScan()
        }
    }

    func centralManager(central: CBCentralManager, didConnectPeripheral peripheral: CBPeripheral) {
        print(#function)
        navigationItem.title = "Connected!"
        sendBytesButton.enabled = true
        sendStringButton.enabled = true

        peripheral.delegate = self
        peripheral.discoverServices([serviceUUID])
    }

    func centralManager(central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: NSError?) {
        self.peripheral = nil
        central.scanForPeripheralsWithServices(nil, options: nil)
    }

    func centralManager(central: CBCentralManager, didFailToConnectPeripheral peripheral: CBPeripheral, error: NSError?) {
        print(#function)
        self.peripheral = nil
    }

}

extension ViewController: CBPeripheralDelegate {

    func peripheral(peripheral: CBPeripheral, didDiscoverServices error: NSError?) {
        print(#function)

        guard let services = peripheral.services else {
            return
        }

        noOfServicesLabel.text = "\(services.count)"

        for service in services {
            print(service.UUID)
            if service.UUID == serviceUUID {
                peripheral.discoverCharacteristics(nil, forService: service)
            }
        }
    }

    func peripheral(peripheral: CBPeripheral, didDiscoverCharacteristicsForService service: CBService, error: NSError?) {
        print(#function)

        guard let characteristics = service.characteristics else {
            return
        }

        for characteristic in characteristics {
            print("characteristic: \(characteristic.UUID)")
            if characteristic.UUID == characteristicUUID {
                self.characteristic = characteristic
                peripheral.setNotifyValue(true, forCharacteristic: characteristic)
            }
        }
    }

    func peripheral(peripheral: CBPeripheral, didWriteValueForCharacteristic characteristic: CBCharacteristic, error: NSError?) {
        print(#function)
        print(error)
    }

    func peripheral(peripheral: CBPeripheral, didUpdateValueForCharacteristic characteristic: CBCharacteristic, error: NSError?) {
        print(#function)

        if characteristic.UUID == characteristicUUID {
            print("Got reply from: \(characteristic.UUID)")
            if let data = characteristic.value, let string = String(data: data, encoding: NSUTF8StringEncoding) {
                responseTextView.text = string
            } else {
                print("No response!")
            }
        }
    }

}

I'm supposed to send a byte array like this,

0x35 0x31 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

to the peripheral and if the peripheral successfully receives it, I get this as a response,

0x43 0x41 0x4E 0x20 0x31 0x31 0x2F 0x35 0x30 0x30 0x0D 0x0A.

How it's supposed to look like.

enter image description here

This is what really happens.

enter image description here

enter image description here

enter image description here

Data packets are broken into pieces when transferred. Therefore I don't get the success response.

Weird part is sometimes, very rarely it actually works! The data gets sent properly (like shown in the very first image) and I receive the success response. But failures occur more often than not. Like 99% of the time.

I tried both ways, sending data as a byte array (didTapSendBytesButton()) and sending it as a string converted (didTapSendStringButton()). Both resulted in the same.

Also tested it with app called Bluetooth Serial. Same result.

I can't figure out why this is happening.

1
You reference both iOS and Windows here- which are you using to send the data? What peripheral are you trying to talk to?Carter
Sorry if I wasn't clear on that. I'll update the answer. The iOS device acts as the central. The PC acts as the peripheral.Isuru
You tested with the Bluetooth Serial app and had the same behavior, doesn't that suggest the issue is happening on the receiver end? What is your code for receiving the data?Carter
Unfortunately I don't have the code for the program running on the PC.Isuru

1 Answers

1
votes

You are sending less than 20 bytes, so the Bluetooth data will be sent in a single transmission.

The problem is on your receiving side, or actually how you have structured your communications.

A serial port can only send or receive a single byte at a time, so even though iOS sends all of the bytes at once (this is a side-effect how how serial ports are emulated over GATT), Windows has to present them to the virtual COM port driver one at a time. Windows has buffering on COM ports so that bytes aren't lost if your program doesn't read fast enough, which is why you see more than one byte per "RX" but there is no concept of a "packet" in the COM driver, so it isn't aware of how many bytes were sent or that it should wait until all have been received and deliver them as one group.

The ultimate answer is that you need to modify your message so that it has some sort of delimiter (even a simple \n) that lets the receiving program know that the end of a message has been received. It can then validate the message and respond appropriately.

Or, if you can't control the receiving program, and that program works with other sending code, then I suspect you have a problem with the data you are sending, because the fact that the serial bytes are split acros multiple RX calls is normal and the receiving program must have been written to handle this