top of page

Qt + Bluetooth Classic + iOS


Purpose

The goal was to make a mobile application using Qt that could be compiled for both android and iOS. The application had to talk to a Bluetooth classic device. Android would communicate with the device via SPP, and iOS would communicate with it via iAP2. According to Qt's Bluetooth overview, the API is capable of interfacing with both Android and iOS. I was able to get the android app working with the examples Qt provided. When I went to write the iOS side, there wasn't any documentation which is why I'm writing this.

One API Doesn't Fit All

The reason the QBluetooth API doesn't work for iOS is because, like most Apple products, Apple has decided to make a lot of things proprietary. For iOS, pairing of devices happens in the OS itself. An app then communicates with that device if the device matches a protocol that the app supports. QBluetooth provides no way of accessing this information. Instead, it all has to be done through Apple's External Accessory Protocol (EAP). I will go through the steps I used for implementing the EAP.

Disclaimer: There is probably a better way to do this. My comfort zone is C and this was my first time using Objective-C, so leave a comment if you disagree with something.

Linking EAP

The first step is to link in the EAP library so things compile correctly. The easiest way to do this is to right click on the project and select "Add Library...". Select "External library". Finally point to the library file and the include path. Here is what my .pro file looked like after those steps:

Add Protocols

A list of protocols that your app supports needs to be added to the .plist file. In the image above it can be seen that I am passing the path to a custom plist file using "QMAKE_INFO_PLIST". I just used a plist found in the EA demo from apple and modified it to match the protocols supported by my accessory.

Getting the Accessory List

We are finally ready to start writing code. The first class I made is called "ExternalAccessory". The purpose of this class is to get a list of connected accessories, check the protocols of each accessory to see if they match the ones the app supports, and open input/output streams if there is a protocol match.

When the class is created in Qt it will be named "externalaccessory.cpp". It needs to be renamed to "externalaccessory.mm". The "mm" extension tells the compiler that this is an Objective-C++ file. This allows for the mixing of Objective-C and C++ in the same file. Any Objective-C file has to be added to "OBJECTIVE_SOURCES" in the .pro file instead of being added to "SOURCES" as usual. See Qt's documentation for more explanation.

The EA library is in Objective-C. That is why we have to mix the two worlds together. I would suggest editing .mm files in xcode because Qt will not recognize any of the Objective-C syntax.

Below is what the constructor looks like for the External Accessory class. After the constructor is called, "connectedAccessories" will contain all of the paired devices. Later, this will be iterated through in order to see if any of the accessories contain protocols matching the ones in our info.plist. I got a lot of this code from Apple's EAP documentation.

Opening Up Streams

The next step is to find an accessory that will work with our app and then open up input and output streams to it. The following code shows how each accessory is looped through along with each accessories supported protocol list.

In order to initialize a EASession, an EAAccessory object and a protocol string are needed. After the code snippet above is executed, we should have both of those objects. Once the session object is initialized, it can be used to open an input and output stream. (See the reference to Apple's stream objects).

Notice the "setDelegate" method being called. Delegates are basically objects that handle callbacks. Below, you'll notice references to "ioDelegate". This is an object of an objective-C class I made called "StreamDelegate". The class (which will be shown later) handles all stream events. In a lot of Apple's examples you'll see the delegate is set to "self". Since "ExternalAccessory" is a C++ class, an instance of itself can not be a delegate. Essentially we need a reference to "self" not a reference to "this" that C++ will give us. This is why a pure objective-C class had to be made.

Writing to the Output Stream

Now that the output stream is open, it is easy to write to it. The function below is used to send data to an external accessory.

Reading From the Input Stream

Reading from the input stream is a little trickier. This is where the delegate that I mentioned earlier comes into play. Whenever data is received, the method "handleEvent" of the "StreamDelegate" class is called. Behind the scenes a "NSStreamEvent" and a "NSStream" are passed into the method. If bytes are available the "NSStreamEvent" will be "NSStreamEventHasBytesAvailable". Data can then be pulled out of the stream.

At line 27 a C++ object "dataHandler" is being used. I had to make another class because I needed a way to get the data back to the higher level C++ code. Basically, the higher layer has a reference to "dataHandler". There is probably a better way to do that part.

Cleanup

Make sure to free up the memory used by the streams once they are no longer in use.

I hope this is helpful to those trying to make a cross-platform Bluetooth app with Qt. If anything is unclear, or could be improved, just leave a comment!

bottom of page