Introduction
My past few articles on Bluetooth and Android , came with some sample source codes to provide an easier start for those interested in this wireless technology. Now I'm surprised to see that another successful mobile OS - Apple's iOS - has the same never-ending issues when it comes to a simple development task - writing a bluetooth application. Starting with iOS development was easy and straight-forward, but Bluetooth seems to be a no can do. Is it really? The purpose is to control the Bluetooth Radio: on, off , set it in discoverable mode, discover nearby devices, and establish a RFCOMM connection. Ideally we would also want access to L2CAP and SDP, but for a start let's take it slow. |
Evaluating the available options
Here's a list with possible approaches one should consider when wanting to write a BLuetooth application for iPhone. The list is sorted having the better/official choice in mind. Workarounds get to the bottom of the list:
1. Enroll in the made for iPhone/iPod/iPad (MFI) program. Details on costs are not available, but this is not for the small development companies, barely selling a few licenses. Some sources indicate costs depending on project, and starting numbers somewhere at 10K USD.
Not really an option IMO, as the costs involved and trouble getting certified are ridiculously high, for something so basic and simple such as building a Bluetooth application.
2. CoreBluetooth framework, currently usable only with Low Energy Bluetooth 4 devices. Since these are not largely spread this is not really an option. You won't be able to connect to standard headsets, keyboards, or other non-Bluetooth 4 devices. Also at the moment of speaking, the iPhone 4S is the only device capable of LE Bluetooth functionality. Again, not an option.
3. GameKit framework, this allows some basic Bluetooth functionality, such as finding nearby devices and establishing a serial communication link, but it only intended for use between iOS devices. So Android plus iPhone via GameKit is a no go. Remember to thank Apple for making it this way. Or not.
4. Private APIs. There is a BluetoothManager framework, in the private APIs, inside the SDK. This can be used to achieve the proposed task, but you won't get your App approved on Appstore, as private API's is not allowed by Apple. Since this is so convenient, and working so nice, almost like the real thing Apple didn't want to include, I will be using it for this article.
5. Jailbreaking and using Ringwald's BTStack. Jailbreaking = rooting = freedom, probably the best way to go . But this places you so far away from Apple's guidelines, and the Appstore itself. So better decide what your project is all about, and who your users will be.
iOS BluetoothManager Framework
Installing the Header files
Get the 4 .h files from here or here.
Browse to your Xcode installation at:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.1.sdk/System/Library/PrivateFrameworks/BluetoothManager.framework
Create a new folder, "Headers", and copy the 4 .h files there:
Adding the new Framework in Xcode
Go to Xcode, click the project in the Navigator, and select "Build Phases". Under "Link binary with Libraries", press the Plus symbol. Select BluetoothManager.framework and click Add.
Using BluetoothManager.framework
In your
- viewDidLoad
do the following:
1. get an handler to and instance of the BluetoothManager service:
-
- // setup bluetooth interface
- btManager = [BluetoothManager sharedInstance];
-
2. register for notifications, for Bluetooth radio change (on/off) and for discovering a new device:
-
- // setup bluetooth notifications
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(deviceDiscovered:)
- name:@"BluetoothDeviceDiscoveredNotification"
- object:nil];
-
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(bluetoothAvailabilityChanged:)
- name:@"BluetoothAvailabilityChangedNotification"
- object:nil];
-
3. Not needed for the purpose of this app, but it really helped me during development, you can set a notification Observer, and get all system notifications, including the Bluetooth notifications or other unknown notifications. You can later register them as shown at step 2).
-
-
- // global notification explorer
- CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(),
- NULL,
- MyCallBack,
- NULL,
- NULL,
- CFNotificationSuspensionBehaviorDeliverImmediately);
-
And the callback function is a simple debug logger, outside viewDidLoad:
-
- // global notification callback
- void MyCallBack (CFNotificationCenterRef center,
- void *observer,
- CFStringRef name,
- const void *object,
- CFDictionaryRef userInfo) {
- NSLog(@"CFN Name:%@ Data:%@", name, userInfo);
- }
-
As I said, this is an extremely useful piece of code.
4. Set the callback for the notifications registered at step 2):
-
- /* Bluetooth notifications */
- - (void)bluetoothAvailabilityChanged:(NSNotification *)notification {
-
- NSLog(@"NOTIFICATION:bluetoothAvailabilityChanged called. BT State: %d", [btManager enabled]);
- }
-
And here is the second one, for discovering devices nearby:
-
- - (void)deviceDiscovered:(NSNotification *) notification {
-
- BluetoothDevice *bt = [notification object];
-
- NSLog(@"NOTIFICATION:deviceDiscovered: %@ %@",bt.name, bt.address);
-
- //create a new list item
- BTListDevItem *item = [[BTListDevItem alloc] initWithName:bt.name description:bt.address type:0 btdev:bt];
-
- //add it to list
- NSMutableArray *tempArray = [[NSMutableArray alloc] initWithArray:btDevItems];
- [tempArray addObject:(item)];
- btDevItems = tempArray;
- [myTableView reloadData];
- }
-
As you can see, the incoming notification itself is a newly discovered device, sent in the form of a BluetoothDevice object. To get it correctly I've used :
-
- BluetoothDevice *bt = [notification object];
-
We store the name, the bluetooth address and the pointer to the BluetoothDevice object for later use. The name and address is used to populate the list view:
5. Turn bluetooth on / off . This is easy: we have two buttons, pressing them results in calling one of the following methods:
-
- /* Interface actions - bt on */
- - (IBAction)bluetoothON {
- NSLog(@"bluetoothON called.");
- [btManager setPowered:YES];
- [btManager setEnabled:YES];
-
- }
-
- /* Interface actions - bt off */
- - (IBAction)bluetoothOFF {
- NSLog(@"bluetoothOFF called.");
- //BluetoothManager *manager = [BluetoothManager sharedInstance];
- [btManager setEnabled:NO];
- [btManager setPowered:NO];
- }
-
6. Triggering bluetooth discovery. I do not enable searching for nearby bluetooth devices by default. Instead, I've added a Scan Button. The logic behind it is as following: check the bluetooth state , if on, start looking for nearby devices (resulting in device found notifications), if bluetooth is off, just throw an error message to inform the user:
-
- /* Interface actions - scan */
- - (IBAction)scanButtonAction {
- if ([btManager enabled]) {
- // clear listview
- [self clearAllList];
- // start scan
- [btManager setDeviceScanningEnabled:YES];
- } else {
- showMessage(@"Error", @"Turn Bluetooth on first!");
- }
- }
-
7. Establishing a connection
There are two approaches here:
7A. Use BluetoothManager's "connectDevice" method (see BluetoothManager.h) . This method taken a single parameter, a string representing the bluetooth address we need to connect to. For some reason this only worked partially, so I abandoned this method in favor of:
7B. Use BluetoothDevice's "connect" . Remember when we saved a pointer to a BluetoothDevice in the Discovery callback function? Now it's so easy using it!
When the user clicks an item in our list, we do the following:
-
- - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
- BTListDevItem *item = (BTListDevItem *)[btDevItems objectAtIndex:indexPath.row];
-
- NSString *message = [NSString stringWithFormat:@"Device %@ [%@]", item.name, item.description];
-
- showMessage(@"Connect to:", message);
-
- [self deviceConnect <img src='http://www.pocketmagic.net/wp-includes/images/smilies/icon_sad.gif' alt=':(' class='wp-smiley' /> indexPath.row)];
-
- }
-
And the deviceConnect function, taking a parameter identifying the index of the device we want to connect to , in our array of devices , is even simpler:
-
- /* Bluetooth connectivity */
- - (void)deviceConnect:(NSInteger)index {
- BTListDevItem *item = (BTListDevItem *)[btDevItems objectAtIndex:index];
- NSLog(@"deviceConnect to %@", item.name);
-
- [item.btdev connect];
- }
-
The connection results
I've tried to connect to a notebook computer equipped with Bluetooth (named Moon-PC) and to a HID Bluetooth Keyboard (named Celluon).
Connecting to the notebook
The notebook exposes the following Bluetooth profiles:
Bluetooth File Transfer Service
Bluetooth Information Synchronization Service
Bluetooth Object Push Service
Bluetooth AV Service
Bluetooth Headset Service
The BluetoothDevice.connect() resulted in the following:
-
- 2012-07-16 19:51:25.364 Bluetooth[706:707] deviceConnect to MOON-PC
- 2012-07-16 19:51:25.366 Bluetooth[706:707] BTM: connecting to device "MOON-PC" C4:46:19:C6:39:D1
- 2012-07-16 19:51:27.743 Bluetooth[706:707] BTM: attempting to connect to service 0x00000010 on device "MOON-PC" C4:46:19:C6:39:D1
- 2012-07-16 19:51:27.751 Bluetooth[706:707] BTM: attempting to connect to service 0x00000008 on device "MOON-PC" C4:46:19:C6:39:D1
- 2012-07-16 19:51:28.994 Bluetooth[706:707] BTM: connection to service 0x00000010 on device "MOON-PC" C4:46:19:C6:39:D1 failed with error 305
- 2012-07-16 19:51:30.286 Bluetooth[706:707] BTM: connection to service 0x00000008 on device "MOON-PC" C4:46:19:C6:39:D1 failed with error 305
-
The Celluon keyboard only exposes the HID profile, and here is the connection result:
-
- 2012-07-16 19:53:45.727 Bluetooth[706:707] deviceConnect to Celluon
- 2012-07-16 19:53:45.732 Bluetooth[706:707] BTM: connecting to device "Celluon " 00:18:E4:27:18:39
- 2012-07-16 19:53:47.204 Bluetooth[706:707] BTM: attempting to connect to service 0x00000020 on device "Celluon " 00:18:E4:27:18:39
- 2012-07-16 19:53:47.216 Bluetooth[706:707] BTM: connection to service 0x00000020 on device "Celluon " 00:18:E4:27:18:39 failed with error 305
-
It is clear that the BluetoothManager identified 2 of the 5 profiles exported by the notebook, and the HID profile exported by the Celluon keyboard. These profiles seem to be coded with the hex identifiers shown in the debug content: 0x00000010, 0x00000008, 0x00000020
Even if the connection fails, it is a good starting point in investigating how to establish a solid connection, and receive data. Since the services are recognized by the BluetoothManager , it is surely possible to use the existing functionality, and the already implemented protocols.
This research work has been performed on an iPod, running OS 5.1 .
You can use this code or any parts of it, ONLY if you provide a visible link within your work/project/article, to this webpage. If you agree, you can download the complete source code: Bluetooth iOS Code.