Creating Bluetooth Clients

The MAUtil::BluetoothDiscoverer class can be used to locate nearby devices and find out which Bluetooth services they support. Once you’ve discovered a device and a service you will need to write a client for that service and open a data connection to the server. That's what we will be doing in this tutorial.

Opening Connections

To create a connection to the Bluetooth device that you want to communicate with, you can use the Connection class in the MAUtil library (#include <MAUtil/Connection.h>). The Connection class wraps up common connection commands into one easy-to-use object.

Creating ConnectionListeners

Creating Connection objects is discussed at length in the tutorial Downloading Data from the Internet, but the one thing you do have to do though is to implement ConnectionListener. The ConnectionListener has the following methods you need to implement:

void connectFinished(Connection* conn, int result);
void connRecvFinished(Connection* conn, int result);
void connWriteFinished(Connection* conn, int result);
void connReadFinished(Connection* conn, int result);

When the Connection object reads to writes data, it will inform your ConnectionListener of the result.

//ConnectionListener
void connectFinished(Connection* conn, int result)
{
 lprintfln("Connection established");
}
void connRecvFinished(Connection* conn, int result)
{
 lprintfln("Received %d bytes", result);
}
void connWriteFinished(Connection* conn, int result)
{
 lprintfln("Wrote %d bytes", result);
}
void connReadFinished(Connection* conn, int result)
{
 lprintfln("Read %d bytes", result);
}

To create the Connection, you need to be able to format a URL to the desired Bluetooth server correctly. This is the only real difference between connecting to a Bluetooth service and connecting to an HTTP resource on the Internet.

This tutorial assumes that you’ve either already followed the discovery processes in our tutorial Discovering Devices and Services with Bluetooth. That tutorial explains how to find the service you want to connect to, and introduces the classes BtDevice and BtService.

Formatting Bluetooth URLs

When you want to create a URL to connect to a Bluetooth service, you need the MABtAddr address class from BtDevice and the port number from BtService. The example we are working through today assumes you’ve already got the both a BtDevice and a BtService.

The Bluetooth URL is in the format:

btspp://<device address>:<service port>

The device address is in an MABtAddr object in BtDevice.address. The MABtAddr is a struct that contains byte[6]. Each of these bytes needs to be converted to two-character hex value in array order.

This little function will convert it for you:

void BluetoothClient::formatConnection(char* output, BtDevice& device, int port)
{
	MABtAddr a = device.address;
	sprintf(output, "btspp://%02x%02x%02x%02x%02x%02x:%i",
		a[0], a[1], a[2], a[3], a[4], a[5], port);
}

Reading and Writing to the Service

Once connected, you’ve got the same Connection object as you have for connecting to the Internet. You can read and write into the connection.

In this example, we are going to create a client which is for the Bluetooth service created for the tutorial Creating a Bluetooth Server. In that tutorial, the service simply receives a stream of bytes, counts them, and then returns a running total to the client. Our client is going to connect to that server, send a short string, and then listen for the count to be returned.

To do this, we need to expand the implementation of ConnectionListener we produced earlier. The first stage is to make the connection to the correct URL. This means we need to write a short method to start the connection:

void BluetoothClient::connect(BtDevice& device, BtService& service)
{
	char buffer[100];
	formatConnection(&buffer[0], device, service.port);
	mConn.connect(buffer);
}

We use the formatConnection method from earlier to create the URL, and make a connection. If successful, our connectFinished method will be called:

void BluetoothClient::connectFinished(Connection* conn, int result)
{
 lprintfln("Connection established");
 //Connection finished, start sending data
 mConn.write("123456", 6);
}

Once the connection is open, we can write data into it. As with all Connection objects, the write is asynchronous, so don’t write from local variables as they can go out of scope before the write has completed. Here, we are just writing six bytes representing the characters one to six. Once the write has finished, our connWriteFinished method will be called.

void BluetoothClient::connWriteFinished(Connection* conn, int result)
{
 lprintfln("Wrote %d bytes", result);
 //Listen for response
 mConn.recv(&readBuffer, 63);
}

We know that we’ve written data to the server, so now we can wait for the server to respond with the lenght of the data we’ve sent. There is a char[64] already declared to read the response into. When data has been received, our connRecvFinished method will be called.

void BluetoothClient::connRecvFinished(Connection* conn, int result)
{
 lprintfln("Received %d bytes", result);
 //Received the data
 lprintfln("Server response: %s", readBuffer);
 //Finished
 mConn.close();
}

We can echo the server’s response to the screen, and hopefully it tells us that we’ve sent six bytes. Now that we’ve completed our exchange, we can close the connection. Obviously, this is a simple example to demonstrate the process, but all application level protocols can be implemented in this fashion.

The Completed Client

There is a conversation between the client and the server, but the responses and the actions will depend on the protocol you wish to create. The complete source code follows:

#include <MAUtil/Moblet.h>
#include <MAUtil/Connection.h>
#include <MAUtil/BluetoothDiscovery.h>
#include <conprint.h>
using namespace MAUtil;
class BluetoothClient : public ConnectionListener
{
	private:
		Connection mConn;
		char readBuffer[64];
		void BluetoothClient::formatConnection(char* output, BtDevice& device, int port)
		{
			const byte* a = device.address.a;
			sprintf(output, "btspp://%02x%02x%02x%02x%02x%02x:%i",
				a[0], a[1], a[2], a[3], a[4], a[5], port);
		}
	public:
		BluetoothClient() : mConn(this)
		{}
		~BluetoothClient()
		{
			if(mConn.isOpen())
				mConn.close();
		}
		void BluetoothClient::connect(BtDevice& device, BtService& service)
		{
			char buffer[100];
			formatConnection(&buffer[0], device, service.port);
			mConn.connect(buffer);
		}
		//ConnectionListener
	 void BluetoothClient::connectFinished(Connection* conn, int result)
	 {
	 	lprintfln("Connection established");
	 	//Connection finished, start sending data
	 	mConn.write("123456", 6);
	 }
	 void BluetoothClient::connRecvFinished(Connection* conn, int result)
	 {
	 	lprintfln("Received %d bytes", result);
	 	//Received the data
	 	lprintfln("Server response: %s", readBuffer);
	 	//Finished
	 	mConn.close();
	 }
	 void BluetoothClient::connWriteFinished(Connection* conn, int result)
	 {
	 	lprintfln("Wrote %d bytes", result);
	 	//Listen for response
	 	mConn.recv(&readBuffer, 63);
	 }
	 void BluetoothClient::connReadFinished(Connection* conn, int result)
	 {
	 	lprintfln("Read %d bytes", result);
	 }
};