Wednesday, January 20, 2016

Bluetooth (BLE) Robot Remote Control using an iPhone - Part 2

Introduction


If you want to use an iPhone as your controller then Bluetooth is one of the better communication options for directing your robot / drone / whatever.

In Part 2 we will cover the iPhone app required to control your BLE device. The AVA BLE Remote is available as a free download from iTunes. We will first cover how the app works and then how it was coded. For those who don't want to write their own controller, feel free to use ours. We have included the ability to customise the characters sent in response to a command.

We suggest you read Part 1 to understand the overall design but here is a quick recap.

This app has been designed to work with a Bluno board from DFRobot. The Bluno is a combination of an Arduino and a Bluetooth shield. It uses Bluetooth Low Energy (BLE), which is compatible with iOS 7.0+ devices: iPhone 5+,iPad 3+,iPad Mini,and iPod 5th Gen (note that Bluetooth LE capability is required).




Getting Started: The Connection Screen


When the app starts, it will open the connection tab and display any compatible BLE devices which are within range. To connect to a device just tap on its name. If the connection is successful, the word connected will be displayed and the communication indicator "LEDs" will turn from flashing blue to green. You can start a manual search for BLE devices by tapping the search (magnifying glass) icon in the top right of the connection screen. You can stop the search by tapping the disconnect device button (a cross) in the top left.

If you are already connected to a BLE device, tapping the disconnect device button will disconnect you. You can only attach to one BLE device at a time.



Setting a Task


Tasks are a behavioural robotics concept, but of course you can use them as you wish. You don't need to use tasks at all, if you want you can just use the next tab (Control) to direct your robot. Tasks are useful if you want the robot to do more than one thing. In our case, the robot only responds to remote control commands when the remote task has been set.

The idea is that each task is made up of subset of core behaviours. By combining different behaviours you can create a robot task. For example, the default tasks provided with the app are mapped to the following core behaviours in AVA.

Task ID        Name        Behaviours

     0               Status        BID_STOP
     1               Remote     BID_MANUAL
     2               Patrol        BID_AVOID, BID_ESCAPE, BID_POWER
     3               Follow IR BID_PIR_ATTRACT, BID_AVOID, BID_ESCAPE, BID_POWER
     4               Avoid IR   BID_PIR_REPEL, BID_AVOID, BID_ESCAPE, BID_POWER

By combining simple core behaviours you can get emergent complex task following behaviours which degrade gracefully when the robot faces unexpected situations. In our robot, each of the behaviours are set a priority. This priority decides which behaviour takes precedence. If the priorities are the same then the code will deal with them in the order presented (which gives an implied priority). You can refresh your knowledge on behavioural robotics by reading our earlier post on different approaches to robot AI.

Behaviour                        Priority                      Inputs                               Outputs 

BID_STOP                            1                            cliff, collision sensors       left & right motor stop
BID_MANUAL                    2                            BLE remote                       left & right motor controls
BID_ESCAPE                       3                            distance sensors, heading  left & right motor controls
BID_AVOID                         4                            distance sensors                 left & right motor controls
BID_POWER                        5                            batt voltage, homing          left & right motor controls
BID_PIR_ATTRACT           6                            PIR                                     left & right motor controls
BID_PIR_REPEL                 6                            PIR                                     left & right motor controls

The Task tab in the iPhone app allows you to send a tasking message to your Bluno. It is the responsibility of the robot code to assign current behaviours based on the assigned task.

Tapping a task will transmit the tasking ID (e.g. 1) via Bluetooth. Tap the add button (+) to add your own tasks. Tap Edit to delete or rearrange the tasks. Tapping "Default Tasks" will reload the task list that comes with the app.




Controlling your BLE Device


The Control tab acts as a virtual gamepad for your BLE device. The currently selected task is displayed at the top of the screen. Tapping the function keys (F1 to F4) will send the designated character via Bluetooth to your Bluno. Similarly, tapping the directional dPad or stop button will send the character assigned to those buttons. You can change the characters which are transmitted from the key mapping option in the console tab. The default mappings are:

F1    w
F2    x
F3    y
F4    z

Up Arrow         f
Down Arrow    b
Left Arrow       l
Right Arrow     r
Stop                  s

The speed slider will send the selected speed encapsulated with less than and greater than brackets.

Messages received back from the Bluno are displayed at the bottom of the screen.




The Console


The console tab allows you to send any character strings that you wish to the Bluno. Just type the message in the text field and tap Send. Any received messages will be displayed below.

The console tab also contains the system log which records various events, such as device discovery, connection, data transmission and disconnection. You can email the system log by tapping the mail button in the top right of the screen.

Tap the settings (gear icon) button if you wish to change the keys mapped to the Control buttons.




iOS Code


The iOS code is pretty straight forward and made much easier by the Bluno frameworks provided by DFRobot. You will need to include the following classes in your code.

- BLEDevice
- BLEUtility
- DFBlunoDevice
- DFBlunoManager

The download link for the entire Xcode project is provided below, but here are the highlights. Most of the heavy lifting is done by the DFBlunoManager class. This is a shared instance (singleton), so you just access it in your classes using:

 blunoManager = [DFBlunoManager sharedInstance];

The first step is scanning for and connecting with available Bluetooth LE (BLE) devices. Scanning is as simple as:

[blunoManager scan];

The results of the scan are handled by the DFBlunoDelegate, so you will need to have one of your classes conform to this protocol. We used the TabBarViewController as this is nice and central. Within our TabBarViewController, there are a couple of delegate methods which get notified when a scan is performed.

#pragma mark- DFBlunoDelegate

- (void)bleDidUpdateState: (BOOL)bleSupported
{
    NSString *logString;
    NSString *timeStamp = [formatter stringFromDate: [NSDate date]];
    
    if (bleSupported) {
        logString = [NSString stringWithFormat: @"%@: Scanning for BLE devices\n", timeStamp];
        [blunoManager scan];
    } else {
        logString = [NSString stringWithFormat: @"%@: BLE not supported\n", timeStamp];
    }
    
    [self log: logString];
}

- (void)didDiscoverDevice: (DFBlunoDevice *)device
{
    NSString *timeStamp = [formatter stringFromDate: [NSDate date]];
    NSString *logString = [NSString stringWithFormat: @"%@: New device discovered - %@\n", timeStamp, device.identifier];
    
    [self log: logString];
    
    BOOL bRepeat = NO;
    
    for (DFBlunoDevice *bleDevice in appDelegate.deviceArray) {
        if ([bleDevice isEqual: device]) {
            bRepeat = YES;
            break;
        }
    }
    
    if (!bRepeat) {
        [appDelegate.deviceArray addObject: device];
        
        NSString *logString = [NSString stringWithFormat: @"%@: Device added to BLE list\n", timeStamp];
        
        [self log: logString];
    }
    
    [connectionViewController.tbDevices reloadData];
}

We use these to update the table tbDevices in the ConnectionViewController. Once you have found an eligible device, you connect to it using:

[blunoManager connectToDevice: device];

In particular, since we use a table view to list the devices in range we allow the user to connect to a specific device by tapping on the row in the table that corresponds to that device. The device data is stored in deviceArray (which is a property of the application delegate). We also have a property in the application delegate which points to the currently active device (blunoDevice). The relevant code from the ConnectionViewController is:

#pragma mark- Table View Delegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    DFBlunoDevice *device = [appDelegate.deviceArray objectAtIndex:indexPath.row];
    
    if (appDelegate.blunoDevice == nil) {
        appDelegate.blunoDevice = device;
        [blunoManager connectToDevice: appDelegate.blunoDevice];
    } else if ([device isEqual: appDelegate.blunoDevice]) {
        if (!appDelegate.blunoDevice.bReadyToWrite) {
            [blunoManager connectToDevice: appDelegate.blunoDevice];
        }
    } else {
        if (appDelegate.blunoDevice.bReadyToWrite) {
            [blunoManager disconnectToDevice: appDelegate.blunoDevice];
            appDelegate.blunoDevice = nil;
        }
        
        [blunoManager connectToDevice: device];
    }
    
    [self.activityIndicator stopAnimating];
    self.activityIndicator.hidden = YES;
    [tableView deselectRowAtIndexPath: indexPath animated: YES];
}

Once you are connected to the remote device, sending data to it is performed by the following method (found in the application delegate).

#pragma mark - Bluno Communications

- (void)sendString: (NSString *)msg {
    if (self.blunoDevice.bReadyToWrite) {
        NSData *data = [msg dataUsingEncoding: NSUTF8StringEncoding];
        
        [blunoManager writeDataToDevice: data Device: self.blunoDevice];
    }
}

The DFBlunoDelegate will let you know what happens via two methods.

- (void)didWriteData: (DFBlunoDevice*)device
{
    //  NSLog(@"%s", __func__);
    
    NSString *timeStamp = [formatter stringFromDate: [NSDate date]];
    NSString *logString = [NSString stringWithFormat: @"%@: Data written\n", timeStamp];
    
    appDelegate.state = Transmitting;
    [self log: logString];
}

- (void)didReceiveData: (NSData *)data Device: (DFBlunoDevice *)device
{
    //  NSLog(@"%s", __func__);
    
    NSString *timeStamp = [formatter stringFromDate: [NSDate date]];
    NSString *receivedTextString = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
    
    //  NSLog(@"ASCII Rx: %d",[receivedTextString characterAtIndex: 0]);
    
    if (receivedTextString && receivedTextString.length > 0 && ![receivedTextString isEqualToString: @"\n"]) {
        NSString *logString = [NSString stringWithFormat: @"%@: Rx - %@\n", timeStamp, receivedTextString];
        manualViewController.txtReceivedMsg.text = receivedTextString;
        consoleViewController.txtReceivedMsg.text = receivedTextString;
        [self log: logString];
    }
    
    appDelegate.state = Receiving;
    consoleViewController.txtSendMsg.text = @"";
}


In our app we use these to update the log file in the console and print out any data received from the remote device.

Finally, to disconnect a device, just use:

[blunoManager disconnectToDevice: appDelegate.blunoDevice];

You will need to replace appDelegate.blunoDevice with whatever you have called your Bluno Device object.

The complete source code is available at the Reefwing Code Repository.

Bluno Code


The Bluno code was covered in Part 1 of this series. Sample code to allow you to test the app is available from the Reefwing Gist Repository (https://gist.github.com/reefwing/eab05c12b615070732c3).

9 comments:

  1. Hey David,
    I'm looking to send Bluetooth commands from my Raspberry Pi to my Bluno board, is this possible? Thanks in advance!

    ReplyDelete
    Replies
    1. Hi Matt, the Raspberry Pi 3 has Bluetooth 4.1 and BLE so it should be possible. Note on the wiki that Bluno BLE is not compatible with all brands of BLE. I don't know if this includes the Pi. Doing the iOS app was easy because DFRobot provided an API framework to communicate with the Bluno. There is also an API for Android but I couldn't find anything for Linux. You could try one of the Android distros for the Pi (but this isn't straight forward and they all look a bit flaky at the moment) or you will need to write your own library to talk with the Bluno (in Python or whatever your programming language of choice is on the Pi).

      Delete
    2. Looks like the best way to achieve this is to buy the Bluno dongle and plug it into your Pi (https://www.dfrobot.com/product-1220.html). You can then just use it like a serial port - read the comments at the bottom of the page. I haven't tried this myself. Let me know how you go.

      Delete
  2. Thanks for the quick reply. I'll look into that and report back!

    ReplyDelete
  3. Matt - we are using an DFRobot FireBeetle in an application and would like to encrypt the data between the IOS app and the device that we are controlling. Is there a library for this?

    ReplyDelete
  4. Sorry, Matt, my question was mean't for David.

    ReplyDelete
    Replies
    1. Hi EricG - the Bluno framework is just providing the transport layer and allows you to communicate via BLE. It doesn't care what the data is and whether it is encrypted or not. You can encrypt/decrypt the strings at either end using any number of techniques. The limiting factor is probably going to be the capability of the FireBeetle.

      Delete